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 source type 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;
+}