diff --git a/man/systemd.network.xml b/man/systemd.network.xml index f0882c2bc22..74ec6a29f18 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -744,9 +744,14 @@ IPv6Token=prefixstable:2002:da8:1:: 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 - IPForward=ipv4. Defaults to - no. + Takes one of ipv4, ipv6, + both, no. + The setting yes is the same as ipv4 and not as + both! + Defaults to no. + If enabled, this automatically sets IPForward to one of + ipv4, ipv6 or both. + IPv6PrivacyExtensions= diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 095dde01200..d862e45e61c 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -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; } diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 7c2d0db3d07..d50c5a77f52 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -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 */ diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index ad261406f0a..44d86415721 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 69cf95fedee..eebbe19527b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 899b86eec91..4a3d126b7ba 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index a5c6fa8d006..494b23f9f4a 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -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, diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index 1736e2b57b7..1b842a9a4ec 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -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_; diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c index c368b205635..3bce3241021 100644 --- a/src/nspawn/nspawn-expose-ports.c +++ b/src/nspawn/nspawn-expose-ports.c @@ -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; diff --git a/src/nspawn/nspawn-expose-ports.h b/src/nspawn/nspawn-expose-ports.h index 8cfabd97971..27cfccf0152 100644 --- a/src/nspawn/nspawn-expose-ports.h +++ b/src/nspawn/nspawn-expose-ports.h @@ -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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index c50e635196a..f38f8c2a876 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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); diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index 98dd7ff3b4a..6b900f0d067 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -9,6 +9,7 @@ #include #include #include +#include #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; } diff --git a/src/test/test-firewall-util.c b/src/test/test-firewall-util.c index 14678c048d8..d5501b807d1 100644 --- a/src/test/test-firewall-util.c +++ b/src/test/test-firewall-util.c @@ -1,11 +1,128 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include #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; }