mirror of
https://github.com/systemd/systemd.git
synced 2025-03-19 22:50:17 +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:
commit
b1b4e9204c
@ -744,9 +744,14 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
|
||||
<listitem><para>Configures IP masquerading for the network
|
||||
interface. If enabled, packets forwarded from the network
|
||||
interface will be appear as coming from the local host.
|
||||
Takes a boolean argument. Implies
|
||||
<varname>IPForward=ipv4</varname>. Defaults to
|
||||
<literal>no</literal>.</para></listitem>
|
||||
Takes one of <literal>ipv4</literal>, <literal>ipv6</literal>,
|
||||
<literal>both</literal>, <literal>no</literal>.
|
||||
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>
|
||||
<term><varname>IPv6PrivacyExtensions=</varname></term>
|
||||
|
@ -262,16 +262,23 @@ static int address_set_masquerade(Address *address, bool add) {
|
||||
if (!address->link->network)
|
||||
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;
|
||||
|
||||
if (address->family != AF_INET)
|
||||
if (address->family == AF_INET6 &&
|
||||
!FLAGS_SET(address->link->network->ip_masquerade, ADDRESS_FAMILY_IPV6))
|
||||
return 0;
|
||||
|
||||
if (address->scope >= RT_SCOPE_LINK)
|
||||
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;
|
||||
|
||||
masked = address->in_addr;
|
||||
@ -279,11 +286,14 @@ static int address_set_masquerade(Address *address, bool add) {
|
||||
if (r < 0)
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ typedef struct Address {
|
||||
|
||||
bool scope_set:1;
|
||||
bool ip_masquerade_done:1;
|
||||
bool ipv6_masquerade_done:1;
|
||||
AddressFamily duplicate_address_detection;
|
||||
|
||||
/* Called when address become ready */
|
||||
|
@ -111,7 +111,7 @@ Network.DNSSEC, config_parse_dnssec_mode,
|
||||
Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
|
||||
Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp)
|
||||
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.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
|
||||
Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
|
||||
|
@ -208,9 +208,8 @@ int network_verify(Network *network) {
|
||||
if (network->link_local < 0)
|
||||
network->link_local = network->bridge ? ADDRESS_FAMILY_NO : ADDRESS_FAMILY_IPV6;
|
||||
|
||||
/* IPMasquerade=yes implies IPForward=yes */
|
||||
if (network->ip_masquerade)
|
||||
network->ip_forward |= ADDRESS_FAMILY_IPV4;
|
||||
/* IPMasquerade implies IPForward */
|
||||
network->ip_forward |= network->ip_masquerade;
|
||||
|
||||
network_adjust_ipv6_accept_ra(network);
|
||||
network_adjust_dhcp(network);
|
||||
|
@ -110,7 +110,7 @@ struct Network {
|
||||
KeepConfiguration keep_configuration;
|
||||
char **bind_carrier;
|
||||
bool default_route_on_device;
|
||||
bool ip_masquerade;
|
||||
AddressFamily ip_masquerade;
|
||||
|
||||
/* DHCP Client Support */
|
||||
AddressFamily dhcp;
|
||||
|
@ -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(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(
|
||||
const char* unit,
|
||||
const char *filename,
|
||||
|
@ -28,6 +28,7 @@ typedef struct NetworkConfigSection {
|
||||
|
||||
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_compat);
|
||||
|
||||
const char *address_family_to_string(AddressFamily b) _const_;
|
||||
AddressFamily address_family_from_string(const char *s) _pure_;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "sd-netlink.h"
|
||||
|
||||
#include "af-list.h"
|
||||
#include "alloc-util.h"
|
||||
#include "fd-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;
|
||||
int r, af = AF_INET;
|
||||
int r;
|
||||
|
||||
assert(exposed);
|
||||
|
||||
@ -106,19 +107,19 @@ int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, union in_addr_uni
|
||||
p->container_port,
|
||||
NULL);
|
||||
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;
|
||||
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;
|
||||
union in_addr_union new_exposed;
|
||||
ExposePort *p;
|
||||
bool add;
|
||||
int af = AF_INET, r;
|
||||
int r;
|
||||
|
||||
assert(exposed);
|
||||
|
||||
@ -137,7 +138,7 @@ int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *
|
||||
addresses[0].scope < RT_SCOPE_LINK;
|
||||
|
||||
if (!add)
|
||||
return expose_port_flush(fw_ctx, l, exposed);
|
||||
return expose_port_flush(fw_ctx, l, af, exposed);
|
||||
|
||||
new_exposed = addresses[0].address;
|
||||
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,
|
||||
in_addr_is_null(af, exposed) ? NULL : exposed);
|
||||
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;
|
||||
|
@ -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_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_flush(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, int af, union in_addr_union *exposed);
|
||||
|
@ -2467,7 +2467,8 @@ static int setup_kmsg(int kmsg_socket) {
|
||||
}
|
||||
|
||||
struct ExposeArgs {
|
||||
union in_addr_union address;
|
||||
union in_addr_union address4;
|
||||
union in_addr_union address6;
|
||||
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(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;
|
||||
}
|
||||
|
||||
@ -4900,7 +4902,8 @@ static int run_container(
|
||||
if (r < 0)
|
||||
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]);
|
||||
@ -5027,7 +5030,8 @@ static int run_container(
|
||||
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);
|
||||
*veth_created = false;
|
||||
@ -5582,7 +5586,8 @@ finish:
|
||||
(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)
|
||||
(void) remove_veth_links(veth_name, arg_network_veth_extra);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <linux/netfilter/nf_nat.h>
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
|
||||
#include "sd-netlink.h"
|
||||
|
||||
@ -17,6 +18,7 @@
|
||||
#include "firewall-util-private.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "macro.h"
|
||||
#include "memory-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
@ -28,6 +30,9 @@
|
||||
|
||||
#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,
|
||||
sd_netlink_message *messages[],
|
||||
size_t msgcount) {
|
||||
@ -329,9 +334,13 @@ static int sd_nfnl_message_new_masq_rule(sd_netlink *nfnl, sd_netlink_message **
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* 1st statement: ip saddr @masq_saddr. Place iph->saddr in reg1. */
|
||||
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, saddr),
|
||||
sizeof(uint32_t), NFT_REG32_01);
|
||||
/* 1st statement: ip saddr @masq_saddr. Place iph->saddr in reg1, resp. ipv6 in reg1..reg4. */
|
||||
if (family == AF_INET)
|
||||
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)
|
||||
return r;
|
||||
|
||||
@ -397,7 +406,7 @@ static int sd_nfnl_message_new_dnat_rule_pre(sd_netlink *nfnl, sd_netlink_messag
|
||||
if (r < 0)
|
||||
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);
|
||||
if (r < 0)
|
||||
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,
|
||||
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;
|
||||
enum nft_registers proto_reg;
|
||||
int r;
|
||||
@ -426,19 +434,31 @@ static int sd_nfnl_message_new_dnat_rule_out(sd_netlink *nfnl, sd_netlink_messag
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* 1st statement: exclude 127.0.0.1/8: ip daddr != 127.0.0.1/8 */
|
||||
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, daddr),
|
||||
sizeof(uint32_t), NFT_REG32_01);
|
||||
if (r < 0)
|
||||
return r;
|
||||
/* 1st statement: exclude 127.0.0.1/8: ip daddr != 127.0.0.1/8, resp. avoid ::1 */
|
||||
if (family == AF_INET) {
|
||||
uint32_t lonet = htobe32(UINT32_C(0x7F000000)), lomask = htobe32(UINT32_C(0xff000000));
|
||||
|
||||
/* 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;
|
||||
r = nfnl_add_expr_payload(m, NFT_PAYLOAD_NETWORK_HEADER, offsetof(struct iphdr, daddr),
|
||||
sizeof(lonet), NFT_REG32_01);
|
||||
if (r < 0)
|
||||
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 */
|
||||
r = nfnl_add_expr_cmp(m, NFT_CMP_NEQ, NFT_REG32_01, &lonet, sizeof(lonet));
|
||||
/* 1st statement (cont.): compare reg1 with 127/8 */
|
||||
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)
|
||||
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
|
||||
* 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);
|
||||
if (r < 0)
|
||||
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
|
||||
static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
|
||||
sd_netlink_message *batch[NFT_INIT_MSGS] = {};
|
||||
size_t ip_type_size = sizeof(uint32_t);
|
||||
int ip_type = TYPE_IPADDR, r;
|
||||
size_t msgcnt = 0, i;
|
||||
size_t msgcnt = 0, i, ip_type_size;
|
||||
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]);
|
||||
if (r < 0)
|
||||
@ -661,6 +682,14 @@ static int fw_nftables_init_family(sd_netlink *nfnl, int family) {
|
||||
if (r < 0)
|
||||
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++;
|
||||
assert(msgcnt < NFT_INIT_MSGS);
|
||||
/* set to store ip address ranges we should masquerade for */
|
||||
@ -731,6 +760,10 @@ int fw_nftables_init(FirewallContext *ctx) {
|
||||
if (r < 0)
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
@ -815,6 +848,73 @@ static int fw_nftables_recreate_table(sd_netlink *nfnl, int af, sd_netlink_messa
|
||||
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
|
||||
|
||||
int fw_nftables_add_masquerade(
|
||||
@ -831,6 +931,8 @@ int fw_nftables_add_masquerade(
|
||||
if (!source || source_prefixlen == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (af == AF_INET6 && source_prefixlen < 8)
|
||||
return -EINVAL;
|
||||
again:
|
||||
r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
|
||||
if (r < 0)
|
||||
@ -844,7 +946,11 @@ again:
|
||||
if (r < 0)
|
||||
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)
|
||||
goto out_unref;
|
||||
|
||||
@ -881,7 +987,7 @@ int fw_nftables_add_local_dnat(
|
||||
const union in_addr_union *remote,
|
||||
uint16_t remote_port,
|
||||
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] = {};
|
||||
bool retry = true;
|
||||
size_t tsize;
|
||||
@ -889,9 +995,6 @@ int fw_nftables_add_local_dnat(
|
||||
|
||||
assert(add || !previous_remote);
|
||||
|
||||
if (af != AF_INET)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP))
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
@ -908,7 +1011,14 @@ again:
|
||||
if (remote_port <= 0)
|
||||
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]);
|
||||
if (r < 0)
|
||||
@ -916,23 +1026,29 @@ again:
|
||||
|
||||
tsize = 1;
|
||||
/* If a previous remote is set, remove its entry */
|
||||
if (add && previous_remote && previous_remote->in.s_addr != remote->in.s_addr) {
|
||||
data[0] = previous_remote->in.s_addr;
|
||||
if (add && previous_remote && !in_addr_equal(af, previous_remote, remote)) {
|
||||
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)
|
||||
goto out_unref;
|
||||
|
||||
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);
|
||||
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
|
||||
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++;
|
||||
assert(tsize < NFT_DNAT_MSGS);
|
||||
@ -949,6 +1065,8 @@ again:
|
||||
int tmp = fw_nftables_recreate_table(ctx->nfnl, af, transaction, tsize);
|
||||
|
||||
if (tmp == 0) {
|
||||
/* table created anew; previous address already gone */
|
||||
previous_remote = NULL;
|
||||
retry = false;
|
||||
goto again;
|
||||
}
|
||||
|
@ -1,11 +1,128 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#include <arpa/inet.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "firewall-util.h"
|
||||
#include "log.h"
|
||||
#include "random-util.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))}
|
||||
|
||||
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[]) {
|
||||
_cleanup_(fw_ctx_freep) FirewallContext *ctx;
|
||||
int r;
|
||||
@ -57,5 +174,8 @@ int main(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to modify firewall: %m");
|
||||
|
||||
test_v6(&ctx);
|
||||
test_v6_range();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user