summary refs log tree commit diff
path: root/src/nm-device-wifi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-device-wifi.c')
-rw-r--r--src/nm-device-wifi.c214
1 files changed, 185 insertions, 29 deletions
diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c
index 36be9e3e..b09d1125 100644
--- a/src/nm-device-wifi.c
+++ b/src/nm-device-wifi.c
@@ -30,6 +30,9 @@
 #include <sys/wait.h>
 #include <signal.h>
 #include <unistd.h>
+#include <linux/sockios.h>
+#include <linux/ethtool.h>
+#include <sys/ioctl.h>
 
 #include "nm-glib-compat.h"
 #include "nm-device.h"
@@ -78,6 +81,7 @@ G_DEFINE_TYPE_EXTENDED (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE, 0,
 enum {
 	PROP_0,
 	PROP_HW_ADDRESS,
+	PROP_PERM_HW_ADDRESS,
 	PROP_MODE,
 	PROP_BITRATE,
 	PROP_ACTIVE_ACCESS_POINT,
@@ -142,7 +146,9 @@ typedef struct Supplicant {
 struct _NMDeviceWifiPrivate {
 	gboolean          disposed;
 
-	struct ether_addr hw_addr;
+	guint8            hw_addr[ETH_ALEN];         /* Currently set MAC address */
+	guint8            perm_hw_addr[ETH_ALEN];    /* Permanent MAC address */
+	guint8            initial_hw_addr[ETH_ALEN]; /* Initial MAC address (as seen when NM starts) */
 
 	/* Legacy rfkill for ipw2x00; will be fixed with 2.6.33 kernel */
 	char *            ipw_rfkill_path;
@@ -1125,6 +1131,62 @@ real_bring_up (NMDevice *dev)
 }
 
 static void
+_update_hw_addr (NMDeviceWifi *self, const guint8 *addr)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	g_return_if_fail (addr != NULL);
+
+	if (memcmp (&priv->hw_addr, addr, ETH_ALEN)) {
+		memcpy (&priv->hw_addr, addr, ETH_ALEN);
+		g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_HW_ADDRESS);
+	}
+}
+
+static gboolean
+_set_hw_addr (NMDeviceWifi *self, const guint8 *addr, const char *detail)
+{
+	NMDevice *dev = NM_DEVICE (self);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	const char *iface;
+	char *mac_str = NULL;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (addr != NULL, FALSE);
+
+	iface = nm_device_get_iface (dev);
+
+	mac_str = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+	                           addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+
+	/* Do nothing if current MAC is same */
+	if (!memcmp (&priv->hw_addr, addr, ETH_ALEN)) {
+		nm_log_dbg (LOGD_DEVICE | LOGD_ETHER, "(%s): no MAC address change needed",
+		            iface, detail, mac_str);
+		g_free (mac_str);
+		return TRUE;
+	}
+
+	/* Can't change MAC address while device is up */
+	real_hw_take_down (dev);
+
+	success = nm_system_device_set_mac (iface, (struct ether_addr *) addr);
+	if (success) {
+		/* MAC address succesfully changed; update the current MAC to match */
+		_update_hw_addr (self, addr);
+		nm_log_info (LOGD_DEVICE | LOGD_ETHER, "(%s): %s MAC address to %s",
+		             iface, detail, mac_str);
+	} else {
+		nm_log_warn (LOGD_DEVICE | LOGD_ETHER, "(%s): failed to %s MAC address to %s",
+		             iface, detail, mac_str);
+	}
+	real_hw_bring_up (dev, NULL);
+	g_free (mac_str);
+
+	return success;
+}
+
+static void
 access_point_removed (NMDeviceWifi *device, NMAccessPoint *ap)
 {
 	g_signal_emit (device, signals[ACCESS_POINT_REMOVED], 0, ap);
@@ -1188,6 +1250,9 @@ real_deactivate_quickly (NMDevice *dev)
 		priv->ap_list = g_slist_remove (priv->ap_list, orig_ap);
 		g_object_unref (orig_ap);
 	}
+
+	/* Reset MAC address back to initial address */
+	_set_hw_addr (self, priv->initial_hw_addr, "reset");
 }
 
 static void
