shill: Use real MAC address for cellular devices.

Read the MAC address for cellular devices from the kernel at
the time of device creation instead of using the MAC address
from the RTNL link-add message.

The MAC address in the link-add message for usb-based devices
may not be consistent from one reboot to another.  The MAC
address is used in the shill profile's entry for the device,
and so if the MAC address changes from one reboot to another,
ChromeOS does not use the existing entry in the profile but
instead creates another, with input from the user.

BUG=chromium-os:33006
TEST=Run all unit tests.  Test with Gobi 2k (which exhibits the problem
of 33006), Gobi 3k, and E362.

Change-Id: I2147139c2f33a0ee9909516933e500cefb7dc2bc
Reviewed-on: https://gerrit.chromium.org/gerrit/28738
Reviewed-by: Paul Stewart <pstew@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Gary Morain <gmorain@chromium.org>
Tested-by: Gary Morain <gmorain@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index e8d44ee..e45e1a0 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -363,6 +363,11 @@
       SLOG(Device, 2) << "Cellular link " << link_name
                       << " at index " << interface_index
                       << " -- notifying ModemInfo.";
+
+      // The MAC address provided by RTNL is not reliable for Gobi 2K modems.
+      // Clear it here, and it will be fetched from the kernel is
+      // GetMacAddress().
+      infos_[interface_index].mac_address.Clear();
       manager_->modem_info()->OnDeviceInfoAvailable(link_name);
       break;
     case Technology::kEthernet:
@@ -530,8 +535,45 @@
   if (!info) {
     return false;
   }
-  *address = info->mac_address;
-  return true;
+  // |mac_address| from RTNL is not used for some devices, in which case it will
+  // be empty here.
+  if (!info->mac_address.IsEmpty()) {
+    *address = info->mac_address;
+    return true;
+  }
+
+  // Ask the kernel for the MAC address.
+  *address = GetMACAddressFromKernel(interface_index);
+  return !address->IsEmpty();
+}
+
+ByteString DeviceInfo::GetMACAddressFromKernel(int interface_index) const {
+  ByteString mac_address;
+  mac_address.Clear();
+  // TODO(gmorain): Use ScopedSocketCloser instead.
+  const int fd = socket(PF_INET, SOCK_DGRAM, 0);
+  if (fd < 0) {
+    LOG(ERROR) << __func__ << ": Unable to open socket: " << fd;
+    return mac_address;
+  }
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  ifr.ifr_ifindex = interface_index;
+  int err = HANDLE_EINTR(ioctl(fd, SIOCGIFNAME, &ifr));
+  if (err < 0) {
+    LOG(ERROR) << __func__ << ": Unable to read ifname: " << errno;
+    close(fd);
+    return mac_address;
+  }
+  err = HANDLE_EINTR(ioctl(fd, SIOCGIFHWADDR, &ifr));
+  close(fd);
+  if (err < 0) {
+    LOG(ERROR) << __func__ << ": Unable to read MAC address: " << errno;
+    return mac_address;
+  }
+  mac_address.Resize(IFHWADDRLEN);
+  memcpy(mac_address.GetData(), ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
+  return mac_address;
 }
 
 bool DeviceInfo::GetAddresses(int interface_index,