diff options
-rw-r--r-- | src/devices/nm-device.c | 96 | ||||
-rw-r--r-- | src/devices/nm-device.h | 2 | ||||
-rw-r--r-- | src/nm-manager.c | 1 | ||||
-rw-r--r-- | src/platform/nm-linux-platform.c | 243 |
4 files changed, 257 insertions, 85 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index a5c13f6d88..997284878a 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -854,6 +854,22 @@ nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean configure, return success; } +/** + * nm_device_finish_init: + * @self: the master device + * + * Whatever needs to be done post-initialization, when the device has a DBus + * object name. + */ +void +nm_device_finish_init (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->master) + nm_device_enslave_slave (priv->master, self, NULL); +} + static void carrier_changed (NMDevice *device, gboolean carrier) { @@ -1018,6 +1034,27 @@ update_for_ip_ifname_change (NMDevice *device) } static void +device_set_master (NMDevice *self, int ifindex) +{ + NMDevice *master; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + master = nm_manager_get_device_by_ifindex (nm_manager_get (), ifindex); + if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) { + g_clear_object (&priv->master); + priv->master = g_object_ref (master); + nm_device_master_add_slave (master, self, FALSE); + } else if (master) { + nm_log_info (LOGD_DEVICE, "(%s): enslaved to non-master-type device %s; ignoring", + nm_device_get_iface (self), + nm_device_get_iface (master)); + } else { + nm_log_warn (LOGD_DEVICE, "(%s): enslaved to unknown device", + nm_device_get_iface (self)); + } +} + +static void device_link_changed (NMDevice *device, NMPlatformLink *info) { NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device); @@ -1060,27 +1097,13 @@ device_link_changed (NMDevice *device, NMPlatformLink *info) } /* Update slave status for external changes */ - if (info->master && !priv->enslaved) { - NMDevice *master; - - master = nm_manager_get_device_by_ifindex (nm_manager_get (), info->master); - if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave) { - g_clear_object (&priv->master); - priv->master = g_object_ref (master); - nm_device_master_add_slave (master, device, FALSE); - nm_device_enslave_slave (master, device, NULL); - } else if (master) { - nm_log_info (LOGD_DEVICE, "(%s): enslaved to non-master-type device %s; ignoring", - nm_device_get_iface (device), - nm_device_get_iface (master)); - } else { - nm_log_warn (LOGD_DEVICE, "(%s): enslaved to unknown device %d %s", - nm_device_get_iface (device), - info->master, - nm_platform_link_get_name (info->master)); - } - } else if (priv->enslaved && !info->master) + if (priv->enslaved && info->master != nm_device_get_ifindex (priv->master)) nm_device_release_one_slave (priv->master, device, FALSE, NM_DEVICE_STATE_REASON_NONE); + if (info->master && !priv->enslaved) { + device_set_master (device, info->master); + if (priv->master) + nm_device_enslave_slave (priv->master, device, NULL); + } if (klass->link_changed) klass->link_changed (device, info); @@ -1252,6 +1275,7 @@ nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure) G_CALLBACK (slave_state_changed), dev); priv->slaves = g_slist_append (priv->slaves, info); } + nm_device_queue_recheck_assume (dev); return TRUE; } @@ -1471,11 +1495,16 @@ nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason) master_status); nm_device_queue_state (dev, new_state, reason); - } else { + } else if (priv->master) { nm_log_info (LOGD_DEVICE, "(%s): released from master %s", nm_device_get_iface (dev), nm_device_get_iface (priv->master)); + } else { + nm_log_info (LOGD_DEVICE, + "(%s): released from master%s", + nm_device_get_iface (dev), + priv->enslaved ? "" : " (was not enslaved)"); } if (priv->enslaved) { @@ -1760,8 +1789,9 @@ nm_device_generate_connection (NMDevice *device) ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG); if ( g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0 && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0 - && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))) { - nm_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP and not slave)", ifname); + && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con)) + && !priv->slaves) { + nm_log_dbg (LOGD_DEVICE, "(%s): ignoring generated connection (no IP and not in master-slave relationship)", ifname); g_object_unref (connection); connection = NULL; } @@ -1971,7 +2001,7 @@ nm_device_emit_recheck_assume (gpointer self) NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); priv->recheck_assume_id = 0; - if (!nm_device_get_act_request (self) && (priv->ip4_config || priv->ip6_config)) + if (!nm_device_get_act_request (self)) g_signal_emit (self, signals[RECHECK_ASSUME], 0); return G_SOURCE_REMOVE; } @@ -6879,7 +6909,10 @@ _set_state_full (NMDevice *device, nm_act_request_get_connection (req), device); } else { - priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + if (nm_device_is_available (device)) + priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + else + priv->dispatcher.post_state = NM_DEVICE_STATE_UNAVAILABLE; priv->dispatcher.post_state_reason = reason; if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN, nm_act_request_get_connection (req), @@ -6915,6 +6948,13 @@ _set_state_full (NMDevice *device, "Activation (%s) failed for connection '%s'", nm_device_get_iface (device), connection ? nm_connection_get_id (connection) : "<unknown>"); + if (req && nm_active_connection_get_assumed (NM_ACTIVE_CONNECTION (req))) { + /* Avoid tearing down assumed connection, assume it's connected */ + nm_device_queue_state (device, + NM_DEVICE_STATE_ACTIVATED, + NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); + break; + } /* Notify any slaves of the unexpected failure */ nm_device_master_release_slaves (device); @@ -7409,6 +7449,7 @@ constructed (GObject *object) { NMDevice *dev = NM_DEVICE (object); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); + int master; nm_device_update_hw_address (dev); @@ -7441,6 +7482,11 @@ constructed (GObject *object) if (priv->ifindex > 0) priv->mtu = nm_platform_link_get_mtu (priv->ifindex); + /* Enslave ourselves */ + master = nm_platform_link_get_master (priv->ifindex); + if (master) + device_set_master (dev, master); + priv->con_provider = nm_connection_provider_get (); g_assert (priv->con_provider); g_signal_connect (priv->con_provider, diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index a2f276934e..f6ceff7116 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -220,6 +220,8 @@ GType nm_device_get_type (void); const char * nm_device_get_path (NMDevice *dev); void nm_device_dbus_export (NMDevice *device); +void nm_device_finish_init (NMDevice *device); + const char * nm_device_get_udi (NMDevice *dev); const char * nm_device_get_iface (NMDevice *dev); int nm_device_get_ifindex (NMDevice *dev); diff --git a/src/nm-manager.c b/src/nm-manager.c index 6be83380eb..0214d47f36 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -1790,6 +1790,7 @@ add_device (NMManager *self, NMDevice *device, gboolean generate_con) nm_device_set_initial_unmanaged_flag (device, NM_UNMANAGED_INTERNAL, sleeping); nm_device_dbus_export (device); + nm_device_finish_init (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/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 1f925a65a3..cf2164d748 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -1818,6 +1818,33 @@ _rtnl_addr_timestamps_equal_fuzzy (guint32 ts1, guint32 ts2) return diff <= 2; } +static gboolean +nm_nl_object_diff (struct nl_object *_a, struct nl_object *_b, ObjectType type) +{ + if (nl_object_diff (_a, _b)) { + /* libnl thinks objects are different*/ + return TRUE; + } + + if (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS) { + struct rtnl_addr *a = (struct rtnl_addr *) _a; + struct rtnl_addr *b = (struct rtnl_addr *) _b; + + /* libnl nl_object_diff() ignores differences in timestamp. Let's care about + * them (if they are large enough). + * + * Note that these valid and preferred timestamps are absolute, after + * _rtnl_addr_hack_lifetimes_rel_to_abs(). */ + if ( !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_preferred_lifetime (a), + rtnl_addr_get_preferred_lifetime (b)) + || !_rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_valid_lifetime (a), + rtnl_addr_get_valid_lifetime (b))) + return TRUE; + } + + return FALSE; +} + /* This function does all the magic to avoid race conditions caused * by concurrent usage of synchronous commands and an asynchronous cache. This * might be a nice future addition to libnl but it requires to do all operations @@ -1928,24 +1955,9 @@ event_notification (struct nl_msg *msg, gpointer user_data) * This also catches notifications for internal addition or change, unless * another action occured very soon after it. */ - if (!nl_object_diff (kernel_object, cached_object)) { - if (type == OBJECT_TYPE_IP4_ADDRESS || type == OBJECT_TYPE_IP6_ADDRESS) { - struct rtnl_addr *c = (struct rtnl_addr *) cached_object; - struct rtnl_addr *k = (struct rtnl_addr *) kernel_object; - - /* libnl nl_object_diff() ignores differences in timestamp. Let's care about - * them (if they are large enough). - * - * Note that these valid and preferred timestamps are absolute, after - * _rtnl_addr_hack_lifetimes_rel_to_abs(). */ - if ( _rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_preferred_lifetime (c), - rtnl_addr_get_preferred_lifetime (k)) - && _rtnl_addr_timestamps_equal_fuzzy (rtnl_addr_get_valid_lifetime (c), - rtnl_addr_get_valid_lifetime (k))) - return NL_OK; - } else - return NL_OK; - } + if (!nm_nl_object_diff (kernel_object, cached_object, type)) + return NL_OK; + /* Handle external change */ nl_cache_remove (cached_object); nle = nl_cache_add (cache, kernel_object); @@ -3818,6 +3830,152 @@ ip6_route_exists (NMPlatform *platform, int ifindex, struct in6_addr network, in /******************************************************************/ +/* Initialize the link cache while ensuring all links are of AF_UNSPEC, + * family (even though the kernel might set AF_BRIDGE for bridges). + * See also: _nl_link_family_unset() */ +static void +init_link_cache (NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + struct nl_object *object = NULL; + + rtnl_link_alloc_cache (priv->nlh, AF_UNSPEC, &priv->link_cache); + + do { + for (object = nl_cache_get_first (priv->link_cache); object; object = nl_cache_get_next (object)) { + if (rtnl_link_get_family ((struct rtnl_link *)object) != AF_UNSPEC) + break; + } + + if (object) { + /* A non-AF_UNSPEC object encoutnered */ + struct nl_object *existing; + + nl_object_get (object); + nl_cache_remove (object); + rtnl_link_set_family ((struct rtnl_link *)object, AF_UNSPEC); + existing = nl_cache_search (priv->link_cache, object); + if (existing) { + nl_object_put (existing); + } else { + nl_cache_add (priv->link_cache, object); + } + nl_object_put (object); + } + } while (object); +} + +/* Calls announce_object with appropriate arguments for all objects + * which are not coherent between old and new caches and deallocates + * the old cache. */ +static gboolean +sync_cache (NMPlatform *platform, struct nl_cache *new, struct nl_cache *old) +{ + struct nl_object *object; + gboolean changed = FALSE; + + if (!old) + return changed; + + for (object = nl_cache_get_first (new); object; object = nl_cache_get_next (object)) { + struct nl_object *cached_object = nm_nl_cache_search (old, object); + + if (cached_object) { + ObjectType type = object_type_from_nl_object (object); + if (nm_nl_object_diff (object, cached_object, type)) { + announce_object (platform, object, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_REASON_EXTERNAL); + changed = TRUE; + } + nl_object_put (cached_object); + } else { + announce_object (platform, object, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_REASON_EXTERNAL); + changed = TRUE; + } + } + for (object = nl_cache_get_first (old); object; object = nl_cache_get_next (object)) { + struct nl_object *cached_object = nm_nl_cache_search (new, object); + if (cached_object) { + nl_object_put (cached_object); + } else { + announce_object (platform, object, NM_PLATFORM_SIGNAL_REMOVED, NM_PLATFORM_REASON_EXTERNAL); + changed = TRUE; + } + } + + nl_cache_free (old); + return changed; +} + +/* The cache should always avoid containing objects not handled by NM, like + * e.g. addresses of the AF_PHONET family. */ +static void +cache_remove_unknown (struct nl_cache *cache) +{ + GPtrArray *objects_to_remove = NULL; + struct nl_object *object; + + for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) { + if (object_type_from_nl_object (object) == OBJECT_TYPE_UNKNOWN) { + if (!objects_to_remove) + objects_to_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put); + nl_object_get (object); + g_ptr_array_add (objects_to_remove, object); + } + } + + if (objects_to_remove) { + guint i; + + for (i = 0; i < objects_to_remove->len; i++) + nl_cache_remove (g_ptr_array_index (objects_to_remove, i)); + + g_ptr_array_free (objects_to_remove, TRUE); + } +} + +/* Creates and populates the netlink object caches. Called upon platform init + * and when we run out of sync (out of buffer space, netlink congestion control). + * Returns TRUE if there were changes, + * Does not guarrantee the caches are coherent with kernel -- we could have missed + * events while synchronizing. Should be re-called when there were changes. */ +static gboolean +sync_platform (NMPlatform *platform) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + struct nl_cache *old_link_cache = priv->link_cache; + struct nl_cache *old_address_cache = priv->address_cache; + struct nl_cache *old_route_cache = priv->route_cache; + struct nl_object *object; + gboolean changed = FALSE; + + /* Allocate new netlink caches */ + init_link_cache (platform); + rtnl_addr_alloc_cache (priv->nlh, &priv->address_cache); + rtnl_route_alloc_cache (priv->nlh, AF_UNSPEC, 0, &priv->route_cache); + g_assert (priv->link_cache && priv->address_cache && priv->route_cache); + + /* Remove all unknown objects from the caches */ + cache_remove_unknown (priv->link_cache); + cache_remove_unknown (priv->address_cache); + cache_remove_unknown (priv->route_cache); + + for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) { + _rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object); + } + + /* Make sure all changes we've missed are announced. */ + if (sync_cache (platform, priv->link_cache, old_link_cache)) + changed = TRUE; + if (sync_cache (platform, priv->address_cache, old_address_cache)) + changed = TRUE; + if (sync_cache (platform, priv->route_cache, old_route_cache)) + changed = TRUE; + + return changed; +} + +/******************************************************************/ + #define EVENT_CONDITIONS ((GIOCondition) (G_IO_IN | G_IO_PRI)) #define ERROR_CONDITIONS ((GIOCondition) (G_IO_ERR | G_IO_NVAL)) #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP)) @@ -3844,7 +4002,8 @@ event_handler (GIOChannel *channel, GIOCondition io_condition, gpointer user_data) { - NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (user_data); + NMPlatform *platform = NM_PLATFORM (user_data); + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); int nle; nle = nl_recvmsgs_default (priv->nlh_event); @@ -3856,6 +4015,10 @@ event_handler (GIOChannel *channel, * and can happen easily. */ debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); break; + case -NLE_NOMEM: + warning ("Too many netlink events. Trying to resynchronize: %s (%d)", nl_geterror (nle), nle); + while (sync_platform (platform)); + break; default: error ("Failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle); break; @@ -4015,33 +4178,6 @@ nm_linux_platform_init (NMLinuxPlatform *platform) { } -/* The cache should always avoid containing objects not handled by NM, like - * e.g. addresses of the AF_PHONET family. */ -static void -cache_remove_unknown (struct nl_cache *cache) -{ - GPtrArray *objects_to_remove = NULL; - struct nl_object *object; - - for (object = nl_cache_get_first (cache); object; object = nl_cache_get_next (object)) { - if (object_type_from_nl_object (object) == OBJECT_TYPE_UNKNOWN) { - if (!objects_to_remove) - objects_to_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) nl_object_put); - nl_object_get (object); - g_ptr_array_add (objects_to_remove, object); - } - } - - if (objects_to_remove) { - guint i; - - for (i = 0; i < objects_to_remove->len; i++) - nl_cache_remove (g_ptr_array_index (objects_to_remove, i)); - - g_ptr_array_free (objects_to_remove, TRUE); - } -} - static gboolean setup (NMPlatform *platform) { @@ -4052,7 +4188,6 @@ setup (NMPlatform *platform) int channel_flags; gboolean status; int nle; - struct nl_object *object; /* Initialize netlink socket for requests */ priv->nlh = setup_socket (FALSE, platform); @@ -4088,19 +4223,7 @@ setup (NMPlatform *platform) (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS), event_handler, platform); - /* Allocate netlink caches */ - rtnl_link_alloc_cache (priv->nlh, AF_UNSPEC, &priv->link_cache); - rtnl_addr_alloc_cache (priv->nlh, &priv->address_cache); - rtnl_route_alloc_cache (priv->nlh, AF_UNSPEC, 0, &priv->route_cache); - g_assert (priv->link_cache && priv->address_cache && priv->route_cache); - - /* Remove all unknown objects from the caches */ - cache_remove_unknown (priv->link_cache); - cache_remove_unknown (priv->address_cache); - cache_remove_unknown (priv->route_cache); - - for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object)) - _rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object); + sync_platform (platform); /* Set up udev monitoring */ priv->udev_client = g_udev_client_new (udev_subsys); |