@@ -1230,7 +1295,7 @@ real_check_connection_compatible (NMDevice *device,
 	}
 
 	mac = nm_setting_wireless_get_mac_address (s_wireless);
-	if (mac && memcmp (mac->data, &(priv->hw_addr.ether_addr_octet), ETH_ALEN)) {
+	if (mac && memcmp (mac->data, &priv->perm_hw_addr, ETH_ALEN)) {
 		g_set_error (error,
 		             NM_WIFI_ERROR, NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE,
 		             "The connection's MAC address did not match this device.");
@@ -1305,7 +1370,7 @@ real_get_best_auto_connection (NMDevice *dev,
 			continue;
 
 		mac = nm_setting_wireless_get_mac_address (s_wireless);
-		if (mac && memcmp (mac->data, priv->hw_addr.ether_addr_octet, ETH_ALEN))
+		if (mac && memcmp (mac->data, &priv->perm_hw_addr, ETH_ALEN))
 				continue;
 
 		/* Use the connection if it's a shared connection */
@@ -1345,7 +1410,7 @@ nm_device_wifi_get_address (NMDeviceWifi *self,
 	g_return_if_fail (addr != NULL);
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	memcpy (addr, &(priv->hw_addr), sizeof (struct ether_addr));
+	memcpy (addr, &priv->hw_addr, sizeof (struct ether_addr));
 }
 
 static void
@@ -2530,6 +2595,11 @@ supplicant_iface_connection_state_cb_handler (gpointer user_data)
 	}
 
 out:
+	/* Signal scanning state changes */
+	if (   task->new_state == NM_SUPPLICANT_INTERFACE_CON_STATE_SCANNING
+	    || task->old_state == NM_SUPPLICANT_INTERFACE_CON_STATE_SCANNING)
+		g_object_notify (G_OBJECT (self), "scanning");
+
 	finish_supplicant_task (task, FALSE);
 	return FALSE;
 }
@@ -2586,7 +2656,8 @@ supplicant_mgr_state_cb_handler (gpointer user_data)
 		dev_state = nm_device_get_state (dev);
 		if (    priv->enabled
 		    && !priv->supplicant.iface
-		    && (dev_state >= NM_DEVICE_STATE_UNAVAILABLE)) {
+		    && (dev_state >= NM_DEVICE_STATE_UNAVAILABLE)
+		    && (nm_device_get_firmware_missing (NM_DEVICE (self)) == FALSE)) {
 			/* request a supplicant interface from the supplicant manager */
 			supplicant_interface_acquire (self);
 
@@ -2974,7 +3045,6 @@ static void
 real_update_hw_address (NMDevice *dev)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	struct ifreq req;
 	int fd;
 
@@ -2986,21 +3056,84 @@ real_update_hw_address (NMDevice *dev)
 
 	memset (&req, 0, sizeof (struct ifreq));
 	strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ);
+	errno = 0;
 	if (ioctl (fd, SIOCGIFHWADDR, &req) < 0) {
-		nm_log_err (LOGD_HW | LOGD_WIFI, "(%s) error getting hardware address: %d",
+		nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): unable to read hardware address (error %d)",
 		            nm_device_get_iface (dev), errno);
-		goto out;
+	} else
+		_update_hw_addr (self, (const guint8 *) &req.ifr_hwaddr.sa_data);
+
+	close (fd);
+}
+
+static void
+real_update_permanent_hw_address (NMDevice *dev)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	struct ifreq req;
+	struct ethtool_perm_addr *epaddr = NULL;
+	int fd, ret;
+
+	fd = socket (PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		nm_log_err (LOGD_HW, "could not open control socket.");
+		return;
+	}
+
+	/* Get permanent MAC address */
+	memset (&req, 0, sizeof (struct ifreq));
+	strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ);
+
+	epaddr = g_malloc0 (sizeof (struct ethtool_perm_addr) + ETH_ALEN);
+	epaddr->cmd = ETHTOOL_GPERMADDR;
+	epaddr->size = ETH_ALEN;
+	req.ifr_data = (void *) epaddr;
+
+	errno = 0;
+	ret = ioctl (fd, SIOCETHTOOL, &req);
+	if ((ret < 0) || !nm_ethernet_address_is_valid ((struct ether_addr *) epaddr->data)) {
+		nm_log_err (LOGD_HW | LOGD_ETHER, "(%s): unable to read permanent MAC address (error %d)",
+		            nm_device_get_iface (dev), errno);
+		/* Fall back to current address */
+		memcpy (epaddr->data, &priv->hw_addr, ETH_ALEN);
 	}
 
