iface-helper: add nm-iface-helper for dynamic configure-then-quit support
authorDan Williams <dcbw@redhat.com>
Wed, 29 Oct 2014 14:12:18 +0000 (09:12 -0500)
committerDan Williams <dcbw@redhat.com>
Fri, 7 Nov 2014 18:18:33 +0000 (12:18 -0600)
When quitting, the Manager asks each device to spawn the interface helper,
which persists and manages dynamic address on the interface after NetworkManager
is gone.  If the dynamic address cannot be maintaned, the helper quits and
the interface's address may be removed when their lifetime runs out.

To keep the helper as simple as possible, NetworkManager passes most of the
configuration on the command-line, including some properties of the device's
current state, which are necessary for the helper to maintain DHCP leases
or IPv6 SLAAC addresses.

14 files changed:
.gitignore
contrib/fedora/rpm/NetworkManager.spec
po/POTFILES.in
src/Makefile.am
src/devices/nm-device.c
src/devices/nm-device.h
src/dhcp-manager/nm-dhcp-manager.c
src/main.c
src/nm-config.c
src/nm-iface-helper.c [new file with mode: 0644]
src/nm-ip4-config.c
src/nm-ip6-config.c
src/nm-manager.c
src/nm-manager.h

index d7c47e3..8aa06ca 100644 (file)
@@ -232,6 +232,7 @@ valgrind-*.log
 /src/dhcp-manager/tests/test-dhcp-options
 /src/dhcp-manager/tests/test-dhcp-utils
 /src/dnsmasq-manager/tests/test-dnsmasq-utils
