devices: observe externally-caused master/slave changes (rh #1066706)

If a link's "master" property changes unexpectedly (ie, from outside
NM), update the master and slave NMDevices to reflect it, without
making any changes to them.
This commit is contained in:
Dan Winship 2014-02-25 16:44:01 -05:00
parent 1dbf69cd0a
commit 08e0cfb484
5 changed files with 189 additions and 91 deletions

View file

@ -421,42 +421,56 @@ enslave_slave (NMDevice *device,
success = nm_platform_link_enslave (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
nm_device_bring_up (slave, TRUE, &no_firmware);
}
if (success) {
if (!success)
return FALSE;
nm_log_info (LOGD_BOND, "(%s): enslaved bond slave %s", iface, slave_iface);
g_object_notify (G_OBJECT (device), "slaves");
}
} else
nm_log_info (LOGD_BOND, "(%s): bond slave %s was enslaved", iface, slave_iface);
return success;
g_object_notify (G_OBJECT (device), NM_DEVICE_BOND_SLAVES);
return TRUE;
}
static gboolean
release_slave (NMDevice *device, NMDevice *slave)
release_slave (NMDevice *device,
NMDevice *slave,
gboolean configure)
{
gboolean success, no_firmware = FALSE;
gboolean success = TRUE, no_firmware = FALSE;
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (configure) {
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (success) {
nm_log_info (LOGD_BOND, "(%s): released bond slave %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
if (success) {
nm_log_info (LOGD_BOND, "(%s): released bond slave %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
} else {
nm_log_warn (LOGD_BOND, "(%s): failed to release bond slave %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
} else {
nm_log_warn (LOGD_BOND, "(%s): failed to release bond slave %s",
nm_log_info (LOGD_BOND, "(%s): bond slave %s was released",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
g_object_notify (G_OBJECT (device), "slaves");
/* Kernel bonding code "closes" the slave when releasing it, (which clears
* IFF_UP), so we must bring it back up here to ensure carrier changes and
* other state is noticed by the now-released slave.
*/
if (!nm_device_bring_up (slave, TRUE, &no_firmware)) {
nm_log_warn (LOGD_BOND, "(%s): released bond slave could not be brought up.",
nm_device_get_iface (slave));
if (success)
g_object_notify (G_OBJECT (device), NM_DEVICE_BOND_SLAVES);
if (configure) {
/* Kernel bonding code "closes" the slave when releasing it, (which clears
* IFF_UP), so we must bring it back up here to ensure carrier changes and
* other state is noticed by the now-released slave.
*/
if (!nm_device_bring_up (slave, TRUE, &no_firmware)) {
nm_log_warn (LOGD_BOND, "(%s): released bond slave could not be brought up.",
nm_device_get_iface (slave));
}
}
return success;

View file

@ -391,30 +391,43 @@ enslave_slave (NMDevice *device,
return FALSE;
commit_slave_options (slave, nm_connection_get_setting_bridge_port (connection));
nm_log_info (LOGD_BRIDGE, "(%s): attached bridge port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
} else {
nm_log_info (LOGD_BRIDGE, "(%s): bridge port %s was attached",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
nm_log_info (LOGD_BRIDGE, "(%s): attached bridge port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
g_object_notify (G_OBJECT (device), NM_DEVICE_BRIDGE_SLAVES);
return TRUE;
}
static gboolean
release_slave (NMDevice *device, NMDevice *slave)
release_slave (NMDevice *device,
NMDevice *slave,
gboolean configure)
{
gboolean success;
gboolean success = TRUE;
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (configure) {
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (success) {
nm_log_info (LOGD_BRIDGE, "(%s): detached bridge port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
if (success) {
nm_log_info (LOGD_BRIDGE, "(%s): detached bridge port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
} else {
nm_log_warn (LOGD_BRIDGE, "(%s): failed to detach bridge port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
} else {
nm_log_warn (LOGD_BRIDGE, "(%s): failed to detach bridge port %s",
nm_log_info (LOGD_BRIDGE, "(%s): bridge port %s was detached",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}

View file

@ -691,42 +691,57 @@ enslave_slave (NMDevice *device,
success = nm_platform_link_enslave (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
nm_device_bring_up (slave, TRUE, &no_firmware);
}
if (success) {
if (!success)
return FALSE;
nm_log_info (LOGD_TEAM, "(%s): enslaved team port %s", iface, slave_iface);
g_object_notify (G_OBJECT (device), "slaves");
}
} else
nm_log_info (LOGD_TEAM, "(%s): team port %s was enslaved", iface, slave_iface);
return success;
g_object_notify (G_OBJECT (device), NM_DEVICE_TEAM_SLAVES);
return TRUE;
}
static gboolean
release_slave (NMDevice *device, NMDevice *slave)
release_slave (NMDevice *device,
NMDevice *slave,
gboolean configure)
{
gboolean success, no_firmware = FALSE;
gboolean success = TRUE, no_firmware = FALSE;
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (configure) {
success = nm_platform_link_release (nm_device_get_ip_ifindex (device),
nm_device_get_ip_ifindex (slave));
if (success) {
nm_log_info (LOGD_TEAM, "(%s): released team port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
if (success) {
nm_log_info (LOGD_TEAM, "(%s): released team port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
} else {
nm_log_warn (LOGD_TEAM, "(%s): failed to release team port %s",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
} else {
nm_log_warn (LOGD_TEAM, "(%s): failed to release team port %s",
nm_log_info (LOGD_TEAM, "(%s): team port %s was released",
nm_device_get_ip_iface (device),
nm_device_get_ip_iface (slave));
}
g_object_notify (G_OBJECT (device), "slaves");
/* Kernel team code "closes" the port when releasing it, (which clears
* IFF_UP), so we must bring it back up here to ensure carrier changes and
* other state is noticed by the now-released port.
*/
if (!nm_device_bring_up (slave, TRUE, &no_firmware)) {
nm_log_warn (LOGD_TEAM, "(%s): released team port could not be brought up.",
nm_device_get_iface (slave));
if (success)
g_object_notify (G_OBJECT (device), NM_DEVICE_TEAM_SLAVES);
if (configure) {
/* Kernel team code "closes" the port when releasing it, (which clears
* IFF_UP), so we must bring it back up here to ensure carrier changes and
* other state is noticed by the now-released port.
*/
if (!nm_device_bring_up (slave, TRUE, &no_firmware)) {
nm_log_warn (LOGD_TEAM, "(%s): released team port could not be brought up.",
nm_device_get_iface (slave));
}
}
return success;

View file

@ -896,7 +896,7 @@ free_slave_info (SlaveInfo *info)
* nm_device_enslave_slave:
* @dev: the master device
* @slave: the slave device to enslave
* @connection: the slave device's connection
* @connection: (allow-none): the slave device's connection
*
* If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
* etc) then this function enslaves @slave.
@ -909,20 +909,27 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio
{
SlaveInfo *info;
gboolean success = FALSE;
gboolean configure;
g_return_val_if_fail (dev != NULL, FALSE);
g_return_val_if_fail (slave != NULL, FALSE);
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
info = find_slave_info (dev, slave);
if (!info)
return FALSE;
g_warn_if_fail (info->enslaved == FALSE);
success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, info->configure);
if (info->enslaved)
success = TRUE;
else {
configure = (info->configure && connection != NULL);
if (configure)
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
success = NM_DEVICE_GET_CLASS (dev)->enslave_slave (dev, slave, connection, configure);
info->enslaved = success;
}
info->enslaved = success;
nm_device_slave_notify_enslave (info->slave, success);
/* Ensure the device's hardware address is up-to-date; it often changes
@ -949,15 +956,17 @@ nm_device_enslave_slave (NMDevice *dev, NMDevice *slave, NMConnection *connectio
* nm_device_release_one_slave:
* @dev: the master device
* @slave: the slave device to release
* @configure: whether @dev needs to actually release @slave
*
* If @dev is capable of enslaving other devices (ie it's a bridge, bond, team,
* etc) then this function releases the previously enslaved @slave.
* etc) then this function releases the previously enslaved @slave and/or
* updates the state of @dev and @slave to reflect its release.
*
* Returns: %TRUE on success, %FALSE on failure, if this device cannot enslave
* other devices, or if @slave was never enslaved.
*/
static gboolean
nm_device_release_one_slave (NMDevice *dev, NMDevice *slave)
nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean configure)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
SlaveInfo *info;
@ -973,13 +982,15 @@ nm_device_release_one_slave (NMDevice *dev, NMDevice *slave)
priv->slaves = g_slist_remove (priv->slaves, info);
if (info->enslaved) {
success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave);
success = NM_DEVICE_GET_CLASS (dev)->release_slave (dev, slave, configure);
/* The release_slave() implementation logs success/failure (in the
* correct device-specific log domain), so we don't have to do anything.
*/
}
if (priv->state == NM_DEVICE_STATE_FAILED)
if (!configure)
reason = NM_DEVICE_STATE_REASON_NONE;
else if (priv->state == NM_DEVICE_STATE_FAILED)
reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
else
reason = priv->state_reason;
@ -1208,6 +1219,29 @@ device_link_changed (NMDevice *device, NMPlatformLink *info)
nm_device_emit_recheck_auto_activate (device);
}
/* 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)
nm_device_release_one_slave (priv->master, device, FALSE);
if (klass->link_changed)
klass->link_changed (device, info);
@ -1330,8 +1364,6 @@ slave_state_changed (NMDevice *slave,
slave_new_state,
state_to_string (slave_new_state));
g_assert (priv->state > NM_DEVICE_STATE_DISCONNECTED);
/* Don't try to enslave slaves until the master is ready */
if (priv->state < NM_DEVICE_STATE_CONFIG)
return;
@ -1347,7 +1379,7 @@ slave_state_changed (NMDevice *slave,
}
if (release) {
nm_device_release_one_slave (self, slave);
nm_device_release_one_slave (self, slave, TRUE);
/* Bridge/bond/team 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",
@ -1376,9 +1408,11 @@ nm_device_master_add_slave (NMDevice *dev, NMDevice *slave, gboolean configure)
g_return_val_if_fail (dev != NULL, FALSE);
g_return_val_if_fail (slave != NULL, FALSE);
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
g_return_val_if_fail (NM_DEVICE_GET_CLASS (dev)->enslave_slave != NULL, FALSE);
if (configure)
g_return_val_if_fail (nm_device_get_state (slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
if (!find_slave_info (dev, slave)) {
info = g_malloc0 (sizeof (SlaveInfo));
info->slave = g_object_ref (slave);
@ -1493,10 +1527,14 @@ nm_device_master_release_slaves (NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
/* Don't release the slaves if this connection doesn't belong to NM. */
if (nm_device_uses_generated_connection (self))
return;
while (priv->slaves) {
SlaveInfo *info = priv->slaves->data;
nm_device_release_one_slave (self, info->slave);
nm_device_release_one_slave (self, info->slave, TRUE);
}
}
@ -1535,31 +1573,42 @@ nm_device_slave_notify_enslave (NMDevice *dev, gboolean success)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (dev);
NMConnection *connection = nm_device_get_connection (dev);
gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG);
g_assert (priv->master);
g_warn_if_fail (priv->enslaved == FALSE);
g_warn_if_fail (priv->state == NM_DEVICE_STATE_IP_CONFIG);
if (success) {
nm_log_info (LOGD_DEVICE,
"Activation (%s) connection '%s' enslaved, continuing activation",
nm_device_get_iface (dev),
nm_connection_get_id (connection));
if (!priv->enslaved) {
if (success) {
if (activating) {
nm_log_info (LOGD_DEVICE,
"Activation (%s) connection '%s' enslaved, continuing activation",
nm_device_get_iface (dev),
nm_connection_get_id (connection));
} else {
nm_log_info (LOGD_DEVICE,
"(%s): enslaved to %s",
nm_device_get_iface (dev),
nm_device_get_iface (priv->master));
}
priv->enslaved = TRUE;
g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
} else {
nm_log_warn (LOGD_DEVICE,
"Activation (%s) connection '%s' could not be enslaved",
nm_device_get_iface (dev),
nm_connection_get_id (connection));
priv->enslaved = TRUE;
g_object_notify (G_OBJECT (dev), NM_DEVICE_MASTER);
} else if (activating) {
nm_log_warn (LOGD_DEVICE,
"Activation (%s) connection '%s' could not be enslaved",
nm_device_get_iface (dev),
nm_connection_get_id (connection));
}
}
priv->ip4_state = IP_DONE;
priv->ip6_state = IP_DONE;
nm_device_queue_state (dev,
success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NONE);
if (activating) {
priv->ip4_state = IP_DONE;
priv->ip6_state = IP_DONE;
nm_device_queue_state (dev,
success ? NM_DEVICE_STATE_SECONDARIES : NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NONE);
} else
nm_device_queue_recheck_assume (dev);
}
/**
@ -1577,7 +1626,8 @@ nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason)
NMDeviceState new_state;
const char *master_status;
if ( priv->state > NM_DEVICE_STATE_DISCONNECTED
if ( reason != NM_DEVICE_STATE_REASON_NONE
&& priv->state > NM_DEVICE_STATE_DISCONNECTED
&& priv->state <= NM_DEVICE_STATE_ACTIVATED) {
if (reason == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) {
new_state = NM_DEVICE_STATE_FAILED;
@ -1597,6 +1647,11 @@ nm_device_slave_notify_release (NMDevice *dev, NMDeviceStateReason reason)
master_status);
nm_device_queue_state (dev, new_state, reason);
} else {
nm_log_info (LOGD_DEVICE,
"(%s): released from master %s",
nm_device_get_iface (dev),
nm_device_get_iface (priv->master));
}
if (priv->enslaved) {

View file

@ -198,7 +198,8 @@ typedef struct {
gboolean configure);
gboolean (* release_slave) (NMDevice *self,
NMDevice *slave);
NMDevice *slave,
gboolean configure);
gboolean (* have_any_ready_slaves) (NMDevice *self,
const GSList *slaves);