From 7bc5a1fc267987d48627d7f33b6fc200fe6f524a Mon Sep 17 00:00:00 2001 From: Alexander Lochmann Date: Sun, 24 Dec 2023 14:24:04 +0100 Subject: [PATCH 1/5] Allow any kind of VPN as secondaries (Fixed #1395) As of now, NM allows only connections derived from the VPN plugin to be used as secondaries. This excludes non-VPN-plugin connections still providing VPN functionality, such as Wireguard, to be used as secondary. This commit extends NetworkManager to allow any kind of VPN connection to be used as secondary connection. (Code rework done by Martin Sucha) --- src/core/nm-manager.c | 22 +++++++++++++++ src/core/nm-policy.c | 5 ++-- src/libnm-client-impl/libnm.ver | 2 ++ src/libnm-core-impl/nm-connection.c | 40 +++++++++++++++++++++++++++ src/libnm-core-public/nm-connection.h | 4 +++ src/nmcli/settings.c | 17 ++++++------ 6 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 87dde2c3af..5bc6c80038 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -6196,6 +6196,28 @@ _new_active_connection(NMManager *self, activation_reason, initial_state_flags, subject); + } else if (nm_connection_is_valid_secondary(sett_conn ? nm_settings_connection_get_connection(sett_conn) + : incompl_conn)) { + /** + * For non-plugin VPN connections, re-validate the corresponding device. + * Wireguard, for example, needs its own (virtual) device. + * If it, however, gets activated as secondary device, + * 'device' points to the primary device that is currently starting. + * Bringing up a WG tunnel this way will fail. + */ + NMDevice *_device = NULL; + gs_free_error GError *local = NULL; + _device = + nm_manager_get_best_device_for_connection(self, sett_conn, applied, TRUE, NULL, &local); + if (!_device) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "No suitable device found for non-plugin VPN connection (%s).", + local->message); + return NULL; + } + device = _device; } return (NMActiveConnection *) nm_act_request_new(sett_conn, diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index f7be1a9f87..be6e1c19bc 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -2163,11 +2163,10 @@ activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevic break; } - if (!nm_connection_is_type(nm_settings_connection_get_connection(sett_conn), - NM_SETTING_VPN_SETTING_NAME)) { + if (!nm_connection_is_valid_secondary(nm_settings_connection_get_connection(sett_conn))) { _LOGW(LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: The connection is not a " - "VPN.", + "valid secondary.", nm_settings_connection_get_id(sett_conn), sec_uuid); success = FALSE; diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 64e2155d60..7d9735bb50 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -141,6 +141,7 @@ global: nm_connection_get_setting_vpn; nm_connection_get_setting_wimax; nm_connection_get_setting_wired; + nm_connection_get_setting_wireguard; nm_connection_get_setting_wireless; nm_connection_get_setting_wireless_security; nm_connection_get_type; @@ -2026,6 +2027,7 @@ libnm_1_50_4 { libnm_1_52_0 { global: + nm_connection_is_valid_secondary; nm_device_ipvlan_get_mode; nm_device_ipvlan_get_parent; nm_device_ipvlan_get_private; diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 0fbadfb85c..81df36572e 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -15,6 +15,7 @@ #include "nm-connection-private.h" #include "nm-utils.h" #include "nm-setting-private.h" +#include "nm-setting-wireguard.h" #include "libnm-core-intern/nm-core-internal.h" /** @@ -2876,6 +2877,31 @@ nm_connection_is_type(NMConnection *connection, const char *type) return nm_streq0(type, nm_connection_get_connection_type(connection)); } +/** + * nm_connection_is_valid_secondary: + * @connection: the #NMConnection + * + * Checks whether the given connection can be activated as a secondary. + * + * Returns: %TRUE if the connection is a valid secondary. + * + * Since: 1.52 +*/ +gboolean +nm_connection_is_valid_secondary(NMConnection *connection) +{ + const char *type = nm_connection_get_connection_type(connection); + if (type) + return nm_streq(type, NM_SETTING_VPN_SETTING_NAME) + || nm_streq(type, NM_SETTING_WIREGUARD_SETTING_NAME); + + /* we have an incomplete (invalid) connection at hand. That can only + * happen during AddAndActivate. Determine whether it's VPN type based + * on the existence of a [vpn] section. */ + return !!nm_connection_get_setting_vpn(connection) + || nm_connection_get_setting_wireguard(connection); +} + int _nm_setting_sort_for_nm_assert(NMSetting *a, NMSetting *b) { @@ -3822,6 +3848,20 @@ nm_connection_get_setting_vpn(NMConnection *connection) return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_VPN); } +/** + * nm_connection_get_setting_wireguard: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingVpn the connection might contain. + * + * Returns: (transfer none): an #NMSettingVpn if the connection contains one, otherwise %NULL + **/ +NMSettingVpn * +nm_connection_get_setting_wireguard(NMConnection *connection) +{ + return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_WIREGUARD); +} + /** * nm_connection_get_setting_vxlan: * @connection: the #NMConnection diff --git a/src/libnm-core-public/nm-connection.h b/src/libnm-core-public/nm-connection.h index ff3e0f924a..3b8c60b3e6 100644 --- a/src/libnm-core-public/nm-connection.h +++ b/src/libnm-core-public/nm-connection.h @@ -240,12 +240,16 @@ NMSettingVpn *nm_connection_get_setting_vpn(NMConnection *connectio NMSettingWimax *nm_connection_get_setting_wimax(NMConnection *connection); NMSettingAdsl *nm_connection_get_setting_adsl(NMConnection *connection); NMSettingWired *nm_connection_get_setting_wired(NMConnection *connection); +NMSettingVpn *nm_connection_get_setting_wireguard(NMConnection *connection); NMSettingWireless *nm_connection_get_setting_wireless(NMConnection *connection); NMSettingWirelessSecurity *nm_connection_get_setting_wireless_security(NMConnection *connection); NMSettingVlan *nm_connection_get_setting_vlan(NMConnection *connection); NM_AVAILABLE_IN_1_2 NMSettingVxlan *nm_connection_get_setting_vxlan(NMConnection *connection); +NM_AVAILABLE_IN_1_52 +gboolean nm_connection_is_valid_secondary(NMConnection *connection); + G_END_DECLS #endif /* __NM_CONNECTION_H__ */ diff --git a/src/nmcli/settings.c b/src/nmcli/settings.c index 458b03e58d..bf497240bb 100644 --- a/src/nmcli/settings.c +++ b/src/nmcli/settings.c @@ -378,13 +378,13 @@ _set_fcn_precheck_connection_secondaries(NMClient *client, con = nmc_find_connection(connections, "uuid", *iter, NULL, FALSE); if (!con) { nmc_printerr(_("Warning: %s is not an UUID of any existing connection profile\n"), - *iter); - } else { + *iter); + } else { /* Currently, NM only supports VPN connections as secondaries */ - if (!nm_connection_is_type(con, NM_SETTING_VPN_SETTING_NAME)) { - g_set_error(error, 1, 0, _("'%s' is not a VPN connection profile"), *iter); + if (!nm_connection_is_valid_secondary(con)) { + g_set_error(error, 1, 0, _("'%s' is not a valid secondary profile"), *iter); return FALSE; - } + } } } else { con = nmc_find_connection(connections, "id", *iter, NULL, FALSE); @@ -392,10 +392,9 @@ _set_fcn_precheck_connection_secondaries(NMClient *client, g_set_error(error, 1, 0, _("'%s' is not a name of any existing profile"), *iter); return FALSE; } - - /* Currently, NM only supports VPN connections as secondaries */ - if (!nm_connection_is_type(con, NM_SETTING_VPN_SETTING_NAME)) { - g_set_error(error, 1, 0, _("'%s' is not a VPN connection profile"), *iter); + /* Currently, NM only supports VPN connections, including Wireguard, as secondaries */ + if (!nm_connection_is_valid_secondary(con)) { + g_set_error(error, 1, 0, _("'%s' is not a valid secondary profile"), *iter); return FALSE; } From 9275889767ca30af8ee4e5d06ba120869da947ac Mon Sep 17 00:00:00 2001 From: Alexander Krause Date: Thu, 25 Dec 2025 21:48:42 +0100 Subject: [PATCH 2/5] nm_connection_is_valid_secondary: Doc++ --- src/libnm-core-impl/nm-connection.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 81df36572e..80a515e25f 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -2882,6 +2882,8 @@ nm_connection_is_type(NMConnection *connection, const char *type) * @connection: the #NMConnection * * Checks whether the given connection can be activated as a secondary. + * A valid secondary connection is either a Wireguard connection or + * a connection belonging to a VPN plugin, e.g., OpenVPN. * * Returns: %TRUE if the connection is a valid secondary. * From a708f2ce9b03e137f4c6e7f6726a2d9f8a162de7 Mon Sep 17 00:00:00 2001 From: Alexander Krause Date: Thu, 25 Dec 2025 21:54:56 +0100 Subject: [PATCH 3/5] nmcli: Clarify what a valid secondary is --- src/nmcli/settings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmcli/settings.c b/src/nmcli/settings.c index bf497240bb..27867c9860 100644 --- a/src/nmcli/settings.c +++ b/src/nmcli/settings.c @@ -382,7 +382,7 @@ _set_fcn_precheck_connection_secondaries(NMClient *client, } else { /* Currently, NM only supports VPN connections as secondaries */ if (!nm_connection_is_valid_secondary(con)) { - g_set_error(error, 1, 0, _("'%s' is not a valid secondary profile"), *iter); + g_set_error(error, 1, 0, _("'%s' is not a valid VPN profile, e.g., Wireguard or OpenVPN."), *iter); return FALSE; } } @@ -394,7 +394,7 @@ _set_fcn_precheck_connection_secondaries(NMClient *client, } /* Currently, NM only supports VPN connections, including Wireguard, as secondaries */ if (!nm_connection_is_valid_secondary(con)) { - g_set_error(error, 1, 0, _("'%s' is not a valid secondary profile"), *iter); + g_set_error(error, 1, 0, _("'%s' is not a valid VPN profile, e.g., Wireguard or OpenVPN."), *iter); return FALSE; } From 20d1be36f0b93e6bc27b669e603a1fc27b619978 Mon Sep 17 00:00:00 2001 From: Alexander Krause Date: Thu, 25 Dec 2025 21:57:17 +0100 Subject: [PATCH 4/5] Doc: clarify secondary connection --- src/libnm-core-impl/nm-setting-connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c index 0ad97846df..a41500d3f9 100644 --- a/src/libnm-core-impl/nm-setting-connection.c +++ b/src/libnm-core-impl/nm-setting-connection.c @@ -3159,7 +3159,7 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass) * * List of connection UUIDs that should be activated when the base * connection itself is activated. Currently, only VPN connections are - * supported. + * supported, i.e., either Wireguard or of type VPN. **/ /* ---ifcfg-rh--- * property: secondaries From 7c46adae1c03769cc749119c4ff689d1f368aaba Mon Sep 17 00:00:00 2001 From: Alexander Krause Date: Sun, 25 Jan 2026 22:55:47 +0100 Subject: [PATCH 5/5] WIP++ --- src/core/nm-manager.c | 47 +++------------------------ src/core/nm-manager.h | 7 ++++ src/core/nm-policy.c | 37 ++++++++++++++++++--- src/libnm-core-impl/nm-connection.c | 29 ++++++++++++++++- src/libnm-core-public/nm-connection.h | 1 + 5 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 5bc6c80038..fe899a907c 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -491,23 +491,6 @@ _version_info_get(void) /*****************************************************************************/ -static gboolean -_connection_is_vpn(NMConnection *connection) -{ - const char *type; - - type = nm_connection_get_connection_type(connection); - if (type) - return nm_streq(type, NM_SETTING_VPN_SETTING_NAME); - - /* we have an incomplete (invalid) connection at hand. That can only - * happen during AddAndActivate. Determine whether it's VPN type based - * on the existence of a [vpn] section. */ - return !!nm_connection_get_setting_vpn(connection); -} - -/*****************************************************************************/ - static gboolean concheck_enabled(NMManager *self, gboolean *out_changed) { @@ -4530,7 +4513,7 @@ _device_get_activation_prio(NMDevice *device) g_return_val_if_reached(DEVICE_ACTIVATION_PRIO_UNAVAILABLE); } -static NMDevice * +NMDevice * nm_manager_get_best_device_for_connection(NMManager *self, NMSettingsConnection *sett_conn, NMConnection *connection, @@ -6121,7 +6104,7 @@ _new_active_connection(NMManager *self, nm_assert((!incompl_conn) ^ (!sett_conn)); nm_assert(NM_IS_AUTH_SUBJECT(subject)); nm_assert(is_vpn - == _connection_is_vpn(sett_conn ? nm_settings_connection_get_connection(sett_conn) + == nm_connection_is_vpn(sett_conn ? nm_settings_connection_get_connection(sett_conn) : incompl_conn)); nm_assert(is_vpn || NM_IS_DEVICE(device)); nm_assert(!nm_streq0(specific_object, "/")); @@ -6196,28 +6179,6 @@ _new_active_connection(NMManager *self, activation_reason, initial_state_flags, subject); - } else if (nm_connection_is_valid_secondary(sett_conn ? nm_settings_connection_get_connection(sett_conn) - : incompl_conn)) { - /** - * For non-plugin VPN connections, re-validate the corresponding device. - * Wireguard, for example, needs its own (virtual) device. - * If it, however, gets activated as secondary device, - * 'device' points to the primary device that is currently starting. - * Bringing up a WG tunnel this way will fail. - */ - NMDevice *_device = NULL; - gs_free_error GError *local = NULL; - _device = - nm_manager_get_best_device_for_connection(self, sett_conn, applied, TRUE, NULL, &local); - if (!_device) { - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_UNKNOWN_DEVICE, - "No suitable device found for non-plugin VPN connection (%s).", - local->message); - return NULL; - } - device = _device; } return (NMActiveConnection *) nm_act_request_new(sett_conn, @@ -6372,7 +6333,7 @@ nm_manager_activate_connection(NMManager *self, g_return_val_if_fail(NM_IS_MANAGER(self), NULL); g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), NULL); - is_vpn = _connection_is_vpn(nm_settings_connection_get_connection(sett_conn)); + is_vpn = nm_connection_is_vpn(nm_settings_connection_get_connection(sett_conn)); g_return_val_if_fail(is_vpn || NM_IS_DEVICE(device), NULL); g_return_val_if_fail(!error || !*error, NULL); nm_assert(!nm_streq0(specific_object, "/")); @@ -6524,7 +6485,7 @@ find_device_for_activation(NMManager *self, if (!connection) connection = nm_settings_connection_get_connection(sett_conn); - is_vpn = _connection_is_vpn(connection); + is_vpn = nm_connection_is_vpn(connection); if (*out_device) { device = *out_device; diff --git a/src/core/nm-manager.h b/src/core/nm-manager.h index e10ca0d110..12f57f9e68 100644 --- a/src/core/nm-manager.h +++ b/src/core/nm-manager.h @@ -271,6 +271,13 @@ gboolean nm_manager_devcon_autoconnect_blocked_reason_set(NMManager * NMSettingsAutoconnectBlockedReason value, gboolean set); +NMDevice *nm_manager_get_best_device_for_connection(NMManager *self, + NMSettingsConnection *sett_conn, + NMConnection *connection, + gboolean for_user_request, + GHashTable *exclude_devices, + GError **error); + NMConfig *nm_manager_get_config(NMManager *self); #endif /* __NETWORKMANAGER_MANAGER_H__ */ diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index be6e1c19bc..e433d7ec4c 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -2153,6 +2153,7 @@ activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevic NMSettingsConnection *sett_conn; const char *sec_uuid = nm_setting_connection_get_secondary(s_con, i); NMActRequest *req; + NMConnection *nm_conn; sett_conn = nm_settings_get_connection_by_uuid(priv->settings, sec_uuid); if (!sett_conn) { @@ -2162,25 +2163,47 @@ activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevic success = FALSE; break; } + nm_conn = nm_settings_connection_get_connection(sett_conn); - if (!nm_connection_is_valid_secondary(nm_settings_connection_get_connection(sett_conn))) { + if (!nm_connection_is_valid_secondary(nm_conn)) { _LOGW(LOGD_DEVICE, - "secondary connection '%s (%s)' auto-activation failed: The connection is not a " - "valid secondary.", + "secondary connection '%s (%s)' auto-activation failed: The connection is neither a " + "valid VPN nor a Wireguard connection.", nm_settings_connection_get_id(sett_conn), sec_uuid); success = FALSE; break; } + if (!nm_connection_is_vpn(nm_conn)) { + /** + * For non-plugin VPN connections, re-validate the corresponding device. + * Wireguard, for example, needs its own (virtual) device. + * If it, however, gets activated as secondary device, + * 'device' points to the primary device that is currently starting. + * Bringing up a WG tunnel this way will fail. + */ + NMDevice *_device = NULL; + gs_free_error GError *local = NULL; + _device = + nm_manager_get_best_device_for_connection(priv->manager, sett_conn, nm_conn, TRUE, NULL, &local); + if (!_device) { + _LOGW(LOGD_DEVICE, + "No suitable device found for non-plugin VPN connection (%s).", + local->message); + success = FALSE; + break; + } + device = _device; + } req = nm_device_get_act_request(device); _LOGD(LOGD_DEVICE, - "activating secondary connection '%s (%s)' for base connection '%s (%s)'", + "activating secondary connection '%s (%s)' for base connection '%s (%s), real: %d, sw:%d, iface: %s'", nm_settings_connection_get_id(sett_conn), sec_uuid, nm_connection_get_id(connection), - nm_connection_get_uuid(connection)); + nm_connection_get_uuid(connection), nm_device_is_real(device), nm_device_is_software(device), nm_device_get_iface(device)); ac = nm_manager_activate_connection( priv->manager, sett_conn, @@ -2195,6 +2218,10 @@ activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevic if (ac) secondary_ac_list = g_slist_append(secondary_ac_list, g_object_ref(ac)); else { + _LOGW(LOGD_DEVICE, + "secondary connection '%s (%s)' auto-activation failed:", + nm_settings_connection_get_id(sett_conn), + sec_uuid); _LOGW(LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: (%d) %s", nm_settings_connection_get_id(sett_conn), diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 80a515e25f..d2049538af 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -2877,6 +2877,33 @@ nm_connection_is_type(NMConnection *connection, const char *type) return nm_streq0(type, nm_connection_get_connection_type(connection)); } +/** + * nm_connection_is_vpn: + * @connection: the #NMConnection + * + * Checks whether the given connection is VPN connection. + * This applies to a connection belonging to a VPN plugin, + * e.g., OpenVPN. + * + * Returns: %TRUE if the connection is a valid secondary. + * + * Since: 1.57 +*/ +gboolean +nm_connection_is_vpn(NMConnection *connection) +{ + const char *type; + + type = nm_connection_get_connection_type(connection); + if (type) + return nm_streq(type, NM_SETTING_VPN_SETTING_NAME); + + /* we have an incomplete (invalid) connection at hand. That can only + * happen during AddAndActivate. Determine whether it's VPN type based + * on the existence of a [vpn] section. */ + return !!nm_connection_get_setting_vpn(connection); +} + /** * nm_connection_is_valid_secondary: * @connection: the #NMConnection @@ -2887,7 +2914,7 @@ nm_connection_is_type(NMConnection *connection, const char *type) * * Returns: %TRUE if the connection is a valid secondary. * - * Since: 1.52 + * Since: 1.57 */ gboolean nm_connection_is_valid_secondary(NMConnection *connection) diff --git a/src/libnm-core-public/nm-connection.h b/src/libnm-core-public/nm-connection.h index 3b8c60b3e6..03d7be6c20 100644 --- a/src/libnm-core-public/nm-connection.h +++ b/src/libnm-core-public/nm-connection.h @@ -249,6 +249,7 @@ NMSettingVxlan *nm_connection_get_setting_vxlan(NMConnection *connection); NM_AVAILABLE_IN_1_52 gboolean nm_connection_is_valid_secondary(NMConnection *connection); +gboolean nm_connection_is_vpn(NMConnection *connection); G_END_DECLS