1
0
mirror of https://github.com/systemd/systemd.git synced 2025-08-24 09:49:49 +03:00

Merge pull request #18007 from fw-strlen/ipv6_masq_and_dnat

Support ipv6 for masquerade and dnat in nspawn and networkd
This commit is contained in:
Lennart Poettering
2021-02-16 23:41:35 +01:00
committed by GitHub
13 changed files with 330 additions and 60 deletions

View File

@ -744,9 +744,14 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
<listitem><para>Configures IP masquerading for the network <listitem><para>Configures IP masquerading for the network
interface. If enabled, packets forwarded from the network interface. If enabled, packets forwarded from the network
interface will be appear as coming from the local host. interface will be appear as coming from the local host.
Takes a boolean argument. Implies Takes one of <literal>ipv4</literal>, <literal>ipv6</literal>,
<varname>IPForward=ipv4</varname>. Defaults to <literal>both</literal>, <literal>no</literal>.
<literal>no</literal>.</para></listitem> The setting <literal>yes</literal> is the same as <literal>ipv4</literal> and not as
<literal>both</literal>!
Defaults to <literal>no</literal>.
If enabled, this automatically sets <varname>IPForward</varname> to one of
<literal>ipv4</literal>, <literal>ipv6</literal> or <literal>both</literal>.
</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><varname>IPv6PrivacyExtensions=</varname></term> <term><varname>IPv6PrivacyExtensions=</varname></term>

View File

@ -262,16 +262,23 @@ static int address_set_masquerade(Address *address, bool add) {
if (!address->link->network) if (!address->link->network)
return 0; return 0;
if (!address->link->network->ip_masquerade) if (address->family == AF_INET &&
!FLAGS_SET(address->link->network->ip_masquerade, ADDRESS_FAMILY_IPV4))
return 0; return 0;
if (address->family != AF_INET) if (address->family == AF_INET6 &&
!FLAGS_SET(address->link->network->ip_masquerade, ADDRESS_FAMILY_IPV6))
return 0; return 0;
if (address->scope >= RT_SCOPE_LINK) if (address->scope >= RT_SCOPE_LINK)
return 0; return 0;
if (address->ip_masquerade_done == add) if (address->family == AF_INET &&
address->ip_masquerade_done == add)
return 0;
if (address->family == AF_INET6 &&
address->ipv6_masquerade_done == add)
return 0; return 0;
masked = address->in_addr; masked = address->in_addr;
@ -279,11 +286,14 @@ static int address_set_masquerade(Address *address, bool add) {
if (r < 0) if (r < 0)
return r; return r;
r = fw_add_masquerade(&address->link->manager->fw_ctx, add, AF_INET, &masked, address->prefixlen); r = fw_add_masquerade(&address->link->manager->fw_ctx, add, address->family, &masked, address->prefixlen);
if (r < 0) if (r < 0)
return r; return r;
address->ip_masquerade_done = add; if (address->family == AF_INET)
address->ip_masquerade_done = add;
else if (address->family == AF_INET6)
address->ipv6_masquerade_done = add;
return 0; return 0;
} }

View File

@ -38,6 +38,7 @@ typedef struct Address {
bool scope_set:1; bool scope_set:1;
bool ip_masquerade_done:1; bool ip_masquerade_done:1;
bool ipv6_masquerade_done:1;
AddressFamily duplicate_address_detection; AddressFamily duplicate_address_detection;
/* Called when address become ready */ /* Called when address become ready */

View File

@ -111,7 +111,7 @@ Network.DNSSEC, config_parse_dnssec_mode,
Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0 Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp) Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp)
Network.IPForward, config_parse_address_family_with_kernel, 0, offsetof(Network, ip_forward) Network.IPForward, config_parse_address_family_with_kernel, 0, offsetof(Network, ip_forward)
Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) Network.IPMasquerade, config_parse_address_family_compat, 0, offsetof(Network, ip_masquerade)
Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions)
Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)

View File

