1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 01:55:22 +03:00

network: firewall integration with NFT sets

New directives `NFTSet=`, `IPv4NFTSet=` and `IPv6NFTSet=` provide a method for
integrating configuration of dynamic networks into firewall rules with NFT
sets.

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

```
table netdev filter {
        set eth_ipv4_address {
                type ipv4_addr
                flags interval
        }
        chain eth_ingress {
                type filter hook ingress device "eth0" priority filter; policy drop;
                ip saddr != @eth_ipv4_address drop
                accept
        }
}
```
```
sudo nft list set netdev filter eth_ipv4_address
table netdev filter {
        set eth_ipv4_address {
                type ipv4_addr
                flags interval
                elements = { 10.0.0.0/24 }
        }
}
```
This commit is contained in:
Topi Miettinen 2022-05-22 14:09:06 +03:00
parent db6f9b02a7
commit 51bb9076ab
No known key found for this signature in database
GPG Key ID: 765D5002DEBE53AE
13 changed files with 792 additions and 7 deletions

View File

@ -1141,6 +1141,39 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
and the reverse operation when the IPv4 address is deconfigured.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv4NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<term><varname>IPv6NFTSet=</varname><replaceable>family</replaceable>:<replaceable>table</replaceable>:<replaceable>set</replaceable></term>
<listitem>
<para>These settings provide a method for integrating dynamic network configuration into firewall
rules with NFT sets. These options expect a whitespace separated list of NFT set definitions. Each
definition consists of a colon-separated tuple of NFT address family (one of
<literal>arp</literal>, <literal>bridge</literal>, <literal>inet</literal>, <literal>ip</literal>,
<literal>ip6</literal>, or <literal>netdev</literal>), table name and set name. The names of tables
and sets must conform to lexical restrictions of NFT table names. When an interface is configured
with IP addresses, the addresses and subnetwork masks will be appended to the NFT sets. They will
be removed when the interface is deconfigured. Failures to manage the sets will be ignored.</para>
<para>Example:
<programlisting>[Address]
IPv4NFTSet=netdev:filter:eth_ipv4_address
IPv6NFTSet=netdev:filter:eth_ipv6_address</programlisting>
Corresponding NFT rules:
<programlisting>table netdev filter {
set eth_ipv4_address {
type ipv4_addr
flags interval
}
chain eth_ingress {
type filter hook ingress device "eth0" priority filter; policy drop;
ip daddr != @eth_ipv4_address drop
accept
}
}</programlisting>
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2089,6 +2122,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>As in [Address] section. The type in NFT set definition must be
<literal>ipv4_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2208,6 +2249,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>As in [DHCPv4] section. The type in NFT set definition must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
<!-- How to communicate with the server -->
<varlistentry>
@ -2311,6 +2360,14 @@ NetLabel=system_u:object_r:localnet_peer_t:s0</programlisting>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>As in [DHCPv6] section. The type in NFT set definition must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -2575,6 +2632,13 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
<para>As in [Address] section.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NFTSet=</varname></term>
<listitem>
<para>As in [DHCPv6] section. The type in NFT set definition must be
<literal>ipv6_addr</literal>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -750,3 +750,38 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
return store_loadavg_fixed_point(i, f, ret);
}
static bool nft_first_char_bad(const char c) {
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z'))
return false;
return true;
}
static bool nft_next_char_bad(const char c) {
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '/' || c == '\\' || c == '_' || c == '.')
return false;
return true;
}
/* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and
* https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */
bool nft_identifier_bad(const char *id) {
assert(id);
size_t len;
len = strlen(id);
if (len == 0 || len > 31)
return true;
if (nft_first_char_bad(id[0]))
return true;
for (size_t i = 1; i < len; i++)
if (nft_next_char_bad(id[i]))
return true;
return false;
}

View File

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

View File