+/src/nm-iface-helper
 /src/settings/plugins/ibft/tests/test-ibft
 /src/settings/plugins/ifcfg-rh/tests/network-scripts/*-Test_Write_*
 /src/settings/plugins/ifcfg-rh/tests/network-scripts/Test_Write_*
index 20d1a43..b0c7679 100644 (file)
@@ -518,6 +518,7 @@ fi
 %{_libexecdir}/nm-dhcp-helper
 %{_libexecdir}/nm-avahi-autoipd.action
 %{_libexecdir}/nm-dispatcher
+%{_libexecdir}/nm-iface-helper
 %dir %{_libdir}/NetworkManager
 %{_libdir}/NetworkManager/libnm-settings-plugin*.so
 %{_mandir}/man1/*
index b964b93..0571f0e 100644 (file)
@@ -147,6 +147,7 @@ src/devices/wifi/nm-wifi-ap-utils.c
 src/devices/wimax/nm-device-wimax.c
 src/devices/wwan/nm-modem-broadband.c
 src/nm-config.c
+src/nm-iface-helper.c
 src/nm-logging.c
 src/nm-manager.c
 src/nm-sleep-monitor-systemd.c
index e186632..eb5c64c 100644 (file)
@@ -48,7 +48,10 @@ AM_CPPFLAGS =                                \
 # primarily for its side effect of removing duplicates.
 AM_CPPFLAGS += $(foreach d,$(sort $(dir $(libNetworkManager_la_SOURCES))),-I$(top_srcdir)/src/$d)
 
-noinst_LTLIBRARIES = libNetworkManager.la libsystemd-dhcp.la
+noinst_LTLIBRARIES = \
+       libNetworkManager.la \
+       libnm-iface-helper.la \
+       libsystemd-dhcp.la
 
 ######################
 # libsystemd-dhcp
@@ -458,6 +461,86 @@ NetworkManager_LDFLAGS = -rdynamic
 
 ######################
 
+libnm_iface_helper_la_SOURCES = \
+       dhcp-manager/nm-dhcp-client.c \
+       dhcp-manager/nm-dhcp-client.h \
+       dhcp-manager/nm-dhcp-utils.c \
+       dhcp-manager/nm-dhcp-utils.h \
+       dhcp-manager/nm-dhcp-manager.c \
+       dhcp-manager/nm-dhcp-manager.h \
+       \
+       platform/nm-linux-platform.c \
+       platform/nm-linux-platform.h \
+       platform/nm-platform.c \
+       platform/nm-platform.h \
+       platform/wifi/wifi-utils-nl80211.c \
+       platform/wifi/wifi-utils-nl80211.h \
+       platform/wifi/wifi-utils-private.h \
+       platform/wifi/wifi-utils.c \
+       platform/wifi/wifi-utils.h \
+       \
+       rdisc/nm-fake-rdisc.c \
+       rdisc/nm-fake-rdisc.h \
+       rdisc/nm-lndp-rdisc.c \
+       rdisc/nm-lndp-rdisc.h \
+       rdisc/nm-rdisc.c \
+       rdisc/nm-rdisc.h \
+       \
+       nm-ip4-config.c \
+       nm-ip4-config.h \
+       nm-ip6-config.c \
+       nm-ip6-config.h \
+       \
+       nm-enum-types.c \
+       nm-enum-types.h \
+       nm-logging.c \
+       nm-logging.h \
+       nm-posix-signals.c \
+       nm-posix-signals.h \
+       NetworkManagerUtils.c \
+       NetworkManagerUtils.h
+
+if WITH_WEXT
+libnm_iface_helper_la_SOURCES += \
+       platform/wifi/wifi-utils-wext.c \
+       platform/wifi/wifi-utils-wext.h
+endif
+
+libnm_iface_helper_la_LIBADD = \
+       $(top_builddir)/libnm-core/libnm-core.la \
+       libsystemd-dhcp.la \
+       $(DBUS_LIBS) \
+       $(GLIB_LIBS) \
+       $(GUDEV_LIBS) \
+       $(LIBNL_LIBS) \
+       $(LIBNDP_LIBS) \
+       $(LIBDL) \
+       $(LIBM)
+
+libexec_PROGRAMS = nm-iface-helper
+
+nm_iface_helper_SOURCES = \
+       dhcp-manager/nm-dhcp-systemd.h \
+       dhcp-manager/nm-dhcp-systemd.c \
+       nm-iface-helper.c \
+       main-utils.c \
+       main-utils.h
+
+nm_iface_helper_LDADD = \
+       $(top_builddir)/libnm-core/libnm-core.la \
+       libsystemd-dhcp.la \
+       libnm-iface-helper.la \
+       $(DBUS_LIBS) \
+       $(GLIB_LIBS) \
+       $(GUDEV_LIBS) \
+       $(LIBNL_LIBS) \
+       $(LIBNDP_LIBS) \
+       $(LIBM)
+
+nm_iface_helper_LDFLAGS = -rdynamic
+
+######################
+
 dbusservicedir = $(DBUS_SYS_DIR)
 dbusservice_DATA = org.freedesktop.NetworkManager.conf
 
index 9d8932f..3390bf7 100644 (file)
@@ -6990,6 +6990,173 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
        _cleanup_generic_post (self, TRUE);
 }
 
+static char *
+bin2hexstr (const char *bytes, gsize len)
+{
+       GString *str;
+       int i;
+
+       g_return_val_if_fail (bytes != NULL, NULL);
+       g_return_val_if_fail (len > 0, NULL);
+
+       str = g_string_sized_new (len * 2 + 1);
+       for (i = 0; i < len; i++) {
+               if (str->len)
+                       g_string_append_c (str, ':');
+               g_string_append_printf (str, "%02x", (guint8) bytes[i]);
+       }
+       return g_string_free (str, FALSE);
+}
+
+static char *
+find_dhcp4_address (NMDevice *self)
+{
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       guint i, n;
+
+       if (!priv->ip4_config)
+               return NULL;
+
+       n = nm_ip4_config_get_num_addresses (priv->ip4_config);
+       for (i = 0; i < n; i++) {
+               const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i);
+
+               if (a->source == NM_IP_CONFIG_SOURCE_DHCP)
+                       return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
+       }
+       return NULL;
+}
+
+void
+nm_device_spawn_iface_helper (NMDevice *self)
+{
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       gboolean priority_set = FALSE, configured = FALSE;
+       NMConnection *connection;
+       GError *error = NULL;
+       const char *method;
+       GPtrArray *argv;
+       gs_free char *dhcp4_address = NULL;
+
+       if (priv->state != NM_DEVICE_STATE_ACTIVATED)
+               return;
+       if (!nm_device_can_assume_connections (self))
+               return;
+
+       connection = nm_device_get_connection (self);
+       g_assert (connection);
+
+       argv = g_ptr_array_sized_new (10);
+       g_ptr_array_set_free_func (argv, g_free);
+
+       g_ptr_array_add (argv, g_strdup (LIBEXECDIR "/nm-iface-helper"));
+       g_ptr_array_add (argv, g_strdup ("--ifname"));
+       g_ptr_array_add (argv, g_strdup (nm_device_get_ip_iface (self)));
+       g_ptr_array_add (argv, g_strdup ("--uuid"));
+       g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
+
+       dhcp4_address = find_dhcp4_address (self);
+
+       method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+       if (   priv->ip4_config
+           && priv->ip4_state == IP_DONE
+           && g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0
+           && priv->dhcp4_client
+           && dhcp4_address) {
+               NMSettingIPConfig *s_ip4;
+               GBytes *client_id;
+               char *hex_client_id;
+               const char *hostname;
+
+               s_ip4 = nm_connection_get_setting_ip4_config (connection);
+               g_assert (s_ip4);
+
+               g_ptr_array_add (argv, g_strdup ("--priority"));
+               g_ptr_array_add (argv, g_strdup_printf ("%u", nm_dhcp_client_get_priority (priv->dhcp4_client)));
+               priority_set = TRUE;
+
+               g_ptr_array_add (argv, g_strdup ("--dhcp4"));
+               g_ptr_array_add (argv, g_strdup (dhcp4_address));
+               if (nm_setting_ip_config_get_may_fail (s_ip4) == FALSE)
+                       g_ptr_array_add (argv, g_strdup ("--dhcp4-required"));
+
+               client_id = nm_dhcp_client_get_client_id (priv->dhcp4_client);
+               if (client_id) {
+                       g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
+                       hex_client_id = bin2hexstr (g_bytes_get_data (client_id, NULL),
+                                                   g_bytes_get_size (client_id));
+                       g_ptr_array_add (argv, hex_client_id);
+               }
+
+               hostname = nm_dhcp_client_get_hostname (priv->dhcp4_client);
+               if (client_id) {
+                       g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname"));
+                       g_ptr_array_add (argv, g_strdup (hostname));
+               }
+
+               configured = TRUE;
+       }
+
+       method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+       if (   priv->ip6_config
+           && priv->ip6_state == IP_DONE
+           && g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
+           && priv->rdisc
+           && priv->ac_ip6_config) {
+               NMSettingIPConfig *s_ip6;
+               char *hex_iid;
+               NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
+
+               s_ip6 = nm_connection_get_setting_ip6_config (connection);
+               g_assert (s_ip6);
+
+               g_ptr_array_add (argv, g_strdup ("--slaac"));
+
+               if (nm_setting_ip_config_get_may_fail (s_ip6) == FALSE)
+                       g_ptr_array_add (argv, g_strdup ("--slaac-required"));
+
+               g_ptr_array_add (argv, g_strdup ("--slaac-tempaddr"));
+               g_ptr_array_add (argv, g_strdup_printf ("%d", priv->rdisc_use_tempaddr));
+
+               if (nm_device_get_ip_iface_identifier (self, &iid)) {
+                       g_ptr_array_add (argv, g_strdup ("--iid"));
+                       hex_iid = bin2hexstr ((const char *) iid.id_u8, sizeof (NMUtilsIPv6IfaceId));
+                       g_ptr_array_add (argv, hex_iid);
+               }
+
+               configured = TRUE;
+       }
+
+       if (configured) {
+               GPid pid;
+
+               if (!priority_set) {
+                       g_ptr_array_add (argv, g_strdup ("--priority"));
+                       g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_priority (self)));
+               }
+
+               g_ptr_array_add (argv, NULL);
+
+               if (nm_logging_enabled (LOGL_DEBUG, LOGD_DEVICE)) {
+                       char *tmp;
+
+                       tmp = g_strjoinv (" ", (char **) argv->pdata);
+                       _LOGD (LOGD_DEVICE, "running '%s'", tmp);
+                       g_free (tmp);
+               }
+
+               if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
+                                  G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, &error)) {
+                       _LOGI (LOGD_DEVICE, "spawned helper PID %u", (guint) pid);
+               } else {
+                       _LOGW (LOGD_DEVICE, "failed to spawn helper: %s", error->message);
+                       g_error_free (error);
+               }
+       }
+
+       g_ptr_array_unref (argv);
+}
+
 /***********************************************************/
 
 static gboolean