@ -208,9 +208,8 @@ int network_verify(Network *network) {
if (network->link_local < 0) if (network->link_local < 0)
network->link_local = network->bridge ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_IPV6; network->link_local = network->bridge ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_IPV6;
/* IPMasquerade=yes implies IPForward=yes */ /* IPMasquerade implies IPForward */
if (network->ip_masquerade) network->ip_forward |= network->ip_masquerade;
network->ip_forward |= ADDRESS_FAMILY_IPV4;
network_adjust_ipv6_accept_ra(network); network_adjust_ipv6_accept_ra(network);
network_adjust_dhcp(network); network_adjust_dhcp(network);

View File

@ -110,7 +110,7 @@ struct Network {
KeepConfiguration keep_configuration; KeepConfiguration keep_configuration;
char **bind_carrier; char **bind_carrier;
bool default_route_on_device; bool default_route_on_device;
bool ip_masquerade; AddressFamily ip_masquerade;
/* DHCP Client Support */ /* DHCP Client Support */
AddressFamily dhcp; AddressFamily dhcp;

View File

@ -61,6 +61,16 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_link_local_address_family, link_local_addr
DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_deprecated_address_family, AddressFamily); DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_deprecated_address_family, AddressFamily);
DEFINE_STRING_TABLE_LOOKUP(dhcp_lease_server_type, sd_dhcp_lease_server_type); DEFINE_STRING_TABLE_LOOKUP(dhcp_lease_server_type, sd_dhcp_lease_server_type);
static AddressFamily address_family_compat_from_string(const char *s) {
if (streq_ptr(s, "yes")) /* compat name */
return ADDRESS_FAMILY_IPV4;
if (streq_ptr(s, "both"))
return ADDRESS_FAMILY_YES;
return address_family_from_string(s);
}
DEFINE_CONFIG_PARSE_ENUM(config_parse_address_family_compat, address_family_compat,
AddressFamily, "Failed to parse option");
int config_parse_address_family_with_kernel( int config_parse_address_family_with_kernel(
const char* unit, const char* unit,
const char *filename, const char *filename,

View File

@ -28,6 +28,7 @@ typedef struct NetworkConfigSection {
CONFIG_PARSER_PROTOTYPE(config_parse_link_local_address_family); CONFIG_PARSER_PROTOTYPE(config_parse_link_local_address_family);
CONFIG_PARSER_PROTOTYPE(config_parse_address_family_with_kernel); CONFIG_PARSER_PROTOTYPE(config_parse_address_family_with_kernel);
CONFIG_PARSER_PROTOTYPE(config_parse_address_family_compat);
const char *address_family_to_string(AddressFamily b) _const_; const char *address_family_to_string(AddressFamily b) _const_;
AddressFamily address_family_from_string(const char *s) _pure_; AddressFamily address_family_from_string(const char *s) _pure_;

View File

@ -2,6 +2,7 @@
#include "sd-netlink.h" #include "sd-netlink.h"
#include "af-list.h"
#include "alloc-util.h" #include "alloc-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "firewall-util.h" #include "firewall-util.h"
@ -82,9 +83,9 @@ void expose_port_free_all(ExposePort *p) {
} }
} }
int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, union in_addr_union *exposed) { int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed) {
ExposePort *p; ExposePort *p;
int r, af = AF_INET; int r;
assert(exposed); assert(exposed);
@ -106,19 +107,19 @@ int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, union in_addr_uni
p->container_port, p->container_port,
NULL); NULL);
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to modify firewall: %m"); log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af));
} }
*exposed = IN_ADDR_NULL; *exposed = IN_ADDR_NULL;
return 0; return 0;
} }
int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, union in_addr_union *exposed) { int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, int af, union in_addr_union *exposed) {
_cleanup_free_ struct local_address *addresses = NULL; _cleanup_free_ struct local_address *addresses = NULL;
union in_addr_union new_exposed; union in_addr_union new_exposed;
ExposePort *p; ExposePort *p;
bool add; bool add;
int af = AF_INET, r; int r;
assert(exposed); assert(exposed);
@ -137,7 +138,7 @@ int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *
addresses[0].scope < RT_SCOPE_LINK; addresses[0].scope < RT_SCOPE_LINK;
if (!add) if (!add)
return expose_port_flush(fw_ctx, l, exposed); return expose_port_flush(fw_ctx, l, af, exposed);
new_exposed = addresses[0].address; new_exposed = addresses[0].address;
if (in_addr_equal(af, exposed, &new_exposed)) if (in_addr_equal(af, exposed, &new_exposed))
@ -160,7 +161,7 @@ int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *
p->container_port, p->container_port,
in_addr_is_null(af, exposed) ? NULL : exposed); in_addr_is_null(af, exposed) ? NULL : exposed);
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to modify firewall: %m"); log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af));
} }
*exposed = new_exposed; *exposed = new_exposed;

