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
10 #include "base/logging.h"
11 #include "base/posix/eintr_wrapper.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "net/base/network_change_notifier_linux.h"
14
15 namespace net {
16 namespace internal {
17
18 namespace {
19
20 // Retrieves address from NETLINK address message.
GetAddress(const struct nlmsghdr * header,IPAddressNumber * out)21 bool GetAddress(const struct nlmsghdr* header, IPAddressNumber* out) {
22 const struct ifaddrmsg* msg =
23 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
24 size_t address_length = 0;
25 switch (msg->ifa_family) {
26 case AF_INET:
27 address_length = kIPv4AddressSize;
28 break;
29 case AF_INET6:
30 address_length = kIPv6AddressSize;
31 break;
32 default:
33 // Unknown family.
34 return false;
35 }
36 // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on
37 // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of
38 // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6
39 // have the IFA_LOCAL attribute.
40 unsigned char* address = NULL;
41 unsigned char* local = NULL;
42 size_t length = IFA_PAYLOAD(header);
43 for (const struct rtattr* attr =
44 reinterpret_cast<const struct rtattr*>(IFA_RTA(msg));
45 RTA_OK(attr, length);
46 attr = RTA_NEXT(attr, length)) {
47 switch (attr->rta_type) {
48 case IFA_ADDRESS:
49 DCHECK_GE(RTA_PAYLOAD(attr), address_length);
50 address = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
51 break;
52 case IFA_LOCAL:
53 DCHECK_GE(RTA_PAYLOAD(attr), address_length);
54 local = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
55 break;
56 default:
57 break;
58 }
59 }
60 if (local)
61 address = local;
62 if (!address)
63 return false;
64 out->assign(address, address + address_length);
65 return true;
66 }
67
68 } // namespace
69
AddressTrackerLinux(const base::Closure & address_callback,const base::Closure & link_callback)70 AddressTrackerLinux::AddressTrackerLinux(const base::Closure& address_callback,
71 const base::Closure& link_callback)
72 : address_callback_(address_callback),
73 link_callback_(link_callback),
74 netlink_fd_(-1),
75 is_offline_(true),
76 is_offline_initialized_(false),
77 is_offline_initialized_cv_(&is_offline_lock_) {
78 DCHECK(!address_callback.is_null());
79 DCHECK(!link_callback.is_null());
80 }
81
~AddressTrackerLinux()82 AddressTrackerLinux::~AddressTrackerLinux() {
83 CloseSocket();
84 }
85
Init()86 void AddressTrackerLinux::Init() {
87 netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
88 if (netlink_fd_ < 0) {
89 PLOG(ERROR) << "Could not create NETLINK socket";
90 AbortAndForceOnline();
91 return;
92 }
93
94 // Request notifications.
95 struct sockaddr_nl addr = {};
96 addr.nl_family = AF_NETLINK;
97 addr.nl_pid = getpid();
98 // TODO(szym): Track RTMGRP_LINK as well for ifi_type, http://crbug.com/113993
99 addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY |
100 RTMGRP_LINK;
101 int rv = bind(netlink_fd_,
102 reinterpret_cast<struct sockaddr*>(&addr),
103 sizeof(addr));
104 if (rv < 0) {
105 PLOG(ERROR) << "Could not bind NETLINK socket";
106 AbortAndForceOnline();
107 return;
108 }
109
110 // Request dump of addresses.
111 struct sockaddr_nl peer = {};
112 peer.nl_family = AF_NETLINK;
113
114 struct {
115 struct nlmsghdr header;
116 struct rtgenmsg msg;
117 } request = {};
118
119 request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
120 request.header.nlmsg_type = RTM_GETADDR;
121 request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
122 request.header.nlmsg_pid = getpid();
123 request.msg.rtgen_family = AF_UNSPEC;
124
125 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len,
126 0, reinterpret_cast<struct sockaddr*>(&peer),
127 sizeof(peer)));
128 if (rv < 0) {
129 PLOG(ERROR) << "Could not send NETLINK request";
130 AbortAndForceOnline();
131 return;
132 }
133
134 // Consume pending message to populate the AddressMap, but don't notify.
135 // Sending another request without first reading responses results in EBUSY.
136 bool address_changed;
137 bool link_changed;
138 ReadMessages(&address_changed, &link_changed);
139
140 // Request dump of link state
141 request.header.nlmsg_type = RTM_GETLINK;
142
143 rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len, 0,
144 reinterpret_cast<struct sockaddr*>(&peer),
145 sizeof(peer)));
146 if (rv < 0) {
147 PLOG(ERROR) << "Could not send NETLINK request";
148 AbortAndForceOnline();
149 return;
150 }
151
152 // Consume pending message to populate links_online_, but don't notify.
153 ReadMessages(&address_changed, &link_changed);
154 {
155 base::AutoLock lock(is_offline_lock_);
156 is_offline_initialized_ = true;
157 is_offline_initialized_cv_.Signal();
158 }
159
160 rv = base::MessageLoopForIO::current()->WatchFileDescriptor(
161 netlink_fd_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this);
162 if (rv < 0) {
163 PLOG(ERROR) << "Could not watch NETLINK socket";
164 AbortAndForceOnline();
165 return;
166 }
167 }
168
AbortAndForceOnline()169 void AddressTrackerLinux::AbortAndForceOnline() {
170 CloseSocket();
171 base::AutoLock lock(is_offline_lock_);
172 is_offline_ = false;
173 is_offline_initialized_ = true;
174 is_offline_initialized_cv_.Signal();
175 }
176
GetAddressMap() const177 AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const {
178 base::AutoLock lock(address_map_lock_);
179 return address_map_;
180 }
181
182 NetworkChangeNotifier::ConnectionType
GetCurrentConnectionType()183 AddressTrackerLinux::GetCurrentConnectionType() {
184 // http://crbug.com/125097
185 base::ThreadRestrictions::ScopedAllowWait allow_wait;
186 base::AutoLock lock(is_offline_lock_);
187 // Make sure the initial offline state is set before returning.
188 while (!is_offline_initialized_) {
189 is_offline_initialized_cv_.Wait();
190 }
191 // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
192 // http://crbug.com/160537
193 return is_offline_ ? NetworkChangeNotifier::CONNECTION_NONE :
194 NetworkChangeNotifier::CONNECTION_UNKNOWN;
195 }
196
ReadMessages(bool * address_changed,bool * link_changed)197 void AddressTrackerLinux::ReadMessages(bool* address_changed,
198 bool* link_changed) {
199 *address_changed = false;
200 *link_changed = false;
201 char buffer[4096];
202 bool first_loop = true;
203 for (;;) {
204 int rv = HANDLE_EINTR(recv(netlink_fd_,
205 buffer,
206 sizeof(buffer),
207 // Block the first time through loop.
208 first_loop ? 0 : MSG_DONTWAIT));
209 first_loop = false;
210 if (rv == 0) {
211 LOG(ERROR) << "Unexpected shutdown of NETLINK socket.";
212 return;
213 }
214 if (rv < 0) {
215 if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
216 break;
217 PLOG(ERROR) << "Failed to recv from netlink socket";
218 return;
219 }
220 HandleMessage(buffer, rv, address_changed, link_changed);
221 };
222 if (*link_changed) {
223 base::AutoLock lock(is_offline_lock_);
224 is_offline_ = online_links_.empty();
225 }
226 }
227
HandleMessage(const char * buffer,size_t length,bool * address_changed,bool * link_changed)228 void AddressTrackerLinux::HandleMessage(const char* buffer,
229 size_t length,
230 bool* address_changed,
231 bool* link_changed) {
232 DCHECK(buffer);
233 for (const struct nlmsghdr* header =
234 reinterpret_cast<const struct nlmsghdr*>(buffer);
235 NLMSG_OK(header, length);
236 header = NLMSG_NEXT(header, length)) {
237 switch (header->nlmsg_type) {
238 case NLMSG_DONE:
239 return;
240 case NLMSG_ERROR: {
241 const struct nlmsgerr* msg =
242 reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header));
243 LOG(ERROR) << "Unexpected netlink error " << msg->error << ".";
244 } return;
245 case RTM_NEWADDR: {
246 IPAddressNumber address;
247 if (GetAddress(header, &address)) {
248 base::AutoLock lock(address_map_lock_);
249 const struct ifaddrmsg* msg =
250 reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
251 // Only indicate change if the address is new or ifaddrmsg info has
252 // changed.
253 AddressMap::iterator it = address_map_.find(address);
254 if (it == address_map_.end()) {
255 address_map_.insert(it, std::make_pair(address, *msg));
256 *address_changed = true;
257 } else if (memcmp(&it->second, msg, sizeof(*msg))) {
258 it->second = *msg;
259 *address_changed = true;
260 }
261 }
262 } break;
263 case RTM_DELADDR: {
264 IPAddressNumber address;
265 if (GetAddress(header, &address)) {
266 base::AutoLock lock(address_map_lock_);
267 if (address_map_.erase(address))
268 *address_changed = true;
269 }
270 } break;
271 case RTM_NEWLINK: {
272 const struct ifinfomsg* msg =
273 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
274 if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) &&
275 (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) {
276 if (online_links_.insert(msg->ifi_index).second)
277 *link_changed = true;
278 } else {
279 if (online_links_.erase(msg->ifi_index))
280 *link_changed = true;
281 }
282 } break;
283 case RTM_DELLINK: {
284 const struct ifinfomsg* msg =
285 reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
286 if (online_links_.erase(msg->ifi_index))
287 *link_changed = true;
288 } break;
289 default:
290 break;
291 }
292 }
293 }
294
OnFileCanReadWithoutBlocking(int fd)295 void AddressTrackerLinux::OnFileCanReadWithoutBlocking(int fd) {
296 DCHECK_EQ(netlink_fd_, fd);
297 bool address_changed;
298 bool link_changed;
299 ReadMessages(&address_changed, &link_changed);
300 if (address_changed)
301 address_callback_.Run();
302 if (link_changed)
303 link_callback_.Run();
304 }
305
OnFileCanWriteWithoutBlocking(int)306 void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {}
307
CloseSocket()308 void AddressTrackerLinux::CloseSocket() {
309 if (netlink_fd_ >= 0 && IGNORE_EINTR(close(netlink_fd_)) < 0)
310 PLOG(ERROR) << "Could not close NETLINK socket.";
311 netlink_fd_ = -1;
312 }
313
314 } // namespace internal
315 } // namespace net
316