/* 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 . */ /** * @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 ] -p \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(); }