@ -139,6 +139,8 @@ Address *address_free(Address *address) {
config_section_free(address->section);
free(address->label);
set_free(address->netlabels);
nft_set_context_free_many(address->ipv4_nft_set_context, &address->n_ipv4_nft_set_contexts);
nft_set_context_free_many(address->ipv6_nft_set_context, &address->n_ipv6_nft_set_contexts);
return mfree(address);
}
@ -450,6 +452,102 @@ static int address_set_masquerade(Address *address, bool add) {
return 0;
}
static void address_add_nft_set_context(const Address *address, const NFTSetContext *nft_set_context, size_t n_nft_set_contexts) {
assert(address);
for (size_t i = 0; i < n_nft_set_contexts; i++) {
int r;
r = nft_set_element_add_in_addr(&nft_set_context[i], address->family,
&address->in_addr, address->prefixlen);
if (r < 0) {
_cleanup_free_ char *addr_str = NULL;
(void) in_addr_prefix_to_string(address->family, &address->in_addr, address->prefixlen, &addr_str);
log_warning_errno(r, "Adding NFT family %s table %s set %s for IP address %s failed, ignoring",
nfproto_to_string(nft_set_context[i].nfproto),
nft_set_context[i].table,
nft_set_context[i].set,
strna(addr_str));
}
}
}
static void address_del_nft_set_context(const Address *address, const NFTSetContext *nft_set_context, size_t n_nft_set_contexts) {
assert(address);
for (size_t i = 0; i < n_nft_set_contexts; i++) {
int r;
r = nft_set_element_del_in_addr(&nft_set_context[i], address->family,
&address->in_addr, address->prefixlen);
if (r < 0) {
_cleanup_free_ char *addr_str = NULL;
(void) in_addr_prefix_to_string(address->family, &address->in_addr, address->prefixlen, &addr_str);
log_warning_errno(r, "Deleting NFT family %s table %s set %s for IP address %s failed, ignoring",
nfproto_to_string(nft_set_context[i].nfproto),
nft_set_context[i].table,
nft_set_context[i].set,
strna(addr_str));
}
}
}
static void address_add_nft_set(const Address *address) {
assert(address);
assert(address->link);
if (!address->link->network || !IN_SET(address->family, AF_INET, AF_INET6))
return;
switch (address->source) {
case NETWORK_CONFIG_SOURCE_DHCP4:
return address_add_nft_set_context(address, address->link->network->dhcp_nft_set_context, address->link->network->n_dhcp_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_DHCP6:
return address_add_nft_set_context(address, address->link->network->dhcp6_nft_set_context, address->link->network->n_dhcp6_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_DHCP_PD:
return address_add_nft_set_context(address, address->link->network->dhcp_pd_nft_set_context, address->link->network->n_dhcp_pd_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_NDISC:
return address_add_nft_set_context(address, address->link->network->ndisc_nft_set_context, address->link->network->n_ndisc_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_STATIC:
if (address->family == AF_INET)
return address_add_nft_set_context(address, address->ipv4_nft_set_context, address->n_ipv4_nft_set_contexts);
else
return address_add_nft_set_context(address, address->ipv6_nft_set_context, address->n_ipv6_nft_set_contexts);
default:
return;
}
}
static void address_del_nft_set(const Address *address) {
assert(address);
assert(address->link);
if (!address->link->network || !IN_SET(address->family, AF_INET, AF_INET6))
return;
switch (address->source) {
case NETWORK_CONFIG_SOURCE_DHCP4:
return address_del_nft_set_context(address, address->link->network->dhcp_nft_set_context, address->link->network->n_dhcp_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_DHCP6:
return address_del_nft_set_context(address, address->link->network->dhcp6_nft_set_context, address->link->network->n_dhcp6_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_DHCP_PD:
return address_del_nft_set_context(address, address->link->network->dhcp_pd_nft_set_context, address->link->network->n_dhcp_pd_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_NDISC:
return address_del_nft_set_context(address, address->link->network->ndisc_nft_set_context, address->link->network->n_ndisc_nft_set_contexts);
case NETWORK_CONFIG_SOURCE_STATIC:
if (address->family == AF_INET)
return address_del_nft_set_context(address, address->ipv4_nft_set_context, address->n_ipv4_nft_set_contexts);
else
return address_del_nft_set_context(address, address->ipv6_nft_set_context, address->n_ipv6_nft_set_contexts);
default:
return;
}
}
static int address_add(Link *link, Address *address) {
int r;
@ -496,6 +594,8 @@ static int address_update(Address *address) {
address_add_netlabel(address);
address_add_nft_set(address);
if (address_is_ready(address) && address->callback) {
r = address->callback(address);
if (r < 0)
@ -522,6 +622,8 @@ static int address_drop(Address *address) {
if (r < 0)
log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m");
address_del_nft_set(address);
address_del_netlabel(address);
if (address->state == 0)
@ -2084,3 +2186,71 @@ int network_drop_invalid_addresses(Network *network) {
return 0;
}
int config_parse_address_ipv4_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_(address_free_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
assert(network);
r = address_new_static(network, filename, section_line, &n);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to allocate new address, ignoring assignment: %m");
return 0;
}
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->ipv4_nft_set_context, &n->n_ipv4_nft_set_contexts);
}
int config_parse_address_ipv6_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_(address_free_or_set_invalidp) Address *n = NULL;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
assert(network);
r = address_new_static(network, filename, section_line, &n);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to allocate new address, ignoring assignment: %m");
return 0;
}
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->ipv6_nft_set_context, &n->n_ipv6_nft_set_contexts);
}

View File

@ -8,6 +8,7 @@
#include "sd-ipv4acd.h"
#include "conf-parser.h"
#include "firewall-util.h"
#include "in-addr-util.h"
#include "networkd-link.h"
#include "networkd-util.h"
@ -64,6 +65,9 @@ struct Address {
/* NetLabel */
Set *netlabels;
NFTSetContext *ipv4_nft_set_context, *ipv6_nft_set_context;
size_t n_ipv4_nft_set_contexts, n_ipv6_nft_set_contexts;
};
const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) _warn_unused_result_;
@ -139,3 +143,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_address_scope);
CONFIG_PARSER_PROTOTYPE(config_parse_address_route_metric);
CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection);
CONFIG_PARSER_PROTOTYPE(config_parse_address_netlabel);
CONFIG_PARSER_PROTOTYPE(config_parse_address_ipv4_nft_set_context);
CONFIG_PARSER_PROTOTYPE(config_parse_address_ipv6_nft_set_context);

View File

@ -158,6 +158,8 @@ Address.DuplicateAddressDetection, config_parse_duplicate_address_dete
Address.Scope, config_parse_address_scope, 0, 0
Address.RouteMetric, config_parse_address_route_metric, 0, 0
Address.NetLabel, config_parse_address_netlabel, 0, 0
Address.IPv4NFTSet, config_parse_address_ipv4_nft_set_context, 0, 0
Address.IPv6NFTSet, config_parse_address_ipv6_nft_set_context, 0, 0
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
@ -246,6 +248,7 @@ DHCPv4.RouteMTUBytes, config_parse_mtu,
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
DHCPv4.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp_netlabels)
DHCPv4.NFTSet, config_parse_dhcp_nft_set_context, 0, 0
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
@ -264,6 +267,7 @@ DHCPv6.IAID, config_parse_iaid,
DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
DHCPv6.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp6_netlabels)
DHCPv6.NFTSet, config_parse_dhcp6_nft_set_context, 0, 0
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@ -282,6 +286,7 @@ IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes,
IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix)
IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens)
IPv6AcceptRA.NetLabel, config_parse_netlabel, 0, offsetof(Network, ndisc_netlabels)
IPv6AcceptRA.NFTSet, config_parse_ndisc_nft_set_context, 0, 0
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
@ -349,6 +354,7 @@ DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool,
DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens)
DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric)
DHCPPrefixDelegation.NetLabel, config_parse_netlabel, 0, offsetof(Network, dhcp_pd_netlabels)
DHCPPrefixDelegation.NFTSet, config_parse_dhcp_pd_nft_set_context, 0, 0
IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec)
IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)

