1 // Copyright 2014 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/devtools/device/port_forwarding_controller.h"
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/memory/singleton.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/threading/non_thread_safe.h"
19 #include "chrome/browser/devtools/devtools_protocol.h"
20 #include "chrome/browser/devtools/devtools_protocol_constants.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/pref_names.h"
23 #include "components/keyed_service/content/browser_context_dependency_manager.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "net/base/address_list.h"
26 #include "net/base/io_buffer.h"
27 #include "net/base/net_errors.h"
28 #include "net/base/net_util.h"
29 #include "net/dns/host_resolver.h"
30 #include "net/socket/tcp_client_socket.h"
32 using content::BrowserThread;
36 const int kBufferSize = 16 * 1024;
40 kStatusDisconnecting = -2,
41 kStatusConnecting = -1,
43 // Positive values are used to count open connections.
46 namespace tethering = ::chrome::devtools::Tethering;
48 static const char kDevToolsRemoteBrowserTarget[] = "/devtools/browser";
49 const int kMinVersionPortForwarding = 28;
51 class SocketTunnel : public base::NonThreadSafe {
53 typedef base::Callback<void(int)> CounterCallback;
55 static void StartTunnel(const std::string& host,
57 const CounterCallback& callback,
59 scoped_ptr<net::StreamSocket> socket) {
62 SocketTunnel* tunnel = new SocketTunnel(callback);
63 tunnel->Start(socket.Pass(), host, port);
67 explicit SocketTunnel(const CounterCallback& callback)
69 pending_destruction_(false),
71 about_to_destroy_(false) {
75 void Start(scoped_ptr<net::StreamSocket> socket,
76 const std::string& host, int port) {
77 remote_socket_.swap(socket);
79 host_resolver_ = net::HostResolver::CreateDefaultResolver(NULL);
80 net::HostResolver::RequestInfo request_info(net::HostPortPair(host, port));
81 int result = host_resolver_->Resolve(
83 net::DEFAULT_PRIORITY,
85 base::Bind(&SocketTunnel::OnResolved, base::Unretained(this)),
88 if (result != net::ERR_IO_PENDING)
92 void OnResolved(int result) {
98 host_socket_.reset(new net::TCPClientSocket(address_list_, NULL,
99 net::NetLog::Source()));
100 result = host_socket_->Connect(base::Bind(&SocketTunnel::OnConnected,
101 base::Unretained(this)));
102 if (result != net::ERR_IO_PENDING)
107 about_to_destroy_ = true;
109 host_socket_->Disconnect();
111 remote_socket_->Disconnect();
115 void OnConnected(int result) {
121 ++pending_writes_; // avoid SelfDestruct in first Pump
122 Pump(host_socket_.get(), remote_socket_.get());
124 if (pending_destruction_) {
127 Pump(remote_socket_.get(), host_socket_.get());
131 void Pump(net::StreamSocket* from, net::StreamSocket* to) {
132 scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kBufferSize);
133 int result = from->Read(
137 &SocketTunnel::OnRead, base::Unretained(this), from, to, buffer));
138 if (result != net::ERR_IO_PENDING)
139 OnRead(from, to, buffer, result);
142 void OnRead(net::StreamSocket* from,
143 net::StreamSocket* to,
144 scoped_refptr<net::IOBuffer> buffer,
152 scoped_refptr<net::DrainableIOBuffer> drainable =
153 new net::DrainableIOBuffer(buffer.get(), total);
156 result = to->Write(drainable.get(),
158 base::Bind(&SocketTunnel::OnWritten,
159 base::Unretained(this),
163 if (result != net::ERR_IO_PENDING)
164 OnWritten(drainable, from, to, result);
167 void OnWritten(scoped_refptr<net::DrainableIOBuffer> drainable,
168 net::StreamSocket* from,
169 net::StreamSocket* to,
177 drainable->DidConsume(result);
178 if (drainable->BytesRemaining() > 0) {
180 result = to->Write(drainable.get(),
181 drainable->BytesRemaining(),
182 base::Bind(&SocketTunnel::OnWritten,
183 base::Unretained(this),
187 if (result != net::ERR_IO_PENDING)
188 OnWritten(drainable, from, to, result);
192 if (pending_destruction_) {
199 void SelfDestruct() {
200 // In case one of the connections closes, we could get here
201 // from another one due to Disconnect firing back on all
203 if (about_to_destroy_)
205 if (pending_writes_ > 0) {
206 pending_destruction_ = true;
212 scoped_ptr<net::StreamSocket> remote_socket_;
213 scoped_ptr<net::StreamSocket> host_socket_;
214 scoped_ptr<net::HostResolver> host_resolver_;
215 net::AddressList address_list_;
217 bool pending_destruction_;
218 CounterCallback callback_;
219 bool about_to_destroy_;
222 typedef DevToolsAndroidBridge::RemoteBrowser::ParsedVersion ParsedVersion;
224 static bool IsVersionLower(const ParsedVersion& left,
225 const ParsedVersion& right) {
226 return std::lexicographical_compare(
227 left.begin(), left.end(), right.begin(), right.end());
230 static bool IsPortForwardingSupported(const ParsedVersion& version) {
231 return !version.empty() && version[0] >= kMinVersionPortForwarding;
234 static scoped_refptr<DevToolsAndroidBridge::RemoteBrowser>
235 FindBestBrowserForTethering(
236 const DevToolsAndroidBridge::RemoteBrowsers browsers) {
237 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> best_browser;
238 ParsedVersion newest_version;
239 for (DevToolsAndroidBridge::RemoteBrowsers::const_iterator it =
240 browsers.begin(); it != browsers.end(); ++it) {
241 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser = *it;
242 ParsedVersion current_version = browser->GetParsedVersion();
243 if (IsPortForwardingSupported(current_version) &&
244 IsVersionLower(newest_version, current_version)) {
245 best_browser = browser;
246 newest_version = current_version;
254 class PortForwardingController::Connection
255 : public AndroidDeviceManager::AndroidWebSocket::Delegate {
257 Connection(PortForwardingController* controller,
258 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser,
259 const ForwardingMap& forwarding_map);
260 ~Connection() override;
262 const PortStatusMap& GetPortStatusMap();
264 void UpdateForwardingMap(const ForwardingMap& new_forwarding_map);
266 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser() {
271 friend struct content::BrowserThread::DeleteOnThread<
272 content::BrowserThread::UI>;
273 friend class base::DeleteHelper<Connection>;
275 typedef std::map<int, std::string> ForwardingMap;
276 typedef base::Callback<void(PortStatus)> CommandCallback;
277 typedef std::map<int, CommandCallback> CommandCallbackMap;
279 void SerializeChanges(const std::string& method,
280 const ForwardingMap& old_map,
281 const ForwardingMap& new_map);
283 void SendCommand(const std::string& method, int port);
284 bool ProcessResponse(const std::string& json);
286 void ProcessBindResponse(int port, PortStatus status);
287 void ProcessUnbindResponse(int port, PortStatus status);
289 static void UpdateSocketCountOnHandlerThread(
290 base::WeakPtr<Connection> weak_connection, int port, int increment);
291 void UpdateSocketCount(int port, int increment);
293 // DevToolsAndroidBridge::AndroidWebSocket::Delegate implementation:
294 void OnSocketOpened() override;
295 void OnFrameRead(const std::string& message) override;
296 void OnSocketClosed() override;
298 PortForwardingController* controller_;
299 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser_;
300 scoped_ptr<AndroidDeviceManager::AndroidWebSocket> web_socket_;
303 ForwardingMap forwarding_map_;
304 CommandCallbackMap pending_responses_;
305 PortStatusMap port_status_;
306 base::WeakPtrFactory<Connection> weak_factory_;
308 DISALLOW_COPY_AND_ASSIGN(Connection);
311 PortForwardingController::Connection::Connection(
312 PortForwardingController* controller,
313 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser,
314 const ForwardingMap& forwarding_map)
315 : controller_(controller),
319 forwarding_map_(forwarding_map),
320 weak_factory_(this) {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322 controller_->registry_[browser->serial()] = this;
323 scoped_refptr<AndroidDeviceManager::Device> device(
324 controller_->bridge_->FindDevice(browser->serial()));
325 DCHECK(device.get());
327 device->CreateWebSocket(browser->socket(),
328 kDevToolsRemoteBrowserTarget, this));
331 PortForwardingController::Connection::~Connection() {
332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
333 DCHECK(controller_->registry_.find(browser_->serial()) !=
334 controller_->registry_.end());
335 controller_->registry_.erase(browser_->serial());
338 void PortForwardingController::Connection::UpdateForwardingMap(
339 const ForwardingMap& new_forwarding_map) {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
342 SerializeChanges(tethering::unbind::kName,
343 new_forwarding_map, forwarding_map_);
344 SerializeChanges(tethering::bind::kName,
345 forwarding_map_, new_forwarding_map);
347 forwarding_map_ = new_forwarding_map;
350 void PortForwardingController::Connection::SerializeChanges(
351 const std::string& method,
352 const ForwardingMap& old_map,
353 const ForwardingMap& new_map) {
354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
355 for (ForwardingMap::const_iterator new_it(new_map.begin());
356 new_it != new_map.end(); ++new_it) {
357 int port = new_it->first;
358 const std::string& location = new_it->second;
359 ForwardingMap::const_iterator old_it = old_map.find(port);
360 if (old_it != old_map.end() && old_it->second == location)
361 continue; // The port points to the same location in both configs, skip.
363 SendCommand(method, port);
367 void PortForwardingController::Connection::SendCommand(
368 const std::string& method, int port) {
369 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
370 base::DictionaryValue params;
371 if (method == tethering::bind::kName) {
372 params.SetInteger(tethering::bind::kParamPort, port);
374 DCHECK_EQ(tethering::unbind::kName, method);
375 params.SetInteger(tethering::unbind::kParamPort, port);
377 DevToolsProtocol::Command command(++command_id_, method, ¶ms);
379 if (method == tethering::bind::kName) {
380 pending_responses_[command.id()] =
381 base::Bind(&Connection::ProcessBindResponse,
382 base::Unretained(this), port);
383 #if defined(DEBUG_DEVTOOLS)
384 port_status_[port] = kStatusConnecting;
385 #endif // defined(DEBUG_DEVTOOLS)
387 PortStatusMap::iterator it = port_status_.find(port);
388 if (it != port_status_.end() && it->second == kStatusError) {
389 // The bind command failed on this port, do not attempt unbind.
390 port_status_.erase(it);
394 pending_responses_[command.id()] =
395 base::Bind(&Connection::ProcessUnbindResponse,
396 base::Unretained(this), port);
397 #if defined(DEBUG_DEVTOOLS)
398 port_status_[port] = kStatusDisconnecting;
399 #endif // defined(DEBUG_DEVTOOLS)
402 web_socket_->SendFrame(command.Serialize());
405 bool PortForwardingController::Connection::ProcessResponse(
406 const std::string& message) {
407 scoped_ptr<DevToolsProtocol::Response> response(
408 DevToolsProtocol::ParseResponse(message));
412 CommandCallbackMap::iterator it = pending_responses_.find(response->id());
413 if (it == pending_responses_.end())
416 it->second.Run(response->error_code() ? kStatusError : kStatusOK);
417 pending_responses_.erase(it);
421 void PortForwardingController::Connection::ProcessBindResponse(
422 int port, PortStatus status) {
423 port_status_[port] = status;
426 void PortForwardingController::Connection::ProcessUnbindResponse(
427 int port, PortStatus status) {
428 PortStatusMap::iterator it = port_status_.find(port);
429 if (it == port_status_.end())
431 if (status == kStatusError)
434 port_status_.erase(it);
438 void PortForwardingController::Connection::UpdateSocketCountOnHandlerThread(
439 base::WeakPtr<Connection> weak_connection, int port, int increment) {
440 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
441 base::Bind(&Connection::UpdateSocketCount,
442 weak_connection, port, increment));
445 void PortForwardingController::Connection::UpdateSocketCount(
446 int port, int increment) {
447 #if defined(DEBUG_DEVTOOLS)
448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449 PortStatusMap::iterator it = port_status_.find(port);
450 if (it == port_status_.end())
452 if (it->second < 0 || (it->second == 0 && increment < 0))
454 it->second += increment;
455 #endif // defined(DEBUG_DEVTOOLS)
458 const PortForwardingController::PortStatusMap&
459 PortForwardingController::Connection::GetPortStatusMap() {
460 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
464 void PortForwardingController::Connection::OnSocketOpened() {
465 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
467 SerializeChanges(tethering::bind::kName, ForwardingMap(), forwarding_map_);
470 void PortForwardingController::Connection::OnSocketClosed() {
474 void PortForwardingController::Connection::OnFrameRead(
475 const std::string& message) {
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477 if (ProcessResponse(message))
480 scoped_ptr<DevToolsProtocol::Notification> notification(
481 DevToolsProtocol::ParseNotification(message));
485 if (notification->method() != tethering::accepted::kName)
488 base::DictionaryValue* params = notification->params();
493 std::string connection_id;
494 if (!params->GetInteger(tethering::accepted::kParamPort, &port) ||
495 !params->GetString(tethering::accepted::kParamConnectionId,
499 std::map<int, std::string>::iterator it = forwarding_map_.find(port);
500 if (it == forwarding_map_.end())
503 std::string location = it->second;
504 std::vector<std::string> tokens;
505 Tokenize(location, ":", &tokens);
506 int destination_port = 0;
507 if (tokens.size() != 2 || !base::StringToInt(tokens[1], &destination_port))
509 std::string destination_host = tokens[0];
511 SocketTunnel::CounterCallback callback =
512 base::Bind(&Connection::UpdateSocketCountOnHandlerThread,
513 weak_factory_.GetWeakPtr(), port);
515 scoped_refptr<AndroidDeviceManager::Device> device(
516 controller_->bridge_->FindDevice(browser_->serial()));
517 DCHECK(device.get());
519 connection_id.c_str(),
520 base::Bind(&SocketTunnel::StartTunnel,
526 PortForwardingController::PortForwardingController(
528 DevToolsAndroidBridge* bridge)
531 pref_service_(profile->GetPrefs()) {
532 pref_change_registrar_.Init(pref_service_);
533 base::Closure callback = base::Bind(
534 &PortForwardingController::OnPrefsChange, base::Unretained(this));
535 pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled, callback);
536 pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig, callback);
540 PortForwardingController::~PortForwardingController() {}
542 PortForwardingController::ForwardingStatus
543 PortForwardingController::DeviceListChanged(
544 const DevToolsAndroidBridge::RemoteDevices& devices) {
545 ForwardingStatus status;
546 if (forwarding_map_.empty())
549 for (const auto& device : devices) {
550 if (!device->is_connected())
552 Registry::iterator rit = registry_.find(device->serial());
553 if (rit == registry_.end()) {
554 scoped_refptr<DevToolsAndroidBridge::RemoteBrowser> browser(
555 FindBestBrowserForTethering(device->browsers()));
557 new Connection(this, browser, forwarding_map_);
559 status.push_back(std::make_pair(rit->second->browser(),
560 rit->second->GetPortStatusMap()));
566 void PortForwardingController::OnPrefsChange() {
567 forwarding_map_.clear();
569 if (pref_service_->GetBoolean(prefs::kDevToolsPortForwardingEnabled)) {
570 const base::DictionaryValue* dict =
571 pref_service_->GetDictionary(prefs::kDevToolsPortForwardingConfig);
572 for (base::DictionaryValue::Iterator it(*dict);
573 !it.IsAtEnd(); it.Advance()) {
575 std::string location;
576 if (base::StringToInt(it.key(), &port_num) &&
577 dict->GetString(it.key(), &location))
578 forwarding_map_[port_num] = location;
582 if (!forwarding_map_.empty()) {
585 std::vector<Connection*> registry_copy;
586 for (Registry::iterator it = registry_.begin();
587 it != registry_.end(); ++it) {
588 registry_copy.push_back(it->second);
590 STLDeleteElements(®istry_copy);
594 void PortForwardingController::UpdateConnections() {
595 for (Registry::iterator it = registry_.begin(); it != registry_.end(); ++it)
596 it->second->UpdateForwardingMap(forwarding_map_);