1/* 2 Copyright Copyright (C) 2013 Andrey Uzunov 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16*/ 17 18/** 19 * @file mhd2spdy.c 20 * @brief The main file of the HTTP-to-SPDY proxy with the 'main' function 21 * and event loop. No threads are used. 22 * Currently only GET is supported. 23 * TODOs: 24 * - non blocking SSL connect 25 * - check certificate 26 * - on closing spdy session, close sockets for all requests 27 * @author Andrey Uzunov 28 */ 29 30 31#include "mhd2spdy_structures.h" 32#include "mhd2spdy_spdy.h" 33#include "mhd2spdy_http.h" 34 35 36static int run = 1; 37//static int spdy_close = 0; 38 39 40static void 41catch_signal(int signal) 42{ 43 (void)signal; 44 //spdy_close = 1; 45 run = 0; 46} 47 48 49void 50print_stat() 51{ 52 if(!glob_opt.statistics) 53 return; 54 55 printf("--------------------------\n"); 56 printf("Statistics (TLS overhead is ignored when used):\n"); 57 //printf("HTTP bytes received: %lld\n", glob_stat.http_bytes_received); 58 //printf("HTTP bytes sent: %lld\n", glob_stat.http_bytes_sent); 59 printf("SPDY bytes sent: %lld\n", glob_stat.spdy_bytes_sent); 60 printf("SPDY bytes received: %lld\n", glob_stat.spdy_bytes_received); 61 printf("SPDY bytes received and dropped: %lld\n", glob_stat.spdy_bytes_received_and_dropped); 62} 63 64 65int 66run_everything () 67{ 68 unsigned long long timeoutlong=0; 69 struct timeval timeout; 70 int ret; 71 fd_set rs; 72 fd_set ws; 73 fd_set es; 74 int maxfd = -1; 75 int maxfd_s = -1; 76 struct MHD_Daemon *daemon; 77 nfds_t spdy_npollfds = 1; 78 struct URI * spdy2http_uri = NULL; 79 struct SPDY_Connection *connection; 80 struct SPDY_Connection *connections[MAX_SPDY_CONNECTIONS]; 81 struct SPDY_Connection *connection_for_delete; 82 83 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) 84 PRINT_INFO("signal failed"); 85 86 if (signal(SIGINT, catch_signal) == SIG_ERR) 87 PRINT_INFO("signal failed"); 88 89 glob_opt.streams_opened = 0; 90 glob_opt.responses_pending = 0; 91 //glob_opt.global_memory = 0; 92 93 srand(time(NULL)); 94 95 if(init_parse_uri(&glob_opt.uri_preg)) 96 DIE("Regexp compilation failed"); 97 98 if(NULL != glob_opt.spdy2http_str) 99 { 100 ret = parse_uri(&glob_opt.uri_preg, glob_opt.spdy2http_str, &spdy2http_uri); 101 if(ret != 0) 102 DIE("spdy_parse_uri failed"); 103 } 104 105 SSL_load_error_strings(); 106 SSL_library_init(); 107 glob_opt.ssl_ctx = SSL_CTX_new(SSLv23_client_method()); 108 if(glob_opt.ssl_ctx == NULL) { 109 PRINT_INFO2("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), NULL)); 110 abort(); 111 } 112 spdy_ssl_init_ssl_ctx(glob_opt.ssl_ctx, &glob_opt.spdy_proto_version); 113 114 daemon = MHD_start_daemon ( 115 MHD_SUPPRESS_DATE_NO_CLOCK, 116 glob_opt.listen_port, 117 NULL, NULL, &http_cb_request, NULL, 118 MHD_OPTION_URI_LOG_CALLBACK, &http_cb_log, NULL, 119 MHD_OPTION_NOTIFY_COMPLETED, &http_cb_request_completed, NULL, 120 MHD_OPTION_END); 121 if(NULL==daemon) 122 DIE("MHD_start_daemon failed"); 123 124 do 125 { 126 timeout.tv_sec = 0; 127 timeout.tv_usec = 0; 128 129 if(NULL == glob_opt.spdy_connection && NULL != glob_opt.spdy2http_str) 130 { 131 glob_opt.spdy_connection = spdy_connect(spdy2http_uri, spdy2http_uri->port, strcmp("https", spdy2http_uri->scheme)==0); 132 if(NULL == glob_opt.spdy_connection && glob_opt.only_proxy) 133 PRINT_INFO("cannot connect to the proxy"); 134 } 135 136 FD_ZERO(&rs); 137 FD_ZERO(&ws); 138 FD_ZERO(&es); 139 140 ret = MHD_get_timeout(daemon, &timeoutlong); 141 if(MHD_NO == ret || timeoutlong > 5000) 142 timeout.tv_sec = 5; 143 else 144 { 145 timeout.tv_sec = timeoutlong / 1000; 146 timeout.tv_usec = (timeoutlong % 1000) * 1000; 147 } 148 149 if(MHD_NO == MHD_get_fdset (daemon, 150 &rs, 151 &ws, 152 &es, 153 &maxfd)) 154 { 155 PRINT_INFO("MHD_get_fdset error"); 156 } 157 assert(-1 != maxfd); 158 159 maxfd_s = spdy_get_selectfdset( 160 &rs, 161 &ws, 162 &es, 163 connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds); 164 if(maxfd_s > maxfd) 165 maxfd = maxfd_s; 166 167 PRINT_INFO2("MHD timeout %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec); 168 169 glob_opt.spdy_data_received = false; 170 171 ret = select(maxfd+1, &rs, &ws, &es, &timeout); 172 PRINT_INFO2("timeout now %lld %lld ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret); 173 174 switch(ret) 175 { 176 case -1: 177 PRINT_INFO2("select error: %i", errno); 178 break; 179 case 0: 180 //break; 181 default: 182 PRINT_INFO("run"); 183 //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past 184 MHD_run(daemon); 185 spdy_run_select(&rs, &ws, &es, connections, spdy_npollfds); 186 if(glob_opt.spdy_data_received) 187 { 188 PRINT_INFO("MHD run again"); 189 //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past 190 MHD_run(daemon); 191 } 192 break; 193 } 194 } 195 while(run); 196 197 MHD_stop_daemon (daemon); 198 199 //TODO SSL_free brakes 200 spdy_free_connection(glob_opt.spdy_connection); 201 202 connection = glob_opt.spdy_connections_head; 203 while(NULL != connection) 204 { 205 connection_for_delete = connection; 206 connection = connection_for_delete->next; 207 glob_opt.streams_opened -= connection_for_delete->streams_opened; 208 DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection_for_delete); 209 spdy_free_connection(connection_for_delete); 210 } 211 212 free_uri(spdy2http_uri); 213 214 deinit_parse_uri(&glob_opt.uri_preg); 215 216 SSL_CTX_free(glob_opt.ssl_ctx); 217 ERR_free_strings(); 218 EVP_cleanup(); 219 220 PRINT_INFO2("spdy streams: %i; http requests: %i", glob_opt.streams_opened, glob_opt.responses_pending); 221 //PRINT_INFO2("memory allocated %zu bytes", glob_opt.global_memory); 222 223 print_stat(); 224 225 return 0; 226} 227 228 229void 230display_usage() 231{ 232 printf( 233 "Usage: mhd2spdy [-ovs] [-b <SPDY2HTTP-PROXY>] -p <PORT>\n\n" 234 "OPTIONS:\n" 235 " -p, --port Listening port.\n" 236 " -b, --backend-proxy If set, he proxy will send requests to\n" 237 " that SPDY server or proxy. Set the address\n" 238 " in the form 'http://host:port'. Use 'https'\n" 239 " for SPDY over TLS, or 'http' for plain SPDY\n" 240 " communication with the backend.\n" 241 " -o, --only-proxy If set, the proxy will always forward the\n" 242 " requests to the backend proxy. If not set,\n" 243 " the proxy will first try to establsh SPDY\n" 244 " connection to the requested server. If the\n" 245 " server does not support SPDY and TLS, the\n" 246 " backend proxy will be used for the request.\n" 247 " -v, --verbose Print debug information.\n" 248 " -s, --statistics Print simple statistics on exit.\n\n" 249 250 ); 251} 252 253 254int 255main (int argc, 256 char *const *argv) 257{ 258 int getopt_ret; 259 int option_index; 260 struct option long_options[] = { 261 {"port", required_argument, 0, 'p'}, 262 {"backend-proxy", required_argument, 0, 'b'}, 263 {"verbose", no_argument, 0, 'v'}, 264 {"only-proxy", no_argument, 0, 'o'}, 265 {"statistics", no_argument, 0, 's'}, 266 {0, 0, 0, 0} 267 }; 268 269 while (1) 270 { 271 getopt_ret = getopt_long( argc, argv, "p:b:vos", long_options, &option_index); 272 if (getopt_ret == -1) 273 break; 274 275 switch(getopt_ret) 276 { 277 case 'p': 278 glob_opt.listen_port = atoi(optarg); 279 break; 280 281 case 'b': 282 glob_opt.spdy2http_str = strdup(optarg); 283 if(NULL == glob_opt.spdy2http_str) 284 return 1; 285 break; 286 287 case 'v': 288 glob_opt.verbose = true; 289 break; 290 291 case 'o': 292 glob_opt.only_proxy = true; 293 break; 294 295 case 's': 296 glob_opt.statistics = true; 297 break; 298 299 case 0: 300 PRINT_INFO("0 from getopt"); 301 break; 302 303 case '?': 304 display_usage(); 305 return 1; 306 307 default: 308 DIE("default from getopt"); 309 } 310 } 311 312 if( 313 0 == glob_opt.listen_port 314 || (glob_opt.only_proxy && NULL == glob_opt.spdy2http_str) 315 ) 316 { 317 display_usage(); 318 return 1; 319 } 320 321 return run_everything(); 322} 323