[net] Adds AddressTrackerLinux which keeps track of interface addresses using rtnetlink.

BUG=100690,113993
TEST=./net_unittests --gtest_filter=AddressTrackerLinuxTest.*

Review URL: https://chromiumcodereview.appspot.com/10689015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@146907 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/base/address_tracker_linux.cc b/net/base/address_tracker_linux.cc
new file mode 100644
index 0000000..b97b2b0
--- /dev/null
+++ b/net/base/address_tracker_linux.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/address_tracker_linux.h"
+
+#include <errno.h>
+
+#include "base/eintr_wrapper.h"
+#include "base/logging.h"
+#include "net/base/network_change_notifier_linux.h"
+
+namespace net {
+namespace internal {
+
+namespace {
+
+// Retrieves address from NETLINK address message.
+bool GetAddress(const struct nlmsghdr* header, IPAddressNumber* out) {
+  const struct ifaddrmsg* msg =
+      reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
+  size_t address_length = 0;
+  switch (msg->ifa_family) {
+    case AF_INET:
+      address_length = kIPv4AddressSize;
+      break;
+    case AF_INET6:
+      address_length = kIPv6AddressSize;
+      break;
+    default:
+      // Unknown family.
+      return false;
+  }
+  // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on
+  // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of
+  // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6
+  // have the IFA_LOCAL attribute.
+  unsigned char* address = NULL;
+  unsigned char* local = NULL;
+  size_t length = IFA_PAYLOAD(header);
+  for (const struct rtattr* attr =
+           reinterpret_cast<const struct rtattr*>(IFA_RTA(msg));
+       RTA_OK(attr, length);
+       attr = RTA_NEXT(attr, length)) {
+    switch (attr->rta_type) {
+      case IFA_ADDRESS:
+        DCHECK_GE(RTA_PAYLOAD(attr), address_length);
+        address = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
+        break;
+      case IFA_LOCAL:
+        DCHECK_GE(RTA_PAYLOAD(attr), address_length);
+        local = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
+        break;
+      default:
+        break;
+    }
+  }
+  if (local)
+    address = local;
+  if (!address)
+    return false;
+  out->assign(address, address + address_length);
+  return true;
+}
+
+void CloseSocket(int fd) {
+  if (HANDLE_EINTR(close(fd)) < 0)
+    PLOG(ERROR) << "Could not close NETLINK socket.";
+}
+
+}  // namespace
+
+AddressTrackerLinux::AddressTrackerLinux(const base::Closure& callback)
+    : callback_(callback),
+      netlink_fd_(-1) {
+  DCHECK(!callback.is_null());
+}
+
+AddressTrackerLinux::~AddressTrackerLinux() {
+  if (netlink_fd_ >= 0)
+    CloseSocket(netlink_fd_);
+}
+
+void AddressTrackerLinux::Init() {
+  int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0) {
+    PLOG(ERROR) << "Could not create NETLINK socket";
+    return;
+  }
+
+  // Request notifications.
+  struct sockaddr_nl addr = {};
+  addr.nl_family = AF_NETLINK;
+  addr.nl_pid = getpid();
+  // TODO(szym): Track RTMGRP_LINK as well for ifi_type, http://crbug.com/113993
+  addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY;
+  int rv = bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+  if (rv < 0) {
+    PLOG(ERROR) << "Could not bind NETLINK socket";
+    CloseSocket(sock);
+    return;
+  }
+
+  // Watch for asynchronous messages.
+  if (SetNonBlocking(sock)) {
+    PLOG(ERROR) << "Could not make NETLINK socket non-blocking";
+    CloseSocket(sock);
+    return;
+  }
+
+  rv = MessageLoopForIO::current()->WatchFileDescriptor(
+      sock, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
+  if (rv < 0) {
+    PLOG(ERROR) << "Could not watch NETLINK socket";
+    CloseSocket(sock);
+    return;
+  }
+
+  // Request dump of addresses.
+  struct sockaddr_nl peer = {};
+  peer.nl_family = AF_NETLINK;
+
+  struct {
+    struct nlmsghdr header;
+    struct rtgenmsg msg;
+  } request = {};
+
+  request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
+  request.header.nlmsg_type = RTM_GETADDR;
+  request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  request.header.nlmsg_pid = getpid();
+  request.msg.rtgen_family = AF_UNSPEC;
+
+  rv = HANDLE_EINTR(sendto(sock, &request, request.header.nlmsg_len, 0,
+                           reinterpret_cast<struct sockaddr*>(&peer),
+                           sizeof(peer)));
+  if (rv < 0) {
+    PLOG(ERROR) << "Could not send NETLINK request";
+    CloseSocket(sock);
+    return;
+  }
+
+  netlink_fd_ = sock;
+}
+
+AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const {
+  base::AutoLock lock(lock_);
+  return map_;
+}
+
+bool AddressTrackerLinux::ReadMessages() {
+  char buffer[4096];
+  bool changed = false;
+  for (;;) {
+    int rv = HANDLE_EINTR(recv(netlink_fd_, buffer, sizeof(buffer), 0));
+    if (rv == 0) {
+      LOG(ERROR) << "Unexpected shutdown of NETLINK socket.";
+      return false;
+    }
+    if (rv < 0) {
+      if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+        break;
+      PLOG(ERROR) << "Failed to recv from netlink socket";
+      return false;
+    }
+    changed |= HandleMessage(buffer, rv);
+  };
+  return changed;
+}
+
+bool AddressTrackerLinux::HandleMessage(const char* buffer, size_t length) {
+  DCHECK(buffer);
+  bool changed = false;
+  for (const struct nlmsghdr* header =
+          reinterpret_cast<const struct nlmsghdr*>(buffer);
+       NLMSG_OK(header, length);
+       header = NLMSG_NEXT(header, length)) {
+    switch (header->nlmsg_type) {
+      case NLMSG_DONE:
+        return changed;
+      case NLMSG_ERROR:
+        LOG(ERROR) << "Unexpected netlink error.";
+        return changed;
+      case RTM_NEWADDR: {
+        IPAddressNumber address;
+        if (GetAddress(header, &address)) {
+          base::AutoLock lock(lock_);
+          const struct ifaddrmsg* msg =
+              reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
+          // Only indicate change if the address is new or ifaddrmsg info has
+          // changed.
+          AddressMap::iterator it = map_.find(address);
+          if (it == map_.end()) {
+            map_.insert(it, std::make_pair(address, *msg));
+            changed = true;
+          } else if (memcmp(&it->second, msg, sizeof(*msg))) {
+            it->second = *msg;
+            changed = true;
+          }
+        }
+      } break;
+      case RTM_DELADDR: {
+        IPAddressNumber address;
+        if (GetAddress(header, &address)) {
+          base::AutoLock lock(lock_);
+          if (map_.erase(address))
+            changed = true;
+        }
+      } break;
+      default:
+        break;
+    }
+  }
+  return changed;
+}
+
+void AddressTrackerLinux::OnFileCanReadWithoutBlocking(int fd) {
+  DCHECK_EQ(netlink_fd_, fd);
+  if (ReadMessages())
+    callback_.Run();
+}
+
+void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {}
+
+}  // namespace internal
+}  // namespace net
diff --git a/net/base/address_tracker_linux.h b/net/base/address_tracker_linux.h
new file mode 100644
index 0000000..a70a14c
--- /dev/null
+++ b/net/base/address_tracker_linux.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_BASE_ADDRESS_TRACKER_LINUX_H_
+#define NET_BASE_ADDRESS_TRACKER_LINUX_H_
+
+#include <sys/socket.h>  // Needed to include netlink.
+// Mask superfluous definition of |struct net|. This is fixed in Linux 2.6.38.
+#define net net_kernel
+#include <linux/rtnetlink.h>
+#undef net
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "net/base/net_util.h"
+
+namespace net {
+namespace internal {
+
+// Keeps track of network interface addresses using rtnetlink. Used by
+// NetworkChangeNotifier to provide signals to registered IPAddressObservers.
+class NET_EXPORT_PRIVATE AddressTrackerLinux
+    : public MessageLoopForIO::Watcher {
+ public:
+  typedef std::map<IPAddressNumber, struct ifaddrmsg> AddressMap;
+
+  // Will run |callback| when the AddressMap changes.
+  explicit AddressTrackerLinux(const base::Closure& callback);
+  virtual ~AddressTrackerLinux();
+
+  // Starts watching system configuration for changes. The current thread must
+  // have a MessageLoopForIO.
+  void Init();
+
+  AddressMap GetAddressMap() const;
+
+ private:
+  friend class AddressTrackerLinuxTest;
+
+  // Returns true if |map_| changed while reading messages from |netlink_fd_|.
+  bool ReadMessages();
+
+  // Returns true if |map_| changed while reading the message from |buffer|.
+  bool HandleMessage(const char* buffer, size_t length);
+
+  // MessageLoopForIO::Watcher:
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+  virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE;
+
+  base::Closure callback_;
+
+  int netlink_fd_;
+  MessageLoopForIO::FileDescriptorWatcher watcher_;
+
+  mutable base::Lock lock_;
+  AddressMap map_;
+};
+
+}  // namespace internal
+}  // namespace net
+
+#endif  // NET_BASE_ADDRESS_TRACKER_LINUX_H_
diff --git a/net/base/address_tracker_linux_unittest.cc b/net/base/address_tracker_linux_unittest.cc
new file mode 100644
index 0000000..5c2fbc5
--- /dev/null
+++ b/net/base/address_tracker_linux_unittest.cc
@@ -0,0 +1,259 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/base/address_tracker_linux.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace internal {
+
+void Noop() {}
+
+class AddressTrackerLinuxTest : public testing::Test {
+ protected:
+  AddressTrackerLinuxTest() : tracker_(base::Bind(&Noop)) {}
+
+  bool HandleMessage(char* buf, size_t length) {
+    return tracker_.HandleMessage(buf, length);
+  }
+
+  AddressTrackerLinux::AddressMap GetAddressMap() {
+    return tracker_.GetAddressMap();
+  }
+
+  AddressTrackerLinux tracker_;
+};
+
+namespace {
+
+typedef std::vector<char> Buffer;
+
+class NetlinkMessage {
+ public:
+  explicit NetlinkMessage(uint16 type) : buffer_(NLMSG_HDRLEN) {
+    header()->nlmsg_type = type;
+    Align();
+  }
+
+  void AddPayload(const void* data, size_t length) {
+    CHECK_EQ(static_cast<size_t>(NLMSG_HDRLEN),
+             buffer_.size()) << "Payload must be added first";
+    Append(data, length);
+    Align();
+  }
+
+  void AddAttribute(uint16 type, const void* data, size_t length) {
+    struct nlattr attr;
+    attr.nla_len = NLA_HDRLEN + length;
+    attr.nla_type = type;
+    Append(&attr, sizeof(attr));
+    Align();
+    Append(data, length);
+    Align();
+  }
+
+  void AppendTo(Buffer* output) const {
+    CHECK_EQ(NLMSG_ALIGN(output->size()), output->size());
+    output->reserve(output->size() + NLMSG_LENGTH(buffer_.size()));
+    output->insert(output->end(), buffer_.begin(), buffer_.end());
+  }
+
+ private:
+  void Append(const void* data, size_t length) {
+    const char* chardata = reinterpret_cast<const char*>(data);
+    buffer_.insert(buffer_.end(), chardata, chardata + length);
+  }
+
+  void Align() {
+    header()->nlmsg_len = buffer_.size();
+    buffer_.insert(buffer_.end(), NLMSG_ALIGN(buffer_.size()) - buffer_.size(),
+                   0);
+    CHECK(NLMSG_OK(header(), buffer_.size()));
+  }
+
+  struct nlmsghdr* header() {
+    return reinterpret_cast<struct nlmsghdr*>(&buffer_[0]);
+  }
+
+  Buffer buffer_;
+};
+
+void MakeMessage(uint16 type,
+                 uint8 flags,
+                 uint8 family,
+                 const IPAddressNumber& address,
+                 const IPAddressNumber& local,
+                 Buffer* output) {
+  NetlinkMessage nlmsg(type);
+  struct ifaddrmsg msg;
+  msg.ifa_family = family;
+  msg.ifa_flags = flags;
+  nlmsg.AddPayload(&msg, sizeof(msg));
+  if (address.size())
+    nlmsg.AddAttribute(IFA_ADDRESS, &address[0], address.size());
+  if (local.size())
+    nlmsg.AddAttribute(IFA_LOCAL, &local[0], local.size());
+  nlmsg.AppendTo(output);
+}
+
+const unsigned char kAddress0[] = { 127, 0, 0, 1 };
+const unsigned char kAddress1[] = { 10, 0, 0, 1 };
+const unsigned char kAddress2[] = { 192, 168, 0, 1 };
+const unsigned char kAddress3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                    0, 0, 0, 1 };
+const unsigned char kAddress4[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255,
+                                    169, 254, 0, 1 };
+
+TEST_F(AddressTrackerLinuxTest, NewAddress) {
+  const IPAddressNumber kEmpty;
+  const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+  const IPAddressNumber kAddr1(kAddress1, kAddress1 + arraysize(kAddress1));
+  const IPAddressNumber kAddr2(kAddress2, kAddress2 + arraysize(kAddress2));
+  const IPAddressNumber kAddr3(kAddress3, kAddress3 + arraysize(kAddress3));
+
+  Buffer buffer;
+  MakeMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  AddressTrackerLinux::AddressMap map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) != map.end());
+  EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+  buffer.clear();
+  MakeMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr1, kAddr2, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(2u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) != map.end());
+  EXPECT_TRUE(map.find(kAddr2) != map.end());
+  EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr2].ifa_flags);
+
+  buffer.clear();
+  MakeMessage(RTM_NEWADDR, 0, AF_INET6, kEmpty, kAddr3, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(3u, map.size());
+  EXPECT_TRUE(map.find(kAddr3) != map.end());
+}
+
+TEST_F(AddressTrackerLinuxTest, NewAddressChange) {
+  const IPAddressNumber kEmpty;
+  const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+
+  Buffer buffer;
+  MakeMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  AddressTrackerLinux::AddressMap map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) != map.end());
+  EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+  buffer.clear();
+  MakeMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr0, kEmpty, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) != map.end());
+  EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr0].ifa_flags);
+
+  // Both messages in one buffer.
+  buffer.clear();
+  MakeMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty, &buffer);
+  MakeMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr0, kEmpty, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr0].ifa_flags);
+}
+
+TEST_F(AddressTrackerLinuxTest, NewAddressDuplicate) {
+  const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+
+  Buffer buffer;
+  MakeMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kAddr0, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  AddressTrackerLinux::AddressMap map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) != map.end());
+  EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+  EXPECT_FALSE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+}
+
+TEST_F(AddressTrackerLinuxTest, DeleteAddress) {
+  const IPAddressNumber kEmpty;
+  const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+  const IPAddressNumber kAddr1(kAddress1, kAddress1 + arraysize(kAddress1));
+  const IPAddressNumber kAddr2(kAddress2, kAddress2 + arraysize(kAddress2));
+
+  Buffer buffer;
+  MakeMessage(RTM_NEWADDR, 0, AF_INET, kAddr0, kEmpty, &buffer);
+  MakeMessage(RTM_NEWADDR, 0, AF_INET, kAddr1, kAddr2, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  AddressTrackerLinux::AddressMap map = GetAddressMap();
+  EXPECT_EQ(2u, map.size());
+
+  buffer.clear();
+  MakeMessage(RTM_DELADDR, 0, AF_INET, kEmpty, kAddr0, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+  EXPECT_TRUE(map.find(kAddr0) == map.end());
+  EXPECT_TRUE(map.find(kAddr2) != map.end());
+
+  buffer.clear();
+  MakeMessage(RTM_DELADDR, 0, AF_INET, kAddr2, kAddr1, &buffer);
+  // kAddr1 does not exist in the map.
+  EXPECT_FALSE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(1u, map.size());
+
+  buffer.clear();
+  MakeMessage(RTM_DELADDR, 0, AF_INET, kAddr2, kEmpty, &buffer);
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  map = GetAddressMap();
+  EXPECT_EQ(0u, map.size());
+}
+
+TEST_F(AddressTrackerLinuxTest, IgnoredMessage) {
+  const IPAddressNumber kEmpty;
+  const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+  const IPAddressNumber kAddr3(kAddress3, kAddress3 + arraysize(kAddress3));
+
+  Buffer buffer;
+  // Ignored family.
+  MakeMessage(RTM_NEWADDR, 0, AF_UNSPEC, kAddr3, kAddr0, &buffer);
+  // No address.
+  MakeMessage(RTM_NEWADDR, 0, AF_INET, kEmpty, kEmpty, &buffer);
+  // Ignored type.
+  MakeMessage(RTM_DELROUTE, 0, AF_INET6, kAddr3, kEmpty, &buffer);
+  EXPECT_FALSE(HandleMessage(&buffer[0], buffer.size()));
+  EXPECT_EQ(0u, GetAddressMap().size());
+
+  // Valid message after ignored messages.
+  NetlinkMessage nlmsg(RTM_NEWADDR);
+  struct ifaddrmsg msg = {};
+  msg.ifa_family = AF_INET;
+  nlmsg.AddPayload(&msg, sizeof(msg));
+  // Ignored attribute.
+  struct ifa_cacheinfo cache_info = {};
+  nlmsg.AddAttribute(IFA_CACHEINFO, &cache_info, sizeof(cache_info));
+  nlmsg.AddAttribute(IFA_ADDRESS, &kAddr0[0], kAddr0.size());
+  nlmsg.AppendTo(&buffer);
+
+  EXPECT_TRUE(HandleMessage(&buffer[0], buffer.size()));
+  EXPECT_EQ(1u, GetAddressMap().size());
+}
+
+}  // namespace
+
+}  // namespace internal
+}  // namespace net
diff --git a/net/base/network_change_notifier.cc b/net/base/network_change_notifier.cc
index 91835320..8c5f446 100644
--- a/net/base/network_change_notifier.cc
+++ b/net/base/network_change_notifier.cc
@@ -84,6 +84,15 @@
       CONNECTION_UNKNOWN;
 }
 
