network_change_notifier_linux.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
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/eintr_wrapper.h"
11#include "base/task.h"
12#include "base/thread.h"
13#include "net/base/net_errors.h"
14#include "net/base/network_change_notifier_netlink_linux.h"
15
16// We only post tasks to a child thread we own, so we don't need refcounting.
17DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierLinux);
18
19namespace net {
20
21namespace {
22
23const int kInvalidSocket = -1;
24
25}  // namespace
26
27NetworkChangeNotifierLinux::NetworkChangeNotifierLinux()
28    : notifier_thread_(new base::Thread("NetworkChangeNotifier")),
29      netlink_fd_(kInvalidSocket) {
30  // We create this notifier thread because the notification implementation
31  // needs a MessageLoopForIO, and there's no guarantee that
32  // MessageLoop::current() meets that criterion.
33  base::Thread::Options thread_options(MessageLoop::TYPE_IO, 0);
34  notifier_thread_->StartWithOptions(thread_options);
35  notifier_thread_->message_loop()->PostTask(FROM_HERE,
36      NewRunnableMethod(this, &NetworkChangeNotifierLinux::Init));
37}
38
39NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {
40  // We don't need to explicitly Stop(), but doing so allows us to sanity-
41  // check that the notifier thread shut down properly.
42  notifier_thread_->Stop();
43  DCHECK_EQ(kInvalidSocket, netlink_fd_);
44}
45
46void NetworkChangeNotifierLinux::WillDestroyCurrentMessageLoop() {
47  DCHECK(notifier_thread_ != NULL);
48  // We can't check the notifier_thread_'s message_loop(), as it's now 0.
49  // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
50
51  if (netlink_fd_ != kInvalidSocket) {
52    if (HANDLE_EINTR(close(netlink_fd_)) != 0)
53      PLOG(ERROR) << "Failed to close socket";
54    netlink_fd_ = kInvalidSocket;
55    netlink_watcher_.StopWatchingFileDescriptor();
56  }
57}
58
59void NetworkChangeNotifierLinux::OnFileCanReadWithoutBlocking(int fd) {
60  DCHECK(notifier_thread_ != NULL);
61  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
62
63  DCHECK_EQ(fd, netlink_fd_);
64  ListenForNotifications();
65}
66
67void NetworkChangeNotifierLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {
68  DCHECK(notifier_thread_ != NULL);
69  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
70
71  NOTREACHED();
72}
73
74void NetworkChangeNotifierLinux::Init() {
75  DCHECK(notifier_thread_ != NULL);
76  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
77
78  netlink_fd_ = InitializeNetlinkSocket();
79  if (netlink_fd_ < 0) {
80    netlink_fd_ = kInvalidSocket;
81    return;
82  }
83  MessageLoop::current()->AddDestructionObserver(this);
84  ListenForNotifications();
85}
86
87void NetworkChangeNotifierLinux::ListenForNotifications() {
88  DCHECK(notifier_thread_ != NULL);
89  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
90
91  char buf[4096];
92  int rv = ReadNotificationMessage(buf, arraysize(buf));
93  while (rv > 0) {
94    if (HandleNetlinkMessage(buf, rv)) {
95      LOG(INFO) << "Detected IP address changes.";
96#if defined(OS_CHROMEOS)
97      // TODO(zelidrag): chromium-os:3996 - introduced artificial delay to
98      // work around the issue of proxy initialization before name resolving
99      // is functional in ChromeOS. This should be removed once this bug
100      // is properly fixed.
101      const int kObserverNotificationDelayMS = 500;
102      MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableFunction(
103          &NetworkChangeNotifier::NotifyObserversOfIPAddressChange),
104          kObserverNotificationDelayMS);
105#else
106      NotifyObserversOfIPAddressChange();
107#endif
108    }
109    rv = ReadNotificationMessage(buf, arraysize(buf));
110  }
111
112  if (rv == ERR_IO_PENDING) {
113    rv = MessageLoopForIO::current()->WatchFileDescriptor(netlink_fd_, false,
114        MessageLoopForIO::WATCH_READ, &netlink_watcher_, this);
115    LOG_IF(ERROR, !rv) << "Failed to watch netlink socket: " << netlink_fd_;
116  }
117}
118
119int NetworkChangeNotifierLinux::ReadNotificationMessage(char* buf, size_t len) {
120  DCHECK(notifier_thread_ != NULL);
121  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
122
123  DCHECK_NE(len, 0u);
124  DCHECK(buf);
125  memset(buf, 0, sizeof(buf));
126  int rv = recv(netlink_fd_, buf, len, 0);
127  if (rv > 0)
128    return rv;
129
130  DCHECK_NE(rv, 0);
131  if (errno != EAGAIN && errno != EWOULDBLOCK) {
132    PLOG(DFATAL) << "recv";
133    return ERR_FAILED;
134  }
135
136  return ERR_IO_PENDING;
137}
138
139}  // namespace net
140