1 // Copyright (c) 2012 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/extensions/api/dial/dial_service.h"
9 #include "base/basictypes.h"
10 #include "base/callback.h"
11 #include "base/logging.h"
12 #include "base/rand_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
17 #include "chrome/common/chrome_version_info.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "net/base/completion_callback.h"
20 #include "net/base/io_buffer.h"
21 #include "net/base/ip_endpoint.h"
22 #include "net/base/net_errors.h"
23 #include "net/base/net_util.h"
24 #include "net/http/http_response_headers.h"
25 #include "net/http/http_util.h"
27 #if defined(OS_CHROMEOS)
28 #include "chromeos/network/network_state.h"
29 #include "chromeos/network/network_state_handler.h"
30 #include "chromeos/network/shill_property_util.h"
31 #include "third_party/cros_system_api/dbus/service_constants.h"
35 using base::TimeDelta;
36 using content::BrowserThread;
37 using net::HttpResponseHeaders;
39 using net::IOBufferWithSize;
40 using net::IPAddressNumber;
41 using net::IPEndPoint;
42 using net::NetworkInterface;
43 using net::NetworkInterfaceList;
44 using net::StringIOBuffer;
47 namespace extensions {
51 // The total number of requests to make per discovery cycle.
52 const int kDialMaxRequests = 4;
54 // The interval to wait between successive requests.
55 const int kDialRequestIntervalMillis = 1000;
57 // The timeout (MX) set for responses.
58 const int kDialResponseTimeoutSecs = 2;
60 // The multicast IP address for discovery.
61 const char kDialRequestAddress[] = "239.255.255.250";
63 // The UDP port number for discovery.
64 const int kDialRequestPort = 1900;
66 // The DIAL service type as part of the search request.
67 const char kDialSearchType[] = "urn:dial-multiscreen-org:service:dial:1";
69 // SSDP headers parsed from the response.
70 const char kSsdpLocationHeader[] = "LOCATION";
71 const char kSsdpCacheControlHeader[] = "CACHE-CONTROL";
72 const char kSsdpConfigIdHeader[] = "CONFIGID.UPNP.ORG";
73 const char kSsdpUsnHeader[] = "USN";
75 // The receive buffer size, in bytes.
76 const int kDialRecvBufferSize = 1500;
78 // Gets a specific header from |headers| and puts it in |value|.
79 bool GetHeader(HttpResponseHeaders* headers, const char* name,
81 return headers->EnumerateHeader(NULL, std::string(name), value);
84 // Returns the request string.
85 std::string BuildRequest() {
86 // Extra line at the end to make UPnP lib happy.
87 chrome::VersionInfo version;
88 std::string request(base::StringPrintf(
89 "M-SEARCH * HTTP/1.1\r\n"
91 "MAN:\"ssdp:discover\"\r\n"
94 "USER-AGENT:%s/%s %s\r\n"
98 kDialResponseTimeoutSecs,
100 version.Name().c_str(),
101 version.Version().c_str(),
102 version.OSType().c_str()));
103 // 1500 is a good MTU value for most Ethernet LANs.
104 DCHECK(request.size() <= 1500);
108 void GetNetworkListOnFileThread(
109 const scoped_refptr<base::MessageLoopProxy>& loop,
110 const base::Callback<void(const NetworkInterfaceList& networks)>& cb) {
111 NetworkInterfaceList list;
112 bool success = net::GetNetworkList(&list);
114 DVLOG(1) << "Could not retrieve network list!";
116 loop->PostTask(FROM_HERE, base::Bind(cb, list));
119 #if defined(OS_CHROMEOS)
120 IPAddressNumber GetBestBindAddressByType(
121 const chromeos::NetworkTypePattern& type) {
122 const chromeos::NetworkState* state = chromeos::NetworkHandler::Get()
123 ->network_state_handler()->ConnectedNetworkByType(type);
124 IPAddressNumber bind_ip_address;
126 !net::ParseIPLiteralToNumber(state->ip_address(), &bind_ip_address)) {
127 return IPAddressNumber();
129 if (bind_ip_address.size() != net::kIPv4AddressSize) {
130 LOG(ERROR) << "Default network is not using IPv4.";
131 return IPAddressNumber();
134 DVLOG(1) << "Found " << state->type() << ", " << state->name() << ":"
135 << state->ip_address();
136 return bind_ip_address;
139 // Returns the IP address of the preferred interface to bind the socket. This
140 // ChromeOS version can prioritize wifi and ethernet interfaces.
141 IPAddressNumber GetBestBindAddressChromeOS() {
142 IPAddressNumber bind_ip_address =
143 GetBestBindAddressByType(chromeos::NetworkTypePattern::Ethernet());
144 if (bind_ip_address.empty()) {
146 GetBestBindAddressByType(chromeos::NetworkTypePattern::WiFi());
148 return bind_ip_address;
154 DialServiceImpl::DialServiceImpl(net::NetLog* net_log)
155 : is_writing_(false),
157 discovery_active_(false),
158 num_requests_sent_(0),
159 max_requests_(kDialMaxRequests),
160 finish_delay_(TimeDelta::FromMilliseconds((kDialMaxRequests - 1) *
161 kDialRequestIntervalMillis) +
162 TimeDelta::FromSeconds(kDialResponseTimeoutSecs)),
163 request_interval_(TimeDelta::FromMilliseconds(kDialRequestIntervalMillis)) {
164 IPAddressNumber address;
165 bool result = net::ParseIPLiteralToNumber(kDialRequestAddress, &address);
167 send_address_ = IPEndPoint(address, kDialRequestPort);
168 send_buffer_ = new StringIOBuffer(BuildRequest());
170 net_log_source_.type = net::NetLog::SOURCE_UDP_SOCKET;
171 net_log_source_.id = net_log_->NextID();
174 DialServiceImpl::~DialServiceImpl() {
175 DCHECK(thread_checker_.CalledOnValidThread());
178 void DialServiceImpl::AddObserver(Observer* observer) {
179 DCHECK(thread_checker_.CalledOnValidThread());
180 observer_list_.AddObserver(observer);
183 void DialServiceImpl::RemoveObserver(Observer* observer) {
184 DCHECK(thread_checker_.CalledOnValidThread());
185 observer_list_.RemoveObserver(observer);
188 bool DialServiceImpl::HasObserver(Observer* observer) {
189 DCHECK(thread_checker_.CalledOnValidThread());
190 return observer_list_.HasObserver(observer);
193 bool DialServiceImpl::Discover() {
194 DCHECK(thread_checker_.CalledOnValidThread());
195 if (discovery_active_)
197 discovery_active_ = true;
199 VLOG(2) << "Discovery started.";
205 void DialServiceImpl::StartDiscovery() {
206 DCHECK(thread_checker_.CalledOnValidThread());
207 DCHECK(discovery_active_);
211 #if defined(OS_CHROMEOS)
212 // The ChromeOS specific version of getting network interfaces does not
213 // require trampolining to another thread, and contains additional interface
214 // information such as interface types (i.e. wifi vs cellular).
215 BindSocketAndSendRequest(GetBestBindAddressChromeOS());
217 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
218 &GetNetworkListOnFileThread,
219 base::MessageLoopProxy::current(), base::Bind(
220 &DialServiceImpl::SendNetworkList, AsWeakPtr())));
224 bool DialServiceImpl::BindSocketAndSendRequest(
225 const IPAddressNumber& bind_ip_address) {
226 DCHECK(thread_checker_.CalledOnValidThread());
227 DCHECK(!socket_.get());
229 if (bind_ip_address.size() == 0) {
230 DVLOG(1) << "Could not find a valid interface to bind.";
235 net::RandIntCallback rand_cb = base::Bind(&base::RandInt);
236 socket_.reset(new UDPSocket(net::DatagramSocket::RANDOM_BIND,
240 socket_->AllowBroadcast();
242 // Schedule a timer to finish the discovery process (and close the socket).
243 if (finish_delay_ > TimeDelta::FromSeconds(0)) {
244 finish_timer_.Start(FROM_HERE,
247 &DialServiceImpl::FinishDiscovery);
250 // 0 means bind a random port
251 IPEndPoint address(bind_ip_address, 0);
253 if (!CheckResult("Bind", socket_->Bind(address)))
256 DCHECK(socket_.get());
258 recv_buffer_ = new IOBufferWithSize(kDialRecvBufferSize);
265 void DialServiceImpl::SendOneRequest() {
266 if (num_requests_sent_ == max_requests_) {
267 request_timer_.Stop();
270 num_requests_sent_++;
271 if (!socket_.get()) {
272 DLOG(WARNING) << "Socket not connected.";
277 VLOG(2) << "Already writing.";
280 VLOG(2) << "Sending request " << num_requests_sent_ << "/"
283 int result = socket_->SendTo(
284 send_buffer_.get(), send_buffer_->size(), send_address_,
285 base::Bind(&DialServiceImpl::OnSocketWrite, AsWeakPtr()));
286 bool result_ok = CheckResult("SendTo", result);
287 if (result_ok && result > 0) {
288 // Synchronous write.
289 OnSocketWrite(result);
293 void DialServiceImpl::OnSocketWrite(int result) {
294 DCHECK(thread_checker_.CalledOnValidThread());
296 if (!CheckResult("OnSocketWrite", result))
299 if (result != send_buffer_->size()) {
300 DLOG(ERROR) << "Sent " << result << " chars, expected "
301 << send_buffer_->size() << " chars";
303 // If discovery is inactive, no reason to notify observers.
304 if (!discovery_active_) {
305 VLOG(2) << "Request sent after discovery finished. Ignoring.";
308 FOR_EACH_OBSERVER(Observer, observer_list_, OnDiscoveryRequest(this));
309 // If we need to send additional requests, schedule a timer to do so.
310 if (num_requests_sent_ < max_requests_ && num_requests_sent_ == 1) {
311 request_timer_.Start(FROM_HERE,
314 &DialServiceImpl::SendOneRequest);
318 bool DialServiceImpl::ReadSocket() {
319 DCHECK(thread_checker_.CalledOnValidThread());
320 if (!socket_.get()) {
321 DLOG(WARNING) << "Socket not connected.";
326 VLOG(2) << "Already reading.";
330 int result = net::OK;
331 bool result_ok = true;
334 result = socket_->RecvFrom(
336 kDialRecvBufferSize, &recv_address_,
337 base::Bind(&DialServiceImpl::OnSocketRead, AsWeakPtr()));
338 result_ok = CheckResult("RecvFrom", result);
339 if (result != net::ERR_IO_PENDING)
341 if (result_ok && result > 0) {
343 HandleResponse(result);
345 } while (result_ok && result != net::OK && result != net::ERR_IO_PENDING);
349 void DialServiceImpl::OnSocketRead(int result) {
350 DCHECK(thread_checker_.CalledOnValidThread());
352 if (!CheckResult("OnSocketRead", result))
355 HandleResponse(result);
357 // Await next response.
361 void DialServiceImpl::HandleResponse(int bytes_read) {
362 DCHECK(thread_checker_.CalledOnValidThread());
363 DCHECK_GT(bytes_read, 0);
364 if (bytes_read > kDialRecvBufferSize) {
365 DLOG(ERROR) << bytes_read << " > " << kDialRecvBufferSize << "!?";
368 VLOG(2) << "Read " << bytes_read << " bytes from "
369 << recv_address_.ToString();
371 // If discovery is inactive, no reason to handle response.
372 if (!discovery_active_) {
373 VLOG(2) << "Got response after discovery finished. Ignoring.";
377 std::string response(recv_buffer_->data(), bytes_read);
378 Time response_time = Time::Now();
380 // Attempt to parse response, notify observers if successful.
381 DialDeviceData parsed_device;
382 if (ParseResponse(response, response_time, &parsed_device))
383 FOR_EACH_OBSERVER(Observer, observer_list_,
384 OnDeviceDiscovered(this, parsed_device));
388 bool DialServiceImpl::ParseResponse(const std::string& response,
389 const base::Time& response_time,
390 DialDeviceData* device) {
391 int headers_end = HttpUtil::LocateEndOfHeaders(response.c_str(),
393 if (headers_end < 1) {
394 VLOG(2) << "Headers invalid or empty, ignoring: " << response;
397 std::string raw_headers =
398 HttpUtil::AssembleRawHeaders(response.c_str(), headers_end);
399 VLOG(2) << "raw_headers: " << raw_headers << "\n";
400 scoped_refptr<HttpResponseHeaders> headers =
401 new HttpResponseHeaders(raw_headers);
403 std::string device_url_str;
404 if (!GetHeader(headers.get(), kSsdpLocationHeader, &device_url_str) ||
405 device_url_str.empty()) {
406 VLOG(2) << "No LOCATION header found.";
410 GURL device_url(device_url_str);
411 if (!DialDeviceData::IsDeviceDescriptionUrl(device_url)) {
412 VLOG(2) << "URL " << device_url_str << " not valid.";
416 std::string device_id;
417 if (!GetHeader(headers.get(), kSsdpUsnHeader, &device_id) ||
419 VLOG(2) << "No USN header found.";
423 device->set_device_id(device_id);
424 device->set_device_description_url(device_url);
425 device->set_response_time(response_time);
427 // TODO(mfoltz): Parse the max-age value from the cache control header.
428 // http://crbug.com/165289
429 std::string cache_control;
430 GetHeader(headers.get(), kSsdpCacheControlHeader, &cache_control);
432 std::string config_id;
434 if (GetHeader(headers.get(), kSsdpConfigIdHeader, &config_id) &&
435 base::StringToInt(config_id, &config_id_int)) {
436 device->set_config_id(config_id_int);
438 VLOG(2) << "Malformed or missing " << kSsdpConfigIdHeader << ": "
445 void DialServiceImpl::SendNetworkList(const NetworkInterfaceList& networks) {
446 DCHECK(thread_checker_.CalledOnValidThread());
447 IPAddressNumber bind_ip_address;
448 // Returns the first IPv4 address found. If there is a need for discovery
449 // across multiple networks, we could manage multiple sockets.
451 // TODO(mfoltz): Support IPV6 multicast. http://crbug.com/165286
452 for (NetworkInterfaceList::const_iterator iter = networks.begin();
453 iter != networks.end(); ++iter) {
454 DVLOG(1) << "Found " << iter->name << ", "
455 << net::IPAddressToString(iter->address);
456 if (iter->address.size() == net::kIPv4AddressSize) {
457 bind_ip_address = (*iter).address;
462 BindSocketAndSendRequest(bind_ip_address);
465 void DialServiceImpl::FinishDiscovery() {
466 DCHECK(thread_checker_.CalledOnValidThread());
467 DCHECK(discovery_active_);
468 VLOG(2) << "Discovery finished.";
470 finish_timer_.Stop();
471 request_timer_.Stop();
472 discovery_active_ = false;
473 num_requests_sent_ = 0;
474 FOR_EACH_OBSERVER(Observer, observer_list_, OnDiscoveryFinished(this));
477 void DialServiceImpl::CloseSocket() {
478 DCHECK(thread_checker_.CalledOnValidThread());
484 bool DialServiceImpl::CheckResult(const char* operation, int result) {
485 DCHECK(thread_checker_.CalledOnValidThread());
486 VLOG(2) << "Operation " << operation << " result " << result;
487 if (result < net::OK && result != net::ERR_IO_PENDING) {
489 std::string error_str(net::ErrorToString(result));
490 DVLOG(0) << "dial socket error: " << error_str;
491 // TODO(justinlin): More granular socket errors.
493 Observer, observer_list_, OnError(this, DIAL_SERVICE_SOCKET_ERROR));
499 } // namespace extensions