]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: firewall integration with NFT sets
authorTopi Miettinen <toiwoton@gmail.com>
Sun, 22 May 2022 11:09:06 +0000 (14:09 +0300)
committerTopi Miettinen <topimiettinen@users.noreply.github.com>
Wed, 8 Jun 2022 16:12:25 +0000 (16:12 +0000)
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 }
        }
}
```

13 files changed:
man/systemd.network.xml
src/basic/parse-util.c
src/basic/parse-util.h
src/network/networkd-address.c
src/network/networkd-address.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/shared/firewall-util-nft.c
src/shared/firewall-util.h
src/test/meson.build
src/test/test-nft-set.c [new file with mode: 0644]
test/fuzz/fuzz-network-parser/directives

index da19d98c462848b1725ea5dec1abf3c97875cd6d..d69e63e6b87bd692177a4fa83d6c1f67de486fa5 100644 (file)
@@ -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>
 
index 35fbb5ec6adc7b4988aec5546c8f0352dee51f9e..0c7c562d17e8ee6d4c8d3c6d46e03a1cd8d60b73 100644 (file)
@@ -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;
+}
index f2222dcffb09d86356345e772ae54fbfb90be26d..8530ad1c49766c783ad89225738285e3fb3d4d5c 100644 (file)
@@ -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);
index 227216744b79772dfb6bb17e8559988a3d33d078..406727729907d64517d9282ece059a60757daa4e 100644 (file)
@@ -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);
+}
index e5770155fa37ff78e1ac41fa52097c1e1b04470c..c7746f931c5698f900ce403224292bb21d559ab7 100644 (file)
@@ -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);
index ef5cec1b52d26202d3c2b46b4867e26fcce45460..faa9aa61b47ba6eddb7367fe11787ad137683d0a 100644 (file)
@@ -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)
index a6660d72b94e44b357a8ef77fce4b3854a518a66..494e87e1265351da3fc5e691e26c7a1f7b870fe2 100644 (file)
@@ -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");
 
index 96cd316e0198adeea0cd088498b71180b9cd8abf..6d0748aedcfdb68ea1e71e0030ec91cb27a05355 100644 (file)
@@ -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);
 
index 2f98e791c21cdc7e75e6f2fcd1c028664d60362a..ab4cf18c56092122e48b0a3c378f4769ce5949ee 100644 (file)
 #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;
+}
index 7725a5e58dfd31a20ae5de8dc7fee653ab969be3..3cea144ab947bfaf9d65fc9b0d012856a7f0e62f 100644 (file)
@@ -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);
index cc590f4f3d91522086566f96ab41097382a2fedc..081d79feeed5918c41b3489927331d3d36d50a81 100644 (file)
@@ -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 (file)
index 0000000..df5322b
--- /dev/null
@@ -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;
+}
index 0b850cdfcf0d7302aed3b0a10a6c2caf89f3e0a0..803f0d19695e5ede1e7c267ec20d495b6041db02 100644 (file)
@@ -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=