vpn-manager: support multiple VPN connections of the same type

A separate instance of the support plugin is spawned for each connection with
a different bus name. The bus name is passed via --bus-name <name> argument.
Plugins that support the feature indicate it with
support-multiple-connections=true key in the [VPN Connection] section.

The bus name is currently generated by adding a .<connection.uuid> to the VPN
service name. It's guarranteed unique, but if it proves to be too long or ugly
it can easily be replaced with something more meaningful (such as the same number
as is used for connection's DBus name).

NMVpnService has been removed and folded into NMVpnConnection. A
NMVpnConnection will spawn a service plugin instance whenever it is activated
and notices the bus name it needs is not provided.

The NMVpnManager no longer needs to keep track of the connections in use apart
for compatibility purposes with plugins that don't support the feature.
This commit is contained in:
Lubomir Rintel 2015-08-19 18:55:53 +02:00
parent 9bbf5e94c2
commit 5b48befaad
9 changed files with 222 additions and 530 deletions

View file

@ -449,9 +449,6 @@ nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError
gboolean
nm_vpn_plugin_info_list_remove (GSList **list, NMVpnPluginInfo *plugin_info)
{
if (!plugin_info)
return FALSE;
g_return_val_if_fail (list, FALSE);
g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), FALSE);

View file

@ -324,8 +324,6 @@ libNetworkManager_la_SOURCES = \
vpn-manager/nm-vpn-connection.h \
vpn-manager/nm-vpn-manager.c \
vpn-manager/nm-vpn-manager.h \
vpn-manager/nm-vpn-service.c \
vpn-manager/nm-vpn-service.h \
\
nm-activation-request.c \
nm-activation-request.h \

View file

@ -3465,7 +3465,6 @@ nm_manager_deactivate_connection (NMManager *manager,
NMDeviceStateReason reason,
GError **error)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
NMActiveConnection *active;
gboolean success = FALSE;
@ -3481,7 +3480,7 @@ nm_manager_deactivate_connection (NMManager *manager,
if (reason == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED)
vpn_reason = NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED;
if (nm_vpn_manager_deactivate_connection (priv->vpn_manager, NM_VPN_CONNECTION (active), vpn_reason))
if (nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE))
success = TRUE;
else
g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,

View file

