1 // Copyright (c) 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 <stddef.h>
6 #include <stdint.h>
7 #include <stdio.h>
8
9 #include <locale>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/at_exit.h"
16 #include "base/bind.h"
17 #include "base/callback.h"
18 #include "base/command_line.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.h"
21 #include "base/json/json_reader.h"
22 #include "base/lazy_instance.h"
23 #include "base/logging.h"
24 #include "base/message_loop/message_pump_type.h"
25 #include "base/run_loop.h"
26 #include "base/single_thread_task_runner.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string_number_conversions.h"
29 #include "base/strings/string_split.h"
30 #include "base/strings/string_util.h"
31 #include "base/strings/stringprintf.h"
32 #include "base/synchronization/waitable_event.h"
33 #include "base/task/single_thread_task_executor.h"
34 #include "base/task/thread_pool/thread_pool_instance.h"
35 #include "base/threading/thread.h"
36 #include "base/threading/thread_local.h"
37 #include "base/threading/thread_task_runner_handle.h"
38 #include "build/build_config.h"
39 #include "chrome/test/chromedriver/constants/version.h"
40 #include "chrome/test/chromedriver/logging.h"
41 #include "chrome/test/chromedriver/server/http_handler.h"
42 #include "chrome/test/chromedriver/server/http_server.h"
43 #include "mojo/core/embedder/embedder.h"
44 #include "net/base/ip_address.h"
45 #include "net/base/ip_endpoint.h"
46 #include "net/base/net_errors.h"
47 #include "net/log/net_log_source.h"
48
49 namespace {
50
51 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
52 // Ensure that there is a writable shared memory directory. We use
53 // network::SimpleURLLoader to connect to Chrome, and it calls
54 // base::subtle::PlatformSharedMemoryRegion::Create to get a shared memory
55 // region. network::SimpleURLLoader would fail if the shared memory directory is
56 // not accessible. We work around this issue by adding --disable-dev-shm-usage
57 // to command line, to use an alternative directory for shared memory.
58 // See https://crbug.com/chromedriver/2782.
EnsureSharedMemory(base::CommandLine * cmd_line)59 void EnsureSharedMemory(base::CommandLine* cmd_line) {
60 if (!cmd_line->HasSwitch("disable-dev-shm-usage")) {
61 base::FilePath directory;
62 if (GetShmemTempDir(false, &directory) &&
63 access(directory.value().c_str(), W_OK | X_OK) < 0) {
64 VLOG(0) << directory
65 << " not writable, adding --disable-dev-shm-usage switch";
66 cmd_line->AppendSwitch("disable-dev-shm-usage");
67 }
68 }
69 }
70 #endif
71
SendResponseOnCmdThread(const scoped_refptr<base::SingleThreadTaskRunner> & io_task_runner,const HttpResponseSenderFunc & send_response_on_io_func,std::unique_ptr<net::HttpServerResponseInfo> response)72 void SendResponseOnCmdThread(
73 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
74 const HttpResponseSenderFunc& send_response_on_io_func,
75 std::unique_ptr<net::HttpServerResponseInfo> response) {
76 io_task_runner->PostTask(
77 FROM_HERE, base::BindOnce(send_response_on_io_func, std::move(response)));
78 }
79
HandleRequestOnCmdThread(HttpHandler * handler,const std::vector<net::IPAddress> & allowed_ips,const net::HttpServerRequestInfo & request,const HttpResponseSenderFunc & send_response_func)80 void HandleRequestOnCmdThread(
81 HttpHandler* handler,
82 const std::vector<net::IPAddress>& allowed_ips,
83 const net::HttpServerRequestInfo& request,
84 const HttpResponseSenderFunc& send_response_func) {
85 if (!allowed_ips.empty()) {
86 const net::IPAddress& peer_address = request.peer.address();
87 if (!base::Contains(allowed_ips, peer_address)) {
88 LOG(WARNING) << "unauthorized access from " << request.peer.ToString();
89 std::unique_ptr<net::HttpServerResponseInfo> response(
90 new net::HttpServerResponseInfo(net::HTTP_UNAUTHORIZED));
91 response->SetBody("Unauthorized access", "text/plain");
92 send_response_func.Run(std::move(response));
93 return;
94 }
95 }
96
97 handler->Handle(request, send_response_func);
98 }
99
HandleRequestOnIOThread(const scoped_refptr<base::SingleThreadTaskRunner> & cmd_task_runner,const HttpRequestHandlerFunc & handle_request_on_cmd_func,const net::HttpServerRequestInfo & request,const HttpResponseSenderFunc & send_response_func)100 void HandleRequestOnIOThread(
101 const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner,
102 const HttpRequestHandlerFunc& handle_request_on_cmd_func,
103 const net::HttpServerRequestInfo& request,
104 const HttpResponseSenderFunc& send_response_func) {
105 cmd_task_runner->PostTask(
106 FROM_HERE,
107 base::BindOnce(handle_request_on_cmd_func, request,
108 base::BindRepeating(&SendResponseOnCmdThread,
109 base::ThreadTaskRunnerHandle::Get(),
110 send_response_func)));
111 }
112
113 base::LazyInstance<base::ThreadLocalPointer<HttpServer>>::DestructorAtExit
114 lazy_tls_server_ipv4 = LAZY_INSTANCE_INITIALIZER;
115 base::LazyInstance<base::ThreadLocalPointer<HttpServer>>::DestructorAtExit
116 lazy_tls_server_ipv6 = LAZY_INSTANCE_INITIALIZER;
117
StopServerOnIOThread()118 void StopServerOnIOThread() {
119 // Note, |server| may be NULL.
120 HttpServer* server = lazy_tls_server_ipv4.Pointer()->Get();
121 lazy_tls_server_ipv4.Pointer()->Set(NULL);
122 delete server;
123
124 server = lazy_tls_server_ipv6.Pointer()->Get();
125 lazy_tls_server_ipv6.Pointer()->Set(NULL);
126 delete server;
127 }
128
StartServerOnIOThread(uint16_t port,bool allow_remote,const std::string & url_base,const std::vector<net::IPAddress> & allowed_ips,const HttpRequestHandlerFunc & handle_request_func,base::WeakPtr<HttpHandler> handler,const scoped_refptr<base::SingleThreadTaskRunner> & cmd_task_runner)129 void StartServerOnIOThread(
130 uint16_t port,
131 bool allow_remote,
132 const std::string& url_base,
133 const std::vector<net::IPAddress>& allowed_ips,
134 const HttpRequestHandlerFunc& handle_request_func,
135 base::WeakPtr<HttpHandler> handler,
136 const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner) {
137 std::unique_ptr<HttpServer> temp_server;
138
139 // On Linux and Windows, we listen to IPv6 first, and then optionally listen
140 // to IPv4 (depending on |need_ipv4| below). The reason is listening to an
141 // IPv6 port may automatically listen to the same IPv4 port as well, and would
142 // return an error if the IPv4 port is already in use.
143 //
144 // On Mac, however, we listen to IPv4 first before listening to IPv6. If we
145 // were to listen to IPv6 first, it would succeed whether the corresponding
146 // IPv4 port is in use or not, and we wouldn't know if we ended up listening
147 // to both IPv4 and IPv6 ports, or only IPv6 port. Listening to IPv4 first
148 // ensures that we successfully listen to both IPv4 and IPv6.
149
150 #if defined(OS_MAC)
151 temp_server = std::make_unique<HttpServer>(
152 url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
153 int ipv4_status = temp_server->Start(port, allow_remote, true);
154 if (ipv4_status == net::OK) {
155 lazy_tls_server_ipv4.Pointer()->Set(temp_server.release());
156 } else if (ipv4_status == net::ERR_ADDRESS_IN_USE) {
157 // ERR_ADDRESS_IN_USE causes an immediate exit, since it indicates the port
158 // is being used by another process. Other errors are assumed to indicate
159 // that IPv4 isn't available for some reason, e.g., on an IPv6-only host.
160 // Thus the error doesn't cause an exit immediately. The HttpServer::Start
161 // method has already printed a message indicating what has happened. Later,
162 // near the end of this function, we exit if both IPv4 and IPv6 failed.
163 printf("IPv4 port not available. Exiting...\n");
164 exit(1);
165 }
166 #endif
167
168 temp_server = std::make_unique<HttpServer>(
169 url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
170 int ipv6_status = temp_server->Start(port, allow_remote, false);
171 if (ipv6_status == net::OK) {
172 lazy_tls_server_ipv6.Pointer()->Set(temp_server.release());
173 } else if (ipv6_status == net::ERR_ADDRESS_IN_USE) {
174 printf("IPv6 port not available. Exiting...\n");
175 exit(1);
176 }
177
178 #if !defined(OS_MAC)
179 // In some cases, binding to an IPv6 port also binds to the same IPv4 port.
180 // The following code determines if it is necessary to bind to IPv4 port.
181 enum class NeedIPv4 { NOT_NEEDED, UNKNOWN, NEEDED } need_ipv4;
182 // Dual-protocol bind deosn't work while binding to localhost (!allow_remote).
183 if (!allow_remote || ipv6_status != net::OK) {
184 need_ipv4 = NeedIPv4::NEEDED;
185 } else {
186 // Currently, the network layer provides no way for us to control dual-protocol
187 // bind option, or to query the current setting of that option, so we do our
188 // best to determine the current setting. See https://crbug.com/858892.
189 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
190 // On Linux, dual-protocol bind is controlled by a system file.
191 // ChromeOS builds also have OS_LINUX defined, so the code below applies.
192 std::string bindv6only;
193 base::FilePath bindv6only_filename("/proc/sys/net/ipv6/bindv6only");
194 if (!base::ReadFileToString(bindv6only_filename, &bindv6only)) {
195 LOG(WARNING) << "Unable to read " << bindv6only_filename << ".";
196 need_ipv4 = NeedIPv4::UNKNOWN;
197 } else if (bindv6only == "1\n") {
198 need_ipv4 = NeedIPv4::NEEDED;
199 } else if (bindv6only == "0\n") {
200 need_ipv4 = NeedIPv4::NOT_NEEDED;
201 } else {
202 LOG(WARNING) << "Unexpected " << bindv6only_filename << " contents.";
203 need_ipv4 = NeedIPv4::UNKNOWN;
204 }
205 #elif defined(OS_WIN)
206 // On Windows, the net component always enables dual-protocol bind. See
207 // https://chromium.googlesource.com/chromium/src/+/69.0.3464.0/net/socket/socket_descriptor.cc#28.
208 need_ipv4 = NeedIPv4::NOT_NEEDED;
209 #else
210 LOG(WARNING) << "Running on a platform not officially supported by "
211 << kChromeDriverProductFullName << ".";
212 need_ipv4 = NeedIPv4::UNKNOWN;
213 #endif
214 }
215 int ipv4_status;
216 if (need_ipv4 == NeedIPv4::NOT_NEEDED) {
217 ipv4_status = ipv6_status;
218 } else {
219 temp_server = std::make_unique<HttpServer>(
220 url_base, allowed_ips, handle_request_func, handler, cmd_task_runner);
221 ipv4_status = temp_server->Start(port, allow_remote, true);
222 if (ipv4_status == net::OK) {
223 lazy_tls_server_ipv4.Pointer()->Set(temp_server.release());
224 } else if (ipv4_status == net::ERR_ADDRESS_IN_USE) {
225 if (need_ipv4 == NeedIPv4::NEEDED) {
226 printf("IPv4 port not available. Exiting...\n");
227 exit(1);
228 } else {
229 printf("Unable to determine if bind to IPv4 port was successful.\n");
230 }
231 }
232 }
233 #endif // !defined(OS_MAC)
234
235 if (ipv4_status != net::OK && ipv6_status != net::OK) {
236 printf("Unable to start server with either IPv4 or IPv6. Exiting...\n");
237 exit(1);
238 }
239 printf("%s was started successfully.\n", kChromeDriverProductShortName);
240 fflush(stdout);
241 }
242
RunServer(uint16_t port,bool allow_remote,const std::vector<net::IPAddress> & allowed_ips,const std::string & url_base,int adb_port)243 void RunServer(uint16_t port,
244 bool allow_remote,
245 const std::vector<net::IPAddress>& allowed_ips,
246 const std::string& url_base,
247 int adb_port) {
248 base::Thread io_thread(
249 base::StringPrintf("%s IO", kChromeDriverProductShortName));
250 CHECK(io_thread.StartWithOptions(
251 base::Thread::Options(base::MessagePumpType::IO, 0)));
252
253 base::SingleThreadTaskExecutor main_task_executor;
254 base::RunLoop cmd_run_loop;
255 HttpHandler handler(cmd_run_loop.QuitClosure(), io_thread.task_runner(),
256 main_task_executor.task_runner(), url_base, adb_port);
257 HttpRequestHandlerFunc handle_request_func =
258 base::BindRepeating(&HandleRequestOnCmdThread, &handler, allowed_ips);
259
260 io_thread.task_runner()->PostTask(
261 FROM_HERE,
262 base::BindOnce(&StartServerOnIOThread, port, allow_remote, url_base,
263 allowed_ips,
264 base::BindRepeating(&HandleRequestOnIOThread,
265 main_task_executor.task_runner(),
266 handle_request_func),
267 handler.WeakPtr(), main_task_executor.task_runner()));
268 // Run the command loop. This loop is quit after the response for a shutdown
269 // request is posted to the IO loop. After the command loop quits, a task
270 // is posted to the IO loop to stop the server. Lastly, the IO thread is
271 // destroyed, which waits until all pending tasks have been completed.
272 // This assumes the response is sent synchronously as part of the IO task.
273 cmd_run_loop.Run();
274 io_thread.task_runner()->PostTask(FROM_HERE,
275 base::BindOnce(&StopServerOnIOThread));
276 }
277
278 } // namespace
279
main(int argc,char * argv[])280 int main(int argc, char *argv[]) {
281 base::CommandLine::Init(argc, argv);
282
283 base::AtExitManager at_exit;
284 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
285
286 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_BSD)
287 // Select the locale from the environment by passing an empty string instead
288 // of the default "C" locale. This is particularly needed for the keycode
289 // conversion code to work.
290 setlocale(LC_ALL, "");
291 #endif
292
293 // Parse command line flags.
294 uint16_t port = 9515;
295 int adb_port = 5037;
296 bool allow_remote = false;
297 std::vector<net::IPAddress> allowed_ips;
298 std::string allowlist;
299 std::string url_base;
300 if (cmd_line->HasSwitch("h") || cmd_line->HasSwitch("help")) {
301 std::string options;
302 const char* const kOptionAndDescriptions[] = {
303 "port=PORT",
304 "port to listen on",
305 "adb-port=PORT",
306 "adb server port",
307 "log-path=FILE",
308 "write server log to file instead of stderr, "
309 "increases log level to INFO",
310 "log-level=LEVEL",
311 "set log level: ALL, DEBUG, INFO, WARNING, SEVERE, OFF",
312 "verbose",
313 "log verbosely (equivalent to --log-level=ALL)",
314 "silent",
315 "log nothing (equivalent to --log-level=OFF)",
316 "append-log",
317 "append log file instead of rewriting",
318 "replayable",
319 "(experimental) log verbosely and don't truncate long "
320 "strings so that the log can be replayed.",
321 "version",
322 "print the version number and exit",
323 "url-base",
324 "base URL path prefix for commands, e.g. wd/url",
325 "readable-timestamp",
326 "add readable timestamps to log",
327 "enable-chrome-logs",
328 "show logs from the browser (overrides other logging options)",
329 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
330 "disable-dev-shm-usage",
331 "do not use /dev/shm "
332 "(add this switch if seeing errors related to shared memory)",
333 #endif
334 };
335 for (size_t i = 0; i < base::size(kOptionAndDescriptions) - 1; i += 2) {
336 options += base::StringPrintf(
337 " --%-30s%s\n",
338 kOptionAndDescriptions[i], kOptionAndDescriptions[i + 1]);
339 }
340
341 // Add helper info for allowed-ips since the product name may be
342 // different.
343 options += base::StringPrintf(
344 " --%-30scomma-separated allowlist of remote IP addresses which are "
345 "allowed to connect to %s\n",
346 "allowed-ips", kChromeDriverProductShortName);
347
348 printf("Usage: %s [OPTIONS]\n\nOptions\n%s", argv[0], options.c_str());
349 return 0;
350 }
351 bool early_exit = false;
352 if (cmd_line->HasSwitch("v") || cmd_line->HasSwitch("version")) {
353 printf("%s %s\n", kChromeDriverProductFullName, kChromeDriverVersion);
354 early_exit = true;
355 }
356 if (early_exit)
357 return 0;
358 if (cmd_line->HasSwitch("port")) {
359 int cmd_line_port;
360 if (!base::StringToInt(cmd_line->GetSwitchValueASCII("port"),
361 &cmd_line_port) ||
362 cmd_line_port < 0 || cmd_line_port > 65535) {
363 printf("Invalid port. Exiting...\n");
364 return 1;
365 }
366 port = static_cast<uint16_t>(cmd_line_port);
367 }
368 if (cmd_line->HasSwitch("adb-port")) {
369 if (!base::StringToInt(cmd_line->GetSwitchValueASCII("adb-port"),
370 &adb_port)) {
371 printf("Invalid adb-port. Exiting...\n");
372 return 1;
373 }
374 }
375 if (cmd_line->HasSwitch("url-base"))
376 url_base = cmd_line->GetSwitchValueASCII("url-base");
377 if (url_base.empty() || url_base.front() != '/')
378 url_base = "/" + url_base;
379 if (url_base.back() != '/')
380 url_base = url_base + "/";
381 if (cmd_line->HasSwitch("allowed-ips") ||
382 cmd_line->HasSwitch("whitelisted-ips")) {
383 allow_remote = true;
384 if (cmd_line->HasSwitch("allowed-ips"))
385 allowlist = cmd_line->GetSwitchValueASCII("allowed-ips");
386 else
387 allowlist = cmd_line->GetSwitchValueASCII("whitelisted-ips");
388
389 std::vector<std::string> allowlist_ip_strs = base::SplitString(
390 allowlist, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
391 if (!allowlist_ip_strs.empty()) {
392 // Convert IP address strings into net::IPAddress objects.
393 for (const auto& ip_str : allowlist_ip_strs) {
394 base::StringPiece ip_str_piece(ip_str);
395 if (ip_str_piece.size() >= 2 && ip_str_piece.front() == '[' &&
396 ip_str_piece.back() == ']') {
397 ip_str_piece.remove_prefix(1);
398 ip_str_piece.remove_suffix(1);
399 }
400 net::IPAddress ip;
401 if (!ip.AssignFromIPLiteral(ip_str_piece)) {
402 printf("Invalid IP address %s. Exiting...\n", ip_str.c_str());
403 return 1;
404 }
405 allowed_ips.push_back(ip);
406 if (ip.IsIPv4()) {
407 allowed_ips.push_back(net::ConvertIPv4ToIPv4MappedIPv6(ip));
408 } else if (ip.IsIPv4MappedIPv6()) {
409 allowed_ips.push_back(net::ConvertIPv4MappedIPv6ToIPv4(ip));
410 }
411 }
412 allowed_ips.push_back(net::IPAddress::IPv4Localhost());
413 allowed_ips.push_back(net::IPAddress::IPv6Localhost());
414 allowed_ips.push_back(
415 net::ConvertIPv4ToIPv4MappedIPv6(net::IPAddress::IPv4Localhost()));
416 }
417 }
418 if (!cmd_line->HasSwitch("silent") &&
419 cmd_line->GetSwitchValueASCII("log-level") != "OFF") {
420 printf("Starting %s %s on port %u\n", kChromeDriverProductShortName,
421 kChromeDriverVersion, port);
422 if (!allow_remote) {
423 printf("Only local connections are allowed.\n");
424 } else if (!allowed_ips.empty()) {
425 printf("Remote connections are allowed by an allowlist (%s).\n",
426 allowlist.c_str());
427 } else {
428 printf("All remote connections are allowed. Use an allowlist instead!\n");
429 }
430 printf("%s\n", GetPortProtectionMessage());
431 fflush(stdout);
432 }
433
434 if (!InitLogging(port)) {
435 printf("Unable to initialize logging. Exiting...\n");
436 return 1;
437 }
438
439 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
440 EnsureSharedMemory(cmd_line);
441 #endif
442
443 mojo::core::Init();
444
445 base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
446 kChromeDriverProductShortName);
447
448 RunServer(port, allow_remote, allowed_ips, url_base, adb_port);
449
450 // clean up
451 base::ThreadPoolInstance::Get()->Shutdown();
452 return 0;
453 }
454