diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 8456eb71b6..9a44db6d30 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1324,6 +1324,86 @@ allow my_server_t localnet_peer_t:peer recv; + + + NFTSet=source:family:table:set + + This setting provides a method for integrating network configuration into firewall rules with + NFT 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. + + This option expects a whitespace separated list of NFT set definitions. Each definition + consists of a colon-separated tuple of source type (one of address, + prefix or ifindex), NFT address family (one of + arp, bridge, inet, ip, + ip6, or netdev), 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 (address, + prefix or ifindex) and address type (IPv4 or IPv6) as shown + in the table below. + + + Defined <varname>source type</varname> values + + + + + + + Source type + Description + Corresponding NFT type name + + + + + + address + host IP address + ipv4_addr or ipv6_addr + + + prefix + network prefix + ipv4_addr or ipv6_addr, with flags interval + + + ifindex + interface index + iface_index + + + +
+ + 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. systemd-networkd 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. + + Example: + [Address] +NFTSet=prefix:netdev:filter:eth_ipv4_prefix + Corresponding NFT rules: + 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 + } +} + +
+
@@ -2497,6 +2577,17 @@ allow my_server_t localnet_peer_t:peer recv; + + + NFTSet= + + This applies the NFT set for the network configuration received with DHCP, like + NFTSet= in [Address] section applies it to static configuration. See + NFTSet= in [Address] section for more details. For address or + prefix source types, the type of the element used in the NFT filter must be + ipv4_addr. + + @@ -2656,6 +2747,17 @@ allow my_server_t localnet_peer_t:peer recv; + + NFTSet= + + This applies the NFT set for the network configuration received with DHCP, like + NFTSet= in [Address] section applies it to static configuration. See + NFTSet= in [Address] section for more details. For address + or prefix source types, the type of the element used in the NFT filter must be + ipv6_addr. + + + @@ -2767,6 +2869,17 @@ allow my_server_t localnet_peer_t:peer recv; + + + NFTSet= + + This applies the NFT set for the network configuration received with DHCP, like + NFTSet= in [Address] section applies it to static configuration. See + NFTSet= in [Address] section for more details. For address or + prefix source types, the type of the element used in the NFT filter must be + ipv6_addr. + + @@ -3119,6 +3232,17 @@ Token=prefixstable:2002:da8:1:: + + + NFTSet= + + This applies the NFT set for the network configuration received with RA, like + NFTSet= in [Address] section applies it to static configuration. See + NFTSet= in [Address] section for more details. For address or + prefix source types, the type of the element used in the NFT filter must be + ipv6_addr. + + diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index cbe5ad6a32..b8fbe3f1c7 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -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; +} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index c480407c2a..ca0252005a 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -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); diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h index bca13bce57..891d3e8413 100644 --- a/src/libsystemd/sd-netlink/netlink-internal.h +++ b/src/libsystemd/sd-netlink/netlink-internal.h @@ -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); diff --git a/src/libsystemd/sd-netlink/netlink-message-nfnl.c b/src/libsystemd/sd-netlink/netlink-message-nfnl.c index a8cec2bd8b..edde5d2a88 100644 --- a/src/libsystemd/sd-netlink/netlink-message-nfnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-nfnl.c @@ -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) { diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 8d6dec3668..58eebc63c6 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -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; +} diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 7a611529f7..4be8f92ef0 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -6,6 +6,7 @@ #include #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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 022bd59758..6309baa056 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index bdf5acceb5..d8938778fb 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 7270ace45d..19e4657f3c 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" @@ -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 */ diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index b5f0d1bab7..39f9188de2 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -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(); +} diff --git a/src/shared/firewall-util-private.h b/src/shared/firewall-util-private.h index 14f5a35a87..97f8fe124e 100644 --- a/src/shared/firewall-util-private.h +++ b/src/shared/firewall-util-private.h @@ -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( diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index afa3e02b45..ba3e9cbc5e 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -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; diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index 7725a5e58d..bb35e76f21 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -4,11 +4,13 @@ #include #include +#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); diff --git a/src/test/meson.build b/src/test/meson.build index 1857c9c53b..6d39006d09 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -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 index 0000000000..df8e93aaa2 --- /dev/null +++ b/src/test/test-nft-set.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#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; +} diff --git a/test/test-network/conf/25-address-static.network b/test/test-network/conf/25-address-static.network index a406aab7e4..67ea6aa088 100644 --- a/test/test-network/conf/25-address-static.network +++ b/test/test-network/conf/25-address-static.network @@ -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 diff --git a/test/test-network/conf/25-dhcp-client-ipv4-only.network b/test/test-network/conf/25-dhcp-client-ipv4-only.network index 80d6bdc1d4..653d7aa661 100644 --- a/test/test-network/conf/25-dhcp-client-ipv4-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv4-only.network @@ -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 diff --git a/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network b/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network index 2babc43203..077b29c5d9 100644 --- a/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network +++ b/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network @@ -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 diff --git a/test/test-network/conf/25-ipv6-prefix-veth.network b/test/test-network/conf/25-ipv6-prefix-veth.network index 905e2fd2c8..80f1b8512e 100644 --- a/test/test-network/conf/25-ipv6-prefix-veth.network +++ b/test/test-network/conf/25-ipv6-prefix-veth.network @@ -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 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 171152040b..bc60e93850 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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')