index f7b91e0..840a19c 100644 (file)
@@ -365,6 +365,8 @@ NMConnection *nm_device_new_default_connection (NMDevice *self);
 const NMPlatformIP4Route *nm_device_get_ip4_default_route (NMDevice *self);
 const NMPlatformIP6Route *nm_device_get_ip6_default_route (NMDevice *self);
 
+void nm_device_spawn_iface_helper (NMDevice *self);
+
 G_END_DECLS
 
 /* For testing only */
index 563b7ac..3147980 100644 (file)
@@ -386,11 +386,18 @@ static void
 nm_dhcp_manager_init (NMDhcpManager *self)
 {
        NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+       NMConfig *config = nm_config_get ();
        const char *client;
        GError *error = NULL;
 
        /* Client-specific setup */
-       client = nm_config_get_dhcp_client (nm_config_get ());
+       client = nm_config_get_dhcp_client (config);
+       if (nm_config_get_configure_and_quit (config)) {
+               if (g_strcmp0 (client, "internal") != 0)
+                       nm_log_warn (LOGD_DHCP, "Using internal DHCP client since configure-and-quit is set.");
+               client = "internal";
+       }
+
        priv->client_type = get_client_type (client, &error);
        if (priv->client_type == G_TYPE_INVALID) {
                nm_log_warn (LOGD_DHCP, "No usable DHCP client found (%s)! DHCP configurations will fail.",
index 708545f..476d2a7 100644 (file)
@@ -178,17 +178,10 @@ _init_nm_debug (const char *debug)
 }
 
 static void
-manager_startup_complete (NMManager *manager, GParamSpec *pspec, gpointer user_data)
+manager_configure_quit (NMManager *manager, gpointer user_data)
 {
-       NMConfig *config = NM_CONFIG (user_data);
-       gboolean startup = FALSE;
-
-       g_object_get (G_OBJECT (manager), NM_MANAGER_STARTUP, &startup, NULL);
-
-       if (!startup && nm_config_get_configure_and_quit (config)) {
-               nm_log_info (LOGD_CORE, "quitting now that startup is complete");
-               g_main_loop_quit (main_loop);
-       }
+       nm_log_info (LOGD_CORE, "quitting now that startup is complete");
+       g_main_loop_quit (main_loop);
 }
 
 /*
@@ -462,10 +455,7 @@ main (int argc, char *argv[])
                }
        }
 
-       g_signal_connect (manager,
-                         "notify::" NM_MANAGER_STARTUP,
-                         G_CALLBACK (manager_startup_complete),
-                         config);
+       g_signal_connect (manager, NM_MANAGER_CONFIGURE_QUIT, G_CALLBACK (manager_configure_quit), config);
 
        nm_manager_start (manager);
 
@@ -485,10 +475,10 @@ main (int argc, char *argv[])
        success = TRUE;
 
        /* Told to quit before getting to the mainloop by the signal handler */
-       if (quit_early == TRUE)
-               goto done;
+       if (!quit_early)
+               g_main_loop_run (main_loop);
 
-       g_main_loop_run (main_loop);
+       nm_manager_stop (manager);
 
 done:
        g_clear_object (&manager);
index 26d5cb4..aba4fa1 100644 (file)
@@ -74,41 +74,36 @@ G_DEFINE_TYPE (NMConfig, nm_config, G_TYPE_OBJECT)
 /************************************************************************/
 
 static gboolean
-_parse_bool_str (const char *str, gboolean *out_value)
+_get_bool_value (GKeyFile *keyfile,
+                 const char *section,
+                 const char *key,
+                 gboolean default_value)
 {
-       gboolean value;
-       gsize len;
-       char *s = NULL;
-
-       g_return_val_if_fail (str, FALSE);
-
-       while (g_ascii_isspace (*str))
-               str++;
-
-       if (!*str)
-               return FALSE;
-
-       len = strlen (str);
-
-       if (g_ascii_isspace (str[len-1])) {
-               str = s = g_strdup (str);
-               g_strchomp (s);
-       }
-
-       if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
-               value = TRUE;
-       else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
-               value = FALSE;
-       else {
-               g_free (s);
-               return FALSE;
+       gboolean value = default_value;
+       char *str;
+
+       g_return_val_if_fail (keyfile != NULL, default_value);
+       g_return_val_if_fail (section != NULL, default_value);
+       g_return_val_if_fail (key != NULL, default_value);
+
+       str = g_key_file_get_value (keyfile, section, key, NULL);
+       if (!str)
+               return default_value;
+
+       g_strstrip (str);
+       if (str[0]) {
+               if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
+                       value = TRUE;
+               else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
+                       value = FALSE;
+               else {
+                       nm_log_warn (LOGD_CORE, "Unrecognized value for %s.%s: '%s'. Assuming '%s'",
+                                    section, key, str, default_value ? "true" : "false");
+               }
        }
 
-       if (out_value)
-               *out_value = value;
-
-       g_free (s);
-       return TRUE;
+       g_free (str);
+       return value;
 }
 
 /************************************************************************/
@@ -521,7 +516,6 @@ nm_config_new (GError **error)
        GFileInfo *info;
        GPtrArray *confs;
        const char *name;
-       char *value;
        int i;
        GString *config_description;
 
@@ -591,23 +585,9 @@ nm_config_new (GError **error)
        if (!priv->plugins && STRLEN (CONFIG_PLUGINS_DEFAULT) > 0)
                priv->plugins = g_strsplit (CONFIG_PLUGINS_DEFAULT, ",", -1);
 
-       value = g_key_file_get_value (priv->keyfile, "main", "monitor-connection-files", NULL);
-       priv->monitor_connection_files = FALSE;
-       if (value) {
-               if (!_parse_bool_str (value, &priv->monitor_connection_files))
-                       nm_log_warn (LOGD_CORE, "Unrecognized value for main.monitor-connection-files: %s. Assuming 'false'", value);
-               g_free (value);
-       }
+       priv->monitor_connection_files = _get_bool_value (priv->keyfile, "main", "monitor-connection-files", FALSE);
 
-       value = g_key_file_get_value (priv->keyfile, "main", "auth-polkit", NULL);
-       priv->auth_polkit = NM_CONFIG_DEFAULT_AUTH_POLKIT;
-       if (value) {
-               if (!_parse_bool_str (value, &priv->auth_polkit)) {
-                       nm_log_warn (LOGD_CORE, "Unrecognized value for main.auth-polkit: %s. Assuming '%s'", value,
-                                    NM_CONFIG_DEFAULT_AUTH_POLKIT ? "true" : "false");
-               }
-               g_free (value);
-       }
+       priv->auth_polkit = _get_bool_value (priv->keyfile, "main", "auth-polkit", NM_CONFIG_DEFAULT_AUTH_POLKIT);
 
        priv->dhcp_client = g_key_file_get_value (priv->keyfile, "main", "dhcp", NULL);
        priv->dns_mode = g_key_file_get_value (priv->keyfile, "main", "dns", NULL);
@@ -631,7 +611,7 @@ nm_config_new (GError **error)
 
        priv->ignore_carrier = g_key_file_get_string_list (priv->keyfile, "main", "ignore-carrier", NULL, NULL);
 
-       priv->configure_and_quit = g_key_file_get_boolean (priv->keyfile, "main", "configure-and-quit", NULL);
+       priv->configure_and_quit = _get_bool_value (priv->keyfile, "main", "configure-and-quit", FALSE);
 
        return singleton;
 }
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
new file mode 100644 (file)
index 0000000..62f1b2a
--- /dev/null
@@ -0,0 +1,529 @@
+/* -*- 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) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib-unix.h>
+#include <getopt.h>
+#include <locale.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <glib/gi18n.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <signal.h>
+
+#include "gsystem-local-alloc.h"
+#include "NetworkManagerUtils.h"
+#include "nm-linux-platform.h"
+#include "nm-dhcp-manager.h"
+#include "nm-logging.h"
+#include "main-utils.h"
+#include "nm-rdisc.h"
+#include "nm-lndp-rdisc.h"
+#include "nm-utils.h"
+
+#if !defined(NM_DIST_VERSION)
+# define NM_DIST_VERSION VERSION
+#endif
+
+#define NMIH_PID_FILE_FMT NMRUNDIR "/nm-iface-helper-%d.pid"
+
+static GMainLoop *main_loop = NULL;
+static char *ifname = NULL;
+static int ifindex = -1;
+static gboolean slaac_required = FALSE;
+static gboolean dhcp4_required = FALSE;
+static int tempaddr = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+static int priority = -1;
+
+static void
+dhcp4_state_changed (NMDhcpClient *client,
+                     NMDhcpState state,
+                     NMIP4Config *ip4_config,
+                     GHashTable *options,
+                     gpointer user_data)
+{
+       static NMIP4Config *last_config = NULL;
+       NMIP4Config *existing;
+
+       g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config));
+
+       nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d", ifname, state);
+
+       switch (state) {
+       case NM_DHCP_STATE_BOUND:
+               g_assert (ip4_config);
+               existing = nm_ip4_config_capture (ifindex, FALSE);
+               if (last_config)
+                       nm_ip4_config_subtract (existing, last_config);
+
+               nm_ip4_config_merge (existing, ip4_config);
+               if (!nm_ip4_config_commit (existing, ifindex))
+                       nm_log_warn (LOGD_DHCP4, "(%s): failed to apply DHCPv4 config", ifname);
+
+               if (last_config) {
+                       g_object_unref (last_config);
+                       last_config = nm_ip4_config_new ();
+                       nm_ip4_config_replace (last_config, ip4_config, NULL);
+               }
+               break;
+       case NM_DHCP_STATE_TIMEOUT:
+       case NM_DHCP_STATE_DONE:
+       case NM_DHCP_STATE_FAIL:
+               if (dhcp4_required) {
+                       nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 timed out or failed, quitting...", ifname);
+                       g_main_loop_quit (main_loop);
+               } else
+                       nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 timed out or failed", ifname);
+               break;
+       default:
+               break;
+       }
+}
+
+static void
+rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, gpointer user_data)
+{
+       static NMIP6Config *last_config = NULL;
+       NMIP6Config *existing;
+       NMIP6Config *ip6_config;
+       static int system_support = -1;
+       guint ifa_flags = 0x00;
+       int i;
+
+       if (system_support == -1) {
+               /*
+                * Check, if both libnl and the kernel are recent enough,
+                * to help user space handling RA. If it's not supported,
+                * we have no ipv6-privacy and must add autoconf addresses
+                * as /128. The reason for the /128 is to prevent the kernel
+                * from adding a prefix route for this address.
+                **/
+               system_support = nm_platform_check_support_libnl_extended_ifa_flags () &&
+                                nm_platform_check_support_kernel_extended_ifa_flags ();
+       }
+
+       if (system_support)
+               ifa_flags = IFA_F_NOPREFIXROUTE;
+       if (tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
+           || tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
+       {
+               /* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */
+               ifa_flags |= IFA_F_MANAGETEMPADDR;
+       }
+
+       ip6_config = nm_ip6_config_new ();
+
+       if (changed & NM_RDISC_CONFIG_GATEWAYS) {
+               /* Use the first gateway as ordered in router discovery cache. */
+               if (rdisc->gateways->len) {
+                       NMRDiscGateway *gateway = &g_array_index (rdisc->gateways, NMRDiscGateway, 0);
+
+                       nm_ip6_config_set_gateway (ip6_config, &gateway->address);
+               } else
+                       nm_ip6_config_set_gateway (ip6_config, NULL);
+       }
+
+       if (changed & NM_RDISC_CONFIG_ADDRESSES) {
+               /* Rebuild address list from router discovery cache. */
+               nm_ip6_config_reset_addresses (ip6_config);
+
+               /* rdisc->addresses contains at most max_addresses entries.
+                * This is different from what the kernel does, which
+                * also counts static and temporary addresses when checking
+                * max_addresses.
+                **/
+               for (i = 0; i < rdisc->addresses->len; i++) {
+                       NMRDiscAddress *discovered_address = &g_array_index (rdisc->addresses, NMRDiscAddress, i);
+                       NMPlatformIP6Address address;
+
+                       memset (&address, 0, sizeof (address));
+                       address.address = discovered_address->address;
+                       address.plen = system_support ? 64 : 128;
+                       address.timestamp = discovered_address->timestamp;
+                       address.lifetime = discovered_address->lifetime;
+                       address.preferred = discovered_address->preferred;
+                       if (address.preferred > address.lifetime)
+                               address.preferred = address.lifetime;
+                       address.source = NM_IP_CONFIG_SOURCE_RDISC;
+                       address.flags = ifa_flags;
+
+                       nm_ip6_config_add_address (ip6_config, &address);
+               }
+       }
+
+       if (changed & NM_RDISC_CONFIG_ROUTES) {
+               /* Rebuild route list from router discovery cache. */
+               nm_ip6_config_reset_routes (ip6_config);
+
+               for (i = 0; i < rdisc->routes->len; i++) {
+                       NMRDiscRoute *discovered_route = &g_array_index (rdisc->routes, NMRDiscRoute, i);
+                       NMPlatformIP6Route route;
+
+                       /* Only accept non-default routes.  The router has no idea what the
+                        * local configuration or user preferences are, so sending routes
+                        * with a prefix length of 0 is quite rude and thus ignored.
+                        */
+                       if (discovered_route->plen > 0) {
+                               memset (&route, 0, sizeof (route));
+                               route.network = discovered_route->network;
+                               route.plen = discovered_route->plen;
+                               route.gateway = discovered_route->gateway;
+                               route.source = NM_IP_CONFIG_SOURCE_RDISC;
+                               route.metric = priority;
+
+                               nm_ip6_config_add_route (ip6_config, &route);
+                       }
+               }
+       }
+
+       if (changed & NM_RDISC_CONFIG_DHCP_LEVEL) {
+               /* Unsupported until systemd DHCPv6 is ready */
+       }
+
+       /* hop_limit == 0 is a special value "unspecified", so do not touch
+        * in this case */
+       if (changed & NM_RDISC_CONFIG_HOP_LIMIT && rdisc->hop_limit > 0) {
+               char val[16];
+
+               g_snprintf (val, sizeof (val), "%d", rdisc->hop_limit);
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "hop_limit"), val);
+       }
+
+       if (changed & NM_RDISC_CONFIG_MTU) {
+               char val[16];
+
+               g_snprintf (val, sizeof (val), "%d", rdisc->mtu);
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "mtu"), val);
+       }
+
+       existing = nm_ip6_config_capture (ifindex, FALSE, tempaddr);
+       if (last_config)
+               nm_ip6_config_subtract (existing, last_config);
+
+       nm_ip6_config_merge (existing, ip6_config);
+       if (!nm_ip6_config_commit (existing, ifindex))
+               nm_log_warn (LOGD_IP6, "(%s): failed to apply IPv6 config", ifname);
+
+       if (last_config) {
+               g_object_unref (last_config);
+               last_config = nm_ip6_config_new ();
+               nm_ip6_config_replace (last_config, ip6_config, NULL);
+       }
+}
+
+static void
+rdisc_ra_timeout (NMRDisc *rdisc, gpointer user_data)
+{
+       if (slaac_required) {
+               nm_log_warn (LOGD_IP6, "(%s): IPv6 timed out or failed, quitting...", ifname);
+               g_main_loop_quit (main_loop);
+       } else
+               nm_log_warn (LOGD_IP6, "(%s): IPv6 timed out or failed", ifname);
+}
+
+static gboolean
+quit_handler (gpointer user_data)
+{
+       gboolean *quit_early_ptr = user_data;
+
+       *quit_early_ptr = TRUE;
+       g_main_loop_quit (main_loop);
+       return G_SOURCE_CONTINUE;
+}
+
+static void
+setup_signals (gboolean *quit_early_ptr)
+{
+       sigset_t sigmask;
+
+       sigemptyset (&sigmask);
+       pthread_sigmask (SIG_SETMASK, &sigmask, NULL);
+
+       signal (SIGPIPE, SIG_IGN);
+       g_unix_signal_add (SIGINT, quit_handler, quit_early_ptr);
+       g_unix_signal_add (SIGTERM, quit_handler, quit_early_ptr);
+}
+
+int
+main (int argc, char *argv[])
+{
+       char *opt_log_level = NULL;
+       char *opt_log_domains = NULL;
+       gboolean debug = FALSE, g_fatal_warnings = FALSE, become_daemon = FALSE;
+       gboolean show_version = FALSE, slaac = FALSE;
+       char *bad_domains = NULL, *dhcp4_hostname = NULL, *uuid = NULL;
+       char *iid_str = NULL, *dhcp4_clientid = NULL, *dhcp4_address = NULL;
+       gs_unref_object NMDhcpManager *dhcp_mgr = NULL;
+       GError *error = NULL;
+       gboolean wrote_pidfile = FALSE;
+       gs_free char *pidfile = NULL;
+       gboolean quit_early = FALSE;
+       gs_unref_object NMDhcpClient *dhcp4_client = NULL;
+       gs_unref_object NMRDisc *rdisc = NULL;
+       GByteArray *hwaddr = NULL;
+       size_t hwaddr_len = 0;
+       gconstpointer tmp;
+       gs_free NMUtilsIPv6IfaceId *iid = NULL;
+
+       GOptionEntry options[] = {
+               /* Interface/IP config */
+               { "ifname", 'i', 0, G_OPTION_ARG_STRING, &ifname, N_("The interface to manage"), N_("eth0") },
+               { "uuid", 'u', 0, G_OPTION_ARG_STRING, &uuid, N_("Connection UUID"), N_("661e8cd0-b618-46b8-9dc9-31a52baaa16b") },
+               { "slaac", 's', 0, G_OPTION_ARG_NONE, &slaac, N_("Whether to manage IPv6 SLAAC"), NULL },
+               { "slaac-required", '6', 0, G_OPTION_ARG_NONE, &slaac_required, N_("Whether SLAAC must be successful"), NULL },
+               { "slaac-tempaddr", 't', 0, G_OPTION_ARG_INT, &tempaddr, N_("Use an IPv6 temporary privacy address"), NULL },
+               { "dhcp4", 'd', 0, G_OPTION_ARG_STRING, &dhcp4_address, N_("Current DHCPv4 address"), NULL },
+               { "dhcp4-required", '4', 0, G_OPTION_ARG_NONE, &dhcp4_required, N_("Whether DHCPv4 must be successful"), NULL },
+               { "dhcp4-clientid", 'c', 0, G_OPTION_ARG_STRING, &dhcp4_clientid, N_("Hex-encoded DHCPv4 client ID"), NULL },
+               { "dhcp4-hostname", 'h', 0, G_OPTION_ARG_STRING, &dhcp4_hostname, N_("Hostname to send to DHCP server"), N_("barbar") },
+               { "priority", 'p', 0, G_OPTION_ARG_INT, &priority, N_("Route priority"), N_("10") },
+               { "iid", 'e', 0, G_OPTION_ARG_STRING, &iid_str, N_("Hex-encoded Interface Identifier"), N_("") },
+
+               /* Logging/debugging */
+               { "version", 'V', 0, G_OPTION_ARG_NONE, &show_version, N_("Print NetworkManager version and exit"), NULL },
+               { "no-daemon", 'n', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &become_daemon, N_("Don't become a daemon"), NULL },
+               { "debug", 'b', 0, G_OPTION_ARG_NONE, &debug, N_("Don't become a daemon, and log to stderr"), NULL },
+               { "log-level", 0, 0, G_OPTION_ARG_STRING, &opt_log_level, N_("Log level: one of [%s]"), "INFO" },
+               { "log-domains", 0, 0, G_OPTION_ARG_STRING, &opt_log_domains,
+                 N_("Log domains separated by ',': any combination of [%s]"),
+                 "PLATFORM,RFKILL,WIFI" },
+               { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, N_("Make all warnings fatal"), NULL },
+               {NULL}
+       };
+
+       setpgid (getpid (), getpid ());
+
+       if (!nm_main_utils_early_setup ("nm-iface-helper",
+                                       &argv,
+                                       &argc,
+                                       options,
+                                       NULL,
+                                       _("nm-iface-helper is a small, standalone process that manages a single network interface.")))
+               exit (1);
+
+       if (show_version) {
+               fprintf (stdout, NM_DIST_VERSION "\n");
+               exit (0);
+       }
+
+       if (!ifname || !uuid) {
+               fprintf (stderr, _("An interface name and UUID are required\n"));
+               exit (1);
+       }
+
+       if (!nm_logging_setup (opt_log_level,
+                              opt_log_domains,
+                              &bad_domains,
+                              &error)) {
+               fprintf (stderr,
+                        _("%s.  Please use --help to see a list of valid options.\n"),
+                        error->message);
+               exit (1);
+       } else if (bad_domains) {
+               fprintf (stderr,
+                        _("Ignoring unrecognized log domain(s) '%s' passed on command line.\n"),
+                        bad_domains);
+               g_clear_pointer (&bad_domains, g_free);
+       }
+
+       pidfile = g_strdup_printf (NMIH_PID_FILE_FMT, ifindex);
+       g_assert (pidfile);
+
+       /* check pid file */
+       if (nm_main_utils_check_pidfile (pidfile, "nm-iface-helper"))
+               exit (1);
+
+       if (become_daemon && !debug) {
+               if (daemon (0, 0) < 0) {
+                       int saved_errno;
+
+                       saved_errno = errno;
+                       fprintf (stderr, _("Could not daemonize: %s [error %u]\n"),
+                                g_strerror (saved_errno),
+                                saved_errno);
+                       exit (1);
+               }
+               if (nm_main_utils_write_pidfile (pidfile))
+                       wrote_pidfile = TRUE;
+       }
+
+       /* Set up unix signal handling - before creating threads, but after daemonizing! */
+       main_loop = g_main_loop_new (NULL, FALSE);
+       setup_signals (&quit_early);
+
+       if (g_fatal_warnings) {
+               GLogLevelFlags fatal_mask;
+
+               fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
+               fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
+               g_log_set_always_fatal (fatal_mask);
+       }
+
+       nm_logging_syslog_openlog (debug);
+
+#if !GLIB_CHECK_VERSION (2, 35, 0)
+       g_type_init ();
+#endif
+
+       nm_log_info (LOGD_CORE, "nm-iface-helper (version " NM_DIST_VERSION ") is starting...");
+
+       /* Set up platform interaction layer */
+       nm_linux_platform_setup ();
+
+       ifindex = nm_platform_link_get_ifindex (ifname);
+       if (ifindex <= 0) {
+               fprintf (stderr, _("Failed to find interface index for %s\n"), ifname);
+               exit (1);
+       }
+
+       tmp = nm_platform_link_get_address (ifindex, &hwaddr_len);
+       if (tmp) {
+               hwaddr = g_byte_array_sized_new (hwaddr_len);
+               g_byte_array_append (hwaddr, tmp, hwaddr_len);
+       }
+
+       if (iid_str) {
+               GBytes *bytes;
+               gsize ignored = 0;
+
+               bytes = nm_utils_hexstr2bin (iid_str);
+               if (!bytes || g_bytes_get_size (bytes) != sizeof (*iid)) {
+                       fprintf (stderr, _("(%s): Invalid IID %s\n"), ifname, iid_str);
+                       exit (1);
+               }
+               iid = g_bytes_unref_to_data (bytes, &ignored);
+       }
+
+       priority = MAX (0, priority);
+
+       if (dhcp4_address) {
+               nm_platform_sysctl_set (nm_utils_ip4_property_path (ifname, "promote_secondaries"), "1");
+
+               /* Initialize DHCP manager */
+               dhcp_mgr = nm_dhcp_manager_get ();
+               g_assert (dhcp_mgr != NULL);
+
+               dhcp4_client = nm_dhcp_manager_start_ip4 (dhcp_mgr,
+                                                         ifname,
+                                                         ifindex,
+                                                         hwaddr,
+                                                         uuid,
+                                                         priority,
+                                                         !!dhcp4_hostname,
+                                                         dhcp4_hostname,
+                                                         dhcp4_clientid,
+                                                         45,
+                                                         NULL,
+                                                         dhcp4_address);
+               g_assert (dhcp4_client);
+               g_signal_connect (dhcp4_client,
+                                 NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
+                                 G_CALLBACK (dhcp4_state_changed),
+                                 NULL);
+       }
+
+       if (slaac) {
+               nm_platform_link_set_user_ipv6ll_enabled (ifindex, TRUE);
+
+               rdisc = nm_lndp_rdisc_new (ifindex, ifname);
+               g_assert (rdisc);
+
+               if (iid)
+                       nm_rdisc_set_iid (rdisc, *iid);
+
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "accept_ra"), "1");
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "accept_ra_defrtr"), "0");
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "accept_ra_pinfo"), "0");
+               nm_platform_sysctl_set (nm_utils_ip6_property_path (ifname, "accept_ra_rtr_pref"), "0");
+
+               g_signal_connect (rdisc,
+                                 NM_RDISC_CONFIG_CHANGED,
+                                 G_CALLBACK (rdisc_config_changed),
+                                 NULL);
+               g_signal_connect (rdisc,
+                                 NM_RDISC_RA_TIMEOUT,
+                                 G_CALLBACK (rdisc_ra_timeout),
+                                 NULL);
+               nm_rdisc_start (rdisc);
+       }
+
+       if (!quit_early)
+               g_main_loop_run (main_loop);
+
+       g_clear_pointer (&hwaddr, g_byte_array_unref);
+
+       nm_logging_syslog_closelog ();
+
+       if (pidfile && wrote_pidfile)
+               unlink (pidfile);
+
+       nm_log_info (LOGD_CORE, "exiting");
+       exit (0);
+}
+
+/*******************************************************/
+/* Stub functions */
+
+gconstpointer nm_config_get (void);
+const char *nm_config_get_dhcp_client (gpointer unused);
+gboolean nm_config_get_configure_and_quit (gpointer unused);
+gconstpointer nm_dbus_manager_get (void);
+void nm_dbus_manager_register_exported_type (gpointer unused, GType gtype, gconstpointer unused2);
+void nm_dbus_manager_register_object (gpointer unused, const char *path, gpointer object);
+
+gconstpointer
+nm_config_get (void)
+{
+       return GUINT_TO_POINTER (1);
+}
+
+const char *
+nm_config_get_dhcp_client (gpointer unused)
+{
+       return "internal";
+}
+
+gboolean
+nm_config_get_configure_and_quit (gpointer unused)
+{
+       return TRUE;
+}
+
+gconstpointer
+nm_dbus_manager_get (void)
+{
+       return GUINT_TO_POINTER (1);
+}
+
+void
+nm_dbus_manager_register_exported_type (gpointer unused, GType gtype, gconstpointer unused2)
+{
+}
+
+void
+nm_dbus_manager_register_object (gpointer unused, const char *path, gpointer object)
+{
+}
+
index 72c1b0c..7548dad 100644 (file)
@@ -83,6 +83,7 @@ nm_ip4_config_new (void)
 }
 
 
