From 2afcebe0c770c2d84faee3b9fb84a5f71f253e6d Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Tue, 11 Mar 2025 11:33:55 +0100 Subject: [PATCH] wireguard: add firewall rules to copy mark When a WG connection is connecting to an IPv6 endpoint, configures a default route, and firewalld is active with IPv6_rpfilter=yes, it never handshakes and doesn't pass traffic. This is because firewalld has a IPv6 reverse path filter which is discarding these packets. Thus, we add some firewall rules whenever a WG connection is brought up that ensure the conntrack mark and packet mark are copied over. These rules are largely inspired by wg-quick: https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash?id=17c78d31c27a3c311a2ff42a881057753c6ef2a4#n221 (cherry picked from commit db557908a2b70ee05ba7d8afe4b388a595109f34) --- src/core/devices/nm-device-wireguard.c | 41 +++++++++ src/core/nm-firewall-utils.c | 114 ++++++++++++++++++++++++- src/core/nm-firewall-utils.h | 2 + 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c index 4a08192e03..52a35655c0 100644 --- a/src/core/devices/nm-device-wireguard.c +++ b/src/core/devices/nm-device-wireguard.c @@ -23,6 +23,7 @@ #include "nm-active-connection.h" #include "nm-act-request.h" #include "dns/nm-dns-manager.h" +#include "nm-firewall-utils.h" #define _NMLOG_DEVICE_TYPE NMDeviceWireGuard #include "nm-device-logging.h" @@ -1207,6 +1208,25 @@ skip: *out_allowed_ips_data = g_steal_pointer(&allowed_ips); } +static void +_configure_firewall(NMDeviceWireGuard *self, NMConnection *connection, int addr_family, gboolean up) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self); + const char *ip_iface; + + if (addr_family == AF_INET && !priv->auto_default_route_enabled_4) + return; + else if (addr_family == AF_INET6 && !priv->auto_default_route_enabled_6) + return; + + ip_iface = nm_device_get_ip_iface(NM_DEVICE(self)); + + nm_assert(priv->auto_default_route_fwmark); + nm_assert(ip_iface); + + nm_firewall_config_set_wg_rule(ip_iface, addr_family, priv->auto_default_route_fwmark, up); +} + /*****************************************************************************/ static void @@ -1300,6 +1320,18 @@ create_and_realize(NMDevice *device, return TRUE; } +static void +deactivate(NMDevice *device) +{ + NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD(device); + NMConnection *connection = nm_device_get_applied_connection(NM_DEVICE(self)); + + if (connection) { + _configure_firewall(self, connection, AF_INET, FALSE); + _configure_firewall(self, connection, AF_INET6, FALSE); + } +} + /*****************************************************************************/ static void @@ -1768,6 +1800,10 @@ act_stage3_ip_config(NMDevice *device, int addr_family) nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL; l3cd = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family); + _configure_firewall(NM_DEVICE_WIREGUARD(device), + nm_device_get_applied_connection(device), + addr_family, + TRUE); nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd); } @@ -1866,6 +1902,10 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne if (state >= NM_DEVICE_STATE_CONFIG) { priv->auto_default_route_refresh = TRUE; + + _configure_firewall(self, con_old, AF_INET, FALSE); + _configure_firewall(self, con_old, AF_INET6, FALSE); + link_config(NM_DEVICE_WIREGUARD(device), "reapply", LINK_CONFIG_MODE_REAPPLY, NULL); } @@ -2018,6 +2058,7 @@ nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass) device_class->state_changed = device_state_changed; device_class->create_and_realize = create_and_realize; + device_class->deactivate = deactivate; device_class->act_stage2_config = act_stage2_config; device_class->act_stage2_config_also_for_external_or_assume = TRUE; device_class->act_stage3_ip_config = act_stage3_ip_config; diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c index 445dadb064..7b3b4a1032 100644 --- a/src/core/nm-firewall-utils.c +++ b/src/core/nm-firewall-utils.c @@ -179,7 +179,7 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c /*****************************************************************************/ static gboolean -_share_iptables_call_v(const char *const *argv) +_iptables_call_v(const char *const *argv) { gs_free_error GError *error = NULL; gs_free char *argv_str = NULL; @@ -213,7 +213,14 @@ _share_iptables_call_v(const char *const *argv) } #define _share_iptables_call(...) \ - _share_iptables_call_v(NM_MAKE_STRV("" IPTABLES_PATH "", "--wait", "2", __VA_ARGS__)) + _iptables_call_v(NM_MAKE_STRV("" IPTABLES_PATH "", "--wait", "2", __VA_ARGS__)) + +#define _ipxtables_call(family, ...) \ + _iptables_call_v( \ + NM_MAKE_STRV((family == AF_INET ? "" IPTABLES_PATH "" : "" IP6TABLES_PATH ""), \ + "--wait", \ + "2", \ + __VA_ARGS__)) static gboolean _share_iptables_chain_op(const char *table, const char *chain, const char *op) @@ -756,6 +763,85 @@ _fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr, return nm_str_buf_finalize_to_gbytes(&strbuf); } +static GBytes * +_fw_nft_wg_default_construct(const char *ip_iface, int family, int fwmark, gboolean up) +{ + nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); + gs_free char *table_name = NULL; + const char *family_str = family == AF_INET ? "ip" : "ip6"; + + table_name = _share_iptables_get_name(FALSE, "nm-wg", ip_iface); + + _fw_nft_append_cmd_table(&strbuf, family_str, table_name, up); + + if (up) { + _append(&strbuf, + "add chain %s %s premangle {" + " type filter hook prerouting priority mangle; policy accept; " + " meta l4proto udp meta mark set ct mark; " + "};", + family_str, + table_name); + + _append(&strbuf, + "add chain %s %s postmangle {" + " type filter hook postrouting priority mangle; policy accept; " + " meta l4proto udp mark 0x%08x ct mark set meta mark; " + "};", + family_str, + table_name, + fwmark); + } + + return nm_str_buf_finalize_to_gbytes(&strbuf); +} + +static void +_fw_iptables_wg_configure(const char *ip_iface, int family, int fwmark, gboolean up) +{ + gs_free char *comment_name = NULL; + char fwmark_str[11]; + + comment_name = _share_iptables_get_name(FALSE, "nm-wg", ip_iface); + g_snprintf(fwmark_str, sizeof(fwmark_str), "%" G_GUINT32_FORMAT, fwmark); + + nm_assert(strlen(fwmark_str) > 0); + + _ipxtables_call(family, + "--table", + "mangle", + up ? "--insert" : "--delete", + "POSTROUTING", + "--match", + "mark", + "--mark", + fwmark_str, + "--protocol", + "udp", + "--jump", + "CONNMARK", + "--save-mark", + "-m", + "comment", + "--comment", + comment_name); + + _ipxtables_call(family, + "--table", + "mangle", + up ? "--insert" : "--delete", + "PREROUTING", + "--protocol", + "udp", + "--jump", + "CONNMARK", + "--restore-mark", + "-m", + "comment", + "--comment", + comment_name); +} + /*****************************************************************************/ GBytes * @@ -1046,6 +1132,30 @@ nm_firewall_config_free(NMFirewallConfig *self) } /*****************************************************************************/ +void +nm_firewall_config_set_wg_rule(const char *ifname, int family, int fwmark, gboolean up) +{ + nm_assert(NM_IN_SET(family, AF_INET, AF_INET6)); + + switch (nm_firewall_utils_get_backend()) { + case NM_FIREWALL_BACKEND_NFTABLES: + { + gs_unref_bytes GBytes *stdin_buf = NULL; + + stdin_buf = _fw_nft_wg_default_construct(ifname, family, fwmark, up); + _fw_nft_call_sync(stdin_buf, NULL); + break; + } + case NM_FIREWALL_BACKEND_IPTABLES: + _fw_iptables_wg_configure(ifname, family, fwmark, up); + break; + case NM_FIREWALL_BACKEND_NONE: + break; + default: + nm_assert_not_reached(); + break; + } +} void nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up) diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h index 9f13a5127e..f46b3666fa 100644 --- a/src/core/nm-firewall-utils.h +++ b/src/core/nm-firewall-utils.h @@ -24,6 +24,8 @@ NMFirewallConfig *nm_firewall_config_new_shared(const char *ip_iface, in_addr_t void nm_firewall_config_free(NMFirewallConfig *self); +void nm_firewall_config_set_wg_rule(const char *ifname, int family, int fwmark, gboolean up); + void nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up); /*****************************************************************************/