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
15namespace net {
16namespace internal {
17
18namespace {
19
20// Retrieves address from NETLINK address message.
21bool 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
70AddressTrackerLinux::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
82AddressTrackerLinux::~AddressTrackerLinux() {
83  CloseSocket();
84}
85
86void 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
169void 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
177AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const {
178  base::AutoLock lock(address_map_lock_);
179  return address_map_;
180}
181
182NetworkChangeNotifier::ConnectionType
183AddressTrackerLinux::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
197void 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
228void 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
295void 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
306void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {}
307
308void 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