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/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/split.h"
26 #include "net/tools/flip_server/streamer_interface.h"
31 // If true, then disables the nagle algorithm);
32 bool FLAGS_disable_nagle
= true;
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;
40 // The size of the TCP accept backlog);
41 int32 FLAGS_accept_backlog_size
= 1024;
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
47 bool FLAGS_reuseport
= false;
49 // Flag to force spdy, even if NPN is not negotiated.
50 bool FLAGS_force_spdy
= false;
52 // The amount of time the server delays before sending back the
54 double FLAGS_server_think_time_in_s
= 0;
56 net::FlipConfig g_proxy_config
;
58 ////////////////////////////////////////////////////////////////////////////////
60 std::vector
<std::string
> &split(const std::string
&s
,
62 std::vector
<std::string
> &elems
) {
63 std::stringstream
ss(s
);
65 while(std::getline(ss
, item
, delim
)) {
66 elems
.push_back(item
);
71 std::vector
<std::string
> split(const std::string
&s
, char delim
) {
72 std::vector
<std::string
> elems
;
73 return split(s
, delim
, elems
);
76 bool GotQuitFromStdin() {
77 // Make stdin nonblocking. Yes this is done each time. Oh well.
78 fcntl(0, F_SETFL
, O_NONBLOCK
);
80 std::string maybequit
;
81 while (read(0, &c
, 1) > 0) {
84 if (maybequit
.size()) {
85 VLOG(1) << "scanning string: \"" << maybequit
<< "\"";
87 return (maybequit
.size() > 1 &&
88 (maybequit
.c_str()[0] == 'q' ||
89 maybequit
.c_str()[0] == 'Q'));
92 const char* BoolToStr(bool b
) {
98 ////////////////////////////////////////////////////////////////////////////////
100 static bool wantExit
= false;
101 static bool wantLogClose
= false;
102 void SignalHandler(int signum
)
115 static int OpenPidFile(const char *pidfile
)
118 struct stat pid_stat
;
121 fd
= open(pidfile
, O_RDWR
| O_CREAT
, 0600);
123 cerr
<< "Could not open pid file '" << pidfile
<< "' for reading.\n";
127 ret
= flock(fd
, LOCK_EX
| LOCK_NB
);
129 if (errno
== EWOULDBLOCK
) {
130 cerr
<< "Flip server is already running.\n";
132 cerr
<< "Error getting lock on pid file: " << strerror(errno
) << "\n";
137 if (fstat(fd
, &pid_stat
) == -1) {
138 cerr
<< "Could not stat pid file '" << pidfile
<< "': " << strerror(errno
)
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";
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";
160 int main (int argc
, char**argv
)
163 bool wait_for_iface
= false;
166 signal(SIGPIPE
, SIG_IGN
);
167 signal(SIGTERM
, SignalHandler
);
168 signal(SIGINT
, SignalHandler
);
169 signal(SIGHUP
, SignalHandler
);
171 CommandLine::Init(argc
, argv
);
172 CommandLine
cl(argc
, argv
);
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"
185 << "\t the http server fields.\n";
186 cout
<< "\t * spdy only prevents non-spdy https connections from being"
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"
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"
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";
212 if (cl
.HasSwitch("pidfile")) {
213 pidfile_fd
= OpenPidFile(cl
.GetSwitchValueASCII("pidfile").c_str());
215 pidfile_fd
= OpenPidFile(PIDFILE
);
218 net::OutputOrdering::set_server_think_time_in_s(FLAGS_server_think_time_in_s
);
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"));
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_TO_FILE
;
231 } else if (log_dest_value
.compare("system") == 0) {
232 g_proxy_config
.log_destination_
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
233 } else if (log_dest_value
.compare("both") == 0) {
234 g_proxy_config
.log_destination_
= logging::LOG_TO_ALL
;
236 LOG(FATAL
) << "Invalid logging destination value: " << log_dest_value
;
239 g_proxy_config
.log_destination_
= logging::LOG_NONE
;
242 if (cl
.HasSwitch("logfile")) {
243 g_proxy_config
.log_filename_
= cl
.GetSwitchValueASCII("logfile");
244 if (g_proxy_config
.log_destination_
== logging::LOG_NONE
) {
245 g_proxy_config
.log_destination_
= logging::LOG_TO_FILE
;
247 } else if ((g_proxy_config
.log_destination_
& logging::LOG_TO_FILE
) != 0) {
248 LOG(FATAL
) << "Logging destination requires a log file to be specified.";
251 if (cl
.HasSwitch("wait-for-iface")) {
252 wait_for_iface
= true;
255 if (cl
.HasSwitch("ssl-session-expiry")) {
256 std::string session_expiry
= cl
.GetSwitchValueASCII("ssl-session-expiry");
257 g_proxy_config
.ssl_session_expiry_
= atoi(session_expiry
.c_str());
260 if (cl
.HasSwitch("ssl-disable-compression")) {
261 g_proxy_config
.ssl_disable_compression_
= true;
264 if (cl
.HasSwitch("idle-timeout")) {
265 g_proxy_config
.idle_socket_timeout_s_
=
266 atoi(cl
.GetSwitchValueASCII("idle-timeout").c_str());
269 if (cl
.HasSwitch("force_spdy"))
270 net::SMConnection::set_force_spdy(true);
272 logging::LoggingSettings settings
;
273 settings
.logging_dest
= g_proxy_config
.log_destination_
;
274 settings
.log_file
= g_proxy_config
.log_filename_
.c_str();
275 settings
.lock_log
= logging::DONT_LOCK_LOG_FILE
;
276 logging::InitLogging(settings
);
278 LOG(INFO
) << "Flip SPDY proxy started with configuration:";
279 LOG(INFO
) << "Logging destination : " << g_proxy_config
.log_destination_
;
280 LOG(INFO
) << "Log file : " << g_proxy_config
.log_filename_
;
281 LOG(INFO
) << "Forward IP Header : "
282 << (net::SpdySM::forward_ip_header().length() ?
283 net::SpdySM::forward_ip_header() : "<disabled>");
284 LOG(INFO
) << "Wait for interfaces : " << (wait_for_iface
?"true":"false");
285 LOG(INFO
) << "Accept backlog size : " << FLAGS_accept_backlog_size
;
286 LOG(INFO
) << "Accepts per wake : " << FLAGS_accepts_per_wake
;
287 LOG(INFO
) << "Disable nagle : "
288 << (FLAGS_disable_nagle
?"true":"false");
289 LOG(INFO
) << "Reuseport : "
290 << (FLAGS_reuseport
?"true":"false");
291 LOG(INFO
) << "Force SPDY : "
292 << (FLAGS_force_spdy
?"true":"false");
293 LOG(INFO
) << "SSL session expiry : "
294 << g_proxy_config
.ssl_session_expiry_
;
295 LOG(INFO
) << "SSL disable compression : "
296 << g_proxy_config
.ssl_disable_compression_
;
297 LOG(INFO
) << "Connection idle timeout : "
298 << g_proxy_config
.idle_socket_timeout_s_
;
303 std::stringstream name
;
304 name
<< "proxy" << i
;
305 if (!cl
.HasSwitch(name
.str())) {
308 std::string value
= cl
.GetSwitchValueASCII(name
.str());
309 std::vector
<std::string
> valueArgs
= split(value
, ',');
310 CHECK_EQ((unsigned int)9, valueArgs
.size());
311 int spdy_only
= atoi(valueArgs
[8].c_str());
312 // If wait_for_iface is enabled, then this call will block
313 // indefinitely until the interface is raised.
314 g_proxy_config
.AddAcceptor(net::FLIP_HANDLER_PROXY
,
315 valueArgs
[0], valueArgs
[1],
316 valueArgs
[2], valueArgs
[3],
317 valueArgs
[4], valueArgs
[5],
318 valueArgs
[6], valueArgs
[7],
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(
386 new net::SMAcceptorThread(acceptor
,
387 (net::MemoryCache
*)acceptor
->memory_cache_
));
388 // Note that spdy_memory_cache is not threadsafe, it is merely
389 // thread compatible. Thus, if ever we are to spawn multiple threads,
390 // we either must make the MemoryCache threadsafe, or use
391 // a separate MemoryCache for each thread.
393 // The latter is what is currently being done as we spawn
394 // a separate thread for each http and spdy server acceptor.
396 sm_worker_threads_
.back()->InitWorker();
397 sm_worker_threads_
.back()->Start();
401 // Close logfile when HUP signal is received. Logging system will
402 // automatically reopen on next log message.
403 if ( wantLogClose
) {
404 wantLogClose
= false;
405 VLOG(1) << "HUP received, reopening log file.";
406 logging::CloseLogFile();
408 if (GotQuitFromStdin()) {
409 for (unsigned int i
= 0; i
< sm_worker_threads_
.size(); ++i
) {
410 sm_worker_threads_
[i
]->Quit();
412 for (unsigned int i
= 0; i
< sm_worker_threads_
.size(); ++i
) {
413 sm_worker_threads_
[i
]->Join();
417 usleep(1000*10); // 10 ms