// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/local_discovery/privet_traffic_detector.h" #include "base/metrics/histogram.h" #include "base/sys_byteorder.h" #include "net/base/dns_util.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" #include "net/dns/dns_protocol.h" #include "net/dns/dns_response.h" #include "net/dns/mdns_client.h" #include "net/udp/datagram_server_socket.h" #include "net/udp/udp_server_socket.h" namespace { const int kMaxRestartAttempts = 10; const char kPrivetDeviceTypeDnsString[] = "\x07_privet\x04_tcp\x05local"; void GetNetworkListOnFileThread( const base::Callback callback) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); net::NetworkInterfaceList networks; if (!GetNetworkList(&networks)) return; net::NetworkInterfaceList ip4_networks; for (size_t i = 0; i < networks.size(); ++i) { net::AddressFamily address_family = net::GetAddressFamily(networks[i].address); if (address_family == net::ADDRESS_FAMILY_IPV4 && networks[i].network_prefix >= 24) { ip4_networks.push_back(networks[i]); } } net::IPAddressNumber localhost_prefix(4, 0); localhost_prefix[0] = 127; ip4_networks.push_back(net::NetworkInterface("lo", localhost_prefix, 8)); content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, base::Bind(callback, ip4_networks)); } } // namespace namespace local_discovery { PrivetTrafficDetector::PrivetTrafficDetector( net::AddressFamily address_family, const base::Closure& on_traffic_detected) : on_traffic_detected_(on_traffic_detected), callback_runner_(base::MessageLoop::current()->message_loop_proxy()), address_family_(address_family), io_buffer_( new net::IOBufferWithSize(net::dns_protocol::kMaxMulticastSize)), restart_attempts_(kMaxRestartAttempts), weak_ptr_factory_(this) { } void PrivetTrafficDetector::Start() { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&PrivetTrafficDetector::StartOnIOThread, weak_ptr_factory_.GetWeakPtr())); } PrivetTrafficDetector::~PrivetTrafficDetector() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); } void PrivetTrafficDetector::StartOnIOThread() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); net::NetworkChangeNotifier::AddNetworkChangeObserver(this); ScheduleRestart(); } void PrivetTrafficDetector::OnNetworkChanged( net::NetworkChangeNotifier::ConnectionType type) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); restart_attempts_ = kMaxRestartAttempts; if (type != net::NetworkChangeNotifier::CONNECTION_NONE) ScheduleRestart(); } void PrivetTrafficDetector::ScheduleRestart() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); socket_.reset(); weak_ptr_factory_.InvalidateWeakPtrs(); content::BrowserThread::PostDelayedTask( content::BrowserThread::FILE, FROM_HERE, base::Bind(&GetNetworkListOnFileThread, base::Bind(&PrivetTrafficDetector::Restart, weak_ptr_factory_.GetWeakPtr())), base::TimeDelta::FromSeconds(3)); } void PrivetTrafficDetector::Restart(const net::NetworkInterfaceList& networks) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); networks_ = networks; if (Bind() < net::OK || DoLoop(0) < net::OK) { if ((restart_attempts_--) > 0) ScheduleRestart(); } else { // Reset on success. restart_attempts_ = kMaxRestartAttempts; } } int PrivetTrafficDetector::Bind() { if (!start_time_.is_null()) { base::TimeDelta time_delta = base::Time::Now() - start_time_; UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorRestartTime", time_delta); } start_time_ = base::Time::Now(); DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); socket_.reset(new net::UDPServerSocket(NULL, net::NetLog::Source())); net::IPEndPoint multicast_addr = net::GetMDnsIPEndPoint(address_family_); net::IPAddressNumber address_any(multicast_addr.address().size()); net::IPEndPoint bind_endpoint(address_any, multicast_addr.port()); socket_->AllowAddressReuse(); int rv = socket_->Listen(bind_endpoint); if (rv < net::OK) return rv; socket_->SetMulticastLoopbackMode(false); return socket_->JoinGroup(multicast_addr.address()); } bool PrivetTrafficDetector::IsSourceAcceptable() const { for (size_t i = 0; i < networks_.size(); ++i) { if (net::IPNumberMatchesPrefix(recv_addr_.address(), networks_[i].address, networks_[i].network_prefix)) { return true; } } return false; } bool PrivetTrafficDetector::IsPrivetPacket(int rv) const { if (rv <= static_cast(sizeof(net::dns_protocol::Header)) || !IsSourceAcceptable()) { return false; } const char* buffer_begin = io_buffer_->data(); const char* buffer_end = buffer_begin + rv; const net::dns_protocol::Header* header = reinterpret_cast(buffer_begin); // Check if response packet. if (!(header->flags & base::HostToNet16(net::dns_protocol::kFlagResponse))) return false; const char* substring_begin = kPrivetDeviceTypeDnsString; const char* substring_end = substring_begin + arraysize(kPrivetDeviceTypeDnsString); // Check for expected substring, any Privet device must include this. return std::search(buffer_begin, buffer_end, substring_begin, substring_end) != buffer_end; } int PrivetTrafficDetector::DoLoop(int rv) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); do { if (IsPrivetPacket(rv)) { socket_.reset(); callback_runner_->PostTask(FROM_HERE, on_traffic_detected_); base::TimeDelta time_delta = base::Time::Now() - start_time_; UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorTriggerTime", time_delta); return net::OK; } rv = socket_->RecvFrom( io_buffer_, io_buffer_->size(), &recv_addr_, base::Bind(base::IgnoreResult(&PrivetTrafficDetector::DoLoop), base::Unretained(this))); } while (rv > 0); if (rv != net::ERR_IO_PENDING) return rv; return net::OK; } } // namespace local_discovery