1 /*
2 This file is part of libmicrospdy
3 Copyright Copyright (C) 2013 Andrey Uzunov
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * @file proxy.c
21 * @brief Translates incoming SPDY requests to http server on localhost.
22 * Uses libcurl.
23 * No error handling for curl requests.
24 * TODO:
25 * - test all options!
26 * - don't abort on lack of memory
27 * - Correct recapitalizetion of header names before giving the headers
28 * to curl.
29 * - curl does not close sockets when connection is closed and no
30 * new sockets are opened (they stay in CLOSE_WAIT)
31 * - add '/' when a user requests http://example.com . Now this is a bad
32 * request
33 * - curl returns 0 or 1 ms for timeout even when nothing will be done;
34 * thus the loop uses CPU for nothing
35 * @author Andrey Uzunov
36 */
37
38 #include "platform.h"
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <stdint.h>
42 #include <stdbool.h>
43 #include <string.h>
44 #include <stdio.h>
45 #include <ctype.h>
46 #include <errno.h>
47 #include "microspdy.h"
48 #include <curl/curl.h>
49 #include <assert.h>
50 #include <getopt.h>
51 #include <regex.h>
52
53 #define ERROR_RESPONSE "502 Bad Gateway"
54
55
56 struct global_options
57 {
58 char *http_backend;
59 char *cert;
60 char *cert_key;
61 char *listen_host;
62 unsigned int timeout;
63 uint16_t listen_port;
64 bool verbose;
65 bool curl_verbose;
66 bool transparent;
67 bool http10;
68 bool notls;
69 bool nodelay;
70 bool ipv4;
71 bool ipv6;
72 } glob_opt;
73
74
75 struct URI
76 {
77 char * full_uri;
78 char * scheme;
79 char * host_and_port;
80 //char * host_and_port_for_connecting;
81 char * host;
82 char * path;
83 char * path_and_more;
84 char * query;
85 char * fragment;
86 uint16_t port;
87 };
88
89
90 #define PRINT_INFO(msg) do{\
91 fprintf(stdout, "%i:%s\n", __LINE__, msg);\
92 fflush(stdout);\
93 }\
94 while(0)
95
96
97 #define PRINT_INFO2(fmt, ...) do{\
98 fprintf(stdout, "%i\n", __LINE__);\
99 fprintf(stdout, fmt,##__VA_ARGS__);\
100 fprintf(stdout, "\n");\
101 fflush(stdout);\
102 }\
103 while(0)
104
105
106 #define PRINT_VERBOSE(msg) do{\
107 if(glob_opt.verbose){\
108 fprintf(stdout, "%i:%s\n", __LINE__, msg);\
109 fflush(stdout);\
110 }\
111 }\
112 while(0)
113
114
115 #define PRINT_VERBOSE2(fmt, ...) do{\
116 if(glob_opt.verbose){\
117 fprintf(stdout, "%i\n", __LINE__);\
118 fprintf(stdout, fmt,##__VA_ARGS__);\
119 fprintf(stdout, "\n");\
120 fflush(stdout);\
121 }\
122 }\
123 while(0)
124
125
126 #define CURL_SETOPT(handle, opt, val) do{\
127 int ret; \
128 if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \
129 { \
130 PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \
131 abort(); \
132 } \
133 }\
134 while(0)
135
136
137 #define DIE(msg) do{\
138 printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\
139 fflush(stdout);\
140 exit(EXIT_FAILURE);\
141 }\
142 while(0)
143
144
145 static int loop = 1;
146
147 static CURLM *multi_handle;
148
149 static int still_running = 0; /* keep number of running handles */
150
151 static regex_t uri_preg;
152
153 static bool call_spdy_run;
154 static bool call_curl_run;
155
156 int debug_num_curls;
157
158
159 struct Proxy
160 {
161 char *url;
162 struct SPDY_Request *request;
163 struct SPDY_Response *response;
164 CURL *curl_handle;
165 struct curl_slist *curl_headers;
166 struct SPDY_NameValue *headers;
167 char *version;
168 char *status_msg;
169 void *http_body;
170 void *received_body;
171 bool *session_alive;
172 size_t http_body_size;
173 size_t received_body_size;
174 //ssize_t length;
175 int status;
176 //bool done;
177 bool receiving_done;
178 bool is_curl_read_paused;
179 bool is_with_body_data;
180 //bool error;
181 bool curl_done;
182 bool curl_error;
183 bool spdy_done;
184 bool spdy_error;
185 };
186
187
188 static void
free_uri(struct URI * uri)189 free_uri(struct URI * uri)
190 {
191 if(NULL != uri)
192 {
193 free(uri->full_uri);
194 free(uri->scheme);
195 free(uri->host_and_port);
196 //free(uri->host_and_port_for_connecting);
197 free(uri->host);
198 free(uri->path);
199 free(uri->path_and_more);
200 free(uri->query);
201 free(uri->fragment);
202 uri->port = 0;
203 free(uri);
204 }
205 }
206
207
208 static int
init_parse_uri(regex_t * preg)209 init_parse_uri(regex_t * preg)
210 {
211 // RFC 2396
212 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
213 /*
214 scheme = $2
215 authority = $4
216 path = $5
217 query = $7
218 fragment = $9
219 */
220
221 return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED);
222 }
223
224
225 static void
deinit_parse_uri(regex_t * preg)226 deinit_parse_uri(regex_t * preg)
227 {
228 regfree(preg);
229 }
230
231
232 static int
parse_uri(regex_t * preg,const char * full_uri,struct URI ** uri)233 parse_uri(regex_t * preg, const char * full_uri, struct URI ** uri)
234 {
235 //TODO memeory checks
236 int ret;
237 char *colon;
238 long long port;
239 size_t nmatch = 10;
240 regmatch_t pmatch[10];
241
242 if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0)))
243 return ret;
244
245 *uri = malloc(sizeof(struct URI));
246 if(NULL == *uri)
247 return -200;
248
249 (*uri)->full_uri = strdup(full_uri);
250
251 asprintf(&((*uri)->scheme),
252 "%.*s",
253 (int) (pmatch[2].rm_eo - pmatch[2].rm_so),
254 &full_uri[pmatch[2].rm_so]);
255 asprintf(&((*uri)->host_and_port), "%.*s",
256 (int) (pmatch[4].rm_eo - pmatch[4].rm_so),
257 &full_uri[pmatch[4].rm_so]);
258 asprintf(&((*uri)->path),
259 "%.*s",
260 (int) (pmatch[5].rm_eo - pmatch[5].rm_so),
261 &full_uri[pmatch[5].rm_so]);
262 asprintf(&((*uri)->path_and_more),
263 "%.*s",
264 (int) (pmatch[9].rm_eo - pmatch[5].rm_so),
265 &full_uri[pmatch[5].rm_so]);
266 asprintf(&((*uri)->query),
267 "%.*s",
268 (int) (pmatch[7].rm_eo - pmatch[7].rm_so),
269 &full_uri[pmatch[7].rm_so]);
270 asprintf(&((*uri)->fragment),
271 "%.*s",
272 (int) (pmatch[9].rm_eo - pmatch[9].rm_so),
273 &full_uri[pmatch[9].rm_so]);
274
275 colon = strrchr((*uri)->host_and_port, ':');
276 if(NULL == colon)
277 {
278 (*uri)->host = strdup((*uri)->host_and_port);
279 /*if(0 == strcasecmp("http", uri->scheme))
280 {
281 uri->port = 80;
282 asprintf(&(uri->host_and_port_for_connecting), "%s:80", uri->host_and_port);
283 }
284 else if(0 == strcasecmp("https", uri->scheme))
285 {
286 uri->port = 443;
287 asprintf(&(uri->host_and_port_for_connecting), "%s:443", uri->host_and_port);
288 }
289 else
290 {
291 PRINT_INFO("no standard scheme!");
292 */(*uri)->port = 0;
293 /*uri->host_and_port_for_connecting = strdup(uri->host_and_port);
294 }*/
295 return 0;
296 }
297
298 port = atoi(colon + 1);
299 if(port<1 || port >= 256 * 256)
300 {
301 free_uri(*uri);
302 return -100;
303 }
304 (*uri)->port = port;
305 asprintf(&((*uri)->host), "%.*s", (int)(colon - (*uri)->host_and_port), (*uri)->host_and_port);
306
307 return 0;
308 }
309
310
311 static bool
store_in_buffer(const void * src,size_t src_size,void ** dst,size_t * dst_size)312 store_in_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size)
313 {
314 if(0 == src_size)
315 return true;
316
317 if(NULL == *dst)
318 *dst = malloc(src_size);
319 else
320 *dst = realloc(*dst, src_size + *dst_size);
321 if(NULL == *dst)
322 return false;
323
324 memcpy(*dst + *dst_size, src, src_size);
325 *dst_size += src_size;
326
327 return true;
328 }
329
330
331 static ssize_t
get_from_buffer(void ** src,size_t * src_size,void * dst,size_t max_size)332 get_from_buffer(void **src, size_t *src_size, void *dst, size_t max_size)
333 {
334 size_t ret;
335 void *newbody;
336
337 if(max_size >= *src_size)
338 {
339 ret = *src_size;
340 newbody = NULL;
341 }
342 else
343 {
344 ret = max_size;
345 if(NULL == (newbody = malloc(*src_size - max_size)))
346 return -1;
347 memcpy(newbody, *src + ret, *src_size - ret);
348 }
349 memcpy(dst, *src, ret);
350 free(*src);
351 *src = newbody;
352 *src_size -= ret;
353
354 return ret;
355 }
356
357
358 static void
catch_signal(int signal)359 catch_signal(int signal)
360 {
361 (void)signal;
362
363 loop = 0;
364 }
365
366 static void
new_session_cb(void * cls,struct SPDY_Session * session)367 new_session_cb (void * cls,
368 struct SPDY_Session * session)
369 {
370 (void)cls;
371
372 bool *session_alive;
373
374 PRINT_VERBOSE("new session");
375 //TODO clean this memory
376 if(NULL == (session_alive = malloc(sizeof(bool))))
377 {
378 DIE("no memory");
379 }
380 *session_alive = true;
381 SPDY_set_cls_to_session(session,
382 session_alive);
383 }
384
385 static void
session_closed_cb(void * cls,struct SPDY_Session * session,int by_client)386 session_closed_cb (void * cls,
387 struct SPDY_Session * session,
388 int by_client)
389 {
390 (void)cls;
391
392 bool *session_alive;
393
394 PRINT_VERBOSE2("session closed; by client: %i", by_client);
395
396 session_alive = SPDY_get_cls_from_session(session);
397 assert(NULL != session_alive);
398
399 *session_alive = false;
400 }
401
402
403 static int
spdy_post_data_cb(void * cls,struct SPDY_Request * request,const void * buf,size_t size,bool more)404 spdy_post_data_cb (void * cls,
405 struct SPDY_Request *request,
406 const void * buf,
407 size_t size,
408 bool more)
409 {
410 (void)cls;
411 int ret;
412 struct Proxy *proxy = (struct Proxy *)SPDY_get_cls_from_request(request);
413
414 if(!store_in_buffer(buf, size, &proxy->received_body, &proxy->received_body_size))
415 {
416 PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
417 return 0;
418 }
419
420 proxy->receiving_done = !more;
421
422 PRINT_VERBOSE2("POST bytes from SPDY: %zu", size);
423
424 call_curl_run = true;
425
426 if(proxy->is_curl_read_paused)
427 {
428 if(CURLE_OK != (ret = curl_easy_pause(proxy->curl_handle, CURLPAUSE_CONT)))
429 {
430 PRINT_INFO2("curl_easy_pause returned %i", ret);
431 abort();
432 }
433 PRINT_VERBOSE("curl_read_cb pause resumed");
434 }
435
436 return SPDY_YES;
437 }
438
439
440 ssize_t
response_callback(void * cls,void * buffer,size_t max,bool * more)441 response_callback (void *cls,
442 void *buffer,
443 size_t max,
444 bool *more)
445 {
446 ssize_t ret;
447 struct Proxy *proxy = (struct Proxy *)cls;
448
449 *more = true;
450
451 assert(!proxy->spdy_error);
452
453 if(proxy->curl_error)
454 {
455 PRINT_VERBOSE("tell spdy about the error");
456 return -1;
457 }
458
459 if(!proxy->http_body_size)//nothing to write now
460 {
461 PRINT_VERBOSE("nothing to write now");
462 if(proxy->curl_done || proxy->curl_error) *more = false;
463 return 0;
464 }
465
466 ret = get_from_buffer(&(proxy->http_body), &(proxy->http_body_size), buffer, max);
467 if(ret < 0)
468 {
469 PRINT_INFO("no memory");
470 //TODO error?
471 return -1;
472 }
473
474 if((proxy->curl_done || proxy->curl_error) && 0 == proxy->http_body_size) *more = false;
475
476 PRINT_VERBOSE2("given bytes to microspdy: %zd", ret);
477
478 return ret;
479 }
480
481
482 static void
cleanup(struct Proxy * proxy)483 cleanup(struct Proxy *proxy)
484 {
485 int ret;
486
487 //fprintf(stderr, "free proxy for %s\n", proxy->url);
488
489 if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle)))
490 {
491 PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret);
492 DIE("bug in cleanup");
493 }
494 debug_num_curls--;
495 //TODO bug on ku6.com or amazon.cn
496 // after curl_multi_remove_handle returned CURLM_BAD_EASY_HANDLE
497 curl_slist_free_all(proxy->curl_headers);
498 curl_easy_cleanup(proxy->curl_handle);
499
500 free(proxy->url);
501 free(proxy);
502 }
503
504
505 static void
response_done_callback(void * cls,struct SPDY_Response * response,struct SPDY_Request * request,enum SPDY_RESPONSE_RESULT status,bool streamopened)506 response_done_callback(void *cls,
507 struct SPDY_Response *response,
508 struct SPDY_Request *request,
509 enum SPDY_RESPONSE_RESULT status,
510 bool streamopened)
511 {
512 (void)streamopened;
513 struct Proxy *proxy = (struct Proxy *)cls;
514
515 if(SPDY_RESPONSE_RESULT_SUCCESS != status)
516 {
517 free(proxy->http_body);
518 proxy->http_body = NULL;
519 proxy->spdy_error = true;
520 }
521 cleanup(proxy);
522 SPDY_destroy_request(request);
523 SPDY_destroy_response(response);
524 }
525
526
527 static size_t
curl_header_cb(void * ptr,size_t size,size_t nmemb,void * userp)528 curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp)
529 {
530 size_t realsize = size * nmemb;
531 struct Proxy *proxy = (struct Proxy *)userp;
532 char *line = (char *)ptr;
533 char *name;
534 char *value;
535 char *status;
536 unsigned int i;
537 unsigned int pos;
538 int ret;
539 int num_values;
540 const char * const * values;
541 bool abort_it;
542
543 //printf("curl_header_cb %s\n", line);
544 if(!*(proxy->session_alive))
545 {
546 PRINT_VERBOSE("headers received, but session is dead");
547 proxy->spdy_error = true;
548 proxy->curl_error = true;
549 return 0;
550 }
551
552 //trailer
553 if(NULL != proxy->response) return 0;
554
555 if('\r' == line[0] || '\n' == line[0])
556 {
557 //all headers were already handled; prepare spdy frames
558 if(NULL == (proxy->response = SPDY_build_response_with_callback(proxy->status,
559 proxy->status_msg,
560 proxy->version,
561 proxy->headers,
562 &response_callback,
563 proxy,
564 0)))
565 //256)))
566 DIE("no response");
567
568 SPDY_name_value_destroy(proxy->headers);
569 proxy->headers = NULL;
570 free(proxy->status_msg);
571 proxy->status_msg = NULL;
572 free(proxy->version);
573 proxy->version = NULL;
574
575 if(SPDY_YES != SPDY_queue_response(proxy->request,
576 proxy->response,
577 true,
578 false,
579 &response_done_callback,
580 proxy))
581 {
582 //DIE("no queue");
583 //TODO right?
584 proxy->spdy_error = true;
585 proxy->curl_error = true;
586 PRINT_VERBOSE2("no queue in curl_header_cb for %s", proxy->url);
587 SPDY_destroy_response(proxy->response);
588 proxy->response = NULL;
589 return 0;
590 }
591
592 call_spdy_run = true;
593
594 return realsize;
595 }
596
597 pos = 0;
598 if(NULL == proxy->version)
599 {
600 //first line from headers
601 //version
602 for(i=pos; i<realsize && ' '!=line[i]; ++i);
603 if(i == realsize)
604 DIE("error on parsing headers");
605 if(NULL == (proxy->version = strndup(line, i - pos)))
606 DIE("No memory");
607 pos = i+1;
608
609 //status (number)
610 for(i=pos; i<realsize && ' '!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i);
611 if(NULL == (status = strndup(&(line[pos]), i - pos)))
612 DIE("No memory");
613 proxy->status = atoi(status);
614 free(status);
615 if(i<realsize && '\r'!=line[i] && '\n'!=line[i])
616 {
617 //status (message)
618 pos = i+1;
619 for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i);
620 if(NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos)))
621 DIE("No memory");
622 }
623 PRINT_VERBOSE2("Header line received '%s' '%i' '%s' ", proxy->version, proxy->status, proxy->status_msg);
624 return realsize;
625 }
626
627 //other lines
628 //header name
629 for(i=pos; i<realsize && ':'!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i)
630 line[i] = tolower(line[i]); //spdy requires lower case
631 if(NULL == (name = strndup(line, i - pos)))
632 DIE("No memory");
633 if(0 == strcmp(SPDY_HTTP_HEADER_CONNECTION, name)
634 || 0 == strcmp(SPDY_HTTP_HEADER_KEEP_ALIVE, name)
635 || 0 == strcmp(SPDY_HTTP_HEADER_TRANSFER_ENCODING, name)
636 )
637 {
638 //forbidden in spdy, ignore
639 free(name);
640 return realsize;
641 }
642 if(i == realsize || '\r'==line[i] || '\n'==line[i])
643 {
644 //no value. is it possible?
645 if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, ""))
646 DIE("SPDY_name_value_add failed");
647 return realsize;
648 }
649
650 //header value
651 pos = i+1;
652 while(pos<realsize && isspace(line[pos])) ++pos; //remove leading space
653 for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i);
654 if(NULL == (value = strndup(&(line[pos]), i - pos)))
655 DIE("No memory");
656 PRINT_VERBOSE2("Adding header: '%s': '%s'", name, value);
657 if(SPDY_YES != (ret = SPDY_name_value_add(proxy->headers, name, value)))
658 {
659 abort_it=true;
660 if(NULL != (values = SPDY_name_value_lookup(proxy->headers, name, &num_values)))
661 for(i=0; i<(unsigned int)num_values; ++i)
662 if(0 == strcasecmp(value, values[i]))
663 {
664 abort_it=false;
665 PRINT_VERBOSE2("header appears more than once with same value '%s: %s'", name, value);
666 break;
667 }
668
669 if(abort_it)
670 {
671 PRINT_INFO2("SPDY_name_value_add failed (%i) for '%s'", ret, name);
672 abort();
673 }
674 }
675 free(name);
676 free(value);
677
678 return realsize;
679 }
680
681
682 static size_t
curl_write_cb(void * contents,size_t size,size_t nmemb,void * userp)683 curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp)
684 {
685 size_t realsize = size * nmemb;
686 struct Proxy *proxy = (struct Proxy *)userp;
687
688 //printf("curl_write_cb %i\n", realsize);
689 if(!*(proxy->session_alive))
690 {
691 PRINT_VERBOSE("data received, but session is dead");
692 proxy->spdy_error = true;
693 proxy->curl_error = true;
694 return 0;
695 }
696
697 if(!store_in_buffer(contents, realsize, &proxy->http_body, &proxy->http_body_size))
698 {
699 PRINT_INFO("not enough memory (malloc/realloc returned NULL)");
700 proxy->curl_error = true;
701 return 0;
702 }
703 /*
704 if(NULL == proxy->http_body)
705 proxy->http_body = malloc(realsize);
706 else
707 proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + realsize);
708 if(NULL == proxy->http_body)
709 {
710 PRINT_INFO("not enough memory (realloc returned NULL)");
711 return 0;
712 }
713
714 memcpy(proxy->http_body + proxy->http_body_size, contents, realsize);
715 proxy->http_body_size += realsize;
716 */
717
718 PRINT_VERBOSE2("received bytes from curl: %zu", realsize);
719
720 call_spdy_run = true;
721
722 return realsize;
723 }
724
725
726 static size_t
curl_read_cb(void * ptr,size_t size,size_t nmemb,void * userp)727 curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userp)
728 {
729 ssize_t ret;
730 size_t max = size * nmemb;
731 struct Proxy *proxy = (struct Proxy *)userp;
732 //void *newbody;
733
734
735 if((proxy->receiving_done && !proxy->received_body_size) || !proxy->is_with_body_data || max < 1)
736 {
737 PRINT_VERBOSE("curl_read_cb last call");
738 return 0;
739 }
740
741 if(!*(proxy->session_alive))
742 {
743 PRINT_VERBOSE("POST is still being sent, but session is dead");
744 return CURL_READFUNC_ABORT;
745 }
746
747 if(!proxy->received_body_size)//nothing to write now
748 {
749 PRINT_VERBOSE("curl_read_cb called paused");
750 proxy->is_curl_read_paused = true;
751 return CURL_READFUNC_PAUSE;//TODO curl pause should be used
752 }
753
754 ret = get_from_buffer(&(proxy->received_body), &(proxy->received_body_size), ptr, max);
755 if(ret < 0)
756 {
757 PRINT_INFO("no memory");
758 return CURL_READFUNC_ABORT;
759 }
760
761 /*
762 if(max >= proxy->received_body_size)
763 {
764 ret = proxy->received_body_size;
765 newbody = NULL;
766 }
767 else
768 {
769 ret = max;
770 if(NULL == (newbody = malloc(proxy->received_body_size - max)))
771 {
772 PRINT_INFO("no memory");
773 return CURL_READFUNC_ABORT;
774 }
775 memcpy(newbody, proxy->received_body + max, proxy->received_body_size - max);
776 }
777 memcpy(ptr, proxy->received_body, ret);
778 free(proxy->received_body);
779 proxy->received_body = newbody;
780 proxy->received_body_size -= ret;
781 * */
782
783 PRINT_VERBOSE2("given POST bytes to curl: %zd", ret);
784
785 return ret;
786 }
787
788
789 static int
iterate_cb(void * cls,const char * name,const char * const * value,int num_values)790 iterate_cb (void *cls, const char *name, const char * const * value, int num_values)
791 {
792 struct Proxy *proxy = (struct Proxy *)cls;
793 struct curl_slist **curl_headers = (&(proxy->curl_headers));
794 char *line;
795 int line_len = strlen(name) + 3; //+ ": \0"
796 int i;
797
798 for(i=0; i<num_values; ++i)
799 {
800 if(i) line_len += 2; //", "
801 line_len += strlen(value[i]);
802 }
803
804 if(NULL == (line = malloc(line_len)))//no recovery
805 DIE("No memory");
806 line[0] = 0;
807
808 strcat(line, name);
809 strcat(line, ": ");
810 //all spdy header names are lower case;
811 //for simplicity here we just capitalize the first letter
812 line[0] = toupper(line[0]);
813
814 for(i=0; i<num_values; ++i)
815 {
816 if(i) strcat(line, ", ");
817 PRINT_VERBOSE2("Adding request header: '%s' len %ld", value[i], strlen(value[i]));
818 strcat(line, value[i]);
819 }
820 PRINT_VERBOSE2("Adding request header: '%s'", line);
821 if(NULL == (*curl_headers = curl_slist_append(*curl_headers, line)))
822 DIE("curl_slist_append failed");
823 free(line);
824
825 return SPDY_YES;
826 }
827
828
829 static void
standard_request_handler(void * cls,struct SPDY_Request * request,uint8_t priority,const char * method,const char * path,const char * version,const char * host,const char * scheme,struct SPDY_NameValue * headers,bool more)830 standard_request_handler(void *cls,
831 struct SPDY_Request * request,
832 uint8_t priority,
833 const char *method,
834 const char *path,
835 const char *version,
836 const char *host,
837 const char *scheme,
838 struct SPDY_NameValue * headers,
839 bool more)
840 {
841 (void)cls;
842 (void)priority;
843 (void)host;
844 (void)scheme;
845
846 struct Proxy *proxy;
847 int ret;
848 struct URI *uri;
849 struct SPDY_Session *session;
850
851 proxy = SPDY_get_cls_from_request(request);
852 if(NULL != proxy)
853 {
854 //ignore trailers or more headers
855 return;
856 }
857
858 PRINT_VERBOSE2("received request for '%s %s %s'\n", method, path, version);
859
860 //TODO not freed once in a while
861 if(NULL == (proxy = malloc(sizeof(struct Proxy))))
862 DIE("No memory");
863 memset(proxy, 0, sizeof(struct Proxy));
864
865 //fprintf(stderr, "new proxy for %s\n", path);
866
867 session = SPDY_get_session_for_request(request);
868 assert(NULL != session);
869 proxy->session_alive = SPDY_get_cls_from_session(session);
870 assert(NULL != proxy->session_alive);
871
872 SPDY_set_cls_to_request(request, proxy);
873
874 proxy->request = request;
875 proxy->is_with_body_data = more;
876 if(NULL == (proxy->headers = SPDY_name_value_create()))
877 DIE("No memory");
878
879 if(glob_opt.transparent)
880 {
881 if(NULL != glob_opt.http_backend) //use always same host
882 ret = asprintf(&(proxy->url),"%s://%s%s", scheme, glob_opt.http_backend, path);
883 else //use host header
884 ret = asprintf(&(proxy->url),"%s://%s%s", scheme, host, path);
885 if(-1 == ret)
886 DIE("No memory");
887
888 ret = parse_uri(&uri_preg, proxy->url, &uri);
889 if(ret != 0)
890 DIE("parsing built uri failed");
891 }
892 else
893 {
894 ret = parse_uri(&uri_preg, path, &uri);
895 PRINT_INFO2("path %s '%s' '%s'", path, uri->scheme, uri->host);
896 if(ret != 0 || !strlen(uri->scheme) || !strlen(uri->host))
897 DIE("parsing received uri failed");
898
899 if(NULL != glob_opt.http_backend) //use backend host
900 {
901 ret = asprintf(&(proxy->url),"%s://%s%s", uri->scheme, glob_opt.http_backend, uri->path_and_more);
902 if(-1 == ret)
903 DIE("No memory");
904 }
905 else //use request path
906 if(NULL == (proxy->url = strdup(path)))
907 DIE("No memory");
908 }
909
910 free_uri(uri);
911
912 PRINT_VERBOSE2("curl will request '%s'", proxy->url);
913
914 SPDY_name_value_iterate(headers, &iterate_cb, proxy);
915
916 if(NULL == (proxy->curl_handle = curl_easy_init()))
917 {
918 PRINT_INFO("curl_easy_init failed");
919 abort();
920 }
921
922 if(glob_opt.curl_verbose)
923 CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1);
924
925 if(0 == strcmp(SPDY_HTTP_METHOD_POST,method))
926 {
927 if(NULL == (proxy->curl_headers = curl_slist_append(proxy->curl_headers, "Expect:")))
928 DIE("curl_slist_append failed");
929 CURL_SETOPT(proxy->curl_handle, CURLOPT_POST, 1);
930 CURL_SETOPT(proxy->curl_handle, CURLOPT_READFUNCTION, curl_read_cb);
931 CURL_SETOPT(proxy->curl_handle, CURLOPT_READDATA, proxy);
932 }
933
934 if(glob_opt.timeout)
935 CURL_SETOPT(proxy->curl_handle, CURLOPT_TIMEOUT, glob_opt.timeout);
936 CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, proxy->url);
937 if(glob_opt.http10)
938 CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
939 CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
940 CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy);
941 CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
942 CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy);
943 CURL_SETOPT(proxy->curl_handle, CURLOPT_PRIVATE, proxy);
944 CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers);
945 CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);//TODO
946 CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
947 if(glob_opt.ipv4 && !glob_opt.ipv6)
948 CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
949 else if(glob_opt.ipv6 && !glob_opt.ipv4)
950 CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
951
952 if(CURLM_OK != (ret = curl_multi_add_handle(multi_handle, proxy->curl_handle)))
953 {
954 PRINT_INFO2("curl_multi_add_handle failed (%i)", ret);
955 abort();
956 }
957 debug_num_curls++;
958
959 //~5ms additional latency for calling this
960 if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running))
961 && CURLM_CALL_MULTI_PERFORM != ret)
962 {
963 PRINT_INFO2("curl_multi_perform failed (%i)", ret);
964 abort();
965 }
966
967 call_curl_run = true;
968 }
969
970
971 static int
run()972 run ()
973 {
974 unsigned long long timeoutlong = 0;
975 unsigned long long timeout_spdy = 0;
976 long timeout_curl = -1;
977 struct timeval timeout;
978 int ret;
979 int ret_curl;
980 int ret_spdy;
981 fd_set rs;
982 fd_set ws;
983 fd_set es;
984 int maxfd = -1;
985 int maxfd_curl = -1;
986 struct SPDY_Daemon *daemon;
987 CURLMsg *msg;
988 int msgs_left;
989 struct Proxy *proxy;
990 struct sockaddr_in *addr;
991 struct addrinfo hints;
992 char service[NI_MAXSERV];
993 struct addrinfo *gai;
994 enum SPDY_IO_SUBSYSTEM io = glob_opt.notls ? SPDY_IO_SUBSYSTEM_RAW : SPDY_IO_SUBSYSTEM_OPENSSL;
995 enum SPDY_DAEMON_FLAG flags = SPDY_DAEMON_FLAG_NO;
996 //struct SPDY_Response *error_response;
997 char *curl_private;
998
999 signal(SIGPIPE, SIG_IGN);
1000
1001 if (signal(SIGINT, catch_signal) == SIG_ERR)
1002 PRINT_VERBOSE("signal failed");
1003
1004 srand(time(NULL));
1005 if(init_parse_uri(&uri_preg))
1006 DIE("Regexp compilation failed");
1007
1008 SPDY_init();
1009
1010 if(glob_opt.nodelay)
1011 flags |= SPDY_DAEMON_FLAG_NO_DELAY;
1012
1013 if(NULL == glob_opt.listen_host)
1014 {
1015 daemon = SPDY_start_daemon(glob_opt.listen_port,
1016 glob_opt.cert,
1017 glob_opt.cert_key,
1018 &new_session_cb,
1019 &session_closed_cb,
1020 &standard_request_handler,
1021 &spdy_post_data_cb,
1022 NULL,
1023 SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
1024 1800,
1025 SPDY_DAEMON_OPTION_IO_SUBSYSTEM,
1026 io,
1027 SPDY_DAEMON_OPTION_FLAGS,
1028 flags,
1029 SPDY_DAEMON_OPTION_END);
1030 }
1031 else
1032 {
1033 snprintf (service, sizeof(service), "%u", glob_opt.listen_port);
1034 memset (&hints, 0, sizeof(struct addrinfo));
1035 hints.ai_family = AF_INET;
1036 hints.ai_socktype = SOCK_STREAM;
1037
1038 ret = getaddrinfo(glob_opt.listen_host, service, &hints, &gai);
1039 if(ret != 0)
1040 DIE("problem with specified host");
1041
1042 addr = (struct sockaddr_in *) gai->ai_addr;
1043
1044 daemon = SPDY_start_daemon(0,
1045 glob_opt.cert,
1046 glob_opt.cert_key,
1047 &new_session_cb,
1048 &session_closed_cb,
1049 &standard_request_handler,
1050 &spdy_post_data_cb,
1051 NULL,
1052 SPDY_DAEMON_OPTION_SESSION_TIMEOUT,
1053 1800,
1054 SPDY_DAEMON_OPTION_IO_SUBSYSTEM,
1055 io,
1056 SPDY_DAEMON_OPTION_FLAGS,
1057 flags,
1058 SPDY_DAEMON_OPTION_SOCK_ADDR,
1059 addr,
1060 //SPDY_DAEMON_OPTION_MAX_NUM_FRAMES,
1061 //1,
1062 SPDY_DAEMON_OPTION_END);
1063 }
1064
1065 if(NULL==daemon){
1066 printf("no daemon\n");
1067 return 1;
1068 }
1069
1070 multi_handle = curl_multi_init();
1071 if(NULL==multi_handle)
1072 DIE("no multi_handle");
1073
1074 timeout.tv_usec = 0;
1075
1076 do
1077 {
1078 FD_ZERO(&rs);
1079 FD_ZERO(&ws);
1080 FD_ZERO(&es);
1081
1082 PRINT_VERBOSE2("num curls %i", debug_num_curls);
1083
1084 ret_spdy = SPDY_get_timeout(daemon, &timeout_spdy);
1085 if(SPDY_NO == ret_spdy || timeout_spdy > 5000)
1086 timeoutlong = 5000;
1087 else
1088 timeoutlong = timeout_spdy;
1089 PRINT_VERBOSE2("SPDY timeout %lld; %i", timeout_spdy, ret_spdy);
1090
1091 if(CURLM_OK != (ret_curl = curl_multi_timeout(multi_handle, &timeout_curl)))
1092 {
1093 PRINT_VERBOSE2("curl_multi_timeout failed (%i)", ret_curl);
1094 //curl_timeo = timeoutlong;
1095 }
1096 else if(timeout_curl >= 0 && timeoutlong > (unsigned long)timeout_curl)
1097 timeoutlong = (unsigned long)timeout_curl;
1098
1099 PRINT_VERBOSE2("curl timeout %ld", timeout_curl);
1100
1101 timeout.tv_sec = timeoutlong / 1000;
1102 timeout.tv_usec = (timeoutlong % 1000) * 1000;
1103
1104 maxfd = SPDY_get_fdset (daemon,
1105 &rs,
1106 &ws,
1107 &es);
1108 assert(-1 != maxfd);
1109
1110 if(CURLM_OK != (ret = curl_multi_fdset(multi_handle, &rs,
1111 &ws,
1112 &es, &maxfd_curl)))
1113 {
1114 PRINT_INFO2("curl_multi_fdset failed (%i)", ret);
1115 abort();
1116 }
1117
1118 if(maxfd_curl > maxfd)
1119 maxfd = maxfd_curl;
1120
1121 PRINT_VERBOSE2("timeout before %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec);
1122 ret = select(maxfd+1, &rs, &ws, &es, &timeout);
1123 PRINT_VERBOSE2("timeout after %lld %lld; ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret);
1124
1125 /*switch(ret) {
1126 case -1:
1127 PRINT_INFO2("select error: %i", errno);
1128 break;
1129 case 0:
1130 break;
1131 default:*/
1132
1133 //the second part should not happen with current implementation
1134 if(ret > 0 || (SPDY_YES == ret_spdy && 0 == timeout_spdy))
1135 {
1136 PRINT_VERBOSE("run spdy");
1137 SPDY_run(daemon);
1138 call_spdy_run = false;
1139 }
1140
1141 //if(ret > 0 || (CURLM_OK == ret_curl && 0 == timeout_curl) || call_curl_run)
1142 {
1143 PRINT_VERBOSE("run curl");
1144 if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running))
1145 && CURLM_CALL_MULTI_PERFORM != ret)
1146 {
1147 PRINT_INFO2("curl_multi_perform failed (%i)", ret);
1148 abort();
1149 }
1150 call_curl_run = false;
1151 }
1152 /*break;
1153 }*/
1154
1155 while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
1156 if (msg->msg == CURLMSG_DONE) {
1157 PRINT_VERBOSE("A curl handler is done");
1158 if(CURLE_OK != (ret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &curl_private)))
1159 {
1160 PRINT_INFO2("err %i",ret);
1161 abort();
1162 }
1163 assert(NULL != curl_private);
1164 proxy = (struct Proxy *)curl_private;
1165 if(CURLE_OK == msg->data.result)
1166 {
1167 proxy->curl_done = true;
1168 call_spdy_run = true;
1169 //TODO what happens with proxy when the client resets a stream
1170 //and response_done is not yet set for the last frame? is it
1171 //possible?
1172 }
1173 else
1174 {
1175 PRINT_VERBOSE2("bad curl result (%i) for '%s'", msg->data.result, proxy->url);
1176 if(proxy->spdy_done || proxy->spdy_error || (NULL == proxy->response && !*(proxy->session_alive)))
1177 {
1178 PRINT_VERBOSE("cleaning");
1179 SPDY_name_value_destroy(proxy->headers);
1180 SPDY_destroy_request(proxy->request);
1181 SPDY_destroy_response(proxy->response);
1182 cleanup(proxy);
1183 }
1184 else if(NULL == proxy->response && *(proxy->session_alive))
1185 {
1186 //generate error for the client
1187 PRINT_VERBOSE("will send Bad Gateway");
1188 SPDY_name_value_destroy(proxy->headers);
1189 proxy->headers = NULL;
1190 if(NULL == (proxy->response = SPDY_build_response(SPDY_HTTP_BAD_GATEWAY,
1191 NULL,
1192 SPDY_HTTP_VERSION_1_1,
1193 NULL,
1194 ERROR_RESPONSE,
1195 strlen(ERROR_RESPONSE))))
1196 DIE("no response");
1197 if(SPDY_YES != SPDY_queue_response(proxy->request,
1198 proxy->response,
1199 true,
1200 false,
1201 &response_done_callback,
1202 proxy))
1203 {
1204 //clean and forget
1205 PRINT_VERBOSE("cleaning");
1206 SPDY_destroy_request(proxy->request);
1207 SPDY_destroy_response(proxy->response);
1208 cleanup(proxy);
1209 }
1210 }
1211 else
1212 {
1213 proxy->curl_error = true;
1214 }
1215 call_spdy_run = true;
1216 //TODO spdy should be notified to send RST_STREAM
1217 }
1218 }
1219 else PRINT_INFO("shouldn't happen");
1220 }
1221
1222 if(call_spdy_run)
1223 {
1224 PRINT_VERBOSE("second call to SPDY_run");
1225 SPDY_run(daemon);
1226 call_spdy_run = false;
1227 }
1228
1229 if(glob_opt.verbose)
1230 {
1231
1232 #ifdef HAVE_CLOCK_GETTIME
1233 #ifdef CLOCK_MONOTONIC
1234 struct timespec ts;
1235
1236 if (0 == clock_gettime(CLOCK_MONOTONIC, &ts))
1237 PRINT_VERBOSE2 ("time now %lld %lld",
1238 (unsigned long long) ts.tv_sec,
1239 (unsigned long long) ts.tv_nsec);
1240 #endif
1241 #endif
1242 }
1243
1244 }
1245 while(loop);
1246
1247 SPDY_stop_daemon(daemon);
1248
1249 curl_multi_cleanup(multi_handle);
1250
1251 SPDY_deinit();
1252
1253 deinit_parse_uri(&uri_preg);
1254
1255 return 0;
1256 }
1257
1258
1259 static void
display_usage()1260 display_usage()
1261 {
1262 printf(
1263 "Usage: microspdy2http -p <PORT> [-c <CERTIFICATE>] [-k <CERT-KEY>]\n"
1264 " [-rvh0DtT] [-b <HTTP-SERVER>] [-l <HOST>]\n\n"
1265 "OPTIONS:\n"
1266 " -p, --port Listening port.\n"
1267 " -l, --host Listening host. If not set, will listen on [::]\n"
1268 " -c, --certificate Path to a certificate file. Requiered if\n"
1269 " --no-tls is not set.\n"
1270 " -k, --certificate-key Path to a key file for the certificate.\n"
1271 " Requiered if --no-tls is not set.\n"
1272 " -b, --backend-server If set, the proxy will connect always to it.\n"
1273 " Otherwise the proxy will connect to the URL\n"
1274 " which is specified in the path or 'Host:'.\n"
1275 " -v, --verbose Print debug information.\n"
1276 " -r, --no-tls Do not use TLS. Client must use SPDY/3.\n"
1277 " -h, --curl-verbose Print debug information for curl.\n"
1278 " -0, --http10 Prefer HTTP/1.0 connections to the next hop.\n"
1279 " -D, --no-delay This makes sense only if --no-tls is used.\n"
1280 " TCP_NODELAY will be used for all sessions' sockets.\n"
1281 " -4, --curl-ipv4 Curl may use IPv4 to connect to the final destination.\n"
1282 " -6, --curl-ipv6 Curl may use IPv6 to connect to the final destination.\n"
1283 " If neither --curl-ipv4 nor --curl-ipv6 is set,\n"
1284 " both will be used by default.\n"
1285 " -T, --timeout Maximum time in seconds for each HTTP transfer.\n"
1286 " Use 0 for no timeout; this is the default value.\n"
1287 " -t, --transparent If set, the proxy will fetch an URL which\n"
1288 " is based on 'Host:' header and requested path.\n"
1289 " Otherwise, full URL in the requested path is required.\n\n"
1290
1291 );
1292 }
1293
1294
1295 int
main(int argc,char * const * argv)1296 main (int argc, char *const *argv)
1297 {
1298
1299 int getopt_ret;
1300 int option_index;
1301 struct option long_options[] = {
1302 {"port", required_argument, 0, 'p'},
1303 {"certificate", required_argument, 0, 'c'},
1304 {"certificate-key", required_argument, 0, 'k'},
1305 {"backend-server", required_argument, 0, 'b'},
1306 {"no-tls", no_argument, 0, 'r'},
1307 {"verbose", no_argument, 0, 'v'},
1308 {"curl-verbose", no_argument, 0, 'h'},
1309 {"http10", no_argument, 0, '0'},
1310 {"no-delay", no_argument, 0, 'D'},
1311 {"transparent", no_argument, 0, 't'},
1312 {"curl-ipv4", no_argument, 0, '4'},
1313 {"curl-ipv6", no_argument, 0, '6'},
1314 {"timeout", required_argument, 0, 'T'},
1315 {0, 0, 0, 0}
1316 };
1317
1318 while (1)
1319 {
1320 getopt_ret = getopt_long( argc, argv, "p:l:c:k:b:rv0Dth46T:", long_options, &option_index);
1321 if (getopt_ret == -1)
1322 break;
1323
1324 switch(getopt_ret)
1325 {
1326 case 'p':
1327 glob_opt.listen_port = atoi(optarg);
1328 break;
1329
1330 case 'l':
1331 glob_opt.listen_host= strdup(optarg);
1332 if(NULL == glob_opt.listen_host)
1333 return 1;
1334 break;
1335
1336 case 'c':
1337 glob_opt.cert = strdup(optarg);
1338 break;
1339
1340 case 'k':
1341 glob_opt.cert_key = strdup(optarg);
1342 break;
1343
1344 case 'b':
1345 glob_opt.http_backend = strdup(optarg);
1346 if(NULL == glob_opt.http_backend)
1347 return 1;
1348 break;
1349
1350 case 'r':
1351 glob_opt.notls = true;
1352 break;
1353
1354 case 'v':
1355 glob_opt.verbose = true;
1356 break;
1357
1358 case 'h':
1359 glob_opt.curl_verbose = true;
1360 break;
1361
1362 case '0':
1363 glob_opt.http10 = true;
1364 break;
1365
1366 case 'D':
1367 glob_opt.nodelay = true;
1368 break;
1369
1370 case 't':
1371 glob_opt.transparent = true;
1372 break;
1373
1374 case '4':
1375 glob_opt.ipv4 = true;
1376 break;
1377
1378 case '6':
1379 glob_opt.ipv6 = true;
1380 break;
1381
1382 case 'T':
1383 glob_opt.timeout = atoi(optarg);
1384 break;
1385
1386 case 0:
1387 PRINT_INFO("0 from getopt");
1388 break;
1389
1390 case '?':
1391 display_usage();
1392 return 1;
1393
1394 default:
1395 DIE("default from getopt");
1396 }
1397 }
1398
1399 if(
1400 0 == glob_opt.listen_port
1401 || (!glob_opt.notls && (NULL == glob_opt.cert || NULL == glob_opt.cert_key))
1402 //|| !glob_opt.transparent && NULL != glob_opt.http_backend
1403 )
1404 {
1405 display_usage();
1406 return 1;
1407 }
1408
1409 return run();
1410 }
1411
1412