View File

@ -690,6 +690,8 @@ static Network *network_free(Network *network) {
strv_free(network->dhcp6_vendor_class);
set_free(network->dhcp_netlabels);
set_free(network->dhcp6_netlabels);
nft_set_context_free_many(network->dhcp_nft_set_context, &network->n_dhcp_nft_set_contexts);
nft_set_context_free_many(network->dhcp6_nft_set_context, &network->n_dhcp6_nft_set_contexts);
strv_free(network->ntp);
for (unsigned i = 0; i < network->n_dns; i++)
@ -758,6 +760,8 @@ static Network *network_free(Network *network) {
set_free(network->ndisc_tokens);
set_free(network->dhcp_pd_netlabels);
set_free(network->ndisc_netlabels);
nft_set_context_free_many(network->dhcp_pd_nft_set_context, &network->n_dhcp_pd_nft_set_contexts);
nft_set_context_free_many(network->ndisc_nft_set_context, &network->n_ndisc_nft_set_contexts);
return mfree(network);
}
@ -1302,6 +1306,90 @@ int config_parse_ignore_carrier_loss(
return 0;
}
int config_parse_dhcp_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(network);
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp_nft_set_context, &network->n_dhcp_nft_set_contexts);
}
int config_parse_dhcp6_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(network);
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp6_nft_set_context, &network->n_dhcp6_nft_set_contexts);
}
int config_parse_dhcp_pd_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(network);
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->dhcp_pd_nft_set_context, &network->n_dhcp_pd_nft_set_contexts);
}
int config_parse_ndisc_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(network);
return config_parse_nft_set_context(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &network->ndisc_nft_set_context, &network->n_ndisc_nft_set_contexts);
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily,
"Failed to parse RequiredFamilyForOnline= setting");

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"
@ -156,6 +157,8 @@ struct Network {
OrderedHashmap *dhcp_client_send_options;
OrderedHashmap *dhcp_client_send_vendor_options;
Set *dhcp_netlabels;
NFTSetContext *dhcp_nft_set_context;
size_t n_dhcp_nft_set_contexts;
/* DHCPv6 Client support */
bool dhcp6_use_address;
@ -181,6 +184,8 @@ struct Network {
OrderedHashmap *dhcp6_client_send_vendor_options;
Set *dhcp6_request_options;
Set *dhcp6_netlabels;
NFTSetContext *dhcp6_nft_set_context;
size_t n_dhcp6_nft_set_contexts;
/* DHCP Server Support */
bool dhcp_server;
@ -238,6 +243,8 @@ struct Network {
int dhcp_pd_uplink_index;
char *dhcp_pd_uplink_name;
Set *dhcp_pd_netlabels;
NFTSetContext *dhcp_pd_nft_set_context;
size_t n_dhcp_pd_nft_set_contexts;
/* Bridge Support */
int use_bpdu;
@ -323,6 +330,8 @@ struct Network {
Set *ndisc_allow_listed_route_prefix;
Set *ndisc_tokens;
Set *ndisc_netlabels;
NFTSetContext *ndisc_nft_set_context;
size_t n_ndisc_nft_set_contexts;
/* LLDP support */
LLDPMode lldp_mode; /* LLDP reception */
@ -388,6 +397,10 @@ CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
CONFIG_PARSER_PROTOTYPE(config_parse_activation_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_link_group);
CONFIG_PARSER_PROTOTYPE(config_parse_ignore_carrier_loss);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_nft_set_context);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_nft_set_context);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_nft_set_context);
CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_nft_set_context);
const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length);