@ -43,6 +43,8 @@
#include "nm-route-manager.h"
#include "nm-firewall-manager.h"
#include "nm-config.h"
#include "nm-vpn-plugin-info.h"
#include "nm-vpn-manager.h"
#include "nmdbus-vpn-connection.h"
@ -89,6 +91,10 @@ typedef struct {
NMVpnConnectionStateReason failure_reason;
NMVpnServiceState service_state;
guint start_timeout;
gboolean service_running;
NMVpnPluginInfo *plugin_info;
char *bus_name;
/* Firewall */
NMFirewallManagerCallId fw_call;
@ -383,6 +389,9 @@ vpn_cleanup (NMVpnConnection *self, NMDevice *parent_dev)
priv->ip_iface = NULL;
priv->ip_ifindex = 0;
g_free (priv->bus_name);
priv->bus_name = NULL;
/* Clear out connection secrets to ensure that the settings service
* gets asked for them next time the connection is activated.
*/
@ -1857,6 +1866,111 @@ ip6_config_cb (GDBusProxy *proxy,
nm_vpn_connection_ip6_config_get (self, dict);
}
static void
_name_owner_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
char *owner;
owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
if (owner && !priv->service_running) {
/* service appeared */
priv->service_running = TRUE;
_LOGI ("Saw the service appear; activating connection");
/* No need to wait for the timeout any longer */
nm_clear_g_source (&priv->start_timeout);
/* Expect success because the VPN service has already appeared */
_nm_dbus_signal_connect (priv->proxy, "Failure", G_VARIANT_TYPE ("(u)"),
G_CALLBACK (failure_cb), self);
_nm_dbus_signal_connect (priv->proxy, "StateChanged", G_VARIANT_TYPE ("(u)"),
G_CALLBACK (state_changed_cb), self);
_nm_dbus_signal_connect (priv->proxy, "SecretsRequired", G_VARIANT_TYPE ("(sas)"),
G_CALLBACK (secrets_required_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (config_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Ip4Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (ip4_config_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Ip6Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (ip6_config_cb), self);
_set_vpn_state (self, STATE_NEED_AUTH, NM_VPN_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.
*/
get_secrets (self, SECRETS_REQ_SYSTEM, NULL);
} else if (!owner && priv->service_running) {
/* service went away */
priv->service_running = FALSE;
_LOGI ("VPN service disappeared");
nm_vpn_connection_disconnect (self, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED, FALSE);
}
g_free (owner);
}
static gboolean
_daemon_exec_timeout (gpointer data)
{
NMVpnConnection *self = NM_VPN_CONNECTION (data);
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
_LOGW ("Timed out waiting for the service to start");
priv->start_timeout = 0;
nm_vpn_connection_disconnect (self, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, FALSE);
g_object_unref (self);
return G_SOURCE_REMOVE;
}
static gboolean
nm_vpn_service_daemon_exec (NMVpnConnection *self, GError **error)
{
NMVpnConnectionPrivate *priv;
GPid pid;
char *vpn_argv[4];
gboolean success = FALSE;
GError *spawn_error = NULL;
int i = 0;
g_return_val_if_fail (NM_IS_VPN_CONNECTION (self), FALSE);
priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
vpn_argv[i++] = (char *) nm_vpn_plugin_info_get_program (priv->plugin_info);
if (nm_vpn_plugin_info_supports_multiple (priv->plugin_info)) {
vpn_argv[i++] = "--bus-name";
vpn_argv[i++] = priv->bus_name;
}
vpn_argv[i] = NULL;
g_assert (vpn_argv[0]);
success = g_spawn_async (NULL, vpn_argv, NULL, 0, nm_utils_setpgid, NULL, &pid, &spawn_error);
if (success) {
_LOGI ("Started the VPN service, PID %ld", (long int) pid);
priv->start_timeout = g_timeout_add_seconds (5, _daemon_exec_timeout, g_object_ref (self));
} else {
g_set_error (error,
NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
"%s", spawn_error ? spawn_error->message : "unknown g_spawn_async() error");
if (spawn_error)
g_error_free (spawn_error);
}
return success;
}
static void
on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data)
{
@ -1883,55 +1997,69 @@ on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data)
}
priv->proxy = proxy;
_nm_dbus_signal_connect (priv->proxy, "Failure", G_VARIANT_TYPE ("(u)"),
G_CALLBACK (failure_cb), self);
_nm_dbus_signal_connect (priv->proxy, "StateChanged", G_VARIANT_TYPE ("(u)"),
G_CALLBACK (state_changed_cb), self);
_nm_dbus_signal_connect (priv->proxy, "SecretsRequired", G_VARIANT_TYPE ("(sas)"),
G_CALLBACK (secrets_required_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (config_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Ip4Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (ip4_config_cb), self);
_nm_dbus_signal_connect (priv->proxy, "Ip6Config", G_VARIANT_TYPE ("(a{sv})"),
G_CALLBACK (ip6_config_cb), self);
_set_vpn_state (self, STATE_NEED_AUTH, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
g_signal_connect (priv->proxy, "notify::g-name-owner",
G_CALLBACK (_name_owner_changed), self);
_name_owner_changed (G_OBJECT (priv->proxy), NULL, self);
/* 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.
*/
get_secrets (self, SECRETS_REQ_SYSTEM, NULL);
if (priv->service_running)
return;
if (!nm_vpn_service_daemon_exec (self, &error)) {
_LOGW ("Could not launch the VPN service. error: %s.",
error->message);
nm_vpn_connection_disconnect (self, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE);
}
}
void
nm_vpn_connection_activate (NMVpnConnection *self)
nm_vpn_connection_activate (NMVpnConnection *self,
NMVpnPluginInfo *plugin_info)
{
NMVpnConnectionPrivate *priv;
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);
s_vpn = nm_connection_get_setting_vpn (_get_applied_connection (self));
g_assert (s_vpn);
g_return_if_fail (s_vpn);
service = nm_setting_vpn_get_service_type (s_vpn);
g_return_if_fail (!g_strcmp0 (service, nm_vpn_plugin_info_get_service (plugin_info)));
if (nm_vpn_plugin_info_supports_multiple (plugin_info)) {
const char *path;
path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
if (path)
path = strrchr (path, '/');
g_return_if_fail (path);
priv->bus_name = g_strdup_printf ("%s.Connection_%s", service, &path[1]);
} else
priv->bus_name = g_strdup (service);
priv->connection_can_persist = nm_setting_vpn_get_persistent (s_vpn);
_set_vpn_state (self, STATE_PREPARE, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
priv->plugin_info = g_object_ref (plugin_info);
priv->cancellable = g_cancellable_new ();
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
nm_vpn_connection_get_service (self),
priv->bus_name,
NM_VPN_DBUS_PLUGIN_PATH,
NM_VPN_DBUS_PLUGIN_INTERFACE,
priv->cancellable,
(GAsyncReadyCallback) on_proxy_acquired,
self);
_set_vpn_state (self, STATE_PREPARE, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
}
NMVpnConnectionState
@ -2305,6 +2433,7 @@ dispose (GObject *object)
g_clear_object (&priv->ip4_config);
g_clear_object (&priv->ip6_config);
g_clear_object (&priv->proxy);
g_clear_object (&priv->plugin_info);
fw_call_cleanup (self);
@ -2429,4 +2558,3 @@ nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class)
NMDBUS_TYPE_VPN_CONNECTION_SKELETON,
NULL);
}

View file

@ -26,6 +26,7 @@
#include "nm-device.h"
#include "nm-auth-subject.h"
#include "nm-active-connection.h"
#include "nm-vpn-plugin-info.h"
#define NM_TYPE_VPN_CONNECTION (nm_vpn_connection_get_type ())
#define NM_VPN_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_CONNECTION, NMVpnConnection))
@ -75,7 +76,8 @@ NMVpnConnection * nm_vpn_connection_new (NMSettingsConnection *settings_connecti
const char *specific_object,
NMAuthSubject *subject);
void nm_vpn_connection_activate (NMVpnConnection *self);
void nm_vpn_connection_activate (NMVpnConnection *self,
NMVpnPluginInfo *plugin_info);
NMVpnConnectionState nm_vpn_connection_get_vpn_state (NMVpnConnection *self);
const char * nm_vpn_connection_get_banner (NMVpnConnection *self);
const gchar * nm_vpn_connection_get_service (NMVpnConnection *self);

View file

@ -25,7 +25,7 @@
#include "nm-default.h"
#include "nm-vpn-manager.h"
#include "nm-vpn-service.h"
#include "nm-vpn-plugin-info.h"
#include "nm-vpn-connection.h"
#include "nm-setting-vpn.h"
#include "nm-vpn-dbus-interface.h"
@ -37,26 +37,33 @@ G_DEFINE_TYPE (NMVpnManager, nm_vpn_manager, G_TYPE_OBJECT)
#define NM_VPN_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_MANAGER, NMVpnManagerPrivate))
typedef struct {
GSList *services;
GSList *plugins;
GFileMonitor *monitor_etc;
GFileMonitor *monitor_lib;
guint monitor_id_etc;
guint monitor_id_lib;
/* This is only used for services that don't support multiple
* connections, to guard access to them. */
GHashTable *active_services;
} NMVpnManagerPrivate;
static NMVpnService *
_plugin_info_get_service (NMVpnPluginInfo *plugin_info)
{
if (plugin_info)
return NM_VPN_SERVICE (g_object_get_data (G_OBJECT (plugin_info), "service-instance"));
return NULL;
}
/******************************************************************************/
static void
_plugin_info_set_service (NMVpnPluginInfo *plugin_info, NMVpnService *service)
vpn_state_changed (NMVpnConnection *vpn,
GParamSpec *pspec,
NMVpnManager *manager)
{
g_object_set_data_full (G_OBJECT (plugin_info), "service-instance", service,
(GDestroyNotify) g_object_unref);
NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (manager);
NMActiveConnectionState state = nm_active_connection_get_state (NM_ACTIVE_CONNECTION (vpn));
const char *service_name = nm_vpn_connection_get_service (vpn);
if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
g_hash_table_remove (priv->active_services, service_name);
g_signal_handlers_disconnect_by_func (vpn, vpn_state_changed, manager);
g_object_unref (manager);
}
}
gboolean
@ -64,18 +71,16 @@ nm_vpn_manager_activate_connection (NMVpnManager *manager,
NMVpnConnection *vpn,
GError **error)
{
NMConnection *applied_connection;
NMSettingVpn *s_vpn;
NMVpnService *service;
NMVpnManagerPrivate *priv;
NMVpnPluginInfo *plugin_info;
const char *service_name;
NMDevice *device;
g_return_val_if_fail (NM_IS_VPN_MANAGER (manager), FALSE);
g_return_val_if_fail (NM_IS_VPN_CONNECTION (vpn), FALSE);
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
priv = NM_VPN_MANAGER_GET_PRIVATE (manager);
device = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
g_assert (device);
if ( nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED
@ -85,60 +90,57 @@ nm_vpn_manager_activate_connection (NMVpnManager *manager,
return FALSE;
}
applied_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (vpn));
s_vpn = nm_connection_get_setting_vpn (applied_connection);
g_assert (s_vpn);
service_name = nm_vpn_connection_get_service (vpn);
service_name = nm_setting_vpn_get_service_type (s_vpn);
plugin_info = nm_vpn_plugin_info_list_find_by_service (NM_VPN_MANAGER_GET_PRIVATE (manager)->services, service_name);
service = _plugin_info_get_service (plugin_info);
if (!service) {
plugin_info = nm_vpn_plugin_info_list_find_by_service (priv->plugins, service_name);
if (!plugin_info) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE,
"The VPN service '%s' was not installed.",
service_name);
return FALSE;
}
return nm_vpn_service_activate (service, vpn, error);
if ( !nm_vpn_plugin_info_supports_multiple (plugin_info)
&& g_hash_table_contains (priv->active_services, service_name)) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_AVAILABLE,
"The '%s' plugin only supports a single active connection.",
nm_vpn_plugin_info_get_name (plugin_info));
return FALSE;
}
nm_vpn_connection_activate (vpn, plugin_info);
if (!nm_vpn_plugin_info_supports_multiple (plugin_info)) {
/* Block activations of the connections of the same service type. */
g_hash_table_add (priv->active_services, g_strdup (service_name));
g_signal_connect (vpn, "notify::" NM_ACTIVE_CONNECTION_STATE,
G_CALLBACK (vpn_state_changed),
g_object_ref (manager));
}
return TRUE;
}
gboolean
nm_vpn_manager_deactivate_connection (NMVpnManager *self,
NMVpnConnection *connection,
NMVpnConnectionStateReason reason)
{
return nm_vpn_connection_deactivate (connection, reason, FALSE);
}
/******************************************************************************/
static void
try_add_service (NMVpnManager *self, NMVpnPluginInfo *plugin_info)
try_add_plugin (NMVpnManager *self, NMVpnPluginInfo *plugin_info)
{
NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self);
NMVpnService *service = NULL;
GError *error = NULL;
const char *program;
program = nm_vpn_plugin_info_get_program (plugin_info);
if (!program || !*program)
return;
/* Make sure we don't add dupes.
* We don't really allow reload of the same file. What we do allow is however to
* delete a file and re-add it. */
if (nm_vpn_plugin_info_list_find_by_filename (priv->services,
if (nm_vpn_plugin_info_list_find_by_filename (priv->plugins,
nm_vpn_plugin_info_get_filename (plugin_info)))
return;
if (!nm_vpn_plugin_info_list_add (&priv->services, plugin_info, NULL))
if (!nm_vpn_plugin_info_list_add (&priv->plugins, plugin_info, NULL))
return;
/* New service */
service = nm_vpn_service_new (plugin_info, &error);
if (service) {
_plugin_info_set_service (plugin_info, service);
nm_log_info (LOGD_VPN, "VPN: loaded %s - %s",
nm_vpn_plugin_info_get_name (plugin_info),
nm_vpn_service_get_dbus_service (service));
} else {
nm_log_warn (LOGD_VPN, "failed to load VPN service file %s: %s",
nm_vpn_plugin_info_get_filename (plugin_info),
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
}
static void
@ -151,7 +153,6 @@ vpn_dir_changed (GFileMonitor *monitor,
NMVpnManager *self = NM_VPN_MANAGER (user_data);
NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self);
NMVpnPluginInfo *plugin_info;
NMVpnService *service;
gs_free char *path = NULL;
GError *error = NULL;
@ -161,25 +162,16 @@ vpn_dir_changed (GFileMonitor *monitor,
switch (event_type) {
case G_FILE_MONITOR_EVENT_DELETED:
plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->services, path);
plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->plugins, path);
if (!plugin_info)
break;
nm_log_dbg (LOGD_VPN, "vpn: service file %s deleted", path);
service = _plugin_info_get_service (plugin_info);
if (service) {
/* Stop active VPN connections and destroy the service */
nm_vpn_service_stop_connections (service, FALSE,
NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
nm_log_info (LOGD_VPN, "VPN: unloaded %s", nm_vpn_service_get_dbus_service (service));
_plugin_info_set_service (plugin_info, NULL);
}
nm_vpn_plugin_info_list_remove (&priv->services, plugin_info);
nm_vpn_plugin_info_list_remove (&priv->plugins, plugin_info);
break;
case G_FILE_MONITOR_EVENT_CREATED:
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->services, path);
plugin_info = nm_vpn_plugin_info_list_find_by_filename (priv->plugins, path);
if (plugin_info) {
/* we don't support reloading an existing plugin. You can only remove the file
* and re-add it. By reloading we want to support the use case of installing
@ -202,7 +194,7 @@ vpn_dir_changed (GFileMonitor *monitor,
}
nm_log_dbg (LOGD_VPN, "vpn: service file %s created or modified", path);
try_add_service (self, plugin_info);
try_add_plugin (self, plugin_info);
g_object_unref (plugin_info);
break;
default:
@ -247,13 +239,15 @@ nm_vpn_manager_init (NMVpnManager *self)
* In case of no-conflict, the order doesn't matter. */
infos = _nm_vpn_plugin_info_list_load_dir (conf_dir_lib, TRUE, 0, NULL, NULL);
for (info = infos; info; info = info->next)
try_add_service (self, info->data);
try_add_plugin (self, info->data);
g_slist_free_full (infos, g_object_unref);
infos = _nm_vpn_plugin_info_list_load_dir (conf_dir_etc, TRUE, 0, NULL, NULL);
for (info = infos; info; info = info->next)
try_add_service (self, info->data);
try_add_plugin (self, info->data);
g_slist_free_full (infos, g_object_unref);
priv->active_services = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static void
@ -275,16 +269,10 @@ dispose (GObject *object)
g_clear_object (&priv->monitor_lib);
}
while (priv->services) {
NMVpnPluginInfo *plugin_info = priv->services->data;
NMVpnService *service = _plugin_info_get_service (plugin_info);
while (priv->plugins)
nm_vpn_plugin_info_list_remove (&priv->plugins, priv->plugins->data);
if (service) {
nm_vpn_service_stop_connections (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
_plugin_info_set_service (plugin_info, NULL);
}
nm_vpn_plugin_info_list_remove (&priv->services, plugin_info);
}
g_hash_table_unref (priv->active_services);
G_OBJECT_CLASS (nm_vpn_manager_parent_class)->dispose (object);
}

View file

@ -48,8 +48,4 @@ gboolean nm_vpn_manager_activate_connection (NMVpnManager *manager,
NMVpnConnection *vpn,
GError **error);
gboolean nm_vpn_manager_deactivate_connection (NMVpnManager *manager,
NMVpnConnection *connection,
NMVpnConnectionStateReason reason);
#endif /* __NETWORKMANAGER_VPN_MANAGER_H__ */

View file

@ -1,356 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2005 - 2014 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#include "config.h"
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include "nm-default.h"
#include "nm-vpn-service.h"
#include "nm-vpn-manager.h"
G_DEFINE_TYPE (NMVpnService, nm_vpn_service, G_TYPE_OBJECT)
typedef struct {
NMVpnPluginInfo *plugin_info;
NMVpnConnection *active;
GSList *pending;
guint start_timeout;
GDBusProxy *proxy;
gboolean service_running;
} NMVpnServicePrivate;
#define NM_VPN_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE, NMVpnServicePrivate))
static gboolean start_pending_vpn (NMVpnService *self, GError **error);
static void _name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
NMVpnService *
nm_vpn_service_new (NMVpnPluginInfo *plugin_info, GError **error)
{
NMVpnService *self;
NMVpnServicePrivate *priv;
g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), NULL);
g_return_val_if_fail (nm_vpn_plugin_info_get_filename (plugin_info), NULL);
if (!nm_vpn_plugin_info_get_program (plugin_info)) {
g_set_error (error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_FAILED,
"missing \"program\" entry");
return NULL;
}
self = (NMVpnService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL);
priv = NM_VPN_SERVICE_GET_PRIVATE (self);
priv->plugin_info = g_object_ref (plugin_info);
priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL,
nm_vpn_plugin_info_get_service (plugin_info),
NM_VPN_DBUS_PLUGIN_PATH,
NM_VPN_DBUS_PLUGIN_INTERFACE,
NULL, error);
if (!priv->proxy) {
g_object_unref (self);
return NULL;
}
g_signal_connect (priv->proxy, "notify::g-name-owner",
G_CALLBACK (_name_owner_changed), self);
_name_owner_changed (G_OBJECT (priv->proxy), NULL, self);
return self;
}
const char *
nm_vpn_service_get_dbus_service (NMVpnService *service)
{
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
return nm_vpn_plugin_info_get_service (NM_VPN_SERVICE_GET_PRIVATE (service)->plugin_info);
}
static void
connection_vpn_state_changed (NMVpnConnection *connection,
NMVpnConnectionState new_state,
NMVpnConnectionState old_state,
NMVpnConnectionStateReason reason,
gpointer user_data)
{
NMVpnService *self = NM_VPN_SERVICE (user_data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
if (new_state == NM_VPN_CONNECTION_STATE_FAILED ||
new_state == NM_VPN_CONNECTION_STATE_DISCONNECTED) {
g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_vpn_state_changed), self);
if (connection == priv->active) {
priv->active = NULL;
start_pending_vpn (self, NULL);
} else
priv->pending = g_slist_remove (priv->pending, connection);
g_object_unref (connection);
}
}
void
nm_vpn_service_stop_connections (NMVpnService *service,
gboolean quitting,
NMVpnConnectionStateReason reason)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
GSList *iter;
/* Just add priv->active to the beginning of priv->pending,
* since we're going to clear priv->pending immediately anyway.
*/
if (priv->active) {
priv->pending = g_slist_prepend (priv->pending, priv->active);
priv->active = NULL;
}
for (iter = priv->pending; iter; iter = iter->next) {
NMVpnConnection *vpn = NM_VPN_CONNECTION (iter->data);
g_signal_handlers_disconnect_by_func (vpn, G_CALLBACK (connection_vpn_state_changed), service);
if (quitting) {
/* Deactivate to allow pre-down before disconnecting */
nm_vpn_connection_deactivate (vpn, reason, quitting);
}
nm_vpn_connection_disconnect (vpn, reason, quitting);
g_object_unref (vpn);
}
g_clear_pointer (&priv->pending, g_slist_free);
}
static gboolean
_daemon_exec_timeout (gpointer data)
{
NMVpnService *self = NM_VPN_SERVICE (data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
nm_log_warn (LOGD_VPN, "VPN service '%s' start timed out", nm_vpn_plugin_info_get_name (priv->plugin_info));
priv->start_timeout = 0;
nm_vpn_service_stop_connections (self, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT);
return G_SOURCE_REMOVE;
}
static gboolean
nm_vpn_service_daemon_exec (NMVpnService *service, GError **error)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
GPid pid;
char *vpn_argv[2];
gboolean success = FALSE;
GError *spawn_error = NULL;
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE);
vpn_argv[0] = (char *) nm_vpn_plugin_info_get_program (priv->plugin_info);
vpn_argv[1] = NULL;
g_assert (vpn_argv[0]);
success = g_spawn_async (NULL, vpn_argv, NULL, 0, nm_utils_setpgid, NULL, &pid, &spawn_error);
if (success) {
nm_log_info (LOGD_VPN, "VPN service '%s' started (%s), PID %ld",
nm_vpn_plugin_info_get_name (priv->plugin_info),
nm_vpn_service_get_dbus_service (service),
(long int) pid);
priv->start_timeout = g_timeout_add_seconds (5, _daemon_exec_timeout, service);
} else {
nm_log_warn (LOGD_VPN, "VPN service '%s': could not launch the VPN service. error: (%d) %s.",
nm_vpn_plugin_info_get_name (priv->plugin_info),
spawn_error ? spawn_error->code : -1,
spawn_error && spawn_error->message ? spawn_error->message : "(unknown)");
g_set_error (error,
NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
"%s", spawn_error ? spawn_error->message : "unknown g_spawn_async() error");
nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED);
if (spawn_error)
g_error_free (spawn_error);
}
return success;
}
static gboolean
start_active_vpn (NMVpnService *self, GError **error)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
if (!priv->active)
return TRUE;
if (priv->service_running) {
/* Just activate the VPN */
nm_vpn_connection_activate (priv->active);
return TRUE;
} else if (priv->start_timeout == 0) {
/* VPN service not running, start it */
nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", nm_vpn_plugin_info_get_name (priv->plugin_info));
return nm_vpn_service_daemon_exec (self, error);
}
/* Already started VPN service, waiting for it to appear on D-Bus */
return TRUE;
}
static gboolean
start_pending_vpn (NMVpnService *self, GError **error)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
g_assert (priv->active == NULL);
if (!priv->pending)
return TRUE;
/* Make next VPN active */
priv->active = g_slist_nth_data (priv->pending, 0);
priv->pending = g_slist_remove (priv->pending, priv->active);
return start_active_vpn (self, error);
}
gboolean
nm_vpn_service_activate (NMVpnService *service,
NMVpnConnection *vpn,
GError **error)
{
NMVpnServicePrivate *priv;
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE);
g_return_val_if_fail (NM_IS_VPN_CONNECTION (vpn), FALSE);
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
priv = NM_VPN_SERVICE_GET_PRIVATE (service);
g_signal_connect (vpn, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
G_CALLBACK (connection_vpn_state_changed),
service);
/* Queue up the new VPN connection */
priv->pending = g_slist_append (priv->pending, g_object_ref (vpn));
/* Tell the active VPN to deactivate and wait for it to quit before we
* start the next VPN. The just-queued VPN will then be started from
* connection_vpn_state_changed().
*/
if (priv->active) {
nm_vpn_connection_deactivate (priv->active, NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED, FALSE);
return TRUE;
}
/* Otherwise start the next VPN */
return start_pending_vpn (service, error);
}
static void
_name_owner_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
NMVpnService *service = NM_VPN_SERVICE (user_data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
gboolean success;
char *owner;
owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
/* Service changed, no need to wait for the timeout any longer */
if (priv->start_timeout) {
g_source_remove (priv->start_timeout);
priv->start_timeout = 0;
}
if (owner && !priv->service_running) {
/* service appeared */
priv->service_running = TRUE;
nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", nm_vpn_plugin_info_get_name (priv->plugin_info));
/* Expect success because the VPN service has already appeared */
success = start_active_vpn (service, NULL);
g_warn_if_fail (success);
} else if (!owner && priv->service_running) {
/* service went away */
priv->service_running = FALSE;
nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", nm_vpn_plugin_info_get_name (priv->plugin_info));
nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
}
g_free (owner);
}
/******************************************************************************/
static void
nm_vpn_service_init (NMVpnService *self)
{
}
static void
dispose (GObject *object)
{
NMVpnService *self = NM_VPN_SERVICE (object);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
nm_clear_g_source (&priv->start_timeout);
g_clear_object (&priv->plugin_info);
/* VPNService owner is required to stop connections before releasing */
g_assert (priv->active == NULL);
g_assert (priv->pending == NULL);
if (priv->proxy) {
g_signal_handlers_disconnect_by_func (priv->proxy,
G_CALLBACK (_name_owner_changed),
self);
g_clear_object (&priv->proxy);
}
G_OBJECT_CLASS (nm_vpn_service_parent_class)->dispose (object);
}
static void
nm_vpn_service_class_init (NMVpnServiceClass *service_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (service_class);
g_type_class_add_private (service_class, sizeof (NMVpnServicePrivate));
/* virtual methods */
object_class->dispose = dispose;
}

