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
28using std::cout;
29using std::cerr;
30
31// If true, then disables the nagle algorithm);
32bool 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);
38int32 FLAGS_accepts_per_wake = 0;
39
40// The size of the TCP accept backlog);
41int32 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);
47bool FLAGS_reuseport = false;
48
49// Flag to force spdy, even if NPN is not negotiated.
50bool FLAGS_force_spdy = false;
51
52// The amount of time the server delays before sending back the
53//  reply);
54double FLAGS_server_think_time_in_s = 0;
55
56net::FlipConfig g_proxy_config;
57
58////////////////////////////////////////////////////////////////////////////////
59
60std::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
71std::vector<std::string> split(const std::string &s, char delim) {
72  std::vector<std::string> elems;
73  return split(s, delim, elems);
74}
75
76bool 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
92const char* BoolToStr(bool b) {
93  if (b)
94    return "true";
95  return "false";
96}
97
98////////////////////////////////////////////////////////////////////////////////
99
100static bool wantExit = false;
101static bool wantLogClose = false;
102void 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
115static 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
160int 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