Temporarily make lastchange use only the git hash.
[chromium-blink-merge.git] / net / base / address_tracker_linux.cc
blob367381366954f3280e9171f5d93405f05ed667d7
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/base/address_tracker_linux.h"
7 #include <errno.h>
8 #include <linux/if.h>
9 #include <sys/ioctl.h>
11 #include "base/logging.h"
12 #include "base/posix/eintr_wrapper.h"
13 #include "base/threading/thread_restrictions.h"
15 namespace net {
16 namespace internal {
18 namespace {
20 // Retrieves address from NETLINK address message.
21 // Sets |really_deprecated| for IPv6 addresses with preferred lifetimes of 0.
22 bool GetAddress(const struct nlmsghdr* header,
23 IPAddressNumber* out,
24 bool* really_deprecated) {
25 if (really_deprecated)
26 *really_deprecated = false;
27 const struct ifaddrmsg* msg =
28 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
29 size_t address_length = 0;
30 switch (msg->ifa_family) {
31 case AF_INET:
32 address_length = kIPv4AddressSize;
33 break;
34 case AF_INET6:
35 address_length = kIPv6AddressSize;
36 break;
37 default:
38 // Unknown family.
39 return false;
41 // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on
42 // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of
43 // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6
44 // have the IFA_LOCAL attribute.
45 unsigned char* address = NULL;
46 unsigned char* local = NULL;
47 size_t length = IFA_PAYLOAD(header);
48 for (const struct rtattr* attr =
49 reinterpret_cast<const struct rtattr*>(IFA_RTA(msg));
50 RTA_OK(attr, length);
51 attr = RTA_NEXT(attr, length)) {
52 switch (attr->rta_type) {
53 case IFA_ADDRESS:
54 DCHECK_GE(RTA_PAYLOAD(attr), address_length);
55 address = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
56 break;
57 case IFA_LOCAL:
58 DCHECK_GE(RTA_PAYLOAD(attr), address_length);
59 local = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
60 break;
61 case IFA_CACHEINFO: {
62 const struct ifa_cacheinfo *cache_info =
63 reinterpret_cast<const struct ifa_cacheinfo*>(RTA_DATA(attr));
64 if (really_deprecated)
65 *really_deprecated = (cache_info->ifa_prefered == 0);
66 } break;
67 default:
68 break;
71 if (local)
72 address = local;
73 if (!address)
74 return false;
75 out->assign(address, address + address_length);
76 return true;
79 // Returns the name for the interface with interface index |interface_index|.
80 // The return value points to a function-scoped static so it may be changed by
81 // subsequent calls. This function could be replaced with if_indextoname() but
82 // net/if.h cannot be mixed with linux/if.h so we'll stick with exclusively
83 // talking to the kernel and not the C library.
84 const char* GetInterfaceName(int interface_index) {
85 int ioctl_socket = socket(AF_INET, SOCK_DGRAM, 0);
86 if (ioctl_socket < 0)
87 return "";
88 static struct ifreq ifr;
89 memset(&ifr, 0, sizeof(ifr));
90 ifr.ifr_ifindex = interface_index;
91 int rv = ioctl(ioctl_socket, SIOCGIFNAME, &ifr);
92 close(ioctl_socket);
93 if (rv != 0)
94 return "";
95 return ifr.ifr_name;
98 } // namespace
100 AddressTrackerLinux::AddressTrackerLinux(const base::Closure& address_callback,
101 const base::Closure& link_callback,
102 const base::Closure& tunnel_callback)
103 : get_interface_name_(GetInterfaceName),
104 address_callback_(address_callback),
105 link_callback_(link_callback),
106 tunnel_callback_(tunnel_callback),
107 netlink_fd_(-1),
108 is_offline_(true),
109 is_offline_initialized_(false),
110 is_offline_initialized_cv_(&is_offline_lock_) {
111 DCHECK(!address_callback.is_null());
112 DCHECK(!link_callback.is_null());
115 AddressTrackerLinux::~AddressTrackerLinux() {
116 CloseSocket();
119 void AddressTrackerLinux::Init() {
120 netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
121 if (netlink_fd_ < 0) {
122 PLOG(ERROR) << "Could not create NETLINK socket";
123 AbortAndForceOnline();
124 return;
127 // Request notifications.
128 struct sockaddr_nl addr = {};
129 addr.nl_family = AF_NETLINK;
130 addr.nl_pid = getpid();
131 // TODO(szym): Track RTMGRP_LINK as well for ifi_type, http://crbug.com/113993
132 addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY |
133 RTMGRP_LINK;
134 int rv = bind(netlink_fd_,
135 reinterpret_cast<struct sockaddr*>(&addr),
136 sizeof(addr));
137 if (rv < 0) {
138 PLOG(ERROR) << "Could not bind NETLINK socket";
139 AbortAndForceOnline();
140 return;
143 // Request dump of addresses.
144 struct sockaddr_nl peer = {};
145 peer.nl_family = AF_NETLINK;
147 struct {
148 struct nlmsghdr header;
149 struct rtgenmsg msg;
150 } request = {};
152 request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
153 request.header.nlmsg_type = RTM_GETADDR;
154 request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
155 request.header.nlmsg_pid = getpid();
156 request.msg.rtgen_family = AF_UNSPEC;
158 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len,
159 0, reinterpret_cast<struct sockaddr*>(&peer),
160 sizeof(peer)));
161 if (rv < 0) {
162 PLOG(ERROR) << "Could not send NETLINK request";
163 AbortAndForceOnline();
164 return;
167 // Consume pending message to populate the AddressMap, but don't notify.
168 // Sending another request without first reading responses results in EBUSY.
169 bool address_changed;
170 bool link_changed;
171 bool tunnel_changed;
172 ReadMessages(&address_changed, &link_changed, &tunnel_changed);
174 // Request dump of link state
175 request.header.nlmsg_type = RTM_GETLINK;
177 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len, 0,
178 reinterpret_cast<struct sockaddr*>(&peer),
179 sizeof(peer)));
180 if (rv < 0) {
181 PLOG(ERROR) << "Could not send NETLINK request";
182 AbortAndForceOnline();
183 return;
186 // Consume pending message to populate links_online_, but don't notify.
187 ReadMessages(&address_changed, &link_changed, &tunnel_changed);
189 base::AutoLock lock(is_offline_lock_);
190 is_offline_initialized_ = true;
191 is_offline_initialized_cv_.Signal();
194 rv = base::MessageLoopForIO::current()->WatchFileDescriptor(
195 netlink_fd_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this);
196 if (rv < 0) {
197 PLOG(ERROR) << "Could not watch NETLINK socket";
198 AbortAndForceOnline();
199 return;
203 void AddressTrackerLinux::AbortAndForceOnline() {
204 CloseSocket();
205 base::AutoLock lock(is_offline_lock_);
206 is_offline_ = false;
207 is_offline_initialized_ = true;
208 is_offline_initialized_cv_.Signal();
211 AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const {
212 base::AutoLock lock(address_map_lock_);
213 return address_map_;
216 NetworkChangeNotifier::ConnectionType
217 AddressTrackerLinux::GetCurrentConnectionType() {
218 // http://crbug.com/125097
219 base::ThreadRestrictions::ScopedAllowWait allow_wait;
220 base::AutoLock lock(is_offline_lock_);
221 // Make sure the initial offline state is set before returning.
222 while (!is_offline_initialized_) {
223 is_offline_initialized_cv_.Wait();
225 // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
226 // http://crbug.com/160537
227 return is_offline_ ? NetworkChangeNotifier::CONNECTION_NONE :
228 NetworkChangeNotifier::CONNECTION_UNKNOWN;
231 void AddressTrackerLinux::ReadMessages(bool* address_changed,
232 bool* link_changed,
233 bool* tunnel_changed) {
234 *address_changed = false;
235 *link_changed = false;
236 *tunnel_changed = false;
237 char buffer[4096];
238 bool first_loop = true;
239 for (;;) {
240 int rv = HANDLE_EINTR(recv(netlink_fd_,
241 buffer,
242 sizeof(buffer),
243 // Block the first time through loop.
244 first_loop ? 0 : MSG_DONTWAIT));
245 first_loop = false;
246 if (rv == 0) {
247 LOG(ERROR) << "Unexpected shutdown of NETLINK socket.";
248 return;
250 if (rv < 0) {
251 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
252 break;
253 PLOG(ERROR) << "Failed to recv from netlink socket";
254 return;
256 HandleMessage(buffer, rv, address_changed, link_changed, tunnel_changed);
258 if (*link_changed) {
259 base::AutoLock lock(is_offline_lock_);
260 is_offline_ = online_links_.empty();
264 void AddressTrackerLinux::HandleMessage(char* buffer,
265 size_t length,
266 bool* address_changed,
267 bool* link_changed,
268 bool* tunnel_changed) {
269 DCHECK(buffer);
270 for (struct nlmsghdr* header = reinterpret_cast<struct nlmsghdr*>(buffer);
271 NLMSG_OK(header, length);
272 header = NLMSG_NEXT(header, length)) {
273 switch (header->nlmsg_type) {
274 case NLMSG_DONE:
275 return;
276 case NLMSG_ERROR: {
277 const struct nlmsgerr* msg =
278 reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header));
279 LOG(ERROR) << "Unexpected netlink error " << msg->error << ".";
280 } return;
281 case RTM_NEWADDR: {
282 IPAddressNumber address;
283 bool really_deprecated;
284 if (GetAddress(header, &address, &really_deprecated)) {
285 base::AutoLock lock(address_map_lock_);
286 struct ifaddrmsg* msg =
287 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
288 // Routers may frequently (every few seconds) output the IPv6 ULA
289 // prefix which can cause the linux kernel to frequently output two
290 // back-to-back messages, one without the deprecated flag and one with
291 // the deprecated flag but both with preferred lifetimes of 0. Avoid
292 // interpretting this as an actual change by canonicalizing the two
293 // messages by setting the deprecated flag based on the preferred
294 // lifetime also. http://crbug.com/268042
295 if (really_deprecated)
296 msg->ifa_flags |= IFA_F_DEPRECATED;
297 // Only indicate change if the address is new or ifaddrmsg info has
298 // changed.
299 AddressMap::iterator it = address_map_.find(address);
300 if (it == address_map_.end()) {
301 address_map_.insert(it, std::make_pair(address, *msg));
302 *address_changed = true;
303 } else if (memcmp(&it->second, msg, sizeof(*msg))) {
304 it->second = *msg;
305 *address_changed = true;
308 } break;
309 case RTM_DELADDR: {
310 IPAddressNumber address;
311 if (GetAddress(header, &address, NULL)) {
312 base::AutoLock lock(address_map_lock_);
313 if (address_map_.erase(address))
314 *address_changed = true;
316 } break;
317 case RTM_NEWLINK: {
318 const struct ifinfomsg* msg =
319 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
320 if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) &&
321 (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) {
322 if (online_links_.insert(msg->ifi_index).second) {
323 *link_changed = true;
324 if (IsTunnelInterface(msg))
325 *tunnel_changed = true;
327 } else {
328 if (online_links_.erase(msg->ifi_index)) {
329 *link_changed = true;
330 if (IsTunnelInterface(msg))
331 *tunnel_changed = true;
334 } break;
335 case RTM_DELLINK: {
336 const struct ifinfomsg* msg =
337 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
338 if (online_links_.erase(msg->ifi_index)) {
339 *link_changed = true;
340 if (IsTunnelInterface(msg))
341 *tunnel_changed = true;
343 } break;
344 default:
345 break;
350 void AddressTrackerLinux::OnFileCanReadWithoutBlocking(int fd) {
351 DCHECK_EQ(netlink_fd_, fd);
352 bool address_changed;
353 bool link_changed;
354 bool tunnel_changed;
355 ReadMessages(&address_changed, &link_changed, &tunnel_changed);
356 if (address_changed)
357 address_callback_.Run();
358 if (link_changed)
359 link_callback_.Run();
360 if (tunnel_changed)
361 tunnel_callback_.Run();
364 void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {}
366 void AddressTrackerLinux::CloseSocket() {
367 if (netlink_fd_ >= 0 && IGNORE_EINTR(close(netlink_fd_)) < 0)
368 PLOG(ERROR) << "Could not close NETLINK socket.";
369 netlink_fd_ = -1;
372 bool AddressTrackerLinux::IsTunnelInterface(const struct ifinfomsg* msg) const {
373 // Linux kernel drivers/net/tun.c uses "tun" name prefix.
374 return strncmp(get_interface_name_(msg->ifi_index), "tun", 3) == 0;
377 } // namespace internal
378 } // namespace net