+#ifndef NM_IFACE_HELPER
 void
 nm_ip4_config_export (NMIP4Config *config)
 {
@@ -94,6 +95,7 @@ nm_ip4_config_export (NMIP4Config *config)
                nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, config);
        }
 }
+#endif
 
 const char *
 nm_ip4_config_get_dbus_path (const NMIP4Config *config)
@@ -1952,7 +1954,9 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 
        g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
 
+#ifndef NM_IFACE_HELPER
        nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
                                                G_TYPE_FROM_CLASS (config_class),
                                                &dbus_glib_nm_ip4_config_object_info);
+#endif
 }
index fe45f82..f0930b7 100644 (file)
@@ -73,6 +73,7 @@ nm_ip6_config_new (void)
        return (NMIP6Config *) g_object_new (NM_TYPE_IP6_CONFIG, NULL);
 }
 
+#ifndef NM_IFACE_HELPER
 void
 nm_ip6_config_export (NMIP6Config *config)
 {
@@ -84,6 +85,7 @@ nm_ip6_config_export (NMIP6Config *config)
                nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, config);
        }
 }
+#endif
 
 const char *
 nm_ip6_config_get_dbus_path (const NMIP6Config *config)
@@ -1863,7 +1865,9 @@ nm_ip6_config_class_init (NMIP6ConfigClass *config_class)
 
        g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
 