View File

@ -23,5 +23,5 @@ int expose_port_parse(ExposePort **l, const char *s);
int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, void *userdata, sd_netlink **ret); int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, void *userdata, sd_netlink **ret);
int expose_port_send_rtnl(int send_fd); int expose_port_send_rtnl(int send_fd);
int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, union in_addr_union *exposed); int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, int af, union in_addr_union *exposed);
int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, union in_addr_union *exposed); int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed);

View File

@ -2467,7 +2467,8 @@ static int setup_kmsg(int kmsg_socket) {
} }
struct ExposeArgs { struct ExposeArgs {
union in_addr_union address; union in_addr_union address4;
union in_addr_union address6;
struct FirewallContext *fw_ctx; struct FirewallContext *fw_ctx;
}; };
@ -2478,7 +2479,8 @@ static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *user
assert(m); assert(m);
assert(args); assert(args);
expose_port_execute(rtnl, &args->fw_ctx, arg_expose_ports, &args->address); expose_port_execute(rtnl, &args->fw_ctx, arg_expose_ports, AF_INET, &args->address4);
expose_port_execute(rtnl, &args->fw_ctx, arg_expose_ports, AF_INET6, &args->address6);
return 0; return 0;
} }
@ -4900,7 +4902,8 @@ static int run_container(
if (r < 0) if (r < 0)
return r; return r;
(void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, &expose_args->address); (void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, AF_INET, &expose_args->address4);
(void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, AF_INET6, &expose_args->address6);
} }
rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]);
@ -5027,7 +5030,8 @@ static int run_container(
return 0; /* finito */ return 0; /* finito */
} }
expose_port_flush(&expose_args->fw_ctx, arg_expose_ports, &expose_args->address); expose_port_flush(&expose_args->fw_ctx, arg_expose_ports, AF_INET, &expose_args->address4);
expose_port_flush(&expose_args->fw_ctx, arg_expose_ports, AF_INET6, &expose_args->address6);
(void) remove_veth_links(veth_name, arg_network_veth_extra); (void) remove_veth_links(veth_name, arg_network_veth_extra);
*veth_created = false; *veth_created = false;
@ -5582,7 +5586,8 @@ finish:
(void) rm_rf(p, REMOVE_ROOT); (void) rm_rf(p, REMOVE_ROOT);
} }
expose_port_flush(&fw_ctx, arg_expose_ports, &expose_args.address); expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4);
expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET6, &expose_args.address6);
if (veth_created) if (veth_created)
(void) remove_veth_links(veth_name, arg_network_veth_extra); (void) remove_veth_links(veth_name, arg_network_veth_extra);

View File

