diff options
-rw-r--r-- | net/base/network_change_notifier_linux.cc | 151 | ||||
-rw-r--r-- | net/base/network_change_notifier_linux.h | 35 |
2 files changed, 182 insertions, 4 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 diff --git a/net/base/network_change_notifier_linux.h b/net/base/network_change_notifier_linux.h index 78111c9..609c9c8 100644 --- a/net/base/network_change_notifier_linux.h +++ b/net/base/network_change_notifier_linux.h @@ -5,15 +5,25 @@ #ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_H_ #define NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_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 "base/basictypes.h" +#include "base/message_loop.h" #include "net/base/network_change_notifier_helper.h" +struct nlmsghdr; + namespace net { -class NetworkChangeNotifierLinux : public NetworkChangeNotifier { +class NetworkChangeNotifierLinux + : public NetworkChangeNotifier, public MessageLoopForIO::Watcher { public: NetworkChangeNotifierLinux(); + // NetworkChangeNotifier methods: + virtual void AddObserver(Observer* observer) { helper_.AddObserver(observer); } @@ -22,13 +32,34 @@ class NetworkChangeNotifierLinux : public NetworkChangeNotifier { helper_.RemoveObserver(observer); } + // MessageLoopForIO::Watcher methods: + + virtual void OnFileCanReadWithoutBlocking(int fd); + virtual void OnFileCanWriteWithoutBlocking(int /* fd */); + private: virtual ~NetworkChangeNotifierLinux(); - void OnIPAddressChanged() { helper_.OnIPAddressChanged(); } + // 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); + + // Handles the netlink message and notifies the observers. + void HandleNotifications( + const struct nlmsghdr* netlink_message_header, size_t len); internal::NetworkChangeNotifierHelper helper_; + int netlink_fd_; // This is the netlink socket descriptor. + struct sockaddr_nl local_addr_; + MessageLoopForIO* const loop_; + MessageLoopForIO::FileDescriptorWatcher netlink_watcher_; + DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierLinux); }; |