1 // Copyright (c) 2011 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 <errno.h> 6 #include <signal.h> 7 #include <stdio.h> 8 #include <sys/file.h> 9 #include <sys/stat.h> 10 11 #include <string> 12 #include <vector> 13 14 #include "base/command_line.h" 15 #include "base/logging.h" 16 #include "base/synchronization/lock.h" 17 #include "base/timer/timer.h" 18 #include "net/tools/balsa/split.h" 19 #include "net/tools/flip_server/acceptor_thread.h" 20 #include "net/tools/flip_server/constants.h" 21 #include "net/tools/flip_server/flip_config.h" 22 #include "net/tools/flip_server/output_ordering.h" 23 #include "net/tools/flip_server/sm_connection.h" 24 #include "net/tools/flip_server/sm_interface.h" 25 #include "net/tools/flip_server/spdy_interface.h" 26 #include "net/tools/flip_server/streamer_interface.h" 27 28 // If true, then disables the nagle algorithm); 29 bool FLAGS_disable_nagle = true; 30 31 // The number of times that accept() will be called when the 32 // alarm goes off when the accept_using_alarm flag is set to true. 33 // If set to 0, accept() will be performed until the accept queue 34 // is completely drained and the accept() call returns an error); 35 int32 FLAGS_accepts_per_wake = 0; 36 37 // The size of the TCP accept backlog); 38 int32 FLAGS_accept_backlog_size = 1024; 39 40 // If set to false a single socket will be used. If set to true 41 // then a new socket will be created for each accept thread. 42 // Note that this only works with kernels that support 43 // SO_REUSEPORT); 44 bool FLAGS_reuseport = false; 45 46 // Flag to force spdy, even if NPN is not negotiated. 47 bool FLAGS_force_spdy = false; 48 49 // The amount of time the server delays before sending back the 50 // reply); 51 double FLAGS_server_think_time_in_s = 0; 52 53 net::FlipConfig g_proxy_config; 54 55 std::vector<std::string>& split(const std::string& s, 56 char delim, 57 std::vector<std::string>& elems) { 58 std::stringstream ss(s); 59 std::string item; 60 while (std::getline(ss, item, delim)) { 61 elems.push_back(item); 62 } 63 return elems; 64 } 65 66 std::vector<std::string> split(const std::string& s, char delim) { 67 std::vector<std::string> elems; 68 return split(s, delim, elems); 69 } 70 71 bool GotQuitFromStdin() { 72 // Make stdin nonblocking. Yes this is done each time. Oh well. 73 fcntl(0, F_SETFL, O_NONBLOCK); 74 char c; 75 std::string maybequit; 76 while (read(0, &c, 1) > 0) { 77 maybequit += c; 78 } 79 if (maybequit.size()) { 80 VLOG(1) << "scanning string: \"" << maybequit << "\""; 81 } 82 return (maybequit.size() > 1 && 83 (maybequit.c_str()[0] == 'q' || maybequit.c_str()[0] == 'Q')); 84 } 85 86 const char* BoolToStr(bool b) { 87 if (b) 88 return "true"; 89 return "false"; 90 } 91 92 static bool wantExit = false; 93 static bool wantLogClose = false; 94 void SignalHandler(int signum) { 95 switch (signum) { 96 case SIGTERM: 97 case SIGINT: 98 wantExit = true; 99 break; 100 case SIGHUP: 101 wantLogClose = true; 102 break; 103 } 104 } 105 106 static int OpenPidFile(const char* pidfile) { 107 int fd; 108 struct stat pid_stat; 109 int ret; 110 111 fd = open(pidfile, O_RDWR | O_CREAT, 0600); 112 if (fd == -1) { 113 fprintf(stderr, "Could not open pid file '%s' for reading.\n", pidfile); 114 exit(1); 115 } 116 117 ret = flock(fd, LOCK_EX | LOCK_NB); 118 if (ret == -1) { 119 if (errno == EWOULDBLOCK) { 120 fprintf(stderr, "Flip server is already running.\n"); 121 } else { 122 perror("Error getting lock on pid file"); 123 } 124 exit(1); 125 } 126 127 if (fstat(fd, &pid_stat) == -1) { 128 fprintf( 129 stderr, "Could not stat pid file '%s': %s\n", pidfile, strerror(errno)); 130 exit(1); 131 } 132 if (pid_stat.st_size != 0) { 133 if (ftruncate(fd, pid_stat.st_size) == -1) { 134 fprintf(stderr, 135 "Could not truncate pid file '%s': %s\n", 136 pidfile, 137 strerror(errno)); 138 exit(1); 139 } 140 } 141 142 char pid_str[8]; 143 snprintf(pid_str, sizeof(pid_str), "%d", getpid()); 144 int bytes = static_cast<int>(strlen(pid_str)); 145 if (write(fd, pid_str, strlen(pid_str)) != bytes) { 146 perror("Could not write pid file"); 147 close(fd); 148 exit(1); 149 } 150 151 return fd; 152 } 153 154 int main(int argc, char** argv) { 155 unsigned int i = 0; 156 bool wait_for_iface = false; 157 int pidfile_fd; 158 159 signal(SIGPIPE, SIG_IGN); 160 signal(SIGTERM, SignalHandler); 161 signal(SIGINT, SignalHandler); 162 signal(SIGHUP, SignalHandler); 163 164 base::CommandLine::Init(argc, argv); 165 base::CommandLine cl(argc, argv); 166 167 if (cl.HasSwitch("help") || argc < 2) { 168 printf("%s <options>\n", argv[0]); 169 printf(" Proxy options:\n"); 170 printf( 171 "\t--proxy<1..n>=\"<listen ip>,<listen port>," 172 "<ssl cert filename>,\n" 173 "\t <ssl key filename>,<http server ip>," 174 "<http server port>,\n" 175 "\t [https server ip],[https server port]," 176 "<spdy only 0|1>\"\n" 177 "\t * The https server ip and port may be left empty if they are" 178 " the same as\n" 179 "\t the http server fields.\n" 180 "\t * spdy only prevents non-spdy https connections from being" 181 " passed\n" 182 "\t through the proxy listen ip:port.\n" 183 "\t--forward-ip-header=<header name>\n" 184 "\n Server options:\n" 185 "\t--spdy-server=\"<listen ip>,<listen port>,[ssl cert filename]," 186 "\n\t [ssl key filename]\"\n" 187 "\t--http-server=\"<listen ip>,<listen port>,[ssl cert filename]," 188 "\n\t [ssl key filename]\"\n" 189 "\t * Leaving the ssl cert and key fields empty will disable ssl" 190 " for the\n" 191 "\t http and spdy flip servers\n" 192 "\n Global options:\n" 193 "\t--logdest=<file|system|both>\n" 194 "\t--logfile=<logfile>\n" 195 "\t--wait-for-iface\n" 196 "\t * The flip server will block until the listen ip has been" 197 " raised.\n" 198 "\t--ssl-session-expiry=<seconds> (default is 300)\n" 199 "\t--ssl-disable-compression\n" 200 "\t--idle-timeout=<seconds> (default is 300)\n" 201 "\t--pidfile=<filepath> (default /var/run/flip-server.pid)\n" 202 "\t--help\n"); 203 exit(0); 204 } 205 206 if (cl.HasSwitch("pidfile")) { 207 pidfile_fd = OpenPidFile(cl.GetSwitchValueASCII("pidfile").c_str()); 208 } else { 209 pidfile_fd = OpenPidFile(PIDFILE); 210 } 211 212 net::OutputOrdering::set_server_think_time_in_s(FLAGS_server_think_time_in_s); 213 214 if (cl.HasSwitch("forward-ip-header")) { 215 net::SpdySM::set_forward_ip_header( 216 cl.GetSwitchValueASCII("forward-ip-header")); 217 net::StreamerSM::set_forward_ip_header( 218 cl.GetSwitchValueASCII("forward-ip-header")); 219 } 220 221 if (cl.HasSwitch("logdest")) { 222 std::string log_dest_value = cl.GetSwitchValueASCII("logdest"); 223 if (log_dest_value.compare("file") == 0) { 224 g_proxy_config.log_destination_ = logging::LOG_TO_FILE; 225 } else if (log_dest_value.compare("system") == 0) { 226 g_proxy_config.log_destination_ = logging::LOG_TO_SYSTEM_DEBUG_LOG; 227 } else if (log_dest_value.compare("both") == 0) { 228 g_proxy_config.log_destination_ = logging::LOG_TO_ALL; 229 } else { 230 LOG(FATAL) << "Invalid logging destination value: " << log_dest_value; 231 } 232 } else { 233 g_proxy_config.log_destination_ = logging::LOG_NONE; 234 } 235 236 if (cl.HasSwitch("logfile")) { 237 g_proxy_config.log_filename_ = cl.GetSwitchValueASCII("logfile"); 238 if (g_proxy_config.log_destination_ == logging::LOG_NONE) { 239 g_proxy_config.log_destination_ = logging::LOG_TO_FILE; 240 } 241 } else if ((g_proxy_config.log_destination_ & logging::LOG_TO_FILE) != 0) { 242 LOG(FATAL) << "Logging destination requires a log file to be specified."; 243 } 244 245 if (cl.HasSwitch("wait-for-iface")) { 246 wait_for_iface = true; 247 } 248 249 if (cl.HasSwitch("ssl-session-expiry")) { 250 std::string session_expiry = cl.GetSwitchValueASCII("ssl-session-expiry"); 251 g_proxy_config.ssl_session_expiry_ = atoi(session_expiry.c_str()); 252 } 253 254 if (cl.HasSwitch("ssl-disable-compression")) { 255 g_proxy_config.ssl_disable_compression_ = true; 256 } 257 258 if (cl.HasSwitch("idle-timeout")) { 259 g_proxy_config.idle_socket_timeout_s_ = 260 atoi(cl.GetSwitchValueASCII("idle-timeout").c_str()); 261 } 262 263 if (cl.HasSwitch("force_spdy")) 264 net::SMConnection::set_force_spdy(true); 265 266 logging::LoggingSettings settings; 267 settings.logging_dest = g_proxy_config.log_destination_; 268 settings.log_file = g_proxy_config.log_filename_.c_str(); 269 settings.lock_log = logging::DONT_LOCK_LOG_FILE; 270 logging::InitLogging(settings); 271 272 LOG(INFO) << "Flip SPDY proxy started with configuration:"; 273 LOG(INFO) << "Logging destination : " << g_proxy_config.log_destination_; 274 LOG(INFO) << "Log file : " << g_proxy_config.log_filename_; 275 LOG(INFO) << "Forward IP Header : " 276 << (net::SpdySM::forward_ip_header().length() 277 ? net::SpdySM::forward_ip_header() 278 : "<disabled>"); 279 LOG(INFO) << "Wait for interfaces : " << (wait_for_iface ? "true" 280 : "false"); 281 LOG(INFO) << "Accept backlog size : " << FLAGS_accept_backlog_size; 282 LOG(INFO) << "Accepts per wake : " << FLAGS_accepts_per_wake; 283 LOG(INFO) << "Disable nagle : " << (FLAGS_disable_nagle ? "true" 284 : "false"); 285 LOG(INFO) << "Reuseport : " << (FLAGS_reuseport ? "true" 286 : "false"); 287 LOG(INFO) << "Force SPDY : " << (FLAGS_force_spdy ? "true" 288 : "false"); 289 LOG(INFO) << "SSL session expiry : " 290 << g_proxy_config.ssl_session_expiry_; 291 LOG(INFO) << "SSL disable compression : " 292 << g_proxy_config.ssl_disable_compression_; 293 LOG(INFO) << "Connection idle timeout : " 294 << g_proxy_config.idle_socket_timeout_s_; 295 296 // Proxy Acceptors 297 while (true) { 298 i += 1; 299 std::stringstream name; 300 name << "proxy" << i; 301 if (!cl.HasSwitch(name.str())) { 302 break; 303 } 304 std::string value = cl.GetSwitchValueASCII(name.str()); 305 std::vector<std::string> valueArgs = split(value, ','); 306 CHECK_EQ((unsigned int)9, valueArgs.size()); 307 int spdy_only = atoi(valueArgs[8].c_str()); 308 // If wait_for_iface is enabled, then this call will block 309 // indefinitely until the interface is raised. 310 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_PROXY, 311 valueArgs[0], 312 valueArgs[1], 313 valueArgs[2], 314 valueArgs[3], 315 valueArgs[4], 316 valueArgs[5], 317 valueArgs[6], 318 valueArgs[7], 319 spdy_only, 320 FLAGS_accept_backlog_size, 321 FLAGS_disable_nagle, 322 FLAGS_accepts_per_wake, 323 FLAGS_reuseport, 324 wait_for_iface, 325 NULL); 326 } 327 328 // Spdy Server Acceptor 329 net::MemoryCache spdy_memory_cache; 330 if (cl.HasSwitch("spdy-server")) { 331 spdy_memory_cache.AddFiles(); 332 std::string value = cl.GetSwitchValueASCII("spdy-server"); 333 std::vector<std::string> valueArgs = split(value, ','); 334 while (valueArgs.size() < 4) 335 valueArgs.push_back(std::string()); 336 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_SPDY_SERVER, 337 valueArgs[0], 338 valueArgs[1], 339 valueArgs[2], 340 valueArgs[3], 341 std::string(), 342 std::string(), 343 std::string(), 344 std::string(), 345 0, 346 FLAGS_accept_backlog_size, 347 FLAGS_disable_nagle, 348 FLAGS_accepts_per_wake, 349 FLAGS_reuseport, 350 wait_for_iface, 351 &spdy_memory_cache); 352 } 353 354 // Spdy Server Acceptor 355 net::MemoryCache http_memory_cache; 356 if (cl.HasSwitch("http-server")) { 357 http_memory_cache.AddFiles(); 358 std::string value = cl.GetSwitchValueASCII("http-server"); 359 std::vector<std::string> valueArgs = split(value, ','); 360 while (valueArgs.size() < 4) 361 valueArgs.push_back(std::string()); 362 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_HTTP_SERVER, 363 valueArgs[0], 364 valueArgs[1], 365 valueArgs[2], 366 valueArgs[3], 367 std::string(), 368 std::string(), 369 std::string(), 370 std::string(), 371 0, 372 FLAGS_accept_backlog_size, 373 FLAGS_disable_nagle, 374 FLAGS_accepts_per_wake, 375 FLAGS_reuseport, 376 wait_for_iface, 377 &http_memory_cache); 378 } 379 380 std::vector<net::SMAcceptorThread*> sm_worker_threads_; 381 382 for (i = 0; i < g_proxy_config.acceptors_.size(); i++) { 383 net::FlipAcceptor* acceptor = g_proxy_config.acceptors_[i]; 384 385 sm_worker_threads_.push_back(new net::SMAcceptorThread( 386 acceptor, (net::MemoryCache*)acceptor->memory_cache_)); 387 // Note that spdy_memory_cache is not threadsafe, it is merely 388 // thread compatible. Thus, if ever we are to spawn multiple threads, 389 // we either must make the MemoryCache threadsafe, or use 390 // a separate MemoryCache for each thread. 391 // 392 // The latter is what is currently being done as we spawn 393 // a separate thread for each http and spdy server acceptor. 394 395 sm_worker_threads_.back()->InitWorker(); 396 sm_worker_threads_.back()->Start(); 397 } 398 399 while (!wantExit) { 400 // Close logfile when HUP signal is received. Logging system will 401 // automatically reopen on next log message. 402 if (wantLogClose) { 403 wantLogClose = false; 404 VLOG(1) << "HUP received, reopening log file."; 405 logging::CloseLogFile(); 406 } 407 if (GotQuitFromStdin()) { 408 for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) { 409 sm_worker_threads_[i]->Quit(); 410 } 411 for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) { 412 sm_worker_threads_[i]->Join(); 413 } 414 break; 415 } 416 usleep(1000 * 10); // 10 ms 417 } 418 419 unlink(PIDFILE); 420 close(pidfile_fd); 421 return 0; 422 } 423