@ -9,6 +9,7 @@
#include <linux/netfilter/nf_nat.h> #include <linux/netfilter/nf_nat.h>
#include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv4.h>
#include <netinet/ip.h> #include <netinet/ip.h>
#include <netinet/ip6.h>
#include "sd-netlink.h" #include "sd-netlink.h"
@ -17,6 +18,7 @@
#include "firewall-util-private.h" #include "firewall-util-private.h"
#include "in-addr-util.h" #include "in-addr-util.h"
#include "macro.h" #include "macro.h"
#include "memory-util.h"
#include "socket-util.h" #include "socket-util.h"
#include "time-util.h" #include "time-util.h"
@ -28,6 +30,9 @@
#define UDP_DPORT_OFFSET 2 #define UDP_DPORT_OFFSET 2
void nft_in6addr_to_range(const union in_addr_union *source, unsigned int prefixlen,
struct in6_addr *start, struct in6_addr *end);
static int nfnl_netlink_sendv(sd_netlink *nfnl, static int nfnl_netlink_sendv(sd_netlink *nfnl,
sd_netlink_message *messages[], sd_netlink_message *messages[],
size_t msgcount) { size_t msgcount) {
@ -329,9 +334,13 @@ static int sd_nfnl_message_new_masq_rule(sd_netlink *nfnl, sd_netlink_message **
if (r < 0) if (r < 0)
return r; return r;
/* 1st statement: ip saddr @masq_saddr. Place iph->saddr in reg1. */ /* 1st statement: ip saddr @masq_saddr. Place iph->saddr in reg1, resp. ipv6 in reg1..reg4. */
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, saddr), if (family == AF_INET)
sizeof(uint32_t), NFT_REG32_01); r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, saddr),
sizeof(uint32_t), NFT_REG32_01);
else
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct ip6_hdr, ip6_src.s6_addr),
sizeof(struct in6_addr), NFT_REG32_01);
if (r < 0) if (r < 0)
return r; return r;
@ -397,7 +406,7 @@ static int sd_nfnl_message_new_dnat_rule_pre(sd_netlink *nfnl, sd_netlink_messag
if (r < 0) if (r < 0)
return r; return r;
proto_reg = NFT_REG32_02; proto_reg = family == AF_INET ? NFT_REG32_02 : NFT_REG32_05;
r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg); r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg);
if (r < 0) if (r < 0)
return r; return r;
@ -411,9 +420,8 @@ static int sd_nfnl_message_new_dnat_rule_pre(sd_netlink *nfnl, sd_netlink_messag
static int sd_nfnl_message_new_dnat_rule_out(sd_netlink *nfnl, sd_netlink_message **ret, static int sd_nfnl_message_new_dnat_rule_out(sd_netlink *nfnl, sd_netlink_message **ret,
int family, const char *chain) { int family, const char *chain) {
static const uint32_t zero, one = 1; static const uint32_t zero = 0, one = 1;
uint32_t lonet = htobe32(0x7F000000), lomask = htobe32(0xff000000);
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
enum nft_registers proto_reg; enum nft_registers proto_reg;
int r; int r;
@ -426,19 +434,31 @@ static int sd_nfnl_message_new_dnat_rule_out(sd_netlink *nfnl, sd_netlink_messag
if (r < 0) if (r < 0)
return r; return r;
/* 1st statement: exclude 127.0.0.1/8: ip daddr != 127.0.0.1/8 */ /* 1st statement: exclude 127.0.0.1/8: ip daddr != 127.0.0.1/8, resp. avoid ::1 */
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, daddr), if (family == AF_INET) {
sizeof(uint32_t), NFT_REG32_01); uint32_t lonet = htobe32(UINT32_C(0x7F000000)), lomask = htobe32(UINT32_C(0xff000000));
if (r < 0)
return r;
/* 1st statement (cont.): bitops/prefix */ r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, daddr),
r = nfnl_add_expr_bitwise(m, NFT_REG32_01, NFT_REG32_01, &lomask, &zero, sizeof(lomask)); sizeof(lonet), NFT_REG32_01);
if (r < 0) if (r < 0)
return r; return r;
/* 1st statement (cont.): bitops/prefix */
r = nfnl_add_expr_bitwise(m, NFT_REG32_01, NFT_REG32_01, &lomask, &zero, sizeof(lomask));
if (r < 0)
return r;
/* 1st statement (cont.): compare reg1 with 127/8 */ /* 1st statement (cont.): compare reg1 with 127/8 */
r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &lonet, sizeof(lonet)); r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &lonet, sizeof(lonet));
} else {
struct in6_addr loaddr = IN6ADDR_LOOPBACK_INIT;
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct ip6_hdr, ip6_dst.s6_addr),
sizeof(loaddr), NFT_REG32_01);
if (r < 0)
return r;
r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &loaddr, sizeof(loaddr));
}
if (r < 0) if (r < 0)
return r; return r;
@ -475,7 +495,7 @@ static int sd_nfnl_message_new_dnat_rule_out(sd_netlink *nfnl, sd_netlink_messag
/* 4th statement: dnat connection to address/port retrieved by the /* 4th statement: dnat connection to address/port retrieved by the
* preceding expression. */ * preceding expression. */
proto_reg = NFT_REG32_02; proto_reg = family == AF_INET ? NFT_REG32_02 : NFT_REG32_05;
r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg); r = nfnl_add_expr_dnat(m, family, NFT_REG32_01, proto_reg);
if (r < 0) if (r < 0)
return r; return r;
@ -620,10 +640,11 @@ static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) {
#define NFT_INIT_MSGS 16 #define NFT_INIT_MSGS 16
static int fw_nftables_init_family(sd_netlink *nfnl, int family) { static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
sd_netlink_message *batch[NFT_INIT_MSGS] = {}; sd_netlink_message *batch[NFT_INIT_MSGS] = {};
size_t ip_type_size = sizeof(uint32_t); size_t msgcnt = 0, i, ip_type_size;
int ip_type = TYPE_IPADDR, r;
size_t msgcnt = 0, i;
uint32_t set_id = 0; uint32_t set_id = 0;
int ip_type, r;
assert(IN_SET(family, AF_INET, AF_INET6));
r = sd_nfnl_message_batch_begin(nfnl, &batch[msgcnt]); r = sd_nfnl_message_batch_begin(nfnl, &batch[msgcnt]);
if (r < 0) if (r < 0)
@ -661,6 +682,14 @@ static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
if (r < 0) if (r < 0)
goto out_unref; goto out_unref;
if (family == AF_INET) {
ip_type_size = sizeof(uint32_t);
ip_type = TYPE_IPADDR;
} else {
assert(family == AF_INET6);
ip_type_size = sizeof(struct in6_addr);
ip_type = TYPE_IP6ADDR;
}
msgcnt++; msgcnt++;
assert(msgcnt < NFT_INIT_MSGS); assert(msgcnt < NFT_INIT_MSGS);
/* set to store ip address ranges we should masquerade for */ /* set to store ip address ranges we should masquerade for */
@ -731,6 +760,10 @@ int fw_nftables_init(FirewallContext *ctx) {
if (r < 0) if (r < 0)
return r; return r;
r = fw_nftables_init_family(nfnl, AF_INET6);
if (r < 0)
log_debug_errno(r, "Failed to init ipv6 NAT: %m");
ctx->nfnl = TAKE_PTR(nfnl); ctx->nfnl = TAKE_PTR(nfnl);
return 0; return 0;
} }
@ -815,6 +848,73 @@ static int fw_nftables_recreate_table(sd_netlink *nfnl, int af, sd_netlink_messa
return 0; return 0;
} }
void nft_in6addr_to_range(const union in_addr_union *source, unsigned int prefixlen,
struct in6_addr *ret_start, struct in6_addr *ret_end) {
uint8_t carry = 0;
int i, j;
assert(prefixlen <= 128);
for (i = 0, j = 15; i < 16; i++) {
uint8_t nm;
nm = 0xFF;
if (prefixlen < 8)
nm = 0xFF << (8 - prefixlen);
ret_start->s6_addr[i] = source->in6.s6_addr[i] & nm;
if (prefixlen <= 8 && j == 15) {
carry = 1u << (8 - prefixlen);
j = i;
}
if (prefixlen >= 8)
prefixlen -= 8;
else
prefixlen = 0;
}
*ret_end = *ret_start;
for (; j >= 0; j--) {
uint16_t overflow = ret_end->s6_addr[j] + carry;
ret_end->s6_addr[j] = overflow;
if (overflow <= 0xff)
break;
carry = 1;
}
if (memcmp(ret_start, ret_end, sizeof(*ret_start)) > 0)
zero(ret_end);
}
static int nft_message_add_setelem_ip6range(sd_netlink_message *m,
const union in_addr_union *source,
unsigned int prefixlen) {
struct in6_addr start, end;
int r;
nft_in6addr_to_range(source, prefixlen, &start, &end);
r = sd_nfnl_nft_message_add_setelem(m, 0, &start, sizeof(start), NULL, 0);
if (r < 0)
return r;
r = sd_nfnl_nft_message_add_setelem_end(m);
if (r < 0)
return r;
r = sd_nfnl_nft_message_add_setelem(m, 1, &end, sizeof(end), NULL, 0);
if (r < 0)
return r;
r = sd_netlink_message_append_u32(m, NFTA_SET_ELEM_FLAGS, htobe32(NFT_SET_ELEM_INTERVAL_END));
if (r < 0)
return r;
return sd_nfnl_nft_message_add_setelem_end(m);
}
#define NFT_MASQ_MSGS 3 #define NFT_MASQ_MSGS 3
int fw_nftables_add_masquerade( int fw_nftables_add_masquerade(
@ -831,6 +931,8 @@ int fw_nftables_add_masquerade(
if (!source || source_prefixlen == 0) if (!source || source_prefixlen == 0)
return -EINVAL; return -EINVAL;
if (af == AF_INET6 && source_prefixlen < 8)
return -EINVAL;
again: again:
r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]); r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
if (r < 0) if (r < 0)
@ -844,7 +946,11 @@ again:
if (r < 0) if (r < 0)
goto out_unref; goto out_unref;
r = nft_message_add_setelem_iprange(transaction[tsize], source, source_prefixlen); if (af == AF_INET)
r = nft_message_add_setelem_iprange(transaction[tsize], source, source_prefixlen);
else
r = nft_message_add_setelem_ip6range(transaction[tsize], source, source_prefixlen);
if (r < 0) if (r < 0)
goto out_unref; goto out_unref;
@ -881,7 +987,7 @@ int fw_nftables_add_local_dnat(
const union in_addr_union *remote, const union in_addr_union *remote,
uint16_t remote_port, uint16_t remote_port,
const union in_addr_union *previous_remote) { const union in_addr_union *previous_remote) {
uint32_t data[2], key[2]; uint32_t data[5], key[2], dlen;
sd_netlink_message *transaction[NFT_DNAT_MSGS] = {}; sd_netlink_message *transaction[NFT_DNAT_MSGS] = {};
bool retry = true; bool retry = true;
size_t tsize; size_t tsize;
@ -889,9 +995,6 @@ int fw_nftables_add_local_dnat(
assert(add || !previous_remote); assert(add || !previous_remote);
if (af != AF_INET)
return -EAFNOSUPPORT;
if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP))
return -EPROTONOSUPPORT; return -EPROTONOSUPPORT;
@ -908,7 +1011,14 @@ again:
if (remote_port <= 0) if (remote_port <= 0)
return -EINVAL; return -EINVAL;
data[1] = htobe16(remote_port); if (af == AF_INET) {
dlen = 8;
data[1] = htobe16(remote_port);
} else {
assert(af == AF_INET6);
dlen = sizeof(data);
data[4] = htobe16(remote_port);
}
r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]); r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
if (r < 0) if (r < 0)
@ -916,23 +1026,29 @@ again:
tsize = 1; tsize = 1;
/* If a previous remote is set, remove its entry */ /* If a previous remote is set, remove its entry */
if (add && previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { if (add && previous_remote && !in_addr_equal(af, previous_remote, remote)) {
data[0] = previous_remote->in.s_addr; if (af == AF_INET)
data[0] = previous_remote->in.s_addr;
else
memcpy(data, &previous_remote->in6, sizeof(previous_remote->in6));
r = nft_del_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, sizeof(data)); r = nft_del_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
if (r < 0) if (r < 0)
goto out_unref; goto out_unref;
tsize++; tsize++;
} }
data[0] = remote->in.s_addr; if (af == AF_INET)
data[0] = remote->in.s_addr;
else
memcpy(data, &remote->in6, sizeof(remote->in6));
assert(tsize < NFT_DNAT_MSGS); assert(tsize < NFT_DNAT_MSGS);
if (add) if (add)
nft_add_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, sizeof(data)); nft_add_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
else else
nft_del_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, sizeof(data)); nft_del_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen);
tsize++; tsize++;
assert(tsize < NFT_DNAT_MSGS); assert(tsize < NFT_DNAT_MSGS);
@ -949,6 +1065,8 @@ again:
int tmp = fw_nftables_recreate_table(ctx->nfnl, af, transaction, tsize); int tmp = fw_nftables_recreate_table(ctx->nfnl, af, transaction, tsize);
if (tmp == 0) { if (tmp == 0) {
/* table created anew; previous address already gone */
previous_remote = NULL;
retry = false; retry = false;
goto again; goto again;
} }

