]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #24570 from topimiettinen/nft-sets-v2
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 1 Sep 2023 11:13:39 +0000 (20:13 +0900)
committerGitHub <noreply@github.com>
Fri, 1 Sep 2023 11:13:39 +0000 (20:13 +0900)
network: firewall integration with NFT sets

21 files changed:
man/systemd.network.xml
src/basic/parse-util.c
src/basic/parse-util.h
src/libsystemd/sd-netlink/netlink-internal.h
src/libsystemd/sd-netlink/netlink-message-nfnl.c
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-private.h
src/shared/firewall-util.c
src/shared/firewall-util.h
src/test/meson.build
src/test/test-nft-set.c [new file with mode: 0644]
test/test-network/conf/25-address-static.network
test/test-network/conf/25-dhcp-client-ipv4-only.network
test/test-network/conf/25-dhcp-pd-downstream-dummy98.network
test/test-network/conf/25-ipv6-prefix-veth.network
test/test-network/systemd-networkd-tests.py

index 8456eb71b682a173af6fee84a7deefcc717dd9cf..9a44db6d30861ba7813e5cdcc044243255499bee 100644 (file)
@@ -1324,6 +1324,86 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
           <xi:include href="version-info.xml" xpointer="v252"/>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>NFTSet=</varname><replaceable>source</replaceable>:<replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
+        <listitem>
+          <para>This setting provides a method for integrating network configuration into firewall rules with
+          <ulink url="https://netfilter.org/projects/nftables/index.html">NFT</ulink> sets. The benefit of
+          using the setting is that static network configuration (or dynamically obtained network addresses,
+          see similar directives in other sections) can be used in firewall rules with the indirection of NFT
+          set types. For example, access could be granted for hosts in the local subnetwork only. Firewall
+          rules using IP address of an interface are also instantly updated when the network configuration
+          changes, for example via DHCP.</para>
+
+          <para>This option expects a whitespace separated list of NFT set definitions. Each definition
+          consists of a colon-separated tuple of source type (one of <literal>address</literal>,
+          <literal>prefix</literal> or <literal>ifindex</literal>), 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. The type of the element used in
+          the NFT filter must match the type implied by the directive (<literal>address</literal>,
+          <literal>prefix</literal> or <literal>ifindex</literal>) and address type (IPv4 or IPv6) as shown
+          in the table below.</para>
+
+          <table>
+            <title>Defined <varname>source type</varname> values</title>
+            <tgroup cols='3'>
+              <colspec colname='source type'/>
+              <colspec colname='description'/>
+              <colspec colname='NFT type name'/>
+              <thead>
+                <row>
+                  <entry>Source type</entry>
+                  <entry>Description</entry>
+                  <entry>Corresponding NFT type name</entry>
+                </row>
+              </thead>
+
+              <tbody>
+                <row>
+                  <entry><literal>address</literal></entry>
+                  <entry>host IP address</entry>
+                  <entry><literal>ipv4_addr</literal> or <literal>ipv6_addr</literal></entry>
+                </row>
+                <row>
+                  <entry><literal>prefix</literal></entry>
+                  <entry>network prefix</entry>
+                  <entry><literal>ipv4_addr</literal> or <literal>ipv6_addr</literal>, with <literal>flags interval</literal></entry>
+                </row>
+                <row>
+                  <entry><literal>ifindex</literal></entry>
+                  <entry>interface index</entry>
+                  <entry><literal>iface_index</literal></entry>
+                </row>
+              </tbody>
+            </tgroup>
+          </table>
+
+          <para>When an interface is configured with IP addresses, the addresses, subnetwork masks or
+          interface index will be appended to the NFT sets. The information will be removed when the
+          interface is deconfigured. <command>systemd-networkd</command> only inserts elements to (or removes
+          from) the sets, so the related NFT rules, tables and sets must be prepared elsewhere in
+          advance. Failures to manage the sets will be ignored.</para>
+
+          <para>Example:
+          <programlisting>[Address]
+NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
+          Corresponding NFT rules:
+          <programlisting>table netdev filter {
+        set eth_ipv4_prefix {
+                type ipv4_addr
+                flags interval
+        }
+        chain eth_ingress {
+                type filter hook ingress device "eth0" priority filter; policy drop;
+                ip daddr != @eth_ipv4_prefix drop
+                accept
+        }
+}</programlisting>
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
@@ -2497,6 +2577,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
           <xi:include href="version-info.xml" xpointer="v252"/>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>NFTSet=</varname></term>
+        <listitem>
+          <para>This applies the NFT set for the network configuration received with DHCP, like
+          <varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
+          <varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
+          <literal>prefix</literal> source types, the type of the element used in the NFT filter must be
+          <literal>ipv4_addr</literal>.</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
@@ -2656,6 +2747,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>NFTSet=</varname></term>
+        <listitem>
+          <para>This applies the NFT set for the network configuration received with DHCP, like
+          <varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
+          <varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal>
+          or <literal>prefix</literal> source types, the type of the element used in the NFT filter must be
+          <literal>ipv6_addr</literal>.</para>
+        </listitem>
+      </varlistentry>
+
       <!-- How to communicate with the server -->
 
       <varlistentry>
@@ -2767,6 +2869,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
           <xi:include href="version-info.xml" xpointer="v252"/>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>NFTSet=</varname></term>
+        <listitem>
+          <para>This applies the NFT set for the network configuration received with DHCP, like
+          <varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
+          <varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
+          <literal>prefix</literal> source types, the type of the element used in the NFT filter must be
+          <literal>ipv6_addr</literal>.</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
@@ -3119,6 +3232,17 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
           <xi:include href="version-info.xml" xpointer="v252"/>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>NFTSet=</varname></term>
