Commit 67b228d1 authored by Dan Williams's avatar Dan Williams
parents 26a65f4f 02252224
......@@ -26,7 +26,7 @@ dbusservice_DATA = \
nm-avahi-autoipd.conf
libexec_PROGRAMS = \
nm-dispatcher.action \
nm-dispatcher \
nm-avahi-autoipd.action
......@@ -38,13 +38,13 @@ nm_avahi_autoipd_action_LDADD = \
$(GLIB_LIBS)
nm_dispatcher_action_SOURCES = \
nm-dispatcher-action.c \
nm-dispatcher-action.h \
nm_dispatcher_SOURCES = \
nm-dispatcher.c \
nm-dispatcher-api.h \
nm-dispatcher-utils.c \
nm-dispatcher-utils.h
nm_dispatcher_action_LDADD = \
nm_dispatcher_LDADD = \
$(top_builddir)/libnm-util/libnm-util.la \
$(DBUS_LIBS) \
$(GLIB_LIBS)
......
......@@ -20,6 +20,10 @@
#include <dbus/dbus-glib.h>
#define NMD_SCRIPT_DIR NMCONFDIR "/dispatcher.d"
#define NMD_PRE_UP_DIR NMD_SCRIPT_DIR "/pre-up.d"
#define NMD_PRE_DOWN_DIR NMD_SCRIPT_DIR "/pre-down.d"
/* dbus-glib types for dispatcher call return value */
#define DISPATCHER_TYPE_RESULT (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID))
#define DISPATCHER_TYPE_RESULT_ARRAY (dbus_g_type_get_collection ("GPtrArray", DISPATCHER_TYPE_RESULT))
......@@ -36,6 +40,19 @@
#define NMD_DEVICE_PROPS_STATE "state"
#define NMD_DEVICE_PROPS_PATH "path"
/* Actions */
#define NMD_ACTION_HOSTNAME "hostname"
#define NMD_ACTION_PRE_UP "pre-up"
#define NMD_ACTION_UP "up"
#define NMD_ACTION_PRE_DOWN "pre-down"
#define NMD_ACTION_DOWN "down"
#define NMD_ACTION_VPN_PRE_UP "vpn-pre-up"
#define NMD_ACTION_VPN_UP "vpn-up"
#define NMD_ACTION_VPN_PRE_DOWN "vpn-pre-down"
#define NMD_ACTION_VPN_DOWN "vpn-down"
#define NMD_ACTION_DHCP4_CHANGE "dhcp4-change"
#define NMD_ACTION_DHCP6_CHANGE "dhcp6-change"
typedef enum {
DISPATCH_RESULT_UNKNOWN = 0,
DISPATCH_RESULT_SUCCESS = 1,
......
......@@ -30,7 +30,7 @@
#include <nm-setting-ip6-config.h>
#include <nm-setting-connection.h>
#include "nm-dispatcher-action.h"
#include "nm-dispatcher-api.h"
#include "nm-utils.h"
#include "nm-dispatcher-utils.h"
......
......@@ -37,12 +37,10 @@
#include <dbus/dbus-glib.h>
#include "nm-dispatcher-action.h"
#include "nm-dispatcher-api.h"
#include "nm-dispatcher-utils.h"
#include "nm-glib-compat.h"
#define NMD_SCRIPT_DIR NMCONFDIR "/dispatcher.d"
static GMainLoop *loop = NULL;
static gboolean debug = FALSE;
......@@ -322,33 +320,33 @@ script_timeout_cb (gpointer user_data)
}
static inline gboolean
check_permissions (struct stat *s, GError **error)
check_permissions (struct stat *s, const char **out_error_msg)
{
g_return_val_if_fail (s != NULL, FALSE);
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
g_return_val_if_fail (out_error_msg != NULL, FALSE);
g_return_val_if_fail (*out_error_msg == NULL, FALSE);
/* Only accept regular files */
if (!S_ISREG (s->st_mode)) {
g_set_error (error, 0, 0, "not a regular file.");
*out_error_msg = "not a regular file.";
return FALSE;
}
/* Only accept files owned by root */
if (s->st_uid != 0) {
g_set_error (error, 0, 0, "not owned by root.");
*out_error_msg = "not owned by root.";
return FALSE;
}
/* Only accept files not writable by group or other, and not SUID */
if (s->st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
g_set_error (error, 0, 0, "writable by group or other, or set-UID.");
*out_error_msg = "writable by group or other, or set-UID.";
return FALSE;
}
/* Only accept files executable by the owner */
if (!(s->st_mode & S_IXUSR)) {
g_set_error (error, 0, 0, "not executable by owner.");
*out_error_msg = "not executable by owner.";
return FALSE;
}
......@@ -385,6 +383,8 @@ child_setup (gpointer user_data G_GNUC_UNUSED)
setpgid (pid, pid);
}
#define SCRIPT_TIMEOUT 600 /* 10 minutes */
static void
dispatch_one_script (Request *request)
{
......@@ -402,7 +402,7 @@ dispatch_one_script (Request *request)
if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, child_setup, request, &script->pid, &error)) {
request->script_watch_id = g_child_watch_add (script->pid, (GChildWatchFunc) script_watch_cb, script);
request->script_timeout_id = g_timeout_add_seconds (20, script_timeout_cb, script);
request->script_timeout_id = g_timeout_add_seconds (SCRIPT_TIMEOUT, script_timeout_cb, script);
} else {
g_warning ("Failed to execute script '%s': (%d) %s",
script->script, error->code, error->message);
......@@ -416,16 +416,26 @@ dispatch_one_script (Request *request)
}
static GSList *
find_scripts (void)
find_scripts (const char *str_action)
{
GDir *dir;
const char *filename;
GSList *sorted = NULL;
GError *error = NULL;
const char *dirname;
if ( strcmp (str_action, NMD_ACTION_PRE_UP) == 0
|| strcmp (str_action, NMD_ACTION_VPN_PRE_UP) == 0)
dirname = NMD_PRE_UP_DIR;
else if ( strcmp (str_action, NMD_ACTION_PRE_DOWN) == 0
|| strcmp (str_action, NMD_ACTION_VPN_PRE_DOWN) == 0)
dirname = NMD_PRE_DOWN_DIR;
else
dirname = NMD_SCRIPT_DIR;
if (!(dir = g_dir_open (NMD_SCRIPT_DIR, 0, &error))) {
if (!(dir = g_dir_open (dirname, 0, &error))) {
g_warning ("Failed to open dispatcher directory '%s': (%d) %s",
NMD_SCRIPT_DIR, error->code, error->message);
dirname, error->code, error->message);
g_error_free (error);
return NULL;
}
......@@ -434,19 +444,19 @@ find_scripts (void)
char *path;
struct stat st;
int err;
const char *err_msg = NULL;
if (!check_filename (filename))
continue;
path = g_build_filename (NMD_SCRIPT_DIR, filename, NULL);
path = g_build_filename (dirname, filename, NULL);
err = stat (path, &st);
if (err)
g_warning ("Failed to stat '%s': %d", path, err);
else if (!check_permissions (&st, &error)) {
g_warning ("Cannot execute '%s': %s", path, error->message);
g_clear_error (&error);
} else {
else if (!check_permissions (&st, &err_msg))
g_warning ("Cannot execute '%s': %s", path, err_msg);
else {
/* success */
sorted = g_slist_insert_sorted (sorted, path, (GCompareFunc) g_strcmp0);
}
......@@ -478,7 +488,7 @@ impl_dispatch (Handler *h,
char **p;
char *iface = NULL;
sorted_scripts = find_scripts ();
sorted_scripts = find_scripts (str_action);
if (!sorted_scripts) {
dbus_g_method_return (context, g_ptr_array_new ());
......
[D-BUS Service]
Name=org.freedesktop.nm_dispatcher
Exec=@libexecdir@/nm-dispatcher.action
Exec=@libexecdir@/nm-dispatcher
User=root
SystemdService=dbus-org.freedesktop.nm-dispatcher.service
......@@ -29,7 +29,7 @@
#include "nm-setting-connection.h"
#include "nm-dispatcher-utils.h"
#include "nm-dbus-glib-types.h"
#include "nm-dispatcher-action.h"
#include "nm-dispatcher-api.h"
#include "nm-utils.h"
/*******************************************/
......
......@@ -441,7 +441,7 @@ fi
%{_bindir}/nm-online
%{_libexecdir}/nm-dhcp-helper
%{_libexecdir}/nm-avahi-autoipd.action
%{_libexecdir}/nm-dispatcher.action
%{_libexecdir}/nm-dispatcher
%dir %{_libdir}/NetworkManager
%{_libdir}/NetworkManager/libnm-settings-plugin*.so
%{_mandir}/man1/*
......
......@@ -4,7 +4,7 @@ Description=Network Manager Script Dispatcher Service
[Service]
Type=dbus
BusName=org.freedesktop.nm_dispatcher
ExecStart=@libexecdir@/nm-dispatcher.action
ExecStart=@libexecdir@/nm-dispatcher
# We want to allow scripts to spawn long-running daemons, so tell
# systemd to not clean up when nm-dispatcher exits
......
......@@ -53,9 +53,9 @@
<title>Dispatcher scripts</title>
<para>
NetworkManager will execute scripts in the
/etc/NetworkManager/dispatcher.d directory in alphabetical order
in response to network events. Each script should be a regular
executable file, owned by root. Furthermore, it must not be
/etc/NetworkManager/dispatcher.d directory or subdirectories in
alphabetical order in response to network events. Each script should
be a regular executable file owned by root. Furthermore, it must not be
writable by group or other, and not setuid.
</para>
<para>
......@@ -64,22 +64,65 @@
</para>
<para>The actions are:</para>
<variablelist class="dispatcher-options">
<varlistentry>
<term><varname>pre-up</varname></term>
<listitem><para>The interface is connected to the network but is not
yet fully activated. Scripts acting on this event must be placed or
symlinked into the /etc/NetworkManager/dispatcher.d/pre-up.d directory,
and NetworkManager will wait for script execution to complete before
indicating to applications that the interface is fully activated.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>up</varname></term>
<listitem><para>The interface has been activated.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>pre-down</varname></term>
<listitem><para>The interface will be deactivated but has not yet been
disconnected from the network. Scripts acting on this event must be
placed or symlinked into the /etc/NetworkManager/dispatcher.d/pre-down.d
directory, and NetworkManager will wait for script execution to complete
before disconnecting the interface from its network. Note that this
event is not emitted for forced disconnections, like when carrier is
lost or a wireless signal fades. It is only emitted when there is
an opportunity to cleanly handle a network disconnection event.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>down</varname></term>
<listitem><para>
The interface has been deactivated.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>vpn-pre-up</varname></term>
<listitem><para>The VPN is connected to the network but is not yet
fully activated. Scripts acting on this event must be placed or
symlinked into the /etc/NetworkManager/dispatcher.d/pre-up.d directory,
and NetworkManager will wait for script execution to complete before
indicating to applications that the VPN is fully activated.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>vpn-up</varname></term>
<listitem><para>
A VPN connection has been activated.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>vpn-pre-down</varname></term>
<listitem><para>The VPN will be deactivated but has not yet been
disconnected from the network. Scripts acting on this event must be
placed or symlinked into the /etc/NetworkManager/dispatcher.d/pre-down.d
directory, and NetworkManager will wait for script execution to complete
before disconnecting the VPN from its network. Note that this
event is not emitted for forced disconnections, like when the VPN
terminates unexpectedly or general connectivity is lost. It is only
emitted when there is an opportunity to cleanly handle a VPN
disconnection event.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>vpn-down</varname></term>
<listitem><para>
......
This diff is collapsed.
......@@ -218,7 +218,7 @@ typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
GType nm_device_get_type (void);
const char * nm_device_get_path (NMDevice *dev);
void nm_device_set_path (NMDevice *dev, const char *path);
void nm_device_dbus_export (NMDevice *device);
const char * nm_device_get_udi (NMDevice *dev);
const char * nm_device_get_iface (NMDevice *dev);
......@@ -228,7 +228,6 @@ const char * nm_device_get_ip_iface (NMDevice *dev);
int nm_device_get_ip_ifindex(NMDevice *dev);
const char * nm_device_get_driver (NMDevice *dev);
const char * nm_device_get_driver_version (NMDevice *dev);
const char * nm_device_get_firmware_version (NMDevice *dev);
const char * nm_device_get_type_desc (NMDevice *dev);
NMDeviceType nm_device_get_device_type (NMDevice *dev);
......@@ -248,9 +247,7 @@ void nm_device_set_vpn6_config (NMDevice *dev, NMIP6Config *config)
void nm_device_capture_initial_config (NMDevice *dev);
/* Master */
gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure);
GSList * nm_device_master_get_slaves (NMDevice *dev);
gboolean nm_device_is_master (NMDevice *dev);
/* Slave */
NMDevice * nm_device_get_master (NMDevice *dev);
......@@ -275,10 +272,7 @@ gboolean nm_device_complete_connection (NMDevice *device,
gboolean nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection);
gboolean nm_device_can_assume_connections (NMDevice *device);
NMConnection * nm_device_find_assumable_connection (NMDevice *device,
const GSList *connections);
gboolean nm_device_can_assume_active_connection (NMDevice *device);
gboolean nm_device_spec_match_list (NMDevice *device, const GSList *specs);
......@@ -313,19 +307,18 @@ typedef enum {
} NMUnmanagedFlags;
gboolean nm_device_get_managed (NMDevice *device);
gboolean nm_device_get_default_unmanaged (NMDevice *device);
gboolean nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag);
void nm_device_set_unmanaged (NMDevice *device,
NMUnmanagedFlags flag,
gboolean unmanaged,
NMDeviceStateReason reason);
void nm_device_set_unmanaged_quitting (NMDevice *device);
void nm_device_set_initial_unmanaged_flag (NMDevice *device,
NMUnmanagedFlags flag,
gboolean unmanaged);
gboolean nm_device_get_is_nm_owned (NMDevice *device);
gboolean nm_device_set_is_nm_owned (NMDevice *device,
gboolean is_nm_owned);
void nm_device_set_nm_owned (NMDevice *device);
gboolean nm_device_get_autoconnect (NMDevice *device);
......@@ -341,8 +334,6 @@ void nm_device_queue_state (NMDevice *self,
NMDeviceState state,
NMDeviceStateReason reason);
void nm_device_queue_ip_config_change (NMDevice *self);
gboolean nm_device_get_firmware_missing (NMDevice *self);
void nm_device_queue_activation (NMDevice *device, NMActRequest *req);
......@@ -356,10 +347,6 @@ gboolean nm_device_has_pending_action (NMDevice *device);
GPtrArray *nm_device_get_available_connections (NMDevice *device,
const char *specific_object);
const char *nm_device_get_physical_port_id (NMDevice *device);
guint32 nm_device_get_mtu (NMDevice *device);
gboolean nm_device_connection_is_available (NMDevice *device,
NMConnection *connection,
gboolean allow_device_override);
......
......@@ -54,6 +54,7 @@
#include "nm-config.h"
#include "nm-posix-signals.h"
#include "nm-session-monitor.h"
#include "nm-dispatcher.h"
#if !defined(NM_DIST_VERSION)
# define NM_DIST_VERSION VERSION
......@@ -604,6 +605,8 @@ main (int argc, char *argv[])
dhcp_mgr = nm_dhcp_manager_get ();
g_assert (dhcp_mgr != NULL);
nm_dispatcher_init ();
settings = nm_settings_new (&error);
if (!settings) {
nm_log_err (LOGD_CORE, "failed to initialize settings storage: %s",
......
This diff is collapsed.
......@@ -32,9 +32,11 @@
typedef enum {
DISPATCHER_ACTION_HOSTNAME,
DISPATCHER_ACTION_PRE_UP,
DISPATCHER_ACTION_UP,
DISPATCHER_ACTION_PRE_DOWN,
DISPATCHER_ACTION_DOWN,
DISPATCHER_ACTION_VPN_PRE_UP,
DISPATCHER_ACTION_VPN_UP,
DISPATCHER_ACTION_VPN_PRE_DOWN,
DISPATCHER_ACTION_VPN_DOWN,
......@@ -42,23 +44,38 @@ typedef enum {
DISPATCHER_ACTION_DHCP6_CHANGE
} DispatcherAction;
typedef void (*DispatcherFunc) (gconstpointer call, gpointer user_data);
typedef void (*DispatcherFunc) (guint call_id, gpointer user_data);
gconstpointer nm_dispatcher_call (DispatcherAction action,
gboolean nm_dispatcher_call (DispatcherAction action,
NMConnection *connection,
NMDevice *device,
DispatcherFunc callback,
gpointer user_data,
guint *out_call_id);
gboolean nm_dispatcher_call_sync (DispatcherAction action,
NMConnection *connection,
NMDevice *device,
DispatcherFunc callback,
gpointer user_data);
NMDevice *device);
gboolean nm_dispatcher_call_vpn (DispatcherAction action,
NMConnection *connection,
NMDevice *parent_device,
const char *vpn_iface,
NMIP4Config *vpn_ip4_config,
NMIP6Config *vpn_ip6_config,
DispatcherFunc callback,
gpointer user_data,
guint *out_call_id);
gconstpointer nm_dispatcher_call_vpn (DispatcherAction action,
gboolean nm_dispatcher_call_vpn_sync (DispatcherAction action,
NMConnection *connection,
NMDevice *device,
NMDevice *parent_device,
const char *vpn_iface,
NMIP4Config *vpn_ip4_config,
NMIP6Config *vpn_ip6_config,
DispatcherFunc callback,
gpointer user_data);
NMIP6Config *vpn_ip6_config);
void nm_dispatcher_call_cancel (guint call_id);
void nm_dispatcher_call_cancel (gconstpointer call);
void nm_dispatcher_init (void);
#endif /* NM_DISPATCHER_H */
......@@ -727,20 +727,27 @@ remove_device (NMManager *manager, NMDevice *device, gboolean quitting)
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
if (nm_device_get_managed (device)) {
/* Leave configured interfaces up when quitting so they can be
* taken over again if NM starts up, and to ensure connectivity while
* NM is gone. Assumed connections don't get taken down even if they
* haven't been fully activated.
*/
if ( !nm_device_can_assume_connections (device)
|| (nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED)
|| !quitting) {
NMActRequest *req = nm_device_get_act_request (device);
NMActRequest *req = nm_device_get_act_request (device);
gboolean unmanage = FALSE;
if (!req || !nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (req)))
nm_device_set_unmanaged (device, NM_UNMANAGED_INTERNAL, TRUE, NM_DEVICE_STATE_REASON_REMOVED);
}
/* Leave activated interfaces up when quitting so their configuration
* can be taken over when NM restarts. This ensures connectivity while
* NM is stopped. Devices which do not support connection assumption
* cannot be left up.
*/
if (!quitting) /* Forced removal; device already gone */
unmanage = TRUE;
else if (!nm_device_can_assume_active_connection (device))
unmanage = TRUE;
else if (!req)
unmanage = TRUE;
if (unmanage) {
if (quitting)
nm_device_set_unmanaged_quitting (device);
else
nm_device_set_unmanaged (device, NM_UNMANAGED_INTERNAL, TRUE, NM_DEVICE_STATE_REASON_REMOVED);
}
}
g_signal_handlers_disconnect_matched (device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, manager);
......@@ -754,8 +761,7 @@ remove_device (NMManager *manager, NMDevice *device, gboolean quitting)
nm_dbus_manager_unregister_object (priv->dbus_mgr, device);
g_object_unref (device);
if (priv->startup)
check_if_startup_complete (manager);
check_if_startup_complete (manager);
}
static void
......@@ -1087,7 +1093,7 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
}
if (device) {
nm_device_set_is_nm_owned (device, TRUE);
nm_device_set_nm_owned (device);
add_device (self, device, FALSE);
g_object_unref (device);
}
......@@ -1683,8 +1689,6 @@ add_device (NMManager *self, NMDevice *device, gboolean generate_con)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const char *iface, *driver, *type_desc;
char *path;
static guint32 devcount = 0;
const GSList *unmanaged_specs;
gboolean user_unmanaged, sleeping;
NMConnection *connection = NULL;
......@@ -1761,11 +1765,7 @@ add_device (NMManager *self, NMDevice *device, gboolean generate_con)
sleeping = manager_sleeping (self);
nm_device_set_initial_unmanaged_flag (device, NM_UNMANAGED_INTERNAL, sleeping);
path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++);
nm_device_set_path (device, path);
nm_dbus_manager_register_object (priv->dbus_mgr, path, device);
nm_log_info (LOGD_CORE, "(%s): exported as %s", iface, path);
g_free (path);
nm_device_dbus_export (device);
/* Don't generate a connection e.g. for devices NM just created, or
* for the loopback, or when we're sleeping. */
......
......@@ -326,7 +326,7 @@ _set_hostname (NMPolicy *policy,
nm_dns_manager_set_hostname (priv->dns_manager, priv->cur_hostname);
if (set_system_hostname (priv->cur_hostname, msg))
nm_dispatcher_call (DISPATCHER_ACTION_HOSTNAME, NULL, NULL, NULL, NULL);
nm_dispatcher_call (DISPATCHER_ACTION_HOSTNAME, NULL, NULL, NULL, NULL, NULL);
}
static void
......
This diff is collapsed.
......@@ -74,10 +74,14 @@ void nm_vpn_connection_activate (NMVPNConnection *connect
NMConnection * nm_vpn_connection_get_connection (NMVPNConnection *connection);
NMVPNConnectionState nm_vpn_connection_get_vpn_state (NMVPNConnection *connection);
const char * nm_vpn_connection_get_banner (NMVPNConnection *connection);
void nm_vpn_connection_fail (NMVPNConnection *connection,
NMVPNConnectionStateReason reason);
gboolean nm_vpn_connection_deactivate (NMVPNConnection *connection,
NMVPNConnectionStateReason reason,
gboolean quitting);
void nm_vpn_connection_disconnect (NMVPNConnection *connection,
NMVPNConnectionStateReason reason);
NMVPNConnectionStateReason reason,
gboolean quitting);
NMIP4Config * nm_vpn_connection_get_ip4_config (NMVPNConnection *connection);
NMIP6Config * nm_vpn_connection_get_ip6_config (NMVPNConnection *connection);
const char * nm_vpn_connection_get_ip_iface (NMVPNConnection *connection);
......
......@@ -38,8 +38,6 @@ 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 {
gboolean disposed;
GHashTable *services;
GFileMonitor *monitor;
guint monitor_id;
......@@ -77,39 +75,11 @@ get_service_by_namefile (NMVPNManager *self, const char *namefile)
return NULL;
}
static NMVPNConnection *
find_active_vpn_connection (NMVPNManager *self, NMConnection *connection)
{
NMVPNManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self);
GHashTableIter iter;
gpointer data;
const GSList *active, *aiter;
NMVPNConnection *found = NULL;
g_return_val_if_fail (connection, NULL);
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
g_hash_table_iter_init (&iter, priv->services);
while (g_hash_table_iter_next (&iter, NULL, &data) && (found == NULL)) {
active = nm_vpn_service_get_active_connections (NM_VPN_SERVICE (data));