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
36 static int run = 1;
37 //static int spdy_close = 0;
38
39
40 static void
catch_signal(int signal)41 catch_signal(int signal)
42 {
43 (void)signal;
44 //spdy_close = 1;
45 run = 0;
46 }
47
48
49 void
print_stat()50 print_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
65 int
run_everything()66 run_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
229 void
display_usage()230 display_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
254 int
main(int argc,char * const * argv)255 main (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