NetworkManager/src/nm-device-wired.c
Dan Williams 58227d0136 wired: check carrier immediately after bringing device up
Virtual devices that we might create when their slave is started
(like bonds) have a virtual carrier that often isn't set on when
until the device is brought up.  The device is brought up during
creation, but the initial carrier check happens before the device
is up, so the initial carrier state from the constructor isn't
quite accurate in some cases.

Since we want to use virtual interfaces that we create right after
we create them, we want them to be available too, and that usually
requires the carrier to be on.  So recheck the carrier right after
bringing the interface up, so that the carrier state is accurate
immediately after the device is created.
2012-03-01 17:40:17 -06:00

595 lines
16 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2005 - 2012 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "config.h"
#include <glib.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_infiniband.h>
#include <netinet/ether.h>
#include <linux/sockios.h>
#include <linux/version.h>
#include <linux/ethtool.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "nm-device-wired.h"
#include "nm-device-private.h"
#include "nm-dhcp-manager.h"
#include "nm-logging.h"
#include "nm-netlink-monitor.h"
#include "nm-netlink-utils.h"
#include "nm-system.h"
#include "nm-utils.h"
#include "NetworkManagerUtils.h"
G_DEFINE_TYPE (NMDeviceWired, nm_device_wired, NM_TYPE_DEVICE)
#define NM_DEVICE_WIRED_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_WIRED, NMDeviceWiredPrivate))
#define NM_DEVICE_WIRED_LOG_LEVEL(dev) ((nm_device_get_device_type (dev) == NM_DEVICE_TYPE_INFINIBAND) ? LOGD_INFINIBAND : LOGD_ETHER)
typedef struct {
guint8 hw_addr[NM_UTILS_HWADDR_LEN_MAX]; /* Currently set MAC address */
guint hw_addr_type;
guint hw_addr_len;
gboolean carrier;
guint32 speed;
NMNetlinkMonitor * monitor;
gulong link_connected_id;
gulong link_disconnected_id;
guint carrier_action_defer_id;
} NMDeviceWiredPrivate;
/* Returns speed in Mb/s */
static guint32
ethtool_get_speed (NMDeviceWired *self)
{
int fd;
struct ifreq ifr;
struct ethtool_cmd edata = {
.cmd = ETHTOOL_GSET,
};
guint32 speed = 0;
g_return_val_if_fail (self != NULL, 0);
fd = socket (PF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
nm_log_warn (LOGD_HW, "couldn't open control socket.");
return 0;
}
memset (&ifr, 0, sizeof (struct ifreq));
strncpy (ifr.ifr_name, nm_device_get_iface (NM_DEVICE (self)), IFNAMSIZ);
ifr.ifr_data = (char *) &edata;
if (ioctl (fd, SIOCETHTOOL, &ifr) < 0)
goto out;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
speed = edata.speed;
#else
speed = ethtool_cmd_speed (&edata);
#endif
if (speed == G_MAXUINT16 || speed == G_MAXUINT32)
speed = 0;
out:
close (fd);
return speed;
}
static void
set_speed (NMDeviceWired *self, const guint32 speed)
{
NMDeviceWiredPrivate *priv;
g_return_if_fail (NM_IS_DEVICE (self));
priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
if (priv->speed == speed)
return;
priv->speed = speed;
g_object_notify (G_OBJECT (self), "speed");
nm_log_dbg (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): speed is now %d Mb/s",
nm_device_get_iface (NM_DEVICE (self)),
speed);
}
static void
carrier_action_defer_clear (NMDeviceWired *self)
{
NMDeviceWiredPrivate *priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
if (priv->carrier_action_defer_id) {
g_source_remove (priv->carrier_action_defer_id);
priv->carrier_action_defer_id = 0;
}
}
static gboolean
carrier_action_defer_cb (gpointer user_data)
{
NMDeviceWired *self = NM_DEVICE_WIRED (user_data);
NMDeviceWiredPrivate *priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
NMDeviceState state;
priv->carrier_action_defer_id = 0;
state = nm_device_get_state (NM_DEVICE (self));
if (state == NM_DEVICE_STATE_UNAVAILABLE) {
if (priv->carrier)
nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CARRIER);
} else if (state >= NM_DEVICE_STATE_DISCONNECTED) {
if (!priv->carrier)
nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_CARRIER);
}
return FALSE;
}
static void
set_carrier (NMDeviceWired *self,
const gboolean carrier,
const gboolean defer_action)
{
NMDeviceWiredPrivate *priv;
NMDeviceState state;
g_return_if_fail (NM_IS_DEVICE (self));
priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
if (priv->carrier == carrier)
return;
/* Clear any previous deferred action */
carrier_action_defer_clear (self);
priv->carrier = carrier;
g_object_notify (G_OBJECT (self), "carrier");
state = nm_device_get_state (NM_DEVICE (self));
nm_log_info (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): carrier now %s (device state %d%s)",
nm_device_get_iface (NM_DEVICE (self)),
carrier ? "ON" : "OFF",
state,
defer_action ? ", deferring action for 4 seconds" : "");
if (defer_action)
priv->carrier_action_defer_id = g_timeout_add_seconds (4, carrier_action_defer_cb, self);
else
carrier_action_defer_cb (self);
}
static void
carrier_on (NMNetlinkMonitor *monitor,
int idx,
gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceWired *self = NM_DEVICE_WIRED (device);
guint32 caps;
/* Make sure signal is for us */
if (idx == nm_device_get_ifindex (device)) {
/* Ignore spurious netlink messages */
caps = nm_device_get_capabilities (device);
if (!(caps & NM_DEVICE_CAP_CARRIER_DETECT))
return;
set_carrier (self, TRUE, FALSE);
set_speed (self, ethtool_get_speed (self));
}
}
static void
carrier_off (NMNetlinkMonitor *monitor,
int idx,
gpointer user_data)
{
NMDevice *device = NM_DEVICE (user_data);
NMDeviceWired *self = NM_DEVICE_WIRED (device);
guint32 caps;
/* Make sure signal is for us */
if (idx == nm_device_get_ifindex (device)) {
NMDeviceState state;
gboolean defer = FALSE;
/* Ignore spurious netlink messages */
caps = nm_device_get_capabilities (device);
if (!(caps & NM_DEVICE_CAP_CARRIER_DETECT))
return;
/* Defer carrier-off event actions while connected by a few seconds
* so that tripping over a cable, power-cycling a switch, or breaking
* off the RJ45 locking tab isn't so catastrophic.
*/
state = nm_device_get_state (device);
if (state > NM_DEVICE_STATE_DISCONNECTED)
defer = TRUE;
set_carrier (self, FALSE, defer);
}
}
static gboolean
get_carrier_sync (NMDeviceWired *self)
{
NMDeviceWiredPrivate *priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
GError *error = NULL;
guint32 ifflags = 0;
/* Get initial link state */
if (!nm_netlink_monitor_get_flags_sync (priv->monitor,
nm_device_get_ip_ifindex (NM_DEVICE (self)),
&ifflags,
&error)) {
nm_log_warn (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): couldn't get carrier state: (%d) %s",
nm_device_get_ip_iface (NM_DEVICE (self)),
error ? error->code : -1,
(error && error->message) ? error->message : "unknown");
g_clear_error (&error);
}
return !!(ifflags & IFF_LOWER_UP);
}
static GObject*
constructor (GType type,
guint n_construct_params,
GObjectConstructParam *construct_params)
{
GObject *object;
NMDeviceWiredPrivate *priv;
NMDevice *self;
guint32 caps;
object = G_OBJECT_CLASS (nm_device_wired_parent_class)->constructor (type,
n_construct_params,
construct_params);
if (!object)
return NULL;
self = NM_DEVICE (object);
priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
nm_log_dbg (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): kernel ifindex %d",
nm_device_get_iface (NM_DEVICE (self)),
nm_device_get_ifindex (NM_DEVICE (self)));
if (nm_device_get_device_type (self) == NM_DEVICE_TYPE_ETHERNET) {
priv->hw_addr_type = ARPHRD_ETHER;
priv->hw_addr_len = ETH_ALEN;
} else if (nm_device_get_device_type (self) == NM_DEVICE_TYPE_INFINIBAND) {
priv->hw_addr_type = ARPHRD_INFINIBAND;
priv->hw_addr_len = INFINIBAND_ALEN;
} else if (nm_device_get_device_type (self) == NM_DEVICE_TYPE_BOND) {
/* We may not know the hardware address type until a slave is added */
priv->hw_addr_type = ARPHRD_ETHER;
priv->hw_addr_len = ETH_ALEN;
} else
g_assert_not_reached ();
caps = nm_device_get_capabilities (self);
if (caps & NM_DEVICE_CAP_CARRIER_DETECT) {
/* Only listen to netlink for cards that support carrier detect */
priv->monitor = nm_netlink_monitor_get ();
priv->link_connected_id = g_signal_connect (priv->monitor, "carrier-on",
G_CALLBACK (carrier_on),
self);
priv->link_disconnected_id = g_signal_connect (priv->monitor, "carrier-off",
G_CALLBACK (carrier_off),
self);
priv->carrier = get_carrier_sync (NM_DEVICE_WIRED (self));
nm_log_info (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): carrier is %s",
nm_device_get_iface (NM_DEVICE (self)),
priv->carrier ? "ON" : "OFF");
/* Request link state again just in case an error occurred getting the
* initial link state.
*/
nm_netlink_monitor_request_status (priv->monitor);
} else {
nm_log_info (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (NM_DEVICE (self)),
"(%s): driver '%s' does not support carrier detection.",
nm_device_get_iface (self),
nm_device_get_driver (self));
priv->carrier = TRUE;
}
return object;
}
static void
nm_device_wired_init (NMDeviceWired * self)
{
}
static gboolean
real_hw_is_up (NMDevice *device)
{
return nm_system_iface_is_up (nm_device_get_ip_ifindex (device));
}
static gboolean
real_hw_bring_up (NMDevice *dev, gboolean *no_firmware)
{
gboolean success, carrier;
success = nm_system_iface_set_up (nm_device_get_ip_ifindex (dev), TRUE, no_firmware);
if (success) {
carrier = get_carrier_sync (NM_DEVICE_WIRED (dev));
set_carrier (NM_DEVICE_WIRED (dev), carrier, carrier ? FALSE : TRUE);
}
return success;
}
static void
real_hw_take_down (NMDevice *dev)
{
nm_system_iface_set_up (nm_device_get_ip_ifindex (dev), FALSE, NULL);
}
static void
real_update_hw_address (NMDevice *dev)
{
NMDeviceWired *self = NM_DEVICE_WIRED (dev);
NMDeviceWiredPrivate *priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
struct rtnl_link *rtnl;
struct nl_addr *addr;
rtnl = nm_netlink_index_to_rtnl_link (nm_device_get_ip_ifindex (dev));
if (!rtnl) {
nm_log_err (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (dev),
"(%s) failed to read hardware address (error %d)",
nm_device_get_iface (dev), errno);
return;
}
addr = rtnl_link_get_addr (rtnl);
if (!addr) {
nm_log_err (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (dev),
"(%s) no hardware address?",
nm_device_get_iface (dev));
rtnl_link_put (rtnl);
return;
}
if (nl_addr_get_len (addr) != priv->hw_addr_len) {
nm_log_err (LOGD_HW | NM_DEVICE_WIRED_LOG_LEVEL (dev),
"(%s) hardware address is wrong length (expected %d got %d)",
nm_device_get_iface (dev),
priv->hw_addr_len, nl_addr_get_len (addr));
} else {
memcpy (&priv->hw_addr, nl_addr_get_binary_addr (addr),
priv->hw_addr_len);
}
rtnl_link_put (rtnl);
}
static gboolean
real_can_interrupt_activation (NMDevice *dev)
{
NMDeviceWired *self = NM_DEVICE_WIRED (dev);
gboolean interrupt = FALSE;
/* Devices that support carrier detect can interrupt activation
* if the link becomes inactive.
*/
if (nm_device_get_capabilities (dev) & NM_DEVICE_CAP_CARRIER_DETECT) {
if (NM_DEVICE_WIRED_GET_PRIVATE (self)->carrier == FALSE)
interrupt = TRUE;
}
return interrupt;
}
static gboolean
real_is_available (NMDevice *dev)
{
NMDeviceWired *self = NM_DEVICE_WIRED (dev);
/* Can't do anything if there isn't a carrier */
if (!NM_DEVICE_WIRED_GET_PRIVATE (self)->carrier)
return FALSE;
return TRUE;
}
static NMConnection *
connection_match_config (NMDevice *self, const GSList *connections)
{
const GSList *iter;
for (iter = connections; iter; iter = iter->next) {
NMConnection *candidate = NM_CONNECTION (iter->data);
if (!nm_device_match_ip_config (self, candidate))
continue;
return candidate;
}
return NULL;
}
static void
dispose (GObject *object)
{
NMDeviceWired *self = NM_DEVICE_WIRED (object);
NMDeviceWiredPrivate *priv = NM_DEVICE_WIRED_GET_PRIVATE (self);
if (priv->link_connected_id) {
g_signal_handler_disconnect (priv->monitor, priv->link_connected_id);
priv->link_connected_id = 0;
}
if (priv->link_disconnected_id) {
g_signal_handler_disconnect (priv->monitor, priv->link_disconnected_id);
priv->link_disconnected_id = 0;
}
carrier_action_defer_clear (self);
if (priv->monitor) {
g_object_unref (priv->monitor);
priv->monitor = NULL;
}
G_OBJECT_CLASS (nm_device_wired_parent_class)->dispose (object);
}
static void
nm_device_wired_class_init (NMDeviceWiredClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
g_type_class_add_private (object_class, sizeof (NMDeviceWiredPrivate));
/* virtual methods */
object_class->constructor = constructor;
object_class->dispose = dispose;
parent_class->hw_is_up = real_hw_is_up;
parent_class->hw_bring_up = real_hw_bring_up;
parent_class->hw_take_down = real_hw_take_down;
parent_class->can_interrupt_activation = real_can_interrupt_activation;
parent_class->update_hw_address = real_update_hw_address;
parent_class->is_available = real_is_available;
parent_class->connection_match_config = connection_match_config;
}
/**
* nm_device_wired_get_hwaddr:
* @dev: an #NMDeviceWired
*
* Get a device's hardware address
*
* Returns: (transfer none): @dev's hardware address
*/
const guint8 *
nm_device_wired_get_hwaddr (NMDeviceWired *dev)
{
NMDeviceWiredPrivate *priv;
g_return_val_if_fail (dev != NULL, NULL);
priv = NM_DEVICE_WIRED_GET_PRIVATE (dev);
return priv->hw_addr;
}
/**
* nm_device_wired_set_hwaddr:
* @dev: an #NMDeviceWired
* @addr: the new hardware address, @addrlen bytes in length
* @addrlen: the length in bytes of @addr
*
* Sets the device's hardware address.
*/
void
nm_device_wired_set_hwaddr (NMDeviceWired *dev,
const guint8 *addr,
guint addrlen)
{
NMDeviceWiredPrivate *priv;
g_return_if_fail (dev != NULL);
g_return_if_fail (addr != NULL);
priv = NM_DEVICE_WIRED_GET_PRIVATE (dev);
g_return_if_fail (addrlen == priv->hw_addr_len);
memcpy (priv->hw_addr, addr, priv->hw_addr_len);
}
/**
* nm_device_wired_get_hwaddr_type:
* @dev: an #NMDeviceWired
*
* Get the type of a device's hardware address
*
* Returns: the type of @dev's hardware address
*/
int
nm_device_wired_get_hwaddr_type (NMDeviceWired *dev)
{
NMDeviceWiredPrivate *priv;
g_return_val_if_fail (dev != NULL, -1);
priv = NM_DEVICE_WIRED_GET_PRIVATE (dev);
return priv->hw_addr_type;
}
/**
* nm_device_wired_get_carrier:
* @dev: an #NMDeviceWired
*
* Get @dev's carrier status
*
* Return value: @dev's carrier
*/
gboolean
nm_device_wired_get_carrier (NMDeviceWired *dev)
{
NMDeviceWiredPrivate *priv;
g_return_val_if_fail (dev != NULL, FALSE);
priv = NM_DEVICE_WIRED_GET_PRIVATE (dev);
return priv->carrier;
}
/**
* nm_device_wired_get_speed:
* @dev: an #NMDeviceWired
*
* Get @dev's speed
*
* Return value: @dev's speed in Mb/s
*/
guint32
nm_device_wired_get_speed (NMDeviceWired *dev)
{
NMDeviceWiredPrivate *priv;
g_return_val_if_fail (dev != NULL, 0);
priv = NM_DEVICE_WIRED_GET_PRIVATE (dev);
return priv->speed;
}