Skip to content

Commit

Permalink
[net] Adds AddressTrackerLinux which keeps track of interface address…
Browse files Browse the repository at this point in the history
…es 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
  • Loading branch information
szym@chromium.org committed Jul 16, 2012
1 parent 053566a commit 6717280
Show file tree
Hide file tree
Showing 10 changed files with 607 additions and 240 deletions.
226 changes: 226 additions & 0 deletions net/base/address_tracker_linux.cc
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions net/base/address_tracker_linux.h
Original file line number Diff line number Diff line change
@@ -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_
Loading

0 comments on commit 6717280

Please sign in to comment.