diff options
author | Dan Williams <dcbw@redhat.com> | 2014-06-06 13:56:08 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-06-06 14:15:21 -0500 |
commit | 67b228d16bf8523e37c36f45d48da406ba5b461e (patch) | |
tree | 2666f91fbf56cebe17ce11a4304f8edad90b26ea | |
parent | 26a65f4fe410f88c4cf748e4c965833e8fb44e63 (diff) | |
parent | 02252224e286dd3849467567ecbf0238a54e77a0 (diff) | |
download | NetworkManager-67b228d16bf8523e37c36f45d48da406ba5b461e.tar.gz |
merge: add blocking dispatcher pre-up and pre-down events (bgo #387832)
https://bugzilla.gnome.org/show_bug.cgi?id=387832
https://bugzilla.redhat.com/show_bug.cgi?id=1048345
https://bugzilla.redhat.com/show_bug.cgi?id=982734
-rw-r--r-- | callouts/Makefile.am | 10 | ||||
-rw-r--r-- | callouts/nm-dispatcher-api.h (renamed from callouts/nm-dispatcher-action.h) | 17 | ||||
-rw-r--r-- | callouts/nm-dispatcher-utils.c | 2 | ||||
-rw-r--r-- | callouts/nm-dispatcher.c (renamed from callouts/nm-dispatcher-action.c) | 50 | ||||
-rw-r--r-- | callouts/org.freedesktop.nm_dispatcher.service.in | 2 | ||||
-rw-r--r-- | callouts/tests/test-dispatcher-envp.c | 2 | ||||
-rw-r--r-- | contrib/fedora/rpm/NetworkManager.spec | 2 | ||||
-rw-r--r-- | data/NetworkManager-dispatcher.service.in | 2 | ||||
-rw-r--r-- | man/NetworkManager.xml | 49 | ||||
-rw-r--r-- | src/devices/nm-device.c | 4047 | ||||
-rw-r--r-- | src/devices/nm-device.h | 21 | ||||
-rw-r--r-- | src/main.c | 3 | ||||
-rw-r--r-- | src/nm-dispatcher.c | 521 | ||||
-rw-r--r-- | src/nm-dispatcher.h | 39 | ||||
-rw-r--r-- | src/nm-manager.c | 46 | ||||
-rw-r--r-- | src/nm-policy.c | 2 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-connection.c | 442 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-connection.h | 10 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-manager.c | 142 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-service.c | 365 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-service.h | 8 |
21 files changed, 3044 insertions, 2738 deletions
diff --git a/callouts/Makefile.am b/callouts/Makefile.am index 8b018be703..a7ef429849 100644 --- a/callouts/Makefile.am +++ b/callouts/Makefile.am @@ -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) diff --git a/callouts/nm-dispatcher-action.h b/callouts/nm-dispatcher-api.h index 9fea487ada..eb6b33fe0c 100644 --- a/callouts/nm-dispatcher-action.h +++ b/callouts/nm-dispatcher-api.h @@ -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, diff --git a/callouts/nm-dispatcher-utils.c b/callouts/nm-dispatcher-utils.c index 1f99a0b6c1..b1d11daf3d 100644 --- a/callouts/nm-dispatcher-utils.c +++ b/callouts/nm-dispatcher-utils.c @@ -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" diff --git a/callouts/nm-dispatcher-action.c b/callouts/nm-dispatcher.c index 337a5c616c..d66a0d8f94 100644 --- a/callouts/nm-dispatcher-action.c +++ b/callouts/nm-dispatcher.c @@ -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 ()); diff --git a/callouts/org.freedesktop.nm_dispatcher.service.in b/callouts/org.freedesktop.nm_dispatcher.service.in index 9feb3b468b..ff037cca9f 100644 --- a/callouts/org.freedesktop.nm_dispatcher.service.in +++ b/callouts/org.freedesktop.nm_dispatcher.service.in @@ -1,6 +1,6 @@ [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 diff --git a/callouts/tests/test-dispatcher-envp.c b/callouts/tests/test-dispatcher-envp.c index 8dd18430ed..a91ae2b27a 100644 --- a/callouts/tests/test-dispatcher-envp.c +++ b/callouts/tests/test-dispatcher-envp.c @@ -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" /*******************************************/ diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 616f219b32..3a2f29b2b4 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -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/* diff --git a/data/NetworkManager-dispatcher.service.in b/data/NetworkManager-dispatcher.service.in index e1b90036f3..c450478bac 100644 --- a/data/NetworkManager-dispatcher.service.in +++ b/data/NetworkManager-dispatcher.service.in @@ -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 diff --git a/man/NetworkManager.xml b/man/NetworkManager.xml index 70e49aa16a..229c390cdc 100644 --- a/man/NetworkManager.xml +++ b/man/NetworkManager.xml @@ -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> @@ -65,22 +65,65 @@ <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> A VPN connection has been deactivated. diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 445611ddca..a6b4b548f0 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -78,24 +78,12 @@ static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *con #include "nm-device-glue.h" -#define DBUS_G_TYPE_UINT_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)) - -/* default to installed helper, but can be modified for testing */ -const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action"; - -/***********************************************************/ -#define NM_DEVICE_ERROR (nm_device_error_quark ()) +static void nm_device_config_device_interface_init (NMConfigDeviceInterface *iface); -static GQuark -nm_device_error_quark (void) -{ - static GQuark quark = 0; - if (!quark) - quark = g_quark_from_static_string ("nm-device-error"); - return quark; -} +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMDevice, nm_device, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NM_TYPE_CONFIG_DEVICE, nm_device_config_device_interface_init)) -/***********************************************************/ +#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate)) enum { STATE_CHANGED, @@ -146,17 +134,8 @@ enum { LAST_PROP }; -#define DEFAULT_AUTOCONNECT TRUE - /***********************************************************/ -static void nm_device_config_device_interface_init (NMConfigDeviceInterface *iface); - -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (NMDevice, nm_device, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (NM_TYPE_CONFIG_DEVICE, nm_device_config_device_interface_init)) - -#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate)) - #define PENDING_ACTION_DHCP4 "dhcp4" #define PENDING_ACTION_DHCP6 "dhcp6" #define PENDING_ACTION_AUTOCONF6 "autoconf6" @@ -196,8 +175,6 @@ typedef struct { } DeleteOnDeactivateData; typedef struct { - gboolean disposed; - gboolean initialized; gboolean in_state_changed; NMDeviceState state; @@ -239,6 +216,7 @@ typedef struct { guint act_source6_id; gpointer act_source6_func; guint recheck_assume_id; + guint dispatcher_id; /* Link stuff */ guint link_connected_id; @@ -335,291 +313,166 @@ static gboolean nm_device_set_ip6_config (NMDevice *dev, gboolean commit, NMDeviceStateReason *reason); -static gboolean check_connection_available (NMDevice *device, - NMConnection *connection, - const char *specific_object); - -static gboolean spec_match_list (NMDevice *device, const GSList *specs); - -static void _clear_available_connections (NMDevice *device, gboolean do_signal); - -static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release); -static void dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release); - -static const char *reason_to_string (NMDeviceStateReason reason); - -static void ip_check_gw_ping_cleanup (NMDevice *self); - -static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); -static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); -static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); - -static const char *state_to_string (NMDeviceState state); - -static void link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *device); -static void check_carrier (NMDevice *device); - -static void nm_device_queued_ip_config_change_clear (NMDevice *self); -static void update_ip_config (NMDevice *self, gboolean initial); -static void device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformSignalChangeType change_type, NMPlatformReason reason, gpointer user_data); - +static gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure); static void nm_device_slave_notify_enslave (NMDevice *dev, gboolean success); static void nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason); static void addrconf6_start_with_link_ready (NMDevice *self); -static void -nm_device_init (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); +static gboolean nm_device_get_default_unmanaged (NMDevice *device); - priv->type = NM_DEVICE_TYPE_UNKNOWN; - priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED; - priv->state = NM_DEVICE_STATE_UNMANAGED; - priv->state_reason = NM_DEVICE_STATE_REASON_NONE; - priv->dhcp_timeout = 0; - priv->rfkill_type = RFKILL_TYPE_UNKNOWN; - priv->autoconnect = DEFAULT_AUTOCONNECT; - priv->unmanaged_flags = NM_UNMANAGED_INTERNAL; - priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); - priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); -} +static void _set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting); -static inline gboolean -nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value) +/***********************************************************/ + +static GQuark +nm_device_error_quark (void) { - return nm_platform_sysctl_set (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property), value); + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-device-error"); + return quark; } -static const char *ip6_properties_to_save[] = { - "accept_ra", - "accept_ra_defrtr", - "accept_ra_pinfo", - "accept_ra_rtr_pref", - "disable_ipv6", - "hop_limit", - "use_tempaddr", -}; +#define NM_DEVICE_ERROR (nm_device_error_quark ()) -static void -save_ip6_properties (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - const char *ifname = nm_device_get_ip_iface (self); - char *value; - int i; +/***********************************************************/ - g_hash_table_remove_all (priv->ip6_saved_properties); +#define QUEUED_PREFIX "queued state change to " - for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) { - value = nm_platform_sysctl_get (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])); - if (value) { - g_hash_table_insert (priv->ip6_saved_properties, - (char *) ip6_properties_to_save[i], - value); - } - } -} +static const char *state_table[] = { + [NM_DEVICE_STATE_UNKNOWN] = QUEUED_PREFIX "unknown", + [NM_DEVICE_STATE_UNMANAGED] = QUEUED_PREFIX "unmanaged", + [NM_DEVICE_STATE_UNAVAILABLE] = QUEUED_PREFIX "unavailable", + [NM_DEVICE_STATE_DISCONNECTED] = QUEUED_PREFIX "disconnected", + [NM_DEVICE_STATE_PREPARE] = QUEUED_PREFIX "prepare", + [NM_DEVICE_STATE_CONFIG] = QUEUED_PREFIX "config", + [NM_DEVICE_STATE_NEED_AUTH] = QUEUED_PREFIX "need-auth", + [NM_DEVICE_STATE_IP_CONFIG] = QUEUED_PREFIX "ip-config", + [NM_DEVICE_STATE_IP_CHECK] = QUEUED_PREFIX "ip-check", + [NM_DEVICE_STATE_SECONDARIES] = QUEUED_PREFIX "secondaries", + [NM_DEVICE_STATE_ACTIVATED] = QUEUED_PREFIX "activated", + [NM_DEVICE_STATE_DEACTIVATING] = QUEUED_PREFIX "deactivating", + [NM_DEVICE_STATE_FAILED] = QUEUED_PREFIX "failed", +}; -static void -restore_ip6_properties (NMDevice *self) +static const char * +queued_state_to_string (NMDeviceState state) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, priv->ip6_saved_properties); - while (g_hash_table_iter_next (&iter, &key, &value)) - nm_device_ipv6_sysctl_set (self, key, value); + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; + return state_table[NM_DEVICE_STATE_UNKNOWN]; } -/* - * Get driver info from SIOCETHTOOL ioctl() for 'iface' - * Returns driver and firmware versions to 'driver_version and' 'firmware_version' - */ -static gboolean -device_get_driver_info (const char *iface, char **driver_version, char **firmware_version) +static const char * +state_to_string (NMDeviceState state) { - struct ethtool_drvinfo drvinfo; - struct ifreq req; - int fd; - - fd = socket (PF_INET, SOCK_DGRAM, 0); - if (fd < 0) { - nm_log_warn (LOGD_HW, "couldn't open control socket."); - return FALSE; - } - - /* Get driver and firmware version info */ - memset (&drvinfo, 0, sizeof (drvinfo)); - memset (&req, 0, sizeof (struct ifreq)); - strncpy (req.ifr_name, iface, IFNAMSIZ); - drvinfo.cmd = ETHTOOL_GDRVINFO; - req.ifr_data = &drvinfo; - - errno = 0; - if (ioctl (fd, SIOCETHTOOL, &req) < 0) { - nm_log_dbg (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d", - iface, errno); - close (fd); - return FALSE; - } - if (driver_version) - *driver_version = g_strdup (drvinfo.version); - if (firmware_version) - *firmware_version = g_strdup (drvinfo.fw_version); - - close (fd); - return TRUE; + return queued_state_to_string (state) + strlen (QUEUED_PREFIX); } -static gboolean -device_has_capability (NMDevice *device, NMDeviceCapabilities caps) -{ - return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps); -} +static const char *reason_table[] = { + [NM_DEVICE_STATE_REASON_NONE] = "none", + [NM_DEVICE_STATE_REASON_NOW_MANAGED] = "managed", + [NM_DEVICE_STATE_REASON_NOW_UNMANAGED] = "unmanaged", + [NM_DEVICE_STATE_REASON_CONFIG_FAILED] = "config-failed", + [NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE] = "ip-config-unavailable", + [NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED] = "ip-config-expired", + [NM_DEVICE_STATE_REASON_NO_SECRETS] = "no-secrets", + [NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT] = "supplicant-disconnect", + [NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED] = "supplicant-config-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED] = "supplicant-failed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT] = "supplicant-timeout", + [NM_DEVICE_STATE_REASON_PPP_START_FAILED] = "ppp-start-failed", + [NM_DEVICE_STATE_REASON_PPP_DISCONNECT] = "ppp-disconnect", + [NM_DEVICE_STATE_REASON_PPP_FAILED] = "ppp-failed", + [NM_DEVICE_STATE_REASON_DHCP_START_FAILED] = "dhcp-start-failed", + [NM_DEVICE_STATE_REASON_DHCP_ERROR] = "dhcp-error", + [NM_DEVICE_STATE_REASON_DHCP_FAILED] = "dhcp-failed", + [NM_DEVICE_STATE_REASON_SHARED_START_FAILED] = "sharing-start-failed", + [NM_DEVICE_STATE_REASON_SHARED_FAILED] = "sharing-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED] = "autoip-start-failed", + [NM_DEVICE_STATE_REASON_AUTOIP_ERROR] = "autoip-error", + [NM_DEVICE_STATE_REASON_AUTOIP_FAILED] = "autoip-failed", + [NM_DEVICE_STATE_REASON_MODEM_BUSY] = "modem-busy", + [NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE] = "modem-no-dialtone", + [NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER] = "modem-no-carrier", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT] = "modem-dial-timeout", + [NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED] = "modem-dial-failed", + [NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED] = "modem-init-failed", + [NM_DEVICE_STATE_REASON_GSM_APN_FAILED] = "gsm-apn-failed", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING] = "gsm-registration-idle", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED] = "gsm-registration-denied", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT] = "gsm-registration-timeout", + [NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED] = "gsm-registration-failed", + [NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED] = "gsm-pin-check-failed", + [NM_DEVICE_STATE_REASON_FIRMWARE_MISSING] = "firmware-missing", + [NM_DEVICE_STATE_REASON_REMOVED] = "removed", + [NM_DEVICE_STATE_REASON_SLEEPING] = "sleeping", + [NM_DEVICE_STATE_REASON_CONNECTION_REMOVED] = "connection-removed", + [NM_DEVICE_STATE_REASON_USER_REQUESTED] = "user-requested", + [NM_DEVICE_STATE_REASON_CARRIER] = "carrier-changed", + [NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED] = "connection-assumed", + [NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE] = "supplicant-available", + [NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND] = "modem-not-found", + [NM_DEVICE_STATE_REASON_BT_FAILED] = "bluetooth-failed", + [NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED] = "gsm-sim-not-inserted", + [NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED] = "gsm-sim-pin-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED] = "gsm-sim-puk-required", + [NM_DEVICE_STATE_REASON_GSM_SIM_WRONG] = "gsm-sim-wrong", + [NM_DEVICE_STATE_REASON_INFINIBAND_MODE] = "infiniband-mode", + [NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED] = "dependency-failed", + [NM_DEVICE_STATE_REASON_BR2684_FAILED] = "br2684-bridge-failed", + [NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE] = "modem-manager-unavailable", + [NM_DEVICE_STATE_REASON_SSID_NOT_FOUND] = "ssid-not-found", + [NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED] = "secondary-connection-failed", + [NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED] = "dcb-fcoe-failed", + [NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED] = "teamd-control-failed", + [NM_DEVICE_STATE_REASON_MODEM_FAILED] = "modem-failed", + [NM_DEVICE_STATE_REASON_MODEM_AVAILABLE] = "modem-available", + [NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT] = "sim-pin-incorrect", +}; -static GObject* -constructor (GType type, - guint n_construct_params, - GObjectConstructParam *construct_params) +static const char * +reason_to_string (NMDeviceStateReason reason) { - GObject *object; - NMDevice *dev; - NMDevicePrivate *priv; - NMPlatform *platform; - static guint32 id = 0; - - object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type, - n_construct_params, - construct_params); - if (!object) - return NULL; - - dev = NM_DEVICE (object); - priv = NM_DEVICE_GET_PRIVATE (dev); - - if (!priv->iface) { - nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring"); - goto error; - } - - if (!priv->udi) { - /* Use a placeholder UDI until we get a real one */ - priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++); - } - - if (NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities) - priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev); - - priv->fw_manager = nm_firewall_manager_get (); - - device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version); - - /* Watch for external IP config changes */ - platform = nm_platform_get (); - g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); - g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); - g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); - g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); - g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), dev); - - priv->initialized = TRUE; - return object; - -error: - g_object_unref (dev); - return NULL; + if (reason >= 0 && reason < G_N_ELEMENTS (reason_table)) + return reason_table[reason]; + return reason_table[NM_DEVICE_STATE_REASON_UNKNOWN]; } -static void -constructed (GObject *object) -{ - NMDevice *dev = NM_DEVICE (object); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); - - nm_device_update_hw_address (dev); - - if (NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address) - NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address (dev); - - if (NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address) - NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address (dev); - - /* Have to call update_initial_hw_address() before calling get_ignore_carrier() */ - if (device_has_capability (dev, NM_DEVICE_CAP_CARRIER_DETECT)) { - priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), NM_CONFIG_DEVICE (dev)); - - check_carrier (dev); - nm_log_info (LOGD_HW, - "(%s): carrier is %s%s", - nm_device_get_iface (NM_DEVICE (dev)), - priv->carrier ? "ON" : "OFF", - priv->ignore_carrier ? " (but ignored)" : ""); - } else { - /* Fake online link when carrier detection is not available. */ - priv->carrier = TRUE; - } - - if (priv->ifindex > 0) { - priv->is_software = nm_platform_link_is_software (priv->ifindex); - priv->physical_port_id = nm_platform_link_get_physical_port_id (priv->ifindex); - } - - if (priv->ifindex > 0) - priv->mtu = nm_platform_link_get_mtu (priv->ifindex); - - priv->con_provider = nm_connection_provider_get (); - g_assert (priv->con_provider); - g_signal_connect (priv->con_provider, - NM_CP_SIGNAL_CONNECTION_ADDED, - G_CALLBACK (cp_connection_added), - dev); - - g_signal_connect (priv->con_provider, - NM_CP_SIGNAL_CONNECTION_REMOVED, - G_CALLBACK (cp_connection_removed), - dev); - - g_signal_connect (priv->con_provider, - NM_CP_SIGNAL_CONNECTION_UPDATED, - G_CALLBACK (cp_connection_updated), - dev); - - if (G_OBJECT_CLASS (nm_device_parent_class)->constructed) - G_OBJECT_CLASS (nm_device_parent_class)->constructed (object); -} +/***********************************************************/ -static gboolean -nm_device_is_up (NMDevice *self) +static inline gboolean +nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value) { - g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); - - if (NM_DEVICE_GET_CLASS (self)->is_up) - return NM_DEVICE_GET_CLASS (self)->is_up (self); - - return TRUE; + return nm_platform_sysctl_set (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property), value); } static gboolean -is_up (NMDevice *device) +device_has_capability (NMDevice *device, NMDeviceCapabilities caps) { - int ifindex = nm_device_get_ip_ifindex (device); - - return ifindex > 0 ? nm_platform_link_is_up (ifindex) : TRUE; + return !!(NM_DEVICE_GET_PRIVATE (device)->capabilities & caps); } +/***********************************************************/ + void -nm_device_set_path (NMDevice *self, const char *path) +nm_device_dbus_export (NMDevice *device) { + static guint32 devcount = 0; NMDevicePrivate *priv; - g_return_if_fail (self != NULL); + g_return_if_fail (NM_IS_DEVICE (device)); - priv = NM_DEVICE_GET_PRIVATE (self); + priv = NM_DEVICE_GET_PRIVATE (device); g_return_if_fail (priv->path == NULL); - priv->path = g_strdup (path); + priv->path = g_strdup_printf ("/org/freedesktop/NetworkManager/Devices/%d", devcount++); + nm_log_info (LOGD_DEVICE, "(%s): exported as %s", priv->iface, priv->path); + nm_dbus_manager_register_object (nm_dbus_manager_get (), priv->path, device); } const char * @@ -638,9 +491,6 @@ nm_device_get_udi (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->udi; } -/* - * Get/set functions for iface - */ const char * nm_device_get_iface (NMDevice *self) { @@ -725,43 +575,6 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface) g_free (old_ip_iface); } -static guint -get_hw_address_length (NMDevice *dev, gboolean *out_permanent) -{ - size_t len; - - if (nm_platform_link_get_address (nm_device_get_ip_ifindex (dev), &len)) - return len; - else - return 0; -} - -static guint -nm_device_get_hw_address_length (NMDevice *dev, gboolean *out_permanent) -{ - return NM_DEVICE_GET_CLASS (dev)->get_hw_address_length (dev, out_permanent); -} - -const guint8 * -nm_device_get_hw_address (NMDevice *dev, guint *out_len) -{ - NMDevicePrivate *priv; - - g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); - priv = NM_DEVICE_GET_PRIVATE (dev); - - if (out_len) - *out_len = priv->hw_addr_len; - - if (priv->hw_addr_len == 0) - return NULL; - else - return priv->hw_addr; -} - -/* - * Get/set functions for driver - */ const char * nm_device_get_driver (NMDevice *self) { @@ -778,18 +591,6 @@ nm_device_get_driver_version (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->driver_version; } -const char * -nm_device_get_firmware_version (NMDevice *self) -{ - g_return_val_if_fail (self != NULL, NULL); - - return NM_DEVICE_GET_PRIVATE (self)->firmware_version; -} - - -/* - * Get/set functions for type - */ NMDeviceType nm_device_get_device_type (NMDevice *self) { @@ -857,6 +658,44 @@ nm_device_get_type_desc (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->type_desc; } +gboolean +nm_device_has_carrier (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->carrier; +} + +NMActRequest * +nm_device_get_act_request (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + return NM_DEVICE_GET_PRIVATE (self)->act_request; +} + +NMConnection * +nm_device_get_connection (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + return priv->act_request ? nm_act_request_get_connection (priv->act_request) : NULL; +} + +RfKillType +nm_device_get_rfkill_type (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + return NM_DEVICE_GET_PRIVATE (self)->rfkill_type; +} + +static const char * +nm_device_get_physical_port_id (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->physical_port_id; +} + +/***********************************************************/ + static gboolean nm_device_uses_generated_connection (NMDevice *self) { @@ -1020,7 +859,7 @@ carrier_changed (NMDevice *device, gboolean carrier) if (priv->ignore_carrier && !carrier) return; - if (nm_device_is_master (device)) { + if (priv->is_master) { /* Bridge/bond/team carrier does not affect its own activation, * but when carrier comes on, if there are slaves waiting, * it will restart them. @@ -1069,12 +908,6 @@ carrier_changed (NMDevice *device, gboolean carrier) } } -gboolean -nm_device_has_carrier (NMDevice *device) -{ - return NM_DEVICE_GET_PRIVATE (device)->carrier; -} - #define LINK_DISCONNECT_DELAY 4 static gboolean @@ -1338,15 +1171,6 @@ nm_device_owns_iface (NMDevice *device, const char *iface) } static void -check_carrier (NMDevice *device) -{ - int ifindex = nm_device_get_ip_ifindex (device); - - if (!device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) - nm_device_set_carrier (device, nm_platform_link_is_connected (ifindex)); -} - -static void slave_state_changed (NMDevice *slave, NMDeviceState slave_new_state, NMDeviceState slave_old_state, @@ -1400,7 +1224,7 @@ slave_state_changed (NMDevice *slave, * * Returns: %TRUE on success, %FALSE on failure */ -gboolean +static gboolean nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); @@ -1509,18 +1333,6 @@ nm_device_master_check_slave_physical_port (NMDevice *dev, NMDevice *slave, } } -/** - * nm_device_is_master: - * @dev: the device - * - * Returns: whether @dev can enslave other devices (eg, bridge or bond or team) - */ -gboolean -nm_device_is_master (NMDevice *dev) -{ - return NM_DEVICE_GET_PRIVATE (dev)->is_master; -} - /* release all slaves */ static void nm_device_master_release_slaves (NMDevice *self) @@ -1673,28 +1485,6 @@ nm_device_get_enslaved (NMDevice *device) return NM_DEVICE_GET_PRIVATE (device)->enslaved; } -/* - * nm_device_get_act_request - * - * Return the devices activation request, if any. - * - */ -NMActRequest * -nm_device_get_act_request (NMDevice *self) -{ - g_return_val_if_fail (self != NULL, NULL); - - return NM_DEVICE_GET_PRIVATE (self)->act_request; -} - -NMConnection * -nm_device_get_connection (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - return priv->act_request ? nm_act_request_get_connection (priv->act_request) : NULL; -} - static gboolean is_available (NMDevice *device) { @@ -1750,12 +1540,12 @@ nm_device_set_enabled (NMDevice *self, gboolean enabled) NM_DEVICE_GET_CLASS (self)->set_enabled (self, enabled); } -RfKillType -nm_device_get_rfkill_type (NMDevice *self) +gboolean +nm_device_get_autoconnect (NMDevice *device) { - g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - return NM_DEVICE_GET_PRIVATE (self)->rfkill_type; + return NM_DEVICE_GET_PRIVATE (device)->autoconnect; } static gboolean @@ -2061,6 +1851,18 @@ nm_device_check_connection_compatible (NMDevice *device, NMConnection *connectio return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection); } +static gboolean +string_in_list (const char *str, const char **array, gsize array_len) +{ + gsize i; + + for (i = 0; i < array_len; i++) { + if (strcmp (str, array[i]) == 0) + return TRUE; + } + return FALSE; +} + /** * nm_device_can_assume_connections: * @device: #NMDevice instance @@ -2068,19 +1870,70 @@ nm_device_check_connection_compatible (NMDevice *device, NMConnection *connectio * This is a convenience function to determine whether connection assumption * is available for this device. * - * Use this function when you need to determine whether full cleanup should - * be performed for this device or whether the device should be kept running - * between NetworkManager runs. + * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not + */ +static gboolean +nm_device_can_assume_connections (NMDevice *device) +{ + return !!NM_DEVICE_GET_CLASS (device)->update_connection; +} + +/** + * nm_device_can_assume_active_connection: + * @device: #NMDevice instance * - * Returns: %TRUE for assumable connections and %FALS for full-cleanup connections. + * This is a convenience function to determine whether the device's active + * connection can be assumed if NetworkManager restarts. This method returns + * %TRUE if and only if the device can assume connections, and the device has + * an active connection, and that active connection can be assumed. * - * FIXME: Consider turning this method into (a) a device capability or (b) a class - * method. + * Returns: %TRUE if the device's active connection can be assumed, or %FALSE + * if there is no active connection or the active connection cannot be + * assumed. */ gboolean -nm_device_can_assume_connections (NMDevice *device) +nm_device_can_assume_active_connection (NMDevice *device) { - return !!NM_DEVICE_GET_CLASS (device)->update_connection; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMConnection *connection; + const char *method; + const char *assumable_ip6_methods[] = { + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + const char *assumable_ip4_methods[] = { + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + }; + + if (!nm_device_can_assume_connections (device)) + return FALSE; + + connection = nm_device_get_connection (device); + if (!connection) + return FALSE; + + /* Can't assume connections that aren't yet configured + * FIXME: what about bridges/bonds waiting for slaves? + */ + if (priv->state < NM_DEVICE_STATE_IP_CONFIG) + return FALSE; + if (priv->ip4_state != IP_DONE && priv->ip6_state != IP_DONE) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (!string_in_list (method, assumable_ip6_methods, G_N_ELEMENTS (assumable_ip6_methods))) + return FALSE; + + method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (!string_in_list (method, assumable_ip4_methods, G_N_ELEMENTS (assumable_ip4_methods))) + return FALSE; + + return TRUE; } static gboolean @@ -2633,6 +2486,9 @@ aipd_child_setup (gpointer user_data G_GNUC_UNUSED) nm_unblock_posix_signals (NULL); } +/* default to installed helper, but can be modified for testing */ +const char *nm_device_autoipd_helper_path = LIBEXECDIR "/nm-avahi-autoipd.action"; + static NMActStageReturn aipd_start (NMDevice *self, NMDeviceStateReason *reason) { @@ -2708,6 +2564,37 @@ aipd_start (NMDevice *self, NMDeviceStateReason *reason) /* DHCPv4 stuff */ static void +dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dhcp4_config) { + g_clear_object (&priv->dhcp4_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); + } + + if (priv->dhcp4_client) { + /* Stop any ongoing DHCP transaction on this device */ + if (priv->dhcp4_state_sigid) { + g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid); + priv->dhcp4_state_sigid = 0; + } + + if (priv->dhcp4_timeout_sigid) { + g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid); + priv->dhcp4_timeout_sigid = 0; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp4_client, release); + + g_clear_object (&priv->dhcp4_client); + } +} + +static void dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data) { nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data), @@ -2773,6 +2660,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config) nm_device_get_connection (self), self, NULL, + NULL, NULL); } } @@ -3061,7 +2949,7 @@ act_stage3_ip4_config_start (NMDevice *self, g_assert_cmpstr (method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED); if ( strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) != 0 - && nm_device_is_master (self) + && priv->is_master && !priv->carrier) { nm_log_info (LOGD_IP4 | LOGD_DEVICE, "(%s): IPv4 config waiting until carrier is on", @@ -3117,6 +3005,39 @@ act_stage3_ip4_config_start (NMDevice *self, /* DHCPv6 stuff */ static void +dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; + g_clear_object (&priv->dhcp6_ip6_config); + + if (priv->dhcp6_config) { + g_clear_object (&priv->dhcp6_config); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); + } + + if (priv->dhcp6_client) { + if (priv->dhcp6_state_sigid) { + g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid); + priv->dhcp6_state_sigid = 0; + } + + if (priv->dhcp6_timeout_sigid) { + g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid); + priv->dhcp6_timeout_sigid = 0; + } + + nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); + + if (stop) + nm_dhcp_client_stop (priv->dhcp6_client, release); + + g_clear_object (&priv->dhcp6_client); + } +} + +static void dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data) { nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data), @@ -3187,7 +3108,7 @@ dhcp6_lease_change (NMDevice *device) nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); } else { /* Notify dispatcher scripts of new DHCPv6 config */ - nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL); + nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE, connection, device, NULL, NULL, NULL); } } @@ -3485,8 +3406,6 @@ linklocal6_start (NMDevice *self) /******************************************/ -static void dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release); - static void print_support_extended_ifa_flags (NMSettingIP6ConfigPrivacy use_tempaddr) { @@ -3773,6 +3692,48 @@ addrconf6_cleanup (NMDevice *self) /******************************************/ +static const char *ip6_properties_to_save[] = { + "accept_ra", + "accept_ra_defrtr", + "accept_ra_pinfo", + "accept_ra_rtr_pref", + "disable_ipv6", + "hop_limit", + "use_tempaddr", +}; + +static void +save_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ifname = nm_device_get_ip_iface (self); + char *value; + int i; + + g_hash_table_remove_all (priv->ip6_saved_properties); + + for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) { + value = nm_platform_sysctl_get (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])); + if (value) { + g_hash_table_insert (priv->ip6_saved_properties, + (char *) ip6_properties_to_save[i], + value); + } + } +} + +static void +restore_ip6_properties (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, priv->ip6_saved_properties); + while (g_hash_table_iter_next (&iter, &key, &value)) + nm_device_ipv6_sysctl_set (self, key, value); +} + static NMSettingIP6ConfigPrivacy use_tempaddr_clamp (NMSettingIP6ConfigPrivacy use_tempaddr) { @@ -3868,7 +3829,7 @@ act_stage3_ip6_config_start (NMDevice *self, g_assert_cmpstr (method, ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE); if ( strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) != 0 - && nm_device_is_master (self) + && priv->is_master && !priv->carrier) { nm_log_info (LOGD_IP6 | LOGD_DEVICE, "(%s): IPv6 config waiting until carrier is on", ip_iface); @@ -4606,6 +4567,7 @@ nm_device_activate_ip4_config_commit (gpointer user_data) nm_device_get_connection (self), self, NULL, + NULL, NULL); } @@ -4700,6 +4662,7 @@ nm_device_activate_ip6_config_commit (gpointer user_data) nm_device_get_connection (self), self, NULL, + NULL, NULL); } @@ -4772,78 +4735,6 @@ clear_act_request (NMDevice *self) } static void -dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - if (priv->dhcp4_config) { - g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); - g_object_unref (priv->dhcp4_config); - priv->dhcp4_config = NULL; - } - - if (priv->dhcp4_client) { - /* Stop any ongoing DHCP transaction on this device */ - if (priv->dhcp4_state_sigid) { - g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid); - priv->dhcp4_state_sigid = 0; - } - - if (priv->dhcp4_timeout_sigid) { - g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid); - priv->dhcp4_timeout_sigid = 0; - } - - nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); - - if (stop) - nm_dhcp_client_stop (priv->dhcp4_client, release); - - g_object_unref (priv->dhcp4_client); - priv->dhcp4_client = NULL; - } -} - -static void -dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; - - if (priv->dhcp6_ip6_config) { - g_object_unref (priv->dhcp6_ip6_config); - priv->dhcp6_ip6_config = NULL; - } - - if (priv->dhcp6_config) { - g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); - g_object_unref (priv->dhcp6_config); - priv->dhcp6_config = NULL; - } - - if (priv->dhcp6_client) { - if (priv->dhcp6_state_sigid) { - g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid); - priv->dhcp6_state_sigid = 0; - } - - if (priv->dhcp6_timeout_sigid) { - g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid); - priv->dhcp6_timeout_sigid = 0; - } - - nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); - - if (stop) - nm_dhcp_client_stop (priv->dhcp6_client, release); - - g_object_unref (priv->dhcp6_client); - priv->dhcp6_client = NULL; - } -} - -static void dnsmasq_cleanup (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); @@ -4887,6 +4778,20 @@ _update_ip4_address (NMDevice *self) close (fd); } +gboolean +nm_device_get_is_nm_owned (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned; +} + +void +nm_device_set_nm_owned (NMDevice *device) +{ + g_return_if_fail (NM_IS_DEVICE (device)); + + NM_DEVICE_GET_PRIVATE (device)->is_nm_owned = TRUE; +} + /* * delete_on_deactivate_link_delete * @@ -4934,12 +4839,12 @@ delete_on_deactivate_unschedule (NMDevice *self) static void delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex) { - NMDevicePrivate *priv; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); DeleteOnDeactivateData *data; if (ifindex <= 0) return; - if (!nm_device_get_is_nm_owned (self)) + if (!priv->is_nm_owned) return; if (!nm_device_is_software (self)) return; @@ -4949,8 +4854,6 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex) return; delete_on_deactivate_unschedule (self); /* always cancel and reschedule */ - priv = NM_DEVICE_GET_PRIVATE (self); - data = g_new (DeleteOnDeactivateData, 1); g_object_add_weak_pointer (G_OBJECT (self), (void **) &data->device); data->device = self; @@ -4962,143 +4865,6 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex) ifindex, nm_device_get_iface (self), data->idle_add_id); } -gboolean -nm_device_get_is_nm_owned (NMDevice *device) -{ - g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - return NM_DEVICE_GET_PRIVATE (device)->is_nm_owned; -} - -gboolean -nm_device_set_is_nm_owned (NMDevice *device, - gboolean is_nm_owned) -{ - NMDevicePrivate *priv; - - g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - - priv = NM_DEVICE_GET_PRIVATE (device); - - if (is_nm_owned == priv->is_nm_owned) - return TRUE; - if (!is_nm_owned) - return FALSE; - priv->is_nm_owned = TRUE; - return TRUE; -} - -/* - * nm_device_cleanup - * - * Remove a device's routing table entries and IP addresses. - * - */ -static void -nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason) -{ - NMDevicePrivate *priv; - NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; - NMConnection *connection = NULL; - int ifindex; - - g_return_if_fail (NM_IS_DEVICE (self)); - - if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) { - nm_log_info (LOGD_DEVICE, "(%s): preparing device", - nm_device_get_iface (self)); - } else { - nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason '%s') [%d]", - nm_device_get_iface (self), reason_to_string (reason), reason); - } - - /* Save whether or not we tried IPv6 for later */ - priv = NM_DEVICE_GET_PRIVATE (self); - - /* Clean up when device was deactivated during call to firewall */ - if (priv->fw_call) { - nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call); - priv->fw_call = NULL; - } - - if (priv->act_request) - connection = nm_act_request_get_connection (priv->act_request); - if (connection) { - nm_firewall_manager_remove_from_zone (priv->fw_manager, - nm_device_get_ip_iface (self), - NULL); - } - - ip_check_gw_ping_cleanup (self); - - /* Break the activation chain */ - activation_source_clear (self, TRUE, AF_INET); - activation_source_clear (self, TRUE, AF_INET6); - - /* Clear any queued transitions */ - nm_device_queued_state_clear (self); - nm_device_queued_ip_config_change_clear (self); - - priv->ip4_state = priv->ip6_state = IP_NONE; - - dhcp4_cleanup (self, TRUE, FALSE); - arp_cleanup (self); - dhcp6_cleanup (self, TRUE, FALSE); - linklocal6_cleanup (self); - addrconf6_cleanup (self); - dnsmasq_cleanup (self); - aipd_cleanup (self); - - /* Turn off kernel IPv6 */ - nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); - nm_device_ipv6_sysctl_set (self, "accept_ra", "0"); - nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0"); - - /* Call device type-specific deactivation */ - if (NM_DEVICE_GET_CLASS (self)->deactivate) - NM_DEVICE_GET_CLASS (self)->deactivate (self); - - /* master: release slaves */ - nm_device_master_release_slaves (self); - - /* slave: mark no longer enslaved */ - g_clear_object (&priv->master); - priv->enslaved = FALSE; - g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER); - - /* Tear down an existing activation request */ - clear_act_request (self); - - /* Take out any entries in the routing table and any IP address the device had. */ - ifindex = nm_device_get_ip_ifindex (self); - if (ifindex > 0) { - nm_platform_route_flush (ifindex); - nm_platform_address_flush (ifindex); - } - - /* Clean up nameservers and addresses */ - nm_device_set_ip4_config (self, NULL, TRUE, &ignored); - nm_device_set_ip6_config (self, NULL, TRUE, &ignored); - g_clear_object (&priv->ext_ip4_config); - g_clear_object (&priv->vpn4_config); - g_clear_object (&priv->vpn6_config); - g_clear_object (&priv->ext_ip6_config); - - /* Clear legacy IPv4 address property */ - priv->ip4_address = 0; - g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS); - - /* Only clear ip_iface after flushing all routes and addreses, since - * those are identified by ip_iface, not by iface (which might be a tty - * or ATM device). - */ - nm_device_set_ip_iface (self, NULL); - - /* Check if the device was deactivated, and if so, delete_link. - * Don't call delete_link synchronously because we are currently - * handling a state change -- which is not reentrant. */ - delete_on_deactivate_check_and_schedule (self, ifindex); -} - static void disconnect_cb (NMDevice *device, DBusGMethodInvocation *context, @@ -5492,6 +5258,36 @@ nm_device_get_ip6_config (NMDevice *self) /****************************************************************/ static void +ip_check_pre_up_done (guint call_id, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_return_if_fail (call_id == priv->dispatcher_id); + + priv->dispatcher_id = 0; + nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); +} + +static void +ip_check_pre_up (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_warn_if_fail (priv->dispatcher_id == 0); + + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_UP, + nm_device_get_connection (self), + self, + ip_check_pre_up_done, + self, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + ip_check_pre_up_done (0, self); + } +} + +static void ip_check_gw_ping_cleanup (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); @@ -5545,9 +5341,9 @@ ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data) } else nm_log_warn (log_domain, "(%s): ping stopped unexpectedly with status %d", iface, status); - /* We've got connectivity, proceed to secondaries */ + /* We've got connectivity, proceed to pre_up */ ip_check_gw_ping_cleanup (self); - nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + ip_check_pre_up (self); } static gboolean @@ -5562,7 +5358,7 @@ ip_check_ping_timeout_cb (gpointer user_data) nm_device_get_iface (self)); ip_check_gw_ping_cleanup (self); - nm_device_state_changed (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + ip_check_pre_up (self); return FALSE; } @@ -5665,9 +5461,9 @@ nm_device_start_ip_check (NMDevice *self) if (buf[0]) spawn_ping (self, log_domain, ping_binary, buf, timeout); - /* If no ping was started, just advance to SECONDARIES */ + /* If no ping was started, just advance to pre_up */ if (!priv->gw_ping.pid) - nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + ip_check_pre_up (self); } /****************************************************************/ @@ -5682,6 +5478,25 @@ carrier_wait_timeout (gpointer user_data) return G_SOURCE_REMOVE; } +static gboolean +nm_device_is_up (NMDevice *self) +{ + g_return_val_if_fail (NM_IS_DEVICE (self), FALSE); + + if (NM_DEVICE_GET_CLASS (self)->is_up) + return NM_DEVICE_GET_CLASS (self)->is_up (self); + + return TRUE; +} + +static gboolean +is_up (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + return ifindex > 0 ? nm_platform_link_is_up (ifindex) : TRUE; +} + gboolean nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) { @@ -5739,6 +5554,15 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) return TRUE; } +static void +check_carrier (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + if (!device_has_capability (device, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) + nm_device_set_carrier (device, nm_platform_link_is_connected (ifindex)); +} + static gboolean bring_up (NMDevice *device, gboolean *no_firmware) { @@ -5810,760 +5634,6 @@ take_down (NMDevice *device) return FALSE; } -static void -dispose (GObject *object) -{ - NMDevice *self = NM_DEVICE (object); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - gboolean deconfigure = TRUE; - NMPlatform *platform; - - if (priv->disposed || !priv->initialized) - goto out; - - priv->disposed = TRUE; - - /* Don't down can-assume-connection capable devices that are activated with - * a connection that can be assumed. - */ - if (nm_device_can_assume_connections (self) && (priv->state == NM_DEVICE_STATE_ACTIVATED)) { - NMConnection *connection; - const char *method; - - connection = nm_device_get_connection (self); - if (connection) { - /* Only static or DHCP IPv4 connections can be left up. - * All IPv6 connections can be left up, so we don't have - * to check that. - */ - method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG); - if ( !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) - || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) - || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) - deconfigure = FALSE; - } - } - - ip_check_gw_ping_cleanup (self); - - /* Clear any queued transitions */ - nm_device_queued_state_clear (self); - nm_device_queued_ip_config_change_clear (self); - - /* Clean up and stop address configuration */ - dhcp4_cleanup (self, deconfigure, FALSE); - arp_cleanup (self); - dhcp6_cleanup (self, deconfigure, FALSE); - linklocal6_cleanup (self); - addrconf6_cleanup (self); - dnsmasq_cleanup (self); - - g_warn_if_fail (priv->slaves == NULL); - g_assert (priv->master_ready_id == 0); - - /* Take the device itself down and clear its IP configuration */ - if (nm_device_get_managed (self) && deconfigure) { - NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; - - if (nm_device_get_act_request (self)) - nm_device_cleanup (self, NM_DEVICE_STATE_REASON_REMOVED); - nm_device_set_ip4_config (self, NULL, TRUE, &ignored); - nm_device_set_ip6_config (self, NULL, TRUE, &ignored); - - nm_device_take_down (self, FALSE); - - restore_ip6_properties (self); - - /* do a final check whether we should delete_link */ - delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self)); - } - g_clear_object (&priv->dev_ip4_config); - g_clear_object (&priv->ext_ip4_config); - g_clear_object (&priv->vpn4_config); - g_clear_object (&priv->ip4_config); - - g_clear_object (&priv->ip6_config); - g_clear_object (&priv->ac_ip6_config); - g_clear_object (&priv->dhcp6_ip6_config); - g_clear_object (&priv->vpn6_config); - g_clear_object (&priv->ext_ip6_config); - - g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref); - - if (priv->recheck_assume_id) { - g_source_remove (priv->recheck_assume_id); - priv->recheck_assume_id = 0; - } - - link_disconnect_action_cancel (self); - - if (priv->con_provider) { - g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_added, self); - g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_removed, self); - g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_updated, self); - priv->con_provider = NULL; - } - - g_hash_table_unref (priv->available_connections); - priv->available_connections = NULL; - - if (priv->carrier_wait_id) { - g_source_remove (priv->carrier_wait_id); - priv->carrier_wait_id = 0; - } - - g_clear_pointer (&priv->physical_port_id, g_free); - - activation_source_clear (self, TRUE, AF_INET); - activation_source_clear (self, TRUE, AF_INET6); - - clear_act_request (self); - g_clear_object (&priv->queued_act_request); - - platform = nm_platform_get (); - g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ip_changed), self); - g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self); - -out: - G_OBJECT_CLASS (nm_device_parent_class)->dispose (object); -} - -static void -finalize (GObject *object) -{ - NMDevice *self = NM_DEVICE (object); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - if (priv->fw_manager) - g_object_unref (priv->fw_manager); - - g_slist_free_full (priv->pending_actions, g_free); - - g_free (priv->udi); - g_free (priv->path); - g_free (priv->iface); - g_free (priv->ip_iface); - g_free (priv->driver); - g_free (priv->driver_version); - g_free (priv->firmware_version); - g_free (priv->type_desc); - if (priv->dhcp_anycast_address) - g_byte_array_free (priv->dhcp_anycast_address, TRUE); - - G_OBJECT_CLASS (nm_device_parent_class)->finalize (object); -} - - -static void -set_property (GObject *object, guint prop_id, - const GValue *value, GParamSpec *pspec) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object); - NMPlatformLink *platform_device; - const char *hw_addr; - - switch (prop_id) { - case PROP_PLATFORM_DEVICE: - platform_device = g_value_get_pointer (value); - if (platform_device) { - g_free (priv->udi); - priv->udi = g_strdup (platform_device->udi); - g_free (priv->iface); - priv->iface = g_strdup (platform_device->name); - priv->ifindex = platform_device->ifindex; - g_free (priv->driver); - priv->driver = g_strdup (platform_device->driver); - } - break; - case PROP_UDI: - if (g_value_get_string (value)) { - g_free (priv->udi); - priv->udi = g_value_dup_string (value); - } - break; - case PROP_IFACE: - if (g_value_get_string (value)) { - g_free (priv->iface); - priv->ifindex = 0; - priv->iface = g_value_dup_string (value); - - /* Only look up the ifindex if it appears to be an actual kernel - * interface name. eg Bluetooth devices won't have one until we know - * the IP interface. - */ - if (priv->iface && !strchr (priv->iface, ':')) { - priv->ifindex = nm_platform_link_get_ifindex (priv->iface); - if (priv->ifindex <= 0) - nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface); - } - } - break; - case PROP_DRIVER: - if (g_value_get_string (value)) { - g_free (priv->driver); - priv->driver = g_value_dup_string (value); - } - break; - case PROP_DRIVER_VERSION: - g_free (priv->driver_version); - priv->driver_version = g_strdup (g_value_get_string (value)); - break; - case PROP_FIRMWARE_VERSION: - g_free (priv->firmware_version); - priv->firmware_version = g_strdup (g_value_get_string (value)); - break; - case PROP_MTU: - priv->mtu = g_value_get_uint (value); - break; - case PROP_IP4_ADDRESS: - priv->ip4_address = g_value_get_uint (value); - break; - case PROP_AUTOCONNECT: - priv->autoconnect = g_value_get_boolean (value); - break; - case PROP_FIRMWARE_MISSING: - priv->firmware_missing = g_value_get_boolean (value); - break; - case PROP_DEVICE_TYPE: - g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN); - priv->type = g_value_get_uint (value); - break; - case PROP_TYPE_DESC: - g_free (priv->type_desc); - priv->type_desc = g_value_dup_string (value); - break; - case PROP_RFKILL_TYPE: - priv->rfkill_type = g_value_get_uint (value); - break; - case PROP_IS_MASTER: - priv->is_master = g_value_get_boolean (value); - break; - case PROP_HW_ADDRESS: - priv->hw_addr_len = nm_device_get_hw_address_length (NM_DEVICE (object), NULL); - - hw_addr = g_value_get_string (value); - if (!hw_addr) - break; - if (priv->hw_addr_len == 0) { - g_warn_if_fail (*hw_addr == '\0'); - break; - } - - if (!nm_utils_hwaddr_aton_len (hw_addr, priv->hw_addr, priv->hw_addr_len)) { - g_warning ("Could not parse hw-address '%s'", hw_addr); - memset (priv->hw_addr, 0, sizeof (priv->hw_addr)); - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static gboolean -ip_config_valid (NMDeviceState state) -{ - return (state == NM_DEVICE_STATE_UNMANAGED) || - (state >= NM_DEVICE_STATE_IP_CHECK && - state <= NM_DEVICE_STATE_DEACTIVATING); -} - -static void -get_property (GObject *object, guint prop_id, - GValue *value, GParamSpec *pspec) -{ - NMDevice *self = NM_DEVICE (object); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - const char *ac_path = NULL; - GPtrArray *array; - GHashTableIter iter; - NMConnection *connection; - - switch (prop_id) { - case PROP_UDI: - g_value_set_string (value, priv->udi); - break; - case PROP_IFACE: - g_value_set_string (value, priv->iface); - break; - case PROP_IP_IFACE: - if (ip_config_valid (priv->state)) - g_value_set_string (value, nm_device_get_ip_iface (self)); - else - g_value_set_string (value, NULL); - break; - case PROP_IFINDEX: - g_value_set_int (value, priv->ifindex); - break; - case PROP_DRIVER: - g_value_set_string (value, priv->driver); - break; - case PROP_DRIVER_VERSION: - g_value_set_string (value, priv->driver_version); - break; - case PROP_FIRMWARE_VERSION: - g_value_set_string (value, priv->firmware_version); - break; - case PROP_CAPABILITIES: - g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK)); - break; - case PROP_IP4_ADDRESS: - g_value_set_uint (value, priv->ip4_address); - break; - case PROP_CARRIER: - g_value_set_boolean (value, priv->carrier); - break; - case PROP_MTU: - g_value_set_uint (value, priv->mtu); - break; - case PROP_IP4_CONFIG: - if (ip_config_valid (priv->state) && priv->ip4_config) - g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config)); - else - g_value_set_boxed (value, "/"); - break; - case PROP_DHCP4_CONFIG: - if (ip_config_valid (priv->state) && priv->dhcp4_client) - g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config)); - else - g_value_set_boxed (value, "/"); - break; - case PROP_IP6_CONFIG: - if (ip_config_valid (priv->state) && priv->ip6_config) - g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config)); - else - g_value_set_boxed (value, "/"); - break; - case PROP_DHCP6_CONFIG: - if (ip_config_valid (priv->state) && priv->dhcp6_client) - g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config)); - else - g_value_set_boxed (value, "/"); - break; - case PROP_STATE: - g_value_set_uint (value, priv->state); - break; - case PROP_STATE_REASON: - g_value_take_boxed (value, dbus_g_type_specialized_construct (DBUS_G_TYPE_UINT_STRUCT)); - dbus_g_type_struct_set (value, - 0, priv->state, - 1, priv->state_reason, - G_MAXUINT); - break; - case PROP_ACTIVE_CONNECTION: - if (priv->act_request) - ac_path = nm_active_connection_get_path (NM_ACTIVE_CONNECTION (priv->act_request)); - g_value_set_boxed (value, ac_path ? ac_path : "/"); - break; - case PROP_DEVICE_TYPE: - g_value_set_uint (value, priv->type); - break; - case PROP_MANAGED: - g_value_set_boolean (value, nm_device_get_managed (self)); - break; - case PROP_AUTOCONNECT: - g_value_set_boolean (value, priv->autoconnect); - break; - case PROP_FIRMWARE_MISSING: - g_value_set_boolean (value, priv->firmware_missing); - break; - case PROP_TYPE_DESC: - g_value_set_string (value, priv->type_desc); - break; - case PROP_RFKILL_TYPE: - g_value_set_uint (value, priv->rfkill_type); - break; - case PROP_AVAILABLE_CONNECTIONS: - array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections)); - g_hash_table_iter_init (&iter, priv->available_connections); - while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) - g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection))); - g_value_take_boxed (value, array); - break; - case PROP_PHYSICAL_PORT_ID: - g_value_set_string (value, priv->physical_port_id); - break; - case PROP_IS_MASTER: - g_value_set_boolean (value, priv->is_master); - break; - case PROP_MASTER: - g_value_set_object (value, priv->master); - break; - case PROP_HW_ADDRESS: - if (priv->hw_addr_len) - g_value_take_string (value, nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len)); - else - g_value_set_string (value, NULL); - break; - case PROP_HAS_PENDING_ACTION: - g_value_set_boolean (value, nm_device_has_pending_action (self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -nm_device_class_init (NMDeviceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - g_type_class_add_private (object_class, sizeof (NMDevicePrivate)); - - /* Virtual methods */ - object_class->dispose = dispose; - object_class->finalize = finalize; - object_class->set_property = set_property; - object_class->get_property = get_property; - object_class->constructor = constructor; - object_class->constructed = constructed; - - klass->link_changed = link_changed; - - klass->is_available = is_available; - klass->act_stage1_prepare = act_stage1_prepare; - klass->act_stage2_config = act_stage2_config; - klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start; - klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start; - klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout; - klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout; - klass->have_any_ready_slaves = have_any_ready_slaves; - - klass->spec_match_list = spec_match_list; - klass->can_auto_connect = can_auto_connect; - klass->check_connection_compatible = check_connection_compatible; - klass->check_connection_available = check_connection_available; - klass->is_up = is_up; - klass->bring_up = bring_up; - klass->take_down = take_down; - klass->carrier_changed = carrier_changed; - klass->get_hw_address_length = get_hw_address_length; - - /* Properties */ - g_object_class_install_property - (object_class, PROP_PLATFORM_DEVICE, - g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE, - "Platform Device", - "NMPlatform device object", - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_UDI, - g_param_spec_string (NM_DEVICE_UDI, - "UDI", - "Unique Device Identifier", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - g_object_class_install_property - (object_class, PROP_IFACE, - g_param_spec_string (NM_DEVICE_IFACE, - "Interface", - "Interface", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_IP_IFACE, - g_param_spec_string (NM_DEVICE_IP_IFACE, - "IP Interface", - "IP Interface", - NULL, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_DRIVER, - g_param_spec_string (NM_DEVICE_DRIVER, - "Driver", - "Driver", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_DRIVER_VERSION, - g_param_spec_string (NM_DEVICE_DRIVER_VERSION, - "Driver Version", - "Driver Version", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_FIRMWARE_VERSION, - g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, - "Firmware Version", - "Firmware Version", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_CAPABILITIES, - g_param_spec_uint (NM_DEVICE_CAPABILITIES, - "Capabilities", - "Capabilities", - 0, G_MAXUINT32, NM_DEVICE_CAP_NONE, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_CARRIER, - g_param_spec_boolean (NM_DEVICE_CARRIER, - "Carrier", - "Carrier", - FALSE, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_MTU, - g_param_spec_uint (NM_DEVICE_MTU, - "MTU", - "MTU", - 0, G_MAXUINT32, 1500, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_IP4_ADDRESS, - g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, - "IP4 address", - "IP4 address", - 0, G_MAXUINT32, 0, /* FIXME */ - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_IP4_CONFIG, - g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, - "IP4 Config", - "IP4 Config", - DBUS_TYPE_G_OBJECT_PATH, - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_DHCP4_CONFIG, - g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG, - "DHCP4 Config", - "DHCP4 Config", - DBUS_TYPE_G_OBJECT_PATH, - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_IP6_CONFIG, - g_param_spec_boxed (NM_DEVICE_IP6_CONFIG, - "IP6 Config", - "IP6 Config", - DBUS_TYPE_G_OBJECT_PATH, - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_DHCP6_CONFIG, - g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG, - "DHCP6 Config", - "DHCP6 Config", - DBUS_TYPE_G_OBJECT_PATH, - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_STATE, - g_param_spec_uint (NM_DEVICE_STATE, - "State", - "State", - 0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN, - G_PARAM_READABLE)); - g_object_class_install_property - (object_class, PROP_STATE_REASON, - g_param_spec_boxed (NM_DEVICE_STATE_REASON, - "StateReason", - "StateReason", - DBUS_G_TYPE_UINT_STRUCT, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_ACTIVE_CONNECTION, - g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION, - "ActiveConnection", - "ActiveConnection", - DBUS_TYPE_G_OBJECT_PATH, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_DEVICE_TYPE, - g_param_spec_uint (NM_DEVICE_DEVICE_TYPE, - "DeviceType", - "DeviceType", - 0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_MANAGED, - g_param_spec_boolean (NM_DEVICE_MANAGED, - "Managed", - "Managed", - FALSE, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_AUTOCONNECT, - g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, - "Autoconnect", - "Autoconnect", - DEFAULT_AUTOCONNECT, - G_PARAM_READWRITE)); - - g_object_class_install_property - (object_class, PROP_FIRMWARE_MISSING, - g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, - "FirmwareMissing", - "Firmware missing", - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_TYPE_DESC, - g_param_spec_string (NM_DEVICE_TYPE_DESC, - "Type Description", - "Device type description", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_RFKILL_TYPE, - g_param_spec_uint (NM_DEVICE_RFKILL_TYPE, - "Rfkill Type", - "Type of rfkill switch (if any) supported by this device", - RFKILL_TYPE_WLAN, - RFKILL_TYPE_MAX, - RFKILL_TYPE_UNKNOWN, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_IFINDEX, - g_param_spec_int (NM_DEVICE_IFINDEX, - "Ifindex", - "Ifindex", - 0, G_MAXINT, 0, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_AVAILABLE_CONNECTIONS, - g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, - "AvailableConnections", - "AvailableConnections", - DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_PHYSICAL_PORT_ID, - g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID, - "PhysicalPortId", - "PhysicalPortId", - NULL, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_IS_MASTER, - g_param_spec_boolean (NM_DEVICE_IS_MASTER, - "IsMaster", - "IsMaster", - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_MASTER, - g_param_spec_object (NM_DEVICE_MASTER, - "Master", - "Master", - NM_TYPE_DEVICE, - G_PARAM_READABLE)); - - g_object_class_install_property - (object_class, PROP_HW_ADDRESS, - g_param_spec_string (NM_DEVICE_HW_ADDRESS, - "Hardware Address", - "Hardware address", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - g_object_class_install_property - (object_class, PROP_HAS_PENDING_ACTION, - g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION, - "Has pending action", - "Has pending action", - FALSE, - G_PARAM_READABLE)); - - /* Signals */ - signals[STATE_CHANGED] = - g_signal_new ("state-changed", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NMDeviceClass, state_changed), - NULL, NULL, NULL, - G_TYPE_NONE, 3, - G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); - - signals[AUTOCONNECT_ALLOWED] = - g_signal_new ("autoconnect-allowed", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - 0, - autoconnect_allowed_accumulator, NULL, NULL, - G_TYPE_BOOLEAN, 0); - - signals[AUTH_REQUEST] = - g_signal_new (NM_DEVICE_AUTH_REQUEST, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - /* dbus-glib context, connection, permission, allow_interaction, callback, user_data */ - G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER); - - signals[IP4_CONFIG_CHANGED] = - g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); - - signals[IP6_CONFIG_CHANGED] = - g_signal_new (NM_DEVICE_IP6_CONFIG_CHANGED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); - - signals[REMOVED] = - g_signal_new (NM_DEVICE_REMOVED, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); - - signals[RECHECK_AUTO_ACTIVATE] = - g_signal_new (NM_DEVICE_RECHECK_AUTO_ACTIVATE, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); - - signals[RECHECK_ASSUME] = - g_signal_new (NM_DEVICE_RECHECK_ASSUME, - 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 (klass), - &dbus_glib_nm_device_object_info); - - dbus_g_error_domain_register (NM_DEVICE_ERROR, NULL, NM_TYPE_DEVICE_ERROR); -} - -static void -nm_device_config_device_interface_init (NMConfigDeviceInterface *iface) -{ - iface->spec_match_list = (gboolean (*) (NMConfigDevice *, const GSList *)) nm_device_spec_match_list; - iface->get_hw_address = (const guint8 * (*) (NMConfigDevice *, guint *)) nm_device_get_hw_address; -} - void nm_device_set_firmware_missing (NMDevice *self, gboolean new_missing) { @@ -6584,508 +5654,6 @@ nm_device_get_firmware_missing (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->firmware_missing; } -#define QUEUED_PREFIX "queued state change to " - -static const char * -queued_state_to_string (NMDeviceState state) -{ - switch (state) { - case NM_DEVICE_STATE_UNMANAGED: - return QUEUED_PREFIX "unmanaged"; - case NM_DEVICE_STATE_UNAVAILABLE: - return QUEUED_PREFIX "unavailable"; - case NM_DEVICE_STATE_DISCONNECTED: - return QUEUED_PREFIX "disconnected"; - case NM_DEVICE_STATE_PREPARE: - return QUEUED_PREFIX "prepare"; - case NM_DEVICE_STATE_CONFIG: - return QUEUED_PREFIX "config"; - case NM_DEVICE_STATE_NEED_AUTH: - return QUEUED_PREFIX "need-auth"; - case NM_DEVICE_STATE_IP_CONFIG: - return QUEUED_PREFIX "ip-config"; - case NM_DEVICE_STATE_IP_CHECK: - return QUEUED_PREFIX "ip-check"; - case NM_DEVICE_STATE_SECONDARIES: - return QUEUED_PREFIX "secondaries"; - case NM_DEVICE_STATE_ACTIVATED: - return QUEUED_PREFIX "activated"; - case NM_DEVICE_STATE_DEACTIVATING: - return QUEUED_PREFIX "deactivating"; - case NM_DEVICE_STATE_FAILED: - return QUEUED_PREFIX "failed"; - default: - break; - } - return QUEUED_PREFIX "unknown"; -} - -static const char * -state_to_string (NMDeviceState state) -{ - return queued_state_to_string (state) + strlen (QUEUED_PREFIX); -} - -static const char * -reason_to_string (NMDeviceStateReason reason) -{ - switch (reason) { - case NM_DEVICE_STATE_REASON_NONE: - return "none"; - case NM_DEVICE_STATE_REASON_NOW_MANAGED: - return "managed"; - case NM_DEVICE_STATE_REASON_NOW_UNMANAGED: - return "unmanaged"; - case NM_DEVICE_STATE_REASON_CONFIG_FAILED: - return "config-failed"; - case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE: - return "ip-config-unavailable"; - case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED: - return "ip-config-expired"; - case NM_DEVICE_STATE_REASON_NO_SECRETS: - return "no-secrets"; - case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: - return "supplicant-disconnect"; - case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: - return "supplicant-config-failed"; - case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: - return "supplicant-failed"; - case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: - return "supplicant-timeout"; - case NM_DEVICE_STATE_REASON_PPP_START_FAILED: - return "ppp-start-failed"; - case NM_DEVICE_STATE_REASON_PPP_DISCONNECT: - return "ppp-disconnect"; - case NM_DEVICE_STATE_REASON_PPP_FAILED: - return "ppp-failed"; - case NM_DEVICE_STATE_REASON_DHCP_START_FAILED: - return "dhcp-start-failed"; - case NM_DEVICE_STATE_REASON_DHCP_ERROR: - return "dhcp-error"; - case NM_DEVICE_STATE_REASON_DHCP_FAILED: - return "dhcp-failed"; - case NM_DEVICE_STATE_REASON_SHARED_START_FAILED: - return "sharing-start-failed"; - case NM_DEVICE_STATE_REASON_SHARED_FAILED: - return "sharing-failed"; - case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: - return "autoip-start-failed"; - case NM_DEVICE_STATE_REASON_AUTOIP_ERROR: - return "autoip-error"; - case NM_DEVICE_STATE_REASON_AUTOIP_FAILED: - return "autoip-failed"; - case NM_DEVICE_STATE_REASON_MODEM_BUSY: - return "modem-busy"; - case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: - return "modem-no-dialtone"; - case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: - return "modem-no-carrier"; - case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: - return "modem-dial-timeout"; - case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: - return "modem-dial-failed"; - case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: - return "modem-init-failed"; - case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: - return "gsm-apn-failed"; - case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: - return "gsm-registration-idle"; - case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: - return "gsm-registration-denied"; - case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: - return "gsm-registration-timeout"; - case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: - return "gsm-registration-failed"; - case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: - return "gsm-pin-check-failed"; - case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: - return "firmware-missing"; - case NM_DEVICE_STATE_REASON_REMOVED: - return "removed"; - case NM_DEVICE_STATE_REASON_SLEEPING: - return "sleeping"; - case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: - return "connection-removed"; - case NM_DEVICE_STATE_REASON_USER_REQUESTED: - return "user-requested"; - case NM_DEVICE_STATE_REASON_CARRIER: - return "carrier-changed"; - case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED: - return "connection-assumed"; - case NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE: - return "supplicant-available"; - case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND: - return "modem-not-found"; - case NM_DEVICE_STATE_REASON_BT_FAILED: - return "bluetooth-failed"; - case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: - return "gsm-sim-not-inserted"; - case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: - return "gsm-sim-pin-required"; - case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: - return "gsm-sim-puk-required"; - case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: - return "gsm-sim-wrong"; - case NM_DEVICE_STATE_REASON_INFINIBAND_MODE: - return "infiniband-mode"; - case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: - return "dependency-failed"; - case NM_DEVICE_STATE_REASON_BR2684_FAILED: - return "br2684-bridge-failed"; - case NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE: - return "modem-manager-unavailable"; - case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND: - return "SSID not found"; - case NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED: - return "secondary-connection-failed"; - case NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED: - return "DCB-FCoE-failed"; - case NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED: - return "teamd-control-failed"; - case NM_DEVICE_STATE_REASON_MODEM_FAILED: - return "modem-failed"; - case NM_DEVICE_STATE_REASON_MODEM_AVAILABLE: - return "modem-available"; - case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: - return "sim-pin-incorrect"; - default: - break; - } - return "unknown"; -} - -static void -notify_ip_properties (NMDevice *device) -{ - g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); - g_object_notify (G_OBJECT (device), NM_DEVICE_IP4_CONFIG); - g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG); - g_object_notify (G_OBJECT (device), NM_DEVICE_IP6_CONFIG); - g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG); -} - -void -nm_device_state_changed (NMDevice *device, - NMDeviceState state, - NMDeviceStateReason reason) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - NMDeviceState old_state; - NMActRequest *req; - gboolean no_firmware = FALSE; - NMConnection *connection; - - /* Track re-entry */ - g_warn_if_fail (priv->in_state_changed == FALSE); - priv->in_state_changed = TRUE; - - g_return_if_fail (NM_IS_DEVICE (device)); - - /* Do nothing if state isn't changing, but as a special case allow - * re-setting UNAVAILABLE if the device is missing firmware so that we - * can retry device initialization. - */ - if ( (priv->state == state) - && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) { - priv->in_state_changed = FALSE; - return; - } - - old_state = priv->state; - priv->state = state; - priv->state_reason = reason; - - nm_log_info (LOGD_DEVICE, "(%s): device state change: %s -> %s (reason '%s') [%d %d %d]", - nm_device_get_iface (device), - state_to_string (old_state), - state_to_string (state), - reason_to_string (reason), - old_state, - state, - reason); - - /* Clear any queued transitions */ - nm_device_queued_state_clear (device); - - /* Cache the activation request for the dispatcher */ - req = priv->act_request ? g_object_ref (priv->act_request) : NULL; - - if (state <= NM_DEVICE_STATE_UNAVAILABLE) { - _clear_available_connections (device, TRUE); - g_clear_object (&priv->queued_act_request); - } - - /* Update the available connections list when a device first becomes available */ - if ( state >= NM_DEVICE_STATE_DISCONNECTED - && old_state < NM_DEVICE_STATE_DISCONNECTED) - nm_device_recheck_available_connections (device); - - /* Handle the new state here; but anything that could trigger - * another state change should be done below. - */ - switch (state) { - case NM_DEVICE_STATE_UNMANAGED: - nm_device_set_firmware_missing (device, FALSE); - if (old_state > NM_DEVICE_STATE_UNMANAGED) { - /* Clean up if the device is now unmanaged but was activated */ - if (nm_device_get_act_request (device)) - nm_device_cleanup (device, reason); - nm_device_take_down (device, TRUE); - restore_ip6_properties (device); - } - break; - case NM_DEVICE_STATE_UNAVAILABLE: - if (old_state == NM_DEVICE_STATE_UNMANAGED) { - save_ip6_properties (device); - if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) { - nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1"); - nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0"); - nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0"); - nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0"); - nm_device_ipv6_sysctl_set (device, "use_tempaddr", "0"); - } - } - - if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { - if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) - nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device)); - nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE); - } - /* Ensure the device gets deactivated in response to stuff like - * carrier changes or rfkill. But don't deactivate devices that are - * about to assume a connection since that defeats the purpose of - * assuming the device's existing connection. - * - * Note that we "deactivate" the device even when coming from - * UNMANAGED, to ensure that it's in a clean state. - */ - if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) - nm_device_cleanup (device, reason); - break; - case NM_DEVICE_STATE_DISCONNECTED: - if (old_state > NM_DEVICE_STATE_UNAVAILABLE) - nm_device_cleanup (device, reason); - break; - default: - break; - } - - /* Reset autoconnect flag when the device is activating or connected. */ - if ( state >= NM_DEVICE_STATE_PREPARE - && state <= NM_DEVICE_STATE_ACTIVATED) - priv->autoconnect = TRUE; - - g_object_notify (G_OBJECT (device), NM_DEVICE_STATE); - g_object_notify (G_OBJECT (device), NM_DEVICE_STATE_REASON); - g_signal_emit_by_name (device, "state-changed", state, old_state, reason); - - /* Post-process the event after internal notification */ - - switch (state) { - case NM_DEVICE_STATE_UNAVAILABLE: - /* If the device can activate now (ie, it's got a carrier, the supplicant - * is active, or whatever) schedule a delayed transition to DISCONNECTED - * to get things rolling. The device can't transition immediately because - * we can't change states again from the state handler for a variety of - * reasons. - */ - if (nm_device_is_available (device)) { - nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED", - nm_device_get_iface (device)); - nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); - } else { - if (old_state == NM_DEVICE_STATE_UNMANAGED) { - nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED", - nm_device_get_iface (device)); - } else if ( old_state > NM_DEVICE_STATE_UNAVAILABLE - && nm_device_get_default_unmanaged (device)) - nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); - } - break; - case NM_DEVICE_STATE_DEACTIVATING: - nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, reason); - break; - case NM_DEVICE_STATE_DISCONNECTED: - if (priv->queued_act_request) { - NMActRequest *queued_req; - - queued_req = priv->queued_act_request; - priv->queued_act_request = NULL; - _device_activate (device, queued_req); - g_object_unref (queued_req); - } else if ( old_state > NM_DEVICE_STATE_DISCONNECTED - && nm_device_get_default_unmanaged (device)) - nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); - break; - case NM_DEVICE_STATE_ACTIVATED: - nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.", - nm_device_get_iface (device)); - nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL); - break; - case NM_DEVICE_STATE_FAILED: - connection = nm_device_get_connection (device); - nm_log_warn (LOGD_DEVICE | LOGD_WIFI, - "Activation (%s) failed for connection '%s'", - nm_device_get_iface (device), - connection ? nm_connection_get_id (connection) : "<unknown>"); - - /* Notify any slaves of the unexpected failure */ - nm_device_master_release_slaves (device); - - /* If the connection doesn't yet have a timestamp, set it to zero so that - * we can distinguish between connections we've tried to activate and have - * failed (zero timestamp), connections that succeeded (non-zero timestamp), - * and those we haven't tried yet (no timestamp). - */ - if (connection && !nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) { - nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection), - (guint64) 0, - TRUE); - } - - /* Schedule the transition to DISCONNECTED. The device can't transition - * immediately because we can't change states again from the state - * handler for a variety of reasons. - */ - nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); - break; - case NM_DEVICE_STATE_IP_CHECK: - nm_device_start_ip_check (device); - - /* IP-related properties are only valid when the device has IP configuration; - * now that it does, ensure their change notifications are emitted. - */ - notify_ip_properties (device); - break; - case NM_DEVICE_STATE_SECONDARIES: - ip_check_gw_ping_cleanup (device); - nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state", - nm_device_get_iface (device)); - break; - default: - break; - } - - if (state > NM_DEVICE_STATE_DISCONNECTED) - delete_on_deactivate_unschedule (device); - - if (old_state == NM_DEVICE_STATE_ACTIVATED) - nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL); - - /* IP-related properties are only valid when the device has IP configuration. - * If it no longer does, ensure their change notifications are emitted. - */ - if (ip_config_valid (old_state) && !ip_config_valid (state)) - notify_ip_properties (device); - - /* Dispose of the cached activation request */ - if (req) - g_object_unref (req); - - priv->in_state_changed = FALSE; -} - -static gboolean -queued_set_state (gpointer user_data) -{ - NMDevice *self = NM_DEVICE (user_data); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - NMDeviceState new_state; - NMDeviceStateReason new_reason; - - if (priv->queued_state.id) { - nm_log_dbg (LOGD_DEVICE, "(%s): running queued state change to %s (id %d)", - nm_device_get_iface (self), - state_to_string (priv->queued_state.state), - priv->queued_state.id); - - /* Clear queued state struct before triggering state change, since - * the state change may queue another state. - */ - priv->queued_state.id = 0; - new_state = priv->queued_state.state; - new_reason = priv->queued_state.reason; - nm_device_queued_state_clear (self); - - nm_device_state_changed (self, new_state, new_reason); - nm_device_remove_pending_action (self, queued_state_to_string (new_state), TRUE); - } else { - g_warn_if_fail (priv->queued_state.state == NM_DEVICE_STATE_UNKNOWN); - g_warn_if_fail (priv->queued_state.reason == NM_DEVICE_STATE_REASON_NONE); - } - return FALSE; -} - -void -nm_device_queue_state (NMDevice *self, - NMDeviceState state, - NMDeviceStateReason reason) -{ - NMDevicePrivate *priv; - - g_return_if_fail (NM_IS_DEVICE (self)); - - priv = NM_DEVICE_GET_PRIVATE (self); - - if (priv->queued_state.id && priv->queued_state.state == state) - return; - - /* Add pending action for the new state before clearing the queued states, so - * that we don't accidently pop all pending states and reach 'startup complete' */ - nm_device_add_pending_action (self, queued_state_to_string (state), TRUE); - - /* We should only ever have one delayed state transition at a time */ - if (priv->queued_state.id) { - nm_log_warn (LOGD_DEVICE, "(%s): overwriting previously queued state change to %s (%s)", - nm_device_get_iface (self), - state_to_string (priv->queued_state.state), - reason_to_string (priv->queued_state.reason)); - nm_device_queued_state_clear (self); - } - - priv->queued_state.state = state; - priv->queued_state.reason = reason; - priv->queued_state.id = g_idle_add (queued_set_state, self); - - nm_log_dbg (LOGD_DEVICE, "(%s): queued state change to %s due to %s (id %d)", - nm_device_get_iface (self), state_to_string (state), reason_to_string (reason), - priv->queued_state.id); -} - -NMDeviceState -nm_device_queued_state_peek (NMDevice *self) -{ - NMDevicePrivate *priv; - - g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN); - - priv = NM_DEVICE_GET_PRIVATE (self); - - return priv->queued_state.id ? priv->queued_state.state : NM_DEVICE_STATE_UNKNOWN; -} - -void -nm_device_queued_state_clear (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - if (priv->queued_state.id) { - nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)", - nm_device_get_iface (self), priv->queued_state.id); - g_source_remove (priv->queued_state.id); - nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state), TRUE); - } - memset (&priv->queued_state, 0, sizeof (priv->queued_state)); -} - -NMDeviceState -nm_device_get_state (NMDevice *device) -{ - g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN); - - return NM_DEVICE_GET_PRIVATE (device)->state; -} - static NMIP4Config * find_ip4_lease_config (NMDevice *device, NMConnection *connection, @@ -7281,7 +5849,7 @@ device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, } } -void +static void nm_device_queued_ip_config_change_clear (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); @@ -7323,27 +5891,27 @@ nm_device_get_managed (NMDevice *device) } /** - * nm_device_get_default_unmanaged(): + * nm_device_get_unmanaged_flag(): * @device: the #NMDevice * - * Returns: %TRUE if the device is by default unmanaged + * Returns: %TRUE if the device is unmanaged for @flag. */ gboolean -nm_device_get_default_unmanaged (NMDevice *device) +nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag) { - return nm_device_get_unmanaged_flag (device, NM_UNMANAGED_DEFAULT); + return NM_DEVICE_GET_PRIVATE (device)->unmanaged_flags & flag; } /** - * nm_device_get_unmanaged_flag(): + * nm_device_get_default_unmanaged(): * @device: the #NMDevice * - * Returns: %TRUE if the device is unmanaged for @flag. + * Returns: %TRUE if the device is by default unmanaged */ -gboolean -nm_device_get_unmanaged_flag (NMDevice *device, NMUnmanagedFlags flag) +static gboolean +nm_device_get_default_unmanaged (NMDevice *device) { - return NM_DEVICE_GET_PRIVATE (device)->unmanaged_flags & flag; + return nm_device_get_unmanaged_flag (device, NM_UNMANAGED_DEFAULT); } void @@ -7381,6 +5949,21 @@ nm_device_set_unmanaged (NMDevice *device, } } +void +nm_device_set_unmanaged_quitting (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + /* It's OK to block here because we're quitting */ + if (nm_device_is_activating (device) || priv->state == NM_DEVICE_STATE_ACTIVATED) + _set_state_full (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_REMOVED, TRUE); + + nm_device_set_unmanaged (device, + NM_UNMANAGED_INTERNAL, + TRUE, + NM_DEVICE_STATE_REASON_REMOVED); +} + /** * nm_device_set_initial_unmanaged_flag(): * @device: the #NMDevice @@ -7410,60 +5993,6 @@ nm_device_set_initial_unmanaged_flag (NMDevice *device, priv->unmanaged_flags &= ~flag; } -/** - * nm_device_spec_match_list: - * @device: an #NMDevice - * @specs: (element-type utf8): a list of device specs - * - * Checks if @device matches any of the specifications in @specs. The - * currently-supported spec types are: - * - * "mac:00:11:22:33:44:55" - matches a device with the given - * hardware address - * - * "interface-name:foo0" - matches a device with the given - * interface name - * - * "s390-subchannels:00.11.22" - matches a device with the given - * z/VM / s390 subchannels. - * - * "*" - matches any device - * - * Returns: #TRUE if @device matches one of the specs in @specs - */ -gboolean -nm_device_spec_match_list (NMDevice *device, const GSList *specs) -{ - g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - - if (!specs) - return FALSE; - - return NM_DEVICE_GET_CLASS (device)->spec_match_list (device, specs); -} - -static gboolean -spec_match_list (NMDevice *device, const GSList *specs) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - char *hwaddr_str; - gboolean matched = FALSE; - - if (nm_match_spec_string (specs, "*")) - return TRUE; - - if (priv->hw_addr_len) { - hwaddr_str = nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len); - matched = nm_match_spec_hwaddr (specs, hwaddr_str); - g_free (hwaddr_str); - } - - if (!matched) - matched = nm_match_spec_interface_name (specs, nm_device_get_iface (device)); - - return matched; -} - void nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout) { @@ -7492,14 +6021,6 @@ nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr) } } -gboolean -nm_device_get_autoconnect (NMDevice *device) -{ - g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - - return NM_DEVICE_GET_PRIVATE (device)->autoconnect; -} - /** * nm_device_connection_is_available(): * @device: the #NMDevice @@ -7736,6 +6257,683 @@ nm_device_supports_vlans (NMDevice *device) return nm_platform_link_supports_vlans (nm_device_get_ifindex (device)); } +/** + * nm_device_add_pending_action(): + * @device: the #NMDevice to add the pending action to + * @action: a static string that identifies the action + * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending. + * Otherwise, ignore duplicate scheduling of the same action silently. + * + * Adds a pending action to the device. + * + * Returns: %TRUE if the action was added (and not already added before). %FALSE + * if the same action is already scheduled. In the latter case, the action was not scheduled + * a second time. + */ +gboolean +nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + /* Check if the action is already pending. Cannot add duplicate actions */ + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + if (assert_not_yet_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending (expected)", + nm_device_get_iface (device), + count + g_slist_length (iter), + action); + } + return FALSE; + } + count++; + } + + priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action)); + count++; + + nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s'", + nm_device_get_iface (device), + count, + action); + + if (count == 1) + g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); + + return TRUE; +} + +/** + * nm_device_remove_pending_action(): + * @device: the #NMDevice to remove the pending action from + * @action: a static string that identifies the action + * @assert_is_pending: if %TRUE, assert that the @action is pending. + * If %FALSE, don't do anything if the current action is not pending and + * return %FALSE. + * + * Removes a pending action previously added by nm_device_add_pending_action(). + * + * Returns: whether the @action was pending and is now removed. + */ +gboolean +nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GSList *iter; + guint count = 0; + + g_return_val_if_fail (action, FALSE); + + for (iter = priv->pending_actions; iter; iter = iter->next) { + if (!strcmp (action, iter->data)) { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'", + nm_device_get_iface (device), + count + g_slist_length (iter->next), /* length excluding 'iter' */ + action); + g_free (iter->data); + priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter); + if (priv->pending_actions == NULL) + g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); + return TRUE; + } + count++; + } + + if (assert_is_pending) { + nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending", + nm_device_get_iface (device), + count, + action); + g_return_val_if_reached (FALSE); + } else { + nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending (expected)", + nm_device_get_iface (device), + count, + action); + } + return FALSE; +} + +gboolean +nm_device_has_pending_action (NMDevice *device) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + + return !!priv->pending_actions; +} + +/***********************************************************/ + +static void +_cleanup_generic_pre (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + /* Clean up when device was deactivated during call to firewall */ + if (priv->fw_manager) { + NMConnection *connection; + + if (priv->fw_call) { + nm_firewall_manager_cancel_call (priv->fw_manager, priv->fw_call); + priv->fw_call = NULL; + } + + connection = nm_device_get_connection (self); + if (deconfigure && connection) { + nm_firewall_manager_remove_from_zone (priv->fw_manager, + nm_device_get_ip_iface (self), + NULL); + } + } + + ip_check_gw_ping_cleanup (self); + + /* Break the activation chain */ + activation_source_clear (self, TRUE, AF_INET); + activation_source_clear (self, TRUE, AF_INET6); + + /* Clear any queued transitions */ + nm_device_queued_state_clear (self); + nm_device_queued_ip_config_change_clear (self); + + priv->ip4_state = priv->ip6_state = IP_NONE; + + dhcp4_cleanup (self, deconfigure, FALSE); + arp_cleanup (self); + dhcp6_cleanup (self, deconfigure, FALSE); + linklocal6_cleanup (self); + addrconf6_cleanup (self); + dnsmasq_cleanup (self); + aipd_cleanup (self); +} + +static void +_cleanup_generic_post (NMDevice *self, gboolean deconfigure) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE; + + /* Clean up IP configs; this does not actually deconfigure the + * interface; the caller must flush routes and addresses explicitly. + */ + nm_device_set_ip4_config (self, NULL, TRUE, &ignored); + nm_device_set_ip6_config (self, NULL, TRUE, &ignored); + g_clear_object (&priv->dev_ip4_config); + g_clear_object (&priv->ext_ip4_config); + g_clear_object (&priv->vpn4_config); + g_clear_object (&priv->ip4_config); + g_clear_object (&priv->ac_ip6_config); + g_clear_object (&priv->ext_ip6_config); + g_clear_object (&priv->vpn6_config); + g_clear_object (&priv->ip6_config); + + clear_act_request (self); + + /* Clear legacy IPv4 address property */ + if (priv->ip4_address) { + priv->ip4_address = 0; + g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS); + } + + if (deconfigure) { + /* Check if the device was deactivated, and if so, delete_link. + * Don't call delete_link synchronously because we are currently + * handling a state change -- which is not reentrant. */ + delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self)); + } + + /* ip_iface should be cleared after flushing all routes and addreses, since + * those are identified by ip_iface, not by iface (which might be a tty + * or ATM device). + */ + nm_device_set_ip_iface (self, NULL); +} + +/* + * nm_device_cleanup + * + * Remove a device's routing table entries and IP addresses. + * + */ +static void +nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + int ifindex; + + g_return_if_fail (NM_IS_DEVICE (self)); + + if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED) { + nm_log_info (LOGD_DEVICE, "(%s): preparing device", + nm_device_get_iface (self)); + } else { + nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason '%s') [%d]", + nm_device_get_iface (self), reason_to_string (reason), reason); + } + + /* Save whether or not we tried IPv6 for later */ + priv = NM_DEVICE_GET_PRIVATE (self); + + _cleanup_generic_pre (self, TRUE); + + /* Turn off kernel IPv6 */ + nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (self, "accept_ra", "0"); + nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0"); + + /* Call device type-specific deactivation */ + if (NM_DEVICE_GET_CLASS (self)->deactivate) + NM_DEVICE_GET_CLASS (self)->deactivate (self); + + /* master: release slaves */ + nm_device_master_release_slaves (self); + + /* slave: mark no longer enslaved */ + g_clear_object (&priv->master); + priv->enslaved = FALSE; + g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER); + + /* Take out any entries in the routing table and any IP address the device had. */ + ifindex = nm_device_get_ip_ifindex (self); + if (ifindex > 0) { + nm_platform_route_flush (ifindex); + nm_platform_address_flush (ifindex); + } + + _cleanup_generic_post (self, TRUE); +} + +/***********************************************************/ + +static void +dispatcher_pre_down_done (guint call_id, gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_return_if_fail (call_id == priv->dispatcher_id); + + priv->dispatcher_id = 0; + nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); +} + +static void +dispatcher_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher_id) { + nm_dispatcher_call_cancel (priv->dispatcher_id); + priv->dispatcher_id = 0; + } +} + +static gboolean +ip_config_valid (NMDeviceState state) +{ + return (state == NM_DEVICE_STATE_UNMANAGED) || + (state >= NM_DEVICE_STATE_IP_CHECK && + state <= NM_DEVICE_STATE_DEACTIVATING); +} + +static void +notify_ip_properties (NMDevice *device) +{ + g_object_notify (G_OBJECT (device), NM_DEVICE_IP_IFACE); + g_object_notify (G_OBJECT (device), NM_DEVICE_IP4_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_IP6_CONFIG); + g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG); +} + +static void +_set_state_full (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason, + gboolean quitting) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDeviceState old_state; + NMActRequest *req; + gboolean no_firmware = FALSE; + NMConnection *connection; + + /* Track re-entry */ + g_warn_if_fail (priv->in_state_changed == FALSE); + priv->in_state_changed = TRUE; + + g_return_if_fail (NM_IS_DEVICE (device)); + + /* Do nothing if state isn't changing, but as a special case allow + * re-setting UNAVAILABLE if the device is missing firmware so that we + * can retry device initialization. + */ + if ( (priv->state == state) + && !(state == NM_DEVICE_STATE_UNAVAILABLE && priv->firmware_missing)) { + priv->in_state_changed = FALSE; + return; + } + + old_state = priv->state; + priv->state = state; + priv->state_reason = reason; + + nm_log_info (LOGD_DEVICE, "(%s): device state change: %s -> %s (reason '%s') [%d %d %d]", + nm_device_get_iface (device), + state_to_string (old_state), + state_to_string (state), + reason_to_string (reason), + old_state, + state, + reason); + + /* Clear any queued transitions */ + nm_device_queued_state_clear (device); + + dispatcher_cleanup (device); + + /* Cache the activation request for the dispatcher */ + req = priv->act_request ? g_object_ref (priv->act_request) : NULL; + + if (state <= NM_DEVICE_STATE_UNAVAILABLE) { + _clear_available_connections (device, TRUE); + g_clear_object (&priv->queued_act_request); + } + + /* Update the available connections list when a device first becomes available */ + if ( state >= NM_DEVICE_STATE_DISCONNECTED + && old_state < NM_DEVICE_STATE_DISCONNECTED) + nm_device_recheck_available_connections (device); + + /* Handle the new state here; but anything that could trigger + * another state change should be done below. + */ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + nm_device_set_firmware_missing (device, FALSE); + if (old_state > NM_DEVICE_STATE_UNMANAGED) { + /* Clean up if the device is now unmanaged but was activated */ + if (nm_device_get_act_request (device)) + nm_device_cleanup (device, reason); + nm_device_take_down (device, TRUE); + restore_ip6_properties (device); + } + break; + case NM_DEVICE_STATE_UNAVAILABLE: + if (old_state == NM_DEVICE_STATE_UNMANAGED) { + save_ip6_properties (device); + if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) { + nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1"); + nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0"); + nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0"); + nm_device_ipv6_sysctl_set (device, "use_tempaddr", "0"); + } + } + + if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) { + if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) + nm_log_warn (LOGD_HW, "(%s): firmware may be missing.", nm_device_get_iface (device)); + nm_device_set_firmware_missing (device, no_firmware ? TRUE : FALSE); + } + /* Ensure the device gets deactivated in response to stuff like + * carrier changes or rfkill. But don't deactivate devices that are + * about to assume a connection since that defeats the purpose of + * assuming the device's existing connection. + * + * Note that we "deactivate" the device even when coming from + * UNMANAGED, to ensure that it's in a clean state. + */ + if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) + nm_device_cleanup (device, reason); + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (old_state > NM_DEVICE_STATE_UNAVAILABLE) + nm_device_cleanup (device, reason); + break; + default: + break; + } + + /* Reset autoconnect flag when the device is activating or connected. */ + if ( state >= NM_DEVICE_STATE_PREPARE + && state <= NM_DEVICE_STATE_ACTIVATED) + priv->autoconnect = TRUE; + + g_object_notify (G_OBJECT (device), NM_DEVICE_STATE); + g_object_notify (G_OBJECT (device), NM_DEVICE_STATE_REASON); + g_signal_emit_by_name (device, "state-changed", state, old_state, reason); + + /* Post-process the event after internal notification */ + + switch (state) { + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device can activate now (ie, it's got a carrier, the supplicant + * is active, or whatever) schedule a delayed transition to DISCONNECTED + * to get things rolling. The device can't transition immediately because + * we can't change states again from the state handler for a variety of + * reasons. + */ + if (nm_device_is_available (device)) { + nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED", + nm_device_get_iface (device)); + nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + } else { + if (old_state == NM_DEVICE_STATE_UNMANAGED) { + nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED", + nm_device_get_iface (device)); + } else if ( old_state > NM_DEVICE_STATE_UNAVAILABLE + && nm_device_get_default_unmanaged (device)) + nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); + } + break; + case NM_DEVICE_STATE_DEACTIVATING: + if (quitting) { + nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device); + } else { + if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN, + nm_act_request_get_connection (req), + device, + dispatcher_pre_down_done, + device, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + dispatcher_pre_down_done (0, device); + } + } + break; + case NM_DEVICE_STATE_DISCONNECTED: + if (priv->queued_act_request) { + NMActRequest *queued_req; + + queued_req = priv->queued_act_request; + priv->queued_act_request = NULL; + _device_activate (device, queued_req); + g_object_unref (queued_req); + } else if ( old_state > NM_DEVICE_STATE_DISCONNECTED + && nm_device_get_default_unmanaged (device)) + nm_device_queue_state (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_NONE); + break; + case NM_DEVICE_STATE_ACTIVATED: + nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.", + nm_device_get_iface (device)); + nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), device, NULL, NULL, NULL); + break; + case NM_DEVICE_STATE_FAILED: + connection = nm_device_get_connection (device); + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s) failed for connection '%s'", + nm_device_get_iface (device), + connection ? nm_connection_get_id (connection) : "<unknown>"); + + /* Notify any slaves of the unexpected failure */ + nm_device_master_release_slaves (device); + + /* If the connection doesn't yet have a timestamp, set it to zero so that + * we can distinguish between connections we've tried to activate and have + * failed (zero timestamp), connections that succeeded (non-zero timestamp), + * and those we haven't tried yet (no timestamp). + */ + if (connection && !nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), NULL)) { + nm_settings_connection_update_timestamp (NM_SETTINGS_CONNECTION (connection), + (guint64) 0, + TRUE); + } + + /* Schedule the transition to DISCONNECTED. The device can't transition + * immediately because we can't change states again from the state + * handler for a variety of reasons. + */ + nm_device_queue_state (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); + break; + case NM_DEVICE_STATE_IP_CHECK: + nm_device_start_ip_check (device); + + /* IP-related properties are only valid when the device has IP configuration; + * now that it does, ensure their change notifications are emitted. + */ + notify_ip_properties (device); + break; + case NM_DEVICE_STATE_SECONDARIES: + ip_check_gw_ping_cleanup (device); + nm_log_dbg (LOGD_DEVICE, "(%s): device entered SECONDARIES state", + nm_device_get_iface (device)); + break; + default: + break; + } + + if (state > NM_DEVICE_STATE_DISCONNECTED) + delete_on_deactivate_unschedule (device); + + if ( (old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING) + && (state != NM_DEVICE_STATE_DEACTIVATING)) { + if (quitting) + nm_dispatcher_call_sync (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device); + else + nm_dispatcher_call (DISPATCHER_ACTION_DOWN, nm_act_request_get_connection (req), device, NULL, NULL, NULL); + } + + /* IP-related properties are only valid when the device has IP configuration. + * If it no longer does, ensure their change notifications are emitted. + */ + if (ip_config_valid (old_state) && !ip_config_valid (state)) + notify_ip_properties (device); + + /* Dispose of the cached activation request */ + if (req) + g_object_unref (req); + + priv->in_state_changed = FALSE; +} + +void +nm_device_state_changed (NMDevice *device, + NMDeviceState state, + NMDeviceStateReason reason) +{ + _set_state_full (device, state, reason, FALSE); +} + +static gboolean +queued_set_state (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMDeviceState new_state; + NMDeviceStateReason new_reason; + + if (priv->queued_state.id) { + nm_log_dbg (LOGD_DEVICE, "(%s): running queued state change to %s (id %d)", + nm_device_get_iface (self), + state_to_string (priv->queued_state.state), + priv->queued_state.id); + + /* Clear queued state struct before triggering state change, since + * the state change may queue another state. + */ + priv->queued_state.id = 0; + new_state = priv->queued_state.state; + new_reason = priv->queued_state.reason; + nm_device_queued_state_clear (self); + + nm_device_state_changed (self, new_state, new_reason); + nm_device_remove_pending_action (self, queued_state_to_string (new_state), TRUE); + } else { + g_warn_if_fail (priv->queued_state.state == NM_DEVICE_STATE_UNKNOWN); + g_warn_if_fail (priv->queued_state.reason == NM_DEVICE_STATE_REASON_NONE); + } + return FALSE; +} + +void +nm_device_queue_state (NMDevice *self, + NMDeviceState state, + NMDeviceStateReason reason) +{ + NMDevicePrivate *priv; + + g_return_if_fail (NM_IS_DEVICE (self)); + + priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->queued_state.id && priv->queued_state.state == state) + return; + + /* Add pending action for the new state before clearing the queued states, so + * that we don't accidently pop all pending states and reach 'startup complete' */ + nm_device_add_pending_action (self, queued_state_to_string (state), TRUE); + + /* We should only ever have one delayed state transition at a time */ + if (priv->queued_state.id) { + nm_log_warn (LOGD_DEVICE, "(%s): overwriting previously queued state change to %s (%s)", + nm_device_get_iface (self), + state_to_string (priv->queued_state.state), + reason_to_string (priv->queued_state.reason)); + nm_device_queued_state_clear (self); + } + + priv->queued_state.state = state; + priv->queued_state.reason = reason; + priv->queued_state.id = g_idle_add (queued_set_state, self); + + nm_log_dbg (LOGD_DEVICE, "(%s): queued state change to %s due to %s (id %d)", + nm_device_get_iface (self), state_to_string (state), reason_to_string (reason), + priv->queued_state.id); +} + +NMDeviceState +nm_device_queued_state_peek (NMDevice *self) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN); + + priv = NM_DEVICE_GET_PRIVATE (self); + + return priv->queued_state.id ? priv->queued_state.state : NM_DEVICE_STATE_UNKNOWN; +} + +void +nm_device_queued_state_clear (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->queued_state.id) { + nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)", + nm_device_get_iface (self), priv->queued_state.id); + g_source_remove (priv->queued_state.id); + nm_device_remove_pending_action (self, queued_state_to_string (priv->queued_state.state), TRUE); + } + memset (&priv->queued_state, 0, sizeof (priv->queued_state)); +} + +NMDeviceState +nm_device_get_state (NMDevice *device) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN); + + return NM_DEVICE_GET_PRIVATE (device)->state; +} + +/***********************************************************/ +/* NMConfigDevice interface related stuff */ + +static guint +nm_device_get_hw_address_length (NMDevice *dev, gboolean *out_permanent) +{ + return NM_DEVICE_GET_CLASS (dev)->get_hw_address_length (dev, out_permanent); +} + +const guint8 * +nm_device_get_hw_address (NMDevice *dev, guint *out_len) +{ + NMDevicePrivate *priv; + + g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); + priv = NM_DEVICE_GET_PRIVATE (dev); + + if (out_len) + *out_len = priv->hw_addr_len; + + if (priv->hw_addr_len == 0) + return NULL; + else + return priv->hw_addr; +} + gboolean nm_device_update_hw_address (NMDevice *dev) { @@ -7844,137 +7042,912 @@ nm_device_set_hw_addr (NMDevice *device, const guint8 *addr, } /** - * nm_device_add_pending_action(): - * @device: the #NMDevice to add the pending action to - * @action: a static string that identifies the action - * @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending. - * Otherwise, ignore duplicate scheduling of the same action silently. + * nm_device_spec_match_list: + * @device: an #NMDevice + * @specs: (element-type utf8): a list of device specs * - * Adds a pending action to the device. + * Checks if @device matches any of the specifications in @specs. The + * currently-supported spec types are: * - * Returns: %TRUE if the action was added (and not already added before). %FALSE - * if the same action is already scheduled. In the latter case, the action was not scheduled - * a second time. + * "mac:00:11:22:33:44:55" - matches a device with the given + * hardware address + * + * "interface-name:foo0" - matches a device with the given + * interface name + * + * "s390-subchannels:00.11.22" - matches a device with the given + * z/VM / s390 subchannels. + * + * "*" - matches any device + * + * Returns: #TRUE if @device matches one of the specs in @specs */ gboolean -nm_device_add_pending_action (NMDevice *device, const char *action, gboolean assert_not_yet_pending) +nm_device_spec_match_list (NMDevice *device, const GSList *specs) +{ + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); + + if (!specs) + return FALSE; + + return NM_DEVICE_GET_CLASS (device)->spec_match_list (device, specs); +} + +static gboolean +spec_match_list (NMDevice *device, const GSList *specs) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - GSList *iter; - guint count = 0; + char *hwaddr_str; + gboolean matched = FALSE; - g_return_val_if_fail (action, FALSE); + if (nm_match_spec_string (specs, "*")) + return TRUE; - /* Check if the action is already pending. Cannot add duplicate actions */ - for (iter = priv->pending_actions; iter; iter = iter->next) { - if (!strcmp (action, iter->data)) { - if (assert_not_yet_pending) { - nm_log_warn (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending", - nm_device_get_iface (device), - count + g_slist_length (iter), - action); - g_return_val_if_reached (FALSE); - } else { - nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s' already pending (expected)", - nm_device_get_iface (device), - count + g_slist_length (iter), - action); - } - return FALSE; - } - count++; + if (priv->hw_addr_len) { + hwaddr_str = nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len); + matched = nm_match_spec_hwaddr (specs, hwaddr_str); + g_free (hwaddr_str); } - priv->pending_actions = g_slist_append (priv->pending_actions, g_strdup (action)); - count++; + if (!matched) + matched = nm_match_spec_interface_name (specs, nm_device_get_iface (device)); - nm_log_dbg (LOGD_DEVICE, "(%s): add_pending_action (%d): '%s'", - nm_device_get_iface (device), - count, - action); + return matched; +} - if (count == 1) - g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); +static guint +get_hw_address_length (NMDevice *dev, gboolean *out_permanent) +{ + size_t len; - return TRUE; + if (nm_platform_link_get_address (nm_device_get_ip_ifindex (dev), &len)) + return len; + else + return 0; } -/** - * nm_device_remove_pending_action(): - * @device: the #NMDevice to remove the pending action from - * @action: a static string that identifies the action - * @assert_is_pending: if %TRUE, assert that the @action is pending. - * If %FALSE, don't do anything if the current action is not pending and - * return %FALSE. - * - * Removes a pending action previously added by nm_device_add_pending_action(). - * - * Returns: whether the @action was pending and is now removed. +/***********************************************************/ + +#define DEFAULT_AUTOCONNECT TRUE + +static void +nm_device_init (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->type = NM_DEVICE_TYPE_UNKNOWN; + priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED; + priv->state = NM_DEVICE_STATE_UNMANAGED; + priv->state_reason = NM_DEVICE_STATE_REASON_NONE; + priv->dhcp_timeout = 0; + priv->rfkill_type = RFKILL_TYPE_UNKNOWN; + priv->autoconnect = DEFAULT_AUTOCONNECT; + priv->unmanaged_flags = NM_UNMANAGED_INTERNAL; + priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); + priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); +} + +static void +nm_device_config_device_interface_init (NMConfigDeviceInterface *iface) +{ + iface->spec_match_list = (gboolean (*) (NMConfigDevice *, const GSList *)) nm_device_spec_match_list; + iface->get_hw_address = (const guint8 * (*) (NMConfigDevice *, guint *)) nm_device_get_hw_address; +} + +/* + * Get driver info from SIOCETHTOOL ioctl() for 'iface' + * Returns driver and firmware versions to 'driver_version and' 'firmware_version' */ -gboolean -nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending) +static gboolean +device_get_driver_info (const char *iface, char **driver_version, char **firmware_version) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - GSList *iter; - guint count = 0; + struct ethtool_drvinfo drvinfo; + struct ifreq req; + int fd; - g_return_val_if_fail (action, FALSE); + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_warn (LOGD_HW, "couldn't open control socket."); + return FALSE; + } - for (iter = priv->pending_actions; iter; iter = iter->next) { - if (!strcmp (action, iter->data)) { - nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s'", - nm_device_get_iface (device), - count + g_slist_length (iter->next), /* length excluding 'iter' */ - action); - g_free (iter->data); - priv->pending_actions = g_slist_delete_link (priv->pending_actions, iter); - if (priv->pending_actions == NULL) - g_object_notify (G_OBJECT (device), NM_DEVICE_HAS_PENDING_ACTION); - return TRUE; - } - count++; + /* Get driver and firmware version info */ + memset (&drvinfo, 0, sizeof (drvinfo)); + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, iface, IFNAMSIZ); + drvinfo.cmd = ETHTOOL_GDRVINFO; + req.ifr_data = &drvinfo; + + errno = 0; + if (ioctl (fd, SIOCETHTOOL, &req) < 0) { + nm_log_dbg (LOGD_HW, "SIOCETHTOOL ioctl() failed: cmd=ETHTOOL_GDRVINFO, iface=%s, errno=%d", + iface, errno); + close (fd); + return FALSE; } + if (driver_version) + *driver_version = g_strdup (drvinfo.version); + if (firmware_version) + *firmware_version = g_strdup (drvinfo.fw_version); - if (assert_is_pending) { - nm_log_warn (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending", - nm_device_get_iface (device), - count, - action); - g_return_val_if_reached (FALSE); + close (fd); + return TRUE; +} + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + NMDevice *dev; + NMDevicePrivate *priv; + NMPlatform *platform; + static guint32 id = 0; + + object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type, + n_construct_params, + construct_params); + if (!object) + return NULL; + + dev = NM_DEVICE (object); + priv = NM_DEVICE_GET_PRIVATE (dev); + + if (!priv->iface) { + nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring"); + goto error; + } + + if (!priv->udi) { + /* Use a placeholder UDI until we get a real one */ + priv->udi = g_strdup_printf ("/virtual/device/placeholder/%d", id++); + } + + if (NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities) + priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev); + + priv->fw_manager = nm_firewall_manager_get (); + + device_get_driver_info (priv->iface, &priv->driver_version, &priv->firmware_version); + + /* Watch for external IP config changes */ + platform = nm_platform_get (); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), dev); + g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), dev); + + return object; + +error: + g_object_unref (dev); + return NULL; +} + +static void +constructed (GObject *object) +{ + NMDevice *dev = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + + nm_device_update_hw_address (dev); + + if (NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_permanent_hw_address (dev); + + if (NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_initial_hw_address (dev); + + /* Have to call update_initial_hw_address() before calling get_ignore_carrier() */ + if (device_has_capability (dev, NM_DEVICE_CAP_CARRIER_DETECT)) { + priv->ignore_carrier = nm_config_get_ignore_carrier (nm_config_get (), NM_CONFIG_DEVICE (dev)); + + check_carrier (dev); + nm_log_info (LOGD_HW, + "(%s): carrier is %s%s", + nm_device_get_iface (NM_DEVICE (dev)), + priv->carrier ? "ON" : "OFF", + priv->ignore_carrier ? " (but ignored)" : ""); } else { - nm_log_dbg (LOGD_DEVICE, "(%s): remove_pending_action (%d): '%s' not pending (expected)", - nm_device_get_iface (device), - count, - action); + /* Fake online link when carrier detection is not available. */ + priv->carrier = TRUE; } - return FALSE; + + if (priv->ifindex > 0) { + priv->is_software = nm_platform_link_is_software (priv->ifindex); + priv->physical_port_id = nm_platform_link_get_physical_port_id (priv->ifindex); + } + + if (priv->ifindex > 0) + priv->mtu = nm_platform_link_get_mtu (priv->ifindex); + + priv->con_provider = nm_connection_provider_get (); + g_assert (priv->con_provider); + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_ADDED, + G_CALLBACK (cp_connection_added), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK (cp_connection_removed), + dev); + + g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK (cp_connection_updated), + dev); + + G_OBJECT_CLASS (nm_device_parent_class)->constructed (object); } -gboolean -nm_device_has_pending_action (NMDevice *device) +static void +dispose (GObject *object) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMPlatform *platform; - return !!priv->pending_actions; + dispatcher_cleanup (self); + + _cleanup_generic_pre (self, FALSE); + + g_warn_if_fail (priv->slaves == NULL); + g_assert (priv->master_ready_id == 0); + + _cleanup_generic_post (self, FALSE); + + g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref); + + if (priv->recheck_assume_id) { + g_source_remove (priv->recheck_assume_id); + priv->recheck_assume_id = 0; + } + + link_disconnect_action_cancel (self); + + if (priv->con_provider) { + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_added, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_removed, self); + g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_updated, self); + priv->con_provider = NULL; + } + + g_hash_table_unref (priv->available_connections); + priv->available_connections = NULL; + + if (priv->carrier_wait_id) { + g_source_remove (priv->carrier_wait_id); + priv->carrier_wait_id = 0; + } + + g_clear_object (&priv->queued_act_request); + + platform = nm_platform_get (); + g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ip_changed), self); + g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self); + + g_clear_object (&priv->fw_manager); + + G_OBJECT_CLASS (nm_device_parent_class)->dispose (object); } -const char * -nm_device_get_physical_port_id (NMDevice *device) +static void +finalize (GObject *object) { - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + g_slist_free_full (priv->pending_actions, g_free); + g_clear_pointer (&priv->physical_port_id, g_free); + g_free (priv->udi); + g_free (priv->path); + g_free (priv->iface); + g_free (priv->ip_iface); + g_free (priv->driver); + g_free (priv->driver_version); + g_free (priv->firmware_version); + g_free (priv->type_desc); + if (priv->dhcp_anycast_address) + g_byte_array_free (priv->dhcp_anycast_address, TRUE); - return priv->physical_port_id; + G_OBJECT_CLASS (nm_device_parent_class)->finalize (object); } -/** - * nm_device_get_mtu: - * @device: the #NMDevice - * - * Returns: MTU of the #NMDevice - */ -guint32 -nm_device_get_mtu (NMDevice *device) +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object); + NMPlatformLink *platform_device; + const char *hw_addr; + + switch (prop_id) { + case PROP_PLATFORM_DEVICE: + platform_device = g_value_get_pointer (value); + if (platform_device) { + g_free (priv->udi); + priv->udi = g_strdup (platform_device->udi); + g_free (priv->iface); + priv->iface = g_strdup (platform_device->name); + priv->ifindex = platform_device->ifindex; + g_free (priv->driver); + priv->driver = g_strdup (platform_device->driver); + } + break; + case PROP_UDI: + if (g_value_get_string (value)) { + g_free (priv->udi); + priv->udi = g_value_dup_string (value); + } + break; + case PROP_IFACE: + if (g_value_get_string (value)) { + g_free (priv->iface); + priv->ifindex = 0; + priv->iface = g_value_dup_string (value); + + /* Only look up the ifindex if it appears to be an actual kernel + * interface name. eg Bluetooth devices won't have one until we know + * the IP interface. + */ + if (priv->iface && !strchr (priv->iface, ':')) { + priv->ifindex = nm_platform_link_get_ifindex (priv->iface); + if (priv->ifindex <= 0) + nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface); + } + } + break; + case PROP_DRIVER: + if (g_value_get_string (value)) { + g_free (priv->driver); + priv->driver = g_value_dup_string (value); + } + break; + case PROP_DRIVER_VERSION: + g_free (priv->driver_version); + priv->driver_version = g_strdup (g_value_get_string (value)); + break; + case PROP_FIRMWARE_VERSION: + g_free (priv->firmware_version); + priv->firmware_version = g_strdup (g_value_get_string (value)); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint (value); + break; + case PROP_IP4_ADDRESS: + priv->ip4_address = g_value_get_uint (value); + break; + case PROP_AUTOCONNECT: + priv->autoconnect = g_value_get_boolean (value); + break; + case PROP_FIRMWARE_MISSING: + priv->firmware_missing = g_value_get_boolean (value); + break; + case PROP_DEVICE_TYPE: + g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN); + priv->type = g_value_get_uint (value); + break; + case PROP_TYPE_DESC: + g_free (priv->type_desc); + priv->type_desc = g_value_dup_string (value); + break; + case PROP_RFKILL_TYPE: + priv->rfkill_type = g_value_get_uint (value); + break; + case PROP_IS_MASTER: + priv->is_master = g_value_get_boolean (value); + break; + case PROP_HW_ADDRESS: + priv->hw_addr_len = nm_device_get_hw_address_length (NM_DEVICE (object), NULL); + + hw_addr = g_value_get_string (value); + if (!hw_addr) + break; + if (priv->hw_addr_len == 0) { + g_warn_if_fail (*hw_addr == '\0'); + break; + } + + if (!nm_utils_hwaddr_aton_len (hw_addr, priv->hw_addr, priv->hw_addr_len)) { + g_warning ("Could not parse hw-address '%s'", hw_addr); + memset (priv->hw_addr, 0, sizeof (priv->hw_addr)); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +#define DBUS_TYPE_STATE_REASON_STRUCT (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)) + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDevice *self = NM_DEVICE (object); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + const char *ac_path = NULL; + GPtrArray *array; + GHashTableIter iter; + NMConnection *connection; + + switch (prop_id) { + case PROP_UDI: + g_value_set_string (value, priv->udi); + break; + case PROP_IFACE: + g_value_set_string (value, priv->iface); + break; + case PROP_IP_IFACE: + if (ip_config_valid (priv->state)) + g_value_set_string (value, nm_device_get_ip_iface (self)); + else + g_value_set_string (value, NULL); + break; + case PROP_IFINDEX: + g_value_set_int (value, priv->ifindex); + break; + case PROP_DRIVER: + g_value_set_string (value, priv->driver); + break; + case PROP_DRIVER_VERSION: + g_value_set_string (value, priv->driver_version); + break; + case PROP_FIRMWARE_VERSION: + g_value_set_string (value, priv->firmware_version); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK)); + break; + case PROP_IP4_ADDRESS: + g_value_set_uint (value, priv->ip4_address); + break; + case PROP_CARRIER: + g_value_set_boolean (value, priv->carrier); + break; + case PROP_MTU: + g_value_set_uint (value, priv->mtu); + break; + case PROP_IP4_CONFIG: + if (ip_config_valid (priv->state) && priv->ip4_config) + g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_DHCP4_CONFIG: + if (ip_config_valid (priv->state) && priv->dhcp4_client) + g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_IP6_CONFIG: + if (ip_config_valid (priv->state) && priv->ip6_config) + g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_DHCP6_CONFIG: + if (ip_config_valid (priv->state) && priv->dhcp6_client) + g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_STATE: + g_value_set_uint (value, priv->state); + break; + case PROP_STATE_REASON: + g_value_take_boxed (value, dbus_g_type_specialized_construct (DBUS_TYPE_STATE_REASON_STRUCT)); + dbus_g_type_struct_set (value, 0, priv->state, 1, priv->state_reason, G_MAXUINT); + break; + case PROP_ACTIVE_CONNECTION: + if (priv->act_request) + ac_path = nm_active_connection_get_path (NM_ACTIVE_CONNECTION (priv->act_request)); + g_value_set_boxed (value, ac_path ? ac_path : "/"); + break; + case PROP_DEVICE_TYPE: + g_value_set_uint (value, priv->type); + break; + case PROP_MANAGED: + g_value_set_boolean (value, nm_device_get_managed (self)); + break; + case PROP_AUTOCONNECT: + g_value_set_boolean (value, priv->autoconnect); + break; + case PROP_FIRMWARE_MISSING: + g_value_set_boolean (value, priv->firmware_missing); + break; + case PROP_TYPE_DESC: + g_value_set_string (value, priv->type_desc); + break; + case PROP_RFKILL_TYPE: + g_value_set_uint (value, priv->rfkill_type); + break; + case PROP_AVAILABLE_CONNECTIONS: + array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections)); + g_hash_table_iter_init (&iter, priv->available_connections); + while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) + g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection))); + g_value_take_boxed (value, array); + break; + case PROP_PHYSICAL_PORT_ID: + g_value_set_string (value, priv->physical_port_id); + break; + case PROP_IS_MASTER: + g_value_set_boolean (value, priv->is_master); + break; + case PROP_MASTER: + g_value_set_object (value, priv->master); + break; + case PROP_HW_ADDRESS: + if (priv->hw_addr_len) + g_value_take_string (value, nm_utils_hwaddr_ntoa_len (priv->hw_addr, priv->hw_addr_len)); + else + g_value_set_string (value, NULL); + break; + case PROP_HAS_PENDING_ACTION: + g_value_set_boolean (value, nm_device_has_pending_action (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +nm_device_class_init (NMDeviceClass *klass) { - return NM_DEVICE_GET_PRIVATE (device)->mtu; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDevicePrivate)); + + /* Virtual methods */ + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->constructor = constructor; + object_class->constructed = constructed; + + klass->link_changed = link_changed; + + klass->is_available = is_available; + klass->act_stage1_prepare = act_stage1_prepare; + klass->act_stage2_config = act_stage2_config; + klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start; + klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start; + klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout; + klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout; + klass->have_any_ready_slaves = have_any_ready_slaves; + + klass->spec_match_list = spec_match_list; + klass->can_auto_connect = can_auto_connect; + klass->check_connection_compatible = check_connection_compatible; + klass->check_connection_available = check_connection_available; + klass->is_up = is_up; + klass->bring_up = bring_up; + klass->take_down = take_down; + klass->carrier_changed = carrier_changed; + klass->get_hw_address_length = get_hw_address_length; + + /* Properties */ + g_object_class_install_property + (object_class, PROP_PLATFORM_DEVICE, + g_param_spec_pointer (NM_DEVICE_PLATFORM_DEVICE, + "Platform Device", + "NMPlatform device object", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_UDI, + g_param_spec_string (NM_DEVICE_UDI, + "UDI", + "Unique Device Identifier", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (object_class, PROP_IFACE, + g_param_spec_string (NM_DEVICE_IFACE, + "Interface", + "Interface", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_IP_IFACE, + g_param_spec_string (NM_DEVICE_IP_IFACE, + "IP Interface", + "IP Interface", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_DRIVER, + g_param_spec_string (NM_DEVICE_DRIVER, + "Driver", + "Driver", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_DRIVER_VERSION, + g_param_spec_string (NM_DEVICE_DRIVER_VERSION, + "Driver Version", + "Driver Version", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_FIRMWARE_VERSION, + g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, + "Firmware Version", + "Firmware Version", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_CAPABILITIES, + "Capabilities", + "Capabilities", + 0, G_MAXUINT32, NM_DEVICE_CAP_NONE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_CARRIER, + g_param_spec_boolean (NM_DEVICE_CARRIER, + "Carrier", + "Carrier", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_MTU, + g_param_spec_uint (NM_DEVICE_MTU, + "MTU", + "MTU", + 0, G_MAXUINT32, 1500, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_IP4_ADDRESS, + g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, + "IP4 address", + "IP4 address", + 0, G_MAXUINT32, 0, /* FIXME */ + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_IP4_CONFIG, + g_param_spec_boxed (NM_DEVICE_IP4_CONFIG, + "IP4 Config", + "IP4 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_DHCP4_CONFIG, + g_param_spec_boxed (NM_DEVICE_DHCP4_CONFIG, + "DHCP4 Config", + "DHCP4 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_IP6_CONFIG, + g_param_spec_boxed (NM_DEVICE_IP6_CONFIG, + "IP6 Config", + "IP6 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_DHCP6_CONFIG, + g_param_spec_boxed (NM_DEVICE_DHCP6_CONFIG, + "DHCP6 Config", + "DHCP6 Config", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_STATE, + g_param_spec_uint (NM_DEVICE_STATE, + "State", + "State", + 0, G_MAXUINT32, NM_DEVICE_STATE_UNKNOWN, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_STATE_REASON, + g_param_spec_boxed (NM_DEVICE_STATE_REASON, + "StateReason", + "StateReason", + DBUS_TYPE_STATE_REASON_STRUCT, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_ACTIVE_CONNECTION, + g_param_spec_boxed (NM_DEVICE_ACTIVE_CONNECTION, + "ActiveConnection", + "ActiveConnection", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_DEVICE_TYPE, + g_param_spec_uint (NM_DEVICE_DEVICE_TYPE, + "DeviceType", + "DeviceType", + 0, G_MAXUINT32, NM_DEVICE_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MANAGED, + g_param_spec_boolean (NM_DEVICE_MANAGED, + "Managed", + "Managed", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_AUTOCONNECT, + g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, + "Autoconnect", + "Autoconnect", + DEFAULT_AUTOCONNECT, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_FIRMWARE_MISSING, + g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, + "FirmwareMissing", + "Firmware missing", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_TYPE_DESC, + g_param_spec_string (NM_DEVICE_TYPE_DESC, + "Type Description", + "Device type description", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_RFKILL_TYPE, + g_param_spec_uint (NM_DEVICE_RFKILL_TYPE, + "Rfkill Type", + "Type of rfkill switch (if any) supported by this device", + RFKILL_TYPE_WLAN, + RFKILL_TYPE_MAX, + RFKILL_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_IFINDEX, + g_param_spec_int (NM_DEVICE_IFINDEX, + "Ifindex", + "Ifindex", + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_AVAILABLE_CONNECTIONS, + g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, + "AvailableConnections", + "AvailableConnections", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_PHYSICAL_PORT_ID, + g_param_spec_string (NM_DEVICE_PHYSICAL_PORT_ID, + "PhysicalPortId", + "PhysicalPortId", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_IS_MASTER, + g_param_spec_boolean (NM_DEVICE_IS_MASTER, + "IsMaster", + "IsMaster", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_MASTER, + g_param_spec_object (NM_DEVICE_MASTER, + "Master", + "Master", + NM_TYPE_DEVICE, + G_PARAM_READABLE)); + + g_object_class_install_property + (object_class, PROP_HW_ADDRESS, + g_param_spec_string (NM_DEVICE_HW_ADDRESS, + "Hardware Address", + "Hardware address", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_HAS_PENDING_ACTION, + g_param_spec_boolean (NM_DEVICE_HAS_PENDING_ACTION, + "Has pending action", + "Has pending action", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + signals[STATE_CHANGED] = + g_signal_new ("state-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMDeviceClass, state_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 3, + G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + + signals[AUTOCONNECT_ALLOWED] = + g_signal_new ("autoconnect-allowed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + 0, + autoconnect_allowed_accumulator, NULL, NULL, + G_TYPE_BOOLEAN, 0); + + signals[AUTH_REQUEST] = + g_signal_new (NM_DEVICE_AUTH_REQUEST, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + /* dbus-glib context, connection, permission, allow_interaction, callback, user_data */ + G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER); + + signals[IP4_CONFIG_CHANGED] = + g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + signals[IP6_CONFIG_CHANGED] = + g_signal_new (NM_DEVICE_IP6_CONFIG_CHANGED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + signals[REMOVED] = + g_signal_new (NM_DEVICE_REMOVED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_AUTO_ACTIVATE] = + g_signal_new (NM_DEVICE_RECHECK_AUTO_ACTIVATE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[RECHECK_ASSUME] = + g_signal_new (NM_DEVICE_RECHECK_ASSUME, + 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 (klass), + &dbus_glib_nm_device_object_info); + + dbus_g_error_domain_register (NM_DEVICE_ERROR, NULL, NM_TYPE_DEVICE_ERROR); } diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index 1e2f39a31e..f74486e6fc 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -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); diff --git a/src/main.c b/src/main.c index 4bd2b4324c..0ba65b0b8a 100644 --- a/src/main.c +++ b/src/main.c @@ -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", diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c index 63773862c6..7a0c8de178 100644 --- a/src/nm-dispatcher.c +++ b/src/nm-dispatcher.c @@ -23,7 +23,7 @@ #include <string.h> #include "nm-dispatcher.h" -#include "nm-dispatcher-action.h" +#include "nm-dispatcher-api.h" #include "NetworkManagerUtils.h" #include "nm-utils.h" #include "nm-logging.h" @@ -31,7 +31,10 @@ #include "nm-dbus-glib-types.h" #include "nm-glib-compat.h" -static GSList *requests = NULL; +#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 mintues for all scripts */ + +static gboolean do_dispatch = TRUE; +static GHashTable *requests = NULL; static void dump_object_to_props (GObject *object, GHashTable *hash) @@ -132,17 +135,37 @@ fill_vpn_props (NMIP4Config *ip4_config, } typedef struct { + guint request_id; DispatcherFunc callback; gpointer user_data; + guint idle_id; } DispatchInfo; static void dispatcher_info_free (DispatchInfo *info) { - requests = g_slist_remove (requests, info); + if (info->idle_id) + g_source_remove (info->idle_id); g_free (info); } +static void +_ensure_requests (void) +{ + if (G_UNLIKELY (requests == NULL)) { + requests = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify) dispatcher_info_free); + } +} + +static void +dispatcher_info_cleanup (DispatchInfo *info) +{ + g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id)); +} + static const char * dispatch_result_to_string (DispatchResult result) { @@ -161,115 +184,149 @@ dispatch_result_to_string (DispatchResult result) g_assert_not_reached (); } +static gboolean +validate_element (guint request_id, GValue *val, GType expected_type, guint idx, guint eltnum) +{ + if (G_VALUE_TYPE (val) != expected_type) { + nm_log_dbg (LOGD_DISPATCH, "(%u) result %d element %d invalid type %s", + request_id, idx, eltnum, G_VALUE_TYPE_NAME (val)); + return FALSE; + } + return TRUE; +} + +static void +dispatcher_results_process (guint request_id, GPtrArray *results) +{ + guint i; + + g_return_if_fail (results != NULL); + + for (i = 0; i < results->len; i++) { + GValueArray *item = g_ptr_array_index (results, i); + GValue *tmp; + const char *script, *err; + DispatchResult result; + + if (item->n_values != 3) { + nm_log_dbg (LOGD_DISPATCH, "(%u) unexpected number of items in " + "dispatcher result (got %d, expected 3)", + request_id, item->n_values); + continue; + } + + /* Script */ + tmp = g_value_array_get_nth (item, 0); + if (!validate_element (request_id, tmp, G_TYPE_STRING, i, 0)) + continue; + script = g_value_get_string (tmp); + if (!script || strncmp (script, NMD_SCRIPT_DIR "/", STRLEN (NMD_SCRIPT_DIR "/"))) + continue; + + /* Result */ + tmp = g_value_array_get_nth (item, 1); + if (!validate_element (request_id, tmp, G_TYPE_UINT, i, 1)) + continue; + result = g_value_get_uint (tmp); + + /* Error */ + tmp = g_value_array_get_nth (item, 2); + if (!validate_element (request_id, tmp, G_TYPE_STRING, i, 2)) + continue; + err = g_value_get_string (tmp); + + + if (result == DISPATCH_RESULT_SUCCESS) { + nm_log_dbg (LOGD_DISPATCH, "(%u) %s succeeded", + request_id, + script + STRLEN (NMD_SCRIPT_DIR "/")); + } else { + nm_log_warn (LOGD_DISPATCH, "(%u) %s failed (%s): %s", + request_id, + script + STRLEN (NMD_SCRIPT_DIR "/"), + dispatch_result_to_string (result), + err ? err : ""); + } + } +} + +static void +free_results (GPtrArray *results) +{ + g_return_if_fail (results != NULL); + g_ptr_array_foreach (results, (GFunc) g_value_array_free, NULL); + g_ptr_array_free (results, TRUE); +} + static void dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) { DispatchInfo *info = user_data; GError *error = NULL; GPtrArray *results = NULL; - guint i; if (dbus_g_proxy_end_call (proxy, call, &error, DISPATCHER_TYPE_RESULT_ARRAY, &results, G_TYPE_INVALID)) { - for (i = 0; results && (i < results->len); i++) { - GValueArray *item = g_ptr_array_index (results, i); - GValue *tmp; - const char *script, *err; - DispatchResult result; - - if (item->n_values != 3) { - nm_log_dbg (LOGD_DISPATCH, "Unexpected number of items in " - "dispatcher result (got %d, expectd 3)", - item->n_values); - goto next; - } - - /* Script */ - tmp = g_value_array_get_nth (item, 0); - if (G_VALUE_TYPE (tmp) != G_TYPE_STRING) { - nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 0 invalid type %s", - i, G_VALUE_TYPE_NAME (tmp)); - goto next; - } - script = g_value_get_string (tmp); - - /* Result */ - tmp = g_value_array_get_nth (item, 1); - if (G_VALUE_TYPE (tmp) != G_TYPE_UINT) { - nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 1 invalid type %s", - i, G_VALUE_TYPE_NAME (tmp)); - goto next; - } - result = g_value_get_uint (tmp); - - /* Error */ - tmp = g_value_array_get_nth (item, 2); - if (G_VALUE_TYPE (tmp) != G_TYPE_STRING) { - nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 2 invalid type %s", - i, G_VALUE_TYPE_NAME (tmp)); - goto next; - } - err = g_value_get_string (tmp); - - if (result != DISPATCH_RESULT_SUCCESS) { - nm_log_warn (LOGD_DISPATCH, "Dispatcher script \"%s\" failed with %s: %s", - script, dispatch_result_to_string (result), err); - } - -next: - g_value_array_free (item); - } - g_ptr_array_free (results, TRUE); + dispatcher_results_process (info->request_id, results); + free_results (results); } else { g_assert (error); - nm_log_warn (LOGD_DISPATCH, "Dispatcher failed: (%d) %s", error->code, error->message); + nm_log_warn (LOGD_DISPATCH, "(%u) failed: (%d) %s", + info->request_id, error->code, error->message); } if (info->callback) - info->callback (info, info->user_data); + info->callback (info->request_id, info->user_data); g_clear_error (&error); g_object_unref (proxy); } +static const char *action_table[] = { + [DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME, + [DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP, + [DISPATCHER_ACTION_UP] = NMD_ACTION_UP, + [DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN, + [DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN, + [DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP, + [DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP, + [DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN, + [DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN, + [DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE, + [DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE, +}; + static const char * action_to_string (DispatcherAction action) { - switch (action) { - case DISPATCHER_ACTION_HOSTNAME: - return "hostname"; - case DISPATCHER_ACTION_UP: - return "up"; - case DISPATCHER_ACTION_PRE_DOWN: - return "pre-down"; - case DISPATCHER_ACTION_DOWN: - return "down"; - case DISPATCHER_ACTION_VPN_UP: - return "vpn-up"; - case DISPATCHER_ACTION_VPN_PRE_DOWN: - return "vpn-pre-down"; - case DISPATCHER_ACTION_VPN_DOWN: - return "vpn-down"; - case DISPATCHER_ACTION_DHCP4_CHANGE: - return "dhcp4-change"; - case DISPATCHER_ACTION_DHCP6_CHANGE: - return "dhcp6-change"; - default: - break; - } - g_assert_not_reached (); + g_assert (action >= 0 && action < G_N_ELEMENTS (action_table)); + return action_table[action]; +} + +static gboolean +dispatcher_idle_cb (gpointer user_data) +{ + DispatchInfo *info = user_data; + + info->idle_id = 0; + if (info->callback) + info->callback (info->request_id, info->user_data); + dispatcher_info_cleanup (info); + return G_SOURCE_REMOVE; } -static gconstpointer +static gboolean _dispatcher_call (DispatcherAction action, + gboolean blocking, NMConnection *connection, NMDevice *device, const char *vpn_iface, NMIP4Config *vpn_ip4_config, NMIP6Config *vpn_ip6_config, DispatcherFunc callback, - gpointer user_data) + gpointer user_data, + guint *out_call_id) { DBusGProxy *proxy; DBusGConnection *g_connection; @@ -282,22 +339,49 @@ _dispatcher_call (DispatcherAction action, GHashTable *device_dhcp6_props; GHashTable *vpn_ip4_props; GHashTable *vpn_ip6_props; - DispatchInfo *info; + DispatchInfo *info = NULL; + gboolean success = FALSE; + GError *error = NULL; + static guint request_counter = 0; + guint reqid = ++request_counter; + + /* Wrapping protection */ + if (G_UNLIKELY (!reqid)) + reqid = ++request_counter; + + g_assert (!blocking || (!callback && !user_data)); + + _ensure_requests (); /* All actions except 'hostname' require a device */ if (action == DISPATCHER_ACTION_HOSTNAME) { - nm_log_dbg (LOGD_DISPATCH, "dispatching action '%s'", - action_to_string (action)); + nm_log_dbg (LOGD_DISPATCH, "(%u) dispatching action '%s'", + reqid, action_to_string (action)); } else { - g_return_val_if_fail (NM_IS_DEVICE (device), NULL); + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - nm_log_dbg (LOGD_DISPATCH, "(%s) dispatching action '%s'", - nm_device_get_iface (device), action_to_string (action)); + nm_log_dbg (LOGD_DISPATCH, "(%u) (%s) dispatching action '%s'", + reqid, + vpn_iface ? vpn_iface : nm_device_get_iface (device), + action_to_string (action)); } /* VPN actions require at least an IPv4 config (for now) */ if (action == DISPATCHER_ACTION_VPN_UP) - g_return_val_if_fail (vpn_ip4_config != NULL, NULL); + g_return_val_if_fail (vpn_ip4_config != NULL, FALSE); + + if (do_dispatch == FALSE) { + if (blocking == FALSE && (out_call_id || callback)) { + info = g_malloc0 (sizeof (*info)); + info->request_id = reqid; + info->callback = callback; + info->user_data = user_data; + info->idle_id = g_idle_add (dispatcher_idle_cb, info); + } + nm_log_dbg (LOGD_DISPATCH, "(%u) ignoring request; no scripts in " NMD_SCRIPT_DIR, reqid); + success = TRUE; + goto done; + } g_connection = nm_dbus_manager_get_connection (nm_dbus_manager_get ()); proxy = dbus_g_proxy_new_for_name (g_connection, @@ -305,8 +389,8 @@ _dispatcher_call (DispatcherAction action, NM_DISPATCHER_DBUS_PATH, NM_DISPATCHER_DBUS_IFACE); if (!proxy) { - nm_log_err (LOGD_DISPATCH, "could not get dispatcher proxy!"); - return NULL; + nm_log_err (LOGD_DISPATCH, "(%u) could not get dispatcher proxy!", reqid); + return FALSE; } if (connection) { @@ -341,29 +425,61 @@ _dispatcher_call (DispatcherAction action, fill_vpn_props (vpn_ip4_config, vpn_ip6_config, vpn_ip4_props, vpn_ip6_props); } - info = g_malloc0 (sizeof (*info)); - info->callback = callback; - info->user_data = user_data; - /* Send the action to the dispatcher */ - dbus_g_proxy_begin_call_with_timeout (proxy, "Action", - dispatcher_done_cb, - info, - (GDestroyNotify) dispatcher_info_free, - 30000, - G_TYPE_STRING, action_to_string (action), - DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash, - DBUS_TYPE_G_MAP_OF_VARIANT, connection_props, - DBUS_TYPE_G_MAP_OF_VARIANT, device_props, - DBUS_TYPE_G_MAP_OF_VARIANT, device_ip4_props, - DBUS_TYPE_G_MAP_OF_VARIANT, device_ip6_props, - DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp4_props, - DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp6_props, - G_TYPE_STRING, vpn_iface ? vpn_iface : "", - DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip4_props, - DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip6_props, - G_TYPE_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH), - G_TYPE_INVALID); + if (blocking) { + GPtrArray *results = NULL; + + success = dbus_g_proxy_call_with_timeout (proxy, "Action", + CALL_TIMEOUT, + &error, + G_TYPE_STRING, action_to_string (action), + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash, + DBUS_TYPE_G_MAP_OF_VARIANT, connection_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_ip4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_ip6_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp6_props, + G_TYPE_STRING, vpn_iface ? vpn_iface : "", + DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip6_props, + G_TYPE_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH), + G_TYPE_INVALID, + DISPATCHER_TYPE_RESULT_ARRAY, &results, + G_TYPE_INVALID); + if (success) { + dispatcher_results_process (reqid, results); + free_results (results); + } else { + nm_log_warn (LOGD_DISPATCH, "(%u) failed: (%d) %s", reqid, error->code, error->message); + g_error_free (error); + } + } else { + info = g_malloc0 (sizeof (*info)); + info->request_id = reqid; + info->callback = callback; + info->user_data = user_data; + dbus_g_proxy_begin_call_with_timeout (proxy, "Action", + dispatcher_done_cb, + info, + (GDestroyNotify) dispatcher_info_cleanup, + CALL_TIMEOUT, + G_TYPE_STRING, action_to_string (action), + DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash, + DBUS_TYPE_G_MAP_OF_VARIANT, connection_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_ip4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_ip6_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp6_props, + G_TYPE_STRING, vpn_iface ? vpn_iface : "", + DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip4_props, + DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip6_props, + G_TYPE_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH), + G_TYPE_INVALID); + success = TRUE; + } + g_hash_table_destroy (connection_hash); g_hash_table_destroy (connection_props); g_hash_table_destroy (device_props); @@ -374,45 +490,192 @@ _dispatcher_call (DispatcherAction action, g_hash_table_destroy (vpn_ip4_props); g_hash_table_destroy (vpn_ip6_props); - /* Track the request in case of cancelation */ - requests = g_slist_append (requests, info); +done: + if (success && info) { + /* Track the request in case of cancelation */ + g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info); + if (out_call_id) + *out_call_id = info->request_id; + } else if (out_call_id) + *out_call_id = 0; - return info; + return success; } -gconstpointer +/** + * nm_dispatcher_call: + * @action: the %DispatcherAction + * @connection: the #NMConnection the action applies to + * @device: the #NMDevice the action applies to + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the dispatcher action asynchronously. To ignore + * the result, pass %NULL to @callback. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean nm_dispatcher_call (DispatcherAction action, NMConnection *connection, NMDevice *device, DispatcherFunc callback, - gpointer user_data) + gpointer user_data, + guint *out_call_id) +{ + return _dispatcher_call (action, FALSE, connection, device, NULL, NULL, + NULL, callback, user_data, out_call_id); +} + +/** + * nm_dispatcher_call_sync(): + * @action: the %DispatcherAction + * @connection: the #NMConnection the action applies to + * @device: the #NMDevice the action applies to + * + * This method always invokes the dispatcher action synchronously and it may + * take a long time to return. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_sync (DispatcherAction action, + NMConnection *connection, + NMDevice *device) { - return _dispatcher_call (action, connection, device, NULL, NULL, NULL, callback, user_data); + return _dispatcher_call (action, TRUE, connection, device, NULL, NULL, + NULL, NULL, NULL, NULL); } -gconstpointer +/** + * nm_dispatcher_call_vpn(): + * @action: the %DispatcherAction + * @connection: the #NMConnection the action applies to + * @parent_device: the parent #NMDevice of the VPN connection + * @vpn_iface: the IP interface of the VPN tunnel, if any + * @vpn_ip4_config: the #NMIP4Config of the VPN connection + * @vpn_ip6_config: the #NMIP6Config of the VPN connection + * @callback: a caller-supplied callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the dispatcher action asynchronously. To ignore + * the result, pass %NULL to @callback. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean nm_dispatcher_call_vpn (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) + gpointer user_data, + guint *out_call_id) +{ + return _dispatcher_call (action, FALSE, connection, parent_device, vpn_iface, + vpn_ip4_config, vpn_ip6_config, callback, user_data, out_call_id); +} + +/** + * nm_dispatcher_call_vpn_sync(): + * @action: the %DispatcherAction + * @connection: the #NMConnection the action applies to + * @parent_device: the parent #NMDevice of the VPN connection + * @vpn_iface: the IP interface of the VPN tunnel, if any + * @vpn_ip4_config: the #NMIP4Config of the VPN connection + * @vpn_ip6_config: the #NMIP6Config of the VPN connection + * + * This method always invokes the dispatcher action synchronously and it may + * take a long time to return. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_vpn_sync (DispatcherAction action, + NMConnection *connection, + NMDevice *parent_device, + const char *vpn_iface, + NMIP4Config *vpn_ip4_config, + NMIP6Config *vpn_ip6_config) { - return _dispatcher_call (action, connection, device, vpn_iface, vpn_ip4_config, vpn_ip6_config, callback, user_data); + return _dispatcher_call (action, TRUE, connection, parent_device, vpn_iface, + vpn_ip4_config, vpn_ip6_config, NULL, NULL, NULL); } void -nm_dispatcher_call_cancel (gconstpointer call) +nm_dispatcher_call_cancel (guint call_id) { - /* 'call' is really a DispatchInfo pointer, just opaque to callers. - * Look it up in our requests list, but don't access it directly before - * we've made sure it's a valid request,since it may have long since been - * freed. Canceling just means the callback doesn't get called, so set - * the DispatcherInfo's callback to NULL. + DispatchInfo *info; + + _ensure_requests (); + + /* Canceling just means the callback doesn't get called, so set the + * DispatcherInfo's callback to NULL. */ - if (g_slist_find (requests, call)) - ((DispatchInfo *) call)->callback = NULL; + info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id)); + if (info) + info->callback = NULL; + else + g_return_if_reached (); +} + +typedef struct { + const char *dir; + GFileMonitor *monitor; + gboolean has_scripts; +} Monitor; + +static Monitor monitors[3] = { + { NMD_SCRIPT_DIR, NULL, TRUE }, + { NMD_PRE_UP_DIR, NULL, TRUE }, + { NMD_PRE_DOWN_DIR, NULL, TRUE } +}; + +static void +dispatcher_dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + Monitor *item) +{ + GDir *dir; + guint i; + + /* Default to dispatching on any errors */ + item->has_scripts = TRUE; + + dir = g_dir_open (item->dir, 0, NULL); + if (dir) { + item->has_scripts = !!g_dir_read_name (dir); + g_dir_close (dir); + } + + /* Recheck all dirs for scripts and update global variable */ + do_dispatch = FALSE; + for (i = 0; i < G_N_ELEMENTS (monitors); i++) + do_dispatch |= monitors[i].has_scripts; +} + +void +nm_dispatcher_init (void) +{ + GFile *file; + guint i; + + for (i = 0; i < G_N_ELEMENTS (monitors); i++) { + file = g_file_new_for_path (monitors[i].dir); + monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); + if (monitors[i].monitor) { + g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]); + dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]); + } + g_object_unref (file); + } } diff --git a/src/nm-dispatcher.h b/src/nm-dispatcher.h index 05a6c87514..464f6310cb 100644 --- a/src/nm-dispatcher.h +++ b/src/nm-dispatcher.h @@ -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 */ diff --git a/src/nm-manager.c b/src/nm-manager.c index 47245b648c..6fc74df14a 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -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. */ diff --git a/src/nm-policy.c b/src/nm-policy.c index f307f999c6..b54b94928e 100644 --- a/src/nm-policy.c +++ b/src/nm-policy.c @@ -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 diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index 17664ad1a9..f7476b411a 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -62,17 +62,32 @@ typedef enum { SECRETS_REQ_LAST } SecretsReq; -typedef struct { - gboolean disposed; +/* Internal VPN states, private to NMVPNConnection */ +typedef enum { + STATE_UNKNOWN = 0, + STATE_WAITING, + STATE_PREPARE, + STATE_NEED_AUTH, + STATE_CONNECT, + STATE_IP_CONFIG_GET, + STATE_PRE_UP, + STATE_ACTIVATED, + STATE_DEACTIVATING, + STATE_DISCONNECTED, + STATE_FAILED, +} VpnState; +typedef struct { NMConnection *connection; guint32 secrets_id; SecretsReq secrets_idx; char *username; - NMVPNConnectionState vpn_state; + VpnState vpn_state; + guint dispatcher_id; NMVPNConnectionStateReason failure_reason; + DBusGProxy *proxy; GHashTable *connect_hash; guint connect_timeout; @@ -121,20 +136,65 @@ static void plugin_interactive_secrets_required (DBusGProxy *proxy, const char **secrets, gpointer user_data); +static void _set_vpn_state (NMVPNConnection *connection, + VpnState vpn_state, + NMVPNConnectionStateReason reason, + gboolean quitting); + +/*********************************************************************/ + +static NMVPNConnectionState +_state_to_nm_vpn_state (VpnState state) +{ + switch (state) { + case STATE_WAITING: + case STATE_PREPARE: + return NM_VPN_CONNECTION_STATE_PREPARE; + case STATE_NEED_AUTH: + return NM_VPN_CONNECTION_STATE_NEED_AUTH; + case STATE_CONNECT: + return NM_VPN_CONNECTION_STATE_CONNECT; + case STATE_IP_CONFIG_GET: + case STATE_PRE_UP: + return NM_VPN_CONNECTION_STATE_IP_CONFIG_GET; + case STATE_ACTIVATED: + return NM_VPN_CONNECTION_STATE_ACTIVATED; + case STATE_DEACTIVATING: { + /* Map DEACTIVATING to ACTIVATED to preserve external API behavior, + * since our API has no DEACTIVATING state of its own. Since this can + * take some time, and the VPN isn't actually disconnected until it + * hits the DISCONNECTED state, to clients it should still appear + * connected. + */ + return NM_VPN_CONNECTION_STATE_ACTIVATED; + } + case STATE_DISCONNECTED: + return NM_VPN_CONNECTION_STATE_DISCONNECTED; + case STATE_FAILED: + return NM_VPN_CONNECTION_STATE_FAILED; + default: + return STATE_UNKNOWN; + } +} + static NMActiveConnectionState -ac_state_from_vpn_state (NMVPNConnectionState vpn_state) +_state_to_ac_state (VpnState vpn_state) { /* Set the NMActiveConnection state based on VPN state */ switch (vpn_state) { - case NM_VPN_CONNECTION_STATE_PREPARE: - case NM_VPN_CONNECTION_STATE_NEED_AUTH: - case NM_VPN_CONNECTION_STATE_CONNECT: - case NM_VPN_CONNECTION_STATE_IP_CONFIG_GET: + case STATE_WAITING: + case STATE_PREPARE: + case STATE_NEED_AUTH: + case STATE_CONNECT: + case STATE_IP_CONFIG_GET: + case STATE_PRE_UP: return NM_ACTIVE_CONNECTION_STATE_ACTIVATING; - case NM_VPN_CONNECTION_STATE_ACTIVATED: + case STATE_ACTIVATED: return NM_ACTIVE_CONNECTION_STATE_ACTIVATED; - case NM_VPN_CONNECTION_STATE_FAILED: - case NM_VPN_CONNECTION_STATE_DISCONNECTED: + case STATE_DEACTIVATING: + return NM_ACTIVE_CONNECTION_STATE_DEACTIVATING; + case STATE_DISCONNECTED: + case STATE_FAILED: return NM_ACTIVE_CONNECTION_STATE_DEACTIVATED; default: break; @@ -190,12 +250,45 @@ vpn_cleanup (NMVPNConnection *connection, NMDevice *parent_dev) } static void -nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, - NMVPNConnectionState vpn_state, - NMVPNConnectionStateReason reason) +dispatcher_pre_down_done (guint call_id, gpointer user_data) +{ + NMVPNConnection *self = NM_VPN_CONNECTION (user_data); + NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self); + + priv->dispatcher_id = 0; + _set_vpn_state (self, STATE_DISCONNECTED, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); +} + +static void +dispatcher_pre_up_done (guint call_id, gpointer user_data) +{ + NMVPNConnection *self = NM_VPN_CONNECTION (user_data); + NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self); + + priv->dispatcher_id = 0; + _set_vpn_state (self, STATE_ACTIVATED, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); +} + +static void +dispatcher_cleanup (NMVPNConnection *self) +{ + NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self); + + if (priv->dispatcher_id) { + nm_dispatcher_call_cancel (priv->dispatcher_id); + priv->dispatcher_id = 0; + } +} + +static void +_set_vpn_state (NMVPNConnection *connection, + VpnState vpn_state, + NMVPNConnectionStateReason reason, + gboolean quitting) { NMVPNConnectionPrivate *priv; - NMVPNConnectionState old_vpn_state; + VpnState old_vpn_state; + NMVPNConnectionState new_external_state, old_external_state; NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (connection)); g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); @@ -216,7 +309,7 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, /* Update active connection base class state */ nm_active_connection_set_state (NM_ACTIVE_CONNECTION (connection), - ac_state_from_vpn_state (vpn_state)); + _state_to_ac_state (vpn_state)); /* Clear any in-progress secrets request */ if (priv->secrets_id) { @@ -224,24 +317,47 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, priv->secrets_id = 0; } + dispatcher_cleanup (connection); + /* The connection gets destroyed by the VPN manager when it enters the * disconnected/failed state, but we need to keep it around for a bit * to send out signals and handle the dispatcher. So ref it. */ g_object_ref (connection); - g_signal_emit (connection, signals[VPN_STATE_CHANGED], 0, vpn_state, reason); - g_signal_emit (connection, signals[INTERNAL_STATE_CHANGED], 0, vpn_state, old_vpn_state, reason); - g_object_notify (G_OBJECT (connection), NM_VPN_CONNECTION_VPN_STATE); + old_external_state = _state_to_nm_vpn_state (old_vpn_state); + new_external_state = _state_to_nm_vpn_state (priv->vpn_state); + if (new_external_state != old_external_state) { + g_signal_emit (connection, signals[VPN_STATE_CHANGED], 0, new_external_state, reason); + g_signal_emit (connection, signals[INTERNAL_STATE_CHANGED], 0, + new_external_state, + old_external_state, + reason); + g_object_notify (G_OBJECT (connection), NM_VPN_CONNECTION_VPN_STATE); + } switch (vpn_state) { - case NM_VPN_CONNECTION_STATE_NEED_AUTH: + case STATE_NEED_AUTH: /* Do nothing; not part of 'default' because we don't want to touch * priv->secrets_req as NEED_AUTH is re-entered during interactive * secrets. */ break; - case NM_VPN_CONNECTION_STATE_ACTIVATED: + case STATE_PRE_UP: + if (!nm_dispatcher_call_vpn (DISPATCHER_ACTION_VPN_PRE_UP, + priv->connection, + parent_dev, + priv->ip_iface, + priv->ip4_config, + priv->ip6_config, + dispatcher_pre_up_done, + connection, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + dispatcher_pre_up_done (0, connection); + } + break; + case STATE_ACTIVATED: /* Secrets no longer needed now that we're connected */ nm_connection_clear_secrets (priv->connection); @@ -253,20 +369,55 @@ nm_vpn_connection_set_vpn_state (NMVPNConnection *connection, priv->ip4_config, priv->ip6_config, NULL, + NULL, NULL); break; - case NM_VPN_CONNECTION_STATE_FAILED: - case NM_VPN_CONNECTION_STATE_DISCONNECTED: - if (old_vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED) { + case STATE_DEACTIVATING: + if (quitting) { + nm_dispatcher_call_vpn_sync (DISPATCHER_ACTION_VPN_PRE_DOWN, + priv->connection, + parent_dev, + priv->ip_iface, + priv->ip4_config, + priv->ip6_config); + } else { + if (!nm_dispatcher_call_vpn (DISPATCHER_ACTION_VPN_PRE_DOWN, + priv->connection, + parent_dev, + priv->ip_iface, + priv->ip4_config, + priv->ip6_config, + dispatcher_pre_down_done, + connection, + &priv->dispatcher_id)) { + /* Just proceed on errors */ + dispatcher_pre_down_done (0, connection); + } + } + break; + case STATE_FAILED: + case STATE_DISCONNECTED: + if ( old_vpn_state >= STATE_ACTIVATED + && old_vpn_state <= STATE_DEACTIVATING) { /* Let dispatcher scripts know we're about to go down */ - nm_dispatcher_call_vpn (DISPATCHER_ACTION_VPN_DOWN, - priv->connection, - parent_dev, - priv->ip_iface, - NULL, - NULL, - NULL, - NULL); + if (quitting) { + nm_dispatcher_call_vpn_sync (DISPATCHER_ACTION_VPN_DOWN, + priv->connection, + parent_dev, + priv->ip_iface, + NULL, + NULL); + } else { + nm_dispatcher_call_vpn (DISPATCHER_ACTION_VPN_DOWN, + priv->connection, + parent_dev, + priv->ip_iface, + NULL, + NULL, + NULL, + NULL, + NULL); + } } /* Tear down and clean up the connection */ @@ -290,14 +441,20 @@ device_state_changed (NMActiveConnection *active, NMDeviceState old_state) { if (new_state <= NM_DEVICE_STATE_DISCONNECTED) { - nm_vpn_connection_set_vpn_state (NM_VPN_CONNECTION (active), - NM_VPN_CONNECTION_STATE_DISCONNECTED, - NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + _set_vpn_state (NM_VPN_CONNECTION (active), + STATE_DISCONNECTED, + NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + FALSE); } else if (new_state == NM_DEVICE_STATE_FAILED) { - nm_vpn_connection_set_vpn_state (NM_VPN_CONNECTION (active), - NM_VPN_CONNECTION_STATE_FAILED, - NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + _set_vpn_state (NM_VPN_CONNECTION (active), + STATE_FAILED, + NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + FALSE); } + + /* FIXME: map device DEACTIVATING state to VPN DEACTIVATING state and + * block device deactivation on VPN deactivation. + */ } static void @@ -488,27 +645,25 @@ vpn_service_state_to_string (NMVPNServiceState state) return "unknown"; } +static const char *state_table[] = { + [STATE_UNKNOWN] = "unknown", + [STATE_WAITING] = "waiting", + [STATE_PREPARE] = "prepare", + [STATE_NEED_AUTH] = "need-auth", + [STATE_CONNECT] = "connect", + [STATE_IP_CONFIG_GET] = "ip-config-get", + [STATE_PRE_UP] = "pre-up", + [STATE_ACTIVATED] = "activated", + [STATE_DEACTIVATING] = "deactivating", + [STATE_DISCONNECTED] = "disconnected", + [STATE_FAILED] = "failed", +}; + static const char * -vpn_state_to_string (NMVPNConnectionState state) +vpn_state_to_string (VpnState state) { - switch (state) { - case NM_VPN_CONNECTION_STATE_PREPARE: - return "prepare"; - case NM_VPN_CONNECTION_STATE_NEED_AUTH: - return "need-auth"; - case NM_VPN_CONNECTION_STATE_CONNECT: - return "connect"; - case NM_VPN_CONNECTION_STATE_IP_CONFIG_GET: - return "ip-config-get"; - case NM_VPN_CONNECTION_STATE_ACTIVATED: - return "activated"; - case NM_VPN_CONNECTION_STATE_FAILED: - return "failed"; - case NM_VPN_CONNECTION_STATE_DISCONNECTED: - return "disconnected"; - default: - break; - } + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; return "unknown"; } @@ -561,23 +716,13 @@ plugin_state_changed (DBusGProxy *proxy, */ nm_connection_clear_secrets (priv->connection); - switch (nm_vpn_connection_get_vpn_state (connection)) { - case NM_VPN_CONNECTION_STATE_PREPARE: - case NM_VPN_CONNECTION_STATE_NEED_AUTH: - case NM_VPN_CONNECTION_STATE_CONNECT: - case NM_VPN_CONNECTION_STATE_IP_CONFIG_GET: - case NM_VPN_CONNECTION_STATE_ACTIVATED: + if ((priv->vpn_state >= STATE_WAITING) && (priv->vpn_state <= STATE_ACTIVATED)) { nm_log_info (LOGD_VPN, "VPN plugin state change reason: %s (%d)", vpn_reason_to_string (priv->failure_reason), priv->failure_reason); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_FAILED, - priv->failure_reason); + _set_vpn_state (connection, STATE_FAILED, priv->failure_reason, FALSE); /* Reset the failure reason */ priv->failure_reason = NM_VPN_CONNECTION_STATE_REASON_UNKNOWN; - break; - default: - break; } } } @@ -742,9 +887,7 @@ nm_vpn_connection_apply_config (NMVPNConnection *connection) nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) complete.", nm_connection_get_id (priv->connection)); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_ACTIVATED, - NM_VPN_CONNECTION_STATE_REASON_NONE); + _set_vpn_state (connection, STATE_PRE_UP, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); return TRUE; } @@ -784,9 +927,7 @@ nm_vpn_connection_config_maybe_complete (NMVPNConnection *connection, nm_log_warn (LOGD_VPN, "VPN connection '%s' did not receive valid IP config information.", nm_connection_get_id (priv->connection)); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_FAILED, - NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID); + _set_vpn_state (connection, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, FALSE); } #define LOG_INVALID_ARG(property) \ @@ -880,11 +1021,8 @@ nm_vpn_connection_config_get (DBusGProxy *proxy, nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) reply received.", nm_connection_get_id (priv->connection)); - if (nm_vpn_connection_get_vpn_state (connection) == NM_VPN_CONNECTION_STATE_CONNECT) { - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_IP_CONFIG_GET, - NM_VPN_CONNECTION_STATE_REASON_NONE); - } + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state (connection, STATE_IP_CONFIG_GET, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); if (!process_generic_config (connection, config_hash)) return; @@ -937,11 +1075,8 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy, GValue *val; int i; - if (nm_vpn_connection_get_vpn_state (connection) == NM_VPN_CONNECTION_STATE_CONNECT) { - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_IP_CONFIG_GET, - NM_VPN_CONNECTION_STATE_REASON_NONE); - } + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state (connection, STATE_IP_CONFIG_GET, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); if (priv->has_ip4) { nm_log_info (LOGD_VPN, "VPN connection '%s' (IP4 Config Get) reply received.", @@ -1096,11 +1231,8 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy, nm_log_info (LOGD_VPN, "VPN connection '%s' (IP6 Config Get) reply received.", nm_connection_get_id (priv->connection)); - if (nm_vpn_connection_get_vpn_state (connection) == NM_VPN_CONNECTION_STATE_CONNECT) { - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_IP_CONFIG_GET, - NM_VPN_CONNECTION_STATE_REASON_NONE); - } + if (priv->vpn_state == STATE_CONNECT) + _set_vpn_state (connection, STATE_IP_CONFIG_GET, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); if (g_hash_table_size (config_hash) == 0) { priv->has_ip6 = FALSE; @@ -1233,19 +1365,15 @@ connect_timeout_cb (gpointer user_data) { NMVPNConnection *connection = NM_VPN_CONNECTION (user_data); NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); - NMVPNConnectionState state; priv->connect_timeout = 0; /* Cancel activation if it's taken too long */ - state = nm_vpn_connection_get_vpn_state (connection); - if (state == NM_VPN_CONNECTION_STATE_CONNECT || - state == NM_VPN_CONNECTION_STATE_IP_CONFIG_GET) { + if (priv->vpn_state == STATE_CONNECT || + priv->vpn_state == STATE_IP_CONFIG_GET) { nm_log_warn (LOGD_VPN, "VPN connection '%s' connect timeout exceeded.", nm_connection_get_id (priv->connection)); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_FAILED, - NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT); + _set_vpn_state (connection, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, FALSE); } return FALSE; @@ -1282,9 +1410,7 @@ connect_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data) nm_log_warn (LOGD_VPN, "VPN connection '%s' failed to connect: '%s'.", nm_connection_get_id (priv->connection), err->message); g_error_free (err); - nm_vpn_connection_set_vpn_state (self, - NM_VPN_CONNECTION_STATE_FAILED, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); } static void @@ -1313,9 +1439,7 @@ connect_interactive_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data nm_log_warn (LOGD_VPN, "VPN connection '%s' failed to connect interactively: '%s'.", nm_connection_get_id (priv->connection), err->message); g_error_free (err); - nm_vpn_connection_set_vpn_state (self, - NM_VPN_CONNECTION_STATE_FAILED, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED, FALSE); } } @@ -1355,9 +1479,9 @@ really_activate (NMVPNConnection *connection, const char *username) GHashTable *details; g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); - g_return_if_fail (nm_vpn_connection_get_vpn_state (connection) == NM_VPN_CONNECTION_STATE_NEED_AUTH); priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + g_return_if_fail (priv->vpn_state == STATE_NEED_AUTH); dbus_g_object_register_marshaller (g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, G_TYPE_VALUE, G_TYPE_INVALID); @@ -1410,9 +1534,7 @@ really_activate (NMVPNConnection *connection, const char *username) g_object_unref (agent_mgr); g_hash_table_destroy (details); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_CONNECT, - NM_VPN_CONNECTION_STATE_REASON_NONE); + _set_vpn_state (connection, STATE_CONNECT, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); } void @@ -1422,10 +1544,11 @@ nm_vpn_connection_activate (NMVPNConnection *connection) DBusGConnection *bus; g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); - g_return_if_fail (nm_vpn_connection_get_vpn_state (connection) == NM_VPN_CONNECTION_STATE_PREPARE); priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + _set_vpn_state (connection, STATE_PREPARE, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); + bus = nm_dbus_manager_get_connection (nm_dbus_manager_get ()); priv->proxy = dbus_g_proxy_new_for_name (bus, nm_vpn_connection_get_service (connection), @@ -1450,9 +1573,7 @@ nm_vpn_connection_activate (NMVPNConnection *connection) G_CALLBACK (plugin_interactive_secrets_required), connection, NULL); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_NEED_AUTH, - NM_VPN_CONNECTION_STATE_REASON_NONE); + _set_vpn_state (connection, 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 @@ -1475,7 +1596,7 @@ nm_vpn_connection_get_vpn_state (NMVPNConnection *connection) { g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NM_VPN_CONNECTION_STATE_UNKNOWN); - return NM_VPN_CONNECTION_GET_PRIVATE (connection)->vpn_state; + return _state_to_nm_vpn_state (NM_VPN_CONNECTION_GET_PRIVATE (connection)->vpn_state); } const char * @@ -1535,25 +1656,31 @@ nm_vpn_connection_get_ip6_internal_gateway (NMVPNConnection *connection) } void -nm_vpn_connection_fail (NMVPNConnection *connection, - NMVPNConnectionStateReason reason) +nm_vpn_connection_disconnect (NMVPNConnection *connection, + NMVPNConnectionStateReason reason, + gboolean quitting) { g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_FAILED, - reason); + _set_vpn_state (connection, STATE_DISCONNECTED, reason, quitting); } -void -nm_vpn_connection_disconnect (NMVPNConnection *connection, - NMVPNConnectionStateReason reason) +gboolean +nm_vpn_connection_deactivate (NMVPNConnection *connection, + NMVPNConnectionStateReason reason, + gboolean quitting) { - g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); + NMVPNConnectionPrivate *priv; + gboolean success = FALSE; - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_DISCONNECTED, - reason); + g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), FALSE); + + priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + if (priv->vpn_state > STATE_UNKNOWN && priv->vpn_state <= STATE_DEACTIVATING) { + _set_vpn_state (connection, STATE_DEACTIVATING, reason, quitting); + success = TRUE; + } + return success; } /******************************************************************************/ @@ -1576,7 +1703,7 @@ plugin_need_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_dat priv->secrets_idx + 1, g_quark_to_string (error->domain), error->message); - nm_vpn_connection_fail (self, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); g_error_free (error); return; } @@ -1588,7 +1715,7 @@ plugin_need_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_dat nm_log_err (LOGD_VPN, "(%s/%s) final secrets request failed to provide sufficient secrets", nm_connection_get_uuid (priv->connection), nm_connection_get_id (priv->connection)); - nm_vpn_connection_fail (self, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); } else { nm_log_dbg (LOGD_VPN, "(%s/%s) service indicated additional secrets required", nm_connection_get_uuid (priv->connection), @@ -1620,7 +1747,7 @@ plugin_new_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data nm_connection_get_id (priv->connection), g_quark_to_string (error->domain), error->message); - nm_vpn_connection_fail (self, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); g_error_free (error); } } @@ -1645,7 +1772,7 @@ get_secrets_cb (NMSettingsConnection *connection, if (error) { nm_log_err (LOGD_VPN, "Failed to request VPN secrets #%d: (%d) %s", priv->secrets_idx + 1, error->code, error->message); - nm_vpn_connection_fail (self, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); } else { /* Cache the username for later */ if (agent_username) { @@ -1729,7 +1856,7 @@ get_secrets (NMVPNConnection *self, nm_log_err (LOGD_VPN, "failed to request VPN secrets #%d: (%d) %s", priv->secrets_idx + 1, error->code, error->message); } - nm_vpn_connection_fail (self, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS); + _set_vpn_state (self, STATE_FAILED, NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, FALSE); g_clear_error (&error); } } @@ -1749,13 +1876,11 @@ plugin_interactive_secrets_required (DBusGProxy *proxy, nm_log_info (LOGD_VPN, "VPN plugin requested secrets; state %s (%d)", vpn_state_to_string (priv->vpn_state), priv->vpn_state); - g_return_if_fail (priv->vpn_state == NM_VPN_CONNECTION_STATE_CONNECT || - priv->vpn_state == NM_VPN_CONNECTION_STATE_NEED_AUTH); + g_return_if_fail (priv->vpn_state == STATE_CONNECT || + priv->vpn_state == STATE_NEED_AUTH); priv->secrets_idx = SECRETS_REQ_INTERACTIVE; - nm_vpn_connection_set_vpn_state (connection, - NM_VPN_CONNECTION_STATE_NEED_AUTH, - NM_VPN_CONNECTION_STATE_REASON_NONE); + _set_vpn_state (connection, STATE_NEED_AUTH, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); /* Copy hints and add message to the end */ hints = g_malloc0 (sizeof (char *) * (secrets_len + 2)); @@ -1775,7 +1900,7 @@ nm_vpn_connection_init (NMVPNConnection *self) { NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self); - priv->vpn_state = NM_VPN_CONNECTION_STATE_PREPARE; + priv->vpn_state = STATE_WAITING; priv->secrets_idx = SECRETS_REQ_SYSTEM; } @@ -1795,38 +1920,28 @@ dispose (GObject *object) { NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object); - if (priv->disposed) { - G_OBJECT_CLASS (nm_vpn_connection_parent_class)->dispose (object); - return; - } - priv->disposed = TRUE; - - if (priv->connect_hash) + if (priv->connect_hash) { g_hash_table_destroy (priv->connect_hash); + priv->connect_hash = NULL; + } - if (priv->ip6_internal_gw) - g_free (priv->ip6_internal_gw); - if (priv->ip6_external_gw) - g_free (priv->ip6_external_gw); - - if (priv->ip4_config) - g_object_unref (priv->ip4_config); - if (priv->ip6_config) - g_object_unref (priv->ip6_config); - - if (priv->connect_timeout) + if (priv->connect_timeout) { g_source_remove (priv->connect_timeout); + priv->connect_timeout = 0; + } - if (priv->proxy) - g_object_unref (priv->proxy); + dispatcher_cleanup (NM_VPN_CONNECTION (object)); if (priv->secrets_id) { nm_settings_connection_cancel_secrets (NM_SETTINGS_CONNECTION (priv->connection), priv->secrets_id); + priv->secrets_id = 0; } + g_clear_object (&priv->ip4_config); + g_clear_object (&priv->ip6_config); + g_clear_object (&priv->proxy); g_clear_object (&priv->connection); - g_free (priv->username); G_OBJECT_CLASS (nm_vpn_connection_parent_class)->dispose (object); } @@ -1838,10 +1953,19 @@ finalize (GObject *object) g_free (priv->banner); g_free (priv->ip_iface); + g_free (priv->username); + g_free (priv->ip6_internal_gw); + g_free (priv->ip6_external_gw); G_OBJECT_CLASS (nm_vpn_connection_parent_class)->finalize (object); } +static gboolean +ip_config_valid (VpnState state) +{ + return (state == STATE_PRE_UP || state == STATE_ACTIVATED); +} + static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) @@ -1851,19 +1975,19 @@ get_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_VPN_STATE: - g_value_set_uint (value, priv->vpn_state); + g_value_set_uint (value, _state_to_nm_vpn_state (priv->vpn_state)); break; case PROP_BANNER: g_value_set_string (value, priv->banner ? priv->banner : ""); break; case PROP_IP4_CONFIG: - if (priv->vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED && priv->ip4_config) + if (ip_config_valid (priv->vpn_state) && priv->ip4_config) g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config)); else g_value_set_boxed (value, "/"); break; case PROP_IP6_CONFIG: - if (priv->vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED && priv->ip6_config) + if (ip_config_valid (priv->vpn_state) && priv->ip6_config) g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config)); else g_value_set_boxed (value, "/"); diff --git a/src/vpn-manager/nm-vpn-connection.h b/src/vpn-manager/nm-vpn-connection.h index b98dcbe629..c9c88cda14 100644 --- a/src/vpn-manager/nm-vpn-connection.h +++ b/src/vpn-manager/nm-vpn-connection.h @@ -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); diff --git a/src/vpn-manager/nm-vpn-manager.c b/src/vpn-manager/nm-vpn-manager.c index 6c63edc1e7..5550d4d6c6 100644 --- a/src/vpn-manager/nm-vpn-manager.c +++ b/src/vpn-manager/nm-vpn-manager.c @@ -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)); - for (aiter = active; aiter; aiter = g_slist_next (aiter)) { - NMVPNConnection *vpn = NM_VPN_CONNECTION (aiter->data); - - if (nm_vpn_connection_get_connection (vpn) == connection) { - found = vpn; - break; - } - } - } - return found; -} - gboolean nm_vpn_manager_activate_connection (NMVPNManager *manager, NMVPNConnection *vpn, GError **error) { - NMVPNConnection *existing = NULL; NMConnection *connection; NMSettingVPN *s_vpn; NMVPNService *service; @@ -145,11 +115,6 @@ nm_vpn_manager_activate_connection (NMVPNManager *manager, return FALSE; } - existing = find_active_vpn_connection (manager, - nm_active_connection_get_connection (NM_ACTIVE_CONNECTION (vpn))); - if (existing) - nm_vpn_connection_disconnect (vpn, NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED); - return nm_vpn_service_activate (service, vpn, error); } @@ -158,51 +123,7 @@ nm_vpn_manager_deactivate_connection (NMVPNManager *self, NMVPNConnection *connection, NMVPNConnectionStateReason reason) { - NMVPNManagerPrivate *priv; - GHashTableIter iter; - gpointer data; - const GSList *active, *aiter; - gboolean success = FALSE; - - g_return_val_if_fail (self, FALSE); - g_return_val_if_fail (NM_IS_VPN_MANAGER (self), FALSE); - g_return_val_if_fail (connection != NULL, FALSE); - - priv = NM_VPN_MANAGER_GET_PRIVATE (self); - g_hash_table_iter_init (&iter, priv->services); - while (g_hash_table_iter_next (&iter, NULL, &data) && (success == FALSE)) { - active = nm_vpn_service_get_active_connections (NM_VPN_SERVICE (data)); - for (aiter = active; aiter; aiter = g_slist_next (aiter)) { - NMVPNConnection *candidate = aiter->data; - - if (connection == candidate) { - nm_vpn_connection_disconnect (connection, reason); - success = TRUE; - break; - } - } - } - - return success; -} - -static char * -service_name_from_file (const char *path) -{ - GKeyFile *kf = NULL; - char *service_name = NULL; - - g_return_val_if_fail (g_path_is_absolute (path), NULL); - - if (!g_str_has_suffix (path, ".name")) - return NULL; - - kf = g_key_file_new (); - if (g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, NULL)) - service_name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", NULL); - - g_key_file_free (kf); - return service_name; + return nm_vpn_connection_deactivate (connection, reason, FALSE); } static void @@ -210,34 +131,32 @@ try_add_service (NMVPNManager *self, const char *namefile) { NMVPNManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); NMVPNService *service = NULL; + GHashTableIter iter; GError *error = NULL; const char *service_name; - char *tmp; g_return_if_fail (g_path_is_absolute (namefile)); /* Make sure we don't add dupes */ - tmp = service_name_from_file (namefile); - if (tmp) - service = g_hash_table_lookup (priv->services, tmp); - g_free (tmp); - if (service) - return; + g_hash_table_iter_init (&iter, priv->services); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &service)) { + if (g_strcmp0 (namefile, nm_vpn_service_get_name_file (service)) == 0) + return; + } - /* New service, add it */ + /* New service */ service = nm_vpn_service_new (namefile, &error); - if (!service) { + if (service) { + service_name = nm_vpn_service_get_dbus_service (service); + g_hash_table_insert (priv->services, (char *) service_name, service); + nm_log_info (LOGD_VPN, "VPN: loaded %s", service_name); + } else { nm_log_warn (LOGD_VPN, "failed to load VPN service file %s: (%d) %s", namefile, error ? error->code : -1, error && error->message ? error->message : "(unknown)"); g_clear_error (&error); - return; } - - service_name = nm_vpn_service_get_dbus_service (service); - g_hash_table_insert (priv->services, (char *) service_name, service); - nm_log_info (LOGD_VPN, "VPN: loaded %s", service_name); } static void @@ -267,7 +186,7 @@ vpn_dir_changed (GFileMonitor *monitor, const char *service_name = nm_vpn_service_get_dbus_service (service); /* Stop active VPN connections and destroy the service */ - nm_vpn_service_connections_stop (service, TRUE, + nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); nm_log_info (LOGD_VPN, "VPN: unloaded %s", service_name); g_hash_table_remove (priv->services, service_name); @@ -339,21 +258,36 @@ nm_vpn_manager_init (NMVPNManager *self) } static void +stop_all_services (NMVPNManager *self) +{ + NMVPNManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (self); + GHashTableIter iter; + NMVPNService *service; + + g_hash_table_iter_init (&iter, priv->services); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &service)) { + nm_vpn_service_stop_connections (service, + TRUE, + NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); + } +} + +static void dispose (GObject *object) { NMVPNManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (object); - if (!priv->disposed) { - priv->disposed = TRUE; - - if (priv->monitor) { - if (priv->monitor_id) - g_signal_handler_disconnect (priv->monitor, priv->monitor_id); - g_file_monitor_cancel (priv->monitor); - g_object_unref (priv->monitor); - } + if (priv->monitor) { + if (priv->monitor_id) + g_signal_handler_disconnect (priv->monitor, priv->monitor_id); + g_file_monitor_cancel (priv->monitor); + g_clear_object (&priv->monitor); + } + if (priv->services) { + stop_all_services (NM_VPN_MANAGER (object)); g_hash_table_destroy (priv->services); + priv->services = NULL; } G_OBJECT_CLASS (nm_vpn_manager_parent_class)->dispose (object); diff --git a/src/vpn-manager/nm-vpn-service.c b/src/vpn-manager/nm-vpn-service.c index 8ecd9a1176..9eb6b96824 100644 --- a/src/vpn-manager/nm-vpn-service.c +++ b/src/vpn-manager/nm-vpn-service.c @@ -15,7 +15,7 @@ * 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 - 2012 Red Hat, Inc. + * Copyright (C) 2005 - 2014 Red Hat, Inc. * Copyright (C) 2005 - 2008 Novell, Inc. */ @@ -38,30 +38,30 @@ G_DEFINE_TYPE (NMVPNService, nm_vpn_service, G_TYPE_OBJECT) typedef struct { - gboolean disposed; - - NMDBusManager *dbus_mgr; char *name; char *dbus_service; char *program; char *namefile; - GPid pid; - GSList *connections; + NMVPNConnection *active; + GSList *pending; + guint start_timeout; - guint quit_timeout; - guint child_watch; - gulong name_owner_id; + gboolean service_running; } NMVPNServicePrivate; #define NM_VPN_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE, NMVPNServicePrivate)) +#define VPN_CONNECTION_GROUP "VPN Connection" + +static gboolean start_pending_vpn (NMVPNService *self); + NMVPNService * nm_vpn_service_new (const char *namefile, GError **error) { - NMVPNService *self = NULL; + NMVPNService *self; + NMVPNServicePrivate *priv; GKeyFile *kf; - char *dbus_service = NULL, *program = NULL, *name = NULL; g_return_val_if_fail (namefile != NULL, NULL); g_return_val_if_fail (g_path_is_absolute (namefile), NULL); @@ -72,36 +72,31 @@ nm_vpn_service_new (const char *namefile, GError **error) return NULL; } - dbus_service = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", NULL); - if (!dbus_service) { - g_set_error (error, 0, 0, "VPN service file %s had no 'service' key", namefile); - goto out; - } + self = (NMVPNService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL); + priv = NM_VPN_SERVICE_GET_PRIVATE (self); + priv->namefile = g_strdup (namefile); - program = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "program", NULL); - if (!program) { - g_set_error (error, 0, 0, "VPN service file %s had no 'program' key", namefile); - goto out; - } + priv->dbus_service = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", error); + if (!priv->dbus_service) + goto error; - name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "name", NULL); - if (!name) { - g_set_error (error, 0, 0, "VPN service file %s had no 'name' key", namefile); - goto out; - } + priv->program = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "program", error); + if (!priv->program) + goto error; - self = (NMVPNService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL); - NM_VPN_SERVICE_GET_PRIVATE (self)->name = g_strdup (name); - NM_VPN_SERVICE_GET_PRIVATE (self)->dbus_service = g_strdup (dbus_service); - NM_VPN_SERVICE_GET_PRIVATE (self)->program = g_strdup (program); - NM_VPN_SERVICE_GET_PRIVATE (self)->namefile = g_strdup (namefile); + priv->name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "name", error); + if (!priv->name) + goto error; + + priv->service_running = nm_dbus_manager_name_has_owner (nm_dbus_manager_get (), priv->dbus_service); - out: g_key_file_free (kf); - g_free (dbus_service); - g_free (program); - g_free (name); return self; + +error: + g_object_unref (self); + g_key_file_free (kf); + return NULL; } const char * @@ -120,46 +115,60 @@ nm_vpn_service_get_name_file (NMVPNService *service) return NM_VPN_SERVICE_GET_PRIVATE (service)->namefile; } +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); + } else + priv->pending = g_slist_remove (priv->pending, connection); + g_object_unref (connection); + } +} + void -nm_vpn_service_connections_stop (NMVPNService *service, - gboolean fail, +nm_vpn_service_stop_connections (NMVPNService *service, + gboolean quitting, NMVPNConnectionStateReason reason) { NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service); - GSList *iter, *copy; + GSList *iter; - /* Copy because stopping the connection may remove it from the list - * in the NMVPNService objects' VPN connection state handler. + /* Just add priv->active to the beginning of priv->pending, + * since we're going to clear priv->pending immediately anyway. */ - copy = g_slist_copy (priv->connections); - for (iter = copy; iter; iter = iter->next) { - if (fail) - nm_vpn_connection_fail (NM_VPN_CONNECTION (iter->data), reason); - else - nm_vpn_connection_disconnect (NM_VPN_CONNECTION (iter->data), reason); + if (priv->active) { + priv->pending = g_slist_prepend (priv->pending, priv->active); + priv->active = NULL; } - g_slist_free (copy); -} -static void -clear_quit_timeout (NMVPNService *self) -{ - NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); + for (iter = priv->pending; iter; iter = iter->next) { + NMVPNConnection *vpn = NM_VPN_CONNECTION (iter->data); - if (priv->quit_timeout) { - g_source_remove (priv->quit_timeout); - priv->quit_timeout = 0; + 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); } -/* - * nm_vpn_service_child_setup - * - * Set the process group ID of the newly forked process - * - */ static void -nm_vpn_service_child_setup (gpointer user_data G_GNUC_UNUSED) +_daemon_setup (gpointer user_data G_GNUC_UNUSED) { /* We are in the child process at this point */ pid_t pid = getpid (); @@ -172,73 +181,37 @@ nm_vpn_service_child_setup (gpointer user_data G_GNUC_UNUSED) nm_unblock_posix_signals (NULL); } -static void -vpn_service_watch_cb (GPid pid, gint status, gpointer user_data) -{ - NMVPNService *service = NM_VPN_SERVICE (user_data); - NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service); - - if (WIFEXITED (status)) { - guint err = WEXITSTATUS (status); - - if (err != 0) { - nm_log_warn (LOGD_VPN, "VPN service '%s' exited with error: %d", - priv->name, WSTOPSIG (status)); - } - } else if (WIFSTOPPED (status)) { - nm_log_warn (LOGD_VPN, "VPN service '%s' stopped unexpectedly with signal %d", - priv->name, WSTOPSIG (status)); - } else if (WIFSIGNALED (status)) { - nm_log_warn (LOGD_VPN, "VPN service '%s' died with signal %d", - priv->name, WTERMSIG (status)); - } else { - nm_log_warn (LOGD_VPN, "VPN service '%s' died from an unknown cause", - priv->name); - } - - priv->pid = 0; - priv->child_watch = 0; - clear_quit_timeout (service); - - nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); -} - static gboolean -nm_vpn_service_timeout (gpointer data) +_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", priv->name); priv->start_timeout = 0; - nm_vpn_service_connections_stop (self, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT); - return FALSE; + 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); - g_return_val_if_fail (error != NULL, FALSE); - g_return_val_if_fail (*error == NULL, FALSE); vpn_argv[0] = priv->program; vpn_argv[1] = NULL; - success = g_spawn_async (NULL, vpn_argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, - nm_vpn_service_child_setup, NULL, &priv->pid, - &spawn_error); + success = g_spawn_async (NULL, vpn_argv, NULL, 0, _daemon_setup, NULL, &pid, &spawn_error); if (success) { nm_log_info (LOGD_VPN, "VPN service '%s' started (%s), PID %d", - priv->name, priv->dbus_service, priv->pid); - - priv->child_watch = g_child_watch_add (priv->pid, vpn_service_watch_cb, service); - priv->start_timeout = g_timeout_add_seconds (5, nm_vpn_service_timeout, service); + priv->name, priv->dbus_service, 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.", priv->name, @@ -249,7 +222,7 @@ nm_vpn_service_daemon_exec (NMVPNService *service, GError **error) NM_VPN_MANAGER_ERROR, NM_VPN_MANAGER_ERROR_SERVICE_START_FAILED, "%s", spawn_error ? spawn_error->message : "unknown g_spawn_async() error"); - nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED); + nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED); if (spawn_error) g_error_free (spawn_error); } @@ -258,69 +231,42 @@ nm_vpn_service_daemon_exec (NMVPNService *service, GError **error) } static gboolean -ensure_killed (gpointer data) +start_active_vpn (NMVPNService *self, GError **error) { - int pid = GPOINTER_TO_INT (data); + NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - if (kill (pid, 0) == 0) - kill (pid, SIGKILL); + if (!priv->active) + return TRUE; - /* ensure the child is reaped */ - nm_log_dbg (LOGD_VPN, "waiting for VPN service pid %d to exit", pid); - waitpid (pid, NULL, 0); - nm_log_dbg (LOGD_VPN, "VPN service pid %d cleaned up", pid); + 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'...", priv->name); + return nm_vpn_service_daemon_exec (self, error); + } - return FALSE; + /* Already started VPN service, waiting for it to appear on D-Bus */ + return TRUE; } static gboolean -service_quit (gpointer user_data) +start_pending_vpn (NMVPNService *self) { - NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (user_data); - - if (priv->pid) { - if (kill (priv->pid, SIGTERM) == 0) - g_timeout_add_seconds (2, ensure_killed, GINT_TO_POINTER (priv->pid)); - else { - kill (priv->pid, SIGKILL); - - /* ensure the child is reaped */ - nm_log_dbg (LOGD_VPN, "waiting for VPN service pid %d to exit", priv->pid); - waitpid (priv->pid, NULL, 0); - nm_log_dbg (LOGD_VPN, "VPN service pid %d cleaned up", priv->pid); - } - priv->pid = 0; - } - priv->quit_timeout = 0; + NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - return FALSE; -} + g_assert (priv->active == NULL); -static void -connection_vpn_state_changed (NMVPNConnection *connection, - NMVPNConnectionState new_state, - NMVPNConnectionState old_state, - NMVPNConnectionStateReason reason, - gpointer user_data) -{ - NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (user_data); + if (!priv->pending) + return TRUE; - switch (new_state) { - case NM_VPN_CONNECTION_STATE_FAILED: - case NM_VPN_CONNECTION_STATE_DISCONNECTED: - /* Remove the connection from our list */ - priv->connections = g_slist_remove (priv->connections, connection); - g_object_unref (connection); + /* Make next VPN active */ + priv->active = g_slist_nth_data (priv->pending, 0); + priv->pending = g_slist_remove (priv->pending, priv->active); - if (priv->connections == NULL) { - /* Tell the service to quit in a few seconds */ - if (!priv->quit_timeout) - priv->quit_timeout = g_timeout_add_seconds (5, service_quit, user_data); - } - break; - default: - break; - } + return start_active_vpn (self, NULL); } gboolean @@ -337,45 +283,36 @@ nm_vpn_service_activate (NMVPNService *service, priv = NM_VPN_SERVICE_GET_PRIVATE (service); - clear_quit_timeout (service); - g_signal_connect (vpn, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED, G_CALLBACK (connection_vpn_state_changed), service); - priv->connections = g_slist_prepend (priv->connections, g_object_ref (vpn)); + /* Queue up the new VPN connection */ + priv->pending = g_slist_append (priv->pending, g_object_ref (vpn)); - if (nm_dbus_manager_name_has_owner (priv->dbus_mgr, priv->dbus_service)) - nm_vpn_connection_activate (vpn); - else if (priv->start_timeout == 0) { - nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", priv->name); - if (!nm_vpn_service_daemon_exec (service, error)) - return FALSE; + /* 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; } - return TRUE; -} - -const GSList * -nm_vpn_service_get_active_connections (NMVPNService *service) -{ - g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL); - - return NM_VPN_SERVICE_GET_PRIVATE (service)->connections; + /* Otherwise start the next VPN */ + return start_pending_vpn (service); } static void -nm_vpn_service_name_owner_changed (NMDBusManager *mgr, - const char *name, - const char *old, - const char *new, - gpointer user_data) +_name_owner_changed (NMDBusManager *mgr, + const char *name, + const char *old, + const char *new, + gpointer user_data) { NMVPNService *service = NM_VPN_SERVICE (user_data); NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service); - gboolean old_owner_good; - gboolean new_owner_good; - GSList *iter; + gboolean old_owner_good, new_owner_good, success; if (strcmp (name, priv->dbus_service)) return; @@ -386,20 +323,21 @@ nm_vpn_service_name_owner_changed (NMDBusManager *mgr, priv->start_timeout = 0; } - old_owner_good = (old && (strlen (old) > 0)); - new_owner_good = (new && (strlen (new) > 0)); + old_owner_good = (old && old[0]); + new_owner_good = (new && new[0]); if (!old_owner_good && new_owner_good) { - /* service just appeared */ + /* service appeared */ + priv->service_running = TRUE; nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", priv->name); - clear_quit_timeout (service); - - for (iter = priv->connections; iter; iter = iter->next) - nm_vpn_connection_activate (NM_VPN_CONNECTION (iter->data)); + /* Expect success because the VPN service has already appeared */ + success = start_active_vpn (service, NULL); + g_warn_if_fail (success); } else if (old_owner_good && !new_owner_good) { /* service went away */ + priv->service_running = FALSE; nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", priv->name); - nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); + nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); } } @@ -408,13 +346,10 @@ nm_vpn_service_name_owner_changed (NMDBusManager *mgr, static void nm_vpn_service_init (NMVPNService *self) { - NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - - priv->dbus_mgr = nm_dbus_manager_get (); - priv->name_owner_id = g_signal_connect (priv->dbus_mgr, - NM_DBUS_MANAGER_NAME_OWNER_CHANGED, - G_CALLBACK (nm_vpn_service_name_owner_changed), - self); + g_signal_connect (nm_dbus_manager_get (), + NM_DBUS_MANAGER_NAME_OWNER_CHANGED, + G_CALLBACK (_name_owner_changed), + self); } static void @@ -423,34 +358,33 @@ dispose (GObject *object) NMVPNService *self = NM_VPN_SERVICE (object); NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self); - if (priv->disposed) - goto out; - priv->disposed = TRUE; - - if (priv->start_timeout) + if (priv->start_timeout) { g_source_remove (priv->start_timeout); + priv->start_timeout = 0; + } - nm_vpn_service_connections_stop (NM_VPN_SERVICE (object), - FALSE, - NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED); - - g_signal_handler_disconnect (priv->dbus_mgr, priv->name_owner_id); + /* VPNService owner is required to stop connections before releasing */ + g_assert (priv->active == NULL); + g_assert (priv->pending == NULL); - if (priv->child_watch) - g_source_remove (priv->child_watch); + g_signal_handlers_disconnect_by_func (nm_dbus_manager_get (), + G_CALLBACK (_name_owner_changed), + self); - clear_quit_timeout (self); - service_quit (self); + G_OBJECT_CLASS (nm_vpn_service_parent_class)->dispose (object); +} - priv->dbus_mgr = NULL; +static void +finalize (GObject *object) +{ + NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (object); g_free (priv->name); g_free (priv->dbus_service); g_free (priv->program); g_free (priv->namefile); -out: - G_OBJECT_CLASS (nm_vpn_service_parent_class)->dispose (object); + G_OBJECT_CLASS (nm_vpn_service_parent_class)->finalize (object); } static void @@ -462,4 +396,5 @@ nm_vpn_service_class_init (NMVPNServiceClass *service_class) /* virtual methods */ object_class->dispose = dispose; + object_class->finalize = finalize; } diff --git a/src/vpn-manager/nm-vpn-service.h b/src/vpn-manager/nm-vpn-service.h index 2ad79a0d6b..4545d1f07d 100644 --- a/src/vpn-manager/nm-vpn-service.h +++ b/src/vpn-manager/nm-vpn-service.h @@ -34,8 +34,6 @@ #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)) -#define VPN_CONNECTION_GROUP "VPN Connection" - typedef struct { GObject parent; } NMVPNService; @@ -58,10 +56,8 @@ gboolean nm_vpn_service_activate (NMVPNService *service, NMVPNConnection *vpn, GError **error); -const GSList *nm_vpn_service_get_active_connections (NMVPNService *service); - -void nm_vpn_service_connections_stop (NMVPNService *service, - gboolean fail, +void nm_vpn_service_stop_connections (NMVPNService *service, + gboolean quitting, NMVPNConnectionStateReason reason); #endif /* NM_VPN_VPN_SERVICE_H */ |