privet_traffic_detector.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright 2013 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 "chrome/browser/local_discovery/privet_traffic_detector.h"
6
7#include "base/metrics/histogram.h"
8#include "base/sys_byteorder.h"
9#include "net/base/dns_util.h"
10#include "net/base/net_errors.h"
11#include "net/base/net_log.h"
12#include "net/dns/dns_protocol.h"
13#include "net/dns/dns_response.h"
14#include "net/dns/mdns_client.h"
15#include "net/udp/datagram_server_socket.h"
16#include "net/udp/udp_server_socket.h"
17
18namespace {
19
20const int kMaxRestartAttempts = 10;
21const char kPrivetDeviceTypeDnsString[] = "\x07_privet\x04_tcp\x05local";
22
23void GetNetworkListOnFileThread(
24    const base::Callback<void(const net::NetworkInterfaceList&)> callback) {
25  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
26  net::NetworkInterfaceList networks;
27  if (!GetNetworkList(&networks))
28    return;
29
30  net::NetworkInterfaceList ip4_networks;
31  for (size_t i = 0; i < networks.size(); ++i) {
32    net::AddressFamily address_family =
33        net::GetAddressFamily(networks[i].address);
34    if (address_family == net::ADDRESS_FAMILY_IPV4 &&
35        networks[i].network_prefix >= 24) {
36      ip4_networks.push_back(networks[i]);
37    }
38  }
39
40  net::IPAddressNumber localhost_prefix(4, 0);
41  localhost_prefix[0] = 127;
42  ip4_networks.push_back(net::NetworkInterface("lo", localhost_prefix, 8));
43  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
44                                   base::Bind(callback, ip4_networks));
45}
46
47bool IsIpPrefixEqual(const net::IPAddressNumber& ip1,
48                     const net::IPAddressNumber& ip2) {
49  return !ip1.empty() && ip1.size() == ip2.size() &&
50         std::equal(ip1.begin(), ip1.end() - 1, ip2.begin());
51}
52
53}  // namespace
54
55namespace local_discovery {
56
57PrivetTrafficDetector::PrivetTrafficDetector(
58    net::AddressFamily address_family,
59    const base::Closure& on_traffic_detected)
60    : on_traffic_detected_(on_traffic_detected),
61      callback_runner_(base::MessageLoop::current()->message_loop_proxy()),
62      address_family_(address_family),
63      io_buffer_(
64          new net::IOBufferWithSize(net::dns_protocol::kMaxMulticastSize)),
65      restart_attempts_(kMaxRestartAttempts),
66      weak_ptr_factory_(this) {
67}
68
69void PrivetTrafficDetector::Start() {
70  content::BrowserThread::PostTask(
71      content::BrowserThread::IO,
72      FROM_HERE,
73      base::Bind(&PrivetTrafficDetector::StartOnIOThread,
74                 weak_ptr_factory_.GetWeakPtr()));
75}
76
77PrivetTrafficDetector::~PrivetTrafficDetector() {
78  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
79  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
80}
81
82void PrivetTrafficDetector::StartOnIOThread() {
83  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
84  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
85  ScheduleRestart();
86}
87
88void PrivetTrafficDetector::OnNetworkChanged(
89    net::NetworkChangeNotifier::ConnectionType type) {
90  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
91  restart_attempts_ = kMaxRestartAttempts;
92  if (type != net::NetworkChangeNotifier::CONNECTION_NONE)
93    ScheduleRestart();
94}
95
96void PrivetTrafficDetector::ScheduleRestart() {
97  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
98  socket_.reset();
99  weak_ptr_factory_.InvalidateWeakPtrs();
100  content::BrowserThread::PostDelayedTask(
101      content::BrowserThread::FILE,
102      FROM_HERE,
103      base::Bind(&GetNetworkListOnFileThread,
104                 base::Bind(&PrivetTrafficDetector::Restart,
105                            weak_ptr_factory_.GetWeakPtr())),
106      base::TimeDelta::FromSeconds(3));
107}
108
109void PrivetTrafficDetector::Restart(const net::NetworkInterfaceList& networks) {
110  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
111  networks_ = networks;
112  if (Bind() < net::OK || DoLoop(0) < net::OK) {
113    if ((restart_attempts_--) > 0)
114      ScheduleRestart();
115  } else {
116    // Reset on success.
117    restart_attempts_ = kMaxRestartAttempts;
118  }
119}
120
121int PrivetTrafficDetector::Bind() {
122  if (!start_time_.is_null()) {
123    base::TimeDelta time_delta = base::Time::Now() - start_time_;
124    UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorRestartTime", time_delta);
125  }
126  start_time_ = base::Time::Now();
127  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
128  socket_.reset(new net::UDPServerSocket(NULL, net::NetLog::Source()));
129  net::IPEndPoint multicast_addr = net::GetMDnsIPEndPoint(address_family_);
130  net::IPAddressNumber address_any(multicast_addr.address().size());
131  net::IPEndPoint bind_endpoint(address_any, multicast_addr.port());
132  socket_->AllowAddressReuse();
133  int rv = socket_->Listen(bind_endpoint);
134  if (rv < net::OK)
135    return rv;
136  socket_->SetMulticastLoopbackMode(false);
137  return socket_->JoinGroup(multicast_addr.address());
138}
139
140bool PrivetTrafficDetector::IsSourceAcceptable() const {
141  for (size_t i = 0; i < networks_.size(); ++i) {
142    if (net::IPNumberMatchesPrefix(recv_addr_.address(), networks_[i].address,
143                                   networks_[i].network_prefix)) {
144      return true;
145    }
146  }
147  return false;
148}
149
150bool PrivetTrafficDetector::IsPrivetPacket(int rv) const {
151  if (rv <= static_cast<int>(sizeof(net::dns_protocol::Header)) ||
152      !IsSourceAcceptable()) {
153    return false;
154  }
155
156  const char* buffer_begin = io_buffer_->data();
157  const char* buffer_end = buffer_begin + rv;
158  const net::dns_protocol::Header* header =
159      reinterpret_cast<const net::dns_protocol::Header*>(buffer_begin);
160  // Check if response packet.
161  if (!(header->flags & base::HostToNet16(net::dns_protocol::kFlagResponse)))
162    return false;
163  const char* substring_begin = kPrivetDeviceTypeDnsString;
164  const char* substring_end = substring_begin +
165                              arraysize(kPrivetDeviceTypeDnsString);
166  // Check for expected substring, any Privet device must include this.
167  return std::search(buffer_begin, buffer_end, substring_begin,
168                     substring_end) != buffer_end;
169}
170
171int PrivetTrafficDetector::DoLoop(int rv) {
172  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
173  do {
174    if (IsPrivetPacket(rv)) {
175      socket_.reset();
176      callback_runner_->PostTask(FROM_HERE, on_traffic_detected_);
177      base::TimeDelta time_delta = base::Time::Now() - start_time_;
178      UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorTriggerTime",
179                               time_delta);
180      return net::OK;
181    }
182
183    rv = socket_->RecvFrom(
184        io_buffer_,
185        io_buffer_->size(),
186        &recv_addr_,
187        base::Bind(base::IgnoreResult(&PrivetTrafficDetector::DoLoop),
188                   base::Unretained(this)));
189  } while (rv > 0);
190
191  if (rv != net::ERR_IO_PENDING)
192    return rv;
193
194  return net::OK;
195}
196
197}  // namespace local_discovery
198