+#ifndef NM_IFACE_HELPER
        nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
                                                G_TYPE_FROM_CLASS (config_class),
                                                &dbus_glib_nm_ip6_config_object_info);
+#endif
 }
index f936235..d8973b7 100644 (file)
@@ -59,6 +59,7 @@
 #include "nm-session-monitor.h"
 #include "nm-activation-request.h"
 #include "nm-core-internal.h"
+#include "nm-config.h"
 
 #define NM_AUTOIP_DBUS_SERVICE "org.freedesktop.nm_avahi_autoipd"
 #define NM_AUTOIP_DBUS_IFACE   "org.freedesktop.nm_avahi_autoipd"
@@ -214,6 +215,7 @@ enum {
        USER_PERMISSIONS_CHANGED,
        ACTIVE_CONNECTION_ADDED,
        ACTIVE_CONNECTION_REMOVED,
+       CONFIGURE_QUIT,
 
        LAST_SIGNAL
 };
@@ -706,6 +708,9 @@ check_if_startup_complete (NMManager *self)
 
                g_signal_handlers_disconnect_by_func (dev, G_CALLBACK (device_has_pending_action_changed), self);
        }
+
+       if (nm_config_get_configure_and_quit (nm_config_get ()))
+               g_signal_emit (self, signals[CONFIGURE_QUIT], 0);
 }
 
 static void