-	if (memcmp (&priv->hw_addr, &req.ifr_hwaddr.sa_data, sizeof (struct ether_addr))) {
-		memcpy (&priv->hw_addr, &req.ifr_hwaddr.sa_data, sizeof (struct ether_addr));
-		g_object_notify (G_OBJECT (dev), NM_DEVICE_WIFI_HW_ADDRESS);
+	if (memcmp (&priv->perm_hw_addr, epaddr->data, ETH_ALEN)) {
+		memcpy (&priv->perm_hw_addr, epaddr->data, ETH_ALEN);
+		g_object_notify (G_OBJECT (dev), NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS);
 	}
 
-out:
+	g_free (epaddr);
 	close (fd);
 }
 
+static void
+real_update_initial_hw_address (NMDevice *dev)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	char *mac_str = NULL;
+	guint8 *addr = priv->initial_hw_addr;
+	guint8 zero[ETH_ALEN] = {0,0,0,0,0,0};
+
+	/* This sets initial MAC address from current MAC address. It should only
+	 * be called from NMDevice constructor() to really get the initial address.
+	 */
+	if (!memcmp (&priv->hw_addr, &zero, ETH_ALEN))
+		real_update_hw_address (dev);
+
+	if (memcmp (&priv->initial_hw_addr, &priv->hw_addr, ETH_ALEN))
+		memcpy (&priv->initial_hw_addr, &priv->hw_addr, ETH_ALEN);
+
+	mac_str = g_strdup_printf ("%02X:%02X:%02X:%02X:%02X:%02X",
+	                           addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+
+	nm_log_dbg (LOGD_DEVICE | LOGD_ETHER, "(%s): read initial MAC address %s",
+	            nm_device_get_iface (dev), mac_str);
+
+	g_free (mac_str);
+}
 
 static NMActStageReturn
 real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
@@ -3010,8 +3143,24 @@ real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 	NMAccessPoint *ap = NULL;
 	NMActRequest *req;
 	NMConnection *connection;
