diff --git a/meson.build b/meson.build index dddb776615..52850311b5 100644 --- a/meson.build +++ b/meson.build @@ -1305,11 +1305,6 @@ endif conf.set10('HAVE_LIBIDN', not have and libidn.found()) conf.set10('HAVE_LIBIDN2', have) -libiptc = dependency('libiptc', - required : get_option('libiptc')) -conf.set10('HAVE_LIBIPTC', libiptc.found()) -libiptc_cflags = libiptc.partial_dependency(includes: true, compile_args: true) - libqrencode = dependency('libqrencode', version : '>= 3', required : get_option('qrencode')) @@ -3052,7 +3047,6 @@ foreach tuple : [ ['libfido2'], ['libidn'], ['libidn2'], - ['libiptc'], ['microhttpd'], ['openssl'], ['p11kit'], diff --git a/meson_options.txt b/meson_options.txt index cd4b556283..464f388e22 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -432,7 +432,7 @@ option('libidn2', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : description : 'libidn2 support') option('libidn', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'libidn support') -option('libiptc', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, +option('libiptc', type : 'feature', deprecated : true, description : 'libiptc support') option('qrencode', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, description : 'libqrencode support') diff --git a/src/basic/build.c b/src/basic/build.c index ad9d3b1c4b..b9341b184d 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -126,12 +126,6 @@ const char* const systemd_features = " -IDN" #endif -#if HAVE_LIBIPTC - " +IPTC" -#else - " -IPTC" -#endif - #if HAVE_KMOD " +KMOD" #else diff --git a/src/basic/forward.h b/src/basic/forward.h index 66da2b13a5..a9fda3b917 100644 --- a/src/basic/forward.h +++ b/src/basic/forward.h @@ -284,7 +284,6 @@ typedef struct ConfigTableItem ConfigTableItem; typedef struct CPUSet CPUSet; typedef struct FDSet FDSet; typedef struct Fido2HmacSalt Fido2HmacSalt; -typedef struct FirewallContext FirewallContext; typedef struct GroupRecord GroupRecord; typedef struct Image Image; typedef struct ImagePolicy ImagePolicy; diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 48149e15bd..db51539e01 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -28,12 +28,12 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" -#include "firewall-util.h" #include "in-addr-prefix-util.h" #include "inotify-util.h" #include "ip-protocol-list.h" #include "limits-util.h" #include "manager.h" +#include "netlink-internal.h" #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" @@ -1335,12 +1335,10 @@ void unit_modify_nft_set(Unit *u, bool add) { if (!crt || crt->cgroup_id == 0) return; - if (!u->manager->fw_ctx) { - r = fw_ctx_new_full(&u->manager->fw_ctx, /* init_tables= */ false); + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); if (r < 0) return; - - assert(u->manager->fw_ctx); } CGroupContext *c = ASSERT_PTR(unit_get_cgroup_context(u)); @@ -1351,7 +1349,7 @@ void unit_modify_nft_set(Unit *u, bool add) { uint64_t element = crt->cgroup_id; - r = nft_set_element_modify_any(u->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); + r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); if (r < 0) log_warning_errno(r, "Failed to %s NFT set entry: family %s, table %s, set %s, cgroup %" PRIu64 ", ignoring: %m", add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, crt->cgroup_id); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index dd4a6e8367..82e2a9f961 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -9,7 +9,6 @@ #include "cgroup-util.h" #include "dbus-cgroup.h" #include "escape.h" -#include "firewall-util.h" #include "in-addr-prefix-util.h" #include "limits-util.h" #include "manager.h" diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c index 12ce5a86e4..1f694b29d9 100644 --- a/src/core/kmod-setup.c +++ b/src/core/kmod-setup.c @@ -115,10 +115,6 @@ int kmod_setup(void) { /* This should never be a module */ { "unix", "/proc/net/unix", true, true, NULL }, -#if HAVE_LIBIPTC - /* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */ - { "ip_tables", "/proc/net/ip_tables_names", false, false, NULL }, -#endif /* virtio_rng would be loaded by udev later, but real entropy might be needed very early */ { "virtio_rng", NULL, false, false, has_virtio_rng }, diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 4b7bfc772f..c4ebec1601 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -32,7 +32,6 @@ #include "execute.h" #include "extract-word.h" #include "fd-util.h" -#include "firewall-util.h" #include "fstab-util.h" #include "hashmap.h" #include "hexdecoct.h" diff --git a/src/core/manager.c b/src/core/manager.c index 5bba7e81b0..2529a7c3f1 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -12,6 +12,7 @@ #include "sd-bus.h" #include "sd-daemon.h" #include "sd-messages.h" +#include "sd-netlink.h" #include "sd-path.h" #include "all-units.h" @@ -1753,7 +1754,7 @@ Manager* manager_free(Manager *m) { free(m->watchdog_pretimeout_governor); free(m->watchdog_pretimeout_governor_overridden); - fw_ctx_free(m->fw_ctx); + sd_netlink_unref(m->nfnl); #if BPF_FRAMEWORK bpf_restrict_fs_destroy(m->restrict_fs); diff --git a/src/core/manager.h b/src/core/manager.h index a7009a49d7..b4f34f66e6 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -474,7 +474,7 @@ typedef struct Manager { sd_event_source *memory_pressure_event_source; /* For NFTSet= */ - FirewallContext *fw_ctx; + sd_netlink *nfnl; /* Pin the systemd-executor binary, so that it never changes until re-exec, ensuring we don't have * serialization/deserialization compatibility issues during upgrades. */ diff --git a/src/core/unit.c b/src/core/unit.c index a6c3fd8bda..22c080da09 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -44,6 +44,7 @@ #include "manager.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "netlink-internal.h" #include "path-util.h" #include "process-util.h" #include "quota-util.h" @@ -5290,19 +5291,17 @@ static void unit_modify_user_nft_set(Unit *u, bool add, NFTSetSource source, uin if (!c) return; - if (!u->manager->fw_ctx) { - r = fw_ctx_new_full(&u->manager->fw_ctx, /* init_tables= */ false); + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); if (r < 0) return; - - assert(u->manager->fw_ctx); } FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) { if (nft_set->source != source) continue; - r = nft_set_element_modify_any(u->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); + r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); if (r < 0) log_warning_errno(r, "Failed to %s NFT set entry: family %s, table %s, set %s, ID %u, ignoring: %m", add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, element); diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index 57e5a65214..fe0c1a0080 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -466,7 +466,8 @@ static int timeout_compare(const void *a, const void *b) { } size_t netlink_get_reply_callback_count(sd_netlink *nl) { - assert(nl); + if (!nl) + return 0; return hashmap_size(nl->reply_callbacks); } diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 716bee731f..131a84aa72 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -669,6 +669,10 @@ static int address_set_masquerade(Address *address, bool add) { assert(address); assert(address->link); + assert(address->link->manager); + + if (!address->link->manager->nfnl) + return 0; if (!address->link->network) return 0; @@ -687,7 +691,7 @@ static int address_set_masquerade(Address *address, bool add) { if (r < 0) return r; - r = fw_add_masquerade(&address->link->manager->fw_ctx, add, address->family, &masked, address->prefixlen); + r = fw_nftables_add_masquerade(address->link->manager->nfnl, add, address->family, &masked, address->prefixlen); if (r < 0) return r; @@ -702,14 +706,9 @@ static void address_modify_nft_set_context(Address *address, bool add, NFTSetCon assert(address); assert(address->link); assert(address->link->manager); + assert(address->link->manager->nfnl); assert(nft_set_context); - if (!address->link->manager->fw_ctx) { - r = fw_ctx_new_full(&address->link->manager->fw_ctx, /* init_tables= */ false); - if (r < 0) - return; - } - FOREACH_ARRAY(nft_set, nft_set_context->sets, nft_set_context->n_sets) { uint32_t ifindex; @@ -717,16 +716,16 @@ static void address_modify_nft_set_context(Address *address, bool add, NFTSetCon switch (nft_set->source) { case NFT_SET_SOURCE_ADDRESS: - r = nft_set_element_modify_ip(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set, + r = nft_set_element_modify_ip(address->link->manager->nfnl, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set, &address->in_addr); break; case NFT_SET_SOURCE_PREFIX: - r = nft_set_element_modify_iprange(address->link->manager->fw_ctx, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set, + r = nft_set_element_modify_iprange(address->link->manager->nfnl, add, nft_set->nfproto, address->family, nft_set->table, nft_set->set, &address->in_addr, address->prefixlen); break; case NFT_SET_SOURCE_IFINDEX: ifindex = address->link->ifindex; - r = nft_set_element_modify_any(address->link->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, + r = nft_set_element_modify_any(address->link->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &ifindex, sizeof(ifindex)); break; default: @@ -749,6 +748,10 @@ static void address_modify_nft_set_context(Address *address, bool add, NFTSetCon static void address_modify_nft_set(Address *address, bool add) { assert(address); assert(address->link); + assert(address->link->manager); + + if (!address->link->manager->nfnl) + return; if (!IN_SET(address->family, AF_INET, AF_INET6)) return; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index ef61a90fcb..4004be81c0 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -23,9 +23,9 @@ #include "env-util.h" #include "errno-util.h" #include "fd-util.h" -#include "firewall-util.h" #include "initrd-util.h" #include "mount-util.h" +#include "netlink-internal.h" #include "netlink-util.h" #include "networkd-address.h" #include "networkd-address-label.h" @@ -285,6 +285,28 @@ static int manager_connect_genl(Manager *m) { return 0; } +static int manager_connect_nfnl(Manager *m) { + int r; + + assert(m); + + r = sd_nfnl_socket_open(&m->nfnl); + if (r < 0) { + log_warning_errno(r, "Failed to open nftables netlink socket. IPMasquerade= and NFTSet= settings will not be applied. Ignoring: %m"); + return 0; + } + + r = sd_netlink_increase_rxbuf(m->nfnl, RCVBUF_SIZE); + if (r < 0) + log_warning_errno(r, "Failed to increase receive buffer size for nftables netlink socket, ignoring: %m"); + + r = sd_netlink_attach_event(m->nfnl, m->event, 0); + if (r < 0) + return r; + + return 0; +} + static int manager_setup_rtnl_filter(Manager *manager) { struct sock_filter filter[] = { /* Check the packet length. */ @@ -435,7 +457,7 @@ static int manager_post_handler(sd_event_source *s, void *userdata) { if (netlink_get_reply_callback_count(manager->rtnl) > 0 || netlink_get_reply_callback_count(manager->genl) > 0 || - fw_ctx_get_reply_callback_count(manager->fw_ctx) > 0) + netlink_get_reply_callback_count(manager->nfnl) > 0) return 0; /* There are some message calls waiting for their replies. */ (void) manager_serialize(manager); @@ -557,6 +579,10 @@ int manager_setup(Manager *m) { if (r < 0) return r; + r = manager_connect_nfnl(m); + if (r < 0) + return r; + if (m->test_mode) return 0; @@ -696,6 +722,7 @@ Manager* manager_free(Manager *m) { sd_netlink_unref(m->rtnl); sd_netlink_unref(m->genl); + sd_netlink_unref(m->nfnl); sd_resolve_unref(m->resolve); m->routes = set_free(m->routes); @@ -720,8 +747,6 @@ Manager* manager_free(Manager *m) { safe_close(m->ethtool_fd); safe_close(m->persistent_storage_fd); - m->fw_ctx = fw_ctx_free(m->fw_ctx); - m->serialization_fd = safe_close(m->serialization_fd); return mfree(m); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index ad3d815b0c..e350fe80c5 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -17,6 +17,7 @@ typedef struct Manager { sd_netlink *rtnl; /* lazy initialized */ sd_netlink *genl; + sd_netlink *nfnl; sd_event *event; sd_resolve *resolve; sd_bus *bus; @@ -103,8 +104,6 @@ typedef struct Manager { usec_t speed_meter_usec_new; usec_t speed_meter_usec_old; - FirewallContext *fw_ctx; - bool request_queued; OrderedSet *request_queue; OrderedSet *remove_request_queue; diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index bc2e7095fa..0ab372b4b2 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -293,7 +293,7 @@ int manager_process_requests(Manager *manager) { * queued, then this event may make reply callback queue in sd-netlink full. */ if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || - fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) + netlink_get_reply_callback_count(manager->nfnl) >= REPLY_CALLBACK_COUNT_THRESHOLD) break; /* Avoid the request and link freed by req->process() and request_detach(). */ diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c index 9a194089da..e312a90710 100644 --- a/src/nspawn/nspawn-expose-ports.c +++ b/src/nspawn/nspawn-expose-ports.c @@ -76,12 +76,13 @@ void expose_port_free_all(ExposePort *p) { LIST_CLEAR(ports, p, free); } -int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed) { +int expose_port_flush(sd_netlink *nfnl, ExposePort* l, int af, union in_addr_union *exposed) { int r; + assert(IN_SET(af, AF_INET, AF_INET6)); assert(exposed); - if (!l) + if (!nfnl || !l) return 0; if (!in_addr_is_set(af, exposed)) @@ -90,14 +91,15 @@ int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_ log_debug("Lost IP address."); LIST_FOREACH(ports, p, l) { - r = fw_add_local_dnat(fw_ctx, - false, - af, - p->protocol, - p->host_port, - exposed, - p->container_port, - NULL); + r = fw_nftables_add_local_dnat( + nfnl, + /* add = */ false, + af, + p->protocol, + p->host_port, + exposed, + p->container_port, + /* previous_remote = */ NULL); if (r < 0) log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af)); } @@ -106,12 +108,15 @@ int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_ return 0; } -int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort *l, int af, union in_addr_union *exposed) { +int expose_port_execute(sd_netlink *rtnl, sd_netlink *nfnl, ExposePort *l, int af, union in_addr_union *exposed) { _cleanup_free_ struct local_address *addresses = NULL; union in_addr_union new_exposed; bool add; int r; + assert(rtnl); + assert(nfnl); + assert(IN_SET(af, AF_INET, AF_INET6)); assert(exposed); /* Invoked each time an address is added or removed inside the @@ -129,7 +134,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, af, exposed); + return expose_port_flush(nfnl, l, af, exposed); new_exposed = addresses[0].address; if (in_addr_equal(af, exposed, &new_exposed)) @@ -138,14 +143,15 @@ int expose_port_execute(sd_netlink *rtnl, FirewallContext **fw_ctx, ExposePort * log_debug("New container IP is %s.", IN_ADDR_TO_STRING(af, &new_exposed)); LIST_FOREACH(ports, p, l) { - r = fw_add_local_dnat(fw_ctx, - true, - af, - p->protocol, - p->host_port, - &new_exposed, - p->container_port, - in_addr_is_set(af, exposed) ? exposed : NULL); + r = fw_nftables_add_local_dnat( + nfnl, + /* add = */ true, + af, + p->protocol, + p->host_port, + &new_exposed, + p->container_port, + in_addr_is_set(af, exposed) ? exposed : NULL); if (r < 0) log_warning_errno(r, "Failed to modify %s firewall: %m", af_to_name(af)); } diff --git a/src/nspawn/nspawn-expose-ports.h b/src/nspawn/nspawn-expose-ports.h index 4f1ecc2299..3a17179462 100644 --- a/src/nspawn/nspawn-expose-ports.h +++ b/src/nspawn/nspawn-expose-ports.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "firewall-util.h" #include "forward.h" #include "list.h" @@ -18,5 +17,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, int af, union in_addr_union *exposed); -int expose_port_flush(FirewallContext **fw_ctx, ExposePort* l, int af, union in_addr_union *exposed); +int expose_port_execute(sd_netlink *rtnl, sd_netlink *nfnl, ExposePort *l, int af, union in_addr_union *exposed); +int expose_port_flush(sd_netlink *nfnl, ExposePort* l, int af, union in_addr_union *exposed); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index ab8746c442..88187a906e 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -71,6 +71,7 @@ #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" +#include "netlink-internal.h" #include "notify-recv.h" #include "nspawn-bind-user.h" #include "nspawn-cgroup.h" @@ -2539,7 +2540,7 @@ static int setup_kmsg(int fd_inner_socket) { struct ExposeArgs { union in_addr_union address4; union in_addr_union address6; - struct FirewallContext *fw_ctx; + sd_netlink *nfnl; }; static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { @@ -2548,8 +2549,8 @@ static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *user assert(rtnl); assert(m); - (void) expose_port_execute(rtnl, &args->fw_ctx, arg_expose_ports, AF_INET, &args->address4); - (void) expose_port_execute(rtnl, &args->fw_ctx, arg_expose_ports, AF_INET6, &args->address6); + (void) expose_port_execute(rtnl, args->nfnl, arg_expose_ports, AF_INET, &args->address4); + (void) expose_port_execute(rtnl, args->nfnl, arg_expose_ports, AF_INET6, &args->address6); return 0; } @@ -5607,8 +5608,8 @@ static int run_container( if (r < 0) return r; - (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); + (void) expose_port_execute(rtnl, expose_args->nfnl, arg_expose_ports, AF_INET, &expose_args->address4); + (void) expose_port_execute(rtnl, expose_args->nfnl, arg_expose_ports, AF_INET6, &expose_args->address6); } _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; @@ -5730,8 +5731,8 @@ static int run_container( return 0; /* finito */ } - 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); + expose_port_flush(expose_args->nfnl, arg_expose_ports, AF_INET, &expose_args->address4); + expose_port_flush(expose_args->nfnl, arg_expose_ports, AF_INET6, &expose_args->address6); (void) remove_veth_links(veth_name, arg_network_veth_extra); *veth_created = false; @@ -5900,7 +5901,7 @@ static int run(int argc, char *argv[]) { _cleanup_(rmdir_and_freep) char *rootdir = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; - _cleanup_(fw_ctx_freep) FirewallContext *fw_ctx = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL; _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; log_setup(); @@ -6385,12 +6386,12 @@ static int run(int argc, char *argv[]) { } if (arg_expose_ports) { - r = fw_ctx_new(&fw_ctx); + r = sd_nfnl_socket_open(&nfnl); if (r < 0) { - log_error_errno(r, "Cannot expose configured ports, firewall initialization failed: %m"); + log_error_errno(r, "Cannot expose configured ports, failed to initialize nftables: %m"); goto finish; } - expose_args.fw_ctx = fw_ctx; + expose_args.nfnl = nfnl; } for (;;) { @@ -6454,8 +6455,8 @@ finish: cleanup_propagation_and_export_directories(); - 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); + expose_port_flush(nfnl, arg_expose_ports, AF_INET, &expose_args.address4); + expose_port_flush(nfnl, arg_expose_ports, AF_INET6, &expose_args.address6); if (arg_userns_mode != USER_NAMESPACE_MANAGED) { if (veth_created) diff --git a/src/shared/firewall-util-iptables.c b/src/shared/firewall-util-iptables.c deleted file mode 100644 index 1e5b59f0dd..0000000000 --- a/src/shared/firewall-util-iptables.c +++ /dev/null @@ -1,383 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dlfcn-util.h" -#include "firewall-util-private.h" -#include "in-addr-util.h" -#include "log.h" -#include "socket-util.h" - -static DLSYM_PROTOTYPE(iptc_check_entry) = NULL; -static DLSYM_PROTOTYPE(iptc_commit) = NULL; -static DLSYM_PROTOTYPE(iptc_delete_entry) = NULL; -static DLSYM_PROTOTYPE(iptc_free) = NULL; -static DLSYM_PROTOTYPE(iptc_init) = NULL; -static DLSYM_PROTOTYPE(iptc_insert_entry) = NULL; -static DLSYM_PROTOTYPE(iptc_strerror) = NULL; - -static void *iptc_dl = NULL; - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct xtc_handle*, sym_iptc_free, NULL); - -static int entry_fill_basics( - struct ipt_entry *entry, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - assert(entry); - - if (out_interface && !ifname_valid(out_interface)) - return -EINVAL; - if (in_interface && !ifname_valid(in_interface)) - return -EINVAL; - - entry->ip.proto = protocol; - - if (in_interface) { - size_t l; - - l = strlen(in_interface); - assert(l < sizeof entry->ip.iniface); - assert(l < sizeof entry->ip.iniface_mask); - - strcpy(entry->ip.iniface, in_interface); - memset(entry->ip.iniface_mask, 0xFF, l + 1); - } - if (source) { - entry->ip.src = source->in; - in4_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); - } - - if (out_interface) { - size_t l = strlen(out_interface); - assert(l < sizeof entry->ip.outiface); - assert(l < sizeof entry->ip.outiface_mask); - - strcpy(entry->ip.outiface, out_interface); - memset(entry->ip.outiface_mask, 0xFF, l + 1); - } - if (destination) { - entry->ip.dst = destination->in; - in4_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); - } - - return 0; -} - -int fw_iptables_add_masquerade( - bool add, - int af, - const union in_addr_union *source, - unsigned source_prefixlen) { - - static const xt_chainlabel chain = "POSTROUTING"; - _cleanup_(sym_iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - size_t sz; - struct nf_nat_ipv4_multi_range_compat *mr; - int r, protocol = 0; - const char *out_interface = NULL; - const union in_addr_union *destination = NULL; - unsigned destination_prefixlen = 0; - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (!source || source_prefixlen == 0) - return -EINVAL; - - r = fw_iptables_init_nat(&h); - if (r < 0) - return r; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - /* Put together the entry we want to add or remove */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); - r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - - /* Create a search mask entry */ - mask = alloca_safe(sz); - memset(mask, 0xFF, sz); - - if (add) { - if (sym_iptc_check_entry(chain, entry, (unsigned char*) mask, h)) - return 0; - if (errno != ENOENT) /* if other error than not existing yet, fail */ - return -errno; - - if (!sym_iptc_insert_entry(chain, entry, 0, h)) - return -errno; - } else { - if (!sym_iptc_delete_entry(chain, entry, (unsigned char*) mask, h)) { - if (errno == ENOENT) /* if it's already gone, all is good! */ - return 0; - - return -errno; - } - } - - if (!sym_iptc_commit(h)) - return -errno; - - return 0; -} - -int fw_iptables_add_local_dnat( - 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) { - - static const xt_chainlabel chain_pre = "PREROUTING", chain_output = "OUTPUT"; - _cleanup_(sym_iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - struct ipt_entry_match *m; - struct xt_addrtype_info_v1 *at; - struct nf_nat_ipv4_multi_range_compat *mr; - size_t sz, msz; - int r; - const char *in_interface = NULL; - const union in_addr_union *source = NULL; - unsigned source_prefixlen = 0; - const union in_addr_union *destination = NULL; - unsigned destination_prefixlen = 0; - - assert(add || !previous_remote); - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) - return -EOPNOTSUPP; - - if (local_port <= 0) - return -EINVAL; - - if (remote_port <= 0) - return -EINVAL; - - r = fw_iptables_init_nat(&h); - if (r < 0) - return r; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - if (protocol == IPPROTO_TCP) - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_tcp)); - else - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_udp)); - - sz += msz; - - /* Fill in basic part */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = - XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - msz; - r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in first match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); - m->u.match_size = msz; - if (protocol == IPPROTO_TCP) { - struct xt_tcp *tcp; - - strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); - tcp = (struct xt_tcp*) m->data; - tcp->dpts[0] = tcp->dpts[1] = local_port; - tcp->spts[0] = 0; - tcp->spts[1] = 0xFFFF; - - } else { - struct xt_udp *udp; - - strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); - udp = (struct xt_udp*) m->data; - udp->dpts[0] = udp->dpts[1] = local_port; - udp->spts[0] = 0; - udp->spts[1] = 0xFFFF; - } - - /* Fill in second match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); - m->u.match_size = - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); - strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); - m->u.user.revision = 1; - at = (struct xt_addrtype_info_v1*) m->data; - at->dest = XT_ADDRTYPE_LOCAL; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - if (protocol == IPPROTO_TCP) - mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htobe16(remote_port); - else - mr->range[0].min.udp.port = mr->range[0].max.udp.port = htobe16(remote_port); - - mask = alloca0(sz); - memset(mask, 0xFF, sz); - - if (add) { - /* Add the PREROUTING rule, if it is missing so far */ - if (!sym_iptc_check_entry(chain_pre, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -EINVAL; - - if (!sym_iptc_insert_entry(chain_pre, entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!sym_iptc_delete_entry(chain_pre, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - } - - /* Add the OUTPUT rule, if it is missing so far */ - if (!in_interface) { - - /* Don't apply onto loopback addresses */ - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!sym_iptc_check_entry(chain_output, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - - if (!sym_iptc_insert_entry(chain_output, entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!sym_iptc_delete_entry(chain_output, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - } else { - if (!sym_iptc_delete_entry(chain_pre, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - if (!in_interface) { - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!sym_iptc_delete_entry(chain_output, entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - - if (!sym_iptc_commit(h)) - return -errno; - - return 0; -} - -static int dlopen_iptc(void) { - ELF_NOTE_DLOPEN("ip4tc", - "Support for firewall rules with iptables backend", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libip4tc.so.2"); - - return dlopen_many_sym_or_warn( - &iptc_dl, - "libip4tc.so.2", LOG_DEBUG, - DLSYM_ARG(iptc_check_entry), - DLSYM_ARG(iptc_commit), - DLSYM_ARG(iptc_delete_entry), - DLSYM_ARG(iptc_free), - DLSYM_ARG(iptc_init), - DLSYM_ARG(iptc_insert_entry), - DLSYM_ARG(iptc_strerror)); -} - -int fw_iptables_init_nat(struct xtc_handle **ret) { - _cleanup_(sym_iptc_freep) struct xtc_handle *h = NULL; - int r; - - r = dlopen_iptc(); - if (r < 0) - return r; - - h = sym_iptc_init("nat"); - if (!h) - return log_debug_errno(errno, "Failed to init \"nat\" table: %s", sym_iptc_strerror(errno)); - - if (ret) - *ret = TAKE_PTR(h); - - return 0; -} diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c deleted file mode 100644 index 66c0665d66..0000000000 --- a/src/shared/firewall-util-nft.c +++ /dev/null @@ -1,1376 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "escape.h" -#include "extract-word.h" -#include "firewall-util.h" -#include "firewall-util-private.h" -#include "in-addr-util.h" -#include "log.h" -#include "netlink-internal.h" -#include "parse-util.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "time-util.h" - -#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport" -#define NFT_SYSTEMD_TABLE_NAME "io.systemd.nat" -#define NFT_SYSTEMD_MASQ_SET_NAME "masq_saddr" - -#define NFNL_DEFAULT_TIMEOUT_USECS (1ULL * USEC_PER_SEC) - -#define UDP_DPORT_OFFSET 2 - -/* for test-firewall-util */ -static const char* nft_table_name(void) { - static const char *cached = NULL; - - if (!cached) - cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_NFT_TABLE_NAME") ?: NFT_SYSTEMD_TABLE_NAME; - - return cached; -} - -static const char* dnat_map_name(void) { - static const char *cached = NULL; - - if (!cached) - cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_DNAT_MAP_NAME") ?: NFT_SYSTEMD_DNAT_MAP_NAME; - - return cached; -} - -static sd_netlink_message** netlink_message_unref_many(sd_netlink_message **m) { - if (!m) - return NULL; - - /* This does not free array. The end of the array must be NULL. */ - - for (sd_netlink_message **p = m; *p; p++) - *p = sd_netlink_message_unref(*p); - - return m; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_netlink_message**, netlink_message_unref_many); - -static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { - int r; - - assert(m); - assert(name); - - r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM); - if (r < 0) - return r; - - return sd_netlink_message_open_container_union(m, NFTA_EXPR_DATA, name); -} - -static int nfnl_close_expr_container(sd_netlink_message *m) { - int r; - - assert(m); - - r = sd_netlink_message_close_container(m); /* NFTA_EXPR_DATA */ - if (r < 0) - return r; - - return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */ -} - -static int nfnl_add_expr_fib( - sd_netlink_message *m, - uint32_t nft_fib_flags, - enum nft_fib_result result, - enum nft_registers dreg) { - - int r; - - assert(m); - - r = nfnl_open_expr_container(m, "fib"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_FIB_FLAGS, htobe32(nft_fib_flags)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_FIB_RESULT, htobe32(result)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_FIB_DREG, htobe32(dreg)); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_meta( - sd_netlink_message *m, - enum nft_meta_keys key, - enum nft_registers dreg) { - - int r; - - assert(m); - - r = nfnl_open_expr_container(m, "meta"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_META_KEY, htobe32(key)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_META_DREG, htobe32(dreg)); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_payload( - sd_netlink_message *m, - enum nft_payload_bases pb, - uint32_t offset, - uint32_t len, - enum nft_registers dreg) { - - int r; - - assert(m); - - r = nfnl_open_expr_container(m, "payload"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_DREG, htobe32(dreg)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_BASE, htobe32(pb)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_OFFSET, htobe32(offset)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_LEN, htobe32(len)); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_lookup( - sd_netlink_message *m, - const char *set_name, - enum nft_registers sreg, - enum nft_registers dreg) { - - int r; - - assert(m); - assert(set_name); - - r = nfnl_open_expr_container(m, "lookup"); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, NFTA_LOOKUP_SET, set_name); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_SREG, htobe32(sreg)); - if (r < 0) - return r; - - if (dreg != 0) { - r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_DREG, htobe32(dreg)); - if (r < 0) - return r; - } - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_cmp( - sd_netlink_message *m, - enum nft_cmp_ops cmp_op, - enum nft_registers sreg, - const void *data, - size_t dlen) { - - int r; - - assert(m); - assert(data); - - r = nfnl_open_expr_container(m, "cmp"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_CMP_OP, htobe32(cmp_op)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_CMP_SREG, htobe32(sreg)); - if (r < 0) - return r; - - r = sd_netlink_message_append_container_data(m, NFTA_CMP_DATA, NFTA_DATA_VALUE, data, dlen); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_bitwise( - sd_netlink_message *m, - enum nft_registers sreg, - enum nft_registers dreg, - const void *and, - const void *xor, - uint32_t len) { - - int r; - - assert(m); - assert(and); - assert(xor); - - r = nfnl_open_expr_container(m, "bitwise"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_BITWISE_SREG, htobe32(sreg)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_BITWISE_DREG, htobe32(dreg)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_BITWISE_LEN, htobe32(len)); - if (r < 0) - return r; - - r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_MASK, NFTA_DATA_VALUE, and, len); - if (r < 0) - return r; - - r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_XOR, NFTA_DATA_VALUE, xor, len); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_dnat( - sd_netlink_message *m, - int family, - enum nft_registers areg, - enum nft_registers preg) { - - int r; - - assert(m); - - r = nfnl_open_expr_container(m, "nat"); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_NAT_TYPE, htobe32(NFT_NAT_DNAT)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_NAT_FAMILY, htobe32(family)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_ADDR_MIN, htobe32(areg)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_PROTO_MIN, htobe32(preg)); - if (r < 0) - return r; - - return nfnl_close_expr_container(m); -} - -static int nfnl_add_expr_masq(sd_netlink_message *m) { - int r; - - r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, NFTA_EXPR_NAME, "masq"); - if (r < 0) - return r; - - return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */ -} - -static int sd_nfnl_message_new_masq_rule( - sd_netlink *nfnl, - sd_netlink_message **ret, - int family, - const char *chain) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - /* -t nat -A POSTROUTING -p protocol -s source/pflen -o out_interface -d destination/pflen -j MASQUERADE */ - - assert(nfnl); - assert(ret); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(chain); - - r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); - if (r < 0) - return r; - - /* 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; - - /* 1st statement: use reg1 content to make lookup in @masq_saddr set. */ - r = nfnl_add_expr_lookup(m, NFT_SYSTEMD_MASQ_SET_NAME, NFT_REG32_01, 0); - if (r < 0) - return r; - - /* 2nd statement: masq. Only executed by kernel if the previous lookup was successful. */ - r = nfnl_add_expr_masq(m); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -static int sd_nfnl_message_new_dnat_rule_pre( - sd_netlink *nfnl, - sd_netlink_message **ret, - int family, - const char *chain) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - enum nft_registers proto_reg; - uint32_t local = RTN_LOCAL; - int r; - - /* -t nat -A PREROUTING -p protocol --dport local_port -i in_interface -s source/pflen - * -d destination/pflen -j DNAT --to-destination remote_addr:remote_port */ - - assert(nfnl); - assert(ret); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(chain); - - r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); - if (r < 0) - return r; - - /* 1st statement: fib daddr type local */ - r = nfnl_add_expr_fib(m, NFTA_FIB_F_DADDR, NFT_FIB_RESULT_ADDRTYPE, NFT_REG32_01); - if (r < 0) - return r; - - /* 1st statement (cont.): compare RTN_LOCAL */ - r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &local, sizeof(local)); - if (r < 0) - return r; - - /* 2nd statement: lookup local port in map, fetch address:dport to map to */ - r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01); - if (r < 0) - return r; - - r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET, - sizeof(uint16_t), NFT_REG32_02); - if (r < 0) - return r; - - /* 3rd statement: lookup 'l4proto . dport', e.g. 'tcp . 22' as key and - * store address and port for the dnat mapping in REG1/REG2. */ - r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01); - if (r < 0) - return r; - - 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; - - r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -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 = 0, one = 1; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - enum nft_registers proto_reg; - int r; - - assert(nfnl); - assert(ret); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(chain); - - r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); - 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)); - - 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)); - } 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; - - /* 2nd statement: meta oif lo */ - r = nfnl_add_expr_meta(m, NFT_META_OIF, NFT_REG32_01); - if (r < 0) - return r; - - /* 2nd statement (cont.): compare to lo ifindex (1) */ - r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &one, sizeof(one)); - if (r < 0) - return r; - - /* 3rd statement: meta l4proto . th dport dnat ip . port to map @map_port_ipport */ - r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01); - if (r < 0) - return r; - - /* 3rd statement (cont): store the port number in reg2 */ - r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET, - sizeof(uint16_t), NFT_REG32_02); - if (r < 0) - return r; - - /* 3rd statement (cont): use reg1 and reg2 and retrieve - * the new destination ip and port number. - * - * reg1 and reg2 are clobbered and will then contain the new - * address/port number. */ - r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01); - if (r < 0) - return r; - - /* 4th statement: dnat connection to address/port retrieved by the - * preceding expression. */ - 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; - - r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -static int nft_new_set( - struct sd_netlink *nfnl, - sd_netlink_message **ret, - int family, - const char *set_name, - uint32_t set_id, - uint32_t flags, - uint32_t type, - uint32_t klen) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(nfnl); - assert(ret); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(set_name); - - r = sd_nfnl_nft_message_new_set(nfnl, &m, family, nft_table_name(), set_name, set_id, klen); - if (r < 0) - return r; - - if (flags != 0) { - r = sd_netlink_message_append_u32(m, NFTA_SET_FLAGS, htobe32(flags)); - if (r < 0) - return r; - } - - r = sd_netlink_message_append_u32(m, NFTA_SET_KEY_TYPE, htobe32(type)); - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return r; -} - -static int nft_new_map( - struct sd_netlink *nfnl, - sd_netlink_message **ret, - int family, - const char *set_name, - uint32_t set_id, - uint32_t flags, - uint32_t type, - uint32_t klen, - uint32_t dtype, - uint32_t dlen) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(nfnl); - assert(ret); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(set_name); - - r = nft_new_set(nfnl, &m, family, set_name, set_id, flags | NFT_SET_MAP, type, klen); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_TYPE, htobe32(dtype)); - if (r < 0) - return r; - - r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_LEN, htobe32(dlen)); - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -static int nft_add_element( - sd_netlink *nfnl, - sd_netlink_message **ret, - int nfproto, - const char *table_name, - const char *set_name, - const void *key, - uint32_t klen, - const void *data, - uint32_t dlen) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(nfnl); - assert(ret); - assert(nfproto_is_valid(nfproto)); - assert(table_name); - assert(set_name); - assert(key); - assert(data || dlen == 0); - - /* - * Ideally there would be an API that provides: - * - * 1) an init function to add the main ruleset skeleton - * 2) a function that populates the sets with all known address/port pairs to s/dnat for - * 3) a function that can remove address/port pairs again. - * - * At this time, the existing API is used which is built on a - * 'add/delete a rule' paradigm. - * - * This replicated here and each element gets added to the set - * one-by-one. - */ - r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, nfproto, table_name, set_name); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0); - if (r < 0) - return r; - - /* could theoretically append more set elements to add here */ - - r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -static int nft_del_element( - sd_netlink *nfnl, - sd_netlink_message **ret, - int nfproto, - const char *table_name, - const char *set_name, - const void *key, - uint32_t klen, - const void *data, - uint32_t dlen) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(nfnl); - assert(ret); - assert(nfproto_is_valid(nfproto)); - assert(table_name); - assert(set_name); - assert(key); - assert(data || dlen == 0); - - r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, nfproto, table_name, set_name); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ - if (r < 0) - return r; - - *ret = TAKE_PTR(m); - return 0; -} - -/* This is needed so 'nft' userspace tool can properly format the contents - * of the set/map when someone uses 'nft' to inspect their content. - * - * The values cannot be changed, they are part of the nft tool type identifier ABI. */ -#define TYPE_BITS 6 - -enum nft_key_types { - TYPE_IPADDR = 7, - TYPE_IP6ADDR = 8, - TYPE_INET_PROTOCOL = 12, - TYPE_INET_SERVICE = 13, -}; - -static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) { - uint32_t type = (uint32_t)a; - - type <<= TYPE_BITS; - type |= (uint32_t)b; - - return type; -} - -static int fw_nftables_init_family(sd_netlink *nfnl, int family) { - sd_netlink_message *messages[10] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; - size_t msgcnt = 0, ip_type_size; - uint32_t set_id = 0; - int ip_type, r; - - assert(nfnl); - assert(IN_SET(family, AF_INET, AF_INET6)); - - /* Set F_EXCL so table add fails if the table already exists. */ - r = sd_nfnl_nft_message_new_table(nfnl, &messages[msgcnt++], family, nft_table_name()); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), - "prerouting", "nat", - NF_INET_PRE_ROUTING, NF_IP_PRI_NAT_DST + 1); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), - "output", "nat", - NF_INET_LOCAL_OUT, NF_IP_PRI_NAT_DST + 1); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), - "postrouting", "nat", - NF_INET_POST_ROUTING, NF_IP_PRI_NAT_SRC + 1); - if (r < 0) - return r; - - 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; - } - /* set to store ip address ranges we should masquerade for */ - r = nft_new_set(nfnl, &messages[msgcnt++], family, NFT_SYSTEMD_MASQ_SET_NAME, ++set_id, NFT_SET_INTERVAL, ip_type, ip_type_size); - if (r < 0) - return r; - - /* - * map to store ip address:port pair to dnat to. elements in concatenation - * are rounded up to 4 bytes. - * - * Example: ip protocol . tcp daddr is sizeof(uint32_t) + sizeof(uint32_t), not - * sizeof(uint8_t) + sizeof(uint16_t). - */ - r = nft_new_map(nfnl, &messages[msgcnt++], family, dnat_map_name(), ++set_id, 0, - concat_types2(TYPE_INET_PROTOCOL, TYPE_INET_SERVICE), sizeof(uint32_t) * 2, - concat_types2(ip_type, TYPE_INET_SERVICE), ip_type_size + sizeof(uint32_t)); - if (r < 0) - return r; - - r = sd_nfnl_message_new_dnat_rule_pre(nfnl, &messages[msgcnt++], family, "prerouting"); - if (r < 0) - return r; - - r = sd_nfnl_message_new_dnat_rule_out(nfnl, &messages[msgcnt++], family, "output"); - if (r < 0) - return r; - - r = sd_nfnl_message_new_masq_rule(nfnl, &messages[msgcnt++], family, "postrouting"); - if (r < 0) - return r; - - assert(msgcnt < ELEMENTSOF(messages)); - r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS, NULL); - if (r < 0 && r != -EEXIST) - return r; - - return 0; -} - -int fw_nftables_init_full(FirewallContext *ctx, bool init_tables) { - _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL; - int r; - - assert(ctx); - assert(!ctx->nfnl); - - r = sd_nfnl_socket_open(&nfnl); - if (r < 0) - return r; - - if (init_tables) { - r = fw_nftables_init_family(nfnl, AF_INET); - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = fw_nftables_init_family(nfnl, AF_INET6); - if (r < 0) - return log_error_errno(r, "Failed to init ipv6 NAT: %m"); - } - } - - ctx->nfnl = TAKE_PTR(nfnl); - return 0; -} - -int fw_nftables_init(FirewallContext *ctx) { - return fw_nftables_init_full(ctx, /* init_tables= */ true); -} - -void fw_nftables_exit(FirewallContext *ctx) { - assert(ctx); - - ctx->nfnl = sd_netlink_unref(ctx->nfnl); -} - -static int nft_message_append_setelem_iprange( - sd_netlink_message *m, - const union in_addr_union *source, - unsigned prefixlen) { - - uint32_t mask, start, end; - unsigned nplen; - int r; - - assert(m); - assert(source); - assert(prefixlen <= 32); - - nplen = 32 - prefixlen; - - mask = (1U << nplen) - 1U; - mask = htobe32(~mask); - start = source->in.s_addr & mask; - - r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 0, &start, sizeof(start), NULL, 0, 0); - if (r < 0) - return r; - - end = be32toh(start) + (1U << nplen); - if (end < be32toh(start)) - end = 0U; - end = htobe32(end); - - r = sd_nfnl_nft_message_append_setelem(m, 1, &end, sizeof(end), NULL, 0, NFT_SET_ELEM_INTERVAL_END); - if (r < 0) - return r; - - return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ -} - -static int nft_message_append_setelem_ip6range( - sd_netlink_message *m, - const union in_addr_union *source, - unsigned prefixlen) { - - union in_addr_union start, end; - int r; - - assert(m); - assert(source); - - r = in_addr_prefix_range(AF_INET6, source, prefixlen, &start, &end); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 0, &start.in6, sizeof(start.in6), NULL, 0, 0); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 1, &end.in6, sizeof(end.in6), NULL, 0, NFT_SET_ELEM_INTERVAL_END); - if (r < 0) - return r; - - return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ -} - -int nft_set_element_modify_iprange( - FirewallContext *ctx, - bool add, - int nfproto, - int af, - const char *table, - const char *set, - const union in_addr_union *source, - unsigned source_prefixlen) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(ctx->nfnl); - assert(IN_SET(af, AF_INET, AF_INET6)); - assert(nfproto_is_valid(nfproto)); - assert(table); - assert(set); - - if (!source || source_prefixlen == 0) - return -EINVAL; - - if (af == AF_INET6 && source_prefixlen < 8) - return -EINVAL; - - r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set); - if (r < 0) - return r; - - if (af == AF_INET) - r = nft_message_append_setelem_iprange(m, source, source_prefixlen); - else - r = nft_message_append_setelem_ip6range(m, source, source_prefixlen); - if (r < 0) - return r; - - return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); -} - -int nft_set_element_modify_ip( - FirewallContext *ctx, - bool add, - int nfproto, - int af, - const char *table, - const char *set, - const union in_addr_union *source) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(ctx->nfnl); - assert(IN_SET(af, AF_INET, AF_INET6)); - assert(nfproto_is_valid(nfproto)); - assert(table); - assert(set); - - if (!source) - return -EINVAL; - - r = sd_nfnl_nft_message_new_setelems(ctx->nfnl, &m, add, nfproto, table, set); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); - if (r < 0) - return r; - - r = sd_nfnl_nft_message_append_setelem(m, 0, source, FAMILY_ADDRESS_SIZE(af), NULL, 0, 0); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ - if (r < 0) - return r; - - return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); -} - -int nft_set_element_modify_any(FirewallContext *ctx, bool add, int nfproto, const char *table, const char *set, const void *element, size_t element_size) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(ctx); - assert(ctx->nfnl); - assert(nfproto_is_valid(nfproto)); - assert(table); - assert(set); - assert(element); - - if (add) - r = nft_add_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0); - else - r = nft_del_element(ctx->nfnl, &m, nfproto, table, set, element, element_size, NULL, 0); - if (r < 0) - return r; - - return sd_nfnl_call_batch(ctx->nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); -} - -static int af_to_nfproto(int af) { - assert(IN_SET(af, AF_INET, AF_INET6)); - - switch (af) { - case AF_INET: - return NFPROTO_IPV4; - case AF_INET6: - return NFPROTO_IPV6; - default: - assert_not_reached(); - } -} - -int fw_nftables_add_masquerade( - FirewallContext *ctx, - bool add, - int af, - const union in_addr_union *source, - unsigned source_prefixlen) { - - int r; - - assert(ctx); - assert(ctx->nfnl); - assert(IN_SET(af, AF_INET, AF_INET6)); - - if (!socket_ipv6_is_supported() && af == AF_INET6) - return -EOPNOTSUPP; - - r = nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME, - 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 nft_set_element_modify_iprange(ctx, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME, - source, source_prefixlen); -} - -static int fw_nftables_add_local_dnat_internal( - sd_netlink *nfnl, - 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) { - - sd_netlink_message *messages[3] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; - static bool ipv6_supported = true; - uint32_t data[5], key[2], dlen; - size_t msgcnt = 0; - int r; - - assert(nfnl); - assert(add || !previous_remote); - assert(IN_SET(af, AF_INET, AF_INET6)); - - if (!ipv6_supported && af == AF_INET6) - return -EOPNOTSUPP; - - if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) - return -EPROTONOSUPPORT; - - if (local_port <= 0) - return -EINVAL; - - key[0] = protocol; - key[1] = htobe16(local_port); - - if (!remote) - return -EOPNOTSUPP; - - if (remote_port <= 0) - return -EINVAL; - - if (af == AF_INET) { - dlen = 8; - data[1] = htobe16(remote_port); - } else { - assert(af == AF_INET6); - dlen = sizeof(data); - data[4] = htobe16(remote_port); - } - - /* If a previous remote is set, remove its entry */ - 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(nfnl, &messages[msgcnt++], af, nft_table_name(), dnat_map_name(), - key, sizeof(key), data, dlen); - if (r < 0) - return r; - } - - if (af == AF_INET) - data[0] = remote->in.s_addr; - else - memcpy(data, &remote->in6, sizeof(remote->in6)); - - if (add) - r = nft_add_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(), - key, sizeof(key), data, dlen); - else - r = nft_del_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(), - key, sizeof(key), data, dlen); - if (r < 0) - return r; - - assert(msgcnt < ELEMENTSOF(messages)); - r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS, NULL); - 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; - return -EOPNOTSUPP; - } - if (r < 0) - return r; - - return 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; - - assert(ctx); - assert(ctx->nfnl); - assert(IN_SET(af, AF_INET, AF_INET6)); - - if (!socket_ipv6_is_supported() && af == AF_INET6) - return -EOPNOTSUPP; - - r = fw_nftables_add_local_dnat_internal(ctx->nfnl, 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->nfnl, add, af, protocol, local_port, remote, remote_port, NULL); -} - -static const char *const nfproto_table[] = { - [NFPROTO_ARP] = "arp", - [NFPROTO_BRIDGE] = "bridge", - [NFPROTO_INET] = "inet", - [NFPROTO_IPV4] = "ip", - [NFPROTO_IPV6] = "ip6", - [NFPROTO_NETDEV] = "netdev", -}; - -DEFINE_STRING_TABLE_LOOKUP(nfproto, int); - -static const char *const nft_set_source_table[] = { - [NFT_SET_SOURCE_ADDRESS] = "address", - [NFT_SET_SOURCE_PREFIX] = "prefix", - [NFT_SET_SOURCE_IFINDEX] = "ifindex", - [NFT_SET_SOURCE_CGROUP] = "cgroup", - [NFT_SET_SOURCE_USER] = "user", - [NFT_SET_SOURCE_GROUP] = "group", -}; - -DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int); - -void nft_set_context_clear(NFTSetContext *s) { - assert(s); - - FOREACH_ARRAY(nft_set, s->sets, s->n_sets) { - free(nft_set->table); - free(nft_set->set); - } - - s->n_sets = 0; - s->sets = mfree(s->sets); -} - -int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) { - _cleanup_free_ char *table_dup = NULL, *set_dup = NULL; - - assert(s); - assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP)); - assert(nfproto_is_valid(nfproto)); - assert(table); - assert(set); - - table_dup = strdup(table); - if (!table_dup) - return -ENOMEM; - - set_dup = strdup(set); - if (!set_dup) - return -ENOMEM; - - if (!GREEDY_REALLOC(s->sets, s->n_sets + 1)) - return -ENOMEM; - - s->sets[s->n_sets++] = (NFTSet) { - .source = source, - .nfproto = nfproto, - .table = TAKE_PTR(table_dup), - .set = TAKE_PTR(set_dup), - }; - - return 0; -} - -int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst) { - int r; - _cleanup_(nft_set_context_clear) NFTSetContext d = (NFTSetContext) {}; - - assert(src); - assert(dst); - - FOREACH_ARRAY(nft_set, src->sets, src->n_sets) { - r = nft_set_add(&d, nft_set->source, nft_set->nfproto, nft_set->table, nft_set->set); - if (r < 0) - return r; - } - - *dst = TAKE_STRUCT(d); - - return 0; -} - -int config_parse_nft_set( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - NFTSetContext *nft_set_context = ASSERT_PTR(data); - int r; - - assert(IN_SET(ltype, NFT_SET_PARSE_NETWORK, NFT_SET_PARSE_CGROUP)); - - if (isempty(rvalue)) { - nft_set_context_clear(nft_set_context); - return 1; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *tuple = NULL, *source_str = NULL, *family_str = NULL, *table = NULL, *set = NULL; - const char *q = NULL; - int nfproto; - NFTSetSource source; - - r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); - if (r < 0) - return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); - if (r == 0) - return 1; - - q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set); - if (r == -ENOMEM) - return log_oom(); - if (r != 4 || !isempty(q)) { - _cleanup_free_ char *esc = NULL; - - esc = cescape(tuple); - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse NFT set %s, ignoring", strna(esc)); - } - - assert(source_str); - assert(family_str); - assert(table); - assert(set); - - source = nft_set_source_from_string(source_str); - if (source < 0 || - (ltype == NFT_SET_PARSE_NETWORK && !IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX)) || - (ltype == NFT_SET_PARSE_CGROUP && !IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))) { - _cleanup_free_ char *esc = NULL; - - esc = cescape(source_str); - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT source %s, ignoring", strna(esc)); - } - - nfproto = nfproto_from_string(family_str); - if (nfproto < 0) { - _cleanup_free_ char *esc = NULL; - - esc = cescape(family_str); - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family %s, ignoring", strna(esc)); - } - - if (!nft_identifier_valid(table)) { - _cleanup_free_ char *esc = NULL; - - esc = cescape(table); - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", strna(esc)); - } - - if (!nft_identifier_valid(set)) { - _cleanup_free_ char *esc = NULL; - - esc = cescape(set); - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", strna(esc)); - } - - r = nft_set_add(nft_set_context, source, nfproto, table, set); - if (r < 0) - return log_oom(); - } - - assert_not_reached(); -} diff --git a/src/shared/firewall-util-private.h b/src/shared/firewall-util-private.h deleted file mode 100644 index 7e965494c8..0000000000 --- a/src/shared/firewall-util-private.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "firewall-util.h" -#include "forward.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 { - FirewallBackend backend; - sd_netlink *nfnl; -}; - -const char* firewall_backend_to_string(FirewallBackend b) _const_; - -int fw_nftables_init(FirewallContext *ctx); -int fw_nftables_init_full(FirewallContext *ctx, bool init_tables); -void fw_nftables_exit(FirewallContext *ctx); - -int fw_nftables_add_masquerade( - FirewallContext *ctx, - bool add, - int af, - const union in_addr_union *source, - unsigned source_prefixlen); - -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); - -#if HAVE_LIBIPTC -struct xtc_handle; - -int fw_iptables_add_masquerade( - bool add, - int af, - const union in_addr_union *source, - unsigned source_prefixlen); - -int fw_iptables_add_local_dnat( - 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 fw_iptables_init_nat(struct xtc_handle **ret); -#endif diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index c8dfad5d33..b59740166e 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -1,105 +1,1010 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include +#include +#include +#include #include #include +#include "sd-netlink.h" + #include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" #include "firewall-util.h" -#include "firewall-util-private.h" +#include "in-addr-util.h" #include "log.h" -#include "netlink-util.h" +#include "netlink-internal.h" +#include "parse-util.h" +#include "socket-util.h" #include "string-table.h" #include "string-util.h" +#include "time-util.h" -static const char * const firewall_backend_table[_FW_BACKEND_MAX] = { - [FW_BACKEND_NONE] = "none", -#if HAVE_LIBIPTC - [FW_BACKEND_IPTABLES] = "iptables", -#endif - [FW_BACKEND_NFTABLES] = "nftables", -}; +#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport" +#define NFT_SYSTEMD_TABLE_NAME "io.systemd.nat" +#define NFT_SYSTEMD_MASQ_SET_NAME "masq_saddr" -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(firewall_backend, FirewallBackend); +#define NFNL_DEFAULT_TIMEOUT_USECS (1ULL * USEC_PER_SEC) -static void firewall_backend_probe(FirewallContext *ctx, bool init_tables) { - const char *e; +#define UDP_DPORT_OFFSET 2 - assert(ctx); +/* for test-firewall-util */ +static const char* nft_table_name(void) { + static const char *cached = NULL; - if (ctx->backend != _FW_BACKEND_INVALID) - return; + if (!cached) + cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_NFT_TABLE_NAME") ?: NFT_SYSTEMD_TABLE_NAME; - e = secure_getenv("SYSTEMD_FIREWALL_BACKEND"); - if (e) { - if (streq(e, "nftables")) - ctx->backend = FW_BACKEND_NFTABLES; - else if (streq(e, "iptables")) -#if HAVE_LIBIPTC - ctx->backend = FW_BACKEND_IPTABLES; -#else - log_debug("Unsupported firewall backend requested, ignoring: %s", e); -#endif - else - log_debug("Unrecognized $SYSTEMD_FIREWALL_BACKEND value, ignoring: %s", e); - } - - if (ctx->backend == _FW_BACKEND_INVALID) { - - if (fw_nftables_init_full(ctx, init_tables) >= 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."); + return cached; } -int fw_ctx_new_full(FirewallContext **ret, bool init_tables) { - _cleanup_free_ FirewallContext *ctx = NULL; +static const char* dnat_map_name(void) { + static const char *cached = NULL; - ctx = new(FirewallContext, 1); - if (!ctx) - return -ENOMEM; + if (!cached) + cached = secure_getenv("SYSTEMD_FIREWALL_UTIL_DNAT_MAP_NAME") ?: NFT_SYSTEMD_DNAT_MAP_NAME; - *ctx = (FirewallContext) { - .backend = _FW_BACKEND_INVALID, - }; + return cached; +} - firewall_backend_probe(ctx, init_tables); +static sd_netlink_message** netlink_message_unref_many(sd_netlink_message **m) { + if (!m) + return NULL; - *ret = TAKE_PTR(ctx); + /* This does not free array. The end of the array must be NULL. */ + + for (sd_netlink_message **p = m; *p; p++) + *p = sd_netlink_message_unref(*p); + + return m; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_netlink_message**, netlink_message_unref_many); + +static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { + int r; + + assert(m); + assert(name); + + r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM); + if (r < 0) + return r; + + return sd_netlink_message_open_container_union(m, NFTA_EXPR_DATA, name); +} + +static int nfnl_close_expr_container(sd_netlink_message *m) { + int r; + + assert(m); + + r = sd_netlink_message_close_container(m); /* NFTA_EXPR_DATA */ + if (r < 0) + return r; + + return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */ +} + +static int nfnl_add_expr_fib( + sd_netlink_message *m, + uint32_t nft_fib_flags, + enum nft_fib_result result, + enum nft_registers dreg) { + + int r; + + assert(m); + + r = nfnl_open_expr_container(m, "fib"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_FIB_FLAGS, htobe32(nft_fib_flags)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_FIB_RESULT, htobe32(result)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_FIB_DREG, htobe32(dreg)); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_meta( + sd_netlink_message *m, + enum nft_meta_keys key, + enum nft_registers dreg) { + + int r; + + assert(m); + + r = nfnl_open_expr_container(m, "meta"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_META_KEY, htobe32(key)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_META_DREG, htobe32(dreg)); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_payload( + sd_netlink_message *m, + enum nft_payload_bases pb, + uint32_t offset, + uint32_t len, + enum nft_registers dreg) { + + int r; + + assert(m); + + r = nfnl_open_expr_container(m, "payload"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_DREG, htobe32(dreg)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_BASE, htobe32(pb)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_OFFSET, htobe32(offset)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_PAYLOAD_LEN, htobe32(len)); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_lookup( + sd_netlink_message *m, + const char *set_name, + enum nft_registers sreg, + enum nft_registers dreg) { + + int r; + + assert(m); + assert(set_name); + + r = nfnl_open_expr_container(m, "lookup"); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_LOOKUP_SET, set_name); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_SREG, htobe32(sreg)); + if (r < 0) + return r; + + if (dreg != 0) { + r = sd_netlink_message_append_u32(m, NFTA_LOOKUP_DREG, htobe32(dreg)); + if (r < 0) + return r; + } + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_cmp( + sd_netlink_message *m, + enum nft_cmp_ops cmp_op, + enum nft_registers sreg, + const void *data, + size_t dlen) { + + int r; + + assert(m); + assert(data); + + r = nfnl_open_expr_container(m, "cmp"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_CMP_OP, htobe32(cmp_op)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_CMP_SREG, htobe32(sreg)); + if (r < 0) + return r; + + r = sd_netlink_message_append_container_data(m, NFTA_CMP_DATA, NFTA_DATA_VALUE, data, dlen); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_bitwise( + sd_netlink_message *m, + enum nft_registers sreg, + enum nft_registers dreg, + const void *and, + const void *xor, + uint32_t len) { + + int r; + + assert(m); + assert(and); + assert(xor); + + r = nfnl_open_expr_container(m, "bitwise"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_BITWISE_SREG, htobe32(sreg)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_BITWISE_DREG, htobe32(dreg)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_BITWISE_LEN, htobe32(len)); + if (r < 0) + return r; + + r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_MASK, NFTA_DATA_VALUE, and, len); + if (r < 0) + return r; + + r = sd_netlink_message_append_container_data(m, NFTA_BITWISE_XOR, NFTA_DATA_VALUE, xor, len); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_dnat( + sd_netlink_message *m, + int family, + enum nft_registers areg, + enum nft_registers preg) { + + int r; + + assert(m); + + r = nfnl_open_expr_container(m, "nat"); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_NAT_TYPE, htobe32(NFT_NAT_DNAT)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_NAT_FAMILY, htobe32(family)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_ADDR_MIN, htobe32(areg)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_NAT_REG_PROTO_MIN, htobe32(preg)); + if (r < 0) + return r; + + return nfnl_close_expr_container(m); +} + +static int nfnl_add_expr_masq(sd_netlink_message *m) { + int r; + + r = sd_netlink_message_open_array(m, NFTA_LIST_ELEM); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NFTA_EXPR_NAME, "masq"); + if (r < 0) + return r; + + return sd_netlink_message_close_container(m); /* NFTA_LIST_ELEM */ +} + +static int sd_nfnl_message_new_masq_rule( + sd_netlink *nfnl, + sd_netlink_message **ret, + int family, + const char *chain) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + /* -t nat -A POSTROUTING -p protocol -s source/pflen -o out_interface -d destination/pflen -j MASQUERADE */ + + assert(nfnl); + assert(ret); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(chain); + + r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); + if (r < 0) + return r; + + /* 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; + + /* 1st statement: use reg1 content to make lookup in @masq_saddr set. */ + r = nfnl_add_expr_lookup(m, NFT_SYSTEMD_MASQ_SET_NAME, NFT_REG32_01, 0); + if (r < 0) + return r; + + /* 2nd statement: masq. Only executed by kernel if the previous lookup was successful. */ + r = nfnl_add_expr_masq(m); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ + if (r < 0) + return r; + + *ret = TAKE_PTR(m); return 0; } -int fw_ctx_new(FirewallContext **ret) { - return fw_ctx_new_full(ret, /* init_tables= */ true); +static int sd_nfnl_message_new_dnat_rule_pre( + sd_netlink *nfnl, + sd_netlink_message **ret, + int family, + const char *chain) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + enum nft_registers proto_reg; + uint32_t local = RTN_LOCAL; + int r; + + /* -t nat -A PREROUTING -p protocol --dport local_port -i in_interface -s source/pflen + * -d destination/pflen -j DNAT --to-destination remote_addr:remote_port */ + + assert(nfnl); + assert(ret); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(chain); + + r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); + if (r < 0) + return r; + + /* 1st statement: fib daddr type local */ + r = nfnl_add_expr_fib(m, NFTA_FIB_F_DADDR, NFT_FIB_RESULT_ADDRTYPE, NFT_REG32_01); + if (r < 0) + return r; + + /* 1st statement (cont.): compare RTN_LOCAL */ + r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &local, sizeof(local)); + if (r < 0) + return r; + + /* 2nd statement: lookup local port in map, fetch address:dport to map to */ + r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01); + if (r < 0) + return r; + + r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET, + sizeof(uint16_t), NFT_REG32_02); + if (r < 0) + return r; + + /* 3rd statement: lookup 'l4proto . dport', e.g. 'tcp . 22' as key and + * store address and port for the dnat mapping in REG1/REG2. */ + r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01); + if (r < 0) + return r; + + 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; + + r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; } -FirewallContext *fw_ctx_free(FirewallContext *ctx) { - if (!ctx) - return NULL; +static int sd_nfnl_message_new_dnat_rule_out( + sd_netlink *nfnl, + sd_netlink_message **ret, + int family, + const char *chain) { - fw_nftables_exit(ctx); + static const uint32_t zero = 0, one = 1; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + enum nft_registers proto_reg; + int r; - return mfree(ctx); + assert(nfnl); + assert(ret); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(chain); + + r = sd_nfnl_nft_message_new_rule(nfnl, &m, family, nft_table_name(), chain); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_RULE_EXPRESSIONS); + 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)); + + 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)); + } 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; + + /* 2nd statement: meta oif lo */ + r = nfnl_add_expr_meta(m, NFT_META_OIF, NFT_REG32_01); + if (r < 0) + return r; + + /* 2nd statement (cont.): compare to lo ifindex (1) */ + r = nfnl_add_expr_cmp(m, NFT_CMP_EQ, NFT_REG32_01, &one, sizeof(one)); + if (r < 0) + return r; + + /* 3rd statement: meta l4proto . th dport dnat ip . port to map @map_port_ipport */ + r = nfnl_add_expr_meta(m, NFT_META_L4PROTO, NFT_REG32_01); + if (r < 0) + return r; + + /* 3rd statement (cont): store the port number in reg2 */ + r = nfnl_add_expr_payload(m, NFT_PAYLOAD_TRANSPORT_HEADER, UDP_DPORT_OFFSET, + sizeof(uint16_t), NFT_REG32_02); + if (r < 0) + return r; + + /* 3rd statement (cont): use reg1 and reg2 and retrieve + * the new destination ip and port number. + * + * reg1 and reg2 are clobbered and will then contain the new + * address/port number. */ + r = nfnl_add_expr_lookup(m, dnat_map_name(), NFT_REG32_01, NFT_REG32_01); + if (r < 0) + return r; + + /* 4th statement: dnat connection to address/port retrieved by the + * preceding expression. */ + 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; + + r = sd_netlink_message_close_container(m); /* NFTA_RULE_EXPRESSIONS */ + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; } -size_t fw_ctx_get_reply_callback_count(FirewallContext *ctx) { - if (!ctx || !ctx->nfnl) - return 0; +static int nft_new_set( + struct sd_netlink *nfnl, + sd_netlink_message **ret, + int family, + const char *set_name, + uint32_t set_id, + uint32_t flags, + uint32_t type, + uint32_t klen) { - return netlink_get_reply_callback_count(ctx->nfnl); + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(ret); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(set_name); + + r = sd_nfnl_nft_message_new_set(nfnl, &m, family, nft_table_name(), set_name, set_id, klen); + if (r < 0) + return r; + + if (flags != 0) { + r = sd_netlink_message_append_u32(m, NFTA_SET_FLAGS, htobe32(flags)); + if (r < 0) + return r; + } + + r = sd_netlink_message_append_u32(m, NFTA_SET_KEY_TYPE, htobe32(type)); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return r; } -int fw_add_masquerade( - FirewallContext **ctx, +static int nft_new_map( + struct sd_netlink *nfnl, + sd_netlink_message **ret, + int family, + const char *set_name, + uint32_t set_id, + uint32_t flags, + uint32_t type, + uint32_t klen, + uint32_t dtype, + uint32_t dlen) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(ret); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(set_name); + + r = nft_new_set(nfnl, &m, family, set_name, set_id, flags | NFT_SET_MAP, type, klen); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_TYPE, htobe32(dtype)); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, NFTA_SET_DATA_LEN, htobe32(dlen)); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +static int nft_add_element( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table_name, + const char *set_name, + const void *key, + uint32_t klen, + const void *data, + uint32_t dlen) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(ret); + assert(nfproto_is_valid(nfproto)); + assert(table_name); + assert(set_name); + assert(key); + assert(data || dlen == 0); + + /* + * Ideally there would be an API that provides: + * + * 1) an init function to add the main ruleset skeleton + * 2) a function that populates the sets with all known address/port pairs to s/dnat for + * 3) a function that can remove address/port pairs again. + * + * At this time, the existing API is used which is built on a + * 'add/delete a rule' paradigm. + * + * This replicated here and each element gets added to the set + * one-by-one. + */ + r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ true, nfproto, table_name, set_name); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0); + if (r < 0) + return r; + + /* could theoretically append more set elements to add here */ + + r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +static int nft_del_element( + sd_netlink *nfnl, + sd_netlink_message **ret, + int nfproto, + const char *table_name, + const char *set_name, + const void *key, + uint32_t klen, + const void *data, + uint32_t dlen) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(ret); + assert(nfproto_is_valid(nfproto)); + assert(table_name); + assert(set_name); + assert(key); + assert(data || dlen == 0); + + r = sd_nfnl_nft_message_new_setelems(nfnl, &m, /* add = */ false, nfproto, table_name, set_name); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 0, key, klen, data, dlen, 0); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +/* This is needed so 'nft' userspace tool can properly format the contents + * of the set/map when someone uses 'nft' to inspect their content. + * + * The values cannot be changed, they are part of the nft tool type identifier ABI. */ +#define TYPE_BITS 6 + +enum nft_key_types { + TYPE_IPADDR = 7, + TYPE_IP6ADDR = 8, + TYPE_INET_PROTOCOL = 12, + TYPE_INET_SERVICE = 13, +}; + +static uint32_t concat_types2(enum nft_key_types a, enum nft_key_types b) { + uint32_t type = (uint32_t)a; + + type <<= TYPE_BITS; + type |= (uint32_t)b; + + return type; +} + +static int fw_nftables_init_family(sd_netlink *nfnl, int family) { + sd_netlink_message *messages[10] = {}; + _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + size_t msgcnt = 0, ip_type_size; + uint32_t set_id = 0; + int ip_type, r; + + assert(nfnl); + assert(IN_SET(family, AF_INET, AF_INET6)); + + /* Set F_EXCL so table add fails if the table already exists. */ + r = sd_nfnl_nft_message_new_table(nfnl, &messages[msgcnt++], family, nft_table_name()); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), + "prerouting", "nat", + NF_INET_PRE_ROUTING, NF_IP_PRI_NAT_DST + 1); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), + "output", "nat", + NF_INET_LOCAL_OUT, NF_IP_PRI_NAT_DST + 1); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_new_basechain(nfnl, &messages[msgcnt++], family, nft_table_name(), + "postrouting", "nat", + NF_INET_POST_ROUTING, NF_IP_PRI_NAT_SRC + 1); + if (r < 0) + return r; + + 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; + } + /* set to store ip address ranges we should masquerade for */ + r = nft_new_set(nfnl, &messages[msgcnt++], family, NFT_SYSTEMD_MASQ_SET_NAME, ++set_id, NFT_SET_INTERVAL, ip_type, ip_type_size); + if (r < 0) + return r; + + /* + * map to store ip address:port pair to dnat to. elements in concatenation + * are rounded up to 4 bytes. + * + * Example: ip protocol . tcp daddr is sizeof(uint32_t) + sizeof(uint32_t), not + * sizeof(uint8_t) + sizeof(uint16_t). + */ + r = nft_new_map(nfnl, &messages[msgcnt++], family, dnat_map_name(), ++set_id, 0, + concat_types2(TYPE_INET_PROTOCOL, TYPE_INET_SERVICE), sizeof(uint32_t) * 2, + concat_types2(ip_type, TYPE_INET_SERVICE), ip_type_size + sizeof(uint32_t)); + if (r < 0) + return r; + + r = sd_nfnl_message_new_dnat_rule_pre(nfnl, &messages[msgcnt++], family, "prerouting"); + if (r < 0) + return r; + + r = sd_nfnl_message_new_dnat_rule_out(nfnl, &messages[msgcnt++], family, "output"); + if (r < 0) + return r; + + r = sd_nfnl_message_new_masq_rule(nfnl, &messages[msgcnt++], family, "postrouting"); + if (r < 0) + return r; + + assert(msgcnt < ELEMENTSOF(messages)); + r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS, NULL); + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +static int nft_message_append_setelem_iprange( + sd_netlink_message *m, + const union in_addr_union *source, + unsigned prefixlen) { + + uint32_t mask, start, end; + unsigned nplen; + int r; + + assert(m); + assert(source); + assert(prefixlen <= 32); + + nplen = 32 - prefixlen; + + mask = (1U << nplen) - 1U; + mask = htobe32(~mask); + start = source->in.s_addr & mask; + + r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 0, &start, sizeof(start), NULL, 0, 0); + if (r < 0) + return r; + + end = be32toh(start) + (1U << nplen); + if (end < be32toh(start)) + end = 0U; + end = htobe32(end); + + r = sd_nfnl_nft_message_append_setelem(m, 1, &end, sizeof(end), NULL, 0, NFT_SET_ELEM_INTERVAL_END); + if (r < 0) + return r; + + return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ +} + +static int nft_message_append_setelem_ip6range( + sd_netlink_message *m, + const union in_addr_union *source, + unsigned prefixlen) { + + union in_addr_union start, end; + int r; + + assert(m); + assert(source); + + r = in_addr_prefix_range(AF_INET6, source, prefixlen, &start, &end); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 0, &start.in6, sizeof(start.in6), NULL, 0, 0); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 1, &end.in6, sizeof(end.in6), NULL, 0, NFT_SET_ELEM_INTERVAL_END); + if (r < 0) + return r; + + return sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ +} + +int nft_set_element_modify_iprange( + sd_netlink *nfnl, + bool add, + int nfproto, + int af, + const char *table, + const char *set, + const union in_addr_union *source, + unsigned source_prefixlen) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(IN_SET(af, AF_INET, AF_INET6)); + assert(nfproto_is_valid(nfproto)); + assert(table); + assert(set); + + if (!source || source_prefixlen == 0) + return -EINVAL; + + if (af == AF_INET6 && source_prefixlen < 8) + return -EINVAL; + + r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, nfproto, table, set); + if (r < 0) + return r; + + if (af == AF_INET) + r = nft_message_append_setelem_iprange(m, source, source_prefixlen); + else + r = nft_message_append_setelem_ip6range(m, source, source_prefixlen); + if (r < 0) + return r; + + return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); +} + +int nft_set_element_modify_ip( + sd_netlink *nfnl, + bool add, + int nfproto, + int af, + const char *table, + const char *set, + const union in_addr_union *source) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(IN_SET(af, AF_INET, AF_INET6)); + assert(nfproto_is_valid(nfproto)); + assert(table); + assert(set); + + if (!source) + return -EINVAL; + + r = sd_nfnl_nft_message_new_setelems(nfnl, &m, add, nfproto, table, set); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, NFTA_SET_ELEM_LIST_ELEMENTS); + if (r < 0) + return r; + + r = sd_nfnl_nft_message_append_setelem(m, 0, source, FAMILY_ADDRESS_SIZE(af), NULL, 0, 0); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); /* NFTA_SET_ELEM_LIST_ELEMENTS */ + if (r < 0) + return r; + + return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); +} + +int nft_set_element_modify_any( + sd_netlink *nfnl, + bool add, + int nfproto, + const char *table, + const char *set, + const void *element, + size_t element_size) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(nfnl); + assert(nfproto_is_valid(nfproto)); + assert(table); + assert(set); + assert(element); + + if (add) + r = nft_add_element(nfnl, &m, nfproto, table, set, element, element_size, NULL, 0); + else + r = nft_del_element(nfnl, &m, nfproto, table, set, element, element_size, NULL, 0); + if (r < 0) + return r; + + return sd_nfnl_call_batch(nfnl, &m, 1, NFNL_DEFAULT_TIMEOUT_USECS, NULL); +} + +static int af_to_nfproto(int af) { + assert(IN_SET(af, AF_INET, AF_INET6)); + + switch (af) { + case AF_INET: + return NFPROTO_IPV4; + case AF_INET6: + return NFPROTO_IPV6; + default: + assert_not_reached(); + } +} + +int fw_nftables_add_masquerade( + sd_netlink *nfnl, bool add, int af, const union in_addr_union *source, @@ -107,28 +1012,135 @@ int fw_add_masquerade( int r; - assert(ctx); + assert(nfnl); + assert(IN_SET(af, AF_INET, AF_INET6)); - if (!*ctx) { - r = fw_ctx_new(ctx); + if (!socket_ipv6_is_supported() && af == AF_INET6) + return -EOPNOTSUPP; + + r = nft_set_element_modify_iprange(nfnl, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME, + 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(nfnl, af); + if (r < 0) + return r; + + return nft_set_element_modify_iprange(nfnl, add, af_to_nfproto(af), af, nft_table_name(), NFT_SYSTEMD_MASQ_SET_NAME, + source, source_prefixlen); +} + +static int fw_nftables_add_local_dnat_internal( + sd_netlink *nfnl, + 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) { + + sd_netlink_message *messages[3] = {}; + _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + static bool ipv6_supported = true; + uint32_t data[5], key[2], dlen; + size_t msgcnt = 0; + int r; + + assert(nfnl); + assert(add || !previous_remote); + assert(IN_SET(af, AF_INET, AF_INET6)); + + if (!ipv6_supported && af == AF_INET6) + return -EOPNOTSUPP; + + if (!IN_SET(protocol, IPPROTO_TCP, IPPROTO_UDP)) + return -EPROTONOSUPPORT; + + if (local_port <= 0) + return -EINVAL; + + key[0] = protocol; + key[1] = htobe16(local_port); + + if (!remote) + return -EOPNOTSUPP; + + if (remote_port <= 0) + return -EINVAL; + + if (af == AF_INET) { + dlen = 8; + data[1] = htobe16(remote_port); + } else { + assert(af == AF_INET6); + dlen = sizeof(data); + data[4] = htobe16(remote_port); + } + + /* If a previous remote is set, remove its entry */ + 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(nfnl, &messages[msgcnt++], af, nft_table_name(), dnat_map_name(), + key, sizeof(key), data, dlen); if (r < 0) return r; } - 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); - default: + if (af == AF_INET) + data[0] = remote->in.s_addr; + else + memcpy(data, &remote->in6, sizeof(remote->in6)); + + if (add) + r = nft_add_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(), + key, sizeof(key), data, dlen); + else + r = nft_del_element(nfnl, &messages[msgcnt++], af_to_nfproto(af), nft_table_name(), dnat_map_name(), + key, sizeof(key), data, dlen); + if (r < 0) + return r; + + assert(msgcnt < ELEMENTSOF(messages)); + r = sd_nfnl_call_batch(nfnl, messages, msgcnt, NFNL_DEFAULT_TIMEOUT_USECS, NULL); + 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; return -EOPNOTSUPP; } + if (r < 0) + return r; + + return 0; } -int fw_add_local_dnat( - FirewallContext **ctx, +int fw_nftables_add_local_dnat( + sd_netlink *nfnl, bool add, int af, int protocol, @@ -139,22 +1151,193 @@ int fw_add_local_dnat( int r; - assert(ctx); + assert(nfnl); + assert(IN_SET(af, AF_INET, AF_INET6)); - if (!*ctx) { - r = fw_ctx_new(ctx); + if (!socket_ipv6_is_supported() && af == AF_INET6) + return -EOPNOTSUPP; + + r = fw_nftables_add_local_dnat_internal(nfnl, 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(nfnl, af); + if (r < 0) + return r; + + /* table created anew; previous address already gone */ + return fw_nftables_add_local_dnat_internal(nfnl, add, af, protocol, local_port, remote, remote_port, NULL); +} + +static const char *const nfproto_table[] = { + [NFPROTO_ARP] = "arp", + [NFPROTO_BRIDGE] = "bridge", + [NFPROTO_INET] = "inet", + [NFPROTO_IPV4] = "ip", + [NFPROTO_IPV6] = "ip6", + [NFPROTO_NETDEV] = "netdev", +}; + +DEFINE_STRING_TABLE_LOOKUP(nfproto, int); + +static const char *const nft_set_source_table[] = { + [NFT_SET_SOURCE_ADDRESS] = "address", + [NFT_SET_SOURCE_PREFIX] = "prefix", + [NFT_SET_SOURCE_IFINDEX] = "ifindex", + [NFT_SET_SOURCE_CGROUP] = "cgroup", + [NFT_SET_SOURCE_USER] = "user", + [NFT_SET_SOURCE_GROUP] = "group", +}; + +DEFINE_STRING_TABLE_LOOKUP(nft_set_source, int); + +void nft_set_context_clear(NFTSetContext *s) { + assert(s); + + FOREACH_ARRAY(nft_set, s->sets, s->n_sets) { + free(nft_set->table); + free(nft_set->set); + } + + s->n_sets = 0; + s->sets = mfree(s->sets); +} + +int nft_set_add(NFTSetContext *s, NFTSetSource source, int nfproto, const char *table, const char *set) { + _cleanup_free_ char *table_dup = NULL, *set_dup = NULL; + + assert(s); + assert(IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP)); + assert(nfproto_is_valid(nfproto)); + assert(table); + assert(set); + + table_dup = strdup(table); + if (!table_dup) + return -ENOMEM; + + set_dup = strdup(set); + if (!set_dup) + return -ENOMEM; + + if (!GREEDY_REALLOC(s->sets, s->n_sets + 1)) + return -ENOMEM; + + s->sets[s->n_sets++] = (NFTSet) { + .source = source, + .nfproto = nfproto, + .table = TAKE_PTR(table_dup), + .set = TAKE_PTR(set_dup), + }; + + return 0; +} + +int nft_set_context_dup(const NFTSetContext *src, NFTSetContext *dst) { + int r; + _cleanup_(nft_set_context_clear) NFTSetContext d = (NFTSetContext) {}; + + assert(src); + assert(dst); + + FOREACH_ARRAY(nft_set, src->sets, src->n_sets) { + r = nft_set_add(&d, nft_set->source, nft_set->nfproto, nft_set->table, nft_set->set); if (r < 0) return r; } - 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; + *dst = TAKE_STRUCT(d); + + return 0; +} + +int config_parse_nft_set( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + NFTSetContext *nft_set_context = ASSERT_PTR(data); + int r; + + assert(IN_SET(ltype, NFT_SET_PARSE_NETWORK, NFT_SET_PARSE_CGROUP)); + + if (isempty(rvalue)) { + nft_set_context_clear(nft_set_context); + return 1; } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *tuple = NULL, *source_str = NULL, *family_str = NULL, *table = NULL, *set = NULL; + const char *q = NULL; + int nfproto; + NFTSetSource source; + + r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) + return 1; + + q = tuple; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set); + if (r == -ENOMEM) + return log_oom(); + if (r != 4 || !isempty(q)) { + _cleanup_free_ char *esc = NULL; + + esc = cescape(tuple); + return log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse NFT set %s, ignoring", strna(esc)); + } + + assert(source_str); + assert(family_str); + assert(table); + assert(set); + + source = nft_set_source_from_string(source_str); + if (source < 0 || + (ltype == NFT_SET_PARSE_NETWORK && !IN_SET(source, NFT_SET_SOURCE_ADDRESS, NFT_SET_SOURCE_PREFIX, NFT_SET_SOURCE_IFINDEX)) || + (ltype == NFT_SET_PARSE_CGROUP && !IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP))) { + _cleanup_free_ char *esc = NULL; + + esc = cescape(source_str); + return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT source %s, ignoring", strna(esc)); + } + + nfproto = nfproto_from_string(family_str); + if (nfproto < 0) { + _cleanup_free_ char *esc = NULL; + + esc = cescape(family_str); + return log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family %s, ignoring", strna(esc)); + } + + if (!nft_identifier_valid(table)) { + _cleanup_free_ char *esc = NULL; + + esc = cescape(table); + return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", strna(esc)); + } + + if (!nft_identifier_valid(set)) { + _cleanup_free_ char *esc = NULL; + + esc = cescape(set); + return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", strna(esc)); + } + + r = nft_set_add(nft_set_context, source, nfproto, table, set); + if (r < 0) + return log_oom(); + } + + assert_not_reached(); } diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index d4acac55ee..595da38ef6 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -4,25 +4,15 @@ #include "conf-parser-forward.h" #include "forward.h" -typedef struct FirewallContext FirewallContext; - -int fw_ctx_new(FirewallContext **ret); -int fw_ctx_new_full(FirewallContext **ret, bool init_tables); -FirewallContext *fw_ctx_free(FirewallContext *ctx); - -DEFINE_TRIVIAL_CLEANUP_FUNC(FirewallContext *, fw_ctx_free); - -size_t fw_ctx_get_reply_callback_count(FirewallContext *ctx); - -int fw_add_masquerade( - FirewallContext **ctx, +int fw_nftables_add_masquerade( + sd_netlink *nfnl, bool add, int af, const union in_addr_union *source, unsigned source_prefixlen); -int fw_add_local_dnat( - FirewallContext **ctx, +int fw_nftables_add_local_dnat( + sd_netlink *nfnl, bool add, int af, int protocol, @@ -64,7 +54,7 @@ const char* nft_set_source_to_string(int i) _const_; int nft_set_source_from_string(const char *s) _pure_; int nft_set_element_modify_iprange( - FirewallContext *ctx, + sd_netlink *nfnl, bool add, int nfproto, int af, @@ -74,7 +64,7 @@ int nft_set_element_modify_iprange( unsigned source_prefixlen); int nft_set_element_modify_ip( - FirewallContext *ctx, + sd_netlink *nfnl, bool add, int nfproto, int af, @@ -83,7 +73,7 @@ int nft_set_element_modify_ip( const union in_addr_union *source); int nft_set_element_modify_any( - FirewallContext *ctx, + sd_netlink *nfnl, bool add, int nfproto, const char *table, diff --git a/src/shared/meson.build b/src/shared/meson.build index 59ce0ec2b3..134e5ad2b6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -76,7 +76,6 @@ shared_sources = files( 'fdset.c', 'fido2-util.c', 'find-esp.c', - 'firewall-util-nft.c', 'firewall-util.c', 'fork-notify.c', 'format-table.c', @@ -249,10 +248,6 @@ if conf.get('ENABLE_UTMP') == 1 shared_sources += files('utmp-wtmp.c') endif -if conf.get('HAVE_LIBIPTC') == 1 - shared_sources += files('firewall-util-iptables.c') -endif - if conf.get('HAVE_LIBBPF') == 1 shared_sources += files('bpf-link.c') endif @@ -323,7 +318,6 @@ libshared_deps = [threads, libcrypt, libdl, libgcrypt_cflags, - libiptc_cflags, libkmod_cflags, liblz4_cflags, libmount, diff --git a/src/test/test-firewall-util.c b/src/test/test-firewall-util.c index 1a2121d084..d7080fa349 100644 --- a/src/test/test-firewall-util.c +++ b/src/test/test-firewall-util.c @@ -3,22 +3,24 @@ #include #include +#include "sd-netlink.h" + #include "firewall-util.h" -#include "firewall-util-private.h" #include "in-addr-util.h" #include "log.h" +#include "netlink-internal.h" #include "random-util.h" #include "socket-util.h" #include "tests.h" -static void test_v6(FirewallContext *ctx) { +static sd_netlink *nfnl = NULL; + +TEST(v6) { union in_addr_union u1, u2, u3; uint8_t prefixlen; int r; - ASSERT_NOT_NULL(ctx); - - log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); + ASSERT_NOT_NULL(nfnl); if (!socket_ipv6_is_supported()) return log_info("IPv6 is not supported by kernel, skipping tests."); @@ -29,22 +31,19 @@ static void test_v6(FirewallContext *ctx) { prefixlen = random_u64_range(128 + 1 - 8) + 8; random_bytes(&u3, sizeof(u3)); - ASSERT_OK(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 128)); - ASSERT_OK(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 128)); - ASSERT_OK(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 64)); - ASSERT_OK(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 64)); - ASSERT_OK(fw_add_masquerade(&ctx, true, AF_INET6, &u3, prefixlen)); - ASSERT_OK(fw_add_masquerade(&ctx, false, AF_INET6, &u3, prefixlen)); + ASSERT_OK_OR(r = fw_nftables_add_masquerade(nfnl, true, AF_INET6, &u1, 128), + -EPERM, -EOPNOTSUPP, -ENOPROTOOPT); + if (r < 0) + return (void) log_tests_skipped_errno(r, "Failed to add IPv6 masquerade"); - 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_OK(r); - - ASSERT_OK(fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u1)); - ASSERT_OK(fw_add_local_dnat(&ctx, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, false, AF_INET6, &u1, 128)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, true, AF_INET6, &u1, 64)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, false, AF_INET6, &u1, 64)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, true, AF_INET6, &u3, prefixlen)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, false, AF_INET6, &u3, prefixlen)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, true, AF_INET6, IPPROTO_TCP, 4711, &u1, 815, NULL)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u1)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL)); } static union in_addr_union *parse_addr(const char *str, union in_addr_union *u) { @@ -54,52 +53,31 @@ static union in_addr_union *parse_addr(const char *str, union in_addr_union *u) return u; } -static bool test_v4(FirewallContext *ctx) { +TEST(v4) { union in_addr_union u, v; int r; - ASSERT_NOT_NULL(ctx); + ASSERT_NOT_NULL(nfnl); - log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); + ASSERT_ERROR(fw_nftables_add_masquerade(nfnl, true, AF_INET, NULL, 0), EINVAL); + ASSERT_ERROR(fw_nftables_add_masquerade(nfnl, true, AF_INET, parse_addr("10.1.2.0", &u), 0), EINVAL); -#if HAVE_LIBIPTC - if (ctx->backend == FW_BACKEND_IPTABLES && fw_iptables_init_nat(NULL) < 0) { - log_debug("iptables backend is used, but nat table is not enabled, skipping tests"); - return false; - } -#endif + ASSERT_OK_OR(r = fw_nftables_add_masquerade(nfnl, true, AF_INET, parse_addr("10.1.2.3", &u), 32), + -EPERM, -EOPNOTSUPP, -ENOPROTOOPT); + if (r < 0) + return (void) log_tests_skipped_errno(r, "Failed to add IPv4 masquerade"); - ASSERT_ERROR(fw_add_masquerade(&ctx, true, AF_INET, NULL, 0), EINVAL); - ASSERT_ERROR(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_OK(r); - - ASSERT_OK(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.0.2.0", &u), 28)); - ASSERT_OK(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.0.2.0", &u), 28)); - ASSERT_OK(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.1.2.3", &u), 32)); - ASSERT_OK(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL)); - ASSERT_OK(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL)); - ASSERT_OK(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))); - ASSERT_OK(fw_add_local_dnat(&ctx, false, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, NULL)); - - return true; + ASSERT_OK(fw_nftables_add_masquerade(nfnl, true, AF_INET, parse_addr("10.0.2.0", &u), 28)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, false, AF_INET, parse_addr("10.0.2.0", &u), 28)); + ASSERT_OK(fw_nftables_add_masquerade(nfnl, false, AF_INET, parse_addr("10.1.2.3", &u), 32)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL)); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, parse_addr("1.2.3.4", &v))); + ASSERT_OK(fw_nftables_add_local_dnat(nfnl, false, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, NULL)); } -int main(int argc, char *argv[]) { - _cleanup_(fw_ctx_freep) FirewallContext *ctx = NULL; - - test_setup_logging(LOG_DEBUG); +static int intro(void) { + int r; if (getuid() != 0) return log_tests_skipped("not root"); @@ -107,21 +85,16 @@ int main(int argc, char *argv[]) { ASSERT_OK_ERRNO(setenv("SYSTEMD_FIREWALL_UTIL_NFT_TABLE_NAME", "io.systemd-test.nat", /* overwrite = */ true)); ASSERT_OK_ERRNO(setenv("SYSTEMD_FIREWALL_UTIL_DNAT_MAP_NAME", "test_map_port_ipport", /* overwrite = */ true)); - ASSERT_OK(fw_ctx_new(&ctx)); - ASSERT_NOT_NULL(ctx); - - if (ctx->backend == FW_BACKEND_NONE) - return log_tests_skipped("no firewall backend supported"); - - if (test_v4(ctx) && ctx->backend == FW_BACKEND_NFTABLES) - test_v6(ctx); - -#if HAVE_LIBIPTC - if (ctx->backend != FW_BACKEND_IPTABLES) { - ctx->backend = FW_BACKEND_IPTABLES; - test_v4(ctx); - } -#endif + r = sd_nfnl_socket_open(&nfnl); + if (r < 0) + return log_tests_skipped_errno(r, "Failed to initialize nftables"); return 0; } + +static int outro(void) { + sd_netlink_unref(nfnl); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, outro); diff --git a/src/test/test-nft-set.c b/src/test/test-nft-set.c index 6ae2da03eb..239f9a08f7 100644 --- a/src/test/test-nft-set.c +++ b/src/test/test-nft-set.c @@ -2,6 +2,8 @@ #include +#include "sd-netlink.h" + #include "firewall-util.h" #include "in-addr-util.h" #include "netlink-internal.h" @@ -25,9 +27,8 @@ int main(int argc, char **argv) { const char *table = argv[3], *set = argv[4]; - FirewallContext *ctx; - r = fw_ctx_new(&ctx); - assert_se(r == 0); + _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL; + ASSERT_OK(sd_nfnl_socket_open(&nfnl)); bool add; if (streq(argv[1], "add")) @@ -41,7 +42,7 @@ int main(int argc, char **argv) { r = safe_atou32(argv[6], &element); assert_se(r == 0); - r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element)); + r = nft_set_element_modify_any(nfnl, add, nfproto, table, set, &element, sizeof(element)); assert_se(r == 0); } else if (streq(argv[5], "uint64")) { uint64_t element; @@ -49,7 +50,7 @@ int main(int argc, char **argv) { r = safe_atou64(argv[6], &element); assert_se(r == 0); - r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element)); + r = nft_set_element_modify_any(nfnl, add, nfproto, table, set, &element, sizeof(element)); assert_se(r == 0); } else if (streq(argv[5], "in_addr")) { union in_addr_union addr; @@ -58,7 +59,7 @@ int main(int argc, char **argv) { r = in_addr_from_string_auto(argv[6], &af, &addr); assert_se(r == 0); - r = nft_set_element_modify_ip(ctx, add, nfproto, af, table, set, &addr); + r = nft_set_element_modify_ip(nfnl, add, nfproto, af, table, set, &addr); assert_se(r == 0); } else if (streq(argv[5], "network")) { union in_addr_union addr; @@ -68,7 +69,7 @@ int main(int argc, char **argv) { r = in_addr_prefix_from_string_auto(argv[6], &af, &addr, &prefixlen); assert_se(r == 0); - r = nft_set_element_modify_iprange(ctx, add, nfproto, af, table, set, &addr, prefixlen); + r = nft_set_element_modify_iprange(nfnl, add, nfproto, af, table, set, &addr, prefixlen); assert_se(r == 0); }