@@ -745,6 +750,8 @@ remove_device (NMManager *manager,
                                nm_device_set_unmanaged_quitting (device);
                        else
                                nm_device_set_unmanaged (device, NM_UNMANAGED_INTERNAL, TRUE, NM_DEVICE_STATE_REASON_REMOVED);
+               } else if (quitting && nm_config_get_configure_and_quit (nm_config_get ())) {
+                       nm_device_spawn_iface_helper (device);
                }
        }
 
@@ -4167,6 +4174,16 @@ nm_manager_start (NMManager *self)
        check_if_startup_complete (self);
 }
 
+void
+nm_manager_stop (NMManager *self)
+{
+       NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+       /* Remove all devices */
+       while (priv->devices)
+               remove_device (self, NM_DEVICE (priv->devices->data), TRUE, TRUE);
+}
+
 static gboolean
 handle_firmware_changed (gpointer user_data)
 {
@@ -4990,9 +5007,7 @@ dispose (GObject *object)
                                              G_CALLBACK (authority_changed_cb),
                                              manager);
 
-       /* Remove all devices */
-       while (priv->devices)
-               remove_device (manager, NM_DEVICE (priv->devices->data), TRUE, TRUE);
+       g_assert (priv->devices == NULL);
 
        if (priv->ac_cleanup_id) {
                g_source_remove (priv->ac_cleanup_id);
@@ -5258,6 +5273,13 @@ nm_manager_class_init (NMManagerClass *manager_class)
                              0, NULL, NULL, NULL,
                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
+       signals[CONFIGURE_QUIT] =
+               g_signal_new (NM_MANAGER_CONFIGURE_QUIT,
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_FIRST,
+                             0, NULL, NULL, NULL,
+                             G_TYPE_NONE, 0);
+
        nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
                                                G_TYPE_FROM_CLASS (manager_class),
                                                &dbus_glib_nm_manager_object_info);
index f623516..3b00e80 100644 (file)
@@ -59,6 +59,7 @@
 /* Internal signals */
 #define NM_MANAGER_ACTIVE_CONNECTION_ADDED   "active-connection-added"
 #define NM_MANAGER_ACTIVE_CONNECTION_REMOVED "active-connection-removed"
+#define NM_MANAGER_CONFIGURE_QUIT            "configure-quit"
 
 
 struct _NMManager {
@@ -88,6 +89,7 @@ NMManager *   nm_manager_new                           (NMSettings *settings,
 NMManager *   nm_manager_get                           (void);
 
 void          nm_manager_start                         (NMManager *manager);
+void          nm_manager_stop                          (NMManager *manager);
 NMState       nm_manager_get_state                     (NMManager *manager);
 const GSList *nm_manager_get_active_connections        (NMManager *manager);
 GSList *      nm_manager_get_activatable_connections   (NMManager *manager);