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