+#if defined(OS_LINUX)
+// static
+const internal::AddressTrackerLinux*
+NetworkChangeNotifier::GetAddressTracker() {
+  return g_network_change_notifier ?
+        g_network_change_notifier->GetAddressTrackerInternal() : NULL;
+}
+#endif
+
 // static
 bool NetworkChangeNotifier::IsWatchingDNS() {
   if (!g_network_change_notifier)
@@ -155,6 +164,13 @@
   g_network_change_notifier = this;
 }
 
+#if defined(OS_LINUX)
+const internal::AddressTrackerLinux*
+NetworkChangeNotifier::GetAddressTrackerInternal() const {
+  return NULL;
+}
+#endif
+
 // static
 void NetworkChangeNotifier::NotifyObserversOfIPAddressChange() {
   if (g_network_change_notifier) {
diff --git a/net/base/network_change_notifier.h b/net/base/network_change_notifier.h
index 8420a8f5..03bcbf30 100644
--- a/net/base/network_change_notifier.h
+++ b/net/base/network_change_notifier.h
@@ -16,6 +16,10 @@
 
 namespace internal {
 class DnsConfigWatcher;
+
+#if defined(OS_LINUX)
+class AddressTrackerLinux;
+#endif
 }
 
 // NetworkChangeNotifier monitors the system for network changes, and notifies
@@ -124,6 +128,11 @@
   // attempt to a particular remote site will be successful.
   static ConnectionType GetConnectionType();
 
+#if defined(OS_LINUX)
+  // Returns the AddressTrackerLinux if present.
+  static const internal::AddressTrackerLinux* GetAddressTracker();
+#endif
+
   // Convenience method to determine if the user is offline.
   // Returns true if there is currently no internet connection.
   //
@@ -173,6 +182,12 @@
 
   NetworkChangeNotifier();
 
+#if defined(OS_LINUX)
+  // Returns the AddressTrackerLinux if present.
+  virtual const internal::AddressTrackerLinux*
+      GetAddressTrackerInternal() const;
+#endif
+
   // Broadcasts a notification to all registered observers.  Note that this
   // happens asynchronously, even for observers on the current thread, even in
   // tests.
diff --git a/net/base/network_change_notifier_linux.cc b/net/base/network_change_notifier_linux.cc
index b7acd46..1638f974 100644
--- a/net/base/network_change_notifier_linux.cc
+++ b/net/base/network_change_notifier_linux.cc
@@ -9,15 +9,12 @@
 
 #include "net/base/network_change_notifier_linux.h"
 
-#include <errno.h>
 #include <resolv.h>
-#include <sys/socket.h>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
-#include "base/eintr_wrapper.h"
 #include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
@@ -27,16 +24,14 @@
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
+#include "net/base/address_tracker_linux.h"
 #include "net/base/net_errors.h"
-#include "net/base/network_change_notifier_netlink_linux.h"
 #include "net/dns/dns_config_watcher.h"
 
 namespace net {
 
 namespace {
 
-const int kInvalidSocket = -1;
-
 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
@@ -243,56 +238,45 @@
                        NetworkChangeNotifier::CONNECTION_UNKNOWN;
 }
 
-class NetworkChangeNotifierLinux::Thread
-    : public base::Thread, public MessageLoopForIO::Watcher {
+class NetworkChangeNotifierLinux::Thread : public base::Thread {
  public:
   explicit Thread(dbus::Bus* bus);
   virtual ~Thread();
 
-  // MessageLoopForIO::Watcher:
-  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
-  virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE;
-
   // Plumbing for NetworkChangeNotifier::GetCurrentConnectionType.
   // Safe to call from any thread.
   NetworkChangeNotifier::ConnectionType GetCurrentConnectionType() {
     return network_manager_api_.GetCurrentConnectionType();
   }
 
+  const internal::AddressTrackerLinux* address_tracker() const {
+    return &address_tracker_;
+  }
+
  protected:
   // base::Thread
   virtual void Init() OVERRIDE;
   virtual void CleanUp() OVERRIDE;
 
  private:
-  // Starts listening for netlink messages.  Also handles the messages if there
-  // are any available on the netlink socket.
-  void ListenForNotifications();
-
-  // Attempts to read from the netlink socket into |buf| of length |len|.
-  // Returns the bytes read on synchronous success and ERR_IO_PENDING if the
-  // recv() would block.  Otherwise, it returns a net error code.
-  int ReadNotificationMessage(char* buf, size_t len);
-
-  // The netlink socket descriptor.
-  int netlink_fd_;
-  MessageLoopForIO::FileDescriptorWatcher netlink_watcher_;
-
   // Used to detect online/offline state changes.
   NetworkManagerApi network_manager_api_;
 
   internal::DnsConfigWatcher dns_watcher_;
+  internal::AddressTrackerLinux address_tracker_;
 
   DISALLOW_COPY_AND_ASSIGN(Thread);
 };
 
 NetworkChangeNotifierLinux::Thread::Thread(dbus::Bus* bus)
     : base::Thread("NetworkChangeNotifier"),
-      netlink_fd_(kInvalidSocket),
       network_manager_api_(
           base::Bind(&NetworkChangeNotifier::
                      NotifyObserversOfConnectionTypeChange),
-          bus) {
+          bus),
+      address_tracker_(
+          base::Bind(&NetworkChangeNotifier::
+                     NotifyObserversOfIPAddressChange)) {
 }
 
 NetworkChangeNotifierLinux::Thread::~Thread() {
@@ -300,77 +284,16 @@
 }
 
 void NetworkChangeNotifierLinux::Thread::Init() {
-  netlink_fd_ = InitializeNetlinkSocket();
-  if (netlink_fd_ < 0) {
-    netlink_fd_ = kInvalidSocket;
-    return;
-  }
-  ListenForNotifications();
-
   network_manager_api_.Init();
-
   dns_watcher_.Init();
+  address_tracker_.Init();
 }
 
 void NetworkChangeNotifierLinux::Thread::CleanUp() {
-  if (netlink_fd_ != kInvalidSocket) {
-    if (HANDLE_EINTR(close(netlink_fd_)) != 0)
-      PLOG(ERROR) << "Failed to close socket";
-    netlink_fd_ = kInvalidSocket;
-    netlink_watcher_.StopWatchingFileDescriptor();
-  }
   network_manager_api_.CleanUp();
-
   dns_watcher_.CleanUp();
 }
 
-void NetworkChangeNotifierLinux::Thread::OnFileCanReadWithoutBlocking(int fd) {
-  DCHECK_EQ(fd, netlink_fd_);
-  ListenForNotifications();
-}
-
-void NetworkChangeNotifierLinux::Thread::OnFileCanWriteWithoutBlocking(
-    int /* fd */) {
-  NOTREACHED();
-}
-
-void NetworkChangeNotifierLinux::Thread::ListenForNotifications() {
-  char buf[4096];
-  int rv = ReadNotificationMessage(buf, arraysize(buf));
-  while (rv > 0) {
-    if (HandleNetlinkMessage(buf, rv)) {
-      VLOG(1) << "Detected IP address changes.";
-      NotifyObserversOfIPAddressChange();
-    }
-    rv = ReadNotificationMessage(buf, arraysize(buf));
-  }
-
-  if (rv == ERR_IO_PENDING) {
-    rv = MessageLoopForIO::current()->WatchFileDescriptor(netlink_fd_, false,
-        MessageLoopForIO::WATCH_READ, &netlink_watcher_, this);
-    LOG_IF(ERROR, !rv) << "Failed to watch netlink socket: " << netlink_fd_;
-  }
-}
-
-int NetworkChangeNotifierLinux::Thread::ReadNotificationMessage(
-    char* buf,
-    size_t len) {
-  DCHECK_NE(len, 0u);
-  DCHECK(buf);
-  memset(buf, 0, len);
-  int rv = recv(netlink_fd_, buf, len, 0);
-  if (rv > 0)
-    return rv;
-
-  DCHECK_NE(rv, 0);
-  if (errno != EAGAIN && errno != EWOULDBLOCK) {
-    PLOG(DFATAL) << "recv";
-    return ERR_FAILED;
-  }
-
-  return ERR_IO_PENDING;
-}
-
 NetworkChangeNotifierLinux* NetworkChangeNotifierLinux::Create() {
   return new NetworkChangeNotifierLinux(NULL);
 }
@@ -400,4 +323,9 @@
   return notifier_thread_->GetCurrentConnectionType();
 }
 
+const internal::AddressTrackerLinux*
+NetworkChangeNotifierLinux::GetAddressTrackerInternal() const {
+  return notifier_thread_->address_tracker();
+}
+
 }  // namespace net
diff --git a/net/base/network_change_notifier_linux.h b/net/base/network_change_notifier_linux.h
index c1ba1ee0..ba45b0b26 100644
--- a/net/base/network_change_notifier_linux.h
+++ b/net/base/network_change_notifier_linux.h
@@ -34,6 +34,9 @@
   // NetworkChangeNotifier:
   virtual ConnectionType GetCurrentConnectionType() const OVERRIDE;
 
+  virtual const internal::AddressTrackerLinux*
+      GetAddressTrackerInternal() const OVERRIDE;
+
   // The thread used to listen for notifications.  This relays the notification
   // to the registered observers without posting back to the thread the object
   // was created on.
diff --git a/net/base/network_change_notifier_netlink_linux.cc b/net/base/network_change_notifier_netlink_linux.cc
deleted file mode 100644
index 493e17a..0000000
--- a/net/base/network_change_notifier_netlink_linux.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "net/base/network_change_notifier_netlink_linux.h"
-
-#include <fcntl.h>
-// socket.h is needed to define types for the linux kernel header netlink.h
-// so it needs to come before netlink.h.
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "base/logging.h"
-
-namespace {
-
-// Return true on success, false on failure.
-// Too small a function to bother putting in a library?
-bool SetNonBlocking(int fd) {
-  int flags = fcntl(fd, F_GETFL, 0);
-  if (-1 == flags)
-    return false;
-  return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0 ? true : false;
-}
-
-bool IsIPv6Update(const struct nlmsghdr* netlink_message_header) {
-  const struct ifaddrmsg* address_message =
-      reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(netlink_message_header));
-  return address_message->ifa_family == AF_INET6;
-}
-
-bool IsDuplicateIPv6AddressUpdate(
-    const struct nlmsghdr* netlink_message_header) {
-  const struct ifaddrmsg* address_message =
-      reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(netlink_message_header));
-  int address_message_length = IFA_PAYLOAD(netlink_message_header);
-  const struct rtattr* route_attribute =
-      reinterpret_cast<struct rtattr*>(IFA_RTA(address_message));
-  DCHECK_EQ(address_message->ifa_family, AF_INET6);
-
-  // Look for a cacheinfo attribute, and ignore new address broadcasts
-  // where the updated time stamp is newer than the created time stamp.
-  while (RTA_OK(route_attribute, address_message_length)) {
-    if (route_attribute->rta_type == IFA_CACHEINFO) {
-      struct ifa_cacheinfo* cache_info =
-          reinterpret_cast<struct ifa_cacheinfo*>(RTA_DATA(route_attribute));
-      if (cache_info->cstamp != cache_info->tstamp)
-        return true;
-    }
-    route_attribute = RTA_NEXT(route_attribute, address_message_length);
-  }
-  return false;
-}
-
-}  // namespace
-
-int InitializeNetlinkSocket() {
-  int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-  if (sock < 0) {
-    PLOG(ERROR) << "Error creating netlink socket";
-    return -1;
-  }
-
-  if (!SetNonBlocking(sock)) {
-    PLOG(ERROR) << "Failed to set netlink socket to non-blocking mode.";
-    if (close(sock) != 0)
-      PLOG(ERROR) << "Failed to close socket";
-    return -1;
-  }
-
-  struct sockaddr_nl local_addr;
-  memset(&local_addr, 0, sizeof(local_addr));
-  local_addr.nl_family = AF_NETLINK;
-  local_addr.nl_pid = getpid();
-  local_addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR |
-                         RTMGRP_NOTIFY;
-  int ret = bind(sock, reinterpret_cast<struct sockaddr*>(&local_addr),
-                 sizeof(local_addr));
-  if (ret < 0) {
-    PLOG(ERROR) << "Error binding netlink socket";
-    if (close(sock) != 0)
-      PLOG(ERROR) << "Failed to close socket";
-    return -1;
-  }
-
-  return sock;
-}
-
-bool HandleNetlinkMessage(char* buf, size_t len) {
-  const struct nlmsghdr* netlink_message_header =
-      reinterpret_cast<struct nlmsghdr*>(buf);
-  DCHECK(netlink_message_header);
-  for (; NLMSG_OK(netlink_message_header, len);
-       netlink_message_header = NLMSG_NEXT(netlink_message_header, len)) {
-    int netlink_message_type = netlink_message_header->nlmsg_type;
-    switch (netlink_message_type) {
-      case NLMSG_DONE:
-        NOTREACHED()
-            << "This is a monitoring netlink socket.  It should never be done.";
-        return false;
-      case NLMSG_ERROR:
-        LOG(ERROR) << "Unexpected netlink error.";
-        return false;
-      // During IP address changes, we will see all these messages.  Only fire
-      // the notification when we get a new address or remove an address.  We
-      // may still end up notifying observers more than strictly necessary, but
-      // if the primary interface goes down and back up, then this is necessary.
-      case RTM_NEWADDR:
-        if (IsIPv6Update(netlink_message_header) &&
-            IsDuplicateIPv6AddressUpdate(netlink_message_header))
-          return false;
-        return true;
-      case RTM_DELADDR:
-        return true;
-      case RTM_NEWLINK:
-      case RTM_DELLINK:
-        return false;
-      default:
-        LOG(DFATAL) << "Received unexpected netlink message type: "
-                    << netlink_message_type;
-        return false;
-    }
-  }
-
-  return false;
-}
diff --git a/net/base/network_change_notifier_netlink_linux.h b/net/base/network_change_notifier_netlink_linux.h
deleted file mode 100644
index c06fdc79..0000000
--- a/net/base/network_change_notifier_netlink_linux.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This file is to hide the netlink implementation details since the netlink.h
-// header contains a struct net; which conflicts with the net namespace.  So we
-// separate out all the netlink stuff into these files.
-
-#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
-#define NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
-
-#include <cstddef>
-
-// Returns the file descriptor if successful.  Otherwise, returns -1.
-int InitializeNetlinkSocket();
-
-// Returns true if a network change has been detected, otherwise returns false.
-bool HandleNetlinkMessage(char* buf, size_t len);
-
-#endif  // NET_BASE_NETWORK_CHANGE_NOTIFIER_NETLINK_LINUX_H_
diff --git a/net/net.gyp b/net/net.gyp
index 2ffb237..517abb14 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -57,6 +57,8 @@
         'base/address_family.h',
         'base/address_list.cc',
         'base/address_list.h',
+        'base/address_tracker_linux.cc',
+        'base/address_tracker_linux.h',
         'base/asn1_util.cc',
         'base/asn1_util.h',
         'base/auth.cc',
@@ -194,8 +196,6 @@
         'base/network_change_notifier_linux.h',
         'base/network_change_notifier_mac.cc',
         'base/network_change_notifier_mac.h',
-        'base/network_change_notifier_netlink_linux.cc',
-        'base/network_change_notifier_netlink_linux.h',
         'base/network_change_notifier_win.cc',
         'base/network_change_notifier_win.h',
         'base/network_config_watcher_mac.cc',
@@ -1067,6 +1067,7 @@
       ],
       'sources': [
         'base/address_list_unittest.cc',
+        'base/address_tracker_linux_unittest.cc',
         'base/backoff_entry_unittest.cc',
         'base/big_endian_unittest.cc',
         'base/cert_database_nss_unittest.cc',