+	NMSettingWireless *s_wireless;
+	const GByteArray *cloned_mac;
 	GSList *iter;
 
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_connection (req);
+	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Set spoof MAC to the interface */
+	s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS);
+	g_assert (s_wireless);
+
+	cloned_mac = nm_setting_wireless_get_cloned_mac_address (s_wireless);
+	if (cloned_mac && (cloned_mac->len == ETH_ALEN))
+		_set_hw_addr (self, (const guint8 *) cloned_mac->data, "set");
+
 	/* If the user is trying to connect to an AP that NM doesn't yet know about
 	 * (hidden network or something), create an fake AP from the security
 	 * settings in the connection to use until the AP is recognized from the
@@ -3021,12 +3170,6 @@ real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 	if (ap)
 		goto done;
 
-	req = nm_device_get_act_request (NM_DEVICE (self));
-	g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE);
-
-	connection = nm_act_request_get_connection (req);
-	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
-
 	/* Find a compatible AP in the scan list */
 	for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) {
 		NMAccessPoint *candidate = NM_AP (iter->data);
@@ -3478,12 +3621,11 @@ real_get_type_capabilities (NMDevice *dev)
 static gboolean
 spec_match_list (NMDevice *device, const GSList *specs)
 {
-	struct ether_addr ether;
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device);
 	char *hwaddr;
 	gboolean matched;
 
-	nm_device_wifi_get_address (NM_DEVICE_WIFI (device), &ether);
-	hwaddr = nm_ether_ntop (&ether);
+	hwaddr = nm_ether_ntop ((struct ether_addr *) &priv->perm_hw_addr);
 	matched = nm_match_spec_hwaddr (specs, hwaddr);
 	g_free (hwaddr);
 
@@ -3531,7 +3673,7 @@ device_state_changed (NMDevice *device,
 		 * acquire a supplicant interface and transition to DISCONNECTED because
 		 * the device is now ready to use.
 		 */
-		if (priv->enabled) {
+		if (priv->enabled && (nm_device_get_firmware_missing (device) == FALSE)) {
 			gboolean success;
 			struct iw_range range;
 
@@ -3624,8 +3766,12 @@ real_set_enabled (NMDeviceInterface *device, gboolean enabled)
 			nm_log_dbg (LOGD_WIFI, "(%s): enable blocked by failure to bring device up",
 			            nm_device_get_iface (NM_DEVICE (device)));
 
-			/* The device sucks, or HAL was lying to us about the killswitch state */
-			priv->enabled = FALSE;
+			if (no_firmware)
+				nm_device_set_firmware_missing (NM_DEVICE (device), TRUE);
+			else {
+				/* The device sucks, or the kernel was lying to us about the killswitch state */
+				priv->enabled = FALSE;
+			}
 			return;
 		}
 
@@ -3741,12 +3887,13 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDeviceWifi *device = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device);
-	struct ether_addr hw_addr;
 
 	switch (prop_id) {
 	case PROP_HW_ADDRESS:
-		nm_device_wifi_get_address (device, &hw_addr);
-		g_value_take_string (value, nm_ether_ntop (&hw_addr));
+		g_value_take_string (value, nm_ether_ntop ((struct ether_addr *) &priv->hw_addr));
+		break;
+	case PROP_PERM_HW_ADDRESS:
+		g_value_take_string (value, nm_ether_ntop ((struct ether_addr *) &priv->perm_hw_addr));
 		break;
 	case PROP_MODE:
 		g_value_set_uint (value, nm_device_wifi_get_mode (device));
@@ -3815,6 +3962,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	parent_class->bring_up = real_bring_up;
 	parent_class->take_down = real_take_down;
 	parent_class->update_hw_address = real_update_hw_address;
+	parent_class->update_permanent_hw_address = real_update_permanent_hw_address;
+	parent_class->update_initial_hw_address = real_update_initial_hw_address;
 	parent_class->get_best_auto_connection = real_get_best_auto_connection;
 	parent_class->is_available = real_is_available;
 	parent_class->connection_secrets_updated = real_connection_secrets_updated;
@@ -3835,8 +3984,15 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	/* Properties */
 	g_object_class_install_property (object_class, PROP_HW_ADDRESS,
 		g_param_spec_string (NM_DEVICE_WIFI_HW_ADDRESS,
-		                     "MAC Address",
-		                     "Hardware MAC address",
+		                     "Active MAC Address",
+		                     "Currently set hardware MAC address",
+		                     NULL,
+		                     G_PARAM_READABLE));
+
+	g_object_class_install_property (object_class, PROP_PERM_HW_ADDRESS,
+		g_param_spec_string (NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS,
+		                     "Permanent MAC Address",
+		                     "Permanent hardware MAC address",
 		                     NULL,
 		                     G_PARAM_READABLE));