| /* |
| 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(); |
| } |