summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTopi Miettinen <toiwoton@gmail.com>2022-05-22 14:09:06 +0300
committerTopi Miettinen <topimiettinen@users.noreply.github.com>2022-06-08 16:12:25 +0000
commitab51fd9dbdc59f9a37acd8acaea3e9088d092bba (patch)
tree82dbd77f4def265280ea0bb5463cd105f6cd3fcb
parente8f1b50f271f5e28b99182c56eb1b8c704456c34 (diff)
downloadsystemd-ab51fd9dbdc59f9a37acd8acaea3e9088d092bba.tar.gz
network: firewall integration with NFT sets
New directives `NFTSet=`, `IPv4NFTSet=` and `IPv6NFTSet=` provide a method for integrating configuration of dynamic networks into firewall rules with NFT sets. /etc/systemd/network/eth.network ``` [DHCPv4] ... NFTSet=netdev:filter:eth_ipv4_address ``` ``` table netdev filter { set eth_ipv4_address { type ipv4_addr flags interval } chain eth_ingress { type filter hook ingress device "eth0" priority filter; policy drop; ip saddr != @eth_ipv4_address drop accept } } ``` ``` sudo nft list set netdev filter eth_ipv4_address table netdev filter { set eth_ipv4_address { type ipv4_addr flags interval elements = { 10.0.0.0/24 } } } ```
-rw-r--r--man/systemd.network.xml64
-rw-r--r--src/basic/parse-util.c35
-rw-r--r--src/basic/parse-util.h2
-rw-r--r--src/network/networkd-address.c170
-rw-r--r--src/network/networkd-address.h6
-rw-r--r--src/network/networkd-network-gperf.gperf6
-rw-r--r--src/network/networkd-network.c88
-rw-r--r--src/network/networkd-network.h13
-rw-r--r--src/shared/firewall-util-nft.c297
-rw-r--r--src/shared/firewall-util.h40
-rw-r--r--src/test/meson.build3
-rw-r--r--src/test/test-nft-set.c69
-rw-r--r--test/fuzz/fuzz-network-parser/directives6
13 files changed, 792 insertions, 7 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index da19d98c46..d69e63e6b8 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1141,6 +1141,39 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
and the reverse operation when the IPv4 address is deconfigured.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>IPv4NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
+ <term><varname>IPv6NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
+ <listitem>
+ <para>These settings provide a method for integrating dynamic network configuration into firewall
+ rules with NFT sets. These options expect a whitespace separated list of NFT set definitions. Each
+ definition consists of a colon-separated tuple of NFT address family (one of
+ <literal>arp</literal>, <literal>bridge</literal>, <literal>inet</literal>, <literal>ip</literal>,
+ <literal>ip6</literal>, or <literal>netdev</literal>), table name and set name. The names of tables
+ and sets must conform to lexical restrictions of NFT table names. When an interface is configured
+ with IP addresses, the addresses and subnetwork masks will be appended to the NFT sets. They will
+ be removed when the interface is deconfigured. Failures to manage the sets will be ignored.</para>
+
+ <para>Example:
+ <programlisting>[Address]
+IPv4NFTSet=netdev:filter:eth_ipv4_address
+IPv6NFTSet=netdev:filter:eth_ipv6_address</programlisting>
+ Corresponding NFT rules:
+ <programlisting>table netdev filter {
+ set eth_ipv4_address {
+ type ipv4_addr
+ flags interval
+ }
+ chain eth_ingress {
+ type filter hook ingress device "eth0" priority filter; policy drop;
+ ip daddr != @eth_ipv4_address drop
+ accept
+ }
+}</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -2089,6 +2122,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>NFTSet=</varname></term>
+ <listitem>
+ <para>As in [Address] section. The type in NFT set definition must be
+ <literal>ipv4_addr</literal>.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -2208,6 +2249,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>NFTSet=</varname></term>
+ <listitem>
+ <para>As in [DHCPv4] section. The type in NFT set definition must be
+ <literal>ipv6_addr</literal>.</para>
+ </listitem>
+ </varlistentry>
+
<!-- How to communicate with the server -->
<varlistentry>
@@ -2311,6 +2360,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>NFTSet=</varname></term>
+ <listitem>
+ <para>As in [DHCPv6] section. The type in NFT set definition must be
+ <literal>ipv6_addr</literal>.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -2575,6 +2632,13 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>NFTSet=</varname></term>
+ <listitem>
+ <para>As in [DHCPv6] section. The type in NFT set definition must be
+ <literal>ipv6_addr</literal>.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 35fbb5ec6a..0c7c562d17 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -750,3 +750,38 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
return store_loadavg_fixed_point(i, f, ret);
}
+
+static bool nft_first_char_bad(const char c) {
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z'))
+ return false;
+ return true;
+}
+
+static bool nft_next_char_bad(const char c) {
+ if ((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '/' || c == '\\' || c == '_' || c == '.')
+ return false;
+ return true;
+}
+
+/* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and
+ * https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */
+bool nft_identifier_bad(const char *id) {
+ assert(id);
+
+ size_t len;
+ len = strlen(id);
+ if (len == 0 || len > 31)
+ return true;
+
+ if (nft_first_char_bad(id[0]))
+ return true;
+
+ for (size_t i = 1; i < len; i++)
+ if (nft_next_char_bad(id[i]))
+ return true;
+ return false;
+}
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index f2222dcffb..8530ad1c49 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -146,3 +146,5 @@ int parse_oom_score_adjust(const char *s, int *ret);
* to a loadavg_t. */
int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret);
int parse_loadavg_fixed_point(const char *s, loadavg_t *ret);
+
+bool nft_identifier_bad(const char *id);
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 227216744b..4067277299 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -139,6 +139,8 @@ Address *address_free(Address *address) {
config_section_free(address->section);
free(address->label);
set_free(address->netlabels);
+ nft_set_context_free_many(address->ipv4_nft_set_context, &address->n_ipv4_nft_set_contexts);
+ nft_set_context_free_many(address->ipv6_nft_set_context, &address->n_ipv6_nft_set_contexts);
return mfree(address);
}
@@ -450,6 +452,102 @@ static int address_set_masquerade(Address *address, bool add) {
return 0;
}
+static void address_add_nft_set_context(const Address *address, const NFTSetContext *nft_set_context, size_t n_nft_set_contexts) {
+ assert(address);
+
+ for (size_t i = 0; i < n_nft_set_contexts; i++) {
+ int r;
+
+ r = nft_set_element_add_in_addr(&nft_set_context[i], address->family,
+ &address->in_addr, address->prefixlen);
+ if (r < 0) {
+ _cleanup_free_ char *addr_str = NULL;
+
+ (void) in_addr_prefix_to_string(address->family, &address->in_addr, address->prefixlen, &addr_str);
+
+ log_warning_errno(r, "Adding NFT family %s table %s set %s for IP address %s failed, ignoring",
+ nfproto_to_string(nft_set_context[i].nfproto),
+ nft_set_context[i].table,
+ nft_set_context[i].set,
+ strna(addr_str));
+ }
+ }
+}
+
+static void address_del_nft_set_context(const Address *address, const NFTSetContext *nft_set_context, size_t n_nft_set_contexts) {
+ assert(address);
+
+ for (size_t i = 0; i < n_nft_set_contexts; i++) {
+ int r;
+
+ r = nft_set_element_del_in_addr(&nft_set_context[i], address->family,
+ &address->in_addr, address->prefixlen);
+ if (r < 0) {
+ _cleanup_free_ char *addr_str = NULL;
+
+ (void) in_addr_prefix_to_string(address->family, &address->in_addr, address->prefixlen, &addr_str);
+
+ log_warning_errno(r, "Deleting NFT family %s table %s set %s for IP address %s failed, ignoring",
+ nfproto_to_string(nft_set_context[i].nfproto),
+ nft_set_context[i].table,
+ nft_set_context[i].set,
+ strna(addr_str));
+ }
+ }
+}
+
+static void address_add_nft_set(const Address *address) {
+ assert(address);
+ assert(address->link);
+
+ if (!address->link->network || !IN_SET(address->family, AF_INET, AF_INET6))
+ return;
+
+ switch (address->source) {
+ case NETWORK_CONFIG_SOURCE_DHCP4:
+ return address_add_nft_set_context(address, address->link->network->dhcp_nft_set_context, address->link->network->n_dhcp_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_DHCP6:
+ return address_add_nft_set_context(address, address->link->network->dhcp6_nft_set_context, address->link->network->n_dhcp6_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_DHCP_PD:
+ return address_add_nft_set_context(address, address->link->network->dhcp_pd_nft_set_context, address->link->network->n_dhcp_pd_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_NDISC:
+ return address_add_nft_set_context(address, address->link->network->ndisc_nft_set_context, address->link->network->n_ndisc_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_STATIC:
+ if (address->family == AF_INET)
+ return address_add_nft_set_context(address, address->ipv4_nft_set_context, address->n_ipv4_nft_set_contexts);
+ else
+ return address_add_nft_set_context(address, address->ipv6_nft_set_context, address->n_ipv6_nft_set_contexts);
+ default:
+ return;
+ }
+}
+
+static void address_del_nft_set(const Address *address) {
+ assert(address);
+ assert(address->link);
+
+ if (!address->link->network || !IN_SET(address->family, AF_INET, AF_INET6))
+ return;
+
+ switch (address->source) {
+ case NETWORK_CONFIG_SOURCE_DHCP4:
+ return address_del_nft_set_context(address, address->link->network->dhcp_nft_set_context, address->link->network->n_dhcp_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_DHCP6:
+ return address_del_nft_set_context(address, address->link->network->dhcp6_nft_set_context, address->link->network->n_dhcp6_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_DHCP_PD:
+ return address_del_nft_set_context(address, address->link->network->dhcp_pd_nft_set_context, address->link->network->n_dhcp_pd_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_NDISC:
+ return address_del_nft_set_context(address, address->link->network->ndisc_nft_set_context, address->link->network->n_ndisc_nft_set_contexts);
+ case NETWORK_CONFIG_SOURCE_STATIC:
+ if (address->family == AF_INET)
+ return address_del_nft_set_context(address, address->ipv4_nft_set_context, address->n_ipv4_nft_set_contexts);
+ else
+ return address_del_nft_set_context(address, address->ipv6_nft_set_context, address->n_ipv6_nft_set_contexts);
+ default:
+ return;
+ }
+}
+
static int address_add(Link *link, Address *address) {
int r;
@@ -496,6 +594,8 @@ static int address_update(Address *address) {
address_add_netlabel(address);
+ address_add_nft_set(address);
+
if (address_is_ready(address) && address->callback) {
r = address->callback(address);
if (r < 0)
@@ -522,6 +622,8 @@ static int address_drop(Address *address) {
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m");
+ address_del_nft_set(address);
+
address_del_netlabel(address);
if (address->state == 0)
@@ -2084,3 +2186,71 @@ int network_drop_invalid_addresses(Network *network) {
return 0;
}
+
+int config_parse_address_ipv4_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+ assert(network);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate new address, ignoring assignment: %m");
+ return 0;
+ }
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->ipv4_nft_set_context, &n->n_ipv4_nft_set_contexts);
+}
+
+int config_parse_address_ipv6_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(address_free_or_set_invalidp) Address *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+ assert(network);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate new address, ignoring assignment: %m");
+ return 0;
+ }
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->ipv6_nft_set_context, &n->n_ipv6_nft_set_contexts);
+}
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index e5770155fa..c7746f931c 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -8,6 +8,7 @@
#include "sd-ipv4acd.h"
#include "conf-parser.h"
+#include "firewall-util.h"
#include "in-addr-util.h"
#include "networkd-link.h"
#include "networkd-util.h"
@@ -64,6 +65,9 @@ struct Address {
/* NetLabel */
Set *netlabels;
+
+ NFTSetContext *ipv4_nft_set_context, *ipv6_nft_set_context;
+ size_t n_ipv4_nft_set_contexts, n_ipv6_nft_set_contexts;
};
const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) _warn_unused_result_;
@@ -139,3 +143,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_address_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_address_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection);
CONFIG_PARSER_PROTOTYPE(config_parse_address_netlabel);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_ipv4_nft_set_context);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_ipv6_nft_set_context);
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index ef5cec1b52..faa9aa61b4 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -158,6 +158,8 @@ Address.DuplicateAddressDetection, config_parse_duplicate_address_dete
Address.Scope, config_parse_address_scope, 0, 0
Address.RouteMetric, config_parse_address_route_metric, 0, 0
Address.NetLabel, config_parse_address_netlabel, 0, 0
+Address.IPv4NFTSet, config_parse_address_ipv4_nft_set_context, 0, 0
+Address.IPv6NFTSet, config_parse_address_ipv6_nft_set_context, 0, 0
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
@@ -246,6 +248,7 @@ DHCPv4.RouteMTUBytes, config_parse_mtu,
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
DHCPv4.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp_netlabels)
+DHCPv4.NFTSet, config_parse_dhcp_nft_set_context, 0, 0
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
@@ -264,6 +267,7 @@ DHCPv6.IAID, config_parse_iaid,
DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
DHCPv6.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp6_netlabels)
+DHCPv6.NFTSet, config_parse_dhcp6_nft_set_context, 0, 0
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@@ -282,6 +286,7 @@ IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes,
IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix)
IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens)
IPv6AcceptRA.NetLabel, config_parse_netlabel, 0, offsetof(Network, ndisc_netlabels)
+IPv6AcceptRA.NFTSet, config_parse_ndisc_nft_set_context, 0, 0
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
@@ -349,6 +354,7 @@ DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool,
DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens)
DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric)
DHCPPrefixDelegation.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp_pd_netlabels)
+DHCPPrefixDelegation.NFTSet, config_parse_dhcp_pd_nft_set_context, 0, 0
IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec)
IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index a6660d72b9..494e87e126 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -690,6 +690,8 @@ static Network *network_free(Network *network) {
strv_free(network->dhcp6_vendor_class);
set_free(network->dhcp_netlabels);
set_free(network->dhcp6_netlabels);
+ nft_set_context_free_many(network->dhcp_nft_set_context, &network->n_dhcp_nft_set_contexts);
+ nft_set_context_free_many(network->dhcp6_nft_set_context, &network->n_dhcp6_nft_set_contexts);
strv_free(network->ntp);
for (unsigned i = 0; i < network->n_dns; i++)
@@ -758,6 +760,8 @@ static Network *network_free(Network *network) {
set_free(network->ndisc_tokens);
set_free(network->dhcp_pd_netlabels);
set_free(network->ndisc_netlabels);
+ nft_set_context_free_many(network->dhcp_pd_nft_set_context, &network->n_dhcp_pd_nft_set_contexts);
+ nft_set_context_free_many(network->ndisc_nft_set_context, &network->n_ndisc_nft_set_contexts);
return mfree(network);
}
@@ -1302,6 +1306,90 @@ int config_parse_ignore_carrier_loss(
return 0;
}
+int config_parse_dhcp_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(network);
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp_nft_set_context, &network->n_dhcp_nft_set_contexts);
+}
+
+int config_parse_dhcp6_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(network);
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp6_nft_set_context, &network->n_dhcp6_nft_set_contexts);
+}
+
+int config_parse_dhcp_pd_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(network);
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp_pd_nft_set_context, &network->n_dhcp_pd_nft_set_contexts);
+}
+
+int config_parse_ndisc_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(network);
+
+ return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->ndisc_nft_set_context, &network->n_ndisc_nft_set_contexts);
+}
+
DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily,
"Failed to parse RequiredFamilyForOnline= setting");
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 96cd316e01..6d0748aedc 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -10,6 +10,7 @@
#include "bridge.h"
#include "condition.h"
#include "conf-parser.h"
+#include "firewall-util.h"
#include "hashmap.h"
#include "ipoib.h"
#include "net-condition.h"
@@ -156,6 +157,8 @@ struct Network {
OrderedHashmap *dhcp_client_send_options;
OrderedHashmap *dhcp_client_send_vendor_options;
Set *dhcp_netlabels;
+ NFTSetContext *dhcp_nft_set_context;
+ size_t n_dhcp_nft_set_contexts;
/* DHCPv6 Client support */
bool dhcp6_use_address;
@@ -181,6 +184,8 @@ struct Network {
OrderedHashmap *dhcp6_client_send_vendor_options;
Set *dhcp6_request_options;
Set *dhcp6_netlabels;
+ NFTSetContext *dhcp6_nft_set_context;
+ size_t n_dhcp6_nft_set_contexts;
/* DHCP Server Support */
bool dhcp_server;
@@ -238,6 +243,8 @@ struct Network {
int dhcp_pd_uplink_index;
char *dhcp_pd_uplink_name;
Set *dhcp_pd_netlabels;
+ NFTSetContext *dhcp_pd_nft_set_context;
+ size_t n_dhcp_pd_nft_set_contexts;
/* Bridge Support */
int use_bpdu;
@@ -323,6 +330,8 @@ struct Network {
Set *ndisc_allow_listed_route_prefix;
Set *ndisc_tokens;
Set *ndisc_netlabels;
+ NFTSetContext *ndisc_nft_set_context;
+ size_t n_ndisc_nft_set_contexts;
/* LLDP support */
LLDPMode lldp_mode; /* LLDP reception */
@@ -388,6 +397,10 @@ CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
CONFIG_PARSER_PROTOTYPE(config_parse_activation_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_link_group);
CONFIG_PARSER_PROTOTYPE(config_parse_ignore_carrier_loss);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_nft_set_context);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_nft_set_context);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_nft_set_context);
+CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_nft_set_context);
const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c
index 2f98e791c2..ab4cf18c56 100644
--- a/src/shared/firewall-util-nft.c
+++ b/src/shared/firewall-util-nft.c
@@ -14,11 +14,13 @@
#include "sd-netlink.h"
#include "alloc-util.h"
+#include "extract-word.h"
#include "firewall-util.h"
#include "firewall-util-private.h"
#include "in-addr-util.h"
#include "macro.h"
#include "socket-util.h"
+#include "string-table.h"
#include "time-util.h"
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
@@ -848,9 +850,12 @@ static int nft_message_add_setelem_ip6range(
#define NFT_MASQ_MSGS 3
-static int fw_nftables_add_masquerade_internal(
- FirewallContext *ctx,
+static int nft_set_element_op_in_addr(
+ sd_netlink *nfnl,
+ const char *table,
+ const char *set,
bool add,
+ int nfproto,
int af,
const union in_addr_union *source,
unsigned int source_prefixlen) {
@@ -865,14 +870,14 @@ static int fw_nftables_add_masquerade_internal(
if (af == AF_INET6 && source_prefixlen < 8)
return -EINVAL;
- r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
+ r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
if (r < 0)
return r;
tsize = 1;
if (add)
- r = sd_nfnl_nft_message_new_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
+ r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
else
- r = sd_nfnl_nft_message_del_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
+ r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
if (r < 0)
goto out_unref;
@@ -885,12 +890,12 @@ static int fw_nftables_add_masquerade_internal(
++tsize;
assert(tsize < NFT_MASQ_MSGS);
- r = sd_nfnl_message_batch_end(ctx->nfnl, &transaction[tsize]);
+ r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
if (r < 0)
return r;
++tsize;
- r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize);
+ r = nfnl_netlink_sendv(nfnl, transaction, tsize);
out_unref:
while (tsize > 0)
@@ -898,6 +903,65 @@ out_unref:
return r < 0 ? r : 0;
}
+static int nft_set_element_op_in_addr_open(
+ bool add,
+ const NFTSetContext *nft_set_context,
+ int af,
+ const union in_addr_union *address,
+ unsigned int prefixlen) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
+ _cleanup_free_ char *addr_str = NULL;
+ int r, nfproto;
+ const char *table, *set;
+
+ assert(nft_set_context);
+ nfproto = nft_set_context->nfproto;
+ table = nft_set_context->table;
+ assert(table);
+ set = nft_set_context->set;
+ assert(set);
+
+ r = sd_nfnl_socket_open(&nfnl);
+ if (r < 0)
+ return r;
+
+ r = nft_set_element_op_in_addr(nfnl, table, set,
+ add, nfproto, af, address, prefixlen);
+
+ (void) in_addr_prefix_to_string(af, address, prefixlen, &addr_str);
+
+ log_debug("%s NFT family %s table %s set %s IP addresss %s", add? "Added" : "Deleted",
+ nfproto_to_string(nfproto), table, set, strna(addr_str));
+
+ return r;
+}
+
+int nft_set_element_add_in_addr(
+ const NFTSetContext *nft_set_context,
+ int af,
+ const union in_addr_union *address,
+ unsigned int prefixlen) {
+ return nft_set_element_op_in_addr_open(true, nft_set_context, af, address, prefixlen);
+}
+
+int nft_set_element_del_in_addr(
+ const NFTSetContext *nft_set_context,
+ int af,
+ const union in_addr_union *address,
+ unsigned int prefixlen) {
+ return nft_set_element_op_in_addr_open(false, nft_set_context, af, address, prefixlen);
+}
+
+static int fw_nftables_add_masquerade_internal(
+ FirewallContext *ctx,
+ bool add,
+ int af,
+ const union in_addr_union *source,
+ unsigned int source_prefixlen) {
+ return nft_set_element_op_in_addr(ctx->nfnl, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
+ add, af, af, source, source_prefixlen);
+}
+
int fw_nftables_add_masquerade(
FirewallContext *ctx,
bool add,
@@ -1071,3 +1135,222 @@ int fw_nftables_add_local_dnat(
/* table created anew; previous address already gone */
return fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, NULL);
}
+
+static const char *const nfproto_table[] = {
+ [NFPROTO_ARP] = "arp",
+ [NFPROTO_BRIDGE] = "bridge",
+ [NFPROTO_INET] = "inet",
+ [NFPROTO_IPV4] = "ip",
+ [NFPROTO_IPV6] = "ip6",
+ [NFPROTO_NETDEV] = "netdev",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(nfproto, int);
+
+#define NFT_SET_MSGS 3
+
+static int nft_set_element_op(bool add, const NFTSetContext *nft_set_context, void *element, size_t element_size) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
+ sd_netlink_message *transaction[NFT_SET_MSGS] = {};
+ _cleanup_free_ uint32_t *serial = NULL;
+ size_t tsize;
+ int r, nfproto;
+ const char *table, *set;
+
+ assert(nft_set_context);
+ nfproto = nft_set_context->nfproto;
+ table = nft_set_context->table;
+ assert(table);
+ set = nft_set_context->set;
+ assert(set);
+ assert(element);
+
+ r = sd_nfnl_socket_open(&nfnl);
+ if (r < 0)
+ return r;
+
+ r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
+ if (r < 0)
+ return r;
+ tsize = 1;
+
+ if (add)
+ r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
+ else
+ r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
+ if (r < 0)
+ goto out_unref;
+
+ r = sd_nfnl_nft_message_add_setelem(transaction[tsize], 0, element, element_size, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_nfnl_nft_message_add_setelem_end(transaction[tsize]);
+ if (r < 0)
+ return r;
+ ++tsize;
+ assert(tsize < ELEMENTSOF(transaction));
+ r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
+ if (r < 0)
+ return r;
+
+ ++tsize;
+ r = sd_netlink_sendv(nfnl, transaction, tsize, &serial);
+
+out_unref:
+ while (tsize > 0)
+ sd_netlink_message_unref(transaction[--tsize]);
+ return r < 0 ? r : 0;
+}
+
+int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
+ int r;
+
+ assert(nft_set_context);
+ r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
+ if (r == 0)
+ log_debug("Added NFT family %s table %s set %s element %d",
+ nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
+ return r;
+}
+
+int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
+ int r;
+
+ assert(nft_set_context);
+ r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
+ if (r == 0)
+ log_debug("Deleted NFT family %s table %s set %s element %d",
+ nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
+ return r;
+}
+
+int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
+ int r;
+
+ assert(nft_set_context);
+ r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
+ if (r == 0)
+ log_debug("Added NFT family %s table %s set %s element %"PRIu64,
+ nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
+ return r;
+}
+
+int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
+ int r;
+
+ assert(nft_set_context);
+ r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
+ if (r == 0)
+ log_debug("Deleted NFT family %s table %s set %s element %"PRIu64,
+ nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
+ return r;
+}
+
+NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n) {
+ assert(n);
+ assert(s || *n == 0);
+
+ for (size_t i = 0; i < *n; i++) {
+ free(s[i].table);
+ free(s[i].set);
+ }
+
+ free(s);
+ *n = 0;
+ return NULL;
+}
+
+int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set) {
+ _cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
+ assert(s);
+ assert(n);
+
+ table_dup = strdup(table);
+ if (!table_dup)
+ return -ENOMEM;
+
+ set_dup = strdup(set);
+ if (!set_dup)
+ return -ENOMEM;
+
+ NFTSetContext *c;
+ c = reallocarray(*s, *n + 1, sizeof(NFTSetContext));
+ if (!c)
+ return -ENOMEM;
+
+ *s = c;
+
+ c[(*n) ++] = (NFTSetContext) {
+ .nfproto = nfproto,
+ .table = TAKE_PTR(table_dup),
+ .set = TAKE_PTR(set_dup),
+ };
+
+ return 0;
+}
+
+int config_parse_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ NFTSetContext **nft_set_context,
+ size_t *n) {
+ _cleanup_free_ char *family_str = NULL, *table = NULL, *set = NULL;
+ int nfproto, r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(nft_set_context);
+
+ if (isempty(rvalue)) {
+ nft_set_context_free_many(*nft_set_context, n);
+
+ return 0;
+ }
+
+ for (const char *p = rvalue;;) {
+ r = extract_many_words(&p, ":" WHITESPACE, EXTRACT_CUNESCAPE, &family_str, &table, &set, NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r == 0)
+ return 0;
+ if (r != 3) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse IPvxNFT set, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ nfproto = nfproto_from_string(family_str);
+ if (nfproto < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family, ignoring: %s", family_str);
+ return 0;
+ }
+
+ if (nft_identifier_bad(table))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", table);
+
+ if (nft_identifier_bad(set))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", set);
+
+ NFTSetContext *c;
+ c = reallocarray(*nft_set_context, *n + 1, sizeof(NFTSetContext));
+ if (!c)
+ return -ENOMEM;
+
+ *nft_set_context = c;
+
+ c[(*n) ++] = (NFTSetContext) {
+ .nfproto = nfproto,
+ .table = TAKE_PTR(table),
+ .set = TAKE_PTR(set),
+ };
+ }
+
+ return 0;
+}
diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h
index 7725a5e58d..3cea144ab9 100644
--- a/src/shared/firewall-util.h
+++ b/src/shared/firewall-util.h
@@ -29,3 +29,43 @@ int fw_add_local_dnat(
const union in_addr_union *remote,
uint16_t remote_port,
const union in_addr_union *previous_remote);
+
+struct NFTSetContext {
+ int nfproto;
+ char *table;
+ char *set;
+};
+typedef struct NFTSetContext NFTSetContext;
+
+int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set);
+NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n);
+int config_parse_nft_set_context(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ NFTSetContext **nft_set_context,
+ size_t *n);
+
+const char *nfproto_to_string(int i) _const_;
+int nfproto_from_string(const char *s) _pure_;
+
+int nft_set_element_add_in_addr(
+ const NFTSetContext *nft_set_context,
+ int af,
+ const union in_addr_union *address,
+ unsigned int prefixlen);
+int nft_set_element_del_in_addr(
+ const NFTSetContext *nft_set_context,
+ int af,
+ const union in_addr_union *address,
+ unsigned int prefixlen);
+
+int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element);
+int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element);
+int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element);
+int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element);
diff --git a/src/test/meson.build b/src/test/meson.build
index cc590f4f3d..081d79feee 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -672,6 +672,9 @@ tests += [
[files('test-hmac.c')],
[files('test-sha256.c')],
+
+ [files('test-nft-set.c'),
+ [], [], [], '', 'manual'],
]
############################################################
diff --git a/src/test/test-nft-set.c b/src/test/test-nft-set.c
new file mode 100644
index 0000000000..df5322b4b2
--- /dev/null
+++ b/src/test/test-nft-set.c
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <assert.h>
+#include <unistd.h>
+
+#include "firewall-util.h"
+#include "in-addr-util.h"
+#include "log.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "tests.h"
+
+int main(int argc, char **argv) {
+ int r;
+
+ assert_se(argc == 7);
+
+ test_setup_logging(LOG_DEBUG);
+
+ if (getuid() != 0)
+ return log_tests_skipped("not root");
+
+ int nfproto;
+ nfproto = nfproto_from_string(argv[2]);
+ assert_se(nfproto > 0);
+
+ const NFTSetContext nft_set_context = {
+ .nfproto = nfproto,
+ .table = argv[3],
+ .set = argv[4],
+ };
+
+ if (streq(argv[5], "uint32")) {
+ uint32_t element;
+ r = safe_atou32(argv[6], &element);
+ assert_se(r == 0);
+
+ if (streq(argv[1], "add"))
+ r = nft_set_element_add_uint32(&nft_set_context, element);
+ else
+ r = nft_set_element_del_uint32(&nft_set_context, element);
+ assert_se(r == 0);
+ } else if (streq(argv[5], "uint64")) {
+ uint64_t element;
+ r = safe_atou64(argv[6], &element);
+ assert_se(r == 0);
+
+ if (streq(argv[1], "add"))
+ r = nft_set_element_add_uint64(&nft_set_context, element);
+ else
+ r = nft_set_element_del_uint64(&nft_set_context, element);
+ assert_se(r == 0);
+ } else {
+ union in_addr_union addr;
+ int af;
+ unsigned char prefixlen;
+
+ r = in_addr_prefix_from_string_auto(argv[6], &af, &addr, &prefixlen);
+ assert_se(r == 0);
+
+ if (streq(argv[1], "add"))
+ r = nft_set_element_add_in_addr(&nft_set_context, af, &addr, prefixlen);
+ else
+ r = nft_set_element_del_in_addr(&nft_set_context, af, &addr, prefixlen);
+ assert_se(r == 0);
+ }
+
+ return 0;
+}
diff --git a/test/fuzz/fuzz-network-parser/directives b/test/fuzz/fuzz-network-parser/directives
index 0b850cdfcf..803f0d1969 100644
--- a/test/fuzz/fuzz-network-parser/directives
+++ b/test/fuzz/fuzz-network-parser/directives
@@ -132,6 +132,7 @@ RouteMTUBytes=
FallbackLeaseLifetimeSec=
Use6RD=
NetLabel=
+NFTSet=
[DHCPv6]
UseAddress=
UseDelegatedPrefix=
@@ -154,6 +155,7 @@ IAID=
DUIDType=
DUIDRawData=
NetLabel=
+NFTSet=
[DHCPv6PrefixDelegation]
SubnetId=
Announce=
@@ -171,6 +173,7 @@ ManageTemporaryAddress=
Token=
RouteMetric=
NetLabel=
+NFTSet=
[Route]
Destination=
Protocol=
@@ -257,6 +260,8 @@ DHCPv6PrefixDelegation=
DHCPPrefixDelegation=
BatmanAdvanced=
IPoIB=
+IPv4NFTSet=
+IPv6NFTSet=
[IPv6Prefix]
Prefix=
OnLink=
@@ -348,6 +353,7 @@ Managed=
OtherInformation=
UplinkInterface=
NetLabel=
+NFTSet=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=