/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2005 - 2013 Red Hat, Inc. * Copyright (C) 2006 - 2008 Novell, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-vpn-connection.h" #include #include #include #include #include #include #include #include #include "NetworkManagerUtils.h" #include "dns/nm-dns-manager.h" #include "libnm-core-intern/nm-core-internal.h" #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-platform/nm-platform.h" #include "libnm-std-aux/unaligned.h" #include "nm-active-connection.h" #include "nm-config.h" #include "nm-dbus-manager.h" #include "nm-dispatcher.h" #include "nm-firewalld-manager.h" #include "nm-ip-config.h" #include "nm-l3-config-data.h" #include "nm-netns.h" #include "nm-pacrunner-manager.h" #include "nm-vpn-manager.h" #include "nm-vpn-plugin-info.h" #include "settings/nm-agent-manager.h" #include "settings/nm-settings-connection.h" /* FIXME(l3cfg): Check that we handle it correctly if the parent device is VRF type. */ /* FIXME(l3cfg): Proxy settings are no longer configured. That needs to be handled by NML3Cfg. */ /*****************************************************************************/ #define DBUS_DEFAULT_TIMEOUT_MSEC 10000 typedef enum { L3CD_TYPE_GW_EXTERN, L3CD_TYPE_STATIC, L3CD_TYPE_GENERIC, L3CD_TYPE_IP_4, L3CD_TYPE_IP_6, #define L3CD_TYPE_IP_X(IS_IPv4) ((IS_IPv4) ? L3CD_TYPE_IP_4 : L3CD_TYPE_IP_6) _L3CD_TYPE_NUM, } L3CDType; typedef enum { /* Only system secrets */ SECRETS_REQ_SYSTEM = 0, /* All existing secrets including agent secrets */ SECRETS_REQ_EXISTING = 1, /* New secrets required; ask an agent */ SECRETS_REQ_NEW = 2, /* Plugin requests secrets interactively */ SECRETS_REQ_INTERACTIVE = 3, /* Placeholder for bounds checking */ SECRETS_REQ_LAST } SecretsReq; /* Internal VPN states, private to NMVpnConnection */ typedef enum { STATE_UNKNOWN = 0, STATE_WAITING, STATE_PREPARE, STATE_NEED_AUTH, STATE_CONNECT, STATE_IP_CONFIG_GET, STATE_PRE_UP, STATE_ACTIVATED, STATE_DEACTIVATING, STATE_DISCONNECTED, STATE_FAILED, } VpnState; enum { INTERNAL_STATE_CHANGED, INTERNAL_RETRY_AFTER_FAILURE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; NM_GOBJECT_PROPERTIES_DEFINE(NMVpnConnection, PROP_VPN_STATE, PROP_BANNER, #define PROP_IP4_CONFIG 2000 #define PROP_IP6_CONFIG 2001 #define PROP_MASTER 2002 ); typedef struct { NMIPConfig *ip_config; NMIPAddr gw_internal; NMIPAddr gw_external; /* Whether this address family is enabled. If not, then we won't have a l3cd instance, * but the activation for this address family is still complete. */ bool enabled : 1; /* Whether this address family is ready. This means we received the IP configuration. * Usually this implies we also have a corresponding l3cd, but that might not be the * case if this address family is disabled. */ bool conf_ready : 1; } IPData; typedef struct { gboolean service_can_persist; gboolean connection_can_persist; NMSettingsConnectionCallId *secrets_id; SecretsReq secrets_idx; char *username; VpnState vpn_state; NMDispatcherCallId *dispatcher_id; NMActiveConnectionStateReason failure_reason; NMVpnServiceState service_state; GSource *start_timeout_source; NMVpnPluginInfo *plugin_info; NMNetns *netns; NML3Cfg *l3cfg_if; NML3CfgCommitTypeHandle *l3cfg_commit_type_if; NML3Cfg *l3cfg_dev; NML3CfgCommitTypeHandle *l3cfg_commit_type_dev; struct { GDBusConnection *connection; char *bus_name; char *owner; guint signal_id_vpn; guint signal_id_name_changed; bool name_owner_initialized : 1; } dbus; NMFirewalldManagerCallId *fw_call; union { const NML3ConfigData *const l3cds[_L3CD_TYPE_NUM]; const NML3ConfigData *l3cds_[_L3CD_TYPE_NUM]; }; /* This combines the l3cds of the VPN (basically, excluding l3cd_gw_extern which * is only about configuration for the parent device). This is used to configure * DNS. */ const NML3ConfigData *l3cd_combined; union { struct { IPData ip_data_6; IPData ip_data_4; }; IPData ip_data_x[2]; }; GSource *init_fail_on_idle_source; GSource *connect_timeout_source; GCancellable *main_cancellable; GVariant *connect_hash; char *banner; NMPacrunnerConfId *pacrunner_conf_id; int ifindex_if; int ifindex_dev; guint32 mtu; bool wait_for_pre_up_state : 1; bool dbus_service_started : 1; bool generic_config_received : 1; bool l3cds_changed : 1; } NMVpnConnectionPrivate; struct _NMVpnConnection { NMActiveConnection parent; NMVpnConnectionPrivate _priv; }; struct _NMVpnConnectionClass { NMActiveConnectionClass parent; }; G_DEFINE_TYPE(NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION) #define NM_VPN_CONNECTION_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMVpnConnection, NM_IS_VPN_CONNECTION, NMActiveConnection) /*****************************************************************************/ static const NMDBusInterfaceInfoExtended interface_info_vpn_connection; static const GDBusSignalInfo signal_info_vpn_state_changed; static NMSettingsConnection *_get_settings_connection(NMVpnConnection *self, gboolean allow_missing); static void _secrets_get(NMVpnConnection *self, SecretsReq secrets_idx, const char *const *hints); static guint32 get_route_table(NMVpnConnection *self, int addr_family, gboolean fallback_main); static void _set_vpn_state(NMVpnConnection *self, VpnState vpn_state, NMActiveConnectionStateReason reason, gboolean quitting); static void _l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMVpnConnection *self); /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_VPN #define _NMLOG_PREFIX_NAME "vpn" #define __NMLOG_prefix_buf_len 128 static const char * __LOG_create_prefix(char *buf, NMVpnConnection *self, NMSettingsConnection *con) { NMVpnConnectionPrivate *priv; const char *id; const char *iface; char buf1[100]; char buf2[100]; if (!self) return _NMLOG_PREFIX_NAME; priv = NM_VPN_CONNECTION_GET_PRIVATE(self); id = con ? nm_settings_connection_get_id(con) : NULL; iface = nm_vpn_connection_get_ip_iface(self, FALSE); g_snprintf(buf, __NMLOG_prefix_buf_len, "%s[" "%p" /*self*/ "%s%s" /*con-uuid*/ "%s%s%s%s" /*con-id*/ "%s" /*ifindex_if*/ "%s" /*ifindex_dev*/ "%s%s%s" /*iface*/ "]", _NMLOG_PREFIX_NAME, self, con ? "," : "--", con ? (nm_settings_connection_get_uuid(con) ?: "??") : "", con ? "," : "", NM_PRINT_FMT_QUOTED(id, "\"", id, "\"", con ? "??" : ""), priv->ifindex_if > 0 ? nm_sprintf_buf(buf1, ",if:%d", priv->ifindex_if) : "", priv->ifindex_dev > 0 ? nm_sprintf_buf(buf2, ",dev:%d", priv->ifindex_dev) : "", NM_PRINT_FMT_QUOTED(iface, ":(", iface, ")", "")); return buf; } #define _NMLOG(level, ...) \ G_STMT_START \ { \ const NMLogLevel _level = (level); \ NMSettingsConnection *_con = (self) ? _get_settings_connection(self, TRUE) : NULL; \ \ if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \ char __prefix[__NMLOG_prefix_buf_len]; \ \ _nm_log(_level, \ _NMLOG_DOMAIN, \ 0, \ (self) ? nm_vpn_connection_get_ip_iface(self, FALSE) : NULL, \ (_con) ? nm_settings_connection_get_uuid(_con) : NULL, \ "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ __LOG_create_prefix(__prefix, (self), _con) \ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ G_STMT_END /*****************************************************************************/ static NM_UTILS_LOOKUP_STR_DEFINE(_l3cd_type_to_string, L3CDType, NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), NM_UTILS_LOOKUP_STR_ITEM(L3CD_TYPE_GW_EXTERN, "gw-extern"), NM_UTILS_LOOKUP_STR_ITEM(L3CD_TYPE_STATIC, "static"), NM_UTILS_LOOKUP_STR_ITEM(L3CD_TYPE_GENERIC, "generic"), NM_UTILS_LOOKUP_STR_ITEM(L3CD_TYPE_IP_4, "ip-4"), NM_UTILS_LOOKUP_STR_ITEM(L3CD_TYPE_IP_6, "ip-6"), ); static NM_UTILS_LOOKUP_STR_DEFINE( _vpn_service_state_to_string, NMVpnServiceState, NM_UTILS_LOOKUP_DEFAULT(NULL), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_UNKNOWN, "unknown"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_INIT, "init"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_SHUTDOWN, "shutdown"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STARTING, "starting"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STARTED, "started"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STOPPING, "stopping"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_SERVICE_STATE_STOPPED, "stopped"), ); #define vpn_service_state_to_string_a(state) \ NM_UTILS_LOOKUP_STR_A(_vpn_service_state_to_string, state) static NM_UTILS_LOOKUP_STR_DEFINE(_vpn_state_to_string, VpnState, NM_UTILS_LOOKUP_DEFAULT(NULL), NM_UTILS_LOOKUP_STR_ITEM(STATE_UNKNOWN, "unknown"), NM_UTILS_LOOKUP_STR_ITEM(STATE_WAITING, "waiting"), NM_UTILS_LOOKUP_STR_ITEM(STATE_PREPARE, "prepare"), NM_UTILS_LOOKUP_STR_ITEM(STATE_NEED_AUTH, "need-auth"), NM_UTILS_LOOKUP_STR_ITEM(STATE_CONNECT, "connect"), NM_UTILS_LOOKUP_STR_ITEM(STATE_IP_CONFIG_GET, "ip-config-get"), NM_UTILS_LOOKUP_STR_ITEM(STATE_PRE_UP, "pre-up"), NM_UTILS_LOOKUP_STR_ITEM(STATE_ACTIVATED, "activated"), NM_UTILS_LOOKUP_STR_ITEM(STATE_DEACTIVATING, "deactivating"), NM_UTILS_LOOKUP_STR_ITEM(STATE_DISCONNECTED, "disconnected"), NM_UTILS_LOOKUP_STR_ITEM(STATE_FAILED, "failed"), ); #define vpn_state_to_string_a(state) NM_UTILS_LOOKUP_STR_A(_vpn_state_to_string, state) static NM_UTILS_LOOKUP_STR_DEFINE( _vpn_plugin_failure_to_string, NMVpnPluginFailure, NM_UTILS_LOOKUP_DEFAULT(NULL), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED, "login-failed"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED, "connect-failed"), NM_UTILS_LOOKUP_STR_ITEM(NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG, "bad-ip-config"), ); #define vpn_plugin_failure_to_string_a(failure) \ NM_UTILS_LOOKUP_STR_A(_vpn_plugin_failure_to_string, failure) static NMVpnConnectionState _state_to_nm_vpn_state(VpnState state) { switch (state) { case STATE_WAITING: case STATE_PREPARE: return NM_VPN_CONNECTION_STATE_PREPARE; case STATE_NEED_AUTH: return NM_VPN_CONNECTION_STATE_NEED_AUTH; case STATE_CONNECT: return NM_VPN_CONNECTION_STATE_CONNECT; case STATE_IP_CONFIG_GET: case STATE_PRE_UP: return NM_VPN_CONNECTION_STATE_IP_CONFIG_GET; case STATE_ACTIVATED: return NM_VPN_CONNECTION_STATE_ACTIVATED; case STATE_DEACTIVATING: { /* Map DEACTIVATING to ACTIVATED to preserve external API behavior, * since our API has no DEACTIVATING state of its own. Since this can * take some time, and the VPN isn't actually disconnected until it * hits the DISCONNECTED state, to clients it should still appear * connected. */ return NM_VPN_CONNECTION_STATE_ACTIVATED; } case STATE_DISCONNECTED: return NM_VPN_CONNECTION_STATE_DISCONNECTED; case STATE_FAILED: return NM_VPN_CONNECTION_STATE_FAILED; default: return NM_VPN_CONNECTION_STATE_UNKNOWN; } } static NMActiveConnectionState _state_to_ac_state(VpnState vpn_state) { /* Set the NMActiveConnection state based on VPN state */ switch (vpn_state) { case STATE_WAITING: case STATE_PREPARE: case STATE_NEED_AUTH: case STATE_CONNECT: case STATE_IP_CONFIG_GET: case STATE_PRE_UP: return NM_ACTIVE_CONNECTION_STATE_ACTIVATING; case STATE_ACTIVATED: return NM_ACTIVE_CONNECTION_STATE_ACTIVATED; case STATE_DEACTIVATING: return NM_ACTIVE_CONNECTION_STATE_DEACTIVATING; case STATE_DISCONNECTED: case STATE_FAILED: return NM_ACTIVE_CONNECTION_STATE_DEACTIVATED; default: break; } return NM_ACTIVE_CONNECTION_STATE_UNKNOWN; } /*****************************************************************************/ static NMSettingsConnection * _get_settings_connection(NMVpnConnection *self, gboolean allow_missing) { NMSettingsConnection *con; /* Currently, we operate on the assumption, that the settings-connection * never changes after it is set (though initially, it might be unset). * Later we might want to change that, but then we need fixes here too. */ con = _nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(self)); if (!con && !allow_missing) g_return_val_if_reached(NULL); return con; } static NMConnection * _get_applied_connection(NMVpnConnection *connection) { NMConnection *con; con = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(connection)); g_return_val_if_fail(con, NULL); return con; } /*****************************************************************************/ static void _dbus_connection_call(NMVpnConnection *self, const char *method_name, GVariant *parameters, const GVariantType *reply_type, GAsyncReadyCallback callback) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); g_return_if_fail(priv->dbus.bus_name); _LOGT("dbus: call %s on %s", method_name, priv->dbus.bus_name); g_dbus_connection_call(priv->dbus.connection, priv->dbus.bus_name, NM_VPN_DBUS_PLUGIN_PATH, NM_VPN_DBUS_PLUGIN_INTERFACE, method_name, parameters, reply_type, G_DBUS_CALL_FLAGS_NONE, DBUS_DEFAULT_TIMEOUT_MSEC, priv->main_cancellable, callback, self); } static NML3ConfigMergeFlags _l3cfg_get_merge_flags(NMVpnConnection *self, L3CDType l3cd_type) { NMConnection *applied; NMSettingIPConfig *s_ip4; NMSettingIPConfig *s_ip6; NML3ConfigMergeFlags merge_flags; merge_flags = NM_L3_CONFIG_MERGE_FLAGS_NONE; if (l3cd_type == L3CD_TYPE_IP_4) { applied = _get_applied_connection(self); s_ip4 = applied ? nm_connection_get_setting_ip_config(applied, AF_INET) : NULL; if (s_ip4 && nm_setting_ip_config_get_ignore_auto_routes(s_ip4)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES; if (s_ip4 && nm_setting_ip_config_get_never_default(s_ip4)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES; if (s_ip4 && nm_setting_ip_config_get_ignore_auto_dns(s_ip4)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DNS; } else if (l3cd_type == L3CD_TYPE_IP_6) { applied = _get_applied_connection(self); s_ip6 = applied ? nm_connection_get_setting_ip_config(applied, AF_INET6) : NULL; if (s_ip6 && nm_setting_ip_config_get_ignore_auto_routes(s_ip6)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES; if (s_ip6 && nm_setting_ip_config_get_never_default(s_ip6)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES; if (s_ip6 && nm_setting_ip_config_get_ignore_auto_dns(s_ip6)) merge_flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DNS; } return merge_flags; } static NML3ConfigData * _l3cfg_l3cd_new(NMVpnConnection *self, int ifindex) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); return nm_l3_config_data_new(nm_netns_get_multi_idx(priv->netns), ifindex, NM_IP_CONFIG_SOURCE_VPN); } /*****************************************************************************/ guint32 nm_vpn_connection_get_ip4_internal_gateway(NMVpnConnection *self) { g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); return NM_VPN_CONNECTION_GET_PRIVATE(self)->ip_data_4.gw_internal.addr4; } struct in6_addr * nm_vpn_connection_get_ip6_internal_gateway(NMVpnConnection *self) { g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); return &NM_VPN_CONNECTION_GET_PRIVATE(self)->ip_data_6.gw_internal.addr6; } NMVpnConnectionState nm_vpn_connection_get_vpn_state(NMVpnConnection *self) { g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NM_VPN_CONNECTION_STATE_UNKNOWN); return _state_to_nm_vpn_state(NM_VPN_CONNECTION_GET_PRIVATE(self)->vpn_state); } const char * nm_vpn_connection_get_banner(NMVpnConnection *self) { g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); return NM_VPN_CONNECTION_GET_PRIVATE(self)->banner; } const NML3ConfigData * nm_vpn_connection_get_l3cd(NMVpnConnection *self) { NMVpnConnectionPrivate *priv; g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->l3cds_changed) { nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; L3CDType l3cd_type; int ifindex; priv->l3cds_changed = FALSE; ifindex = nm_vpn_connection_get_ip_ifindex(self, TRUE); if (ifindex > 0) { const int default_dns_priority_x[2] = { NM_DNS_PRIORITY_DEFAULT_VPN, NM_DNS_PRIORITY_DEFAULT_VPN, }; guint32 default_route_table_x[2]; guint32 default_route_metric_x[2]; for (l3cd_type = 0; l3cd_type < _L3CD_TYPE_NUM; l3cd_type++) { if (l3cd_type == L3CD_TYPE_GW_EXTERN) continue; if (!priv->l3cds[l3cd_type]) continue; if (!l3cd) { default_route_table_x[0] = get_route_table(self, AF_INET6, TRUE); default_route_table_x[1] = get_route_table(self, AF_INET, TRUE); default_route_metric_x[0] = nm_vpn_connection_get_ip_route_metric(self, AF_INET6); default_route_metric_x[1] = nm_vpn_connection_get_ip_route_metric(self, AF_INET); l3cd = _l3cfg_l3cd_new(self, ifindex); } nm_l3_config_data_merge(l3cd, priv->l3cds[l3cd_type], _l3cfg_get_merge_flags(self, l3cd_type), default_route_table_x, default_route_metric_x, NULL, default_dns_priority_x, NULL, NULL); } } nm_l3_config_data_reset(&priv->l3cd_combined, l3cd); } return priv->l3cd_combined; } static int _get_ifindex_for_device(NMVpnConnection *self) { NMDevice *parent_dev; int ifindex; nm_assert(NM_IS_VPN_CONNECTION(self)); parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); if (!parent_dev) return 0; ifindex = nm_device_get_ip_ifindex(parent_dev); if (ifindex <= 0) return 0; return ifindex; } const char * nm_vpn_connection_get_ip_iface(NMVpnConnection *self, gboolean fallback_device) { NMVpnConnectionPrivate *priv; g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), NULL); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->l3cfg_if) return nm_l3cfg_get_ifname(priv->l3cfg_if, TRUE); if (fallback_device && priv->l3cfg_dev) return nm_l3cfg_get_ifname(priv->l3cfg_dev, TRUE); return NULL; } int nm_vpn_connection_get_ip_ifindex(NMVpnConnection *self, gboolean fallback_device) { NMVpnConnectionPrivate *priv; g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), 0); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->ifindex_if > 0) return priv->ifindex_if; if (fallback_device && priv->ifindex_dev > 0) return priv->ifindex_dev; return 0; } static guint32 _get_vpn_timeout(NMVpnConnection *self) { guint32 timeout; NMSettingVpn *s_vpn; s_vpn = nm_connection_get_setting_vpn(_get_applied_connection(self)); g_return_val_if_fail(s_vpn, 60); /* Timeout waiting for IP config signal from VPN service * It is a configured value or 60 seconds */ timeout = nm_setting_vpn_get_timeout(s_vpn); if (timeout == 0) { timeout = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, NM_CON_DEFAULT("vpn.timeout"), NULL, 1, G_MAXUINT32, 60); } return timeout; } /*****************************************************************************/ static gboolean _l3cfg_l3cd_set(NMVpnConnection *self, L3CDType l3cd_type, const NML3ConfigData *l3cd) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (nm_l3_config_data_equal(l3cd, priv->l3cds[l3cd_type])) return FALSE; if (_LOGT_ENABLED()) { if (l3cd) { char s_name[150]; /* Seal hear, so that we don't log about an unsealed instance. * nm_l3_config_data_reset() anyway seals the instance too. */ nm_l3_config_data_seal(l3cd); _LOGT("l3cd[%s]: set " NM_HASH_OBFUSCATE_PTR_FMT, _l3cd_type_to_string(l3cd_type), NM_HASH_OBFUSCATE_PTR(l3cd)); nm_l3_config_data_log( l3cd, nm_sprintf_buf(s_name, "l3cd[%s]", _l3cd_type_to_string(l3cd_type)), "vpn-config: ", LOGL_TRACE, _NMLOG_DOMAIN); } else _LOGT("l3cd[%s]: unset", _l3cd_type_to_string(l3cd_type)); } nm_l3_config_data_reset(&priv->l3cds_[l3cd_type], l3cd); priv->l3cds_changed = TRUE; return TRUE; } static void _l3cfg_l3cd_update(NMVpnConnection *self, L3CDType l3cd_type) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); NML3Cfg *l3cfg; const NML3ConfigData *const *p_l3cd; if (NM_IN_SET(l3cd_type, L3CD_TYPE_IP_4, L3CD_TYPE_IP_6, L3CD_TYPE_GENERIC, L3CD_TYPE_STATIC)) { l3cfg = priv->l3cfg_if; if (!l3cfg) { l3cfg = priv->l3cfg_dev; } } else { nm_assert(NM_IN_SET(l3cd_type, L3CD_TYPE_GW_EXTERN)); l3cfg = priv->l3cfg_dev; } p_l3cd = &priv->l3cds[l3cd_type]; if (!l3cfg) return; if (!*p_l3cd) { if (!nm_l3cfg_remove_config_all(l3cfg, p_l3cd)) return; _LOGT("l3cd[%s]: remove-config " NM_HASH_OBFUSCATE_PTR_FMT, _l3cd_type_to_string(l3cd_type), NM_HASH_OBFUSCATE_PTR(*p_l3cd)); goto handle_changed; } if (!nm_l3cfg_add_config(l3cfg, p_l3cd, TRUE, *p_l3cd, NM_L3CFG_CONFIG_PRIORITY_VPN, get_route_table(self, AF_INET, TRUE), get_route_table(self, AF_INET6, TRUE), nm_vpn_connection_get_ip_route_metric(self, AF_INET), nm_vpn_connection_get_ip_route_metric(self, AF_INET6), 0, 0, NM_DNS_PRIORITY_DEFAULT_VPN, NM_DNS_PRIORITY_DEFAULT_VPN, NM_L3_ACD_DEFEND_TYPE_ONCE, 0, NM_L3CFG_CONFIG_FLAGS_NONE, _l3cfg_get_merge_flags(self, l3cd_type))) return; _LOGT("l3cd[%s]: add-config " NM_HASH_OBFUSCATE_PTR_FMT, _l3cd_type_to_string(l3cd_type), NM_HASH_OBFUSCATE_PTR(*p_l3cd)); handle_changed: nm_l3cfg_commit_on_idle_schedule(l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO); } static void _l3cfg_l3cd_update_all(NMVpnConnection *self) { L3CDType l3cd_type; for (l3cd_type = 0; l3cd_type < _L3CD_TYPE_NUM; l3cd_type++) _l3cfg_l3cd_update(self, l3cd_type); } static void _l3cfg_l3cd_clear_all(NMVpnConnection *self) { L3CDType l3cd_type; for (l3cd_type = 0; l3cd_type < _L3CD_TYPE_NUM; l3cd_type++) _l3cfg_l3cd_set(self, l3cd_type, NULL); _l3cfg_l3cd_update_all(self); } static void _l3cfg_clear(NMVpnConnection *self, NML3Cfg *l3cfg) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); L3CDType l3cd_type; gboolean changed = FALSE; if (!l3cfg) return; g_signal_handlers_disconnect_by_func(l3cfg, G_CALLBACK(_l3cfg_notify_cb), self); for (l3cd_type = 0; l3cd_type < _L3CD_TYPE_NUM; l3cd_type++) { if (nm_l3cfg_remove_config_all(l3cfg, &priv->l3cds[l3cd_type])) changed = TRUE; } if (changed) nm_l3cfg_commit_on_idle_schedule(l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO); } /*****************************************************************************/ static void cancel_get_secrets(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->secrets_id) { _LOGT("secrets: cancel request"); nm_settings_connection_cancel_secrets(_get_settings_connection(self, FALSE), priv->secrets_id); nm_assert(!priv->secrets_id); } } static void _l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) { if (l3cfg == (priv->l3cfg_if ?: priv->l3cfg_dev) && priv->wait_for_pre_up_state && priv->vpn_state < STATE_PRE_UP) _set_vpn_state(self, STATE_PRE_UP, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } } static gboolean _set_ip_ifindex(NMVpnConnection *self, int ifindex, gboolean is_if) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); int *p_ifindex = is_if ? &priv->ifindex_if : &priv->ifindex_dev; NML3Cfg **p_l3cfg = is_if ? &priv->l3cfg_if : &priv->l3cfg_dev; NML3CfgCommitTypeHandle **p_l3cfg_commit_type = is_if ? &priv->l3cfg_commit_type_if : &priv->l3cfg_commit_type_dev; gs_unref_object NML3Cfg *l3cfg_old = NULL; if (ifindex < 0) ifindex = nm_assert_unreachable_val(0); if (*p_ifindex == ifindex) return FALSE; _LOGD("set ip-ifindex-%s %d", is_if ? "if" : "dev", ifindex); *p_ifindex = ifindex; l3cfg_old = g_steal_pointer(p_l3cfg); nm_l3cfg_commit_type_clear(l3cfg_old, p_l3cfg_commit_type); _l3cfg_clear(self, l3cfg_old); if (ifindex > 0) { *p_l3cfg = nm_netns_l3cfg_acquire(priv->netns, ifindex); g_signal_connect(*p_l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self); *p_l3cfg_commit_type = nm_l3cfg_commit_type_register(*p_l3cfg, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL, "vpn"); } return TRUE; } static void disconnect_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMVpnConnection *self; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = NM_VPN_CONNECTION(user_data); _LOGT("dbus: disconnected%s%s", NM_PRINT_FMT_QUOTED2(error, " failed: ", error->message, " with success")); } static void fw_call_cleanup(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->fw_call) { nm_firewalld_manager_cancel_call(priv->fw_call); g_warn_if_fail(!priv->fw_call); priv->fw_call = NULL; } } static void vpn_cleanup(NMVpnConnection *self, NMDevice *parent_dev) { const char *iface; /* Remove zone from firewall */ iface = nm_vpn_connection_get_ip_iface(self, FALSE); if (iface) { nm_firewalld_manager_remove_from_zone(nm_firewalld_manager_get(), iface, NULL, NULL, NULL); } /* Cancel pending firewall call */ fw_call_cleanup(self); _l3cfg_l3cd_clear_all(self); } static void dispatcher_pre_down_done(NMDispatcherCallId *call_id, gpointer user_data) { NMVpnConnection *self = NM_VPN_CONNECTION(user_data); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_assert(call_id); nm_assert(priv->dispatcher_id == call_id); priv->dispatcher_id = NULL; _set_vpn_state(self, STATE_DISCONNECTED, NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED, FALSE); } static void dispatcher_pre_up_done(NMDispatcherCallId *call_id, gpointer user_data) { NMVpnConnection *self = NM_VPN_CONNECTION(user_data); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_assert(call_id); nm_assert(priv->dispatcher_id == call_id); priv->dispatcher_id = NULL; _set_vpn_state(self, STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } static void dispatcher_cleanup(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->dispatcher_id) nm_dispatcher_call_cancel(g_steal_pointer(&priv->dispatcher_id)); } static void _set_vpn_state(NMVpnConnection *self, VpnState vpn_state, NMActiveConnectionStateReason reason, gboolean quitting) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); VpnState old_vpn_state; NMVpnConnectionState new_external_state; NMVpnConnectionState old_external_state; NMDevice *parent_dev = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)); NMConnection *applied; if (vpn_state == priv->vpn_state) return; old_vpn_state = priv->vpn_state; priv->vpn_state = vpn_state; _LOGD("set state: %s (was %s)", vpn_state_to_string_a(priv->vpn_state), vpn_state_to_string_a(old_vpn_state)); /* The device gets destroyed by active connection when it enters * the deactivated state, so we need to ref it for usage below. */ nm_g_object_ref(parent_dev); /* Update active connection base class state */ nm_active_connection_set_state(NM_ACTIVE_CONNECTION(self), _state_to_ac_state(vpn_state), reason); /* Clear any in-progress secrets request */ cancel_get_secrets(self); dispatcher_cleanup(self); /* The connection gets destroyed by the VPN manager when it enters the * disconnected/failed state, but we need to keep it around for a bit * to send out signals and handle the dispatcher. So ref it. */ g_object_ref(self); old_external_state = _state_to_nm_vpn_state(old_vpn_state); new_external_state = _state_to_nm_vpn_state(priv->vpn_state); if (new_external_state != old_external_state) { nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self), &interface_info_vpn_connection, &signal_info_vpn_state_changed, "(uu)", (guint32) new_external_state, (guint32) reason); g_signal_emit(self, signals[INTERNAL_STATE_CHANGED], 0, (guint) new_external_state, (guint) old_external_state, (guint) reason); _notify(self, PROP_VPN_STATE); } switch (vpn_state) { case STATE_NEED_AUTH: /* Do nothing; not part of 'default' because we don't want to touch * priv->secrets_req as NEED_AUTH is re-entered during interactive * secrets. */ break; case STATE_PRE_UP: if (!nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_PRE_UP, _get_settings_connection(self, FALSE), _get_applied_connection(self), parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), nm_vpn_connection_get_l3cd(self), dispatcher_pre_up_done, self, &priv->dispatcher_id)) { /* Just proceed on errors */ dispatcher_pre_up_done(0, self); } break; case STATE_ACTIVATED: nm_clear_g_source_inst(&priv->start_timeout_source); applied = _get_applied_connection(self); /* Secrets no longer needed now that we're connected */ nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(self)); /* Let dispatcher scripts know we're up and running */ nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_UP, _get_settings_connection(self, FALSE), applied, parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), nm_vpn_connection_get_l3cd(self), NULL, NULL, NULL); break; case STATE_DEACTIVATING: applied = _get_applied_connection(self); if (quitting) { nm_dispatcher_call_vpn_sync(NM_DISPATCHER_ACTION_VPN_PRE_DOWN, _get_settings_connection(self, FALSE), applied, parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), nm_vpn_connection_get_l3cd(self)); } else { if (!nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_PRE_DOWN, _get_settings_connection(self, FALSE), applied, parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), nm_vpn_connection_get_l3cd(self), dispatcher_pre_down_done, self, &priv->dispatcher_id)) { /* Just proceed on errors */ dispatcher_pre_down_done(0, self); } } nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); break; case STATE_FAILED: case STATE_DISCONNECTED: if (old_vpn_state >= STATE_ACTIVATED && old_vpn_state <= STATE_DEACTIVATING) { /* Let dispatcher scripts know we're about to go down */ if (quitting) { nm_dispatcher_call_vpn_sync(NM_DISPATCHER_ACTION_VPN_DOWN, _get_settings_connection(self, FALSE), _get_applied_connection(self), parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), NULL); } else { nm_dispatcher_call_vpn(NM_DISPATCHER_ACTION_VPN_DOWN, _get_settings_connection(self, FALSE), _get_applied_connection(self), parent_dev, nm_vpn_connection_get_ip_iface(self, FALSE), NULL, NULL, NULL, NULL); } } if (priv->dbus.bus_name) _dbus_connection_call(self, "Disconnect", NULL, G_VARIANT_TYPE("()"), disconnect_cb); vpn_cleanup(self, parent_dev); /* fall-through */ default: priv->secrets_idx = SECRETS_REQ_SYSTEM; break; } g_object_unref(self); if (parent_dev) g_object_unref(parent_dev); } static gboolean _service_and_connection_can_persist(NMVpnConnection *self) { return NM_VPN_CONNECTION_GET_PRIVATE(self)->connection_can_persist && NM_VPN_CONNECTION_GET_PRIVATE(self)->service_can_persist; } static gboolean _connection_only_can_persist(NMVpnConnection *self) { return NM_VPN_CONNECTION_GET_PRIVATE(self)->connection_can_persist && !NM_VPN_CONNECTION_GET_PRIVATE(self)->service_can_persist; } static void device_state_changed(NMActiveConnection *active, NMDevice *device, NMDeviceState new_state, NMDeviceState old_state) { if (_service_and_connection_can_persist(NM_VPN_CONNECTION(active))) { if (new_state <= NM_DEVICE_STATE_DISCONNECTED || new_state == NM_DEVICE_STATE_FAILED) { nm_active_connection_set_device(active, NULL); } return; } if (new_state <= NM_DEVICE_STATE_DISCONNECTED) { _set_vpn_state(NM_VPN_CONNECTION(active), STATE_DISCONNECTED, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, FALSE); } else if (new_state == NM_DEVICE_STATE_FAILED) { _set_vpn_state(NM_VPN_CONNECTION(active), STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, FALSE); } /* FIXME: map device DEACTIVATING state to VPN DEACTIVATING state and * block device deactivation on VPN deactivation. */ } static gboolean _parent_device_l3cd_add_gateway_route(NML3ConfigData *l3cd, int addr_family, NMDevice *parent_device, const NMIPAddr *vpn_gw, NMPlatform *platform) { const int IS_IPv4 = NM_IS_IPv4(addr_family); NMIPAddr parent_gw = NM_IP_ADDR_INIT; gboolean has_parent_gw = FALSE; nm_auto_nmpobj const NMPObject *route_resolved = NULL; int ifindex; NMPlatformIPXRoute route; int r; nm_assert(NM_IS_L3_CONFIG_DATA(l3cd)); g_return_val_if_fail(vpn_gw, FALSE); if (nm_ip_addr_is_null(addr_family, vpn_gw)) return FALSE; ifindex = nm_l3_config_data_get_ifindex(l3cd); nm_assert(ifindex > 0); if (parent_device && ifindex != nm_device_get_ip_ifindex(parent_device)) parent_device = 0; /* Ask kernel how to reach @vpn_gw. We can only inject the route in * @parent_device, so whatever we resolve, it can only be on @ifindex. */ r = nm_platform_ip_route_get(platform, addr_family, vpn_gw, ifindex, (NMPObject **) &route_resolved); if (r >= 0) { const NMPlatformIPXRoute *rx = NMP_OBJECT_CAST_IPX_ROUTE(route_resolved); const NMPObject *obj; if (rx->rx.ifindex == ifindex && nm_platform_route_table_is_main(rx->rx.table_coerced)) { gconstpointer gw = nm_platform_ip_route_get_gateway(addr_family, &rx->rx); /* `ip route get` always resolves the route, even if the destination is unreachable. * In which case, it pretends the destination is directly reachable. * * So, only accept direct routes if @vpn_gw is a private network * or if the parent device also has a direct default route */ if (!nm_ip_addr_is_null(addr_family, gw)) { nm_ip_addr_set(addr_family, &parent_gw, gw); has_parent_gw = TRUE; } else if (nm_utils_ip_is_site_local(addr_family, vpn_gw)) has_parent_gw = TRUE; else if ((obj = nm_device_get_best_default_route(parent_device, addr_family)) && nm_ip_addr_is_null( addr_family, nm_platform_ip_route_get_gateway(addr_family, NMP_OBJECT_CAST_IP_ROUTE(obj)))) has_parent_gw = TRUE; } } if (!has_parent_gw) return FALSE; if (IS_IPv4) { route.r4 = (NMPlatformIP4Route){ .ifindex = ifindex, .network = vpn_gw->addr4, .plen = 32, .gateway = parent_gw.addr4, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .metric_any = TRUE, .table_any = TRUE, }; } else { route.r6 = (NMPlatformIP6Route){ .ifindex = ifindex, .network = vpn_gw->addr6, .plen = 128, .gateway = parent_gw.addr6, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .metric_any = TRUE, .table_any = TRUE, }; } nm_l3_config_data_add_route(l3cd, addr_family, NULL, &route.rx); if (!nm_ip_addr_is_null(addr_family, &parent_gw)) { /* Ensure there's a route to the parent device's gateway through the * parent device, since if the VPN claims the default route and the VPN * routes include a subnet that matches the parent device's subnet, * the parent device's gateway would get routed through the VPN and fail. */ if (IS_IPv4) { route.r4 = (NMPlatformIP4Route){ .network = parent_gw.addr4, .plen = 32, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .metric_any = TRUE, .table_any = TRUE, }; } else { route.r6 = (NMPlatformIP6Route){ .network = parent_gw.addr6, .plen = 128, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .metric_any = TRUE, .table_any = TRUE, }; } nm_l3_config_data_add_route(l3cd, addr_family, NULL, &route.rx); } return TRUE; } static gboolean _l3cfg_l3cd_gw_extern_update(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; int ifindex; gboolean changed; int IS_IPv4; ifindex = priv->ifindex_dev; if (ifindex <= 0) goto set; l3cd = _l3cfg_l3cd_new(self, ifindex); changed = FALSE; for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { if (_parent_device_l3cd_add_gateway_route( l3cd, IS_IPv4 ? AF_INET : AF_INET6, nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self)), &priv->ip_data_x[IS_IPv4].gw_external, nm_netns_get_platform(priv->netns))) changed = TRUE; } if (!changed) nm_clear_pointer(&l3cd, nm_l3_config_data_unref); set: if (!_l3cfg_l3cd_set(self, L3CD_TYPE_GW_EXTERN, l3cd)) return FALSE; return TRUE; } const char * nm_vpn_connection_get_service(NMVpnConnection *self) { NMSettingVpn *s_vpn; s_vpn = nm_connection_get_setting_vpn(_get_applied_connection(self)); return nm_setting_vpn_get_service_type(s_vpn); } static void _apply_config(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); _LOGT("apply-config"); if (priv->ifindex_if > 0) { nm_platform_link_change_flags(nm_netns_get_platform(priv->netns), priv->ifindex_if, IFF_UP, TRUE); } if (priv->ifindex_dev > 0) { nm_platform_link_change_flags(nm_netns_get_platform(priv->netns), priv->ifindex_dev, IFF_UP, TRUE); } if (priv->ifindex_if > 0 && priv->ifindex_if != priv->ifindex_dev) { if (priv->mtu && priv->mtu != nm_platform_link_get_mtu(nm_netns_get_platform(priv->netns), priv->ifindex_if)) nm_platform_link_set_mtu(nm_netns_get_platform(priv->netns), priv->ifindex_if, priv->mtu); } priv->wait_for_pre_up_state = TRUE; _l3cfg_l3cd_update_all(self); } static void fw_change_zone_cb(NMFirewalldManager *firewalld_manager, NMFirewalldManagerCallId *call_id, GError *error, gpointer user_data) { NMVpnConnection *self = user_data; NMVpnConnectionPrivate *priv; g_return_if_fail(NM_IS_VPN_CONNECTION(self)); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); g_return_if_fail(priv->fw_call == call_id); priv->fw_call = NULL; if (nm_utils_error_is_cancelled(error)) return; _apply_config(self); } static void _check_complete(NMVpnConnection *self, gboolean success) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; NMConnection *connection; NMSettingConnection *s_con; const char *zone; const char *iface; if (priv->vpn_state < STATE_IP_CONFIG_GET || priv->vpn_state > STATE_ACTIVATED) return; if (success) { if (!priv->generic_config_received || (priv->ip_data_4.enabled && !priv->l3cds[L3CD_TYPE_IP_4]) || (priv->ip_data_6.enabled && !priv->l3cds[L3CD_TYPE_IP_6])) { /* Need to wait more config. */ return; } } nm_clear_g_source_inst(&priv->connect_timeout_source); if (!success) { _LOGW("did not receive valid IP config information"); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, FALSE); return; } connection = _get_applied_connection(self); l3cd = nm_l3_config_data_new_from_connection(nm_netns_get_multi_idx(priv->netns), nm_vpn_connection_get_ip_ifindex(self, TRUE), connection); _l3cfg_l3cd_set(self, L3CD_TYPE_STATIC, l3cd); _l3cfg_l3cd_gw_extern_update(self); iface = nm_vpn_connection_get_ip_iface(self, FALSE); /* Add the tunnel interface to the specified firewall zone */ if (iface) { s_con = nm_connection_get_setting_connection(connection); zone = nm_setting_connection_get_zone(s_con); fw_call_cleanup(self); priv->fw_call = nm_firewalld_manager_add_or_change_zone(nm_firewalld_manager_get(), iface, zone, FALSE, fw_change_zone_cb, self); return; } _apply_config(self); } static gboolean _vardict_to_addr(int addr_family, GVariant *dict, const char *key, gpointer dst) { guint32 u32; if (!NM_IS_IPv4(addr_family)) { gs_unref_variant GVariant *v = NULL; if (g_variant_lookup(dict, key, "@ay", &v)) { if (nm_ip_addr_set_from_variant(AF_INET6, dst, v, NULL)) return TRUE; } nm_ip_addr_set(AF_INET6, dst, &nm_ip_addr_zero.addr6); return FALSE; } /* The way we encode IPv4 addresses is not endianness safe. It works well enough * on the same host and as we know that the VPN plugin sends the address in the * same endianness that we expect. * * But we read a u32 (natively), and that happens to be already in the right * endianness to be used directly as IPv4 address. */ if (g_variant_lookup(dict, key, "u", &u32)) { unaligned_write_ne32(dst, u32); return TRUE; } unaligned_write_ne32(dst, 0); return FALSE; } guint32 nm_vpn_connection_get_ip_route_metric(NMVpnConnection *self, int addr_family) { gint64 route_metric = -1; NMConnection *applied; applied = _get_applied_connection(self); if (!applied) g_return_val_if_reached(NM_VPN_ROUTE_METRIC_DEFAULT); route_metric = nm_setting_ip_config_get_route_metric( nm_connection_get_setting_ip_config(applied, addr_family)); return (route_metric >= 0) ? route_metric : NM_VPN_ROUTE_METRIC_DEFAULT; } static guint32 get_route_table(NMVpnConnection *self, int addr_family, gboolean fallback_main) { NMConnection *connection; NMSettingIPConfig *s_ip; guint32 route_table = 0; nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); connection = _get_applied_connection(self); if (connection) { s_ip = nm_connection_get_setting_ip_config(connection, addr_family); if (s_ip) route_table = nm_setting_ip_config_get_route_table(s_ip); } return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0); } static gboolean connect_timeout_cb(gpointer user_data) { NMVpnConnection *self = NM_VPN_CONNECTION(user_data); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_clear_g_source_inst(&priv->connect_timeout_source); /* Cancel activation if it's taken too long */ if (priv->vpn_state == STATE_CONNECT || priv->vpn_state == STATE_IP_CONFIG_GET) { _LOGW("connect timeout exceeded"); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, FALSE); } return G_SOURCE_CONTINUE; } static void connect_success(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); priv->connect_timeout_source = nm_g_timeout_add_seconds_source(_get_vpn_timeout(self), connect_timeout_cb, self); nm_clear_pointer(&priv->connect_hash, g_variant_unref); } static void connect_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMVpnConnection *self; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = NM_VPN_CONNECTION(user_data); if (error) { g_dbus_error_strip_remote_error(error); _LOGW("failed to connect: '%s'", error->message); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); } else connect_success(self); } static void connect_interactive_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMVpnConnection *self; NMVpnConnectionPrivate *priv; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = NM_VPN_CONNECTION(user_data); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (g_error_matches(error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_INTERACTIVE_NOT_SUPPORTED) && priv->dbus.bus_name) { _LOGD("connect: falling back to non-interactive connect"); _dbus_connection_call(self, "Connect", g_variant_new("(@a{sa{sv}})", priv->connect_hash), G_VARIANT_TYPE("()"), connect_cb); return; } if (error) { _LOGW("connect: failed to connect interactively: '%s'", error->message); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); return; } _LOGD("connect: success from ConnectInteractive"); connect_success(self); } /* Add a username to a hashed connection */ static GVariant * _hash_with_username(NMConnection *connection, const char *username) { gs_unref_object NMConnection *dup = NULL; NMSettingVpn *s_vpn; /* Shortcut if we weren't given a username or if there already was one in * the VPN setting; don't bother duplicating the connection and everything. */ s_vpn = nm_connection_get_setting_vpn(connection); g_return_val_if_fail(s_vpn, NULL); if (!username || nm_setting_vpn_get_user_name(s_vpn)) return nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); dup = nm_simple_connection_new_clone(connection); nm_assert(dup); s_vpn = nm_connection_get_setting_vpn(dup); g_return_val_if_fail(s_vpn, NULL); g_object_set(s_vpn, NM_SETTING_VPN_USER_NAME, username, NULL); return nm_connection_to_dbus(dup, NM_CONNECTION_SERIALIZE_ALL); } static void really_activate(NMVpnConnection *self, const char *username) { NMVpnConnectionPrivate *priv; GVariantBuilder details; g_return_if_fail(NM_IS_VPN_CONNECTION(self)); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); g_return_if_fail(priv->vpn_state == STATE_NEED_AUTH); nm_clear_pointer(&priv->connect_hash, g_variant_unref); priv->connect_hash = _hash_with_username(_get_applied_connection(self), username); g_variant_ref_sink(priv->connect_hash); if (!priv->dbus.bus_name) { _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED, FALSE); return; } /* If at least one agent doesn't support VPN hints, then we can't use * ConnectInteractive(), because that agent won't be able to pass hints * from the VPN plugin's interactive secrets requests to the VPN authentication * dialog and we won't get the secrets we need. In this case fall back to * the old Connect() call. */ if (nm_agent_manager_all_agents_have_capability( nm_agent_manager_get(), nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(self)), NM_SECRET_AGENT_CAPABILITY_VPN_HINTS)) { _LOGD("connect: allowing interactive secrets as all agents have that capability"); g_variant_builder_init(&details, G_VARIANT_TYPE_VARDICT); _dbus_connection_call(self, "ConnectInteractive", g_variant_new("(@a{sa{sv}}a{sv})", priv->connect_hash, &details), G_VARIANT_TYPE("()"), connect_interactive_cb); } else { _LOGD( "connect: calling old Connect function as not all agents support interactive secrets"); _dbus_connection_call(self, "Connect", g_variant_new("(@a{sa{sv}})", priv->connect_hash), G_VARIANT_TYPE("()"), connect_cb); } _set_vpn_state(self, STATE_CONNECT, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } static void _dbus_signal_failure_cb(NMVpnConnection *self, guint32 reason) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); _LOGW("dbus: failure: %s (%d)", vpn_plugin_failure_to_string_a(reason), reason); switch (reason) { case NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED: priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED; break; case NM_VPN_PLUGIN_FAILURE_BAD_IP_CONFIG: priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID; break; default: priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN; break; } } static void _dbus_signal_state_changed_cb(NMVpnConnection *self, guint32 new_service_state) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); NMVpnServiceState old_service_state = priv->service_state; _LOGD("dbus: state changed: %s (%d)", vpn_service_state_to_string_a(new_service_state), new_service_state); priv->service_state = new_service_state; if (new_service_state == NM_VPN_SERVICE_STATE_STOPPED) { if ((priv->vpn_state >= STATE_WAITING) && (priv->vpn_state <= STATE_ACTIVATED)) { VpnState old_state = priv->vpn_state; _set_vpn_state(self, STATE_FAILED, priv->failure_reason, FALSE); /* Reset the failure reason */ priv->failure_reason = NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN; /* If the connection failed, the service cannot persist, but the * connection can persist, ask listeners to re-activate the connection. */ if (old_state == STATE_ACTIVATED && priv->vpn_state == STATE_FAILED && _connection_only_can_persist(self)) g_signal_emit(self, signals[INTERNAL_RETRY_AFTER_FAILURE], 0); } } else if (new_service_state == NM_VPN_SERVICE_STATE_STARTING && old_service_state == NM_VPN_SERVICE_STATE_STARTED) { /* The VPN service got disconnected and is attempting to reconnect */ _set_vpn_state(self, STATE_CONNECT, NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, FALSE); } } static gboolean _config_process_generic(NMVpnConnection *self, GVariant *dict) { nm_auto_g_object_thaw_notify GObject *self_thaw = NULL; NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; int IS_IPv4; int ip_ifindex = 0; const char *v_str; guint32 v_u32; gboolean v_b; if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_TUNDEV, "&s", &v_str)) { const char *iface = NULL; /* Backwards compat with NM-openswan/libreswan */ if (!NM_IN_STRSET(v_str, "", "_none_")) iface = v_str; if (iface) { NMPlatform *platform = nm_netns_get_platform(priv->netns); ip_ifindex = nm_platform_link_get_ifindex(platform, iface); if (ip_ifindex <= 0) { nm_platform_process_events(platform); ip_ifindex = nm_platform_link_get_ifindex(platform, iface); } if (ip_ifindex <= 0) { _LOGW("config: failed to look up VPN interface index for \"%s\"", iface); return FALSE; } } } self_thaw = nm_g_object_freeze_notify(self); _set_ip_ifindex(self, ip_ifindex, TRUE); _set_ip_ifindex(self, _get_ifindex_for_device(self), FALSE); ip_ifindex = nm_vpn_connection_get_ip_ifindex(self, TRUE); if (ip_ifindex <= 0) { _LOGW("config: no ip-ifindex for the VPN"); return FALSE; } for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { NML3Cfg *l3cfg = priv->l3cfg_if ?: priv->l3cfg_dev; gs_unref_object NMIPConfig *ipconfig_old = NULL; ipconfig_old = g_steal_pointer(&priv->ip_data_x[IS_IPv4].ip_config); if (l3cfg) { priv->ip_data_x[IS_IPv4].ip_config = nm_l3cfg_ipconfig_acquire(l3cfg, IS_IPv4 ? AF_INET : AF_INET6); } g_object_notify(G_OBJECT(self), IS_IPv4 ? NM_ACTIVE_CONNECTION_IP4_CONFIG : NM_ACTIVE_CONNECTION_IP6_CONFIG); } if (g_variant_lookup(dict, NM_VPN_PLUGIN_CAN_PERSIST, "b", &v_b) && v_b) { /* Defaults to FALSE, so only let service indicate TRUE */ priv->service_can_persist = TRUE; } if (!g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_BANNER, "&s", &v_str)) v_str = NULL; if (nm_strdup_reset(&priv->banner, v_str)) _notify(self, PROP_BANNER); _vardict_to_addr(AF_INET, dict, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, &priv->ip_data_4.gw_external); _vardict_to_addr(AF_INET6, dict, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, &priv->ip_data_6.gw_external); if (nm_ip_addr_is_null(AF_INET, &priv->ip_data_4.gw_external) && nm_ip_addr_is_null(AF_INET6, &priv->ip_data_6.gw_external)) { _LOGW("config: no VPN gateway address received"); return FALSE; } l3cd = _l3cfg_l3cd_new(self, ip_ifindex); if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_PROXY_PAC, "&s", &v_str)) { nm_l3_config_data_set_proxy_method(l3cd, NM_PROXY_CONFIG_METHOD_AUTO); nm_l3_config_data_set_proxy_pac_url(l3cd, v_str); } else nm_l3_config_data_set_proxy_method(l3cd, NM_PROXY_CONFIG_METHOD_NONE); if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_MTU, "u", &v_u32)) priv->mtu = v_u32; else priv->mtu = 0; priv->generic_config_received = TRUE; nm_g_object_thaw_notify_clear(&self_thaw); _l3cfg_l3cd_set(self, L3CD_TYPE_GENERIC, l3cd); return TRUE; } static void _dbus_signal_config_cb(NMVpnConnection *self, GVariant *dict) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); gboolean v_b; g_return_if_fail(dict); if (!g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)) { _LOGD("config: ignore invalid configuration type"); return; } if (priv->vpn_state < STATE_NEED_AUTH) { /* Only list to this signals during and after connection */ _LOGD("config: ignore configuration before need-auth state"); return; } if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_HAS_IP4, "b", &v_b)) priv->ip_data_4.enabled = v_b; else priv->ip_data_4.enabled = FALSE; if (g_variant_lookup(dict, NM_VPN_PLUGIN_CONFIG_HAS_IP6, "b", &v_b)) priv->ip_data_6.enabled = v_b; else priv->ip_data_6.enabled = FALSE; _LOGD("config: reply received (IPv4:%s, IPv6:%s)", priv->ip_data_4.enabled ? "on" : "off", priv->ip_data_6.enabled ? "on" : "off"); if (priv->vpn_state == STATE_CONNECT) _set_vpn_state(self, STATE_IP_CONFIG_GET, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); if (!_config_process_generic(self, dict)) { _check_complete(self, FALSE); return; } _check_complete(self, TRUE); } static void _dbus_signal_ip_config_cb(NMVpnConnection *self, int addr_family, GVariant *dict) { const int IS_IPv4 = NM_IS_IPv4(addr_family); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; GVariantIter *var_iter; guint32 u32; const char *v_str; NMIPAddr v_addr; GVariant *v; gboolean v_b; int ip_ifindex; guint32 mss = 0; gboolean never_default; NMPlatformIPXAddress address; g_return_if_fail(dict && g_variant_is_of_type(dict, G_VARIANT_TYPE_VARDICT)); if (priv->vpn_state < STATE_NEED_AUTH) { _LOGD("config%c: ignoring, the connection is not in need-auth state", nm_utils_addr_family_to_char(addr_family)); return; } if (priv->vpn_state > STATE_ACTIVATED) { _LOGD("config%c: ignoring, the connection is no longer active", nm_utils_addr_family_to_char(addr_family)); return; } if (IS_IPv4) { if (priv->generic_config_received) { _LOGD("config4: reply received"); if (g_variant_n_children(dict) == 0) { priv->ip_data_4.enabled = FALSE; _check_complete(self, TRUE); return; } } else { _LOGD("config4: reply received (old style)"); /* In the old API, the generic and IPv4 configuration items * were mixed together. */ if (!_config_process_generic(self, dict)) { _check_complete(self, FALSE); return; } priv->ip_data_4.enabled = TRUE; priv->ip_data_6.enabled = FALSE; } } else { _LOGD("config6: reply received"); if (g_variant_n_children(dict) == 0) { priv->ip_data_6.enabled = FALSE; _check_complete(self, TRUE); return; } } if (priv->vpn_state == STATE_CONNECT) { _set_vpn_state(self, STATE_IP_CONFIG_GET, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } ip_ifindex = nm_vpn_connection_get_ip_ifindex(self, TRUE); if (ip_ifindex <= 0) g_return_if_reached(); l3cd = _l3cfg_l3cd_new(self, ip_ifindex); nm_l3_config_data_set_dns_priority(l3cd, AF_INET, NM_DNS_PRIORITY_DEFAULT_VPN); if (IS_IPv4) { address.a4 = (NMPlatformIP4Address){ .plen = 24, }; } else { address.a6 = (NMPlatformIP6Address){ .plen = 128, }; } _vardict_to_addr(addr_family, dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY : NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, &priv->ip_data_x[IS_IPv4].gw_internal); _vardict_to_addr(addr_family, dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS : NM_VPN_PLUGIN_IP6_CONFIG_ADDRESS, address.ax.address_ptr); if (!_vardict_to_addr(addr_family, dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_PTP : NM_VPN_PLUGIN_IP6_CONFIG_PTP, nm_platform_ip_address_get_peer_address(addr_family, &address.ax))) { if (IS_IPv4) address.a4.peer_address = address.a4.address; } if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_PREFIX : NM_VPN_PLUGIN_IP6_CONFIG_PREFIX, "u", &u32)) address.ax.plen = u32; if (address.ax.plen > 0 && address.ax.plen <= (IS_IPv4 ? 32 : 128) && !nm_ip_addr_is_null(addr_family, &address.ax.address_ptr)) { address.ax.addr_source = NM_IP_CONFIG_SOURCE_VPN; nm_l3_config_data_add_address(l3cd, addr_family, NULL, &address.ax); } else { _LOGW("invalid IP%c config received: no valid IP address/prefix", nm_utils_addr_family_to_char(addr_family)); _check_complete(self, FALSE); return; } if (IS_IPv4) { if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_DNS, "au", &var_iter)) { while (g_variant_iter_next(var_iter, "u", &u32)) nm_l3_config_data_add_nameserver(l3cd, addr_family, &u32); g_variant_iter_free(var_iter); } } else { if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_DNS, "aay", &var_iter)) { while (g_variant_iter_next(var_iter, "@ay", &v)) { if (nm_ip_addr_set_from_variant(AF_INET6, &v_addr, v, NULL)) nm_l3_config_data_add_nameserver(l3cd, addr_family, &v_addr); g_variant_unref(v); } g_variant_iter_free(var_iter); } } if (IS_IPv4) { if (g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_NBNS, "au", &var_iter)) { while (g_variant_iter_next(var_iter, "u", &u32)) nm_l3_config_data_add_wins(l3cd, u32); g_variant_iter_free(var_iter); } } if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_MSS : NM_VPN_PLUGIN_IP6_CONFIG_MSS, "u", &u32)) mss = u32; if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN : NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, "&s", &v_str)) nm_l3_config_data_add_domain(l3cd, addr_family, v_str); if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_DOMAINS : NM_VPN_PLUGIN_IP6_CONFIG_DOMAINS, "as", &var_iter)) { while (g_variant_iter_next(var_iter, "&s", &v_str)) nm_l3_config_data_add_domain(l3cd, addr_family, v_str); g_variant_iter_free(var_iter); } if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_PRESERVE_ROUTES : NM_VPN_PLUGIN_IP6_CONFIG_PRESERVE_ROUTES, "b", &v_b) && v_b) { if (priv->l3cds[L3CD_TYPE_IP_X(IS_IPv4)]) { NMDedupMultiIter ipconf_iter; const NMPObject *route; nm_l3_config_data_iter_obj_for_each (&ipconf_iter, priv->l3cds[L3CD_TYPE_IP_X(IS_IPv4)], &route, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) nm_l3_config_data_add_route(l3cd, addr_family, route, NULL); } } else if (IS_IPv4 ? g_variant_lookup(dict, NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, "aau", &var_iter) : g_variant_lookup(dict, NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, "a(ayuayu)", &var_iter)) { _nm_unused nm_auto_free_variant_iter GVariantIter *var_iter_ref_owner = var_iter; NMPlatformIPXRoute route = {}; guint32 plen; GVariant *next_hop; GVariant *dest; guint32 prefix; guint32 metric; if (IS_IPv4) { while (g_variant_iter_next(var_iter, "@au", &v)) { _nm_unused gs_unref_variant GVariant *v_ref_owner = v; switch (g_variant_n_children(v)) { case 5: g_variant_get_child(v, 4, "u", &route.r4.pref_src); /* fall-through */ case 4: g_variant_get_child(v, 0, "u", &route.r4.network); g_variant_get_child(v, 1, "u", &plen); g_variant_get_child(v, 2, "u", &route.r4.gateway); /* 4th item is unused route metric */ route.r4.table_any = TRUE; route.r4.metric_any = TRUE; route.r4.rt_source = NM_IP_CONFIG_SOURCE_VPN; if (plen > 32) break; route.r4.plen = plen; route.r4.network = nm_utils_ip4_address_clear_host_address(route.r4.network, plen); if (priv->ip_data_4.gw_external.addr4 && route.r4.network == priv->ip_data_4.gw_external.addr4 && route.r4.plen == 32) { /* Ignore host routes to the VPN gateway since NM adds one itself * below. Since NM knows more about the routing situation than * the VPN server, we want to use the NM created route instead of * whatever the server provides. */ break; } nm_l3_config_data_add_route_4(l3cd, &route.r4); break; default: break; } } } else { while ( g_variant_iter_next(var_iter, "(@ayu@ayu)", &dest, &prefix, &next_hop, &metric)) { _nm_unused gs_unref_variant GVariant *next_hop_ref_owner = next_hop; _nm_unused gs_unref_variant GVariant *dest_ref_owner = dest; if (prefix > 128) continue; route.r6 = (NMPlatformIP6Route){ .plen = prefix, .table_any = TRUE, .metric_any = TRUE, .rt_source = NM_IP_CONFIG_SOURCE_VPN, }; if (!nm_ip_addr_set_from_variant(AF_INET6, &route.r6.network, dest, NULL)) continue; nm_ip_addr_set_from_variant(AF_INET6, &route.r6.gateway, next_hop, NULL); nm_utils_ip6_address_clear_host_address(&route.r6.network, &route.r6.network, route.r6.plen); if (!IN6_IS_ADDR_UNSPECIFIED(&priv->ip_data_6.gw_external.addr6) && IN6_ARE_ADDR_EQUAL(&route.r6.network, &priv->ip_data_6.gw_external.addr6) && route.r6.plen == 128) { /* Ignore host routes to the VPN gateway since NM adds one itself. * Since NM knows more about the routing situation than the VPN * server, we want to use the NM created route instead of whatever * the server provides. */ continue; } nm_l3_config_data_add_route_6(l3cd, &route.r6); } } } if (g_variant_lookup(dict, IS_IPv4 ? NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT : NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, "b", &v_b)) never_default = v_b; else never_default = FALSE; if (!never_default) { NMPlatformIPXRoute route; if (IS_IPv4) { route.r4 = (NMPlatformIP4Route){ .ifindex = ip_ifindex, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .gateway = priv->ip_data_4.gw_internal.addr4, .table_any = TRUE, .metric_any = TRUE, .mss = mss, }; } else { route.r6 = (NMPlatformIP6Route){ .ifindex = ip_ifindex, .rt_source = NM_IP_CONFIG_SOURCE_VPN, .gateway = priv->ip_data_6.gw_internal.addr6, .table_any = TRUE, .metric_any = TRUE, .mss = mss, }; } nm_l3_config_data_add_route(l3cd, addr_family, NULL, &route.rx); } _l3cfg_l3cd_set(self, L3CD_TYPE_IP_X(IS_IPv4), l3cd); _check_complete(self, TRUE); } void nm_vpn_connection_disconnect(NMVpnConnection *self, NMActiveConnectionStateReason reason, gboolean quitting) { g_return_if_fail(NM_IS_VPN_CONNECTION(self)); _set_vpn_state(self, STATE_DISCONNECTED, reason, quitting); } gboolean nm_vpn_connection_deactivate(NMVpnConnection *self, NMActiveConnectionStateReason reason, gboolean quitting) { NMVpnConnectionPrivate *priv; g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), FALSE); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (priv->vpn_state <= STATE_UNKNOWN || priv->vpn_state > STATE_DEACTIVATING) return FALSE; _set_vpn_state(self, STATE_DEACTIVATING, reason, quitting); return TRUE; } /*****************************************************************************/ static void _secrets_dbus_need_secrets_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMVpnConnection *self; NMVpnConnectionPrivate *priv; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; const char *setting_name; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = NM_VPN_CONNECTION(user_data); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); if (error) { _LOGW("plugin NeedSecrets request #%d failed: %s", priv->secrets_idx + 1, error->message); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); return; } g_variant_get(res, "(&s)", &setting_name); if (nm_str_is_empty(setting_name)) { /* No secrets required; we can start the VPN */ _LOGD("service indicated no additional secrets required"); really_activate(self, priv->username); return; } /* More secrets required */ if (priv->secrets_idx == SECRETS_REQ_NEW) { _LOGW("final secrets request failed to provide sufficient secrets"); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); return; } _LOGD("service indicated additional secrets required"); _secrets_get(self, priv->secrets_idx + 1, NULL); } static void _secrets_dbus_new_secrets_cb(GObject *source, GAsyncResult *result, gpointer user_data) { NMVpnConnection *self; gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = NM_VPN_CONNECTION(user_data); if (error) { _LOGW("sending new secrets to the plugin failed: %s", error->message); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); return; } _set_vpn_state(self, STATE_CONNECT, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } static void _secrets_get_secrets_cb(NMSettingsConnection *connection, NMSettingsConnectionCallId *call_id, const char *agent_username, const char *setting_name, GError *error, gpointer user_data) { NMVpnConnection *self = NM_VPN_CONNECTION(user_data); NMVpnConnectionPrivate *priv; GVariant *dict; g_return_if_fail(NM_IS_VPN_CONNECTION(self)); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); g_return_if_fail(connection && connection == _get_settings_connection(self, FALSE)); g_return_if_fail(call_id == priv->secrets_id); priv->secrets_id = NULL; if (nm_utils_error_is_cancelled(error)) return; if (error && priv->secrets_idx >= SECRETS_REQ_NEW) { _LOGW("secrets: failed to request VPN secrets #%d: %s", priv->secrets_idx + 1, error->message); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); return; } if (!priv->dbus.bus_name) { _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED, FALSE); return; } /* Cache the username for later */ if (agent_username) nm_strdup_reset(&priv->username, agent_username); dict = _hash_with_username(_get_applied_connection(self), priv->username); if (priv->secrets_idx == SECRETS_REQ_INTERACTIVE) { _LOGD("secrets: sending secrets to the plugin"); _dbus_connection_call(self, "NewSecrets", g_variant_new("(@a{sa{sv}})", dict), G_VARIANT_TYPE("()"), _secrets_dbus_new_secrets_cb); return; } _LOGD("secrets: asking service if additional secrets are required"); _dbus_connection_call(self, "NeedSecrets", g_variant_new("(@a{sa{sv}})", dict), G_VARIANT_TYPE("(s)"), _secrets_dbus_need_secrets_cb); } static void _secrets_get(NMVpnConnection *self, SecretsReq secrets_idx, const char *const *hints) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE; g_return_if_fail(secrets_idx < SECRETS_REQ_LAST); priv->secrets_idx = secrets_idx; cancel_get_secrets(self); _LOGD("secrets: requesting VPN secrets pass #%d", priv->secrets_idx + 1); switch (priv->secrets_idx) { case SECRETS_REQ_SYSTEM: flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM; break; case SECRETS_REQ_EXISTING: flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE; break; case SECRETS_REQ_NEW: case SECRETS_REQ_INTERACTIVE: flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; break; default: g_return_if_reached(); } if (nm_active_connection_get_user_requested(NM_ACTIVE_CONNECTION(self))) flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED; priv->secrets_id = nm_settings_connection_get_secrets( _get_settings_connection(self, FALSE), _get_applied_connection(self), nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(self)), NM_SETTING_VPN_SETTING_NAME, flags, hints, _secrets_get_secrets_cb, self); g_return_if_fail(priv->secrets_id); } static void _dbus_signal_secrets_required_cb(NMVpnConnection *self, const char *message, const char *const *secrets) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); const gsize secrets_len = NM_PTRARRAY_LEN(secrets); gsize i; gs_free const char **hints = NULL; gs_free char *message_hint = NULL; if (!NM_IN_SET(priv->vpn_state, STATE_CONNECT, STATE_NEED_AUTH)) { _LOGD("secrets: request ignored in current state %s", vpn_state_to_string_a(priv->vpn_state)); return; } _LOGD("secrets: request (state %s)", vpn_state_to_string_a(priv->vpn_state)); priv->secrets_idx = SECRETS_REQ_INTERACTIVE; _set_vpn_state(self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); /* Copy hints and add message to the end */ hints = g_new(const char *, secrets_len + 2); for (i = 0; i < secrets_len; i++) hints[i] = secrets[i]; if (message) { message_hint = g_strdup_printf("x-vpn-message:%s", message); hints[i++] = message_hint; } hints[i] = NULL; nm_assert(i < secrets_len + 2); _secrets_get(self, SECRETS_REQ_INTERACTIVE, hints); } /*****************************************************************************/ static int _get_log_level(void) { NMLogLevel level; /* curiously enough, nm-logging also uses syslog. But it * maps NMLogLevel differently to the syslog levels then we * do here. * * The reason is, that LOG_NOTICE is already something worth * highlighting in the journal, but we have 3 levels that are * lower then LOG_NOTICE (LOGL_TRACE, LOGL_DEBUG, LOGL_INFO), * On the other hand, syslog only defines LOG_DEBUG and LOG_INFO. * Thus, we must map them differently. * * Inside the VPN plugin, you might want to treat LOG_NOTICE as * as low severity, not worthy to be highlighted (like NM does). */ level = nm_logging_get_level(LOGD_VPN_PLUGIN); if (level != _LOGL_OFF) { if (level <= LOGL_TRACE) return LOG_DEBUG; if (level <= LOGL_DEBUG) return LOG_INFO; if (level <= LOGL_INFO) return LOG_NOTICE; if (level <= LOGL_WARN) return LOG_WARNING; if (level <= LOGL_ERR) return LOG_ERR; } return LOG_EMERG; } static gboolean nm_vpn_service_daemon_exec(NMVpnConnection *self, GError **error) { NMVpnConnectionPrivate *priv; GPid pid; char *vpn_argv[4]; gs_free char **envp = NULL; char env_log_level[NM_STRLEN("NM_VPN_LOG_LEVEL=") + 100]; char env_log_syslog[NM_STRLEN("NM_VPN_LOG_SYSLOG=") + 10]; const gsize N_ENVIRON_EXTRA = 3; char **p_environ; gsize n_environ; gsize i; gsize j; g_return_val_if_fail(NM_IS_VPN_CONNECTION(self), FALSE); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); i = 0; vpn_argv[i++] = (char *) nm_vpn_plugin_info_get_program(priv->plugin_info); g_return_val_if_fail(vpn_argv[0], FALSE); if (nm_vpn_plugin_info_supports_multiple(priv->plugin_info)) { vpn_argv[i++] = "--bus-name"; vpn_argv[i++] = priv->dbus.bus_name; } vpn_argv[i++] = NULL; /* we include and "config.h" defines _GNU_SOURCE for us. So, we have @environ. */ p_environ = environ; n_environ = NM_PTRARRAY_LEN(p_environ); envp = g_new(char *, n_environ + N_ENVIRON_EXTRA); for (i = 0, j = 0; j < n_environ; j++) { if (NM_STR_HAS_PREFIX(p_environ[j], "NM_VPN_LOG_LEVEL=") || NM_STR_HAS_PREFIX(p_environ[j], "NM_VPN_LOG_SYSLOG=")) continue; envp[i++] = p_environ[j]; } /* NM_VPN_LOG_LEVEL: the syslog logging level for the plugin. */ envp[i++] = nm_sprintf_buf(env_log_level, "NM_VPN_LOG_LEVEL=%d", _get_log_level()); /* NM_VPN_LOG_SYSLOG: whether to log to stdout or syslog. If NetworkManager itself runs in * foreground, we also want the plugin to log to stdout. * If the plugin runs in background, the plugin should prefer logging to syslog. Otherwise * logging messages will be lost (unless using journald, in which case it wouldn't matter). */ envp[i++] = nm_sprintf_buf(env_log_syslog, "NM_VPN_LOG_SYSLOG=%c", nm_logging_syslog_enabled() ? '1' : '0'); envp[i++] = NULL; nm_assert(i <= n_environ + N_ENVIRON_EXTRA); if (!g_spawn_async(NULL, vpn_argv, envp, 0, nm_utils_setpgid, NULL, &pid, error)) return FALSE; _LOGD("starting: VPN service has PID %lld", (long long) pid); return TRUE; } /*****************************************************************************/ static gboolean _start_timeout_cb(gpointer data) { NMVpnConnection *self = NM_VPN_CONNECTION(data); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_clear_g_source_inst(&priv->start_timeout_source); if (priv->dbus_service_started) _LOGW("starting: timed out waiting for the service to start"); else _LOGW("starting: timed out waiting for the VPN to activate"); nm_vpn_connection_disconnect(self, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, FALSE); return G_SOURCE_CONTINUE; } /*****************************************************************************/ static void _dbus_dispatch_cb(GDBusConnection *connection, const char *sender_name, const char *object_path, const char *interface_name, const char *signal_name, GVariant *parameters, gpointer user_data) { NMVpnConnection *self = user_data; NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); guint32 v_u; nm_assert(nm_streq0(object_path, NM_VPN_DBUS_PLUGIN_PATH)); nm_assert(nm_streq0(interface_name, NM_VPN_DBUS_PLUGIN_INTERFACE)); nm_assert(signal_name); if (!nm_streq0(priv->dbus.owner, sender_name)) return; if (nm_streq(signal_name, "Failure")) { if (nm_g_variant_tuple_get_u(parameters, &v_u)) _dbus_signal_failure_cb(self, v_u); } else if (nm_streq(signal_name, "StateChanged")) { if (nm_g_variant_tuple_get_u(parameters, &v_u)) _dbus_signal_state_changed_cb(self, v_u); } else if (nm_streq(signal_name, "SecretsRequired")) { if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sas)"))) { const char *v_s; gs_free const char **v_strv = NULL; g_variant_get(parameters, "(&s^a&s)", &v_s, &v_strv); _dbus_signal_secrets_required_cb(self, v_s, v_strv); } } else if (NM_IN_STRSET(signal_name, "Config", "Ip4Config", "Ip6Config")) { if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(a{sv})"))) { gs_unref_variant GVariant *v_var = NULL; g_variant_get(parameters, "(@a{sv})", &v_var); if (signal_name[0] == 'C') _dbus_signal_config_cb(self, v_var); else if (signal_name[2] == '4') _dbus_signal_ip_config_cb(self, AF_INET, v_var); else _dbus_signal_ip_config_cb(self, AF_INET6, v_var); } } } static void _name_owner_changed(NMVpnConnection *self, const char *owner, gboolean initializing) { _nm_unused gs_unref_object NMVpnConnection *self_keep_alive = g_object_ref(self); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); owner = nm_str_not_empty(owner); if (!owner && initializing) { gs_free_error GError *error = NULL; nm_assert(!priv->dbus.owner); _LOGT("dbus: no name owner for %s (start VPN service)", priv->dbus.bus_name); if (!nm_vpn_service_daemon_exec(self, &error)) { _LOGW("starting: failure to start VPN service: %s", error->message); nm_vpn_connection_disconnect(self, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); } priv->start_timeout_source = nm_g_timeout_add_seconds_source(5, _start_timeout_cb, self); return; } if (!nm_strdup_reset(&priv->dbus.owner, owner)) return; if (!priv->dbus.owner) { _LOGT("dbus: name owner for %s disappeared", priv->dbus.bus_name); /* We don't want to restart if the service re-appears. Disconnect the signal * so that cannot happen and we don't disconnect the VPN again. */ nm_clear_g_dbus_connection_signal(priv->dbus.connection, &priv->dbus.signal_id_name_changed); nm_vpn_connection_disconnect(self, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED, FALSE); return; } _LOGT("dbus: name owner %s for %s", priv->dbus.owner, priv->dbus.bus_name); priv->dbus_service_started = TRUE; nm_clear_g_source_inst(&priv->start_timeout_source); priv->start_timeout_source = nm_g_timeout_add_seconds_source(_get_vpn_timeout(self) + 180, _start_timeout_cb, self); _set_vpn_state(self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); /* Kick off the secrets requests; first we get existing system secrets * and ask the plugin if these are sufficient, next we get all existing * secrets from system and from user agents and ask the plugin again, * and last we ask the user for new secrets if required. */ _secrets_get(self, SECRETS_REQ_SYSTEM, NULL); } static void _name_owner_changed_cb(GDBusConnection *connection, const char *sender_name, const char *object_path, const char *interface_name, const char *signal_name, GVariant *parameters, gpointer user_data) { NMVpnConnection *self = user_data; NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); const char *new_owner; if (!priv->dbus.name_owner_initialized) return; if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) return; g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); _name_owner_changed(self, new_owner, FALSE); } static void _name_owner_get_cb(const char *name_owner, GError *error, gpointer user_data) { NMVpnConnection *self; NMVpnConnectionPrivate *priv; if (nm_utils_error_is_cancelled(error)) return; self = user_data; priv = NM_VPN_CONNECTION_GET_PRIVATE(self); priv->dbus.name_owner_initialized = TRUE; _name_owner_changed(self, name_owner, TRUE); } static gboolean _init_fail_on_idle_cb(gpointer user_data) { NMVpnConnection *self = user_data; NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_clear_g_source_inst(&priv->init_fail_on_idle_source); _set_vpn_state(self, STATE_FAILED, NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); return G_SOURCE_CONTINUE; } /*****************************************************************************/ void nm_vpn_connection_activate(NMVpnConnection *self, NMVpnPluginInfo *plugin_info) { NMVpnConnectionPrivate *priv; NMConnection *connection; NMSettingVpn *s_vpn; const char *service; g_return_if_fail(NM_IS_VPN_CONNECTION(self)); g_return_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info)); priv = NM_VPN_CONNECTION_GET_PRIVATE(self); g_return_if_fail(!priv->plugin_info); connection = _get_applied_connection(self); s_vpn = nm_connection_get_setting_vpn(connection); g_return_if_fail(s_vpn); service = nm_vpn_plugin_info_get_service(plugin_info); nm_assert(service); if (nm_vpn_plugin_info_supports_multiple(plugin_info)) { const char *path; path = nm_dbus_object_get_path(NM_DBUS_OBJECT(self)); if (path) path = strrchr(path, '/'); g_return_if_fail(path); priv->dbus.bus_name = g_strdup_printf("%s.Connection_%s", service, &path[1]); } else priv->dbus.bus_name = g_strdup(service); _LOGI("starting %s", nm_vpn_plugin_info_get_name(plugin_info)); priv->connection_can_persist = nm_setting_vpn_get_persistent(s_vpn); priv->plugin_info = g_object_ref(plugin_info); priv->main_cancellable = g_cancellable_new(); priv->dbus.connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); if (!priv->dbus.connection) { _LOGD("starting: no D-Bus connection (will fail)"); priv->init_fail_on_idle_source = nm_g_idle_add_source(_init_fail_on_idle_cb, self); goto out; } _LOGD("starting: watch D-Bus service %s", priv->dbus.bus_name); priv->dbus.signal_id_name_changed = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus.connection, priv->dbus.bus_name, _name_owner_changed_cb, self, NULL); priv->dbus.signal_id_vpn = g_dbus_connection_signal_subscribe(priv->dbus.connection, priv->dbus.bus_name, NM_VPN_DBUS_PLUGIN_INTERFACE, NULL, NM_VPN_DBUS_PLUGIN_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, _dbus_dispatch_cb, self, NULL); nm_dbus_connection_call_get_name_owner(priv->dbus.connection, priv->dbus.bus_name, 3000, priv->main_cancellable, _name_owner_get_cb, self); out: _set_vpn_state(self, STATE_PREPARE, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE); } /*****************************************************************************/ static void device_changed(NMActiveConnection *active, NMDevice *new_device, NMDevice *old_device) { NMVpnConnection *self = NM_VPN_CONNECTION(active); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(active); gs_unref_object NML3Cfg *l3cfg_old = NULL; int ifindex; if (!priv->generic_config_received) return; if (priv->vpn_state > STATE_ACTIVATED) return; if (!_service_and_connection_can_persist(self)) return; if (priv->ifindex_if <= 0) { /* Route-based VPNs must updvate their routing and send a new IP config * since all their routes need to be adjusted for new_device. */ return; } ifindex = _get_ifindex_for_device(self); if (ifindex <= 0) return; if (priv->ifindex_dev == ifindex) return; _LOGD("set ip-ifindex-dev %d (was %d)", ifindex, priv->ifindex_dev); l3cfg_old = g_steal_pointer(&priv->l3cfg_dev); nm_l3cfg_commit_type_clear(l3cfg_old, &priv->l3cfg_commit_type_dev); _l3cfg_clear(self, l3cfg_old); priv->ifindex_dev = ifindex; if (ifindex > 0) { priv->l3cfg_dev = nm_netns_l3cfg_acquire(priv->netns, ifindex); g_signal_connect(priv->l3cfg_dev, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self); priv->l3cfg_commit_type_dev = nm_l3cfg_commit_type_register(priv->l3cfg_dev, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL, "vpn"); } if (_l3cfg_l3cd_gw_extern_update(self)) nm_l3cfg_commit_on_idle_schedule(priv->l3cfg_dev, NM_L3_CFG_COMMIT_TYPE_AUTO); } /*****************************************************************************/ static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMVpnConnection *self = NM_VPN_CONNECTION(object); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); switch (prop_id) { case PROP_VPN_STATE: g_value_set_uint(value, _state_to_nm_vpn_state(priv->vpn_state)); break; case PROP_BANNER: g_value_set_string(value, priv->banner ?: ""); break; case PROP_IP4_CONFIG: nm_dbus_utils_g_value_set_object_path(value, priv->ip_data_4.ip_config); break; case PROP_IP6_CONFIG: nm_dbus_utils_g_value_set_object_path(value, priv->ip_data_6.ip_config); break; case PROP_MASTER: nm_dbus_utils_g_value_set_object_path( value, nm_active_connection_get_device(NM_ACTIVE_CONNECTION(self))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /*****************************************************************************/ static void nm_vpn_connection_init(NMVpnConnection *self) { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); priv->vpn_state = STATE_WAITING; priv->secrets_idx = SECRETS_REQ_SYSTEM; priv->netns = g_object_ref(nm_netns_get()); } NMVpnConnection * nm_vpn_connection_new(NMSettingsConnection *settings_connection, NMDevice *parent_device, const char *specific_object, NMActivationReason activation_reason, NMActivationStateFlags initial_state_flags, NMAuthSubject *subject) { g_return_val_if_fail(!settings_connection || NM_IS_SETTINGS_CONNECTION(settings_connection), NULL); g_return_val_if_fail(NM_IS_DEVICE(parent_device), NULL); g_return_val_if_fail(specific_object, NULL); return g_object_new(NM_TYPE_VPN_CONNECTION, NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, settings_connection, NM_ACTIVE_CONNECTION_INT_DEVICE, parent_device, NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, specific_object, NM_ACTIVE_CONNECTION_INT_SUBJECT, subject, NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, activation_reason, NM_ACTIVE_CONNECTION_VPN, TRUE, NM_ACTIVE_CONNECTION_STATE_FLAGS, (guint) initial_state_flags, NULL); } static void dispose(GObject *object) { NMVpnConnection *self = NM_VPN_CONNECTION(object); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); nm_clear_g_dbus_connection_signal(priv->dbus.connection, &priv->dbus.signal_id_vpn); nm_clear_g_dbus_connection_signal(priv->dbus.connection, &priv->dbus.signal_id_name_changed); nm_clear_g_source_inst(&priv->init_fail_on_idle_source); nm_clear_g_cancellable(&priv->main_cancellable); nm_clear_g_source_inst(&priv->start_timeout_source); nm_clear_pointer(&priv->connect_hash, g_variant_unref); nm_clear_g_source_inst(&priv->connect_timeout_source); if (nm_l3cfg_commit_type_clear(priv->l3cfg_if, &priv->l3cfg_commit_type_if)) nm_l3cfg_commit_on_idle_schedule(priv->l3cfg_if, NM_L3_CFG_COMMIT_TYPE_AUTO); if (nm_l3cfg_commit_type_clear(priv->l3cfg_dev, &priv->l3cfg_commit_type_dev)) nm_l3cfg_commit_on_idle_schedule(priv->l3cfg_dev, NM_L3_CFG_COMMIT_TYPE_AUTO); g_clear_object(&priv->ip_data_4.ip_config); g_clear_object(&priv->ip_data_6.ip_config); dispatcher_cleanup(self); cancel_get_secrets(self); fw_call_cleanup(self); nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id); G_OBJECT_CLASS(nm_vpn_connection_parent_class)->dispose(object); } static void finalize(GObject *object) { NMVpnConnection *self = NM_VPN_CONNECTION(object); NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE(self); G_OBJECT_CLASS(nm_vpn_connection_parent_class)->finalize(object); g_free(priv->banner); g_free(priv->username); g_free(priv->dbus.bus_name); _l3cfg_l3cd_clear_all(self); _l3cfg_clear(self, priv->l3cfg_if); _l3cfg_clear(self, priv->l3cfg_dev); g_clear_object(&priv->plugin_info); g_clear_object(&priv->l3cfg_if); g_clear_object(&priv->l3cfg_dev); g_clear_object(&priv->netns); g_clear_object(&priv->dbus.connection); } static const GDBusSignalInfo signal_info_vpn_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( "VpnStateChanged", .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("state", "u"), NM_DEFINE_GDBUS_ARG_INFO("reason", "u"), ), ); static const NMDBusInterfaceInfoExtended interface_info_vpn_connection = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( NM_DBUS_INTERFACE_VPN_CONNECTION, .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_vpn_state_changed, ), .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VpnState", "u", NM_VPN_CONNECTION_VPN_STATE), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Banner", "s", NM_VPN_CONNECTION_BANNER), ), ), }; static void nm_vpn_connection_class_init(NMVpnConnectionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); NMActiveConnectionClass *active_class = NM_ACTIVE_CONNECTION_CLASS(klass); NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_vpn_connection); object_class->get_property = get_property; object_class->dispose = dispose; object_class->finalize = finalize; active_class->device_state_changed = device_state_changed; active_class->device_changed = device_changed; obj_properties[PROP_VPN_STATE] = g_param_spec_uint(NM_VPN_CONNECTION_VPN_STATE, "", "", NM_VPN_CONNECTION_STATE_UNKNOWN, NM_VPN_CONNECTION_STATE_DISCONNECTED, NM_VPN_CONNECTION_STATE_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_BANNER] = g_param_spec_string(NM_VPN_CONNECTION_BANNER, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); g_object_class_override_property(object_class, PROP_MASTER, NM_ACTIVE_CONNECTION_MASTER); g_object_class_override_property(object_class, PROP_IP4_CONFIG, NM_ACTIVE_CONNECTION_IP4_CONFIG); g_object_class_override_property(object_class, PROP_IP6_CONFIG, NM_ACTIVE_CONNECTION_IP6_CONFIG); signals[INTERNAL_STATE_CHANGED] = g_signal_new(NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT, /* NMVpnConnectionState new_external_state */ G_TYPE_UINT, /* NMVpnConnectionState old_external_state */ G_TYPE_UINT /* NMActiveConnectionStateReason reason */); signals[INTERNAL_RETRY_AFTER_FAILURE] = g_signal_new(NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE, G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); }