View file

@ -1,60 +0,0 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2005 - 2011 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#ifndef __NETWORKMANAGER_VPN_SERVICE_H__
#define __NETWORKMANAGER_VPN_SERVICE_H__
#include "nm-default.h"
#include "nm-device.h"
#include "nm-vpn-connection.h"
#include "nm-vpn-plugin-info.h"
#define NM_TYPE_VPN_SERVICE (nm_vpn_service_get_type ())
#define NM_VPN_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_SERVICE, NMVpnService))
#define NM_VPN_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_VPN_SERVICE, NMVpnServiceClass))
#define NM_IS_VPN_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_VPN_SERVICE))
#define NM_IS_VPN_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_VPN_SERVICE))
#define NM_VPN_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_VPN_SERVICE, NMVpnServiceClass))
typedef struct {
GObject parent;
} NMVpnService;
typedef struct {
GObjectClass parent;
} NMVpnServiceClass;
GType nm_vpn_service_get_type (void);
NMVpnService * nm_vpn_service_new (NMVpnPluginInfo *plugin_info, GError **error);
/* Returns the VPN service's D-Bus service name */
const char *nm_vpn_service_get_dbus_service (NMVpnService *service);
gboolean nm_vpn_service_activate (NMVpnService *service,
NMVpnConnection *vpn,
GError **error);
void nm_vpn_service_stop_connections (NMVpnService *service,
gboolean quitting,
NMVpnConnectionStateReason reason);
#endif /* NM_VPN_VPN_SERVICE_H */