+        <listitem>
+          <para>This applies the NFT set for the network configuration received with RA, like
+          <varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
+          <varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
+          <literal>prefix</literal> source types, the type of the element used in the NFT filter must be
+          <literal>ipv6_addr</literal>.</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index cbe5ad6a32eff8290c9d75fb96162ba1904b7edb..b8fbe3f1c7718ec8dd91060e2205003fb7afb793 100644 (file)
@@ -747,3 +747,22 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
 
         return store_loadavg_fixed_point(i, f, ret);
 }
+
+/* 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_valid(const char *id) {
+        if (!id)
+                return false;
+
+        size_t len = strlen(id);
+        if (len == 0 || len > 31)
+                return false;
+
+        if (!ascii_isalpha(id[0]))
+                return false;
+
+        for (size_t i = 1; i < len; i++)
+                if (!ascii_isalpha(id[i]) && !ascii_isdigit(id[i]) && !IN_SET(id[i], '/', '\\', '_', '.'))
+                        return false;
+        return true;
+}
index c480407c2a57a9bd1d1c1f960d7e23e04cb15c9e..ca0252005ac87e4cb716ecfda130fb2143626106 100644 (file)
@@ -152,3 +152,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_valid(const char *id);
index bca13bce57527801e10b90c11fd5163b054fce7a..891d3e841345f0ba1cb3ba2f7709ff30d56c377f 100644 (file)
@@ -170,6 +170,8 @@ int netlink_add_match_internal(
 #define NETLINK_DONT_DESTROY(nl) \
         _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##nl = sd_netlink_ref(nl)
 
+bool nfproto_is_valid(int nfproto);
+
 /* nfnl */
 /* TODO: to be exported later */
 int sd_nfnl_socket_open(sd_netlink **ret);
index a8cec2bd8bdf512782638718d268c19ce656b5f9..edde5d2a88f8fb69d23b78b0f559e2ce5d94d047 100644 (file)
@@ -12,7 +12,7 @@
 #include "netlink-types.h"
 #include "netlink-util.h"
 
