diff --git a/man/systemd.network.xml b/man/systemd.network.xml index b4fb051be3..8d0306771e 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1200,6 +1200,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 + } +} + +
+
@@ -2214,6 +2294,17 @@ allow my_server_t localnet_peer_t:peer recv; addresses. See NetLabel= in [Address] section for more details. + + + 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. + + @@ -2355,6 +2446,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. + + + @@ -2460,6 +2562,17 @@ allow my_server_t localnet_peer_t:peer recv; addresses. See NetLabel= in [Address] section for more details. + + + 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. + + @@ -2758,6 +2871,17 @@ Token=prefixstable:2002:da8:1:: addresses. See NetLabel= in [Address] section for more details. + + + 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/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 0f87ce24bb..14734dadd5 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 @@ -255,6 +256,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 @@ -276,6 +278,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) @@ -297,6 +300,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) @@ -364,6 +368,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.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 dbe8c59a62..57c0c999ac 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -723,6 +723,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); @@ -732,11 +733,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); @@ -752,6 +755,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 031ba398e7..9654911b58 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; @@ -249,6 +252,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; @@ -340,6 +344,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 cc35b1c2de..8adc7f8faa 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" @@ -941,6 +944,67 @@ int nft_set_element_modify_iprange( 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)); @@ -1122,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.h b/src/shared/firewall-util.h index 4f3cd61bf4..bb35e76f21 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -4,6 +4,7 @@ #include #include +#include "conf-parser.h" #include "in-addr-util.h" typedef struct FirewallContext FirewallContext; @@ -31,6 +32,35 @@ int fw_add_local_dnat( 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, @@ -40,3 +70,23 @@ int nft_set_element_modify_iprange( 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; +}