View File

@ -14,11 +14,13 @@
#include "sd-netlink.h"
#include "alloc-util.h"
#include "extract-word.h"
#include "firewall-util.h"
#include "firewall-util-private.h"
#include "in-addr-util.h"
#include "macro.h"
#include "socket-util.h"
#include "string-table.h"
#include "time-util.h"
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
@ -848,9 +850,12 @@ static int nft_message_add_setelem_ip6range(
#define NFT_MASQ_MSGS 3
static int fw_nftables_add_masquerade_internal(
FirewallContext *ctx,
static int nft_set_element_op_in_addr(
sd_netlink *nfnl,
const char *table,
const char *set,
bool add,
int nfproto,
int af,
const union in_addr_union *source,
unsigned int source_prefixlen) {
@ -865,14 +870,14 @@ static int fw_nftables_add_masquerade_internal(
if (af == AF_INET6 && source_prefixlen < 8)
return -EINVAL;
r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
if (r < 0)
return r;
tsize = 1;
if (add)
r = sd_nfnl_nft_message_new_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
else
r = sd_nfnl_nft_message_del_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
if (r < 0)
goto out_unref;
@ -885,12 +890,12 @@ static int fw_nftables_add_masquerade_internal(
++tsize;
assert(tsize < NFT_MASQ_MSGS);
r = sd_nfnl_message_batch_end(ctx->nfnl, &transaction[tsize]);
r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
if (r < 0)
return r;
++tsize;
r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize);
r = nfnl_netlink_sendv(nfnl, transaction, tsize);
out_unref:
while (tsize > 0)
@ -898,6 +903,65 @@ out_unref:
return r < 0 ? r : 0;
}
static int nft_set_element_op_in_addr_open(
bool add,
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
_cleanup_free_ char *addr_str = NULL;
int r, nfproto;
const char *table, *set;
assert(nft_set_context);
nfproto = nft_set_context->nfproto;
table = nft_set_context->table;
assert(table);
set = nft_set_context->set;
assert(set);
r = sd_nfnl_socket_open(&nfnl);
if (r < 0)
return r;
r = nft_set_element_op_in_addr(nfnl, table, set,
add, nfproto, af, address, prefixlen);
(void) in_addr_prefix_to_string(af, address, prefixlen, &addr_str);
log_debug("%s NFT family %s table %s set %s IP addresss %s", add? "Added" : "Deleted",
nfproto_to_string(nfproto), table, set, strna(addr_str));
return r;
}
int nft_set_element_add_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
return nft_set_element_op_in_addr_open(true, nft_set_context, af, address, prefixlen);
}
int nft_set_element_del_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
return nft_set_element_op_in_addr_open(false, nft_set_context, af, address, prefixlen);
}
static int fw_nftables_add_masquerade_internal(
FirewallContext *ctx,
bool add,
int af,
const union in_addr_union *source,
unsigned int source_prefixlen) {
return nft_set_element_op_in_addr(ctx->nfnl, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
add, af, af, source, source_prefixlen);
}
int fw_nftables_add_masquerade(
FirewallContext *ctx,
bool add,
@ -1071,3 +1135,222 @@ int fw_nftables_add_local_dnat(
/* table created anew; previous address already gone */
return fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, NULL);
}
static const char *const nfproto_table[] = {
[NFPROTO_ARP] = "arp",
[NFPROTO_BRIDGE] = "bridge",
[NFPROTO_INET] = "inet",
[NFPROTO_IPV4] = "ip",
[NFPROTO_IPV6] = "ip6",
[NFPROTO_NETDEV] = "netdev",
};
DEFINE_STRING_TABLE_LOOKUP(nfproto, int);
#define NFT_SET_MSGS 3
static int nft_set_element_op(bool add, const NFTSetContext *nft_set_context, void *element, size_t element_size) {
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
sd_netlink_message *transaction[NFT_SET_MSGS] = {};
_cleanup_free_ uint32_t *serial = NULL;
size_t tsize;
int r, nfproto;
const char *table, *set;
assert(nft_set_context);
nfproto = nft_set_context->nfproto;
table = nft_set_context->table;
assert(table);
set = nft_set_context->set;
assert(set);
assert(element);
r = sd_nfnl_socket_open(&nfnl);
if (r < 0)
return r;
r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
if (r < 0)
return r;
tsize = 1;
if (add)
r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
else
r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
if (r < 0)
goto out_unref;
r = sd_nfnl_nft_message_add_setelem(transaction[tsize], 0, element, element_size, NULL, 0);
if (r < 0)
return r;
r = sd_nfnl_nft_message_add_setelem_end(transaction[tsize]);
if (r < 0)
return r;
++tsize;
assert(tsize < ELEMENTSOF(transaction));
r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
if (r < 0)
return r;
++tsize;
r = sd_netlink_sendv(nfnl, transaction, tsize, &serial);
out_unref:
while (tsize > 0)
sd_netlink_message_unref(transaction[--tsize]);
return r < 0 ? r : 0;
}
int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Added NFT family %s table %s set %s element %d",
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Deleted NFT family %s table %s set %s element %d",
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Added NFT family %s table %s set %s element %"PRIu64,
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Deleted NFT family %s table %s set %s element %"PRIu64,
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n) {
assert(n);
assert(s || *n == 0);
for (size_t i = 0; i < *n; i++) {
free(s[i].table);
free(s[i].set);
}
free(s);
*n = 0;
return NULL;
}
int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set) {
_cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
assert(s);
assert(n);
table_dup = strdup(table);
if (!table_dup)
return -ENOMEM;
set_dup = strdup(set);
if (!set_dup)
return -ENOMEM;
NFTSetContext *c;
c = reallocarray(*s, *n + 1, sizeof(NFTSetContext));
if (!c)
return -ENOMEM;
*s = c;
c[(*n) ++] = (NFTSetContext) {
.nfproto = nfproto,
.table = TAKE_PTR(table_dup),
.set = TAKE_PTR(set_dup),
};
return 0;
}
int config_parse_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
NFTSetContext **nft_set_context,
size_t *n) {
_cleanup_free_ char *family_str = NULL, *table = NULL, *set = NULL;
int nfproto, r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(nft_set_context);
if (isempty(rvalue)) {
nft_set_context_free_many(*nft_set_context, n);
return 0;
}
for (const char *p = rvalue;;) {
r = extract_many_words(&p, ":" WHITESPACE, EXTRACT_CUNESCAPE, &family_str, &table, &set, NULL);
if (r == -ENOMEM)
return log_oom();
if (r == 0)
return 0;
if (r != 3) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse IPvxNFT set, ignoring: %s", rvalue);
return 0;
}
nfproto = nfproto_from_string(family_str);
if (nfproto < 0) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family, ignoring: %s", family_str);
return 0;
}
if (nft_identifier_bad(table))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", table);
if (nft_identifier_bad(set))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", set);
NFTSetContext *c;
c = reallocarray(*nft_set_context, *n + 1, sizeof(NFTSetContext));
if (!c)
return -ENOMEM;
*nft_set_context = c;
c[(*n) ++] = (NFTSetContext) {
.nfproto = nfproto,
.table = TAKE_PTR(table),
.set = TAKE_PTR(set),
};
}
return 0;
}

