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);
29bool 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);
35int32 FLAGS_accepts_per_wake = 0;
36
37// The size of the TCP accept backlog);
38int32 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);
44bool FLAGS_reuseport = false;
45
46// Flag to force spdy, even if NPN is not negotiated.
47bool FLAGS_force_spdy = false;
48
49// The amount of time the server delays before sending back the
50//  reply);
51double FLAGS_server_think_time_in_s = 0;
52
53net::FlipConfig g_proxy_config;
54
55std::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
66std::vector<std::string> split(const std::string& s, char delim) {
67  std::vector<std::string> elems;
68  return split(s, delim, elems);
69}
70
71bool 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
86const char* BoolToStr(bool b) {
87  if (b)
88    return "true";
89  return "false";
90}
91
92static bool wantExit = false;
93static bool wantLogClose = false;
94void 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
106static 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
154int 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