NetworkManager/src/core/vpn/nm-vpn-connection.c
Gris Ge 2aadb5dcb0 vpn: Place gateway route to table defined in ipvx.route-table
Previously, NM create direct route to gateway to main(254) route table
regardless `ipvx.route-table` value.

Fixed by setting `NMPlatformIP4Route.table_any` to `TRUE`.

Resolves: https://issues.redhat.com/browse/RHEL-69901

Signed-off-by: Gris Ge <fge@redhat.com>
(cherry picked from commit 6d06286f1d)
(cherry picked from commit 29f23d3519)
(cherry picked from commit 0dc07c5ca4)
(cherry picked from commit 6a04a966c2)
(cherry picked from commit 70060d84f2)
(cherry picked from commit b92a07713c)
2025-01-07 10:13:09 -05:00

3083 lines
110 KiB
C

/* 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 <arpa/inet.h>
#include <linux/if.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#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 <unistd.h> 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);
}