From da00b84087dd5f5dc3d0588fdef93bb71789bfb1 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 22 Mar 2021 22:33:23 +0900 Subject: [PATCH 1/8] firewall-util: logs which backend will be used This also modernizes code a bit. --- src/shared/firewall-util-private.h | 13 ++-- src/shared/firewall-util.c | 100 ++++++++++++++++++----------- src/shared/firewall-util.h | 6 +- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/shared/firewall-util-private.h b/src/shared/firewall-util-private.h index 59e1e502fd..07e2d0bbd3 100644 --- a/src/shared/firewall-util-private.h +++ b/src/shared/firewall-util-private.h @@ -4,22 +4,27 @@ #include #include -#include "in-addr-util.h" #include "sd-netlink.h" -enum FirewallBackend { +#include "in-addr-util.h" + +typedef enum FirewallBackend { FW_BACKEND_NONE, #if HAVE_LIBIPTC FW_BACKEND_IPTABLES, #endif FW_BACKEND_NFTABLES, -}; + _FW_BACKEND_MAX, + _FW_BACKEND_INVALID = -EINVAL, +} FirewallBackend; struct FirewallContext { - enum FirewallBackend firewall_backend; + FirewallBackend backend; sd_netlink *nfnl; }; +const char *firewall_backend_to_string(FirewallBackend b) _const_; + int fw_nftables_init(FirewallContext *ctx); void fw_nftables_exit(FirewallContext *ctx); diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 3bed941127..65a2250ed3 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -7,21 +7,44 @@ #include "alloc-util.h" #include "firewall-util.h" #include "firewall-util-private.h" +#include "log.h" +#include "string-table.h" -static enum FirewallBackend firewall_backend_probe(FirewallContext *ctx) { - if (fw_nftables_init(ctx) == 0) - return FW_BACKEND_NFTABLES; +static const char * const firewall_backend_table[_FW_BACKEND_MAX] = { + [FW_BACKEND_NONE] = "none", #if HAVE_LIBIPTC - return FW_BACKEND_IPTABLES; -#else - return FW_BACKEND_NONE; + [FW_BACKEND_IPTABLES] = "iptables", #endif + [FW_BACKEND_NFTABLES] = "nftables", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(firewall_backend, FirewallBackend); + +static void firewall_backend_probe(FirewallContext *ctx) { + assert(ctx); + + if (ctx->backend != _FW_BACKEND_INVALID) + return; + + if (fw_nftables_init(ctx) >= 0) + ctx->backend = FW_BACKEND_NFTABLES; + else +#if HAVE_LIBIPTC + ctx->backend = FW_BACKEND_IPTABLES; +#else + ctx->backend = FW_BACKEND_NONE; +#endif + + if (ctx->backend != FW_BACKEND_NONE) + log_debug("Using %s as firewall backend.", firewall_backend_to_string(ctx->backend)); + else + log_debug("No firewall backend found."); } int fw_ctx_new(FirewallContext **ret) { _cleanup_free_ FirewallContext *ctx = NULL; - ctx = new0(FirewallContext, 1); + ctx = new(FirewallContext, 1); if (!ctx) return -ENOMEM; @@ -32,6 +55,11 @@ int fw_ctx_new(FirewallContext **ret) { * fw_ctx_new when nspawn/networkd know they will call * fw_add_masquerade/local_dnat later anyway. */ + + *ctx = (FirewallContext) { + .backend = _FW_BACKEND_INVALID, + }; + *ret = TAKE_PTR(ctx); return 0; } @@ -40,47 +68,44 @@ FirewallContext *fw_ctx_free(FirewallContext *ctx) { if (!ctx) return NULL; - if (ctx->firewall_backend == FW_BACKEND_NFTABLES) - fw_nftables_exit(ctx); + fw_nftables_exit(ctx); return mfree(ctx); } int fw_add_masquerade( - FirewallContext **fw_ctx, + FirewallContext **ctx, bool add, int af, const union in_addr_union *source, unsigned source_prefixlen) { - FirewallContext *ctx; + int r; - if (!*fw_ctx) { - r = fw_ctx_new(fw_ctx); + assert(ctx); + + if (!*ctx) { + r = fw_ctx_new(ctx); if (r < 0) return r; } - ctx = *fw_ctx; - if (ctx->firewall_backend == FW_BACKEND_NONE) - ctx->firewall_backend = firewall_backend_probe(ctx); + firewall_backend_probe(*ctx); - switch (ctx->firewall_backend) { - case FW_BACKEND_NONE: - return -EOPNOTSUPP; + switch ((*ctx)->backend) { #if HAVE_LIBIPTC case FW_BACKEND_IPTABLES: return fw_iptables_add_masquerade(add, af, source, source_prefixlen); #endif case FW_BACKEND_NFTABLES: - return fw_nftables_add_masquerade(ctx, add, af, source, source_prefixlen); + return fw_nftables_add_masquerade(*ctx, add, af, source, source_prefixlen); + default: + return -EOPNOTSUPP; } - - return -EOPNOTSUPP; } int fw_add_local_dnat( - FirewallContext **fw_ctx, + FirewallContext **ctx, bool add, int af, int protocol, @@ -88,28 +113,27 @@ int fw_add_local_dnat( const union in_addr_union *remote, uint16_t remote_port, const union in_addr_union *previous_remote) { - FirewallContext *ctx; - if (!*fw_ctx) { - int ret = fw_ctx_new(fw_ctx); - if (ret < 0) - return ret; + int r; + + assert(ctx); + + if (!*ctx) { + r = fw_ctx_new(ctx); + if (r < 0) + return r; } - ctx = *fw_ctx; - if (ctx->firewall_backend == FW_BACKEND_NONE) - ctx->firewall_backend = firewall_backend_probe(ctx); + firewall_backend_probe(*ctx); - switch (ctx->firewall_backend) { - case FW_BACKEND_NONE: - return -EOPNOTSUPP; - case FW_BACKEND_NFTABLES: - return fw_nftables_add_local_dnat(ctx, add, af, protocol, local_port, remote, remote_port, previous_remote); + switch ((*ctx)->backend) { #if HAVE_LIBIPTC case FW_BACKEND_IPTABLES: return fw_iptables_add_local_dnat(add, af, protocol, local_port, remote, remote_port, previous_remote); #endif + case FW_BACKEND_NFTABLES: + return fw_nftables_add_local_dnat(*ctx, add, af, protocol, local_port, remote, remote_port, previous_remote); + default: + return -EOPNOTSUPP; } - - return -EOPNOTSUPP; } diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index 5180b429d3..7725a5e58d 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -9,19 +9,19 @@ typedef struct FirewallContext FirewallContext; int fw_ctx_new(FirewallContext **ret); -FirewallContext *fw_ctx_free(FirewallContext *fw_ctx); +FirewallContext *fw_ctx_free(FirewallContext *ctx); DEFINE_TRIVIAL_CLEANUP_FUNC(FirewallContext *, fw_ctx_free); int fw_add_masquerade( - FirewallContext **fw_ctx, + FirewallContext **ctx, bool add, int af, const union in_addr_union *source, unsigned source_prefixlen); int fw_add_local_dnat( - FirewallContext **fw_ctx, + FirewallContext **ctx, bool add, int af, int protocol, From 4fb8a48ff09a214a792ecc1d699ed764edc04efd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 14:37:05 +0900 Subject: [PATCH 2/8] network: allocate FirewallContext lazily --- src/network/networkd.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/network/networkd.c b/src/network/networkd.c index 24b3cb57af..48f6061b1f 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -93,10 +93,6 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = fw_ctx_new(&m->fw_ctx); - if (r < 0) - log_warning_errno(r, "Could not initialize firewall, IPMasquerade= option not available: %m"); - r = manager_start(m); if (r < 0) return log_error_errno(r, "Could not start manager: %m"); From dfb433f2a77a04d44aaa64d8364805e36cb25f5a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 14:38:18 +0900 Subject: [PATCH 3/8] firewall-util: probe firewall backend in fw_ctx_new() FirewallContext is used by networkd and nspawn. Both allocates the context when it is really necessary. Hence, it is not necessary to delay probing backend. Moreover, if iptables backend is not enabled on build, and nftables is not supported by kernel, previously `fw_nftables_init()` is called everytime when we try to configure masquerade or dnat. It causes significant performance loss. Fixes test-firewall-util issue in #19052. --- src/shared/firewall-util.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index 65a2250ed3..afa3e02b45 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -48,18 +48,12 @@ int fw_ctx_new(FirewallContext **ret) { if (!ctx) return -ENOMEM; - /* could probe here. However, this means that we will load - * iptable_nat or nf_tables, both will enable connection tracking. - * - * Alternative would be to probe here but only call - * fw_ctx_new when nspawn/networkd know they will call - * fw_add_masquerade/local_dnat later anyway. - */ - *ctx = (FirewallContext) { .backend = _FW_BACKEND_INVALID, }; + firewall_backend_probe(ctx); + *ret = TAKE_PTR(ctx); return 0; } @@ -90,8 +84,6 @@ int fw_add_masquerade( return r; } - firewall_backend_probe(*ctx); - switch ((*ctx)->backend) { #if HAVE_LIBIPTC case FW_BACKEND_IPTABLES: @@ -124,8 +116,6 @@ int fw_add_local_dnat( return r; } - firewall_backend_probe(*ctx); - switch ((*ctx)->backend) { #if HAVE_LIBIPTC case FW_BACKEND_IPTABLES: From 84af90ba5df8682cb2abecbc7bccf23aca94df77 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 01:34:30 +0900 Subject: [PATCH 4/8] firewall-util: add missing return value check --- src/shared/firewall-util-nft.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index 05c322d353..fb5857d9ec 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -1006,9 +1006,11 @@ again: assert(tsize < NFT_DNAT_MSGS); if (add) - nft_add_element(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_DNAT_MAP_NAME, key, sizeof(key), data, dlen); + r = 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, dlen); + 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++; assert(tsize < NFT_DNAT_MSGS); From 5ee7c719e1480f66eaa7ec0534c6e93d78bfce6f Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 01:52:07 +0900 Subject: [PATCH 5/8] firewall-util: do not use goto for retrying --- src/shared/firewall-util-nft.c | 132 +++++++++++++++++---------------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index fb5857d9ec..b661a81ff7 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -810,40 +810,6 @@ static int nft_message_add_setelem_iprange(sd_netlink_message *m, return 0; } -/* When someone runs 'nft flush ruleset' in the same net namespace - * this will also tear down the systemd nat table. - * - * Unlike iptables -t nat -F (which will remove all rules added by the - * systemd iptables backend, iptables has builtin chains that cannot be - * deleted -- the next add operation will 'just work'. - * - * In the nftables case, everything gets removed. The next add operation - * will yield -ENOENT. - * - * If we see -ENOENT on add, replay the initial table setup. - * If that works, re-do the add operation. - * - * Note that this doesn't protect against external sabotage such as a - * 'while true; nft flush ruleset;done'. There is nothing that could be - * done about that short of extending the kernel to allow tables to be - * owned by stystemd-networkd and making them non-deleteable except by - * the 'owning process'. - */ -static int fw_nftables_recreate_table(sd_netlink *nfnl, int af, sd_netlink_message **old, size_t size) { - int r = fw_nftables_init_family(nfnl, af); - - if (r != 0) - return r; - - while (size > 0) { - size_t i = --size; - - old[i] = sd_netlink_message_unref(old[i]); - } - - return 0; -} - static int nft_message_add_setelem_ip6range( sd_netlink_message *m, const union in_addr_union *source, @@ -877,14 +843,14 @@ static int nft_message_add_setelem_ip6range( #define NFT_MASQ_MSGS 3 -int fw_nftables_add_masquerade( +static int fw_nftables_add_masquerade_internal( FirewallContext *ctx, bool add, int af, const union in_addr_union *source, unsigned int source_prefixlen) { + sd_netlink_message *transaction[NFT_MASQ_MSGS] = {}; - bool retry = true; size_t tsize; int r; @@ -893,7 +859,7 @@ int fw_nftables_add_masquerade( if (af == AF_INET6 && source_prefixlen < 8) return -EINVAL; -again: + r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]); if (r < 0) return r; @@ -902,7 +868,6 @@ again: r = sd_nfnl_nft_message_new_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME); else r = sd_nfnl_nft_message_del_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME); - if (r < 0) goto out_unref; @@ -910,7 +875,6 @@ again: 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; @@ -919,26 +883,56 @@ again: r = sd_nfnl_message_batch_end(ctx->nfnl, &transaction[tsize]); if (r < 0) return r; + ++tsize; r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize); - if (retry && r == -ENOENT) { - int tmp = fw_nftables_recreate_table(ctx->nfnl, af, transaction, tsize); - if (tmp == 0) { - retry = false; - goto again; - } - } - out_unref: while (tsize > 0) sd_netlink_message_unref(transaction[--tsize]); return r < 0 ? r : 0; } +int fw_nftables_add_masquerade( + FirewallContext *ctx, + bool add, + int af, + const union in_addr_union *source, + unsigned int source_prefixlen) { + + int r; + + r = fw_nftables_add_masquerade_internal(ctx, add, af, source, source_prefixlen); + if (r != -ENOENT) + return r; + + /* When someone runs 'nft flush ruleset' in the same net namespace this will also tear down the + * systemd nat table. + * + * Unlike iptables -t nat -F (which will remove all rules added by the systemd iptables + * backend, iptables has builtin chains that cannot be deleted -- the next add operation will + * 'just work'. + * + * In the nftables case, everything gets removed. The next add operation will yield -ENOENT. + * + * If we see -ENOENT on add, replay the initial table setup. If that works, re-do the add + * operation. + * + * Note that this doesn't protect against external sabotage such as a + * 'while true; nft flush ruleset; done'. There is nothing that could be done about that short + * of extending the kernel to allow tables to be owned by stystemd-networkd and making them + * non-deleteable except by the 'owning process'. */ + + r = fw_nftables_init_family(ctx->nfnl, af); + if (r < 0) + return r; + + return fw_nftables_add_masquerade_internal(ctx, add, af, source, source_prefixlen); +} + #define NFT_DNAT_MSGS 4 -int fw_nftables_add_local_dnat( +static int fw_nftables_add_local_dnat_internal( FirewallContext *ctx, bool add, int af, @@ -947,9 +941,9 @@ 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[5], key[2], dlen; + sd_netlink_message *transaction[NFT_DNAT_MSGS] = {}; - bool retry = true; + uint32_t data[5], key[2], dlen; size_t tsize; int r; @@ -961,7 +955,6 @@ int fw_nftables_add_local_dnat( if (local_port <= 0) return -EINVAL; -again: key[0] = protocol; key[1] = htobe16(local_port); @@ -1023,19 +1016,34 @@ again: assert(tsize <= NFT_DNAT_MSGS); r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize); - if (retry && r == -ENOENT) { - 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; - } - } - out_unref: while (tsize > 0) sd_netlink_message_unref(transaction[--tsize]); + return r < 0 ? r : 0; } + +int fw_nftables_add_local_dnat( + FirewallContext *ctx, + bool add, + int af, + int protocol, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + + int r; + + r = fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, previous_remote); + if (r != -ENOENT) + return r; + + /* See comment in fw_nftables_add_masquerade(). */ + r = fw_nftables_init_family(ctx->nfnl, af); + if (r < 0) + return r; + + /* table created anew; previous address already gone */ + return fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, NULL); +} From 175bc86315403a6c779530c7f3c495ffe1c2e9ef Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 01:57:51 +0900 Subject: [PATCH 6/8] firewall-util: gracefully handle -EOVERFLOW returned from older kernel --- src/shared/firewall-util-nft.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index b661a81ff7..1c6a25c4c0 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -943,12 +943,16 @@ static int fw_nftables_add_local_dnat_internal( const union in_addr_union *previous_remote) { sd_netlink_message *transaction[NFT_DNAT_MSGS] = {}; + static bool ipv6_supported = true; uint32_t data[5], key[2], dlen; size_t tsize; int r; assert(add || !previous_remote); + if (!ipv6_supported && af == AF_INET6) + return -EOPNOTSUPP; + if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) return -EPROTONOSUPPORT; @@ -1014,7 +1018,16 @@ static int fw_nftables_add_local_dnat_internal( tsize++; assert(tsize <= NFT_DNAT_MSGS); + r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize); + if (r == -EOVERFLOW && af == AF_INET6) { + /* The current implementation of DNAT in systemd requires kernel's + * fdb9c405e35bdc6e305b9b4e20ebc141ed14fc81 (v5.8), and the older kernel returns + * -EOVERFLOW. Let's treat the error as -EOPNOTSUPP. */ + log_debug_errno(r, "The current implementation of IPv6 DNAT in systemd requires kernel 5.8 or newer, ignoring: %m"); + ipv6_supported = false; + r = -EOPNOTSUPP; + } out_unref: while (tsize > 0) From 0c4363a0052ffaafc7d7571d148cb77dd795ebd3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 23 Mar 2021 12:02:54 +0900 Subject: [PATCH 7/8] firewall-util: refuse IPv6 firewall rules when kernel does not support IPv6 --- src/shared/firewall-util-nft.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index 1c6a25c4c0..ecabc5fc40 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -756,9 +756,11 @@ 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"); + if (socket_ipv6_is_supported()) { + 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; @@ -902,6 +904,9 @@ int fw_nftables_add_masquerade( int r; + if (!socket_ipv6_is_supported() && af == AF_INET6) + return -EOPNOTSUPP; + r = fw_nftables_add_masquerade_internal(ctx, add, af, source, source_prefixlen); if (r != -ENOENT) return r; @@ -1048,6 +1053,9 @@ int fw_nftables_add_local_dnat( int r; + if (!socket_ipv6_is_supported() && af == AF_INET6) + return -EOPNOTSUPP; + r = fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, previous_remote); if (r != -ENOENT) return r; From b5d2f4e757ba2beda2f911c14ee622d59fde7de9 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 22 Mar 2021 22:44:25 +0900 Subject: [PATCH 8/8] test-firewall-util: use assert_se() at most places Otherwise, we cannot notice any failures... --- src/test/test-firewall-util.c | 160 +++++++++++++++++----------------- 1 file changed, 78 insertions(+), 82 deletions(-) diff --git a/src/test/test-firewall-util.c b/src/test/test-firewall-util.c index 8d69f192d8..abc28d500d 100644 --- a/src/test/test-firewall-util.c +++ b/src/test/test-firewall-util.c @@ -1,114 +1,110 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "firewall-util.h" +#include "firewall-util-private.h" #include "log.h" #include "random-util.h" +#include "socket-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))} -#define MAKE_IN6_ADDR_UNION(str, u) assert_se(in_addr_from_string(AF_INET6, str, u) >= 0) - -static void test_v6(FirewallContext **ctx) { - union in_addr_union u = {}, u2 = {}; +static void test_v6(FirewallContext *ctx) { + union in_addr_union u1, u2, u3; uint8_t prefixlen; int r; - MAKE_IN6_ADDR_UNION("dead::beef", &u); + log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); - r = fw_add_masquerade(ctx, true, AF_INET6, &u, 128); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); + if (!socket_ipv6_is_supported()) + return log_info("IPv6 is not supported by kernel, skipping tests."); - r = fw_add_masquerade(ctx, false, AF_INET6, &u, 128); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); + assert_se(in_addr_from_string(AF_INET6, "dead::beef", &u1) >= 0); + assert_se(in_addr_from_string(AF_INET6, "1c3::c01d", &u2) >= 0); - r = fw_add_masquerade(ctx, true, AF_INET6, &u, 64); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); + prefixlen = random_u64_range(128 + 1 - 8) + 8; + pseudo_random_bytes(&u3, sizeof(u3)); - r = fw_add_masquerade(ctx, false, AF_INET6, &u, 64); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 128) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 128) >= 0); + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 64) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 64) >= 0); + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u3, prefixlen) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u3, prefixlen) >= 0); - 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"); + r = fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u1, 815, NULL); + if (r == -EOPNOTSUPP) { + log_info("IPv6 DNAT seems not supported, skipping the following tests."); + return; + } + assert_se(r >= 0); - 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"); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u1) >= 0); + assert_se(fw_add_local_dnat(&ctx, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL) >= 0); - 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)); +static union in_addr_union *parse_addr(const char *str, union in_addr_union *u) { + assert(str); + assert_se(in_addr_from_string(AF_INET, str, u) >= 0); + return u; +} - r = fw_add_masquerade(ctx, true, AF_INET6, &u, prefixlen); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); +static bool test_v4(FirewallContext *ctx) { + union in_addr_union u, v; + int r; - r = fw_add_masquerade(ctx, false, AF_INET6, &u, prefixlen); - if (r < 0) - log_error_errno(r, "Failed to modify ipv6 firewall: %m"); + log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); + + assert_se(fw_add_masquerade(&ctx, true, AF_INET, NULL, 0) == -EINVAL); + assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.0", &u), 0) == -EINVAL); + + r = fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.3", &u), 32); + if (r < 0) { + bool ignore = IN_SET(r, -EPERM, -EOPNOTSUPP, -ENOPROTOOPT); + + log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r, + "Failed to add IPv4 masquerade%s: %m", + ignore ? ", skipping following tests" : ""); + + if (ignore) + return false; + } + assert(r >= 0); + + assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.1.2.3", &u), 32) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, parse_addr("1.2.3.4", &v)) >= 0); + assert_se(fw_add_local_dnat(&ctx, false, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, NULL) >= 0); + + return true; } int main(int argc, char *argv[]) { - _cleanup_(fw_ctx_freep) FirewallContext *ctx; - int r; + _cleanup_(fw_ctx_freep) FirewallContext *ctx = NULL; + test_setup_logging(LOG_DEBUG); - uint8_t prefixlen = 32; - r = fw_ctx_new(&ctx); - if (r < 0) - return log_error_errno(r, "Failed to init firewall: %m"); + if (getuid() != 0) + return log_tests_skipped("not root"); - r = fw_add_masquerade(&ctx, true, AF_INET, NULL, 0); - if (r == 0) - log_error("Expected failure: NULL source"); + assert_se(fw_ctx_new(&ctx) >= 0); - r = fw_add_masquerade(&ctx, true, AF_INET, &MAKE_IN_ADDR_UNION(10,1,2,0), 0); - if (r == 0) - log_error("Expected failure: 0 prefixlen"); + if (ctx->backend == FW_BACKEND_NONE) + return EXIT_TEST_SKIP; - r = fw_add_masquerade(&ctx, true, AF_INET, &MAKE_IN_ADDR_UNION(10,1,2,3), prefixlen); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); + if (test_v4(ctx) && ctx->backend == FW_BACKEND_NFTABLES) + test_v6(ctx); - prefixlen = 28; - r = fw_add_masquerade(&ctx, true, AF_INET, &MAKE_IN_ADDR_UNION(10,0,2,0), prefixlen); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_masquerade(&ctx, false, AF_INET, &MAKE_IN_ADDR_UNION(10,0,2,0), prefixlen); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_masquerade(&ctx, false, AF_INET, &MAKE_IN_ADDR_UNION(10,1,2,3), 32); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 4), 815, NULL); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, &MAKE_IN_ADDR_UNION(1, 2, 3, 4)); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - r = fw_add_local_dnat(&ctx, false, AF_INET, IPPROTO_TCP, 4711, &MAKE_IN_ADDR_UNION(1, 2, 3, 5), 815, NULL); - if (r < 0) - log_error_errno(r, "Failed to modify firewall: %m"); - - test_v6(&ctx); +#if HAVE_LIBIPTC + if (ctx->backend != FW_BACKEND_IPTABLES) { + ctx->backend = FW_BACKEND_IPTABLES; + test_v4(ctx); + } +#endif return 0; }