1 // Copyright (c) 2009 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.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/streamer_interface.h" 26 #include "net/tools/flip_server/split.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_ONLY_TO_FILE; 231 } else if (log_dest_value.compare("system") == 0) { 232 g_proxy_config.log_destination_ = logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG; 233 } else if (log_dest_value.compare("both") == 0) { 234 g_proxy_config.log_destination_ = 235 logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG; 236 } else { 237 LOG(FATAL) << "Invalid logging destination value: " << log_dest_value; 238 } 239 } else { 240 g_proxy_config.log_destination_ = logging::LOG_NONE; 241 } 242 243 if (cl.HasSwitch("logfile")) { 244 g_proxy_config.log_filename_ = cl.GetSwitchValueASCII("logfile"); 245 if (g_proxy_config.log_destination_ == logging::LOG_NONE) { 246 g_proxy_config.log_destination_ = logging::LOG_ONLY_TO_FILE; 247 } 248 } else if (g_proxy_config.log_destination_ == logging::LOG_ONLY_TO_FILE || 249 g_proxy_config.log_destination_ == 250 logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) { 251 LOG(FATAL) << "Logging destination requires a log file to be specified."; 252 } 253 254 if (cl.HasSwitch("wait-for-iface")) { 255 wait_for_iface = true; 256 } 257 258 if (cl.HasSwitch("ssl-session-expiry")) { 259 std::string session_expiry = cl.GetSwitchValueASCII("ssl-session-expiry"); 260 g_proxy_config.ssl_session_expiry_ = atoi(session_expiry.c_str()); 261 } 262 263 if (cl.HasSwitch("ssl-disable-compression")) { 264 g_proxy_config.ssl_disable_compression_ = true; 265 } 266 267 if (cl.HasSwitch("idle-timeout")) { 268 g_proxy_config.idle_socket_timeout_s_ = 269 atoi(cl.GetSwitchValueASCII("idle-timeout").c_str()); 270 } 271 272 if (cl.HasSwitch("force_spdy")) 273 net::SMConnection::set_force_spdy(true); 274 275 InitLogging(g_proxy_config.log_filename_.c_str(), 276 g_proxy_config.log_destination_, 277 logging::DONT_LOCK_LOG_FILE, 278 logging::APPEND_TO_OLD_LOG_FILE, 279 logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); 280 281 LOG(INFO) << "Flip SPDY proxy started with configuration:"; 282 LOG(INFO) << "Logging destination : " << g_proxy_config.log_destination_; 283 LOG(INFO) << "Log file : " << g_proxy_config.log_filename_; 284 LOG(INFO) << "Forward IP Header : " 285 << (net::SpdySM::forward_ip_header().length() ? 286 net::SpdySM::forward_ip_header() : "<disabled>"); 287 LOG(INFO) << "Wait for interfaces : " << (wait_for_iface?"true":"false"); 288 LOG(INFO) << "Accept backlog size : " << FLAGS_accept_backlog_size; 289 LOG(INFO) << "Accepts per wake : " << FLAGS_accepts_per_wake; 290 LOG(INFO) << "Disable nagle : " 291 << (FLAGS_disable_nagle?"true":"false"); 292 LOG(INFO) << "Reuseport : " 293 << (FLAGS_reuseport?"true":"false"); 294 LOG(INFO) << "Force SPDY : " 295 << (FLAGS_force_spdy?"true":"false"); 296 LOG(INFO) << "SSL session expiry : " 297 << g_proxy_config.ssl_session_expiry_; 298 LOG(INFO) << "SSL disable compression : " 299 << g_proxy_config.ssl_disable_compression_; 300 LOG(INFO) << "Connection idle timeout : " 301 << g_proxy_config.idle_socket_timeout_s_; 302 303 // Proxy Acceptors 304 while (true) { 305 i += 1; 306 std::stringstream name; 307 name << "proxy" << i; 308 if (!cl.HasSwitch(name.str())) { 309 break; 310 } 311 std::string value = cl.GetSwitchValueASCII(name.str()); 312 std::vector<std::string> valueArgs = split(value, ','); 313 CHECK_EQ((unsigned int)9, valueArgs.size()); 314 int spdy_only = atoi(valueArgs[8].c_str()); 315 // If wait_for_iface is enabled, then this call will block 316 // indefinitely until the interface is raised. 317 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_PROXY, 318 valueArgs[0], valueArgs[1], 319 valueArgs[2], valueArgs[3], 320 valueArgs[4], valueArgs[5], 321 valueArgs[6], valueArgs[7], 322 spdy_only, 323 FLAGS_accept_backlog_size, 324 FLAGS_disable_nagle, 325 FLAGS_accepts_per_wake, 326 FLAGS_reuseport, 327 wait_for_iface, 328 NULL); 329 } 330 331 // Spdy Server Acceptor 332 net::MemoryCache spdy_memory_cache; 333 if (cl.HasSwitch("spdy-server")) { 334 spdy_memory_cache.AddFiles(); 335 std::string value = cl.GetSwitchValueASCII("spdy-server"); 336 std::vector<std::string> valueArgs = split(value, ','); 337 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_SPDY_SERVER, 338 valueArgs[0], valueArgs[1], 339 valueArgs[2], valueArgs[3], 340 "", "", "", "", 341 0, 342 FLAGS_accept_backlog_size, 343 FLAGS_disable_nagle, 344 FLAGS_accepts_per_wake, 345 FLAGS_reuseport, 346 wait_for_iface, 347 &spdy_memory_cache); 348 } 349 350 // Spdy Server Acceptor 351 net::MemoryCache http_memory_cache; 352 if (cl.HasSwitch("http-server")) { 353 http_memory_cache.AddFiles(); 354 std::string value = cl.GetSwitchValueASCII("http-server"); 355 std::vector<std::string> valueArgs = split(value, ','); 356 g_proxy_config.AddAcceptor(net::FLIP_HANDLER_HTTP_SERVER, 357 valueArgs[0], valueArgs[1], 358 valueArgs[2], valueArgs[3], 359 "", "", "", "", 360 0, 361 FLAGS_accept_backlog_size, 362 FLAGS_disable_nagle, 363 FLAGS_accepts_per_wake, 364 FLAGS_reuseport, 365 wait_for_iface, 366 &http_memory_cache); 367 } 368 369 std::vector<net::SMAcceptorThread*> sm_worker_threads_; 370 371 for (i = 0; i < g_proxy_config.acceptors_.size(); i++) { 372 net::FlipAcceptor *acceptor = g_proxy_config.acceptors_[i]; 373 374 sm_worker_threads_.push_back( 375 new net::SMAcceptorThread(acceptor, 376 (net::MemoryCache *)acceptor->memory_cache_)); 377 // Note that spdy_memory_cache is not threadsafe, it is merely 378 // thread compatible. Thus, if ever we are to spawn multiple threads, 379 // we either must make the MemoryCache threadsafe, or use 380 // a separate MemoryCache for each thread. 381 // 382 // The latter is what is currently being done as we spawn 383 // a separate thread for each http and spdy server acceptor. 384 385 sm_worker_threads_.back()->InitWorker(); 386 sm_worker_threads_.back()->Start(); 387 } 388 389 while (!wantExit) { 390 // Close logfile when HUP signal is received. Logging system will 391 // automatically reopen on next log message. 392 if ( wantLogClose ) { 393 wantLogClose = false; 394 VLOG(1) << "HUP received, reopening log file."; 395 logging::CloseLogFile(); 396 } 397 if (GotQuitFromStdin()) { 398 for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) { 399 sm_worker_threads_[i]->Quit(); 400 } 401 for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) { 402 sm_worker_threads_[i]->Join(); 403 } 404 break; 405 } 406 usleep(1000*10); // 10 ms 407 } 408 409 unlink(PIDFILE); 410 close(pidfile_fd); 411 return 0; 412 } 413 414