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