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