OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "net/base/address_tracker_linux.h" | |
6 | |
7 #include <errno.h> | |
8 #include <linux/if.h> | |
9 #include <sys/ioctl.h> | |
10 | |
11 #include "base/files/scoped_file.h" | |
12 #include "base/logging.h" | |
13 #include "base/posix/eintr_wrapper.h" | |
14 #include "base/threading/thread_restrictions.h" | |
15 #include "net/base/net_util_linux.h" | |
16 | |
17 namespace net { | |
18 namespace internal { | |
19 | |
20 namespace { | |
21 | |
22 // Retrieves address from NETLINK address message. | |
23 // Sets |really_deprecated| for IPv6 addresses with preferred lifetimes of 0. | |
24 bool GetAddress(const struct nlmsghdr* header, | |
25 IPAddressNumber* out, | |
26 bool* really_deprecated) { | |
27 if (really_deprecated) | |
28 *really_deprecated = false; | |
29 const struct ifaddrmsg* msg = | |
30 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header)); | |
31 size_t address_length = 0; | |
32 switch (msg->ifa_family) { | |
33 case AF_INET: | |
34 address_length = kIPv4AddressSize; | |
35 break; | |
36 case AF_INET6: | |
37 address_length = kIPv6AddressSize; | |
38 break; | |
39 default: | |
40 // Unknown family. | |
41 return false; | |
42 } | |
43 // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on | |
44 // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of | |
45 // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6 | |
46 // have the IFA_LOCAL attribute. | |
47 unsigned char* address = NULL; | |
48 unsigned char* local = NULL; | |
49 size_t length = IFA_PAYLOAD(header); | |
50 for (const struct rtattr* attr = | |
51 reinterpret_cast<const struct rtattr*>(IFA_RTA(msg)); | |
52 RTA_OK(attr, length); | |
53 attr = RTA_NEXT(attr, length)) { | |
54 switch (attr->rta_type) { | |
55 case IFA_ADDRESS: | |
56 DCHECK_GE(RTA_PAYLOAD(attr), address_length); | |
57 address = reinterpret_cast<unsigned char*>(RTA_DATA(attr)); | |
58 break; | |
59 case IFA_LOCAL: | |
60 DCHECK_GE(RTA_PAYLOAD(attr), address_length); | |
61 local = reinterpret_cast<unsigned char*>(RTA_DATA(attr)); | |
62 break; | |
63 case IFA_CACHEINFO: { | |
64 const struct ifa_cacheinfo *cache_info = | |
65 reinterpret_cast<const struct ifa_cacheinfo*>(RTA_DATA(attr)); | |
66 if (really_deprecated) | |
67 *really_deprecated = (cache_info->ifa_prefered == 0); | |
68 } break; | |
69 default: | |
70 break; | |
71 } | |
72 } | |
73 if (local) | |
74 address = local; | |
75 if (!address) | |
76 return false; | |
77 out->assign(address, address + address_length); | |
78 return true; | |
79 } | |
80 | |
81 } // namespace | |
82 | |
83 // static | |
84 char* AddressTrackerLinux::GetInterfaceName(int interface_index, char* buf) { | |
85 memset(buf, 0, IFNAMSIZ); | |
86 base::ScopedFD ioctl_socket(socket(AF_INET, SOCK_DGRAM, 0)); | |
87 if (!ioctl_socket.is_valid()) | |
88 return buf; | |
89 | |
90 struct ifreq ifr = {}; | |
91 ifr.ifr_ifindex = interface_index; | |
92 | |
93 if (ioctl(ioctl_socket.get(), SIOCGIFNAME, &ifr) == 0) | |
94 strncpy(buf, ifr.ifr_name, IFNAMSIZ - 1); | |
95 return buf; | |
96 } | |
97 | |
98 AddressTrackerLinux::AddressTrackerLinux() | |
99 : get_interface_name_(GetInterfaceName), | |
100 address_callback_(base::Bind(&base::DoNothing)), | |
101 link_callback_(base::Bind(&base::DoNothing)), | |
102 tunnel_callback_(base::Bind(&base::DoNothing)), | |
103 netlink_fd_(-1), | |
104 connection_type_initialized_(false), | |
105 connection_type_initialized_cv_(&connection_type_lock_), | |
106 current_connection_type_(NetworkChangeNotifier::CONNECTION_NONE), | |
107 tracking_(false) { | |
108 } | |
109 | |
110 AddressTrackerLinux::AddressTrackerLinux(const base::Closure& address_callback, | |
111 const base::Closure& link_callback, | |
112 const base::Closure& tunnel_callback) | |
113 : get_interface_name_(GetInterfaceName), | |
114 address_callback_(address_callback), | |
115 link_callback_(link_callback), | |
116 tunnel_callback_(tunnel_callback), | |
117 netlink_fd_(-1), | |
118 connection_type_initialized_(false), | |
119 connection_type_initialized_cv_(&connection_type_lock_), | |
120 current_connection_type_(NetworkChangeNotifier::CONNECTION_NONE), | |
121 tracking_(true) { | |
122 DCHECK(!address_callback.is_null()); | |
123 DCHECK(!link_callback.is_null()); | |
124 } | |
125 | |
126 AddressTrackerLinux::~AddressTrackerLinux() { | |
127 CloseSocket(); | |
128 } | |
129 | |
130 void AddressTrackerLinux::Init() { | |
131 netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); | |
132 if (netlink_fd_ < 0) { | |
133 PLOG(ERROR) << "Could not create NETLINK socket"; | |
134 AbortAndForceOnline(); | |
135 return; | |
136 } | |
137 | |
138 int rv; | |
139 | |
140 if (tracking_) { | |
141 // Request notifications. | |
142 struct sockaddr_nl addr = {}; | |
143 addr.nl_family = AF_NETLINK; | |
144 addr.nl_pid = getpid(); | |
145 // TODO(szym): Track RTMGRP_LINK as well for ifi_type, | |
146 // http://crbug.com/113993 | |
147 addr.nl_groups = | |
148 RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY | RTMGRP_LINK; | |
149 rv = bind( | |
150 netlink_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)); | |
151 if (rv < 0) { | |
152 PLOG(ERROR) << "Could not bind NETLINK socket"; | |
153 AbortAndForceOnline(); | |
154 return; | |
155 } | |
156 } | |
157 | |
158 // Request dump of addresses. | |
159 struct sockaddr_nl peer = {}; | |
160 peer.nl_family = AF_NETLINK; | |
161 | |
162 struct { | |
163 struct nlmsghdr header; | |
164 struct rtgenmsg msg; | |
165 } request = {}; | |
166 | |
167 request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg)); | |
168 request.header.nlmsg_type = RTM_GETADDR; | |
169 request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; | |
170 request.header.nlmsg_pid = getpid(); | |
171 request.msg.rtgen_family = AF_UNSPEC; | |
172 | |
173 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len, | |
174 0, reinterpret_cast<struct sockaddr*>(&peer), | |
175 sizeof(peer))); | |
176 if (rv < 0) { | |
177 PLOG(ERROR) << "Could not send NETLINK request"; | |
178 AbortAndForceOnline(); | |
179 return; | |
180 } | |
181 | |
182 // Consume pending message to populate the AddressMap, but don't notify. | |
183 // Sending another request without first reading responses results in EBUSY. | |
184 bool address_changed; | |
185 bool link_changed; | |
186 bool tunnel_changed; | |
187 ReadMessages(&address_changed, &link_changed, &tunnel_changed); | |
188 | |
189 // Request dump of link state | |
190 request.header.nlmsg_type = RTM_GETLINK; | |
191 | |
192 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len, 0, | |
193 reinterpret_cast<struct sockaddr*>(&peer), | |
194 sizeof(peer))); | |
195 if (rv < 0) { | |
196 PLOG(ERROR) << "Could not send NETLINK request"; | |
197 AbortAndForceOnline(); | |
198 return; | |
199 } | |
200 | |
201 // Consume pending message to populate links_online_, but don't notify. | |
202 ReadMessages(&address_changed, &link_changed, &tunnel_changed); | |
203 { | |
204 AddressTrackerAutoLock lock(*this, connection_type_lock_); | |
205 connection_type_initialized_ = true; | |
206 connection_type_initialized_cv_.Signal(); | |
207 } | |
208 | |
209 if (tracking_) { | |
210 rv = base::MessageLoopForIO::current()->WatchFileDescriptor( | |
211 netlink_fd_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this); | |
212 if (rv < 0) { | |
213 PLOG(ERROR) << "Could not watch NETLINK socket"; | |
214 AbortAndForceOnline(); | |
215 return; | |
216 } | |
217 } | |
218 } | |
219 | |
220 void AddressTrackerLinux::AbortAndForceOnline() { | |
221 CloseSocket(); | |
222 AddressTrackerAutoLock lock(*this, connection_type_lock_); | |
223 current_connection_type_ = NetworkChangeNotifier::CONNECTION_UNKNOWN; | |
224 connection_type_initialized_ = true; | |
225 connection_type_initialized_cv_.Signal(); | |
226 } | |
227 | |
228 AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const { | |
229 AddressTrackerAutoLock lock(*this, address_map_lock_); | |
230 return address_map_; | |
231 } | |
232 | |
233 base::hash_set<int> AddressTrackerLinux::GetOnlineLinks() const { | |
234 AddressTrackerAutoLock lock(*this, online_links_lock_); | |
235 return online_links_; | |
236 } | |
237 | |
238 NetworkChangeNotifier::ConnectionType | |
239 AddressTrackerLinux::GetCurrentConnectionType() { | |
240 // http://crbug.com/125097 | |
241 base::ThreadRestrictions::ScopedAllowWait allow_wait; | |
242 AddressTrackerAutoLock lock(*this, connection_type_lock_); | |
243 // Make sure the initial connection type is set before returning. | |
244 while (!connection_type_initialized_) { | |
245 connection_type_initialized_cv_.Wait(); | |
246 } | |
247 return current_connection_type_; | |
248 } | |
249 | |
250 void AddressTrackerLinux::ReadMessages(bool* address_changed, | |
251 bool* link_changed, | |
252 bool* tunnel_changed) { | |
253 *address_changed = false; | |
254 *link_changed = false; | |
255 *tunnel_changed = false; | |
256 char buffer[4096]; | |
257 bool first_loop = true; | |
258 for (;;) { | |
259 int rv = HANDLE_EINTR(recv(netlink_fd_, | |
260 buffer, | |
261 sizeof(buffer), | |
262 // Block the first time through loop. | |
263 first_loop ? 0 : MSG_DONTWAIT)); | |
264 first_loop = false; | |
265 if (rv == 0) { | |
266 LOG(ERROR) << "Unexpected shutdown of NETLINK socket."; | |
267 return; | |
268 } | |
269 if (rv < 0) { | |
270 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) | |
271 break; | |
272 PLOG(ERROR) << "Failed to recv from netlink socket"; | |
273 return; | |
274 } | |
275 HandleMessage(buffer, rv, address_changed, link_changed, tunnel_changed); | |
276 } | |
277 if (*link_changed || *address_changed) | |
278 UpdateCurrentConnectionType(); | |
279 } | |
280 | |
281 void AddressTrackerLinux::HandleMessage(char* buffer, | |
282 size_t length, | |
283 bool* address_changed, | |
284 bool* link_changed, | |
285 bool* tunnel_changed) { | |
286 DCHECK(buffer); | |
287 for (struct nlmsghdr* header = reinterpret_cast<struct nlmsghdr*>(buffer); | |
288 NLMSG_OK(header, length); | |
289 header = NLMSG_NEXT(header, length)) { | |
290 switch (header->nlmsg_type) { | |
291 case NLMSG_DONE: | |
292 return; | |
293 case NLMSG_ERROR: { | |
294 const struct nlmsgerr* msg = | |
295 reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header)); | |
296 LOG(ERROR) << "Unexpected netlink error " << msg->error << "."; | |
297 } return; | |
298 case RTM_NEWADDR: { | |
299 IPAddressNumber address; | |
300 bool really_deprecated; | |
301 if (GetAddress(header, &address, &really_deprecated)) { | |
302 AddressTrackerAutoLock lock(*this, address_map_lock_); | |
303 struct ifaddrmsg* msg = | |
304 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header)); | |
305 // Routers may frequently (every few seconds) output the IPv6 ULA | |
306 // prefix which can cause the linux kernel to frequently output two | |
307 // back-to-back messages, one without the deprecated flag and one with | |
308 // the deprecated flag but both with preferred lifetimes of 0. Avoid | |
309 // interpretting this as an actual change by canonicalizing the two | |
310 // messages by setting the deprecated flag based on the preferred | |
311 // lifetime also. http://crbug.com/268042 | |
312 if (really_deprecated) | |
313 msg->ifa_flags |= IFA_F_DEPRECATED; | |
314 // Only indicate change if the address is new or ifaddrmsg info has | |
315 // changed. | |
316 AddressMap::iterator it = address_map_.find(address); | |
317 if (it == address_map_.end()) { | |
318 address_map_.insert(it, std::make_pair(address, *msg)); | |
319 *address_changed = true; | |
320 } else if (memcmp(&it->second, msg, sizeof(*msg))) { | |
321 it->second = *msg; | |
322 *address_changed = true; | |
323 } | |
324 } | |
325 } break; | |
326 case RTM_DELADDR: { | |
327 IPAddressNumber address; | |
328 if (GetAddress(header, &address, NULL)) { | |
329 AddressTrackerAutoLock lock(*this, address_map_lock_); | |
330 if (address_map_.erase(address)) | |
331 *address_changed = true; | |
332 } | |
333 } break; | |
334 case RTM_NEWLINK: { | |
335 const struct ifinfomsg* msg = | |
336 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header)); | |
337 if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) && | |
338 (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) { | |
339 AddressTrackerAutoLock lock(*this, online_links_lock_); | |
340 if (online_links_.insert(msg->ifi_index).second) { | |
341 *link_changed = true; | |
342 if (IsTunnelInterface(msg->ifi_index)) | |
343 *tunnel_changed = true; | |
344 } | |
345 } else { | |
346 AddressTrackerAutoLock lock(*this, online_links_lock_); | |
347 if (online_links_.erase(msg->ifi_index)) { | |
348 *link_changed = true; | |
349 if (IsTunnelInterface(msg->ifi_index)) | |
350 *tunnel_changed = true; | |
351 } | |
352 } | |
353 } break; | |
354 case RTM_DELLINK: { | |
355 const struct ifinfomsg* msg = | |
356 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header)); | |
357 AddressTrackerAutoLock lock(*this, online_links_lock_); | |
358 if (online_links_.erase(msg->ifi_index)) { | |
359 *link_changed = true; | |
360 if (IsTunnelInterface(msg->ifi_index)) | |
361 *tunnel_changed = true; | |
362 } | |
363 } break; | |
364 default: | |
365 break; | |
366 } | |
367 } | |
368 } | |
369 | |
370 void AddressTrackerLinux::OnFileCanReadWithoutBlocking(int fd) { | |
371 DCHECK_EQ(netlink_fd_, fd); | |
372 bool address_changed; | |
373 bool link_changed; | |
374 bool tunnel_changed; | |
375 ReadMessages(&address_changed, &link_changed, &tunnel_changed); | |
376 if (address_changed) | |
377 address_callback_.Run(); | |
378 if (link_changed) | |
379 link_callback_.Run(); | |
380 if (tunnel_changed) | |
381 tunnel_callback_.Run(); | |
382 } | |
383 | |
384 void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {} | |
385 | |
386 void AddressTrackerLinux::CloseSocket() { | |
387 if (netlink_fd_ >= 0 && IGNORE_EINTR(close(netlink_fd_)) < 0) | |
388 PLOG(ERROR) << "Could not close NETLINK socket."; | |
389 netlink_fd_ = -1; | |
390 } | |
391 | |
392 bool AddressTrackerLinux::IsTunnelInterface(int interface_index) const { | |
393 // Linux kernel drivers/net/tun.c uses "tun" name prefix. | |
394 char buf[IFNAMSIZ] = {0}; | |
395 return strncmp(get_interface_name_(interface_index, buf), "tun", 3) == 0; | |
396 } | |
397 | |
398 void AddressTrackerLinux::UpdateCurrentConnectionType() { | |
399 AddressTrackerLinux::AddressMap address_map = GetAddressMap(); | |
400 base::hash_set<int> online_links = GetOnlineLinks(); | |
401 | |
402 // Strip out tunnel interfaces from online_links | |
403 for (base::hash_set<int>::const_iterator it = online_links.begin(); | |
404 it != online_links.end();) { | |
405 if (IsTunnelInterface(*it)) { | |
406 base::hash_set<int>::const_iterator tunnel_it = it; | |
407 ++it; | |
408 online_links.erase(*tunnel_it); | |
409 } else { | |
410 ++it; | |
411 } | |
412 } | |
413 | |
414 NetworkInterfaceList networks; | |
415 NetworkChangeNotifier::ConnectionType type = | |
416 NetworkChangeNotifier::CONNECTION_NONE; | |
417 if (GetNetworkListImpl(&networks, 0, online_links, address_map, | |
418 get_interface_name_)) { | |
419 type = NetworkChangeNotifier::ConnectionTypeFromInterfaceList(networks); | |
420 } else { | |
421 type = online_links.empty() ? NetworkChangeNotifier::CONNECTION_NONE | |
422 : NetworkChangeNotifier::CONNECTION_UNKNOWN; | |
423 } | |
424 | |
425 AddressTrackerAutoLock lock(*this, connection_type_lock_); | |
426 current_connection_type_ = type; | |
427 } | |
428 | |
429 AddressTrackerLinux::AddressTrackerAutoLock::AddressTrackerAutoLock( | |
430 const AddressTrackerLinux& tracker, | |
431 base::Lock& lock) | |
432 : tracker_(tracker), lock_(lock) { | |
433 if (tracker_.tracking_) { | |
434 lock_.Acquire(); | |
435 } else { | |
436 DCHECK(tracker_.thread_checker_.CalledOnValidThread()); | |
437 } | |
438 } | |
439 | |
440 AddressTrackerLinux::AddressTrackerAutoLock::~AddressTrackerAutoLock() { | |
441 if (tracker_.tracking_) { | |
442 lock_.AssertAcquired(); | |
443 lock_.Release(); | |
444 } | |
445 } | |
446 | |
447 } // namespace internal | |
448 } // namespace net | |
OLD | NEW |