-static bool nfproto_is_valid(int nfproto) {
+bool nfproto_is_valid(int nfproto) {
         return IN_SET(nfproto,
                       NFPROTO_UNSPEC,
                       NFPROTO_INET,
@@ -20,8 +20,7 @@ static bool nfproto_is_valid(int nfproto) {
                       NFPROTO_ARP,
                       NFPROTO_NETDEV,
                       NFPROTO_BRIDGE,
-                      NFPROTO_IPV6,
-                      NFPROTO_DECNET);
+                      NFPROTO_IPV6);
 }
 
 int sd_nfnl_message_new(sd_netlink *nfnl, sd_netlink_message **ret, int nfproto, uint16_t subsys, uint16_t msg_type, uint16_t flags) {
index 8d6dec3668930a086f2234236fbea8bceccbfa44..58eebc63c6e7cf287d4b32fc6982607a4045ff80 100644 (file)
@@ -173,6 +173,7 @@ Address *address_free(Address *address) {
         config_section_free(address->section);
         free(address->label);
         free(address->netlabel);
+        nft_set_context_clear(&address->nft_set_context);
         return mfree(address);
 }
 
@@ -502,6 +503,8 @@ int address_dup(const Address *src, Address **ret) {
         dest->link = NULL;
         dest->label = NULL;
         dest->netlabel = NULL;
+        dest->nft_set_context.sets = NULL;
+        dest->nft_set_context.n_sets = 0;
 
         if (src->family == AF_INET) {
                 r = free_and_strdup(&dest->label, src->label);
@@ -513,6 +516,10 @@ int address_dup(const Address *src, Address **ret) {
         if (r < 0)
                 return r;
 
+        r = nft_set_context_dup(&src->nft_set_context, &dest->nft_set_context);
+        if (r < 0)
+                return r;
+
         *ret = TAKE_PTR(dest);
         return 0;
 }
@@ -555,6 +562,82 @@ static int address_set_masquerade(Address *address, bool add) {
         return 0;
 }
 
+static void address_modify_nft_set_context(Address *address, bool add, NFTSetContext *nft_set_context) {
+        int r;
+
+        assert(address);
+        assert(address->link);
+        assert(address->link->manager);
+        assert(nft_set_context);
+
+        if (!address->link->manager->fw_ctx) {
+                r = fw_ctx_new(&address->link->manager->fw_ctx);
+                if (r < 0)
+                        return;
+        }
+
+        FOREACH_ARRAY(nft_set, nft_set_context->sets, nft_set_context->n_sets) {
+                uint32_t ifindex;
+
+                assert(nft_set);
+
+                switch (nft_set->source) {
+                case NFT_SET_SOURCE_ADDRESS:
+                        r = nft_set_element_modify_ip(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set,
+                                                      &address->in_addr);
+                        break;
+                case NFT_SET_SOURCE_PREFIX:
+                        r = nft_set_element_modify_iprange(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set,
+                                                           &address->in_addr, address->prefixlen);
+                        break;
+                case NFT_SET_SOURCE_IFINDEX:
+                        ifindex = address->link->ifindex;
+                        r = nft_set_element_modify_any(address->link->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set,
+                                                       &ifindex, sizeof(ifindex));
+                        break;
+                default:
+                        assert_not_reached();
+                }
+
+                if (r < 0)
+                        log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring",
+                                          add? "add" : "delete",
+                                          nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set,
+                                          IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
+                else
+                        log_debug("%s NFT set: family %s, table %s, set %s, IP address %s",
+                                  add ? "Added" : "Deleted",
+                                  nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set,
+                                  IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
+        }
+}
+
+static void address_modify_nft_set(Address *address, bool add) {
+        assert(address);
+        assert(address->link);
+
+        if (!IN_SET(address->family, AF_INET, AF_INET6))
+                return;
+
+        if (!address->link->network)
+                return;
+
+        switch (address->source) {
+        case NETWORK_CONFIG_SOURCE_DHCP4:
+                return address_modify_nft_set_context(address, add, &address->link->network->dhcp_nft_set_context);
+        case NETWORK_CONFIG_SOURCE_DHCP6:
+                return address_modify_nft_set_context(address, add, &address->link->network->dhcp6_nft_set_context);
+        case NETWORK_CONFIG_SOURCE_DHCP_PD:
+                return address_modify_nft_set_context(address, add, &address->link->network->dhcp_pd_nft_set_context);
+        case NETWORK_CONFIG_SOURCE_NDISC:
+                return address_modify_nft_set_context(address, add, &address->link->network->ndisc_nft_set_context);
+        case NETWORK_CONFIG_SOURCE_STATIC:
+                return address_modify_nft_set_context(address, add, &address->nft_set_context);
+        default:
+                return;
+        }
+}
+
 static int address_add(Link *link, Address *address) {
         int r;
 
@@ -596,6 +679,8 @@ static int address_update(Address *address) {
 
         address_add_netlabel(address);
 
+        address_modify_nft_set(address, /* add = */ true);
+
         if (address_is_ready(address) && address->callback) {
                 r = address->callback(address);
                 if (r < 0)
@@ -615,6 +700,8 @@ static int address_drop(Address *address) {
         if (r < 0)
                 log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m");
 
+        address_modify_nft_set(address, /* add = */ false);
+
         address_del_netlabel(address);
 
         address_free(address);
@@ -1664,6 +1751,8 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
                 address->source = a->source;
                 address->provider = a->provider;
                 (void) free_and_strdup_warn(&address->netlabel, a->netlabel);
+                nft_set_context_clear(&address->nft_set_context);
+                (void) nft_set_context_dup(&a->nft_set_context, &address->nft_set_context);
                 address->requested_as_null = a->requested_as_null;
                 address->callback = a->callback;
         }
@@ -2352,3 +2441,41 @@ int network_drop_invalid_addresses(Network *network) {
 
         return 0;
 }
+
+int config_parse_address_ip_nft_set(
+                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(lvalue);
+        assert(rvalue);
+        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 a new address, ignoring assignment: %m");
+                return 0;
+        }
+
+        r = config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->nft_set_context, network);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(n);
+        return 0;
+}
index 7a611529f76e4154e46d49388716a798fe69d92f..4be8f92ef08302609401435e83305497c18ecb27 100644 (file)
@@ -6,6 +6,7 @@
 #include <stdio.h>
 
 #include "conf-parser.h"
+#include "firewall-util.h"
 #include "in-addr-util.h"
 #include "networkd-link.h"
 #include "networkd-util.h"
@@ -59,6 +60,8 @@ struct Address {
 
         /* Called when address become ready */
         address_ready_callback_t callback;
+
+        NFTSetContext nft_set_context;
 };
 
 const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) _warn_unused_result_;
@@ -132,3 +135,4 @@ 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_ip_nft_set);
index 022bd597583c960c66fb0dd6bc3e50f0ae9577b6..6309baa0569383aa71da54b2386ec9dcdcb56e8b 100644 (file)
@@ -161,6 +161,7 @@ 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.NFTSet,                              config_parse_address_ip_nft_set,                          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
@@ -257,6 +258,7 @@ DHCPv4.InitialAdvertisedReceiveWindow,       config_parse_tcp_window,
 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_string,                                      CONFIG_PARSE_STRING_SAFE,      offsetof(Network, dhcp_netlabel)
+DHCPv4.NFTSet,                               config_parse_nft_set,                                     0,                             offsetof(Network, dhcp_nft_set_context)
 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
@@ -278,6 +280,7 @@ DHCPv6.DUIDRawData,                          config_parse_duid_rawdata,
 DHCPv6.RapidCommit,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp6_use_rapid_commit)
 DHCPv6.NetLabel,                             config_parse_string,                                      CONFIG_PARSE_STRING_SAFE,      offsetof(Network, dhcp6_netlabel)
 DHCPv6.SendRelease,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp6_send_release)
+DHCPv6.NFTSet,                               config_parse_nft_set,                                     0,                             offsetof(Network, dhcp6_nft_set_context)
 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)
@@ -300,6 +303,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_string,                                      CONFIG_PARSE_STRING_SAFE,      offsetof(Network, ndisc_netlabel)
+IPv6AcceptRA.NFTSet,                         config_parse_nft_set,                                     0,                             offsetof(Network, ndisc_nft_set_context)
 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)
@@ -367,6 +371,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_string,                                      CONFIG_PARSE_STRING_SAFE,      offsetof(Network, dhcp_pd_netlabel)
+DHCPPrefixDelegation.NFTSet,                 config_parse_nft_set,                                     0,                             offsetof(Network, dhcp_pd_nft_set_context)
 IPv6SendRA.RouterLifetimeSec,                config_parse_router_lifetime,                             0,                             offsetof(Network, router_lifetime_usec)
 IPv6SendRA.RetransmitSec,                    config_parse_router_retransmit,                           0,                             offsetof(Network, router_retransmit_usec)
 IPv6SendRA.Managed,                          config_parse_bool,                                        0,                             offsetof(Network, router_managed)
