diff options
author | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-19 02:07:55 +0000 |
---|---|---|
committer | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-19 02:07:55 +0000 |
commit | 7303224a7fdb9547604cf764251629da1516b82e (patch) | |
tree | 813e34d2ee452bb630c00757727ee13bf566faa6 /net/base/network_change_notifier_linux.cc | |
parent | 61b233a357f91b67bc4f363cd9818a7c28ab796b (diff) | |
download | chromium_src-7303224a7fdb9547604cf764251629da1516b82e.zip chromium_src-7303224a7fdb9547604cf764251629da1516b82e.tar.gz chromium_src-7303224a7fdb9547604cf764251629da1516b82e.tar.bz2 |
Implement NetworkChangeNotifierLinux.
BUG=26156
TEST=Browse a bit. Observe about:net-internals/hostresolver.hostcache to note that the cache is populated with data. sudo /sbin/ifconfig lo down; sudo /sbin/ifconfig lo up. Observe about:net-internals/hostresolver.hostcache to note that the cache should now be empty (or almost empty, since other host resolutions may have happened in the meanwhile).
Review URL: http://codereview.chromium.org/645001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39417 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/network_change_notifier_linux.cc')
-rw-r--r-- | net/base/network_change_notifier_linux.cc | 151 |
1 files changed, 149 insertions, 2 deletions
diff --git a/net/base/network_change_notifier_linux.cc b/net/base/network_change_notifier_linux.cc index e6eba24..647a2ad 100644 --- a/net/base/network_change_notifier_linux.cc +++ b/net/base/network_change_notifier_linux.cc @@ -4,9 +4,156 @@ #include "net/base/network_change_notifier_linux.h" +#include <errno.h> +#include <fcntl.h> +#include <linux/rtnetlink.h> +#include <string.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "net/base/net_errors.h" + namespace net { -NetworkChangeNotifierLinux::NetworkChangeNotifierLinux() {} -NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {} +namespace { + +const int kInvalidSocket = -1; + +// 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; +} + +} // namespace + +NetworkChangeNotifierLinux::NetworkChangeNotifierLinux() + : netlink_fd_(kInvalidSocket), + loop_(MessageLoopForIO::current()) { + netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (netlink_fd_ < 0) { + PLOG(ERROR) << "Error creating netlink socket"; + return; + } + + if (!SetNonBlocking(netlink_fd_)) { + PLOG(ERROR) << "Failed to set netlink socket to non-blocking mode."; + if (close(netlink_fd_) != 0) + PLOG(ERROR) << "Failed to close socket"; + netlink_fd_ = kInvalidSocket; + return; + } + + 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(netlink_fd_, reinterpret_cast<struct sockaddr*>(&local_addr_), + sizeof(local_addr_)); + if (ret < 0) { + PLOG(ERROR) << "Error binding netlink socket"; + if (close(netlink_fd_) != 0) + PLOG(ERROR) << "Failed to close socket"; + netlink_fd_ = kInvalidSocket; + return; + } + + ListenForNotifications(); +} + +NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { + if (netlink_fd_ != kInvalidSocket) { + if (close(netlink_fd_) != 0) + PLOG(ERROR) << "Failed to close socket"; + netlink_fd_ = kInvalidSocket; + netlink_watcher_.StopWatchingFileDescriptor(); + } +} + +void NetworkChangeNotifierLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(fd, netlink_fd_); + + ListenForNotifications(); +} + +void NetworkChangeNotifierLinux::OnFileCanWriteWithoutBlocking(int /* fd */) { + NOTREACHED(); +} + +void NetworkChangeNotifierLinux::ListenForNotifications() { + char buf[4096]; + int rv = ReadNotificationMessage(buf, arraysize(buf)); + while (rv > 0 ) { + const struct nlmsghdr* netlink_message_header = + reinterpret_cast<struct nlmsghdr*>(buf); + HandleNotifications(netlink_message_header, rv); + rv = ReadNotificationMessage(buf, arraysize(buf)); + } + + if (rv == ERR_IO_PENDING) { + rv = loop_->WatchFileDescriptor( + netlink_fd_, false, MessageLoopForIO::WATCH_READ, &netlink_watcher_, + this); + LOG_IF(ERROR, !rv) << "Failed to watch netlink socket: " << netlink_fd_; + } +} + +int NetworkChangeNotifierLinux::ReadNotificationMessage(char* buf, size_t len) { + DCHECK_NE(len, 0u); + DCHECK(buf); + + memset(buf, 0, sizeof(buf)); + int rv = recv(netlink_fd_, buf, len, 0); + if (rv > 0) { + return rv; + } else { + DCHECK_NE(rv, 0); + if (errno != EAGAIN && errno != EWOULDBLOCK) { + PLOG(DFATAL) << "recv"; + return ERR_FAILED; + } + + return ERR_IO_PENDING; + } +} + +void NetworkChangeNotifierLinux::HandleNotifications( + const struct nlmsghdr* netlink_message_header, size_t len) { + 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; + case NLMSG_ERROR: + LOG(ERROR) << "Unexpected netlink error."; + return; + // 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: + case RTM_DELADDR: + helper_.OnIPAddressChanged(); + return; + case RTM_NEWLINK: + case RTM_DELLINK: + return; + default: + LOG(DFATAL) << "Received unexpected netlink message type: " + << netlink_message_type; + return; + } + } +} } // namespace net |