/*
Copyright Copyright (C) 2013 Andrey Uzunov
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file mhd2spdy.c
* @brief The main file of the HTTP-to-SPDY proxy with the 'main' function
* and event loop. No threads are used.
* Currently only GET is supported.
* TODOs:
* - non blocking SSL connect
* - check certificate
* - on closing spdy session, close sockets for all requests
* @author Andrey Uzunov
*/
#include "mhd2spdy_structures.h"
#include "mhd2spdy_spdy.h"
#include "mhd2spdy_http.h"
static int run = 1;
//static int spdy_close = 0;
static void
catch_signal(int signal)
{
(void)signal;
//spdy_close = 1;
run = 0;
}
void
print_stat()
{
if(!glob_opt.statistics)
return;
printf("--------------------------\n");
printf("Statistics (TLS overhead is ignored when used):\n");
//printf("HTTP bytes received: %lld\n", glob_stat.http_bytes_received);
//printf("HTTP bytes sent: %lld\n", glob_stat.http_bytes_sent);
printf("SPDY bytes sent: %lld\n", glob_stat.spdy_bytes_sent);
printf("SPDY bytes received: %lld\n", glob_stat.spdy_bytes_received);
printf("SPDY bytes received and dropped: %lld\n", glob_stat.spdy_bytes_received_and_dropped);
}
int
run_everything ()
{
unsigned long long timeoutlong=0;
struct timeval timeout;
int ret;
fd_set rs;
fd_set ws;
fd_set es;
int maxfd = -1;
int maxfd_s = -1;
struct MHD_Daemon *daemon;
nfds_t spdy_npollfds = 1;
struct URI * spdy2http_uri = NULL;
struct SPDY_Connection *connection;
struct SPDY_Connection *connections[MAX_SPDY_CONNECTIONS];
struct SPDY_Connection *connection_for_delete;
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
PRINT_INFO("signal failed");
if (signal(SIGINT, catch_signal) == SIG_ERR)
PRINT_INFO("signal failed");
glob_opt.streams_opened = 0;
glob_opt.responses_pending = 0;
//glob_opt.global_memory = 0;
srand(time(NULL));
if(init_parse_uri(&glob_opt.uri_preg))
DIE("Regexp compilation failed");
if(NULL != glob_opt.spdy2http_str)
{
ret = parse_uri(&glob_opt.uri_preg, glob_opt.spdy2http_str, &spdy2http_uri);
if(ret != 0)
DIE("spdy_parse_uri failed");
}
SSL_load_error_strings();
SSL_library_init();
glob_opt.ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if(glob_opt.ssl_ctx == NULL) {
PRINT_INFO2("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), NULL));
abort();
}
spdy_ssl_init_ssl_ctx(glob_opt.ssl_ctx, &glob_opt.spdy_proto_version);
daemon = MHD_start_daemon (
MHD_SUPPRESS_DATE_NO_CLOCK,
glob_opt.listen_port,
NULL, NULL, &http_cb_request, NULL,
MHD_OPTION_URI_LOG_CALLBACK, &http_cb_log, NULL,
MHD_OPTION_NOTIFY_COMPLETED, &http_cb_request_completed, NULL,
MHD_OPTION_END);
if(NULL==daemon)
DIE("MHD_start_daemon failed");
do
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if(NULL == glob_opt.spdy_connection && NULL != glob_opt.spdy2http_str)
{
glob_opt.spdy_connection = spdy_connect(spdy2http_uri, spdy2http_uri->port, strcmp("https", spdy2http_uri->scheme)==0);
if(NULL == glob_opt.spdy_connection && glob_opt.only_proxy)
PRINT_INFO("cannot connect to the proxy");
}
FD_ZERO(&rs);
FD_ZERO(&ws);
FD_ZERO(&es);
ret = MHD_get_timeout(daemon, &timeoutlong);
if(MHD_NO == ret || timeoutlong > 5000)
timeout.tv_sec = 5;
else
{
timeout.tv_sec = timeoutlong / 1000;
timeout.tv_usec = (timeoutlong % 1000) * 1000;
}
if(MHD_NO == MHD_get_fdset (daemon,
&rs,
&ws,
&es,
&maxfd))
{
PRINT_INFO("MHD_get_fdset error");
}
assert(-1 != maxfd);
maxfd_s = spdy_get_selectfdset(
&rs,
&ws,
&es,
connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds);
if(maxfd_s > maxfd)
maxfd = maxfd_s;
PRINT_INFO2("MHD timeout %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec);
glob_opt.spdy_data_received = false;
ret = select(maxfd+1, &rs, &ws, &es, &timeout);
PRINT_INFO2("timeout now %lld %lld ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret);
switch(ret)
{
case -1:
PRINT_INFO2("select error: %i", errno);
break;
case 0:
//break;
default:
PRINT_INFO("run");
//MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
MHD_run(daemon);
spdy_run_select(&rs, &ws, &es, connections, spdy_npollfds);
if(glob_opt.spdy_data_received)
{
PRINT_INFO("MHD run again");
//MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
MHD_run(daemon);
}
break;
}
}
while(run);
MHD_stop_daemon (daemon);
//TODO SSL_free brakes
spdy_free_connection(glob_opt.spdy_connection);
connection = glob_opt.spdy_connections_head;
while(NULL != connection)
{
connection_for_delete = connection;
connection = connection_for_delete->next;
glob_opt.streams_opened -= connection_for_delete->streams_opened;
DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection_for_delete);
spdy_free_connection(connection_for_delete);
}
free_uri(spdy2http_uri);
deinit_parse_uri(&glob_opt.uri_preg);
SSL_CTX_free(glob_opt.ssl_ctx);
ERR_free_strings();
EVP_cleanup();
PRINT_INFO2("spdy streams: %i; http requests: %i", glob_opt.streams_opened, glob_opt.responses_pending);
//PRINT_INFO2("memory allocated %zu bytes", glob_opt.global_memory);
print_stat();
return 0;
}
void
display_usage()
{
printf(
"Usage: mhd2spdy [-ovs] [-b <SPDY2HTTP-PROXY>] -p <PORT>\n\n"
"OPTIONS:\n"
" -p, --port Listening port.\n"
" -b, --backend-proxy If set, he proxy will send requests to\n"
" that SPDY server or proxy. Set the address\n"
" in the form 'http://host:port'. Use 'https'\n"
" for SPDY over TLS, or 'http' for plain SPDY\n"
" communication with the backend.\n"
" -o, --only-proxy If set, the proxy will always forward the\n"
" requests to the backend proxy. If not set,\n"
" the proxy will first try to establsh SPDY\n"
" connection to the requested server. If the\n"
" server does not support SPDY and TLS, the\n"
" backend proxy will be used for the request.\n"
" -v, --verbose Print debug information.\n"
" -s, --statistics Print simple statistics on exit.\n\n"
);
}
int
main (int argc,
char *const *argv)
{
int getopt_ret;
int option_index;
struct option long_options[] = {
{"port", required_argument, 0, 'p'},
{"backend-proxy", required_argument, 0, 'b'},
{"verbose", no_argument, 0, 'v'},
{"only-proxy", no_argument, 0, 'o'},
{"statistics", no_argument, 0, 's'},
{0, 0, 0, 0}
};
while (1)
{
getopt_ret = getopt_long( argc, argv, "p:b:vos", long_options, &option_index);
if (getopt_ret == -1)
break;
switch(getopt_ret)
{
case 'p':
glob_opt.listen_port = atoi(optarg);
break;
case 'b':
glob_opt.spdy2http_str = strdup(optarg);
if(NULL == glob_opt.spdy2http_str)
return 1;
break;
case 'v':
glob_opt.verbose = true;
break;
case 'o':
glob_opt.only_proxy = true;
break;
case 's':
glob_opt.statistics = true;
break;
case 0:
PRINT_INFO("0 from getopt");
break;
case '?':
display_usage();
return 1;
default:
DIE("default from getopt");
}
}
if(
0 == glob_opt.listen_port
|| (glob_opt.only_proxy && NULL == glob_opt.spdy2http_str)
)
{
display_usage();
return 1;
}
return run_everything();
}