index bdf5acceb55a81dd215e886fa5749ffff8e9a6fb..d8938778fbfbdc3d3c4f3ba28afab15ac2e42f3c 100644 (file)
@@ -725,6 +725,7 @@ static Network *network_free(Network *network) {
         ordered_hashmap_free(network->dhcp_client_send_options);
         ordered_hashmap_free(network->dhcp_client_send_vendor_options);
         free(network->dhcp_netlabel);
+        nft_set_context_clear(&network->dhcp_nft_set_context);
 
         /* DHCPv6 client */
         free(network->dhcp6_mudurl);
@@ -734,11 +735,13 @@ static Network *network_free(Network *network) {
         ordered_hashmap_free(network->dhcp6_client_send_options);
         ordered_hashmap_free(network->dhcp6_client_send_vendor_options);
         free(network->dhcp6_netlabel);
+        nft_set_context_clear(&network->dhcp6_nft_set_context);
 
         /* DHCP PD */
         free(network->dhcp_pd_uplink_name);
         set_free(network->dhcp_pd_tokens);
         free(network->dhcp_pd_netlabel);
+        nft_set_context_clear(&network->dhcp_pd_nft_set_context);
 
         /* Router advertisement */
         ordered_set_free(network->router_search_domains);
@@ -754,6 +757,7 @@ static Network *network_free(Network *network) {
         set_free(network->ndisc_allow_listed_route_prefix);
         set_free(network->ndisc_tokens);
         free(network->ndisc_netlabel);
+        nft_set_context_clear(&network->ndisc_nft_set_context);
 
         /* LLDP */
         free(network->lldp_mudurl);
index 7270ace45d39c98788cd24bde76c48b3c711f5c8..19e4657f3c7c09eca301f7f822afef525d308482 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"
@@ -163,6 +164,7 @@ struct Network {
         OrderedHashmap *dhcp_client_send_options;
         OrderedHashmap *dhcp_client_send_vendor_options;
         char *dhcp_netlabel;
+        NFTSetContext dhcp_nft_set_context;
 
         /* DHCPv6 Client support */
         bool dhcp6_use_address;
@@ -191,6 +193,7 @@ struct Network {
         Set *dhcp6_request_options;
         char *dhcp6_netlabel;
         bool dhcp6_send_release;
+        NFTSetContext dhcp6_nft_set_context;
 
         /* DHCP Server Support */
         bool dhcp_server;
@@ -251,6 +254,7 @@ struct Network {
         int dhcp_pd_uplink_index;
         char *dhcp_pd_uplink_name;
         char *dhcp_pd_netlabel;
+        NFTSetContext dhcp_pd_nft_set_context;
 
         /* Bridge Support */
         int use_bpdu;
@@ -343,6 +347,7 @@ struct Network {
         Set *ndisc_allow_listed_route_prefix;
         Set *ndisc_tokens;
         char *ndisc_netlabel;
+        NFTSetContext ndisc_nft_set_context;
 
         /* LLDP support */
         LLDPMode lldp_mode; /* LLDP reception */
index b5f0d1bab75afe184d873436878efe1a6d9187a5..39f9188de29d07c0b9685c290c7665549159d252 100644 (file)
@@ -14,6 +14,8 @@
 #include "sd-netlink.h"
 
 #include "alloc-util.h"
+#include "escape.h"
+#include "extract-word.h"
 #include "firewall-util.h"
 #include "firewall-util-private.h"
 #include "in-addr-util.h"
@@ -21,6 +23,7 @@
 #include "netlink-internal.h"
 #include "netlink-util.h"
 #include "socket-util.h"
+#include "string-table.h"
 #include "time-util.h"
 
 #define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
@@ -598,7 +601,8 @@ static int nft_new_map(
 static int nft_add_element(
                 sd_netlink *nfnl,
                 sd_netlink_message **ret,
-                int family,
+                int nfproto,
+                const char *table_name,
                 const char *set_name,
                 const void *key,
                 uint32_t klen,
@@ -610,10 +614,12 @@ static int nft_add_element(
 
         assert(nfnl);
         assert(ret);
-        assert(IN_SET(family, AF_INET, AF_INET6));
+        assert(nfproto_is_valid(nfproto));
+        assert(table_name);
         assert(set_name);
         assert(key);
-        assert(data);
+        assert(data || dlen == 0);
+
 
         /*
          * Ideally there would be an API that provides:
@@ -628,7 +634,7 @@ static int nft_add_element(
          * This replicated here and each element gets added to the set
          * one-by-one.
          */
-        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, family, NFT_SYSTEMD_TABLE_NAME, set_name);
+        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, nfproto, table_name, set_name);
         if (r < 0)
                 return r;
 
@@ -653,7 +659,8 @@ static int nft_add_element(
 static int nft_del_element(
                 sd_netlink *nfnl,
                 sd_netlink_message **ret,
-                int family,
+                int nfproto,
+                const char *table_name,
                 const char *set_name,
                 const void *key,
                 uint32_t klen,
@@ -665,12 +672,13 @@ static int nft_del_element(
 
         assert(nfnl);
         assert(ret);
-        assert(IN_SET(family, AF_INET, AF_INET6));
+        assert(nfproto_is_valid(nfproto));
+        assert(table_name);
         assert(set_name);
         assert(key);
-        assert(data);
+        assert(data || dlen == 0);
 
-        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, family, NFT_SYSTEMD_TABLE_NAME, set_name);
+        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, nfproto, table_name, set_name);
         if (r < 0)
                return r;
 
@@ -791,7 +799,7 @@ static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
         return 0;
 }
 
-int fw_nftables_init(FirewallContext *ctx) {
+int fw_nftables_init_full(FirewallContext *ctx, bool init_tables) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
         int r;
 
@@ -802,20 +810,26 @@ int fw_nftables_init(FirewallContext *ctx) {
         if (r < 0)
                 return r;
 
-        r = fw_nftables_init_family(nfnl, AF_INET);
-        if (r < 0)
-                return r;
-
-        if (socket_ipv6_is_supported()) {
-                r = fw_nftables_init_family(nfnl, AF_INET6);
+        if (init_tables) {
+                r = fw_nftables_init_family(nfnl, AF_INET);
                 if (r < 0)
-                        log_debug_errno(r, "Failed to init ipv6 NAT: %m");
+                        return r;
+
+                if (socket_ipv6_is_supported()) {
+                        r = fw_nftables_init_family(nfnl, AF_INET6);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to init ipv6 NAT: %m");
+                }
         }
 
         ctx->nfnl = TAKE_PTR(nfnl);
         return 0;
 }
 
+int fw_nftables_init(FirewallContext *ctx) {
+        return fw_nftables_init_full(ctx, /* init_tables= */ true);
+}
+
 void fw_nftables_exit(FirewallContext *ctx) {
         assert(ctx);
 
@@ -891,18 +905,24 @@ static int nft_message_append_setelem_ip6range(
         return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
 }
 
-static int fw_nftables_add_masquerade_internal(
-                sd_netlink *nfnl,
+int nft_set_element_modify_iprange(
+                FirewallContext *ctx,
                 bool add,
+                int nfproto,
                 int af,
+                const char *table,
+                const char *set,
                 const union in_addr_union *source,
                 unsigned int source_prefixlen) {
 
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
         int r;
 
-        assert(nfnl);
+        assert(ctx->nfnl);
         assert(IN_SET(af, AF_INET, AF_INET6));
+        assert(nfproto_is_valid(nfproto));
+        assert(table);
+        assert(set);
 
         if (!source || source_prefixlen == 0)
                 return -EINVAL;
@@ -910,7 +930,7 @@ static int fw_nftables_add_masquerade_internal(
         if (af == AF_INET6 && source_prefixlen < 8)
                 return -EINVAL;
 
-        r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
+        r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set);
         if (r < 0)
                 return r;
 
@@ -921,7 +941,81 @@ static int fw_nftables_add_masquerade_internal(
         if (r < 0)
                 return r;
 
-        return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
+        return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
+}
+
+int nft_set_element_modify_ip(
+                FirewallContext *ctx,
+                bool add,
+                int nfproto,
+                int af,
+                const char *table,
+                const char *set,
+                const union in_addr_union *source) {
+
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        assert(ctx->nfnl);
+        assert(IN_SET(af, AF_INET, AF_INET6));
+        assert(nfproto_is_valid(nfproto));
+        assert(table);
+        assert(set);
+
+        if (!source)
+                return -EINVAL;
+
+        r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
+        if (r < 0)
+                return r;
+
+        r = sd_nfnl_nft_message_append_setelem(m, 0, source, FAMILY_ADDRESS_SIZE(af), NULL, 0, 0);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
+        if (r < 0)
+                return r;
+
+        return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
+}
+
+int nft_set_element_modify_any(FirewallContext *ctx, bool add, int nfproto, const char *table, const char *set, const void *element, size_t element_size) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        assert(ctx);
+        assert(ctx->nfnl);
+        assert(nfproto_is_valid(nfproto));
+        assert(table);
+        assert(set);
+        assert(element);
+
+        if (add)
+                r = nft_add_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
+        else
+                r = nft_del_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
+        if (r < 0)
+                return r;
+
+        return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
+}
+
+static int af_to_nfproto(int af) {
+        assert(IN_SET(af, AF_INET, AF_INET6));
+
+        switch (af) {
+        case AF_INET:
+                return NFPROTO_IPV4;
+        case AF_INET6:
+                return NFPROTO_IPV6;
+        default:
+                assert_not_reached();
+        }
 }
 
 int fw_nftables_add_masquerade(
@@ -940,7 +1034,8 @@ int fw_nftables_add_masquerade(
         if (!socket_ipv6_is_supported() && af == AF_INET6)
                 return -EOPNOTSUPP;
 
-        r = fw_nftables_add_masquerade_internal(ctx->nfnl, add, af, source, source_prefixlen);
+        r = nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
+                                           source, source_prefixlen);
         if (r != -ENOENT)
                 return r;
 
@@ -965,7 +1060,8 @@ int fw_nftables_add_masquerade(
         if (r < 0)
                 return r;
 
-        return fw_nftables_add_masquerade_internal(ctx->nfnl, add, af, source, source_prefixlen);
+        return nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
+                                              source, source_prefixlen);
 }
 
 static int fw_nftables_add_local_dnat_internal(
@@ -1023,7 +1119,8 @@ static int fw_nftables_add_local_dnat_internal(
                 else
                         memcpy(data, &previous_remote->in6, sizeof(previous_remote->in6));
 
-                r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
+                r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
+                                    key, sizeof(key), data, dlen);
                 if (r < 0)
                         return r;
         }
@@ -1034,9 +1131,11 @@ static int fw_nftables_add_local_dnat_internal(
                 memcpy(data, &remote->in6, sizeof(remote->in6));
 
         if (add)
-                r = nft_add_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
+                r = nft_add_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
+                                    key, sizeof(key), data, dlen);
         else
-                r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
+                r = nft_del_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
+                                    key, sizeof(key), data, dlen);
         if (r < 0)
                 return r;
 
@@ -1087,3 +1186,181 @@ int fw_nftables_add_local_dnat(
         /* table created anew; previous address already gone */
         return fw_nftables_add_local_dnat_internal(ctx->nfnl, 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);
+
+static const char *const nft_set_source_table[] = {
+        [NFT_SET_SOURCE_ADDRESS] = "address",
+        [NFT_SET_SOURCE_PREFIX]  = "prefix",
+        [NFT_SET_SOURCE_IFINDEX] = "ifindex",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
+
+void nft_set_context_clear(NFTSetContext *s) {
+        assert(s);
+
+        FOREACH_ARRAY(nft_set, s->sets, s->n_sets) {
+                free(nft_set->table);
+                free(nft_set->set);
+        }
+
+        s->n_sets = 0;
+        s->sets = mfree(s->sets);
+}
+
+static int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
+        _cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
+
+        assert(s);
+        assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX));
+        assert(nfproto_is_valid(nfproto));
+        assert(table);
+        assert(set);
+
+        table_dup = strdup(table);
+        if (!table_dup)
+                return -ENOMEM;
+
+        set_dup = strdup(set);
+        if (!set_dup)
+                return -ENOMEM;
+
+        if (!GREEDY_REALLOC(s->sets, s->n_sets + 1))
+                return -ENOMEM;
+
+        s->sets[s->n_sets++] = (NFTSet) {
+                .source = source,
+                .nfproto = nfproto,
+                .table = TAKE_PTR(table_dup),
+                .set = TAKE_PTR(set_dup),
+        };
+
+        return 0;
+}
+
+int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst) {
+        int r;
+        _cleanup_(nft_set_context_clear) NFTSetContext d = (NFTSetContext) {};
+
+        assert(src);
+        assert(dst);
+
+        FOREACH_ARRAY(nft_set, src->sets, src->n_sets) {
+                r = nft_set_add(&d, nft_set->source, nft_set->nfproto, nft_set->table, nft_set->set);
+                if (r < 0)
+                        return r;
+        }
+
+        *dst = TAKE_STRUCT(d);
+
+        return 0;
+}
+
+int config_parse_nft_set(
+                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) {
+
+        NFTSetContext *nft_set_context = ASSERT_PTR(data);
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(nft_set_context);
+
+        if (isempty(rvalue)) {
+                nft_set_context_clear(nft_set_context);
+
+                return 0;
+        }
+
+        for (const char *p = rvalue;;) {
+                _cleanup_free_ char *tuple = NULL, *source_str = NULL, *family_str = NULL, *table = NULL, *set = NULL;
+                const char *q = NULL;
+                int nfproto;
+                NFTSetSource source;
+
+                r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(rvalue);
+                        log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax %s=%s, ignoring: %m", lvalue, strna(esc));
+                        return 0;
+                }
+                if (r == 0)
+                        return 0;
+
+                q = tuple;
+                r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set, NULL);
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r != 4 || !isempty(q)) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(tuple);
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse NFT set %s, ignoring", strna(esc));
+                }
+
+                assert(source_str);
+                assert(family_str);
+                assert(table);
+                assert(set);
+
+                source = nft_set_source_from_string(source_str);
+                if (source < 0) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(source_str);
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT source %s, ignoring", strna(esc));
+                }
+
+                nfproto = nfproto_from_string(family_str);
+                if (nfproto < 0) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(family_str);
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family %s, ignoring", strna(esc));
+                }
+
+                if (!nft_identifier_valid(table)) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(table);
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", strna(esc));
+                }
+
+                if (!nft_identifier_valid(set)) {
+                        _cleanup_free_ char *esc = NULL;
+
+                        esc = cescape(set);
+                        return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", strna(esc));
+                }
+
+                r = nft_set_add(nft_set_context, source, nfproto, table, set);
+                if (r < 0)
+                        return r;
+        }
+
+        assert_not_reached();
+}
index 14f5a35a878efe89a3c2e395299015be452cec7c..97f8fe124ef7ef6500e36e78abce9b1e6e44585b 100644 (file)
@@ -26,6 +26,7 @@ struct FirewallContext {
 const char *firewall_backend_to_string(FirewallBackend b) _const_;
 
 int fw_nftables_init(FirewallContext *ctx);
+int fw_nftables_init_full(FirewallContext *ctx, bool init_tables);
 void fw_nftables_exit(FirewallContext *ctx);
 
 int fw_nftables_add_masquerade(
index afa3e02b45463dfac980975f2642c10fab06d5be..ba3e9cbc5e073127eac653e39573ca2a9d801864 100644 (file)
@@ -20,13 +20,13 @@ static const char * const firewall_backend_table[_FW_BACKEND_MAX] = {
 
 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(firewall_backend, FirewallBackend);
 
-static void firewall_backend_probe(FirewallContext *ctx) {
+static void firewall_backend_probe(FirewallContext *ctx, bool init_tables) {
         assert(ctx);
 
         if (ctx->backend != _FW_BACKEND_INVALID)
                 return;
 
-        if (fw_nftables_init(ctx) >= 0)
+        if (fw_nftables_init_full(ctx, init_tables) >= 0)
                 ctx->backend = FW_BACKEND_NFTABLES;
         else
 #if HAVE_LIBIPTC
@@ -41,7 +41,7 @@ static void firewall_backend_probe(FirewallContext *ctx) {
                 log_debug("No firewall backend found.");
 }
 
-int fw_ctx_new(FirewallContext **ret) {
+int fw_ctx_new_full(FirewallContext **ret, bool init_tables) {
         _cleanup_free_ FirewallContext *ctx = NULL;
 
         ctx = new(FirewallContext, 1);
@@ -52,12 +52,16 @@ int fw_ctx_new(FirewallContext **ret) {
                 .backend = _FW_BACKEND_INVALID,
         };
 
-        firewall_backend_probe(ctx);
+        firewall_backend_probe(ctx, init_tables);
 
         *ret = TAKE_PTR(ctx);
         return 0;
 }
 
+int fw_ctx_new(FirewallContext **ret) {
+        return fw_ctx_new_full(ret, /* init_tables= */ true);
+}
+
 FirewallContext *fw_ctx_free(FirewallContext *ctx) {
         if (!ctx)
                 return NULL;
index 7725a5e58dfd31a20ae5de8dc7fee653ab969be3..bb35e76f21efd094c95c24f205c218454751ba90 100644 (file)
@@ -4,11 +4,13 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include "conf-parser.h"
 #include "in-addr-util.h"
 
 typedef struct FirewallContext FirewallContext;
 
 int fw_ctx_new(FirewallContext **ret);
+int fw_ctx_new_full(FirewallContext **ret, bool init_tables);
 FirewallContext *fw_ctx_free(FirewallContext *ctx);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(FirewallContext *, fw_ctx_free);
@@ -29,3 +31,62 @@ int fw_add_local_dnat(
                 const union in_addr_union *remote,
                 uint16_t remote_port,
                 const union in_addr_union *previous_remote);
+
+typedef enum NFTSetSource {
+        NFT_SET_SOURCE_ADDRESS,
+        NFT_SET_SOURCE_PREFIX,
+        NFT_SET_SOURCE_IFINDEX,
+        _NFT_SET_SOURCE_MAX,
+        _NFT_SET_SOURCE_INVALID = -EINVAL,
+}  NFTSetSource;
+
+typedef struct NFTSet {
+        NFTSetSource source;
+        int nfproto;
+        char *table;
+        char *set;
+} NFTSet;
+
+typedef struct NFTSetContext {
+        NFTSet *sets;
+        size_t n_sets;
+} NFTSetContext;
+
+void nft_set_context_clear(NFTSetContext *s);
+int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst);
+
+const char *nfproto_to_string(int i) _const_;
+int nfproto_from_string(const char *s) _pure_;
+
+const char *nft_set_source_to_string(int i) _const_;
+int nft_set_source_from_string(const char *s) _pure_;
+
+int nft_set_element_modify_iprange(
+                FirewallContext *ctx,
+                bool add,
+                int nfproto,
+                int af,
+                const char *table,
+                const char *set,
+                const union in_addr_union *source,
+                unsigned int source_prefixlen);
+
+int nft_set_element_modify_ip(
+                FirewallContext *ctx,
+                bool add,
+                int nfproto,
+                int af,
+                const char *table,
+                const char *set,
+                const union in_addr_union *source);
+
+int nft_set_element_modify_any(
+                FirewallContext *ctx,
+                bool add,
+                int nfproto,
+                const char *table,
+                const char *set,
+                const void *element,
+                size_t element_size);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_nft_set);
index 1857c9c53b7ab91598ab6cfc094bf877e32490e1..6d39006d092d1190141043b322915841e05cb0b9 100644 (file)
@@ -330,6 +330,10 @@ executables += [
                 'conditions' : ['HAVE_KMOD'],
                 'type' : 'manual',
         },
+        test_template + {
+                'sources' : files('test-nft-set.c'),
+                'type' : 'manual',
+        },
         test_template + {
                 'sources' : files('test-nscd-flush.c'),
                 'conditions' : ['ENABLE_NSCD'],
diff --git a/src/test/test-nft-set.c b/src/test/test-nft-set.c
new file mode 100644 (file)
index 0000000..df8e93a
--- /dev/null
@@ -0,0 +1,70 @@
+/* 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 "netlink-internal.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_is_valid(nfproto));
+
+        const char *table = argv[3], *set = argv[4];
+
+        FirewallContext *ctx;
+        r = fw_ctx_new(&ctx);
+        assert_se(r == 0);
+
+        bool add;
+        if (streq(argv[1], "add"))
+                add = true;
+        else
+                add = false;
+
+        if (streq(argv[5], "uint32")) {
+                uint32_t element;
+
+                r = safe_atou32(argv[6], &element);
+                assert_se(r == 0);
+
+                r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element));
+                assert_se(r == 0);
+        } else if (streq(argv[5], "in_addr")) {
+                union in_addr_union addr;
+                int af;
+
+                r = in_addr_from_string_auto(argv[6], &af, &addr);
+                assert_se(r == 0);
+
+                r = nft_set_element_modify_ip(ctx, add, nfproto, af, table, set, &addr);
+                assert_se(r == 0);
+        } else if (streq(argv[5], "network")) {
+                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);
+
+                r = nft_set_element_modify_iprange(ctx, add, nfproto, af, table, set, &addr, prefixlen);
+                assert_se(r == 0);
+        }
+
+        return 0;
+}
index a406aab7e48f12b5f2da458fdcfacba9ae42be3d..67ea6aa088bb03f342b03ab1fd75f9876f9c8c7d 100644 (file)
@@ -177,3 +177,4 @@ Address=::/73
 Address=10.10.1.1/24
 # just a random label which should exist
 NetLabel=system_u:object_r:root_t:s0
+NFTSet=address:inet:sd_test:addr4 prefix:inet:sd_test:network4 ifindex:inet:sd_test:ifindex
index 80d6bdc1d428673f339914a5fe309011cbc5ac7b..653d7aa661bda2b597237b0544e64f5509900260 100644 (file)
@@ -28,6 +28,7 @@ AllowList=192.168.5.0/24 192.168.6.0/24
 DenyList=192.168.5.0/24
 # just a random label which should exist
 NetLabel=system_u:object_r:root_t:s0
+NFTSet=address:inet:sd_test:addr4 prefix:inet:sd_test:network4 ifindex:inet:sd_test:ifindex
 
 [Route]
 Destination=192.168.5.0/24
index 2babc4320357b494ca008529060fe5461a746218..077b29c5d9e23e5d522716650f3fd333c48ab8aa 100644 (file)
@@ -16,3 +16,4 @@ Token=eui64
 Token=::1a:2b:3c:4d
 # just a random label which should exist
 NetLabel=system_u:object_r:root_t:s0
+NFTSet=address:inet:sd_test:addr6 prefix:inet:sd_test:network6 ifindex:inet:sd_test:ifindex
index 905e2fd2c86dd978731228bbf5ed93fe8f306cbe..80f1b8512e025aefa3992cec4376139ba9e4fe60 100644 (file)
@@ -9,3 +9,4 @@ IPv6AcceptRA=true
 UseDomains=yes
 # just a random label which should exist
 NetLabel=system_u:object_r:root_t:s0
+NFTSet=address:inet:sd_test:addr6 prefix:inet:sd_test:network6 ifindex:inet:sd_test:ifindex
index 171152040b94ddab437469c521c67f07de50db89..bc60e9385045699bbaf9a5bf3f2522ba11033ec7 100755 (executable)
@@ -944,6 +944,37 @@ class Utilities():
             print(output)
             self.assertRegex(output, f'interface:{interface},address:{address},label:"{label}"')
 
+    def setup_nftset(self, filter_name, filter_type, flags=''):
+        if not shutil.which('nft'):
+            print('## Setting up NFT sets skipped: nft command not found.')
+        else:
+            if call(f'nft add table inet sd_test') != 0:
+                print('## Setting up NFT table failed.')
+                self.fail()
+            if call(f'nft add set inet sd_test {filter_name} {{ type {filter_type}; {flags} }}') != 0:
+                print('## Setting up NFT sets failed.')
+                self.fail()
+
+    def teardown_nftset(self, *filters):
+        if not shutil.which('nft'):
+            print('## Tearing down NFT sets skipped: nft command not found.')
+        else:
+            for filter_name in filters:
+                if call(f'nft delete set inet sd_test {filter_name}') != 0:
+                    print('## Tearing down NFT sets failed.')
+                    self.fail()
+            if call(f'nft delete table inet sd_test') != 0:
+                print('## Tearing down NFT table failed.')
+                self.fail()
+
+    def check_nftset(self, filter_name, contents):
+        if not shutil.which('nft'):
+            print('## Checking NFT sets skipped: nft command not found.')
+        else:
+            output = check_output(f'nft list set inet sd_test {filter_name}')
+            print(output)
+            self.assertRegex(output, r'.*elements = { [^}]*' + contents + r'[^}]* }.*')
+
 class NetworkctlTests(unittest.TestCase, Utilities):
 
     def setUp(self):
@@ -2432,6 +2463,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
     def test_address_static(self):
         copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False)
         start_networkd()
+        self.setup_nftset('addr4', 'ipv4_addr')
+        self.setup_nftset('network4', 'ipv4_addr', 'flags interval;')
+        self.setup_nftset('ifindex', 'iface_index')
 
         self.wait_online(['dummy98:routable'])
         self.verify_address_static(
@@ -2459,6 +2493,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
             flag3=' noprefixroute',
             flag4=' home mngtmpaddr',
         )
+        # nft set
+        self.check_nftset('addr4', r'10\.10\.1\.1')
+        self.check_nftset('network4', r'10\.10\.1\.0/24')
+        self.check_nftset('ifindex', 'dummy98')
+
+        self.teardown_nftset('addr4', 'network4', 'ifindex')
 
         copy_network_unit('25-address-static.network.d/10-override.conf')
         networkctl_reload()
@@ -4702,6 +4742,9 @@ class NetworkdRATests(unittest.TestCase, Utilities):
 
     def test_ipv6_prefix_delegation(self):
         copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth.network')
+        self.setup_nftset('addr6', 'ipv6_addr')
+        self.setup_nftset('network6', 'ipv6_addr', 'flags interval;')
+        self.setup_nftset('ifindex', 'iface_index')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
@@ -4721,6 +4764,14 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.check_netlabel('veth99', '2002:da8:1::/64')
         self.check_netlabel('veth99', '2002:da8:2::/64')
 
+        self.check_nftset('addr6', '2002:da8:1:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
+        self.check_nftset('addr6', '2002:da8:2:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
+        self.check_nftset('network6', '2002:da8:1::/64')
+        self.check_nftset('network6', '2002:da8:2::/64')
+        self.check_nftset('ifindex', 'veth99')
+
+        self.teardown_nftset('addr6', 'network6', 'ifindex')
+
     def test_ipv6_token_static(self):
         copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
         start_networkd()
@@ -5048,6 +5099,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
     def test_dhcp_client_ipv4_only(self):
         copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network')
 
+        self.setup_nftset('addr4', 'ipv4_addr')
+        self.setup_nftset('network4', 'ipv4_addr', 'flags interval;')
+        self.setup_nftset('ifindex', 'iface_index')
+
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
         start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7',
@@ -5163,6 +5218,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
 
         self.check_netlabel('veth99', r'192\.168\.5\.0/24')
 
+        self.check_nftset('addr4', r'192\.168\.5\.1')
+        self.check_nftset('network4', r'192\.168\.5\.0/24')
+        self.check_nftset('ifindex', 'veth99')
+
+        self.teardown_nftset('addr4', 'network4', 'ifindex')
+
     def test_dhcp_client_ipv4_use_routes_gateway(self):
         first = True
         for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4):
@@ -5619,6 +5680,10 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
                           '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
                           '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network')
 
+        self.setup_nftset('addr6', 'ipv6_addr')
+        self.setup_nftset('network6', 'ipv6_addr', 'flags interval;')
+        self.setup_nftset('ifindex', 'iface_index')
+
         start_networkd()
         self.wait_online(['veth-peer:routable'])
         start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6')
@@ -5806,6 +5871,13 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
 
         self.check_netlabel('dummy98', '3ffe:501:ffff:[2-9a-f]00::/64')
 
+        self.check_nftset('addr6', '3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d')
+        self.check_nftset('addr6', '3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
+        self.check_nftset('network6', '3ffe:501:ffff:[2-9a-f]00::/64')
+        self.check_nftset('ifindex', 'dummy98')
+
+        self.teardown_nftset('addr6', 'network6', 'ifindex')
+
     def verify_dhcp4_6rd(self, tunnel_name):
         print('### ip -4 address show dev veth-peer scope global')
         output = check_output('ip -4 address show dev veth-peer scope global')