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.
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"
28 // If true, then disables the nagle algorithm);
29 bool FLAGS_disable_nagle
= true;
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;
37 // The size of the TCP accept backlog);
38 int32 FLAGS_accept_backlog_size
= 1024;
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
44 bool FLAGS_reuseport
= false;
46 // Flag to force spdy, even if NPN is not negotiated.
47 bool FLAGS_force_spdy
= false;
49 // The amount of time the server delays before sending back the
51 double FLAGS_server_think_time_in_s
= 0;
53 net::FlipConfig g_proxy_config
;
55 std::vector
<std::string
>& split(const std::string
& s
,
57 std::vector
<std::string
>& elems
) {
58 std::stringstream
ss(s
);
60 while (std::getline(ss
, item
, delim
)) {
61 elems
.push_back(item
);
66 std::vector
<std::string
> split(const std::string
& s
, char delim
) {
67 std::vector
<std::string
> elems
;
68 return split(s
, delim
, elems
);
71 bool GotQuitFromStdin() {
72 // Make stdin nonblocking. Yes this is done each time. Oh well.
73 fcntl(0, F_SETFL
, O_NONBLOCK
);
75 std::string maybequit
;
76 while (read(0, &c
, 1) > 0) {
79 if (maybequit
.size()) {
80 VLOG(1) << "scanning string: \"" << maybequit
<< "\"";
82 return (maybequit
.size() > 1 &&
83 (maybequit
.c_str()[0] == 'q' || maybequit
.c_str()[0] == 'Q'));
86 const char* BoolToStr(bool b
) {
92 static bool wantExit
= false;
93 static bool wantLogClose
= false;
94 void SignalHandler(int signum
) {
106 static int OpenPidFile(const char* pidfile
) {
108 struct stat pid_stat
;
111 fd
= open(pidfile
, O_RDWR
| O_CREAT
, 0600);
113 fprintf(stderr
, "Could not open pid file '%s' for reading.\n", pidfile
);
117 ret
= flock(fd
, LOCK_EX
| LOCK_NB
);
119 if (errno
== EWOULDBLOCK
) {
120 fprintf(stderr
, "Flip server is already running.\n");
122 perror("Error getting lock on pid file");
127 if (fstat(fd
, &pid_stat
) == -1) {
129 stderr
, "Could not stat pid file '%s': %s\n", pidfile
, strerror(errno
));
132 if (pid_stat
.st_size
!= 0) {
133 if (ftruncate(fd
, pid_stat
.st_size
) == -1) {
135 "Could not truncate pid file '%s': %s\n",
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");
154 int main(int argc
, char** argv
) {
156 bool wait_for_iface
= false;
159 signal(SIGPIPE
, SIG_IGN
);
160 signal(SIGTERM
, SignalHandler
);
161 signal(SIGINT
, SignalHandler
);
162 signal(SIGHUP
, SignalHandler
);
164 base::CommandLine::Init(argc
, argv
);
165 base::CommandLine
cl(argc
, argv
);
167 if (cl
.HasSwitch("help") || argc
< 2) {
168 printf("%s <options>\n", argv
[0]);
169 printf(" Proxy options:\n");
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"
179 "\t the http server fields.\n"
180 "\t * spdy only prevents non-spdy https connections from being"
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"
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"
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"
206 if (cl
.HasSwitch("pidfile")) {
207 pidfile_fd
= OpenPidFile(cl
.GetSwitchValueASCII("pidfile").c_str());
209 pidfile_fd
= OpenPidFile(PIDFILE
);
212 net::OutputOrdering::set_server_think_time_in_s(FLAGS_server_think_time_in_s
);
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"));
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
;
230 LOG(FATAL
) << "Invalid logging destination value: " << log_dest_value
;
233 g_proxy_config
.log_destination_
= logging::LOG_NONE
;
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
;
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.";
245 if (cl
.HasSwitch("wait-for-iface")) {
246 wait_for_iface
= true;
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());
254 if (cl
.HasSwitch("ssl-disable-compression")) {
255 g_proxy_config
.ssl_disable_compression_
= true;
258 if (cl
.HasSwitch("idle-timeout")) {
259 g_proxy_config
.idle_socket_timeout_s_
=
260 atoi(cl
.GetSwitchValueASCII("idle-timeout").c_str());
263 if (cl
.HasSwitch("force_spdy"))
264 net::SMConnection::set_force_spdy(true);
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
);
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()
279 LOG(INFO
) << "Wait for interfaces : " << (wait_for_iface
? "true"
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"
285 LOG(INFO
) << "Reuseport : " << (FLAGS_reuseport
? "true"
287 LOG(INFO
) << "Force SPDY : " << (FLAGS_force_spdy
? "true"
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_
;
299 std::stringstream name
;
300 name
<< "proxy" << i
;
301 if (!cl
.HasSwitch(name
.str())) {
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
,
320 FLAGS_accept_backlog_size
,
322 FLAGS_accepts_per_wake
,
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
,
346 FLAGS_accept_backlog_size
,
348 FLAGS_accepts_per_wake
,
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
,
372 FLAGS_accept_backlog_size
,
374 FLAGS_accepts_per_wake
,
380 std::vector
<net::SMAcceptorThread
*> sm_worker_threads_
;
382 for (i
= 0; i
< g_proxy_config
.acceptors_
.size(); i
++) {
383 net::FlipAcceptor
* acceptor
= g_proxy_config
.acceptors_
[i
];
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.
392 // The latter is what is currently being done as we spawn
393 // a separate thread for each http and spdy server acceptor.
395 sm_worker_threads_
.back()->InitWorker();
396 sm_worker_threads_
.back()->Start();
400 // Close logfile when HUP signal is received. Logging system will
401 // automatically reopen on next log message.
403 wantLogClose
= false;
404 VLOG(1) << "HUP received, reopening log file.";
405 logging::CloseLogFile();
407 if (GotQuitFromStdin()) {
408 for (unsigned int i
= 0; i
< sm_worker_threads_
.size(); ++i
) {
409 sm_worker_threads_
[i
]->Quit();
411 for (unsigned int i
= 0; i
< sm_worker_threads_
.size(); ++i
) {
412 sm_worker_threads_
[i
]->Join();
416 usleep(1000 * 10); // 10 ms