network: firewall integration with NFT sets

New directive `NFTSet=` provides a method for integrating network configuration
into firewall rules with NFT sets. The benefit of using this setting is that
static network configuration or dynamically obtained network addresses 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 type implied by the directive ("address", "prefix" or "ifindex") and
address type (IPv4 or IPv6) must also match the set definition.

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.

/etc/systemd/network/eth.network
```
[DHCPv4]
...
NFTSet=prefix:netdev:filter:eth_ipv4_prefix
```

Example 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 saddr != @eth_ipv4_prefix drop
                accept
        }
}
```
```
$ sudo nft list set netdev filter eth_ipv4_prefix
table netdev filter {
        set eth_ipv4_prefix {
                type ipv4_addr
                flags interval
                elements = { 10.0.0.0/24 }
        }
}
```
This commit is contained in:
Topi Miettinen 2023-08-09 23:07:21 +03:00
parent 274ffe1abb
commit fc289dd0ad
No known key found for this signature in database
GPG key ID: 5B98C5D5FAE8939A
12 changed files with 656 additions and 0 deletions

View file

@ -1200,6 +1200,86 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname><replaceable>source</replaceable>:<replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<listitem>
<para>This setting provides a method for integrating network configuration into firewall rules with
<ulink url="https://netfilter.org/projects/nftables/index.html">NFT</ulink> sets. The benefit of
using the setting is that static network configuration (or dynamically obtained network addresses,
see similar directives in other sections) can be used in firewall rules with the indirection of NFT
set types. For example, access could be granted for hosts in the local subnetwork only. Firewall
rules using IP address of an interface are also instantly updated when the network configuration
changes, for example via DHCP.</para>
<para>This option expects a whitespace separated list of NFT set definitions. Each definition
consists of a colon-separated tuple of source type (one of <literal>address</literal>,
<literal>prefix</literal> or <literal>ifindex</literal>), NFT address family (one of
<literal>arp</literal>, <literal>bridge</literal>, <literal>inet</literal>, <literal>ip</literal>,
<literal>ip6</literal>, or <literal>netdev</literal>), table name and set name. The names of tables
and sets must conform to lexical restrictions of NFT table names. The type of the element used in
the NFT filter must match the type implied by the directive (<literal>address</literal>,
<literal>prefix</literal> or <literal>ifindex</literal>) and address type (IPv4 or IPv6) as shown
in the table below.</para>
<table>
<title>Defined <varname>source type</varname> values</title>
<tgroup cols='3'>
<colspec colname='source type'/>
<colspec colname='description'/>
<colspec colname='NFT type name'/>
<thead>
<row>
<entry>Source type</entry>
<entry>Description</entry>
<entry>Corresponding NFT type name</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>address</literal></entry>
<entry>host IP address</entry>
<entry><literal>ipv4_addr</literal> or <literal>ipv6_addr</literal></entry>
</row>
<row>
<entry><literal>prefix</literal></entry>
<entry>network prefix</entry>
<entry><literal>ipv4_addr</literal> or <literal>ipv6_addr</literal>, with <literal>flags interval</literal></entry>
</row>
<row>
<entry><literal>ifindex</literal></entry>
<entry>interface index</entry>
<entry><literal>iface_index</literal></entry>
</row>
</tbody>
</tgroup>
</table>
<para>When an interface is configured with IP addresses, the addresses, subnetwork masks or
interface index will be appended to the NFT sets. The information will be removed when the
interface is deconfigured. <command>systemd-networkd</command> only inserts elements to (or removes
from) the sets, so the related NFT rules, tables and sets must be prepared elsewhere in
advance. Failures to manage the sets will be ignored.</para>
<para>Example:
<programlisting>[Address]
NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
Corresponding NFT rules:
<programlisting>table netdev filter {
set eth_ipv4_prefix {
type ipv4_addr
flags interval
}
chain eth_ingress {
type filter hook ingress device "eth0" priority filter; policy drop;
ip daddr != @eth_ipv4_prefix drop
accept
}
}</programlisting>
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2214,6 +2294,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>This applies the NFT set for the network configuration received with DHCP, like
<varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
<varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
<literal>prefix</literal> source types, the type of the element used in the NFT filter must be
<literal>ipv4_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2355,6 +2446,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>This applies the NFT set for the network configuration received with DHCP, like
<varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
<varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal>
or <literal>prefix</literal> source types, the type of the element used in the NFT filter must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
<!-- How to communicate with the server -->
<varlistentry>
@ -2460,6 +2562,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>This applies the NFT set for the network configuration received with DHCP, like
<varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
<varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
<literal>prefix</literal> source types, the type of the element used in the NFT filter must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2758,6 +2871,17 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
addresses. See <varname>NetLabel=</varname> in [Address] section for more details.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>This applies the NFT set for the network configuration received with RA, like
<varname>NFTSet=</varname> in [Address] section applies it to static configuration. See
<varname>NFTSet=</varname> in [Address] section for more details. For <literal>address</literal> or
<literal>prefix</literal> source types, the type of the element used in the NFT filter must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -747,3 +747,22 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
return store_loadavg_fixed_point(i, f, ret);
}
/* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and
* https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */
bool nft_identifier_valid(const char *id) {
if (!id)
return false;
size_t len = strlen(id);
if (len == 0 || len > 31)
return false;
if (!ascii_isalpha(id[0]))
return false;
for (size_t i = 1; i < len; i++)
if (!ascii_isalpha(id[i]) && !ascii_isdigit(id[i]) && !IN_SET(id[i], '/', '\\', '_', '.'))
return false;
return true;
}

View file

@ -152,3 +152,5 @@ int parse_oom_score_adjust(const char *s, int *ret);
* to a loadavg_t. */
int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret);
int parse_loadavg_fixed_point(const char *s, loadavg_t *ret);
bool nft_identifier_valid(const char *id);

View file

@ -173,6 +173,7 @@ Address *address_free(Address *address) {
config_section_free(address->section);
free(address->label);
free(address->netlabel);
nft_set_context_clear(&address->nft_set_context);
return mfree(address);
}
@ -502,6 +503,8 @@ int address_dup(const Address *src, Address **ret) {
dest->link = NULL;
dest->label = NULL;
dest->netlabel = NULL;
dest->nft_set_context.sets = NULL;
dest->nft_set_context.n_sets = 0;
if (src->family == AF_INET) {
r = free_and_strdup(&dest->label, src->label);
@ -513,6 +516,10 @@ int address_dup(const Address *src, Address **ret) {
if (r < 0)
return r;
r = nft_set_context_dup(&src->nft_set_context, &dest->nft_set_context);
if (r < 0)
return r;
*ret = TAKE_PTR(dest);
return 0;
}
@ -555,6 +562,82 @@ static int address_set_masquerade(Address *address, bool add) {
return 0;
}
static void address_modify_nft_set_context(Address *address, bool add, NFTSetContext *nft_set_context) {
int r;
assert(address);
assert(address->link);
assert(address->link->manager);
assert(nft_set_context);
if (!address->link->manager->fw_ctx) {
r = fw_ctx_new(&address->link->manager->fw_ctx);
if (r < 0)
return;
}
FOREACH_ARRAY(nft_set, nft_set_context->sets, nft_set_context->n_sets) {
uint32_t ifindex;
assert(nft_set);
switch (nft_set->source) {
case NFT_SET_SOURCE_ADDRESS:
r = nft_set_element_modify_ip(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set,
&address->in_addr);
break;
case NFT_SET_SOURCE_PREFIX:
r = nft_set_element_modify_iprange(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set,
&address->in_addr, address->prefixlen);
break;
case NFT_SET_SOURCE_IFINDEX:
ifindex = address->link->ifindex;
r = nft_set_element_modify_any(address->link->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set,
&ifindex, sizeof(ifindex));
break;
default:
assert_not_reached();
}
if (r < 0)
log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring",
add? "add" : "delete",
nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
else
log_debug("%s NFT set: family %s, table %s, set %s, IP address %s",
add ? "Added" : "Deleted",
nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set,
IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen));
}
}
static void address_modify_nft_set(Address *address, bool add) {
assert(address);
assert(address->link);
if (!IN_SET(address->family, AF_INET, AF_INET6))
return;
if (!address->link->network)
return;
switch (address->source) {
case NETWORK_CONFIG_SOURCE_DHCP4:
return address_modify_nft_set_context(address, add, &address->link->network->dhcp_nft_set_context);
case NETWORK_CONFIG_SOURCE_DHCP6:
return address_modify_nft_set_context(address, add, &address->link->network->dhcp6_nft_set_context);
case NETWORK_CONFIG_SOURCE_DHCP_PD:
return address_modify_nft_set_context(address, add, &address->link->network->dhcp_pd_nft_set_context);
case NETWORK_CONFIG_SOURCE_NDISC:
return address_modify_nft_set_context(address, add, &address->link->network->ndisc_nft_set_context);
case NETWORK_CONFIG_SOURCE_STATIC:
return address_modify_nft_set_context(address, add, &address->nft_set_context);
default:
return;
}
}
static int address_add(Link *link, Address *address) {
int r;
@ -596,6 +679,8 @@ static int address_update(Address *address) {
address_add_netlabel(address);
address_modify_nft_set(address, /* add = */ true);
if (address_is_ready(address) && address->callback) {
r = address->callback(address);
if (r < 0)
@ -615,6 +700,8 @@ static int address_drop(Address *address) {
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m");
address_modify_nft_set(address, /* add = */ false);
address_del_netlabel(address);
address_free(address);
@ -1664,6 +1751,8 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
address->source = a->source;
address->provider = a->provider;
(void) free_and_strdup_warn(&address->netlabel, a->netlabel);
nft_set_context_clear(&address->nft_set_context);
(void) nft_set_context_dup(&a->nft_set_context, &address->nft_set_context);
address->requested_as_null = a->requested_as_null;
address->callback = a->callback;
}
@ -2352,3 +2441,41 @@ int network_drop_invalid_addresses(Network *network) {
return 0;
}
int config_parse_address_ip_nft_set(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_(address_free_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(network);
r = address_new_static(network, filename, section_line, &n);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to allocate a new address, ignoring assignment: %m");
return 0;
}
r = config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->nft_set_context, network);
if (r < 0)
return r;
TAKE_PTR(n);
return 0;
}

View file

@ -6,6 +6,7 @@
#include <stdio.h>
#include "conf-parser.h"
#include "firewall-util.h"
#include "in-addr-util.h"
#include "networkd-link.h"
#include "networkd-util.h"
@ -59,6 +60,8 @@ struct Address {
/* Called when address become ready */
address_ready_callback_t callback;
NFTSetContext nft_set_context;
};
const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) _warn_unused_result_;
@ -132,3 +135,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_address_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_address_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection);
CONFIG_PARSER_PROTOTYPE(config_parse_address_netlabel);
CONFIG_PARSER_PROTOTYPE(config_parse_address_ip_nft_set);

View file

@ -161,6 +161,7 @@ Address.DuplicateAddressDetection, config_parse_duplicate_address_dete
Address.Scope, config_parse_address_scope, 0, 0
Address.RouteMetric, config_parse_address_route_metric, 0, 0
Address.NetLabel, config_parse_address_netlabel, 0, 0
Address.NFTSet, config_parse_address_ip_nft_set, 0, 0
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
@ -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)

View file

@ -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);

View file

@ -10,6 +10,7 @@
#include "bridge.h"
#include "condition.h"
#include "conf-parser.h"
#include "firewall-util.h"
#include "hashmap.h"
#include "ipoib.h"
#include "net-condition.h"
@ -163,6 +164,7 @@ struct Network {
OrderedHashmap *dhcp_client_send_options;
OrderedHashmap *dhcp_client_send_vendor_options;
char *dhcp_netlabel;
NFTSetContext dhcp_nft_set_context;
/* DHCPv6 Client support */
bool dhcp6_use_address;
@ -191,6 +193,7 @@ struct Network {
Set *dhcp6_request_options;
char *dhcp6_netlabel;
bool dhcp6_send_release;
NFTSetContext dhcp6_nft_set_context;
/* DHCP Server Support */
bool dhcp_server;
@ -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 */

View file

@ -14,6 +14,8 @@
#include "sd-netlink.h"
#include "alloc-util.h"
#include "escape.h"
#include "extract-word.h"
#include "firewall-util.h"
#include "firewall-util-private.h"
#include "in-addr-util.h"
@ -21,6 +23,7 @@
#include "netlink-internal.h"
#include "netlink-util.h"
#include "socket-util.h"
#include "string-table.h"
#include "time-util.h"
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
@ -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();
}

View file

@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stdint.h>
#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);

View file

@ -330,6 +330,10 @@ executables += [
'conditions' : ['HAVE_KMOD'],
'type' : 'manual',
},
test_template + {
'sources' : files('test-nft-set.c'),
'type' : 'manual',
},
test_template + {
'sources' : files('test-nscd-flush.c'),
'conditions' : ['ENABLE_NSCD'],

70
src/test/test-nft-set.c Normal file
View file

@ -0,0 +1,70 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <assert.h>
#include <unistd.h>
#include "firewall-util.h"
#include "in-addr-util.h"
#include "log.h"
#include "netlink-internal.h"
#include "parse-util.h"
#include "string-util.h"
#include "tests.h"
int main(int argc, char **argv) {
int r;
assert_se(argc == 7);
test_setup_logging(LOG_DEBUG);
if (getuid() != 0)
return log_tests_skipped("not root");
int nfproto;
nfproto = nfproto_from_string(argv[2]);
assert_se(nfproto_is_valid(nfproto));
const char *table = argv[3], *set = argv[4];
FirewallContext *ctx;
r = fw_ctx_new(&ctx);
assert_se(r == 0);
bool add;
if (streq(argv[1], "add"))
add = true;
else
add = false;
if (streq(argv[5], "uint32")) {
uint32_t element;
r = safe_atou32(argv[6], &element);
assert_se(r == 0);
r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element));
assert_se(r == 0);
} else if (streq(argv[5], "in_addr")) {
union in_addr_union addr;
int af;
r = in_addr_from_string_auto(argv[6], &af, &addr);
assert_se(r == 0);
r = nft_set_element_modify_ip(ctx, add, nfproto, af, table, set, &addr);
assert_se(r == 0);
} else if (streq(argv[5], "network")) {
union in_addr_union addr;
int af;
unsigned char prefixlen;
r = in_addr_prefix_from_string_auto(argv[6], &af, &addr, &prefixlen);
assert_se(r == 0);
r = nft_set_element_modify_iprange(ctx, add, nfproto, af, table, set, &addr, prefixlen);
assert_se(r == 0);
}
return 0;
}