diff options
Diffstat (limited to 'src/nm-device.c')
| -rw-r--r-- | src/nm-device.c | 404 |
1 files changed, 376 insertions, 28 deletions
diff --git a/src/nm-device.c b/src/nm-device.c index c4cfbce0..b0810680 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -126,6 +126,7 @@ enum { PROP_RFKILL_TYPE, PROP_IFINDEX, PROP_AVAILABLE_CONNECTIONS, + PROP_IS_MASTER, LAST_PROP }; @@ -250,8 +251,9 @@ typedef struct { NMDevice * master; gboolean enslaved; - /* list of SlaveInfo for bond/bridge master */ - GSList * slaves; + /* slave management */ + gboolean is_master; + GSList * slaves; /* list of SlaveInfo */ NMConnectionProvider *con_provider; @@ -498,6 +500,14 @@ nm_device_hw_is_up (NMDevice *self) return TRUE; } +static gboolean +hw_is_up (NMDevice *device) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + return ifindex > 0 ? nm_system_iface_is_up (ifindex) : TRUE; +} + static guint32 get_generic_capabilities (NMDevice *dev) { @@ -603,6 +613,18 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface) g_free (old_ip_iface); } +const guint8 * +nm_device_get_hw_address (NMDevice *dev, guint *out_len) +{ + g_return_val_if_fail (dev != NULL, NULL); + g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); + g_return_val_if_fail (out_len != NULL, NULL); + g_return_val_if_fail (*out_len == 0, NULL); + + if (NM_DEVICE_GET_CLASS (dev)->get_hw_address) + return NM_DEVICE_GET_CLASS (dev)->get_hw_address (dev, out_len); + return NULL; +} /* * Get/set functions for driver @@ -830,6 +852,25 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio info->enslaved = TRUE; nm_device_slave_notify_enslaved (info->slave, TRUE, FALSE); } + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + if (NM_DEVICE_GET_CLASS (dev)->update_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_hw_address (dev); + + /* Restart IP configuration if we're waiting for slaves. Do this + * after updating the hardware address as IP config may need the + * new address. + */ + if (success) { + if (NM_DEVICE_GET_PRIVATE (dev)->ip4_state == IP_WAIT) + nm_device_activate_stage3_ip4_start (dev); + + if (NM_DEVICE_GET_PRIVATE (dev)->ip6_state == IP_WAIT) + nm_device_activate_stage3_ip6_start (dev); + } + return success; } @@ -867,6 +908,13 @@ nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean failed) priv->slaves = g_slist_remove (priv->slaves, info); free_slave_info (info); + + /* Ensure the device's hardware address is up-to-date; it often changes + * when slaves change. + */ + if (NM_DEVICE_GET_CLASS (dev)->update_hw_address) + NM_DEVICE_GET_CLASS (dev)->update_hw_address (dev); + return success; } @@ -907,8 +955,10 @@ slave_state_changed (NMDevice *slave, if (release) { nm_device_release_one_slave (self, slave, FALSE); - if (priv->slaves == NULL) { - /* FIXME: all slaves gone; do something? */ + /* Bridge/bond interfaces are left up until manually deactivated */ + if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED) { + nm_log_dbg (LOGD_DEVICE, "(%s): last slave removed; remaining activated", + nm_device_get_iface (self)); } } } @@ -964,6 +1014,40 @@ nm_device_master_get_slaves (NMDevice *dev) return slaves; } +/** + * nm_device_master_get_slave_by_ifindex: + * @dev: the master device + * @ifindex: the slave's interface index + * + * Returns: the slave with the given @ifindex of which @device is the master, + * or %NULL if no device with @ifinidex is a slave of @device. + */ +NMDevice * +nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex) +{ + GSList *iter; + + for (iter = NM_DEVICE_GET_PRIVATE (dev)->slaves; iter; iter = g_slist_next (iter)) { + SlaveInfo *info = iter->data; + + if (nm_device_get_ip_ifindex (info->slave) == ifindex) + return info->slave; + } + return NULL; +} + +/** + * nm_device_is_master: + * @dev: the device + * + * Returns: whether @dev can enslave other devices (eg, bridge or bond) + */ +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, gboolean failed) @@ -997,9 +1081,8 @@ nm_device_slave_notify_enslaved (NMDevice *dev, NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev); NMConnection *connection = nm_device_get_connection (dev); - g_assert (priv->master); - if (enslaved) { + g_assert (priv->master); g_warn_if_fail (priv->enslaved == FALSE); g_warn_if_fail (priv->state == NM_DEVICE_STATE_IP_CONFIG); @@ -1019,25 +1102,41 @@ nm_device_slave_notify_enslaved (NMDevice *dev, NMDeviceState new_state = NM_DEVICE_STATE_DISCONNECTED; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE; - if (master_failed) { - new_state = NM_DEVICE_STATE_FAILED; - reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + if ( priv->state > NM_DEVICE_STATE_DISCONNECTED + && priv->state <= NM_DEVICE_STATE_ACTIVATED) { + if (master_failed) { + new_state = NM_DEVICE_STATE_FAILED; + reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; + + nm_log_warn (LOGD_DEVICE, + "Activation (%s) connection '%s' master failed", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } else { + nm_log_dbg (LOGD_DEVICE, + "Activation (%s) connection '%s' master deactivated", + nm_device_get_iface (dev), + nm_connection_get_id (connection)); + } - nm_log_warn (LOGD_DEVICE, - "Activation (%s) connection '%s' master failed", - nm_device_get_iface (dev), - nm_connection_get_id (connection)); - } else { - nm_log_dbg (LOGD_DEVICE, - "Activation (%s) connection '%s' master deactivated", - nm_device_get_iface (dev), - nm_connection_get_id (connection)); + nm_device_queue_state (dev, new_state, reason); } - - nm_device_queue_state (dev, new_state, reason); } } +/** + * nm_device_get_enslaved: + * @device: the #NMDevice + * + * Returns: %TRUE if the device is enslaved to a master device (eg bridge or + * bond), %FALSE if not + */ +gboolean +nm_device_get_enslaved (NMDevice *device) +{ + return NM_DEVICE_GET_PRIVATE (device)->enslaved; +} + /* * nm_device_get_act_request * @@ -1141,6 +1240,8 @@ nm_device_get_best_auto_connection (NMDevice *dev, char **specific_object) { guint32 caps; + GSList *iter, *available_conns; + NMConnection *best_connection; g_return_val_if_fail (NM_IS_DEVICE (dev), NULL); g_return_val_if_fail (specific_object != NULL, NULL); @@ -1154,7 +1255,24 @@ nm_device_get_best_auto_connection (NMDevice *dev, if (!NM_DEVICE_GET_CLASS (dev)->get_best_auto_connection) return NULL; - return NM_DEVICE_GET_CLASS (dev)->get_best_auto_connection (dev, connections, specific_object); + available_conns = NULL; + for (iter = connections; iter; iter = iter->next) { + NMConnection *connection = NM_CONNECTION (iter->data); + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + if (nm_setting_connection_get_autoconnect (s_con)) + available_conns = g_slist_prepend (available_conns, connection); + } + + if (!available_conns) + return NULL; + + best_connection = NM_DEVICE_GET_CLASS (dev)->get_best_auto_connection (dev, available_conns, specific_object); + + g_slist_free (available_conns); + return best_connection; } gboolean @@ -1903,6 +2021,9 @@ dhcp4_start (NMDevice *self, NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMSettingIP4Config *s_ip4; guint8 *anycast = NULL; + GByteArray *tmp = NULL; + guint hwaddr_len = 0; + const guint8 *hwaddr; s_ip4 = nm_connection_get_setting_ip4_config (connection); @@ -1914,14 +2035,25 @@ dhcp4_start (NMDevice *self, g_object_unref (priv->dhcp4_config); priv->dhcp4_config = nm_dhcp4_config_new (); + hwaddr = nm_device_get_hw_address (self, &hwaddr_len); + if (hwaddr) { + tmp = g_byte_array_sized_new (hwaddr_len); + g_byte_array_append (tmp, hwaddr, hwaddr_len); + } + /* Begin DHCP on the interface */ g_warn_if_fail (priv->dhcp4_client == NULL); priv->dhcp4_client = nm_dhcp_manager_start_ip4 (priv->dhcp_manager, nm_device_get_ip_iface (self), + tmp, nm_connection_get_uuid (connection), s_ip4, priv->dhcp_timeout, anycast); + + if (tmp) + g_byte_array_free (tmp, TRUE); + if (!priv->dhcp4_client) { *reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED; return NM_ACT_STAGE_RETURN_FAILURE; @@ -2026,6 +2158,35 @@ shared4_new_config (NMDevice *self, NMDeviceStateReason *reason) /*********************************************/ +static gboolean +have_any_ready_slaves (NMDevice *device, const GSList *slaves) +{ + const GSList *iter; + + /* Any enslaved slave is "ready" in the generic case as it's + * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2 + * properties set up. + */ + for (iter = slaves; iter; iter = g_slist_next (iter)) { + if (nm_device_get_enslaved (iter->data)) + return TRUE; + } + return FALSE; +} + +static gboolean +ip4_requires_slaves (NMConnection *connection) +{ + NMSettingIP4Config *s_ip4; + const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + + s_ip4 = nm_connection_get_setting_ip4_config (connection); + if (s_ip4) + method = nm_setting_ip4_config_get_method (s_ip4); + + return g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0; +} + static NMActStageReturn act_stage3_ip4_config_start (NMDevice *self, NMIP4Config **out_config, @@ -2036,12 +2197,30 @@ act_stage3_ip4_config_start (NMDevice *self, NMSettingIP4Config *s_ip4; NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + GSList *slaves; + gboolean ready_slaves; g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); connection = nm_device_get_connection (self); g_assert (connection); + if (priv->is_master && ip4_requires_slaves (connection)) { + /* If the master has no ready slaves, and depends on slaves for + * a successful IPv4 attempt, then postpone IPv4 addressing. + */ + slaves = nm_device_master_get_slaves (self); + ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves); + g_slist_free (slaves); + + if (ready_slaves == FALSE) { + nm_log_info (LOGD_DEVICE | LOGD_IP4, + "(%s): IPv4 config waiting until slaves are ready", + nm_device_get_ip_iface (self)); + return NM_ACT_STAGE_RETURN_WAIT; + } + } + /* If we did not receive IP4 configuration information, default to DHCP */ s_ip4 = nm_connection_get_setting_ip4_config (connection); if (s_ip4) @@ -2286,7 +2465,9 @@ dhcp6_start (NMDevice *self, NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; guint8 *anycast = NULL; - const char *ip_iface; + GByteArray *tmp = NULL; + guint hwaddr_len = 0; + const guint8 *hwaddr; if (!connection) { connection = nm_device_get_connection (self); @@ -2309,14 +2490,23 @@ dhcp6_start (NMDevice *self, priv->dhcp6_ip6_config = NULL; } - ip_iface = nm_device_get_ip_iface (self); + hwaddr = nm_device_get_hw_address (self, &hwaddr_len); + if (hwaddr) { + tmp = g_byte_array_sized_new (hwaddr_len); + g_byte_array_append (tmp, hwaddr, hwaddr_len); + } + priv->dhcp6_client = nm_dhcp_manager_start_ip6 (priv->dhcp_manager, - ip_iface, + nm_device_get_ip_iface (self), + tmp, nm_connection_get_uuid (connection), nm_connection_get_setting_ip6_config (connection), priv->dhcp_timeout, anycast, (dhcp_opt == IP6_DHCP_OPT_OTHERCONF) ? TRUE : FALSE); + if (tmp) + g_byte_array_free (tmp, TRUE); + if (priv->dhcp6_client) { priv->dhcp6_state_sigid = g_signal_connect (priv->dhcp6_client, "state-changed", @@ -2448,6 +2638,8 @@ addrconf6_start (NMDevice *self) NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMConnection *connection; gboolean success; + const guint8 *hwaddr; + guint hwaddr_len = 0; connection = nm_device_get_connection (self); g_assert (connection); @@ -2470,8 +2662,12 @@ addrconf6_start (NMDevice *self) self); } + hwaddr = nm_device_get_hw_address (self, &hwaddr_len); + g_warn_if_fail (hwaddr != NULL); success = nm_ip6_manager_prepare_interface (priv->ip6_manager, nm_device_get_ip_ifindex (self), + hwaddr, + hwaddr_len, nm_connection_get_setting_ip6_config (connection), priv->ip6_accept_ra_path); if (success) { @@ -2555,6 +2751,25 @@ done: return ret; } +static gboolean +ip6_requires_slaves (NMConnection *connection) +{ + NMSettingIP6Config *s_ip6; + const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + + s_ip6 = nm_connection_get_setting_ip6_config (connection); + if (s_ip6) + method = nm_setting_ip6_config_get_method (s_ip6); + + /* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves) + * to complete addressing. SLAAC and DHCP obviously need a peer to + * provide a prefix, while Link-Local must perform DAD on the local link. + */ + return g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 + || g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0 + || g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0; +} + static NMActStageReturn act_stage3_ip6_config_start (NMDevice *self, NMIP6Config **out_config, @@ -2568,6 +2783,8 @@ act_stage3_ip6_config_start (NMDevice *self, int conf_use_tempaddr; NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; const char *ip6_privacy_str = "0\n"; + GSList *slaves; + gboolean ready_slaves; g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); @@ -2576,6 +2793,22 @@ act_stage3_ip6_config_start (NMDevice *self, ip_iface = nm_device_get_ip_iface (self); + if (priv->is_master && ip6_requires_slaves (connection)) { + /* If the master has no ready slaves, and depends on slaves for + * a successful IPv6 attempt, then postpone IPv6 addressing. + */ + slaves = nm_device_master_get_slaves (self); + ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves); + g_slist_free (slaves); + + if (ready_slaves == FALSE) { + nm_log_info (LOGD_DEVICE | LOGD_IP6, + "(%s): IPv6 config waiting until slaves are ready", + ip_iface); + return NM_ACT_STAGE_RETURN_WAIT; + } + } + update_accept_ra_save (self); update_ip6_privacy_save (self); @@ -3247,6 +3480,13 @@ nm_device_activate_ip4_state_in_conf (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF; } +gboolean +nm_device_activate_ip4_state_in_wait (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT; +} + static gboolean nm_device_activate_ip6_config_commit (gpointer user_data) { @@ -3339,6 +3579,13 @@ nm_device_activate_ip6_state_in_conf (NMDevice *self) return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF; } +gboolean +nm_device_activate_ip6_state_in_wait (NMDevice *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT; +} + static void clear_act_request (NMDevice *self) { @@ -3553,10 +3800,10 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason) NM_DEVICE_GET_CLASS (self)->deactivate (self); /* master: release slaves */ - g_clear_object (&priv->master); nm_device_master_release_slaves (self, FALSE); /* slave: mark no longer enslaved */ + g_clear_object (&priv->master); priv->enslaved = FALSE; /* Tear down an existing activation request */ @@ -3919,6 +4166,14 @@ out: return TRUE; } +static gboolean +hw_bring_up (NMDevice *device, gboolean *no_firmware) +{ + int ifindex = nm_device_get_ip_ifindex (device); + + return ifindex > 0 ? nm_system_iface_set_up (ifindex, TRUE, no_firmware) : TRUE; +} + void nm_device_hw_take_down (NMDevice *self, gboolean block) { @@ -3939,6 +4194,12 @@ nm_device_hw_take_down (NMDevice *self, gboolean block) g_usleep (200); } +static void +hw_take_down (NMDevice *device) +{ + nm_system_iface_set_up (nm_device_get_ip_ifindex (device), FALSE, NULL); +} + static gboolean nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware) { @@ -4177,6 +4438,9 @@ set_property (GObject *object, guint prop_id, 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -4299,6 +4563,9 @@ get_property (GObject *object, guint prop_id, g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection))); g_value_take_boxed (value, array); break; + case PROP_IS_MASTER: + g_value_set_boolean (value, priv->is_master); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -4329,7 +4596,12 @@ nm_device_class_init (NMDeviceClass *klass) 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->check_connection_available = check_connection_available; + klass->hw_is_up = hw_is_up; + klass->hw_bring_up = hw_bring_up; + klass->hw_take_down = hw_take_down; /* Properties */ g_object_class_install_property @@ -4517,6 +4789,14 @@ nm_device_class_init (NMDeviceClass *klass) DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, 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)); + /* Signals */ signals[STATE_CHANGED] = g_signal_new ("state-changed", @@ -5015,9 +5295,9 @@ nm_device_set_managed (NMDevice *device, return; priv->managed = managed; - nm_log_info (LOGD_DEVICE, "(%s): now %s", - nm_device_get_iface (device), - managed ? "managed" : "unmanaged"); + nm_log_dbg (LOGD_DEVICE, "(%s): now %s", + nm_device_get_iface (device), + managed ? "managed" : "unmanaged"); g_object_notify (G_OBJECT (device), NM_DEVICE_MANAGED); @@ -5333,3 +5613,71 @@ nm_device_supports_vlans (NMDevice *device) /* At the moment, NM's VLAN code assumes all VLANs are over ethernet. */ return NM_IS_DEVICE_ETHERNET (device); } + +/** + * nm_device_read_hwaddr: + * @dev: the device + * @buf: an allocated buffer which on success holds the device's hardware + * address + * @buf_len: the size of @buf + * @out_changed: on success, %TRUE if the contents of @buf are different from + * the original contents of @buf when this function was called + * + * Reads the device's hardware address from the kernel and copies it into + * @buf, returning the size of the data copied into @buf. On failure + * @buf is not modified. + * + * Returns: the size of the hardware address in bytes on success, 0 on failure + */ +gsize +nm_device_read_hwaddr (NMDevice *dev, + guint8 *buf, + gsize buf_len, + gboolean *out_changed) +{ + struct rtnl_link *rtnl; + struct nl_addr *addr; + int idx; + gsize addrlen = 0; + const guint8 *binaddr; + + g_return_val_if_fail (dev != NULL, 0); + g_return_val_if_fail (buf != NULL, 0); + g_return_val_if_fail (buf_len > 0, 0); + + idx = nm_device_get_ip_ifindex (dev); + g_return_val_if_fail (idx > 0, 0); + + rtnl = nm_netlink_index_to_rtnl_link (idx); + if (!rtnl) { + nm_log_err (LOGD_HW | LOGD_DEVICE, + "(%s): failed to read hardware address (error %d)", + nm_device_get_iface (dev), errno); + return 0; + } + + addr = rtnl_link_get_addr (rtnl); + if (!addr) { + nm_log_err (LOGD_HW | LOGD_DEVICE, + "(%s): no hardware address?", + nm_device_get_iface (dev)); + goto out; + } + + addrlen = nl_addr_get_len (addr); + if (addrlen > buf_len) { + nm_log_err (LOGD_HW | LOGD_DEVICE, + "(%s): hardware address is wrong length (got %zd max %zd)", + nm_device_get_iface (dev), addrlen, buf_len); + addrlen = 0; + } else { + binaddr = nl_addr_get_binary_addr (addr); + if (out_changed) + *out_changed = memcmp (buf, binaddr, addrlen) ? TRUE : FALSE; + memcpy (buf, binaddr, addrlen); + } + +out: + rtnl_link_put (rtnl); + return addrlen; +} |