1// Copyright (c) 2010 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/network_change_notifier_linux.h"
6
7#include <errno.h>
8#include <sys/socket.h>
9
10#include "base/compiler_specific.h"
11#include "base/eintr_wrapper.h"
12#include "base/task.h"
13#include "base/threading/thread.h"
14#include "net/base/net_errors.h"
15#include "net/base/network_change_notifier_netlink_linux.h"
16
17namespace net {
18
19namespace {
20
21const int kInvalidSocket = -1;
22
23}  // namespace
24
25class NetworkChangeNotifierLinux::Thread
26    : public base::Thread, public MessageLoopForIO::Watcher {
27 public:
28  Thread();
29  virtual ~Thread();
30
31  // MessageLoopForIO::Watcher:
32  virtual void OnFileCanReadWithoutBlocking(int fd);
33  virtual void OnFileCanWriteWithoutBlocking(int /* fd */);
34
35 protected:
36  // base::Thread
37  virtual void Init();
38  virtual void CleanUp();
39
40 private:
41  void NotifyObserversOfIPAddressChange() {
42    NetworkChangeNotifier::NotifyObserversOfIPAddressChange();
43  }
44
45  // Starts listening for netlink messages.  Also handles the messages if there
46  // are any available on the netlink socket.
47  void ListenForNotifications();
48
49  // Attempts to read from the netlink socket into |buf| of length |len|.
50  // Returns the bytes read on synchronous success and ERR_IO_PENDING if the
51  // recv() would block.  Otherwise, it returns a net error code.
52  int ReadNotificationMessage(char* buf, size_t len);
53
54  // The netlink socket descriptor.
55  int netlink_fd_;
56  MessageLoopForIO::FileDescriptorWatcher netlink_watcher_;
57
58  // Technically only needed for ChromeOS, but it's ugly to #ifdef out.
59  ScopedRunnableMethodFactory<Thread> method_factory_;
60
61  DISALLOW_COPY_AND_ASSIGN(Thread);
62};
63
64NetworkChangeNotifierLinux::Thread::Thread()
65    : base::Thread("NetworkChangeNotifier"),
66      netlink_fd_(kInvalidSocket),
67      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {}
68
69NetworkChangeNotifierLinux::Thread::~Thread() {}
70
71void NetworkChangeNotifierLinux::Thread::Init() {
72  netlink_fd_ = InitializeNetlinkSocket();
73  if (netlink_fd_ < 0) {
74    netlink_fd_ = kInvalidSocket;
75    return;
76  }
77  ListenForNotifications();
78}
79
80void NetworkChangeNotifierLinux::Thread::CleanUp() {
81  if (netlink_fd_ != kInvalidSocket) {
82    if (HANDLE_EINTR(close(netlink_fd_)) != 0)
83      PLOG(ERROR) << "Failed to close socket";
84    netlink_fd_ = kInvalidSocket;
85    netlink_watcher_.StopWatchingFileDescriptor();
86  }
87}
88
89void NetworkChangeNotifierLinux::Thread::OnFileCanReadWithoutBlocking(int fd) {
90  DCHECK_EQ(fd, netlink_fd_);
91  ListenForNotifications();
92}
93
94void NetworkChangeNotifierLinux::Thread::OnFileCanWriteWithoutBlocking(
95    int /* fd */) {
96  NOTREACHED();
97}
98
99void NetworkChangeNotifierLinux::Thread::ListenForNotifications() {
100  char buf[4096];
101  int rv = ReadNotificationMessage(buf, arraysize(buf));
102  while (rv > 0) {
103    if (HandleNetlinkMessage(buf, rv)) {
104      VLOG(1) << "Detected IP address changes.";
105#if defined(OS_CHROMEOS)
106      // TODO(oshima): chromium-os:8285 - introduced artificial delay to
107      // work around the issue of network load issue after connection
108      // restored. See the bug for more details.
109      //  This should be removed once this bug is properly fixed.
110      const int kObserverNotificationDelayMS = 200;
111      message_loop()->PostDelayedTask(
112          FROM_HERE,
113          method_factory_.NewRunnableMethod(
114              &Thread::NotifyObserversOfIPAddressChange),
115          kObserverNotificationDelayMS);
116#else
117      NotifyObserversOfIPAddressChange();
118#endif
119    }
120    rv = ReadNotificationMessage(buf, arraysize(buf));
121  }
122
123  if (rv == ERR_IO_PENDING) {
124    rv = MessageLoopForIO::current()->WatchFileDescriptor(netlink_fd_, false,
125        MessageLoopForIO::WATCH_READ, &netlink_watcher_, this);
126    LOG_IF(ERROR, !rv) << "Failed to watch netlink socket: " << netlink_fd_;
127  }
128}
129
130int NetworkChangeNotifierLinux::Thread::ReadNotificationMessage(
131    char* buf,
132    size_t len) {
133  DCHECK_NE(len, 0u);
134  DCHECK(buf);
135  memset(buf, 0, sizeof(buf));
136  int rv = recv(netlink_fd_, buf, len, 0);
137  if (rv > 0)
138    return rv;
139
140  DCHECK_NE(rv, 0);
141  if (errno != EAGAIN && errno != EWOULDBLOCK) {
142    PLOG(DFATAL) << "recv";
143    return ERR_FAILED;
144  }
145
146  return ERR_IO_PENDING;
147}
148
149NetworkChangeNotifierLinux::NetworkChangeNotifierLinux()
150    : notifier_thread_(new Thread) {
151  // We create this notifier thread because the notification implementation
152  // needs a MessageLoopForIO, and there's no guarantee that
153  // MessageLoop::current() meets that criterion.
154  base::Thread::Options thread_options(MessageLoop::TYPE_IO, 0);
155  notifier_thread_->StartWithOptions(thread_options);
156}
157
158NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {
159  // We don't need to explicitly Stop(), but doing so allows us to sanity-
160  // check that the notifier thread shut down properly.
161  notifier_thread_->Stop();
162}
163
164bool NetworkChangeNotifierLinux::IsCurrentlyOffline() const {
165  // TODO(eroman): http://crbug.com/53473
166  return false;
167}
168
169}  // namespace net
170