summaryrefslogtreecommitdiff
path: root/src/devices/nm-device-wireguard.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/nm-device-wireguard.c')
-rw-r--r--src/devices/nm-device-wireguard.c424
1 files changed, 419 insertions, 5 deletions
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 714dbd4740..12fd9f3f5f 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,12 +21,16 @@
#include "nm-device-wireguard.h"
+#include <linux/rtnetlink.h>
+#include <linux/fib_rules.h>
+
#include "nm-setting-wireguard.h"
#include "nm-core-internal.h"
#include "nm-glib-aux/nm-secret-utils.h"
#include "nm-device-private.h"
#include "platform/nm-platform.h"
#include "platform/nmp-object.h"
+#include "platform/nmp-rules-manager.h"
#include "nm-device-factory.h"
#include "nm-active-connection.h"
#include "nm-act-request.h"
@@ -134,10 +138,21 @@ typedef struct {
GHashTable *peers;
gint64 resolve_next_try_at;
- guint resolve_next_try_id;
-
gint64 link_config_last_at;
+
+ guint resolve_next_try_id;
guint link_config_delayed_id;
+
+ guint32 auto_default_route_fwmark;
+
+ guint32 auto_default_route_priority;
+
+ bool auto_default_route_enabled_4:1;
+ bool auto_default_route_enabled_6:1;
+ bool auto_default_route_initialized:1;
+ bool auto_default_route_refresh:1;
+ bool auto_default_route_priority_initialized:1;
+
} NMDeviceWireGuardPrivate;
struct _NMDeviceWireGuard {
@@ -177,6 +192,372 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
/*****************************************************************************/
+static void
+_auto_default_route_get_enabled (NMSettingWireGuard *s_wg,
+ NMConnection *connection,
+ gboolean *out_enabled_v4,
+ gboolean *out_enabled_v6)
+{
+ NMTernary enabled_v4;
+ NMTernary enabled_v6;
+
+ enabled_v4 = nm_setting_wireguard_get_ip4_auto_default_route (s_wg);
+ enabled_v6 = nm_setting_wireguard_get_ip6_auto_default_route (s_wg);
+
+ if (enabled_v4 == NM_TERNARY_DEFAULT) {
+ if (nm_setting_ip_config_get_never_default (nm_connection_get_setting_ip_config (connection, AF_INET)))
+ enabled_v4 = FALSE;
+ }
+ if (enabled_v6 == NM_TERNARY_DEFAULT) {
+ if (nm_setting_ip_config_get_never_default (nm_connection_get_setting_ip_config (connection, AF_INET6)))
+ enabled_v6 = FALSE;
+ }
+
+ if ( enabled_v4 == NM_TERNARY_DEFAULT
+ || enabled_v6 == NM_TERNARY_DEFAULT) {
+ guint i, n_peers;
+
+ n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+ for (i = 0; i < n_peers; i++) {
+ NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+ guint n_aips;
+ guint j;
+
+ n_aips = nm_wireguard_peer_get_allowed_ips_len (peer);
+ for (j = 0; j < n_aips; j++) {
+ const char *aip;
+ gboolean valid;
+ int prefix;
+ int addr_family;
+
+ aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
+ if (!valid)
+ continue;
+ if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+ aip,
+ &addr_family,
+ NULL,
+ &prefix))
+ continue;
+ if (prefix != 0)
+ continue;
+
+ if (addr_family == AF_INET) {
+ if (enabled_v4 == NM_TERNARY_DEFAULT) {
+ enabled_v4 = TRUE;
+ if (enabled_v6 != NM_TERNARY_DEFAULT)
+ goto done;
+ }
+ } else {
+ if (enabled_v6 == NM_TERNARY_DEFAULT) {
+ enabled_v6 = TRUE;
+ if (enabled_v4 != NM_TERNARY_DEFAULT)
+ goto done;
+ }
+ }
+ }
+ }
+done:
+ ;
+ }
+
+ *out_enabled_v4 = (enabled_v4 == TRUE);
+ *out_enabled_v6 = (enabled_v6 == TRUE);
+}
+
+static guint32
+_auto_default_route_find_unused_table (NMPlatform *platform)
+{
+ guint32 table;
+ int is_ipv4;
+
+ for (table = 51820; TRUE; table++) {
+ const NMDedupMultiHeadEntry *head_entry;
+ const guint32 table_coerced = nm_platform_route_table_coerce (table);
+ NMDedupMultiIter iter;
+ const NMPObject *plobj;
+
+ /* find a table/fwmark that is not yet in use. */
+
+ for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) {
+ head_entry = nm_platform_lookup_object (platform,
+ is_ipv4
+ ? NMP_OBJECT_TYPE_IP4_ROUTE
+ : NMP_OBJECT_TYPE_IP6_ROUTE,
+ -1);
+ nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+ if (NMP_OBJECT_CAST_IP_ROUTE (plobj)->table_coerced == table_coerced)
+ goto try_next_table;
+ }
+ }
+
+ head_entry = nm_platform_lookup_object_by_addr_family (platform,
+ NMP_OBJECT_TYPE_ROUTING_RULE,
+ AF_UNSPEC);
+ nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+ const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (plobj);
+
+ if (rr->fwmark == table)
+ goto try_next_table;
+ }
+
+ head_entry = nm_platform_lookup_obj_type (platform, NMP_OBJECT_TYPE_LINK);
+ nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+ const NMPObject *lnk_wg;
+
+ if (plobj->link.type != NM_LINK_TYPE_WIREGUARD)
+ continue;
+
+ lnk_wg = plobj->_link.netlink.lnk;
+
+ if (!lnk_wg)
+ continue;
+
+ if (NMP_OBJECT_GET_TYPE (lnk_wg) != NMP_OBJECT_TYPE_LNK_WIREGUARD)
+ continue;
+
+ if (NMP_OBJECT_CAST_LNK_WIREGUARD (lnk_wg)->fwmark == table)
+ goto try_next_table;
+ }
+
+ return table;
+try_next_table:
+ ;
+ }
+}
+
+#define PRIO_WIDTH ((guint32) 2)
+
+static gboolean
+_auto_default_route_find_priority_exists (const NMDedupMultiHeadEntry *head_entry,
+ guint32 priority)
+{
+ NMDedupMultiIter iter;
+ const NMPObject *plobj;
+
+ nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+ const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (plobj);
+
+ /* we don't differenciate between IPv4 vs. IPv6. There should be no
+ * conflicting rules with the same priority. */
+ if ( rr->priority >= priority
+ && rr->priority < priority + PRIO_WIDTH)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static guint32
+_auto_default_route_find_priority (NMPlatform *platform,
+ const char *uuid)
+{
+ const NMDedupMultiHeadEntry *head_entry;
+ guint64 rnd_seed;
+ guint32 RANGE_BASE = 100u / PRIO_WIDTH;
+ guint32 RANGE_LENGTH = ((32766u - PRIO_WIDTH) / PRIO_WIDTH) - RANGE_BASE;
+ guint32 PRIME_NUMBER = 1111567573u;
+ guint32 prio_candidate = 0;
+ guint32 i_step;
+ guint32 i;
+
+ /* For the auto-default-route policy routing rule we add 4 rules (2 Ipv4 and 2 IPv6).
+ * Hence, we choose a base priority for the first (of the two rules) and the second
+ * rule gets priortiy + 1.
+ * one base priority and then add the rules with priorities [priority, priority+3].
+ * For that, we pick a base-priority that does not yet exist.
+ * We also want to choose a priority that is individual for the profile.
+ * We do so by taking the UUID as seed and then sample the candidate priorities
+ * in a certain order. */
+
+ rnd_seed = c_siphash_hash ((const guint8 [16]) { 0xb9, 0x39, 0x8e, 0xed, 0x15, 0xb3, 0xd1, 0xc4, 0x5f, 0x45, 0x00, 0x4f, 0xec, 0xc2, 0x2b, 0x7e },
+ (const guint8 *) uuid,
+ uuid ? strlen (uuid) + 1u : 0u);
+
+ head_entry = nm_platform_lookup_object_by_addr_family (platform,
+ NMP_OBJECT_TYPE_ROUTING_RULE,
+ AF_UNSPEC);
+
+ i_step = ((guint32) rnd_seed) % RANGE_LENGTH;
+ for (i = 0; i < RANGE_LENGTH; i++) {
+
+ /* we sample the range in a stable, but somewhat arbitrary order to
+ * fine the priority. */
+ i_step = (i_step + PRIME_NUMBER) % RANGE_LENGTH;
+
+ prio_candidate = (RANGE_BASE + i_step) * PRIO_WIDTH;
+
+ if (!_auto_default_route_find_priority_exists (head_entry, prio_candidate))
+ return prio_candidate;
+ }
+
+ /* Couldn't find an unused one? Very odd, this really should not happen unless there
+ * are thousands of rules already. Just pick the last one we sampled. */
+ return prio_candidate;
+}
+
+static void
+_auto_default_route_init (NMDeviceWireGuard *self)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ NMConnection *connection;
+ NMSettingWireGuard *s_wg;
+ gboolean enabled_v4;
+ gboolean enabled_v6;
+ gboolean refreshing_only;
+ guint32 old_fwmark;
+ char sbuf1[100];
+
+ if (G_LIKELY ( priv->auto_default_route_initialized
+ && !priv->auto_default_route_refresh))
+ return;
+
+ refreshing_only = priv->auto_default_route_initialized
+ && priv->auto_default_route_refresh;
+ priv->auto_default_route_refresh = FALSE;
+
+ connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
+ s_wg = _nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD);
+
+ old_fwmark = priv->auto_default_route_fwmark;
+
+ priv->auto_default_route_fwmark = nm_setting_wireguard_get_fwmark (s_wg);
+
+ _auto_default_route_get_enabled (s_wg,
+ connection,
+ &enabled_v4,
+ &enabled_v6);
+ priv->auto_default_route_enabled_4 = enabled_v4;
+ priv->auto_default_route_enabled_6 = enabled_v6;
+ priv->auto_default_route_initialized = TRUE;
+
+ if ( ( priv->auto_default_route_enabled_4
+ || priv->auto_default_route_enabled_6)
+ && priv->auto_default_route_fwmark == 0u) {
+ if (refreshing_only)
+ priv->auto_default_route_fwmark = old_fwmark;
+ else
+ priv->auto_default_route_fwmark = _auto_default_route_find_unused_table (nm_device_get_platform (NM_DEVICE (self)));
+ }
+
+ _LOGT (LOGD_DEVICE,
+ "auto-default-route is %s for IPv4 and %s for IPv6%s",
+ priv->auto_default_route_enabled_4 ? "enabled" : "disabled",
+ priv->auto_default_route_enabled_6 ? "enabled" : "disabled",
+ priv->auto_default_route_enabled_4 || priv->auto_default_route_enabled_6
+ ? nm_sprintf_buf (sbuf1, " (fwmark 0x%x)", priv->auto_default_route_fwmark)
+ : "");
+}
+
+static GPtrArray *
+get_extra_rules (NMDevice *device)
+{
+ NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ gs_unref_ptrarray GPtrArray *extra_rules = NULL;
+ guint32 priority = 0;
+ int is_ipv4;
+ NMConnection *connection;
+
+ _auto_default_route_init (self);
+
+ connection = nm_device_get_applied_connection (device);
+ if (!connection)
+ return NULL;
+
+ for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) {
+ NMSettingIPConfig *s_ip;
+ int addr_family = is_ipv4 ? AF_INET : AF_INET6;
+ guint32 table_main;
+ guint32 fwmark;
+
+ if (is_ipv4) {
+ if (!priv->auto_default_route_enabled_4)
+ continue;
+ } else {
+ if (!priv->auto_default_route_enabled_6)
+ continue;
+ }
+
+ if (!extra_rules) {
+ if (priv->auto_default_route_priority_initialized)
+ priority = priv->auto_default_route_priority;
+ else {
+ priority = _auto_default_route_find_priority (nm_device_get_platform (device),
+ nm_connection_get_uuid (connection));
+ priv->auto_default_route_priority = priority;
+ priv->auto_default_route_priority_initialized = TRUE;
+ }
+ extra_rules = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+ }
+
+ s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+ table_main = nm_setting_ip_config_get_route_table (s_ip);
+ if (table_main == 0)
+ table_main = RT_TABLE_MAIN;
+
+ fwmark = priv->auto_default_route_fwmark;
+
+ G_STATIC_ASSERT_EXPR (PRIO_WIDTH == 2);
+
+ g_ptr_array_add (extra_rules,
+ nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE,
+ &((const NMPlatformRoutingRule) {
+ .priority = priority,
+ .addr_family = addr_family,
+ .action = FR_ACT_TO_TBL,
+ .table = table_main,
+ .suppress_prefixlen_inverse = ~((guint32) 0u),
+ })));
+
+ g_ptr_array_add (extra_rules,
+ nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE,
+ &((const NMPlatformRoutingRule) {
+ .priority = priority + 1u,
+ .addr_family = addr_family,
+ .action = FR_ACT_TO_TBL,
+ .table = fwmark,
+ .flags = FIB_RULE_INVERT,
+ .fwmark = fwmark,
+ .fwmask = 0xFFFFFFFFu,
+ })));
+ }
+
+ return g_steal_pointer (&extra_rules);
+}
+
+static guint32
+coerce_route_table (NMDevice *device,
+ int addr_family,
+ guint32 route_table,
+ gboolean is_user_config)
+{
+ NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ gboolean auto_default_route_enabled;
+
+ if (route_table != 0u)
+ return route_table;
+
+ _auto_default_route_init (self);
+
+ auto_default_route_enabled = (addr_family == AF_INET)
+ ? priv->auto_default_route_enabled_4
+ : priv->auto_default_route_enabled_6;
+
+ if (auto_default_route_enabled) {
+ /* we need to enable full-sync mode of all routing tables. */
+ _LOGT (LOGD_DEVICE, "coerce ipv%c.route-table setting to \"main\" (table 254) as we enable auto-default-route handling",
+ nm_utils_addr_family_to_char (addr_family));
+ return RT_TABLE_MAIN;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
static gboolean
_peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
{
@@ -1084,6 +1465,8 @@ link_config (NMDeviceWireGuard *self,
_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
reason, _link_config_mode_to_string (config_mode));
+ _auto_default_route_init (self);
+
if (!priv->dns_manager) {
priv->dns_manager = g_object_ref (nm_dns_manager_get ());
g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
@@ -1130,7 +1513,7 @@ link_config (NMDeviceWireGuard *self,
wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg);
wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
- wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg);
+ wg_lnk.fwmark = priv->auto_default_route_fwmark;
wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
@@ -1256,6 +1639,7 @@ static NMIPConfig *
_get_dev2_ip_config (NMDeviceWireGuard *self,
int addr_family)
{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
gs_unref_object NMIPConfig *ip_config = NULL;
NMConnection *connection;
NMSettingWireGuard *s_wg;
@@ -1264,6 +1648,9 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
int ip_ifindex;
guint32 route_metric;
guint32 route_table_coerced;
+ gboolean auto_default_route_enabled;
+
+ _auto_default_route_init (self);
connection = nm_device_get_applied_connection (NM_DEVICE (self));
@@ -1303,6 +1690,10 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family));
+ auto_default_route_enabled = (addr_family == AF_INET)
+ ? priv->auto_default_route_enabled_4
+ : priv->auto_default_route_enabled_6;
+
n_peers = nm_setting_wireguard_get_peers_len (s_wg);
for (i = 0; i < n_peers; i++) {
NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
@@ -1316,6 +1707,7 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
const char *aip;
gboolean valid;
int prefix;
+ guint32 rtable_coerced;
aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
@@ -1335,13 +1727,24 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
+ rtable_coerced = route_table_coerced;
+
+ if ( prefix == 0
+ && auto_default_route_enabled) {
+ /* In auto-default-route mode, we place the default route in a table that
+ * has the same number as the fwmark. wg-quick does that too. If you don't
+ * like that, configure the rules and the default-route explicitly in the
+ * connection profile. */
+ rtable_coerced = nm_platform_route_table_coerce (priv->auto_default_route_fwmark);
+ }
+
if (addr_family == AF_INET) {
rt.r4 = (NMPlatformIP4Route) {
.network = addrbin.addr4,
.plen = prefix,
.ifindex = ip_ifindex,
.rt_source = NM_IP_CONFIG_SOURCE_USER,
- .table_coerced = route_table_coerced,
+ .table_coerced = rtable_coerced,
.metric = route_metric,
};
} else {
@@ -1350,7 +1753,7 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
.plen = prefix,
.ifindex = ip_ifindex,
.rt_source = NM_IP_CONFIG_SOURCE_USER,
- .table_coerced = route_table_coerced,
+ .table_coerced = rtable_coerced,
.metric = route_metric,
};
}
@@ -1411,7 +1814,11 @@ _device_cleanup (NMDeviceWireGuard *self)
NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
_peers_remove_all (priv);
+
_secrets_cancel (self);
+
+ priv->auto_default_route_initialized = FALSE;
+ priv->auto_default_route_priority_initialized = FALSE;
}
static void
@@ -1444,6 +1851,8 @@ can_reapply_change (NMDevice *device,
NM_SETTING_WIREGUARD_SETTING_NAME,
error,
NM_SETTING_WIREGUARD_FWMARK,
+ NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE,
+ NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE,
NM_SETTING_WIREGUARD_LISTEN_PORT,
NM_SETTING_WIREGUARD_PEERS,
NM_SETTING_WIREGUARD_PEER_ROUTES,
@@ -1465,9 +1874,12 @@ reapply_connection (NMDevice *device,
NMConnection *con_new)
{
NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
gs_unref_object NMIPConfig *ip4_config = NULL;
gs_unref_object NMIPConfig *ip6_config = NULL;
+ priv->auto_default_route_refresh = TRUE;
+
ip4_config = _get_dev2_ip_config (self, AF_INET);
ip6_config = _get_dev2_ip_config (self, AF_INET6);
@@ -1634,6 +2046,8 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
device_class->can_reapply_change = can_reapply_change;
device_class->reapply_connection = reapply_connection;
device_class->get_configured_mtu = get_configured_mtu;
+ device_class->get_extra_rules = get_extra_rules;
+ device_class->coerce_route_table = coerce_route_table;
obj_properties[PROP_PUBLIC_KEY] =
g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,