Home | History | Annotate | Download | only in flip_server
      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