Merge pull request #24570 from topimiettinen/nft-sets-v2
network: firewall integration with NFT sets
This commit is contained in:
@@ -1324,6 +1324,86 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
</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>
|
||||
|
||||
@@ -2497,6 +2577,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
</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>
|
||||
|
||||
@@ -2656,6 +2747,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>
|
||||
@@ -2767,6 +2869,17 @@ allow my_server_t localnet_peer_t:peer recv;</programlisting>
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
</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>
|
||||
|
||||
@@ -3119,6 +3232,17 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
|
||||
<xi:include href="version-info.xml" xpointer="v252"/>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -170,6 +170,8 @@ int netlink_add_match_internal(
|
||||
#define NETLINK_DONT_DESTROY(nl) \
|
||||
_cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##nl = sd_netlink_ref(nl)
|
||||
|
||||
bool nfproto_is_valid(int nfproto);
|
||||
|
||||
/* nfnl */
|
||||
/* TODO: to be exported later */
|
||||
int sd_nfnl_socket_open(sd_netlink **ret);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "netlink-types.h"
|
||||
#include "netlink-util.h"
|
||||
|
||||
static bool nfproto_is_valid(int nfproto) {
|
||||
bool nfproto_is_valid(int nfproto) {
|
||||
return IN_SET(nfproto,
|
||||
NFPROTO_UNSPEC,
|
||||
NFPROTO_INET,
|
||||
@@ -20,8 +20,7 @@ static bool nfproto_is_valid(int nfproto) {
|
||||
NFPROTO_ARP,
|
||||
NFPROTO_NETDEV,
|
||||
NFPROTO_BRIDGE,
|
||||
NFPROTO_IPV6,
|
||||
NFPROTO_DECNET);
|
||||
NFPROTO_IPV6);
|
||||
}
|
||||
|
||||
int sd_nfnl_message_new(sd_netlink *nfnl, sd_netlink_message **ret, int nfproto, uint16_t subsys, uint16_t msg_type, uint16_t flags) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -161,6 +161,7 @@ Address.DuplicateAddressDetection, config_parse_duplicate_address_dete
|
||||
Address.Scope, config_parse_address_scope, 0, 0
|
||||
Address.RouteMetric, config_parse_address_route_metric, 0, 0
|
||||
Address.NetLabel, config_parse_address_netlabel, 0, 0
|
||||
Address.NFTSet, config_parse_address_ip_nft_set, 0, 0
|
||||
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
|
||||
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
|
||||
Neighbor.Address, config_parse_neighbor_address, 0, 0
|
||||
@@ -257,6 +258,7 @@ DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window,
|
||||
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
|
||||
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
|
||||
DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel)
|
||||
DHCPv4.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp_nft_set_context)
|
||||
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
|
||||
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
|
||||
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
|
||||
@@ -278,6 +280,7 @@ DHCPv6.DUIDRawData, config_parse_duid_rawdata,
|
||||
DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
|
||||
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
|
||||
DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release)
|
||||
DHCPv6.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp6_nft_set_context)
|
||||
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
|
||||
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
|
||||
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
|
||||
@@ -300,6 +303,7 @@ IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes,
|
||||
IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix)
|
||||
IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens)
|
||||
IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel)
|
||||
IPv6AcceptRA.NFTSet, config_parse_nft_set, 0, offsetof(Network, ndisc_nft_set_context)
|
||||
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
|
||||
DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
|
||||
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
|
||||
@@ -367,6 +371,7 @@ DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool,
|
||||
DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens)
|
||||
DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric)
|
||||
DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel)
|
||||
DHCPPrefixDelegation.NFTSet, config_parse_nft_set, 0, offsetof(Network, dhcp_pd_nft_set_context)
|
||||
IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec)
|
||||
IPv6SendRA.RetransmitSec, config_parse_router_retransmit, 0, offsetof(Network, router_retransmit_usec)
|
||||
IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
|
||||
|
||||
@@ -725,6 +725,7 @@ static Network *network_free(Network *network) {
|
||||
ordered_hashmap_free(network->dhcp_client_send_options);
|
||||
ordered_hashmap_free(network->dhcp_client_send_vendor_options);
|
||||
free(network->dhcp_netlabel);
|
||||
nft_set_context_clear(&network->dhcp_nft_set_context);
|
||||
|
||||
/* DHCPv6 client */
|
||||
free(network->dhcp6_mudurl);
|
||||
@@ -734,11 +735,13 @@ static Network *network_free(Network *network) {
|
||||
ordered_hashmap_free(network->dhcp6_client_send_options);
|
||||
ordered_hashmap_free(network->dhcp6_client_send_vendor_options);
|
||||
free(network->dhcp6_netlabel);
|
||||
nft_set_context_clear(&network->dhcp6_nft_set_context);
|
||||
|
||||
/* DHCP PD */
|
||||
free(network->dhcp_pd_uplink_name);
|
||||
set_free(network->dhcp_pd_tokens);
|
||||
free(network->dhcp_pd_netlabel);
|
||||
nft_set_context_clear(&network->dhcp_pd_nft_set_context);
|
||||
|
||||
/* Router advertisement */
|
||||
ordered_set_free(network->router_search_domains);
|
||||
@@ -754,6 +757,7 @@ static Network *network_free(Network *network) {
|
||||
set_free(network->ndisc_allow_listed_route_prefix);
|
||||
set_free(network->ndisc_tokens);
|
||||
free(network->ndisc_netlabel);
|
||||
nft_set_context_clear(&network->ndisc_nft_set_context);
|
||||
|
||||
/* LLDP */
|
||||
free(network->lldp_mudurl);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "bridge.h"
|
||||
#include "condition.h"
|
||||
#include "conf-parser.h"
|
||||
#include "firewall-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "ipoib.h"
|
||||
#include "net-condition.h"
|
||||
@@ -163,6 +164,7 @@ struct Network {
|
||||
OrderedHashmap *dhcp_client_send_options;
|
||||
OrderedHashmap *dhcp_client_send_vendor_options;
|
||||
char *dhcp_netlabel;
|
||||
NFTSetContext dhcp_nft_set_context;
|
||||
|
||||
/* DHCPv6 Client support */
|
||||
bool dhcp6_use_address;
|
||||
@@ -191,6 +193,7 @@ struct Network {
|
||||
Set *dhcp6_request_options;
|
||||
char *dhcp6_netlabel;
|
||||
bool dhcp6_send_release;
|
||||
NFTSetContext dhcp6_nft_set_context;
|
||||
|
||||
/* DHCP Server Support */
|
||||
bool dhcp_server;
|
||||
@@ -251,6 +254,7 @@ struct Network {
|
||||
int dhcp_pd_uplink_index;
|
||||
char *dhcp_pd_uplink_name;
|
||||
char *dhcp_pd_netlabel;
|
||||
NFTSetContext dhcp_pd_nft_set_context;
|
||||
|
||||
/* Bridge Support */
|
||||
int use_bpdu;
|
||||
@@ -343,6 +347,7 @@ struct Network {
|
||||
Set *ndisc_allow_listed_route_prefix;
|
||||
Set *ndisc_tokens;
|
||||
char *ndisc_netlabel;
|
||||
NFTSetContext ndisc_nft_set_context;
|
||||
|
||||
/* LLDP support */
|
||||
LLDPMode lldp_mode; /* LLDP reception */
|
||||
|
||||
+303
-26
@@ -14,6 +14,8 @@
|
||||
#include "sd-netlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "escape.h"
|
||||
#include "extract-word.h"
|
||||
#include "firewall-util.h"
|
||||
#include "firewall-util-private.h"
|
||||
#include "in-addr-util.h"
|
||||
@@ -21,6 +23,7 @@
|
||||
#include "netlink-internal.h"
|
||||
#include "netlink-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-table.h"
|
||||
#include "time-util.h"
|
||||
|
||||
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
|
||||
@@ -598,7 +601,8 @@ static int nft_new_map(
|
||||
static int nft_add_element(
|
||||
sd_netlink *nfnl,
|
||||
sd_netlink_message **ret,
|
||||
int family,
|
||||
int nfproto,
|
||||
const char *table_name,
|
||||
const char *set_name,
|
||||
const void *key,
|
||||
uint32_t klen,
|
||||
@@ -610,10 +614,12 @@ static int nft_add_element(
|
||||
|
||||
assert(nfnl);
|
||||
assert(ret);
|
||||
assert(IN_SET(family, AF_INET, AF_INET6));
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table_name);
|
||||
assert(set_name);
|
||||
assert(key);
|
||||
assert(data);
|
||||
assert(data || dlen == 0);
|
||||
|
||||
|
||||
/*
|
||||
* Ideally there would be an API that provides:
|
||||
@@ -628,7 +634,7 @@ static int nft_add_element(
|
||||
* This replicated here and each element gets added to the set
|
||||
* one-by-one.
|
||||
*/
|
||||
r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, family, NFT_SYSTEMD_TABLE_NAME, set_name);
|
||||
r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, nfproto, table_name, set_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -653,7 +659,8 @@ static int nft_add_element(
|
||||
static int nft_del_element(
|
||||
sd_netlink *nfnl,
|
||||
sd_netlink_message **ret,
|
||||
int family,
|
||||
int nfproto,
|
||||
const char *table_name,
|
||||
const char *set_name,
|
||||
const void *key,
|
||||
uint32_t klen,
|
||||
@@ -665,12 +672,13 @@ static int nft_del_element(
|
||||
|
||||
assert(nfnl);
|
||||
assert(ret);
|
||||
assert(IN_SET(family, AF_INET, AF_INET6));
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table_name);
|
||||
assert(set_name);
|
||||
assert(key);
|
||||
assert(data);
|
||||
assert(data || dlen == 0);
|
||||
|
||||
r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, family, NFT_SYSTEMD_TABLE_NAME, set_name);
|
||||
r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, nfproto, table_name, set_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -791,7 +799,7 @@ static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fw_nftables_init(FirewallContext *ctx) {
|
||||
int fw_nftables_init_full(FirewallContext *ctx, bool init_tables) {
|
||||
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
|
||||
int r;
|
||||
|
||||
@@ -802,20 +810,26 @@ int fw_nftables_init(FirewallContext *ctx) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = fw_nftables_init_family(nfnl, AF_INET);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (socket_ipv6_is_supported()) {
|
||||
r = fw_nftables_init_family(nfnl, AF_INET6);
|
||||
if (init_tables) {
|
||||
r = fw_nftables_init_family(nfnl, AF_INET);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to init ipv6 NAT: %m");
|
||||
return r;
|
||||
|
||||
if (socket_ipv6_is_supported()) {
|
||||
r = fw_nftables_init_family(nfnl, AF_INET6);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to init ipv6 NAT: %m");
|
||||
}
|
||||
}
|
||||
|
||||
ctx->nfnl = TAKE_PTR(nfnl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fw_nftables_init(FirewallContext *ctx) {
|
||||
return fw_nftables_init_full(ctx, /* init_tables= */ true);
|
||||
}
|
||||
|
||||
void fw_nftables_exit(FirewallContext *ctx) {
|
||||
assert(ctx);
|
||||
|
||||
@@ -891,18 +905,24 @@ static int nft_message_append_setelem_ip6range(
|
||||
return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
|
||||
}
|
||||
|
||||
static int fw_nftables_add_masquerade_internal(
|
||||
sd_netlink *nfnl,
|
||||
int nft_set_element_modify_iprange(
|
||||
FirewallContext *ctx,
|
||||
bool add,
|
||||
int nfproto,
|
||||
int af,
|
||||
const char *table,
|
||||
const char *set,
|
||||
const union in_addr_union *source,
|
||||
unsigned int source_prefixlen) {
|
||||
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
assert(nfnl);
|
||||
assert(ctx->nfnl);
|
||||
assert(IN_SET(af, AF_INET, AF_INET6));
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table);
|
||||
assert(set);
|
||||
|
||||
if (!source || source_prefixlen == 0)
|
||||
return -EINVAL;
|
||||
@@ -910,7 +930,7 @@ static int fw_nftables_add_masquerade_internal(
|
||||
if (af == AF_INET6 && source_prefixlen < 8)
|
||||
return -EINVAL;
|
||||
|
||||
r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
|
||||
r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -921,7 +941,81 @@ static int fw_nftables_add_masquerade_internal(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
|
||||
return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
|
||||
}
|
||||
|
||||
int nft_set_element_modify_ip(
|
||||
FirewallContext *ctx,
|
||||
bool add,
|
||||
int nfproto,
|
||||
int af,
|
||||
const char *table,
|
||||
const char *set,
|
||||
const union in_addr_union *source) {
|
||||
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
assert(ctx->nfnl);
|
||||
assert(IN_SET(af, AF_INET, AF_INET6));
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table);
|
||||
assert(set);
|
||||
|
||||
if (!source)
|
||||
return -EINVAL;
|
||||
|
||||
r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_nfnl_nft_message_append_setelem(m, 0, source, FAMILY_ADDRESS_SIZE(af), NULL, 0, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
|
||||
}
|
||||
|
||||
int nft_set_element_modify_any(FirewallContext *ctx, bool add, int nfproto, const char *table, const char *set, const void *element, size_t element_size) {
|
||||
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
||||
int r;
|
||||
|
||||
assert(ctx);
|
||||
assert(ctx->nfnl);
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table);
|
||||
assert(set);
|
||||
assert(element);
|
||||
|
||||
if (add)
|
||||
r = nft_add_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
|
||||
else
|
||||
r = nft_del_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL);
|
||||
}
|
||||
|
||||
static int af_to_nfproto(int af) {
|
||||
assert(IN_SET(af, AF_INET, AF_INET6));
|
||||
|
||||
switch (af) {
|
||||
case AF_INET:
|
||||
return NFPROTO_IPV4;
|
||||
case AF_INET6:
|
||||
return NFPROTO_IPV6;
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
int fw_nftables_add_masquerade(
|
||||
@@ -940,7 +1034,8 @@ int fw_nftables_add_masquerade(
|
||||
if (!socket_ipv6_is_supported() && af == AF_INET6)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
r = fw_nftables_add_masquerade_internal(ctx->nfnl, add, af, source, source_prefixlen);
|
||||
r = nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
|
||||
source, source_prefixlen);
|
||||
if (r != -ENOENT)
|
||||
return r;
|
||||
|
||||
@@ -965,7 +1060,8 @@ int fw_nftables_add_masquerade(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return fw_nftables_add_masquerade_internal(ctx->nfnl, add, af, source, source_prefixlen);
|
||||
return nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
|
||||
source, source_prefixlen);
|
||||
}
|
||||
|
||||
static int fw_nftables_add_local_dnat_internal(
|
||||
@@ -1023,7 +1119,8 @@ static int fw_nftables_add_local_dnat_internal(
|
||||
else
|
||||
memcpy(data, &previous_remote->in6, sizeof(previous_remote->in6));
|
||||
|
||||
r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
|
||||
r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
|
||||
key, sizeof(key), data, dlen);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -1034,9 +1131,11 @@ static int fw_nftables_add_local_dnat_internal(
|
||||
memcpy(data, &remote->in6, sizeof(remote->in6));
|
||||
|
||||
if (add)
|
||||
r = nft_add_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
|
||||
r = nft_add_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
|
||||
key, sizeof(key), data, dlen);
|
||||
else
|
||||
r = nft_del_element(nfnl, &messages[msgcnt++], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
|
||||
r = nft_del_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_DNAT_MAP_NAME,
|
||||
key, sizeof(key), data, dlen);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1087,3 +1186,181 @@ int fw_nftables_add_local_dnat(
|
||||
/* table created anew; previous address already gone */
|
||||
return fw_nftables_add_local_dnat_internal(ctx->nfnl, add, af, protocol, local_port, remote, remote_port, NULL);
|
||||
}
|
||||
|
||||
static const char *const nfproto_table[] = {
|
||||
[NFPROTO_ARP] = "arp",
|
||||
[NFPROTO_BRIDGE] = "bridge",
|
||||
[NFPROTO_INET] = "inet",
|
||||
[NFPROTO_IPV4] = "ip",
|
||||
[NFPROTO_IPV6] = "ip6",
|
||||
[NFPROTO_NETDEV] = "netdev",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(nfproto, int);
|
||||
|
||||
static const char *const nft_set_source_table[] = {
|
||||
[NFT_SET_SOURCE_ADDRESS] = "address",
|
||||
[NFT_SET_SOURCE_PREFIX] = "prefix",
|
||||
[NFT_SET_SOURCE_IFINDEX] = "ifindex",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int);
|
||||
|
||||
void nft_set_context_clear(NFTSetContext *s) {
|
||||
assert(s);
|
||||
|
||||
FOREACH_ARRAY(nft_set, s->sets, s->n_sets) {
|
||||
free(nft_set->table);
|
||||
free(nft_set->set);
|
||||
}
|
||||
|
||||
s->n_sets = 0;
|
||||
s->sets = mfree(s->sets);
|
||||
}
|
||||
|
||||
static int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) {
|
||||
_cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
|
||||
|
||||
assert(s);
|
||||
assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX));
|
||||
assert(nfproto_is_valid(nfproto));
|
||||
assert(table);
|
||||
assert(set);
|
||||
|
||||
table_dup = strdup(table);
|
||||
if (!table_dup)
|
||||
return -ENOMEM;
|
||||
|
||||
set_dup = strdup(set);
|
||||
if (!set_dup)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!GREEDY_REALLOC(s->sets, s->n_sets + 1))
|
||||
return -ENOMEM;
|
||||
|
||||
s->sets[s->n_sets++] = (NFTSet) {
|
||||
.source = source,
|
||||
.nfproto = nfproto,
|
||||
.table = TAKE_PTR(table_dup),
|
||||
.set = TAKE_PTR(set_dup),
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst) {
|
||||
int r;
|
||||
_cleanup_(nft_set_context_clear) NFTSetContext d = (NFTSetContext) {};
|
||||
|
||||
assert(src);
|
||||
assert(dst);
|
||||
|
||||
FOREACH_ARRAY(nft_set, src->sets, src->n_sets) {
|
||||
r = nft_set_add(&d, nft_set->source, nft_set->nfproto, nft_set->table, nft_set->set);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*dst = TAKE_STRUCT(d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_nft_set(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
NFTSetContext *nft_set_context = ASSERT_PTR(data);
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(nft_set_context);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
nft_set_context_clear(nft_set_context);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const char *p = rvalue;;) {
|
||||
_cleanup_free_ char *tuple = NULL, *source_str = NULL, *family_str = NULL, *table = NULL, *set = NULL;
|
||||
const char *q = NULL;
|
||||
int nfproto;
|
||||
NFTSetSource source;
|
||||
|
||||
r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(rvalue);
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax %s=%s, ignoring: %m", lvalue, strna(esc));
|
||||
return 0;
|
||||
}
|
||||
if (r == 0)
|
||||
return 0;
|
||||
|
||||
q = tuple;
|
||||
r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set, NULL);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r != 4 || !isempty(q)) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(tuple);
|
||||
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse NFT set %s, ignoring", strna(esc));
|
||||
}
|
||||
|
||||
assert(source_str);
|
||||
assert(family_str);
|
||||
assert(table);
|
||||
assert(set);
|
||||
|
||||
source = nft_set_source_from_string(source_str);
|
||||
if (source < 0) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(source_str);
|
||||
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT source %s, ignoring", strna(esc));
|
||||
}
|
||||
|
||||
nfproto = nfproto_from_string(family_str);
|
||||
if (nfproto < 0) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(family_str);
|
||||
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family %s, ignoring", strna(esc));
|
||||
}
|
||||
|
||||
if (!nft_identifier_valid(table)) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(table);
|
||||
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", strna(esc));
|
||||
}
|
||||
|
||||
if (!nft_identifier_valid(set)) {
|
||||
_cleanup_free_ char *esc = NULL;
|
||||
|
||||
esc = cescape(set);
|
||||
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", strna(esc));
|
||||
}
|
||||
|
||||
r = nft_set_add(nft_set_context, source, nfproto, table, set);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ struct FirewallContext {
|
||||
const char *firewall_backend_to_string(FirewallBackend b) _const_;
|
||||
|
||||
int fw_nftables_init(FirewallContext *ctx);
|
||||
int fw_nftables_init_full(FirewallContext *ctx, bool init_tables);
|
||||
void fw_nftables_exit(FirewallContext *ctx);
|
||||
|
||||
int fw_nftables_add_masquerade(
|
||||
|
||||
@@ -20,13 +20,13 @@ static const char * const firewall_backend_table[_FW_BACKEND_MAX] = {
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(firewall_backend, FirewallBackend);
|
||||
|
||||
static void firewall_backend_probe(FirewallContext *ctx) {
|
||||
static void firewall_backend_probe(FirewallContext *ctx, bool init_tables) {
|
||||
assert(ctx);
|
||||
|
||||
if (ctx->backend != _FW_BACKEND_INVALID)
|
||||
return;
|
||||
|
||||
if (fw_nftables_init(ctx) >= 0)
|
||||
if (fw_nftables_init_full(ctx, init_tables) >= 0)
|
||||
ctx->backend = FW_BACKEND_NFTABLES;
|
||||
else
|
||||
#if HAVE_LIBIPTC
|
||||
@@ -41,7 +41,7 @@ static void firewall_backend_probe(FirewallContext *ctx) {
|
||||
log_debug("No firewall backend found.");
|
||||
}
|
||||
|
||||
int fw_ctx_new(FirewallContext **ret) {
|
||||
int fw_ctx_new_full(FirewallContext **ret, bool init_tables) {
|
||||
_cleanup_free_ FirewallContext *ctx = NULL;
|
||||
|
||||
ctx = new(FirewallContext, 1);
|
||||
@@ -52,12 +52,16 @@ int fw_ctx_new(FirewallContext **ret) {
|
||||
.backend = _FW_BACKEND_INVALID,
|
||||
};
|
||||
|
||||
firewall_backend_probe(ctx);
|
||||
firewall_backend_probe(ctx, init_tables);
|
||||
|
||||
*ret = TAKE_PTR(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fw_ctx_new(FirewallContext **ret) {
|
||||
return fw_ctx_new_full(ret, /* init_tables= */ true);
|
||||
}
|
||||
|
||||
FirewallContext *fw_ctx_free(FirewallContext *ctx) {
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "conf-parser.h"
|
||||
#include "in-addr-util.h"
|
||||
|
||||
typedef struct FirewallContext FirewallContext;
|
||||
|
||||
int fw_ctx_new(FirewallContext **ret);
|
||||
int fw_ctx_new_full(FirewallContext **ret, bool init_tables);
|
||||
FirewallContext *fw_ctx_free(FirewallContext *ctx);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(FirewallContext *, fw_ctx_free);
|
||||
@@ -29,3 +31,62 @@ int fw_add_local_dnat(
|
||||
const union in_addr_union *remote,
|
||||
uint16_t remote_port,
|
||||
const union in_addr_union *previous_remote);
|
||||
|
||||
typedef enum NFTSetSource {
|
||||
NFT_SET_SOURCE_ADDRESS,
|
||||
NFT_SET_SOURCE_PREFIX,
|
||||
NFT_SET_SOURCE_IFINDEX,
|
||||
_NFT_SET_SOURCE_MAX,
|
||||
_NFT_SET_SOURCE_INVALID = -EINVAL,
|
||||
} NFTSetSource;
|
||||
|
||||
typedef struct NFTSet {
|
||||
NFTSetSource source;
|
||||
int nfproto;
|
||||
char *table;
|
||||
char *set;
|
||||
} NFTSet;
|
||||
|
||||
typedef struct NFTSetContext {
|
||||
NFTSet *sets;
|
||||
size_t n_sets;
|
||||
} NFTSetContext;
|
||||
|
||||
void nft_set_context_clear(NFTSetContext *s);
|
||||
int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst);
|
||||
|
||||
const char *nfproto_to_string(int i) _const_;
|
||||
int nfproto_from_string(const char *s) _pure_;
|
||||
|
||||
const char *nft_set_source_to_string(int i) _const_;
|
||||
int nft_set_source_from_string(const char *s) _pure_;
|
||||
|
||||
int nft_set_element_modify_iprange(
|
||||
FirewallContext *ctx,
|
||||
bool add,
|
||||
int nfproto,
|
||||
int af,
|
||||
const char *table,
|
||||
const char *set,
|
||||
const union in_addr_union *source,
|
||||
unsigned int source_prefixlen);
|
||||
|
||||
int nft_set_element_modify_ip(
|
||||
FirewallContext *ctx,
|
||||
bool add,
|
||||
int nfproto,
|
||||
int af,
|
||||
const char *table,
|
||||
const char *set,
|
||||
const union in_addr_union *source);
|
||||
|
||||
int nft_set_element_modify_any(
|
||||
FirewallContext *ctx,
|
||||
bool add,
|
||||
int nfproto,
|
||||
const char *table,
|
||||
const char *set,
|
||||
const void *element,
|
||||
size_t element_size);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_nft_set);
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -177,3 +177,4 @@ Address=::/73
|
||||
Address=10.10.1.1/24
|
||||
# just a random label which should exist
|
||||
NetLabel=system_u:object_r:root_t:s0
|
||||
NFTSet=address:inet:sd_test:addr4 prefix:inet:sd_test:network4 ifindex:inet:sd_test:ifindex
|
||||
|
||||
@@ -28,6 +28,7 @@ AllowList=192.168.5.0/24 192.168.6.0/24
|
||||
DenyList=192.168.5.0/24
|
||||
# just a random label which should exist
|
||||
NetLabel=system_u:object_r:root_t:s0
|
||||
NFTSet=address:inet:sd_test:addr4 prefix:inet:sd_test:network4 ifindex:inet:sd_test:ifindex
|
||||
|
||||
[Route]
|
||||
Destination=192.168.5.0/24
|
||||
|
||||
@@ -16,3 +16,4 @@ Token=eui64
|
||||
Token=::1a:2b:3c:4d
|
||||
# just a random label which should exist
|
||||
NetLabel=system_u:object_r:root_t:s0
|
||||
NFTSet=address:inet:sd_test:addr6 prefix:inet:sd_test:network6 ifindex:inet:sd_test:ifindex
|
||||
|
||||
@@ -9,3 +9,4 @@ IPv6AcceptRA=true
|
||||
UseDomains=yes
|
||||
# just a random label which should exist
|
||||
NetLabel=system_u:object_r:root_t:s0
|
||||
NFTSet=address:inet:sd_test:addr6 prefix:inet:sd_test:network6 ifindex:inet:sd_test:ifindex
|
||||
|
||||
@@ -944,6 +944,37 @@ class Utilities():
|
||||
print(output)
|
||||
self.assertRegex(output, f'interface:{interface},address:{address},label:"{label}"')
|
||||
|
||||
def setup_nftset(self, filter_name, filter_type, flags=''):
|
||||
if not shutil.which('nft'):
|
||||
print('## Setting up NFT sets skipped: nft command not found.')
|
||||
else:
|
||||
if call(f'nft add table inet sd_test') != 0:
|
||||
print('## Setting up NFT table failed.')
|
||||
self.fail()
|
||||
if call(f'nft add set inet sd_test {filter_name} {{ type {filter_type}; {flags} }}') != 0:
|
||||
print('## Setting up NFT sets failed.')
|
||||
self.fail()
|
||||
|
||||
def teardown_nftset(self, *filters):
|
||||
if not shutil.which('nft'):
|
||||
print('## Tearing down NFT sets skipped: nft command not found.')
|
||||
else:
|
||||
for filter_name in filters:
|
||||
if call(f'nft delete set inet sd_test {filter_name}') != 0:
|
||||
print('## Tearing down NFT sets failed.')
|
||||
self.fail()
|
||||
if call(f'nft delete table inet sd_test') != 0:
|
||||
print('## Tearing down NFT table failed.')
|
||||
self.fail()
|
||||
|
||||
def check_nftset(self, filter_name, contents):
|
||||
if not shutil.which('nft'):
|
||||
print('## Checking NFT sets skipped: nft command not found.')
|
||||
else:
|
||||
output = check_output(f'nft list set inet sd_test {filter_name}')
|
||||
print(output)
|
||||
self.assertRegex(output, r'.*elements = { [^}]*' + contents + r'[^}]* }.*')
|
||||
|
||||
class NetworkctlTests(unittest.TestCase, Utilities):
|
||||
|
||||
def setUp(self):
|
||||
@@ -2432,6 +2463,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||
def test_address_static(self):
|
||||
copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False)
|
||||
start_networkd()
|
||||
self.setup_nftset('addr4', 'ipv4_addr')
|
||||
self.setup_nftset('network4', 'ipv4_addr', 'flags interval;')
|
||||
self.setup_nftset('ifindex', 'iface_index')
|
||||
|
||||
self.wait_online(['dummy98:routable'])
|
||||
self.verify_address_static(
|
||||
@@ -2459,6 +2493,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||
flag3=' noprefixroute',
|
||||
flag4=' home mngtmpaddr',
|
||||
)
|
||||
# nft set
|
||||
self.check_nftset('addr4', r'10\.10\.1\.1')
|
||||
self.check_nftset('network4', r'10\.10\.1\.0/24')
|
||||
self.check_nftset('ifindex', 'dummy98')
|
||||
|
||||
self.teardown_nftset('addr4', 'network4', 'ifindex')
|
||||
|
||||
copy_network_unit('25-address-static.network.d/10-override.conf')
|
||||
networkctl_reload()
|
||||
@@ -4702,6 +4742,9 @@ class NetworkdRATests(unittest.TestCase, Utilities):
|
||||
|
||||
def test_ipv6_prefix_delegation(self):
|
||||
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth.network')
|
||||
self.setup_nftset('addr6', 'ipv6_addr')
|
||||
self.setup_nftset('network6', 'ipv6_addr', 'flags interval;')
|
||||
self.setup_nftset('ifindex', 'iface_index')
|
||||
start_networkd()
|
||||
self.wait_online(['veth99:routable', 'veth-peer:degraded'])
|
||||
|
||||
@@ -4721,6 +4764,14 @@ class NetworkdRATests(unittest.TestCase, Utilities):
|
||||
self.check_netlabel('veth99', '2002:da8:1::/64')
|
||||
self.check_netlabel('veth99', '2002:da8:2::/64')
|
||||
|
||||
self.check_nftset('addr6', '2002:da8:1:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
|
||||
self.check_nftset('addr6', '2002:da8:2:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
|
||||
self.check_nftset('network6', '2002:da8:1::/64')
|
||||
self.check_nftset('network6', '2002:da8:2::/64')
|
||||
self.check_nftset('ifindex', 'veth99')
|
||||
|
||||
self.teardown_nftset('addr6', 'network6', 'ifindex')
|
||||
|
||||
def test_ipv6_token_static(self):
|
||||
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
|
||||
start_networkd()
|
||||
@@ -5048,6 +5099,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
def test_dhcp_client_ipv4_only(self):
|
||||
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network')
|
||||
|
||||
self.setup_nftset('addr4', 'ipv4_addr')
|
||||
self.setup_nftset('network4', 'ipv4_addr', 'flags interval;')
|
||||
self.setup_nftset('ifindex', 'iface_index')
|
||||
|
||||
start_networkd()
|
||||
self.wait_online(['veth-peer:carrier'])
|
||||
start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7',
|
||||
@@ -5163,6 +5218,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
|
||||
self.check_netlabel('veth99', r'192\.168\.5\.0/24')
|
||||
|
||||
self.check_nftset('addr4', r'192\.168\.5\.1')
|
||||
self.check_nftset('network4', r'192\.168\.5\.0/24')
|
||||
self.check_nftset('ifindex', 'veth99')
|
||||
|
||||
self.teardown_nftset('addr4', 'network4', 'ifindex')
|
||||
|
||||
def test_dhcp_client_ipv4_use_routes_gateway(self):
|
||||
first = True
|
||||
for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4):
|
||||
@@ -5619,6 +5680,10 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
|
||||
'12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
|
||||
'13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network')
|
||||
|
||||
self.setup_nftset('addr6', 'ipv6_addr')
|
||||
self.setup_nftset('network6', 'ipv6_addr', 'flags interval;')
|
||||
self.setup_nftset('ifindex', 'iface_index')
|
||||
|
||||
start_networkd()
|
||||
self.wait_online(['veth-peer:routable'])
|
||||
start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6')
|
||||
@@ -5806,6 +5871,13 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
|
||||
|
||||
self.check_netlabel('dummy98', '3ffe:501:ffff:[2-9a-f]00::/64')
|
||||
|
||||
self.check_nftset('addr6', '3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d')
|
||||
self.check_nftset('addr6', '3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*')
|
||||
self.check_nftset('network6', '3ffe:501:ffff:[2-9a-f]00::/64')
|
||||
self.check_nftset('ifindex', 'dummy98')
|
||||
|
||||
self.teardown_nftset('addr6', 'network6', 'ifindex')
|
||||
|
||||
def verify_dhcp4_6rd(self, tunnel_name):
|
||||
print('### ip -4 address show dev veth-peer scope global')
|
||||
output = check_output('ip -4 address show dev veth-peer scope global')
|
||||
|
||||
Reference in New Issue
Block a user