View File

@ -1,11 +1,128 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <arpa/inet.h>
#include <stdlib.h>
#include "firewall-util.h" #include "firewall-util.h"
#include "log.h" #include "log.h"
#include "random-util.h"
#include "tests.h" #include "tests.h"
#define MAKE_IN_ADDR_UNION(a,b,c,d) (union in_addr_union) { .in.s_addr = htobe32((uint32_t) (a) << 24 | (uint32_t) (b) << 16 | (uint32_t) (c) << 8 | (uint32_t) (d))} #define MAKE_IN_ADDR_UNION(a,b,c,d) (union in_addr_union) { .in.s_addr = htobe32((uint32_t) (a) << 24 | (uint32_t) (b) << 16 | (uint32_t) (c) << 8 | (uint32_t) (d))}
void nft_in6addr_to_range(const union in_addr_union *source, unsigned int prefixlen,
struct in6_addr *start, struct in6_addr *end);
static void make_in6_addr_union(const char *addr, union in_addr_union *u) {
assert_se(inet_pton(AF_INET6, addr, &u->in6) >= 0);
}
static bool test_in6_eq(const char *addr, const union in_addr_union *u) {
union in_addr_union tmp;
make_in6_addr_union(addr, &tmp);
return memcmp(&tmp.in6, &u->in6, sizeof(tmp.in6)) == 0;
}
static void assert_in6_eq(const union in_addr_union *a, const union in_addr_union *b) {
assert_se(memcmp(&a->in6, &b->in6, sizeof(a->in6)) == 0);
}
static void test_v6(FirewallContext **ctx) {
union in_addr_union u = {}, u2 = {};
uint8_t prefixlen;
int r;
make_in6_addr_union("dead::beef", &u);
r = fw_add_masquerade(ctx, true, AF_INET6, &u, 128);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
r = fw_add_masquerade(ctx, false, AF_INET6, &u, 128);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
r = fw_add_masquerade(ctx, true, AF_INET6, &u, 64);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
r = fw_add_masquerade(ctx, false, AF_INET6, &u, 64);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
r = fw_add_local_dnat(ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u, 815, NULL);
if (r < 0)
log_error_errno(r, "Failed to modify firewall: %m");
make_in6_addr_union("1c3::c01d", &u2);
r = fw_add_local_dnat(ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u);
if (r < 0)
log_error_errno(r, "Failed to modify firewall: %m");
r = fw_add_local_dnat(ctx, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL);
if (r < 0)
log_error_errno(r, "Failed to modify firewall: %m");
prefixlen = random_u32() % (128 + 1 - 8);
prefixlen += 8;
pseudo_random_bytes(&u, sizeof(u));
r = fw_add_masquerade(ctx, true, AF_INET6, &u, prefixlen);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
r = fw_add_masquerade(ctx, false, AF_INET6, &u, prefixlen);
if (r < 0)
log_error_errno(r, "Failed to modify ipv6 firewall: %m");
}
static void test_v6_range(void) {
unsigned int prefixlen = 64;
union in_addr_union a, b, s;
make_in6_addr_union("dead:0:0:beef::", &s);
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_in6_eq(&s, &a);
assert_se(test_in6_eq("dead:0:0:bef0::", &b));
make_in6_addr_union("2001::", &s);
prefixlen = 56;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_in6_eq(&s, &a);
assert_se(test_in6_eq("2001:0:0:0100::", &b));
prefixlen = 48;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_in6_eq(&s, &a);
assert_se(test_in6_eq("2001:0:0001::", &b));
prefixlen = 65;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_se(test_in6_eq("2001::", &a));
assert_se(test_in6_eq("2001::8000:0:0:0", &b));
prefixlen = 66;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_in6_eq(&s, &a);
assert_se(test_in6_eq("2001::4000:0:0:0", &b));
prefixlen = 127;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_in6_eq(&s, &a);
assert_se(test_in6_eq("2001::0002", &b));
make_in6_addr_union("dead:beef::1", &s);
prefixlen = 64;
nft_in6addr_to_range(&s, prefixlen, &a.in6, &b.in6);
assert_se(test_in6_eq("dead:beef::", &a));
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
_cleanup_(fw_ctx_freep) FirewallContext *ctx; _cleanup_(fw_ctx_freep) FirewallContext *ctx;
int r; int r;
@ -57,5 +174,8 @@ int main(int argc, char *argv[]) {
if (r < 0) if (r < 0)
log_error_errno(r, "Failed to modify firewall: %m"); log_error_errno(r, "Failed to modify firewall: %m");
test_v6(&ctx);
test_v6_range();
return 0; return 0;
} }