View File

@ -29,3 +29,43 @@ int fw_add_local_dnat(
const union in_addr_union *remote,
uint16_t remote_port,
const union in_addr_union *previous_remote);
struct NFTSetContext {
int nfproto;
char *table;
char *set;
};
typedef struct NFTSetContext NFTSetContext;
int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set);
NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n);
int config_parse_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
NFTSetContext **nft_set_context,
size_t *n);
const char *nfproto_to_string(int i) _const_;
int nfproto_from_string(const char *s) _pure_;
int nft_set_element_add_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen);
int nft_set_element_del_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen);
int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element);
int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element);
int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element);
int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element);

View File

@ -672,6 +672,9 @@ tests += [
[files('test-hmac.c')],
[files('test-sha256.c')],
[files('test-nft-set.c'),
[], [], [], '', 'manual'],
]
############################################################

69
src/test/test-nft-set.c Normal file
View File

@ -0,0 +1,69 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <assert.h>
#include <unistd.h>
#include "firewall-util.h"
#include "in-addr-util.h"
#include "log.h"
#include "parse-util.h"
#include "string-util.h"
#include "tests.h"
int main(int argc, char **argv) {
int r;
assert_se(argc == 7);
test_setup_logging(LOG_DEBUG);
if (getuid() != 0)
return log_tests_skipped("not root");
int nfproto;
nfproto = nfproto_from_string(argv[2]);
assert_se(nfproto > 0);
const NFTSetContext nft_set_context = {
.nfproto = nfproto,
.table = argv[3],
.set = argv[4],
};
if (streq(argv[5], "uint32")) {
uint32_t element;
r = safe_atou32(argv[6], &element);
assert_se(r == 0);
if (streq(argv[1], "add"))
r = nft_set_element_add_uint32(&nft_set_context, element);
else
r = nft_set_element_del_uint32(&nft_set_context, element);
assert_se(r == 0);
} else if (streq(argv[5], "uint64")) {
uint64_t element;
r = safe_atou64(argv[6], &element);
assert_se(r == 0);
if (streq(argv[1], "add"))
r = nft_set_element_add_uint64(&nft_set_context, element);
else
r = nft_set_element_del_uint64(&nft_set_context, element);
assert_se(r == 0);
} else {
union in_addr_union addr;
int af;
unsigned char prefixlen;
r = in_addr_prefix_from_string_auto(argv[6], &af, &addr, &prefixlen);
assert_se(r == 0);
if (streq(argv[1], "add"))
r = nft_set_element_add_in_addr(&nft_set_context, af, &addr, prefixlen);
else
r = nft_set_element_del_in_addr(&nft_set_context, af, &addr, prefixlen);
assert_se(r == 0);
}
return 0;
}

View File

@ -132,6 +132,7 @@ RouteMTUBytes=
FallbackLeaseLifetimeSec=
Use6RD=
NetLabel=
NFTSet=
[DHCPv6]
UseAddress=
UseDelegatedPrefix=
@ -154,6 +155,7 @@ IAID=
DUIDType=
DUIDRawData=
NetLabel=
NFTSet=
[DHCPv6PrefixDelegation]
SubnetId=
Announce=
@ -171,6 +173,7 @@ ManageTemporaryAddress=
Token=
RouteMetric=
NetLabel=
NFTSet=
[Route]
Destination=
Protocol=
@ -257,6 +260,8 @@ DHCPv6PrefixDelegation=
DHCPPrefixDelegation=
BatmanAdvanced=
IPoIB=
IPv4NFTSet=
IPv6NFTSet=
[IPv6Prefix]
Prefix=
OnLink=
@ -348,6 +353,7 @@ Managed=
OtherInformation=
UplinkInterface=
NetLabel=
NFTSet=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=