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