/* This file is part of libmicrospdy 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 proxy.c * @brief Translates incoming SPDY requests to http server on localhost. * Uses libcurl. * No error handling for curl requests. * TODO: * - test all options! * - don't abort on lack of memory * - Correct recapitalizetion of header names before giving the headers * to curl. * - curl does not close sockets when connection is closed and no * new sockets are opened (they stay in CLOSE_WAIT) * - add '/' when a user requests http://example.com . Now this is a bad * request * - curl returns 0 or 1 ms for timeout even when nothing will be done; * thus the loop uses CPU for nothing * @author Andrey Uzunov */ #include "platform.h" #include <unistd.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <stdio.h> #include <ctype.h> #include <errno.h> #include "microspdy.h" #include <curl/curl.h> #include <assert.h> #include <getopt.h> #include <regex.h> #define ERROR_RESPONSE "502 Bad Gateway" struct global_options { char *http_backend; char *cert; char *cert_key; char *listen_host; unsigned int timeout; uint16_t listen_port; bool verbose; bool curl_verbose; bool transparent; bool http10; bool notls; bool nodelay; bool ipv4; bool ipv6; } glob_opt; struct URI { char * full_uri; char * scheme; char * host_and_port; //char * host_and_port_for_connecting; char * host; char * path; char * path_and_more; char * query; char * fragment; uint16_t port; }; #define PRINT_INFO(msg) do{\ fprintf(stdout, "%i:%s\n", __LINE__, msg);\ fflush(stdout);\ }\ while(0) #define PRINT_INFO2(fmt, ...) do{\ fprintf(stdout, "%i\n", __LINE__);\ fprintf(stdout, fmt,##__VA_ARGS__);\ fprintf(stdout, "\n");\ fflush(stdout);\ }\ while(0) #define PRINT_VERBOSE(msg) do{\ if(glob_opt.verbose){\ fprintf(stdout, "%i:%s\n", __LINE__, msg);\ fflush(stdout);\ }\ }\ while(0) #define PRINT_VERBOSE2(fmt, ...) do{\ if(glob_opt.verbose){\ fprintf(stdout, "%i\n", __LINE__);\ fprintf(stdout, fmt,##__VA_ARGS__);\ fprintf(stdout, "\n");\ fflush(stdout);\ }\ }\ while(0) #define CURL_SETOPT(handle, opt, val) do{\ int ret; \ if(CURLE_OK != (ret = curl_easy_setopt(handle, opt, val))) \ { \ PRINT_INFO2("curl_easy_setopt failed (%i = %i)", opt, ret); \ abort(); \ } \ }\ while(0) #define DIE(msg) do{\ printf("FATAL ERROR (line %i): %s\n", __LINE__, msg);\ fflush(stdout);\ exit(EXIT_FAILURE);\ }\ while(0) static int loop = 1; static CURLM *multi_handle; static int still_running = 0; /* keep number of running handles */ static regex_t uri_preg; static bool call_spdy_run; static bool call_curl_run; int debug_num_curls; struct Proxy { char *url; struct SPDY_Request *request; struct SPDY_Response *response; CURL *curl_handle; struct curl_slist *curl_headers; struct SPDY_NameValue *headers; char *version; char *status_msg; void *http_body; void *received_body; bool *session_alive; size_t http_body_size; size_t received_body_size; //ssize_t length; int status; //bool done; bool receiving_done; bool is_curl_read_paused; bool is_with_body_data; //bool error; bool curl_done; bool curl_error; bool spdy_done; bool spdy_error; }; static void free_uri(struct URI * uri) { if(NULL != uri) { free(uri->full_uri); free(uri->scheme); free(uri->host_and_port); //free(uri->host_and_port_for_connecting); free(uri->host); free(uri->path); free(uri->path_and_more); free(uri->query); free(uri->fragment); uri->port = 0; free(uri); } } static int init_parse_uri(regex_t * preg) { // RFC 2396 // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? /* scheme = $2 authority = $4 path = $5 query = $7 fragment = $9 */ return regcomp(preg, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", REG_EXTENDED); } static void deinit_parse_uri(regex_t * preg) { regfree(preg); } static int parse_uri(regex_t * preg, const char * full_uri, struct URI ** uri) { //TODO memeory checks int ret; char *colon; long long port; size_t nmatch = 10; regmatch_t pmatch[10]; if (0 != (ret = regexec(preg, full_uri, nmatch, pmatch, 0))) return ret; *uri = malloc(sizeof(struct URI)); if(NULL == *uri) return -200; (*uri)->full_uri = strdup(full_uri); asprintf(&((*uri)->scheme), "%.*s", (int) (pmatch[2].rm_eo - pmatch[2].rm_so), &full_uri[pmatch[2].rm_so]); asprintf(&((*uri)->host_and_port), "%.*s", (int) (pmatch[4].rm_eo - pmatch[4].rm_so), &full_uri[pmatch[4].rm_so]); asprintf(&((*uri)->path), "%.*s", (int) (pmatch[5].rm_eo - pmatch[5].rm_so), &full_uri[pmatch[5].rm_so]); asprintf(&((*uri)->path_and_more), "%.*s", (int) (pmatch[9].rm_eo - pmatch[5].rm_so), &full_uri[pmatch[5].rm_so]); asprintf(&((*uri)->query), "%.*s", (int) (pmatch[7].rm_eo - pmatch[7].rm_so), &full_uri[pmatch[7].rm_so]); asprintf(&((*uri)->fragment), "%.*s", (int) (pmatch[9].rm_eo - pmatch[9].rm_so), &full_uri[pmatch[9].rm_so]); colon = strrchr((*uri)->host_and_port, ':'); if(NULL == colon) { (*uri)->host = strdup((*uri)->host_and_port); /*if(0 == strcasecmp("http", uri->scheme)) { uri->port = 80; asprintf(&(uri->host_and_port_for_connecting), "%s:80", uri->host_and_port); } else if(0 == strcasecmp("https", uri->scheme)) { uri->port = 443; asprintf(&(uri->host_and_port_for_connecting), "%s:443", uri->host_and_port); } else { PRINT_INFO("no standard scheme!"); */(*uri)->port = 0; /*uri->host_and_port_for_connecting = strdup(uri->host_and_port); }*/ return 0; } port = atoi(colon + 1); if(port<1 || port >= 256 * 256) { free_uri(*uri); return -100; } (*uri)->port = port; asprintf(&((*uri)->host), "%.*s", (int)(colon - (*uri)->host_and_port), (*uri)->host_and_port); return 0; } static bool store_in_buffer(const void *src, size_t src_size, void **dst, size_t *dst_size) { if(0 == src_size) return true; if(NULL == *dst) *dst = malloc(src_size); else *dst = realloc(*dst, src_size + *dst_size); if(NULL == *dst) return false; memcpy(*dst + *dst_size, src, src_size); *dst_size += src_size; return true; } static ssize_t get_from_buffer(void **src, size_t *src_size, void *dst, size_t max_size) { size_t ret; void *newbody; if(max_size >= *src_size) { ret = *src_size; newbody = NULL; } else { ret = max_size; if(NULL == (newbody = malloc(*src_size - max_size))) return -1; memcpy(newbody, *src + ret, *src_size - ret); } memcpy(dst, *src, ret); free(*src); *src = newbody; *src_size -= ret; return ret; } static void catch_signal(int signal) { (void)signal; loop = 0; } static void new_session_cb (void * cls, struct SPDY_Session * session) { (void)cls; bool *session_alive; PRINT_VERBOSE("new session"); //TODO clean this memory if(NULL == (session_alive = malloc(sizeof(bool)))) { DIE("no memory"); } *session_alive = true; SPDY_set_cls_to_session(session, session_alive); } static void session_closed_cb (void * cls, struct SPDY_Session * session, int by_client) { (void)cls; bool *session_alive; PRINT_VERBOSE2("session closed; by client: %i", by_client); session_alive = SPDY_get_cls_from_session(session); assert(NULL != session_alive); *session_alive = false; } static int spdy_post_data_cb (void * cls, struct SPDY_Request *request, const void * buf, size_t size, bool more) { (void)cls; int ret; struct Proxy *proxy = (struct Proxy *)SPDY_get_cls_from_request(request); if(!store_in_buffer(buf, size, &proxy->received_body, &proxy->received_body_size)) { PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); return 0; } proxy->receiving_done = !more; PRINT_VERBOSE2("POST bytes from SPDY: %zu", size); call_curl_run = true; if(proxy->is_curl_read_paused) { if(CURLE_OK != (ret = curl_easy_pause(proxy->curl_handle, CURLPAUSE_CONT))) { PRINT_INFO2("curl_easy_pause returned %i", ret); abort(); } PRINT_VERBOSE("curl_read_cb pause resumed"); } return SPDY_YES; } ssize_t response_callback (void *cls, void *buffer, size_t max, bool *more) { ssize_t ret; struct Proxy *proxy = (struct Proxy *)cls; *more = true; assert(!proxy->spdy_error); if(proxy->curl_error) { PRINT_VERBOSE("tell spdy about the error"); return -1; } if(!proxy->http_body_size)//nothing to write now { PRINT_VERBOSE("nothing to write now"); if(proxy->curl_done || proxy->curl_error) *more = false; return 0; } ret = get_from_buffer(&(proxy->http_body), &(proxy->http_body_size), buffer, max); if(ret < 0) { PRINT_INFO("no memory"); //TODO error? return -1; } if((proxy->curl_done || proxy->curl_error) && 0 == proxy->http_body_size) *more = false; PRINT_VERBOSE2("given bytes to microspdy: %zd", ret); return ret; } static void cleanup(struct Proxy *proxy) { int ret; //fprintf(stderr, "free proxy for %s\n", proxy->url); if(CURLM_OK != (ret = curl_multi_remove_handle(multi_handle, proxy->curl_handle))) { PRINT_INFO2("curl_multi_remove_handle failed (%i)", ret); DIE("bug in cleanup"); } debug_num_curls--; //TODO bug on ku6.com or amazon.cn // after curl_multi_remove_handle returned CURLM_BAD_EASY_HANDLE curl_slist_free_all(proxy->curl_headers); curl_easy_cleanup(proxy->curl_handle); free(proxy->url); free(proxy); } static void response_done_callback(void *cls, struct SPDY_Response *response, struct SPDY_Request *request, enum SPDY_RESPONSE_RESULT status, bool streamopened) { (void)streamopened; struct Proxy *proxy = (struct Proxy *)cls; if(SPDY_RESPONSE_RESULT_SUCCESS != status) { free(proxy->http_body); proxy->http_body = NULL; proxy->spdy_error = true; } cleanup(proxy); SPDY_destroy_request(request); SPDY_destroy_response(response); } static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct Proxy *proxy = (struct Proxy *)userp; char *line = (char *)ptr; char *name; char *value; char *status; unsigned int i; unsigned int pos; int ret; int num_values; const char * const * values; bool abort_it; //printf("curl_header_cb %s\n", line); if(!*(proxy->session_alive)) { PRINT_VERBOSE("headers received, but session is dead"); proxy->spdy_error = true; proxy->curl_error = true; return 0; } //trailer if(NULL != proxy->response) return 0; if('\r' == line[0] || '\n' == line[0]) { //all headers were already handled; prepare spdy frames if(NULL == (proxy->response = SPDY_build_response_with_callback(proxy->status, proxy->status_msg, proxy->version, proxy->headers, &response_callback, proxy, 0))) //256))) DIE("no response"); SPDY_name_value_destroy(proxy->headers); proxy->headers = NULL; free(proxy->status_msg); proxy->status_msg = NULL; free(proxy->version); proxy->version = NULL; if(SPDY_YES != SPDY_queue_response(proxy->request, proxy->response, true, false, &response_done_callback, proxy)) { //DIE("no queue"); //TODO right? proxy->spdy_error = true; proxy->curl_error = true; PRINT_VERBOSE2("no queue in curl_header_cb for %s", proxy->url); SPDY_destroy_response(proxy->response); proxy->response = NULL; return 0; } call_spdy_run = true; return realsize; } pos = 0; if(NULL == proxy->version) { //first line from headers //version for(i=pos; i<realsize && ' '!=line[i]; ++i); if(i == realsize) DIE("error on parsing headers"); if(NULL == (proxy->version = strndup(line, i - pos))) DIE("No memory"); pos = i+1; //status (number) for(i=pos; i<realsize && ' '!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i); if(NULL == (status = strndup(&(line[pos]), i - pos))) DIE("No memory"); proxy->status = atoi(status); free(status); if(i<realsize && '\r'!=line[i] && '\n'!=line[i]) { //status (message) pos = i+1; for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i); if(NULL == (proxy->status_msg = strndup(&(line[pos]), i - pos))) DIE("No memory"); } PRINT_VERBOSE2("Header line received '%s' '%i' '%s' ", proxy->version, proxy->status, proxy->status_msg); return realsize; } //other lines //header name for(i=pos; i<realsize && ':'!=line[i] && '\r'!=line[i] && '\n'!=line[i]; ++i) line[i] = tolower(line[i]); //spdy requires lower case if(NULL == (name = strndup(line, i - pos))) DIE("No memory"); if(0 == strcmp(SPDY_HTTP_HEADER_CONNECTION, name) || 0 == strcmp(SPDY_HTTP_HEADER_KEEP_ALIVE, name) || 0 == strcmp(SPDY_HTTP_HEADER_TRANSFER_ENCODING, name) ) { //forbidden in spdy, ignore free(name); return realsize; } if(i == realsize || '\r'==line[i] || '\n'==line[i]) { //no value. is it possible? if(SPDY_YES != SPDY_name_value_add(proxy->headers, name, "")) DIE("SPDY_name_value_add failed"); return realsize; } //header value pos = i+1; while(pos<realsize && isspace(line[pos])) ++pos; //remove leading space for(i=pos; i<realsize && '\r'!=line[i] && '\n'!=line[i]; ++i); if(NULL == (value = strndup(&(line[pos]), i - pos))) DIE("No memory"); PRINT_VERBOSE2("Adding header: '%s': '%s'", name, value); if(SPDY_YES != (ret = SPDY_name_value_add(proxy->headers, name, value))) { abort_it=true; if(NULL != (values = SPDY_name_value_lookup(proxy->headers, name, &num_values))) for(i=0; i<(unsigned int)num_values; ++i) if(0 == strcasecmp(value, values[i])) { abort_it=false; PRINT_VERBOSE2("header appears more than once with same value '%s: %s'", name, value); break; } if(abort_it) { PRINT_INFO2("SPDY_name_value_add failed (%i) for '%s'", ret, name); abort(); } } free(name); free(value); return realsize; } static size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct Proxy *proxy = (struct Proxy *)userp; //printf("curl_write_cb %i\n", realsize); if(!*(proxy->session_alive)) { PRINT_VERBOSE("data received, but session is dead"); proxy->spdy_error = true; proxy->curl_error = true; return 0; } if(!store_in_buffer(contents, realsize, &proxy->http_body, &proxy->http_body_size)) { PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); proxy->curl_error = true; return 0; } /* if(NULL == proxy->http_body) proxy->http_body = malloc(realsize); else proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + realsize); if(NULL == proxy->http_body) { PRINT_INFO("not enough memory (realloc returned NULL)"); return 0; } memcpy(proxy->http_body + proxy->http_body_size, contents, realsize); proxy->http_body_size += realsize; */ PRINT_VERBOSE2("received bytes from curl: %zu", realsize); call_spdy_run = true; return realsize; } static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userp) { ssize_t ret; size_t max = size * nmemb; struct Proxy *proxy = (struct Proxy *)userp; //void *newbody; if((proxy->receiving_done && !proxy->received_body_size) || !proxy->is_with_body_data || max < 1) { PRINT_VERBOSE("curl_read_cb last call"); return 0; } if(!*(proxy->session_alive)) { PRINT_VERBOSE("POST is still being sent, but session is dead"); return CURL_READFUNC_ABORT; } if(!proxy->received_body_size)//nothing to write now { PRINT_VERBOSE("curl_read_cb called paused"); proxy->is_curl_read_paused = true; return CURL_READFUNC_PAUSE;//TODO curl pause should be used } ret = get_from_buffer(&(proxy->received_body), &(proxy->received_body_size), ptr, max); if(ret < 0) { PRINT_INFO("no memory"); return CURL_READFUNC_ABORT; } /* if(max >= proxy->received_body_size) { ret = proxy->received_body_size; newbody = NULL; } else { ret = max; if(NULL == (newbody = malloc(proxy->received_body_size - max))) { PRINT_INFO("no memory"); return CURL_READFUNC_ABORT; } memcpy(newbody, proxy->received_body + max, proxy->received_body_size - max); } memcpy(ptr, proxy->received_body, ret); free(proxy->received_body); proxy->received_body = newbody; proxy->received_body_size -= ret; * */ PRINT_VERBOSE2("given POST bytes to curl: %zd", ret); return ret; } static int iterate_cb (void *cls, const char *name, const char * const * value, int num_values) { struct Proxy *proxy = (struct Proxy *)cls; struct curl_slist **curl_headers = (&(proxy->curl_headers)); char *line; int line_len = strlen(name) + 3; //+ ": \0" int i; for(i=0; i<num_values; ++i) { if(i) line_len += 2; //", " line_len += strlen(value[i]); } if(NULL == (line = malloc(line_len)))//no recovery DIE("No memory"); line[0] = 0; strcat(line, name); strcat(line, ": "); //all spdy header names are lower case; //for simplicity here we just capitalize the first letter line[0] = toupper(line[0]); for(i=0; i<num_values; ++i) { if(i) strcat(line, ", "); PRINT_VERBOSE2("Adding request header: '%s' len %ld", value[i], strlen(value[i])); strcat(line, value[i]); } PRINT_VERBOSE2("Adding request header: '%s'", line); if(NULL == (*curl_headers = curl_slist_append(*curl_headers, line))) DIE("curl_slist_append failed"); free(line); return SPDY_YES; } 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) { (void)cls; (void)priority; (void)host; (void)scheme; struct Proxy *proxy; int ret; struct URI *uri; struct SPDY_Session *session; proxy = SPDY_get_cls_from_request(request); if(NULL != proxy) { //ignore trailers or more headers return; } PRINT_VERBOSE2("received request for '%s %s %s'\n", method, path, version); //TODO not freed once in a while if(NULL == (proxy = malloc(sizeof(struct Proxy)))) DIE("No memory"); memset(proxy, 0, sizeof(struct Proxy)); //fprintf(stderr, "new proxy for %s\n", path); session = SPDY_get_session_for_request(request); assert(NULL != session); proxy->session_alive = SPDY_get_cls_from_session(session); assert(NULL != proxy->session_alive); SPDY_set_cls_to_request(request, proxy); proxy->request = request; proxy->is_with_body_data = more; if(NULL == (proxy->headers = SPDY_name_value_create())) DIE("No memory"); if(glob_opt.transparent) { if(NULL != glob_opt.http_backend) //use always same host ret = asprintf(&(proxy->url),"%s://%s%s", scheme, glob_opt.http_backend, path); else //use host header ret = asprintf(&(proxy->url),"%s://%s%s", scheme, host, path); if(-1 == ret) DIE("No memory"); ret = parse_uri(&uri_preg, proxy->url, &uri); if(ret != 0) DIE("parsing built uri failed"); } else { ret = parse_uri(&uri_preg, path, &uri); PRINT_INFO2("path %s '%s' '%s'", path, uri->scheme, uri->host); if(ret != 0 || !strlen(uri->scheme) || !strlen(uri->host)) DIE("parsing received uri failed"); if(NULL != glob_opt.http_backend) //use backend host { ret = asprintf(&(proxy->url),"%s://%s%s", uri->scheme, glob_opt.http_backend, uri->path_and_more); if(-1 == ret) DIE("No memory"); } else //use request path if(NULL == (proxy->url = strdup(path))) DIE("No memory"); } free_uri(uri); PRINT_VERBOSE2("curl will request '%s'", proxy->url); SPDY_name_value_iterate(headers, &iterate_cb, proxy); if(NULL == (proxy->curl_handle = curl_easy_init())) { PRINT_INFO("curl_easy_init failed"); abort(); } if(glob_opt.curl_verbose) CURL_SETOPT(proxy->curl_handle, CURLOPT_VERBOSE, 1); if(0 == strcmp(SPDY_HTTP_METHOD_POST,method)) { if(NULL == (proxy->curl_headers = curl_slist_append(proxy->curl_headers, "Expect:"))) DIE("curl_slist_append failed"); CURL_SETOPT(proxy->curl_handle, CURLOPT_POST, 1); CURL_SETOPT(proxy->curl_handle, CURLOPT_READFUNCTION, curl_read_cb); CURL_SETOPT(proxy->curl_handle, CURLOPT_READDATA, proxy); } if(glob_opt.timeout) CURL_SETOPT(proxy->curl_handle, CURLOPT_TIMEOUT, glob_opt.timeout); CURL_SETOPT(proxy->curl_handle, CURLOPT_URL, proxy->url); if(glob_opt.http10) CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEFUNCTION, curl_write_cb); CURL_SETOPT(proxy->curl_handle, CURLOPT_WRITEDATA, proxy); CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERFUNCTION, curl_header_cb); CURL_SETOPT(proxy->curl_handle, CURLOPT_HEADERDATA, proxy); CURL_SETOPT(proxy->curl_handle, CURLOPT_PRIVATE, proxy); CURL_SETOPT(proxy->curl_handle, CURLOPT_HTTPHEADER, proxy->curl_headers); CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);//TODO CURL_SETOPT(proxy->curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); if(glob_opt.ipv4 && !glob_opt.ipv6) CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); else if(glob_opt.ipv6 && !glob_opt.ipv4) CURL_SETOPT(proxy->curl_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); if(CURLM_OK != (ret = curl_multi_add_handle(multi_handle, proxy->curl_handle))) { PRINT_INFO2("curl_multi_add_handle failed (%i)", ret); abort(); } debug_num_curls++; //~5ms additional latency for calling this if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) && CURLM_CALL_MULTI_PERFORM != ret) { PRINT_INFO2("curl_multi_perform failed (%i)", ret); abort(); } call_curl_run = true; } static int run () { unsigned long long timeoutlong = 0; unsigned long long timeout_spdy = 0; long timeout_curl = -1; struct timeval timeout; int ret; int ret_curl; int ret_spdy; fd_set rs; fd_set ws; fd_set es; int maxfd = -1; int maxfd_curl = -1; struct SPDY_Daemon *daemon; CURLMsg *msg; int msgs_left; struct Proxy *proxy; struct sockaddr_in *addr; struct addrinfo hints; char service[NI_MAXSERV]; struct addrinfo *gai; enum SPDY_IO_SUBSYSTEM io = glob_opt.notls ? SPDY_IO_SUBSYSTEM_RAW : SPDY_IO_SUBSYSTEM_OPENSSL; enum SPDY_DAEMON_FLAG flags = SPDY_DAEMON_FLAG_NO; //struct SPDY_Response *error_response; char *curl_private; signal(SIGPIPE, SIG_IGN); if (signal(SIGINT, catch_signal) == SIG_ERR) PRINT_VERBOSE("signal failed"); srand(time(NULL)); if(init_parse_uri(&uri_preg)) DIE("Regexp compilation failed"); SPDY_init(); if(glob_opt.nodelay) flags |= SPDY_DAEMON_FLAG_NO_DELAY; if(NULL == glob_opt.listen_host) { daemon = SPDY_start_daemon(glob_opt.listen_port, glob_opt.cert, glob_opt.cert_key, &new_session_cb, &session_closed_cb, &standard_request_handler, &spdy_post_data_cb, NULL, SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 1800, SPDY_DAEMON_OPTION_IO_SUBSYSTEM, io, SPDY_DAEMON_OPTION_FLAGS, flags, SPDY_DAEMON_OPTION_END); } else { snprintf (service, sizeof(service), "%u", glob_opt.listen_port); memset (&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(glob_opt.listen_host, service, &hints, &gai); if(ret != 0) DIE("problem with specified host"); addr = (struct sockaddr_in *) gai->ai_addr; daemon = SPDY_start_daemon(0, glob_opt.cert, glob_opt.cert_key, &new_session_cb, &session_closed_cb, &standard_request_handler, &spdy_post_data_cb, NULL, SPDY_DAEMON_OPTION_SESSION_TIMEOUT, 1800, SPDY_DAEMON_OPTION_IO_SUBSYSTEM, io, SPDY_DAEMON_OPTION_FLAGS, flags, SPDY_DAEMON_OPTION_SOCK_ADDR, addr, //SPDY_DAEMON_OPTION_MAX_NUM_FRAMES, //1, SPDY_DAEMON_OPTION_END); } if(NULL==daemon){ printf("no daemon\n"); return 1; } multi_handle = curl_multi_init(); if(NULL==multi_handle) DIE("no multi_handle"); timeout.tv_usec = 0; do { FD_ZERO(&rs); FD_ZERO(&ws); FD_ZERO(&es); PRINT_VERBOSE2("num curls %i", debug_num_curls); ret_spdy = SPDY_get_timeout(daemon, &timeout_spdy); if(SPDY_NO == ret_spdy || timeout_spdy > 5000) timeoutlong = 5000; else timeoutlong = timeout_spdy; PRINT_VERBOSE2("SPDY timeout %lld; %i", timeout_spdy, ret_spdy); if(CURLM_OK != (ret_curl = curl_multi_timeout(multi_handle, &timeout_curl))) { PRINT_VERBOSE2("curl_multi_timeout failed (%i)", ret_curl); //curl_timeo = timeoutlong; } else if(timeout_curl >= 0 && timeoutlong > (unsigned long)timeout_curl) timeoutlong = (unsigned long)timeout_curl; PRINT_VERBOSE2("curl timeout %ld", timeout_curl); timeout.tv_sec = timeoutlong / 1000; timeout.tv_usec = (timeoutlong % 1000) * 1000; maxfd = SPDY_get_fdset (daemon, &rs, &ws, &es); assert(-1 != maxfd); if(CURLM_OK != (ret = curl_multi_fdset(multi_handle, &rs, &ws, &es, &maxfd_curl))) { PRINT_INFO2("curl_multi_fdset failed (%i)", ret); abort(); } if(maxfd_curl > maxfd) maxfd = maxfd_curl; PRINT_VERBOSE2("timeout before %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec); ret = select(maxfd+1, &rs, &ws, &es, &timeout); PRINT_VERBOSE2("timeout after %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:*/ //the second part should not happen with current implementation if(ret > 0 || (SPDY_YES == ret_spdy && 0 == timeout_spdy)) { PRINT_VERBOSE("run spdy"); SPDY_run(daemon); call_spdy_run = false; } //if(ret > 0 || (CURLM_OK == ret_curl && 0 == timeout_curl) || call_curl_run) { PRINT_VERBOSE("run curl"); if(CURLM_OK != (ret = curl_multi_perform(multi_handle, &still_running)) && CURLM_CALL_MULTI_PERFORM != ret) { PRINT_INFO2("curl_multi_perform failed (%i)", ret); abort(); } call_curl_run = false; } /*break; }*/ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { if (msg->msg == CURLMSG_DONE) { PRINT_VERBOSE("A curl handler is done"); if(CURLE_OK != (ret = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &curl_private))) { PRINT_INFO2("err %i",ret); abort(); } assert(NULL != curl_private); proxy = (struct Proxy *)curl_private; if(CURLE_OK == msg->data.result) { proxy->curl_done = true; call_spdy_run = true; //TODO what happens with proxy when the client resets a stream //and response_done is not yet set for the last frame? is it //possible? } else { PRINT_VERBOSE2("bad curl result (%i) for '%s'", msg->data.result, proxy->url); if(proxy->spdy_done || proxy->spdy_error || (NULL == proxy->response && !*(proxy->session_alive))) { PRINT_VERBOSE("cleaning"); SPDY_name_value_destroy(proxy->headers); SPDY_destroy_request(proxy->request); SPDY_destroy_response(proxy->response); cleanup(proxy); } else if(NULL == proxy->response && *(proxy->session_alive)) { //generate error for the client PRINT_VERBOSE("will send Bad Gateway"); SPDY_name_value_destroy(proxy->headers); proxy->headers = NULL; if(NULL == (proxy->response = SPDY_build_response(SPDY_HTTP_BAD_GATEWAY, NULL, SPDY_HTTP_VERSION_1_1, NULL, ERROR_RESPONSE, strlen(ERROR_RESPONSE)))) DIE("no response"); if(SPDY_YES != SPDY_queue_response(proxy->request, proxy->response, true, false, &response_done_callback, proxy)) { //clean and forget PRINT_VERBOSE("cleaning"); SPDY_destroy_request(proxy->request); SPDY_destroy_response(proxy->response); cleanup(proxy); } } else { proxy->curl_error = true; } call_spdy_run = true; //TODO spdy should be notified to send RST_STREAM } } else PRINT_INFO("shouldn't happen"); } if(call_spdy_run) { PRINT_VERBOSE("second call to SPDY_run"); SPDY_run(daemon); call_spdy_run = false; } if(glob_opt.verbose) { #ifdef HAVE_CLOCK_GETTIME #ifdef CLOCK_MONOTONIC struct timespec ts; if (0 == clock_gettime(CLOCK_MONOTONIC, &ts)) PRINT_VERBOSE2 ("time now %lld %lld", (unsigned long long) ts.tv_sec, (unsigned long long) ts.tv_nsec); #endif #endif } } while(loop); SPDY_stop_daemon(daemon); curl_multi_cleanup(multi_handle); SPDY_deinit(); deinit_parse_uri(&uri_preg); return 0; } static void display_usage() { printf( "Usage: microspdy2http -p <PORT> [-c <CERTIFICATE>] [-k <CERT-KEY>]\n" " [-rvh0DtT] [-b <HTTP-SERVER>] [-l <HOST>]\n\n" "OPTIONS:\n" " -p, --port Listening port.\n" " -l, --host Listening host. If not set, will listen on [::]\n" " -c, --certificate Path to a certificate file. Requiered if\n" " --no-tls is not set.\n" " -k, --certificate-key Path to a key file for the certificate.\n" " Requiered if --no-tls is not set.\n" " -b, --backend-server If set, the proxy will connect always to it.\n" " Otherwise the proxy will connect to the URL\n" " which is specified in the path or 'Host:'.\n" " -v, --verbose Print debug information.\n" " -r, --no-tls Do not use TLS. Client must use SPDY/3.\n" " -h, --curl-verbose Print debug information for curl.\n" " -0, --http10 Prefer HTTP/1.0 connections to the next hop.\n" " -D, --no-delay This makes sense only if --no-tls is used.\n" " TCP_NODELAY will be used for all sessions' sockets.\n" " -4, --curl-ipv4 Curl may use IPv4 to connect to the final destination.\n" " -6, --curl-ipv6 Curl may use IPv6 to connect to the final destination.\n" " If neither --curl-ipv4 nor --curl-ipv6 is set,\n" " both will be used by default.\n" " -T, --timeout Maximum time in seconds for each HTTP transfer.\n" " Use 0 for no timeout; this is the default value.\n" " -t, --transparent If set, the proxy will fetch an URL which\n" " is based on 'Host:' header and requested path.\n" " Otherwise, full URL in the requested path is required.\n\n" ); } int main (int argc, char *const *argv) { int getopt_ret; int option_index; struct option long_options[] = { {"port", required_argument, 0, 'p'}, {"certificate", required_argument, 0, 'c'}, {"certificate-key", required_argument, 0, 'k'}, {"backend-server", required_argument, 0, 'b'}, {"no-tls", no_argument, 0, 'r'}, {"verbose", no_argument, 0, 'v'}, {"curl-verbose", no_argument, 0, 'h'}, {"http10", no_argument, 0, '0'}, {"no-delay", no_argument, 0, 'D'}, {"transparent", no_argument, 0, 't'}, {"curl-ipv4", no_argument, 0, '4'}, {"curl-ipv6", no_argument, 0, '6'}, {"timeout", required_argument, 0, 'T'}, {0, 0, 0, 0} }; while (1) { getopt_ret = getopt_long( argc, argv, "p:l:c:k:b:rv0Dth46T:", long_options, &option_index); if (getopt_ret == -1) break; switch(getopt_ret) { case 'p': glob_opt.listen_port = atoi(optarg); break; case 'l': glob_opt.listen_host= strdup(optarg); if(NULL == glob_opt.listen_host) return 1; break; case 'c': glob_opt.cert = strdup(optarg); break; case 'k': glob_opt.cert_key = strdup(optarg); break; case 'b': glob_opt.http_backend = strdup(optarg); if(NULL == glob_opt.http_backend) return 1; break; case 'r': glob_opt.notls = true; break; case 'v': glob_opt.verbose = true; break; case 'h': glob_opt.curl_verbose = true; break; case '0': glob_opt.http10 = true; break; case 'D': glob_opt.nodelay = true; break; case 't': glob_opt.transparent = true; break; case '4': glob_opt.ipv4 = true; break; case '6': glob_opt.ipv6 = true; break; case 'T': glob_opt.timeout = atoi(optarg); 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.notls && (NULL == glob_opt.cert || NULL == glob_opt.cert_key)) //|| !glob_opt.transparent && NULL != glob_opt.http_backend ) { display_usage(); return 1; } return run(); }