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.
5 #include "chrome/browser/local_discovery/privet_traffic_detector.h"
7 #include "base/location.h"
8 #include "base/metrics/histogram.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/sys_byteorder.h"
11 #include "net/base/dns_util.h"
12 #include "net/base/net_errors.h"
13 #include "net/base/network_interfaces.h"
14 #include "net/dns/dns_protocol.h"
15 #include "net/dns/dns_response.h"
16 #include "net/dns/mdns_client.h"
17 #include "net/log/net_log.h"
18 #include "net/udp/datagram_server_socket.h"
19 #include "net/udp/udp_server_socket.h"
23 const int kMaxRestartAttempts
= 10;
24 const char kPrivetDeviceTypeDnsString
[] = "\x07_privet";
26 void GetNetworkListOnFileThread(
27 const base::Callback
<void(const net::NetworkInterfaceList
&)> callback
) {
28 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
29 net::NetworkInterfaceList networks
;
30 if (!GetNetworkList(&networks
, net::INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES
))
33 net::NetworkInterfaceList ip4_networks
;
34 for (size_t i
= 0; i
< networks
.size(); ++i
) {
35 net::AddressFamily address_family
=
36 net::GetAddressFamily(networks
[i
].address
);
37 if (address_family
== net::ADDRESS_FAMILY_IPV4
&&
38 networks
[i
].prefix_length
>= 24) {
39 ip4_networks
.push_back(networks
[i
]);
43 net::IPAddressNumber
localhost_prefix(4, 0);
44 localhost_prefix
[0] = 127;
45 ip4_networks
.push_back(
46 net::NetworkInterface("lo",
49 net::NetworkChangeNotifier::CONNECTION_UNKNOWN
,
52 net::IP_ADDRESS_ATTRIBUTE_NONE
));
53 content::BrowserThread::PostTask(content::BrowserThread::IO
, FROM_HERE
,
54 base::Bind(callback
, ip4_networks
));
59 namespace local_discovery
{
61 PrivetTrafficDetector::PrivetTrafficDetector(
62 net::AddressFamily address_family
,
63 const base::Closure
& on_traffic_detected
)
64 : on_traffic_detected_(on_traffic_detected
),
65 callback_runner_(base::MessageLoop::current()->task_runner()),
66 address_family_(address_family
),
68 new net::IOBufferWithSize(net::dns_protocol::kMaxMulticastSize
)),
69 restart_attempts_(kMaxRestartAttempts
),
70 weak_ptr_factory_(this) {
73 void PrivetTrafficDetector::Start() {
74 content::BrowserThread::PostTask(
75 content::BrowserThread::IO
,
77 base::Bind(&PrivetTrafficDetector::StartOnIOThread
,
78 weak_ptr_factory_
.GetWeakPtr()));
81 PrivetTrafficDetector::~PrivetTrafficDetector() {
82 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
83 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
86 void PrivetTrafficDetector::StartOnIOThread() {
87 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
88 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
92 void PrivetTrafficDetector::OnNetworkChanged(
93 net::NetworkChangeNotifier::ConnectionType type
) {
94 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
95 restart_attempts_
= kMaxRestartAttempts
;
96 if (type
!= net::NetworkChangeNotifier::CONNECTION_NONE
)
100 void PrivetTrafficDetector::ScheduleRestart() {
101 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
103 weak_ptr_factory_
.InvalidateWeakPtrs();
104 content::BrowserThread::PostDelayedTask(
105 content::BrowserThread::FILE,
107 base::Bind(&GetNetworkListOnFileThread
,
108 base::Bind(&PrivetTrafficDetector::Restart
,
109 weak_ptr_factory_
.GetWeakPtr())),
110 base::TimeDelta::FromSeconds(3));
113 void PrivetTrafficDetector::Restart(const net::NetworkInterfaceList
& networks
) {
114 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
115 networks_
= networks
;
116 if (Bind() < net::OK
|| DoLoop(0) < net::OK
) {
117 if ((restart_attempts_
--) > 0)
121 restart_attempts_
= kMaxRestartAttempts
;
125 int PrivetTrafficDetector::Bind() {
126 if (!start_time_
.is_null()) {
127 base::TimeDelta time_delta
= base::Time::Now() - start_time_
;
128 UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorRestartTime", time_delta
);
130 start_time_
= base::Time::Now();
131 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
132 socket_
.reset(new net::UDPServerSocket(NULL
, net::NetLog::Source()));
133 net::IPEndPoint multicast_addr
= net::GetMDnsIPEndPoint(address_family_
);
134 net::IPAddressNumber
address_any(multicast_addr
.address().size());
135 net::IPEndPoint
bind_endpoint(address_any
, multicast_addr
.port());
136 socket_
->AllowAddressReuse();
137 int rv
= socket_
->Listen(bind_endpoint
);
140 socket_
->SetMulticastLoopbackMode(false);
141 return socket_
->JoinGroup(multicast_addr
.address());
144 bool PrivetTrafficDetector::IsSourceAcceptable() const {
145 for (size_t i
= 0; i
< networks_
.size(); ++i
) {
146 if (net::IPNumberMatchesPrefix(recv_addr_
.address(), networks_
[i
].address
,
147 networks_
[i
].prefix_length
)) {
154 bool PrivetTrafficDetector::IsPrivetPacket(int rv
) const {
155 if (rv
<= static_cast<int>(sizeof(net::dns_protocol::Header
)) ||
156 !IsSourceAcceptable()) {
160 const char* buffer_begin
= io_buffer_
->data();
161 const char* buffer_end
= buffer_begin
+ rv
;
162 const net::dns_protocol::Header
* header
=
163 reinterpret_cast<const net::dns_protocol::Header
*>(buffer_begin
);
164 // Check if response packet.
165 if (!(header
->flags
& base::HostToNet16(net::dns_protocol::kFlagResponse
)))
167 const char* substring_begin
= kPrivetDeviceTypeDnsString
;
168 const char* substring_end
= substring_begin
+
169 arraysize(kPrivetDeviceTypeDnsString
) - 1;
170 // Check for expected substring, any Privet device must include this.
171 return std::search(buffer_begin
, buffer_end
, substring_begin
,
172 substring_end
) != buffer_end
;
175 int PrivetTrafficDetector::DoLoop(int rv
) {
176 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
178 if (IsPrivetPacket(rv
)) {
180 callback_runner_
->PostTask(FROM_HERE
, on_traffic_detected_
);
181 base::TimeDelta time_delta
= base::Time::Now() - start_time_
;
182 UMA_HISTOGRAM_LONG_TIMES("LocalDiscovery.DetectorTriggerTime",
187 rv
= socket_
->RecvFrom(
191 base::Bind(base::IgnoreResult(&PrivetTrafficDetector::DoLoop
),
192 base::Unretained(this)));
195 if (rv
!= net::ERR_IO_PENDING
)
201 } // namespace local_discovery