| /* |
| * |
| * Copyright (c) 2012 Tatsuhiro Tsujikawa |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| /** |
| * @file mhd2spdy_spdy.c |
| * @brief SPDY part of the proxy. libspdylay is used for the client side. |
| * The example spdycli.c from spdylay was used as basis; |
| * however, multiple changes were made. |
| * @author Tatsuhiro Tsujikawa |
| * @author Andrey Uzunov |
| */ |
| |
| #include "mhd2spdy_structures.h" |
| #include "mhd2spdy_spdy.h" |
| #include "mhd2spdy_http.h" |
| |
| |
| /* |
| * Prints error containing the function name |func| and message |msg| |
| * and exit. |
| */ |
| static void |
| spdy_dief(const char *func, |
| const char *msg) |
| { |
| fprintf(stderr, "FATAL: %s: %s\n", func, msg); |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| /* |
| * Prints error containing the function name |func| and error code |
| * |error_code| and exit. |
| */ |
| void |
| spdy_diec(const char *func, |
| int error_code) |
| { |
| fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code, |
| spdylay_strerror(error_code)); |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| static ssize_t |
| spdy_cb_data_source_read(spdylay_session *session, int32_t stream_id, uint8_t *buf, size_t length, int *eof, spdylay_data_source *source, void *user_data) |
| { |
| (void)session; |
| (void)stream_id; |
| (void)user_data; |
| |
| ssize_t ret; |
| assert(NULL != source); |
| assert(NULL != source->ptr); |
| struct Proxy *proxy = (struct Proxy *)(source->ptr); |
| void *newbody; |
| |
| |
| if(length < 1) |
| { |
| PRINT_INFO("spdy_cb_data_source_read: length is 0"); |
| return 0; |
| } |
| |
| if(!proxy->received_body_size)//nothing to write now |
| { |
| if(proxy->receiving_done) |
| { |
| PRINT_INFO("POST spdy EOF"); |
| *eof = 1; |
| } |
| PRINT_INFO("POST SPDYLAY_ERR_DEFERRED"); |
| return SPDYLAY_ERR_DEFERRED;//TODO SPDYLAY_ERR_DEFERRED should be used |
| } |
| |
| if(length >= proxy->received_body_size) |
| { |
| ret = proxy->received_body_size; |
| newbody = NULL; |
| } |
| else |
| { |
| ret = length; |
| if(NULL == (newbody = malloc(proxy->received_body_size - length))) |
| { |
| PRINT_INFO("no memory"); |
| return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE; |
| } |
| memcpy(newbody, proxy->received_body + length, proxy->received_body_size - length); |
| } |
| memcpy(buf, proxy->received_body, ret); |
| free(proxy->received_body); |
| proxy->received_body = newbody; |
| proxy->received_body_size -= ret; |
| |
| if(0 == proxy->received_body_size && proxy->receiving_done) |
| { |
| PRINT_INFO("POST spdy EOF"); |
| *eof = 1; |
| } |
| |
| PRINT_INFO2("given POST bytes to spdylay: %zd", ret); |
| |
| return ret; |
| } |
| |
| |
| /* |
| * The implementation of spdylay_send_callback type. Here we write |
| * |data| with size |length| to the network and return the number of |
| * bytes actually written. See the documentation of |
| * spdylay_send_callback for the details. |
| */ |
| static ssize_t |
| spdy_cb_send(spdylay_session *session, |
| const uint8_t *data, |
| size_t length, |
| int flags, |
| void *user_data) |
| { |
| (void)session; |
| (void)flags; |
| |
| //PRINT_INFO("spdy_cb_send called"); |
| struct SPDY_Connection *connection; |
| ssize_t rv; |
| connection = (struct SPDY_Connection*)user_data; |
| connection->want_io = IO_NONE; |
| |
| if(glob_opt.ignore_rst_stream |
| && 16 == length |
| && 0x80 == data[0] |
| && 0x00 == data[2] |
| && 0x03 == data[3] |
| ) |
| { |
| PRINT_INFO2("ignoring RST_STREAM for stream_id %i %i %i %i", data[8], data[9], data[10], data[11]); |
| glob_opt.ignore_rst_stream = false; |
| return 16; |
| } |
| glob_opt.ignore_rst_stream = false; |
| |
| if(connection->is_tls) |
| { |
| ERR_clear_error(); |
| rv = SSL_write(connection->ssl, data, length); |
| if(rv < 0) { |
| int err = SSL_get_error(connection->ssl, rv); |
| if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { |
| connection->want_io |= (err == SSL_ERROR_WANT_READ ? |
| WANT_READ : WANT_WRITE); |
| rv = SPDYLAY_ERR_WOULDBLOCK; |
| } else { |
| rv = SPDYLAY_ERR_CALLBACK_FAILURE; |
| } |
| } |
| } |
| else |
| { |
| rv = write(connection->fd, |
| data, |
| length); |
| |
| if (rv < 0) |
| { |
| switch(errno) |
| { |
| case EAGAIN: |
| #if EAGAIN != EWOULDBLOCK |
| case EWOULDBLOCK: |
| #endif |
| connection->want_io |= WANT_WRITE; |
| rv = SPDYLAY_ERR_WOULDBLOCK; |
| break; |
| |
| default: |
| rv = SPDYLAY_ERR_CALLBACK_FAILURE; |
| } |
| } |
| } |
| |
| PRINT_INFO2("%zd bytes written by spdy", rv); |
| |
| if(rv > 0) |
| UPDATE_STAT(glob_stat.spdy_bytes_sent, rv); |
| |
| return rv; |
| } |
| |
| |
| /* |
| * The implementation of spdylay_recv_callback type. Here we read data |
| * from the network and write them in |buf|. The capacity of |buf| is |
| * |length| bytes. Returns the number of bytes stored in |buf|. See |
| * the documentation of spdylay_recv_callback for the details. |
| */ |
| static ssize_t |
| spdy_cb_recv(spdylay_session *session, |
| uint8_t *buf, |
| size_t length, |
| int flags, |
| void *user_data) |
| { |
| (void)session; |
| (void)flags; |
| |
| struct SPDY_Connection *connection; |
| ssize_t rv; |
| |
| connection = (struct SPDY_Connection*)user_data; |
| //prevent monopolizing everything |
| if(!(++connection->counter % 10)) return SPDYLAY_ERR_WOULDBLOCK; |
| connection->want_io = IO_NONE; |
| if(connection->is_tls) |
| { |
| ERR_clear_error(); |
| rv = SSL_read(connection->ssl, buf, length); |
| if(rv < 0) { |
| int err = SSL_get_error(connection->ssl, rv); |
| if(err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { |
| connection->want_io |= (err == SSL_ERROR_WANT_READ ? |
| WANT_READ : WANT_WRITE); |
| rv = SPDYLAY_ERR_WOULDBLOCK; |
| } else { |
| rv = SPDYLAY_ERR_CALLBACK_FAILURE; |
| } |
| } else if(rv == 0) { |
| rv = SPDYLAY_ERR_EOF; |
| } |
| } |
| else |
| { |
| rv = read(connection->fd, |
| buf, |
| length); |
| |
| if (rv < 0) |
| { |
| switch(errno) |
| { |
| case EAGAIN: |
| #if EAGAIN != EWOULDBLOCK |
| case EWOULDBLOCK: |
| #endif |
| connection->want_io |= WANT_READ; |
| rv = SPDYLAY_ERR_WOULDBLOCK; |
| break; |
| |
| default: |
| rv = SPDYLAY_ERR_CALLBACK_FAILURE; |
| } |
| } |
| else if(rv == 0) |
| rv = SPDYLAY_ERR_EOF; |
| } |
| |
| if(rv > 0) |
| UPDATE_STAT(glob_stat.spdy_bytes_received, rv); |
| |
| return rv; |
| } |
| |
| |
| static void |
| spdy_cb_before_ctrl_send(spdylay_session *session, |
| spdylay_frame_type type, |
| spdylay_frame *frame, |
| void *user_data) |
| { |
| (void)user_data; |
| |
| int32_t stream_id; |
| struct Proxy *proxy; |
| |
| switch(type) { |
| case SPDYLAY_SYN_STREAM: |
| stream_id = frame->syn_stream.stream_id; |
| proxy = spdylay_session_get_stream_user_data(session, stream_id); |
| proxy->stream_id = stream_id; |
| ++glob_opt.streams_opened; |
| ++proxy->spdy_connection->streams_opened; |
| PRINT_INFO2("opening stream: str open %i; %s", glob_opt.streams_opened, proxy->url); |
| break; |
| case SPDYLAY_RST_STREAM: |
| //try to ignore duplicate RST_STREAMs |
| //TODO this will ignore RST_STREAMs also for bogus data |
| glob_opt.ignore_rst_stream = NULL==spdylay_session_get_stream_user_data(session, frame->rst_stream.stream_id); |
| PRINT_INFO2("sending RST_STREAM for %i; ignore %i; status %i", |
| frame->rst_stream.stream_id, |
| glob_opt.ignore_rst_stream, |
| frame->rst_stream.status_code); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| |
| void |
| spdy_cb_on_ctrl_recv(spdylay_session *session, |
| spdylay_frame_type type, |
| spdylay_frame *frame, |
| void *user_data) |
| { |
| (void)user_data; |
| |
| char **nv; |
| int32_t stream_id; |
| struct Proxy * proxy; |
| |
| switch(type) { |
| case SPDYLAY_SYN_REPLY: |
| nv = frame->syn_reply.nv; |
| stream_id = frame->syn_reply.stream_id; |
| break; |
| case SPDYLAY_RST_STREAM: |
| stream_id = frame->rst_stream.stream_id; |
| break; |
| case SPDYLAY_HEADERS: |
| nv = frame->headers.nv; |
| stream_id = frame->headers.stream_id; |
| break; |
| default: |
| return; |
| break; |
| } |
| |
| proxy = spdylay_session_get_stream_user_data(session, stream_id); |
| if(NULL == proxy) |
| { |
| PRINT_INFO2("received frame type %i for unkonwn stream id %i", type, stream_id); |
| return; |
| //DIE("no proxy obj"); |
| } |
| |
| switch(type) { |
| case SPDYLAY_SYN_REPLY: |
| PRINT_INFO2("received headers for %s", proxy->url); |
| http_create_response(proxy, nv); |
| break; |
| case SPDYLAY_RST_STREAM: |
| PRINT_INFO2("received reset stream for %s", proxy->url); |
| proxy->spdy_error = true; |
| break; |
| case SPDYLAY_HEADERS: |
| PRINT_INFO2("received headers for %s", proxy->url); |
| http_create_response(proxy, nv); |
| break; |
| default: |
| return; |
| break; |
| } |
| |
| glob_opt.spdy_data_received = true; |
| } |
| |
| |
| /* |
| * The implementation of spdylay_on_stream_close_callback type. We use |
| * this function to know the response is fully received. Since we just |
| * fetch 1 resource in this program, after reception of the response, |
| * we submit GOAWAY and close the session. |
| */ |
| static void |
| spdy_cb_on_stream_close(spdylay_session *session, |
| int32_t stream_id, |
| spdylay_status_code status_code, |
| void *user_data) |
| { |
| (void)status_code; |
| (void)user_data; |
| |
| struct Proxy * proxy = spdylay_session_get_stream_user_data(session, stream_id); |
| |
| assert(NULL != proxy); |
| |
| --glob_opt.streams_opened; |
| --proxy->spdy_connection->streams_opened; |
| PRINT_INFO2("closing stream: str opened %i; remove proxy %i", glob_opt.streams_opened, proxy->id); |
| |
| DLL_remove(proxy->spdy_connection->proxies_head, proxy->spdy_connection->proxies_tail, proxy); |
| if(proxy->http_active) |
| { |
| proxy->spdy_active = false; |
| } |
| else |
| { |
| free_proxy(proxy); |
| } |
| } |
| |
| |
| /* |
| * The implementation of spdylay_on_data_chunk_recv_callback type. We |
| * use this function to print the received response body. |
| */ |
| static void |
| spdy_cb_on_data_chunk_recv(spdylay_session *session, |
| uint8_t flags, |
| int32_t stream_id, |
| const uint8_t *data, |
| size_t len, |
| void *user_data) |
| { |
| (void)flags; |
| (void)user_data; |
| |
| struct Proxy *proxy; |
| proxy = spdylay_session_get_stream_user_data(session, stream_id); |
| |
| if(NULL == proxy) |
| { |
| PRINT_INFO("proxy in spdy_cb_on_data_chunk_recv is NULL)"); |
| return; |
| } |
| |
| if(!copy_buffer(data, len, &proxy->http_body, &proxy->http_body_size)) |
| { |
| //TODO handle it better? |
| PRINT_INFO("not enough memory (malloc/realloc returned NULL)"); |
| return; |
| } |
| /* |
| if(NULL == proxy->http_body) |
| proxy->http_body = au_malloc(len); |
| else |
| proxy->http_body = realloc(proxy->http_body, proxy->http_body_size + len); |
| if(NULL == proxy->http_body) |
| { |
| PRINT_INFO("not enough memory (realloc returned NULL)"); |
| return ; |
| } |
| |
| memcpy(proxy->http_body + proxy->http_body_size, data, len); |
| proxy->http_body_size += len; |
| */ |
| PRINT_INFO2("received data for %s; %zu bytes", proxy->url, len); |
| glob_opt.spdy_data_received = true; |
| } |
| |
| |
| static void |
| spdy_cb_on_data_recv(spdylay_session *session, |
| uint8_t flags, |
| int32_t stream_id, |
| int32_t length, |
| void *user_data) |
| { |
| (void)length; |
| (void)user_data; |
| |
| if(flags & SPDYLAY_DATA_FLAG_FIN) |
| { |
| struct Proxy *proxy; |
| proxy = spdylay_session_get_stream_user_data(session, stream_id); |
| proxy->done = true; |
| PRINT_INFO2("last data frame received for %s", proxy->url); |
| } |
| } |
| |
| |
| /* |
| * Setup callback functions. Spdylay API offers many callback |
| * functions, but most of them are optional. The send_callback is |
| * always required. Since we use spdylay_session_recv(), the |
| * recv_callback is also required. |
| */ |
| static void |
| spdy_setup_spdylay_callbacks(spdylay_session_callbacks *callbacks) |
| { |
| memset(callbacks, 0, sizeof(spdylay_session_callbacks)); |
| callbacks->send_callback = spdy_cb_send; |
| callbacks->recv_callback = spdy_cb_recv; |
| callbacks->before_ctrl_send_callback = spdy_cb_before_ctrl_send; |
| callbacks->on_ctrl_recv_callback = spdy_cb_on_ctrl_recv; |
| callbacks->on_stream_close_callback = spdy_cb_on_stream_close; |
| callbacks->on_data_chunk_recv_callback = spdy_cb_on_data_chunk_recv; |
| callbacks->on_data_recv_callback = spdy_cb_on_data_recv; |
| } |
| |
| |
| /* |
| * Callback function for SSL/TLS NPN. Since this program only supports |
| * SPDY protocol, if server does not offer SPDY protocol the Spdylay |
| * library supports, we terminate program. |
| */ |
| static int |
| spdy_cb_ssl_select_next_proto(SSL* ssl, |
| unsigned char **out, |
| unsigned char *outlen, |
| const unsigned char *in, |
| unsigned int inlen, |
| void *arg) |
| { |
| (void)ssl; |
| |
| int rv; |
| uint16_t *spdy_proto_version; |
| |
| /* spdylay_select_next_protocol() selects SPDY protocol version the |
| Spdylay library supports. */ |
| rv = spdylay_select_next_protocol(out, outlen, in, inlen); |
| if(rv <= 0) { |
| PRINT_INFO("Server did not advertise spdy/2 or spdy/3 protocol."); |
| return rv; |
| } |
| spdy_proto_version = (uint16_t*)arg; |
| *spdy_proto_version = rv; |
| return SSL_TLSEXT_ERR_OK; |
| } |
| |
| |
| /* |
| * Setup SSL context. We pass |spdy_proto_version| to get negotiated |
| * SPDY protocol version in NPN callback. |
| */ |
| void |
| spdy_ssl_init_ssl_ctx(SSL_CTX *ssl_ctx, |
| uint16_t *spdy_proto_version) |
| { |
| /* Disable SSLv2 and enable all workarounds for buggy servers */ |
| SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); |
| SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); |
| SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); |
| /* Set NPN callback */ |
| SSL_CTX_set_next_proto_select_cb(ssl_ctx, spdy_cb_ssl_select_next_proto, |
| spdy_proto_version); |
| } |
| |
| |
| static int |
| spdy_ssl_handshake(SSL *ssl, |
| int fd) |
| { |
| int rv; |
| |
| if(SSL_set_fd(ssl, fd) == 0) |
| spdy_dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL)); |
| |
| ERR_clear_error(); |
| rv = SSL_connect(ssl); |
| if(rv <= 0) |
| PRINT_INFO2("SSL_connect %s", ERR_error_string(ERR_get_error(), NULL)); |
| |
| return rv; |
| } |
| |
| |
| /* |
| * Connects to the host |host| and port |port|. This function returns |
| * the file descriptor of the client socket. |
| */ |
| static int |
| spdy_socket_connect_to(const char *host, |
| uint16_t port) |
| { |
| struct addrinfo hints; |
| int fd = -1; |
| int rv; |
| char service[NI_MAXSERV]; |
| struct addrinfo *res, *rp; |
| |
| //TODO checks |
| snprintf(service, sizeof(service), "%u", port); |
| memset(&hints, 0, sizeof(struct addrinfo)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| rv = getaddrinfo(host, service, &hints, &res); |
| if(rv != 0) |
| { |
| printf("%s\n",host); |
| spdy_dief("getaddrinfo", gai_strerror(rv)); |
| } |
| for(rp = res; rp; rp = rp->ai_next) |
| { |
| fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
| if(fd == -1) |
| continue; |
| while((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && |
| errno == EINTR); |
| if(rv == 0) |
| break; |
| close(fd); |
| fd = -1; |
| } |
| freeaddrinfo(res); |
| |
| return fd; |
| } |
| |
| |
| static void |
| spdy_socket_make_non_block(int fd) |
| { |
| int flags; |
| int rv; |
| |
| while((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR); |
| |
| if(flags == -1) |
| spdy_dief("fcntl", strerror(errno)); |
| |
| while((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR); |
| |
| if(rv == -1) |
| spdy_dief("fcntl", strerror(errno)); |
| } |
| |
| |
| /* |
| * Setting TCP_NODELAY is not mandatory for the SPDY protocol. |
| */ |
| static void |
| spdy_socket_set_tcp_nodelay(int fd) |
| { |
| int val = 1; |
| int rv; |
| |
| rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); |
| if(rv == -1) |
| spdy_dief("setsockopt", strerror(errno)); |
| } |
| |
| /* |
| * Update |pollfd| based on the state of |connection|. |
| */ |
| /* |
| void |
| spdy_ctl_poll(struct pollfd *pollfd, |
| struct SPDY_Connection *connection) |
| { |
| pollfd->events = 0; |
| if(spdylay_session_want_read(connection->session) || |
| connection->want_io & WANT_READ) |
| { |
| pollfd->events |= POLLIN; |
| } |
| if(spdylay_session_want_write(connection->session) || |
| connection->want_io & WANT_WRITE) |
| { |
| pollfd->events |= POLLOUT; |
| } |
| }*/ |
| |
| |
| /* |
| * Update |selectfd| based on the state of |connection|. |
| */ |
| bool |
| spdy_ctl_select(fd_set * read_fd_set, |
| fd_set * write_fd_set, |
| fd_set * except_fd_set, |
| struct SPDY_Connection *connection) |
| { |
| (void)except_fd_set; |
| |
| bool ret = false; |
| |
| if(spdylay_session_want_read(connection->session) || |
| connection->want_io & WANT_READ) |
| { |
| FD_SET(connection->fd, read_fd_set); |
| ret = true; |
| } |
| if(spdylay_session_want_write(connection->session) || |
| connection->want_io & WANT_WRITE) |
| { |
| FD_SET(connection->fd, write_fd_set); |
| ret = true; |
| } |
| |
| return ret; |
| } |
| |
| |
| /* |
| * Performs the network I/O. |
| */ |
| int |
| spdy_exec_io(struct SPDY_Connection *connection) |
| { |
| int rv; |
| |
| rv = spdylay_session_recv(connection->session); |
| if(rv != 0) |
| { |
| PRINT_INFO2("spdylay_session_recv %i", rv); |
| return rv; |
| } |
| rv = spdylay_session_send(connection->session); |
| if(rv != 0) |
| PRINT_INFO2("spdylay_session_send %i", rv); |
| |
| return rv; |
| } |
| |
| |
| /* |
| * Fetches the resource denoted by |uri|. |
| */ |
| struct SPDY_Connection * |
| spdy_connect(const struct URI *uri, |
| uint16_t port, |
| bool is_tls) |
| { |
| spdylay_session_callbacks callbacks; |
| int fd; |
| SSL *ssl=NULL; |
| struct SPDY_Connection * connection = NULL; |
| int rv; |
| |
| spdy_setup_spdylay_callbacks(&callbacks); |
| |
| /* Establish connection and setup SSL */ |
| PRINT_INFO2("connecting to %s:%i", uri->host, port); |
| fd = spdy_socket_connect_to(uri->host, port); |
| if(fd == -1) |
| { |
| PRINT_INFO("Could not open file descriptor"); |
| return NULL; |
| } |
| |
| if(is_tls) |
| { |
| ssl = SSL_new(glob_opt.ssl_ctx); |
| if(ssl == NULL) { |
| spdy_dief("SSL_new", ERR_error_string(ERR_get_error(), NULL)); |
| } |
| |
| //TODO non-blocking |
| /* To simplify the program, we perform SSL/TLS handshake in blocking |
| I/O. */ |
| glob_opt.spdy_proto_version = 0; |
| rv = spdy_ssl_handshake(ssl, fd); |
| if(rv <= 0 || (glob_opt.spdy_proto_version != 3 && glob_opt.spdy_proto_version != 2)) |
| { |
| PRINT_INFO("Closing SSL"); |
| //no spdy on the other side |
| goto free_and_fail; |
| } |
| } |
| else |
| { |
| glob_opt.spdy_proto_version = 3; |
| } |
| |
| if(NULL == (connection = au_malloc(sizeof(struct SPDY_Connection)))) |
| goto free_and_fail; |
| |
| connection->is_tls = is_tls; |
| connection->ssl = ssl; |
| connection->want_io = IO_NONE; |
| if(NULL == (connection->host = strdup(uri->host))) |
| goto free_and_fail; |
| |
| /* Here make file descriptor non-block */ |
| spdy_socket_make_non_block(fd); |
| spdy_socket_set_tcp_nodelay(fd); |
| |
| PRINT_INFO2("[INFO] SPDY protocol version = %d\n", glob_opt.spdy_proto_version); |
| rv = spdylay_session_client_new(&(connection->session), glob_opt.spdy_proto_version, |
| &callbacks, connection); |
| if(rv != 0) { |
| spdy_diec("spdylay_session_client_new", rv); |
| } |
| |
| connection->fd = fd; |
| |
| return connection; |
| |
| //for GOTO |
| free_and_fail: |
| if(NULL != connection) |
| { |
| free(connection->host); |
| free(connection); |
| } |
| |
| if(is_tls) |
| SSL_shutdown(ssl); |
| |
| close(fd); |
| |
| if(is_tls) |
| SSL_free(ssl); |
| |
| return NULL; |
| } |
| |
| |
| void |
| spdy_free_connection(struct SPDY_Connection * connection) |
| { |
| struct Proxy *proxy; |
| struct Proxy *proxy_next; |
| |
| if(NULL != connection) |
| { |
| for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy_next) |
| { |
| proxy_next = proxy->next; |
| DLL_remove(connection->proxies_head, connection->proxies_tail, proxy); |
| proxy->spdy_active = false; |
| proxy->spdy_error = true; |
| PRINT_INFO2("spdy_free_connection for id %i", proxy->id); |
| if(!proxy->http_active) |
| { |
| free_proxy(proxy); |
| } |
| } |
| spdylay_session_del(connection->session); |
| SSL_free(connection->ssl); |
| free(connection->host); |
| free(connection); |
| //connection->session = NULL; |
| } |
| } |
| |
| |
| int |
| spdy_request(const char **nv, |
| struct Proxy *proxy, |
| bool with_body) |
| { |
| int ret; |
| uint16_t port; |
| struct SPDY_Connection *connection; |
| spdylay_data_provider post_data; |
| |
| if(glob_opt.only_proxy) |
| { |
| connection = glob_opt.spdy_connection; |
| } |
| else |
| { |
| connection = glob_opt.spdy_connections_head; |
| while(NULL != connection) |
| { |
| if(0 == strcasecmp(proxy->uri->host, connection->host)) |
| break; |
| connection = connection->next; |
| } |
| |
| if(NULL == connection) |
| { |
| //connect to host |
| port = proxy->uri->port; |
| if(0 == port) port = 443; |
| connection = spdy_connect(proxy->uri, port, true); |
| if(NULL != connection) |
| { |
| DLL_insert(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); |
| glob_opt.total_spdy_connections++; |
| } |
| else |
| connection = glob_opt.spdy_connection; |
| } |
| } |
| |
| if(NULL == connection) |
| { |
| PRINT_INFO("there is no proxy!"); |
| return -1; |
| } |
| |
| proxy->spdy_connection = connection; |
| if(with_body) |
| { |
| post_data.source.ptr = proxy; |
| post_data.read_callback = &spdy_cb_data_source_read; |
| ret = spdylay_submit_request(connection->session, 0, nv, &post_data, proxy); |
| } |
| else |
| ret = spdylay_submit_request(connection->session, 0, nv, NULL, proxy); |
| |
| if(ret != 0) { |
| spdy_diec("spdylay_spdy_submit_request", ret); |
| } |
| PRINT_INFO2("adding proxy %i", proxy->id); |
| if(NULL != connection->proxies_head) |
| PRINT_INFO2("before proxy %i", connection->proxies_head->id); |
| DLL_insert(connection->proxies_head, connection->proxies_tail, proxy); |
| |
| return ret; |
| } |
| |
| /* |
| void |
| spdy_get_pollfdset(struct pollfd fds[], |
| struct SPDY_Connection *connections[], |
| unsigned int max_size, |
| nfds_t *real_size) |
| { |
| struct SPDY_Connection *connection; |
| struct Proxy *proxy; |
| |
| *real_size = 0; |
| if(max_size<1) |
| return; |
| |
| if(NULL != glob_opt.spdy_connection) |
| { |
| spdy_ctl_poll(&(fds[*real_size]), glob_opt.spdy_connection); |
| if(!fds[*real_size].events) |
| { |
| //PRINT_INFO("TODO drop connection"); |
| glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened; |
| |
| for(proxy = glob_opt.spdy_connection->proxies_head; NULL != proxy; proxy=proxy->next) |
| { |
| abort(); |
| DLL_remove(glob_opt.spdy_connection->proxies_head, glob_opt.spdy_connection->proxies_tail, proxy); |
| proxy->spdy_active = false; |
| } |
| spdy_free_connection(glob_opt.spdy_connection); |
| glob_opt.spdy_connection = NULL; |
| } |
| else |
| { |
| fds[*real_size].fd = glob_opt.spdy_connection->fd; |
| connections[*real_size] = glob_opt.spdy_connection; |
| ++(*real_size); |
| } |
| } |
| |
| connection = glob_opt.spdy_connections_head; |
| |
| while(NULL != connection && *real_size < max_size) |
| { |
| assert(!glob_opt.only_proxy); |
| spdy_ctl_poll(&(fds[*real_size]), connection); |
| if(!fds[*real_size].events) |
| { |
| //PRINT_INFO("TODO drop connection"); |
| glob_opt.streams_opened -= connection->streams_opened; |
| DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); |
| glob_opt.total_spdy_connections--; |
| |
| for(proxy = connection->proxies_head; NULL != proxy; proxy=proxy->next) |
| { |
| abort(); |
| DLL_remove(connection->proxies_head, connection->proxies_tail, proxy); |
| proxy->spdy_active = false; |
| } |
| spdy_free_connection(connection); |
| } |
| else |
| { |
| fds[*real_size].fd = connection->fd; |
| connections[*real_size] = connection; |
| ++(*real_size); |
| } |
| connection = connection->next; |
| } |
| |
| //, "TODO max num of conn reached; close something" |
| assert(NULL == connection); |
| } |
| */ |
| |
| int |
| spdy_get_selectfdset(fd_set * read_fd_set, |
| fd_set * write_fd_set, |
| fd_set * except_fd_set, |
| struct SPDY_Connection *connections[], |
| unsigned int max_size, |
| nfds_t *real_size) |
| { |
| struct SPDY_Connection *connection; |
| struct SPDY_Connection *next_connection; |
| bool ret; |
| int maxfd = 0; |
| |
| *real_size = 0; |
| if(max_size<1) |
| return 0; |
| |
| if(NULL != glob_opt.spdy_connection) |
| { |
| ret = spdy_ctl_select(read_fd_set, |
| write_fd_set, |
| except_fd_set, glob_opt.spdy_connection); |
| if(!ret) |
| { |
| glob_opt.streams_opened -= glob_opt.spdy_connection->streams_opened; |
| |
| PRINT_INFO("spdy_free_connection in spdy_get_selectfdset"); |
| spdy_free_connection(glob_opt.spdy_connection); |
| glob_opt.spdy_connection = NULL; |
| } |
| else |
| { |
| connections[*real_size] = glob_opt.spdy_connection; |
| ++(*real_size); |
| if(maxfd < glob_opt.spdy_connection->fd) maxfd = glob_opt.spdy_connection->fd; |
| } |
| } |
| |
| connection = glob_opt.spdy_connections_head; |
| |
| while(NULL != connection && *real_size < max_size) |
| { |
| assert(!glob_opt.only_proxy); |
| ret = spdy_ctl_select(read_fd_set, |
| write_fd_set, |
| except_fd_set, connection); |
| |
| next_connection = connection->next; |
| if(!ret) |
| { |
| glob_opt.streams_opened -= connection->streams_opened; |
| DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection); |
| glob_opt.total_spdy_connections--; |
| |
| PRINT_INFO("spdy_free_connection in spdy_get_selectfdset"); |
| spdy_free_connection(connection); |
| } |
| else |
| { |
| connections[*real_size] = connection; |
| ++(*real_size); |
| if(maxfd < connection->fd) maxfd = connection->fd; |
| } |
| connection = next_connection; |
| } |
| |
| //, "TODO max num of conn reached; close something" |
| assert(NULL == connection); |
| |
| return maxfd; |
| } |
| |
| /* |
| void |
| spdy_run(struct pollfd fds[], |
| struct SPDY_Connection *connections[], |
| int size) |
| { |
| int i; |
| int ret; |
| struct Proxy *proxy; |
| |
| for(i=0; i<size; ++i) |
| { |
| // PRINT_INFO2("exec about to be called for %s", connections[i]->host); |
| if(fds[i].revents & (POLLIN | POLLOUT)) |
| { |
| ret = spdy_exec_io(connections[i]); |
| //PRINT_INFO2("%i",ret); |
| //if((spdy_pollfds[i].revents & POLLHUP) || (spdy_pollfds[0].revents & POLLERR)) |
| // PRINT_INFO("SPDY SPDY_Connection error"); |
| |
| //TODO POLLRDHUP |
| // always close on ret != 0? |
| |
| if(0 != ret) |
| { |
| glob_opt.streams_opened -= connections[i]->streams_opened; |
| if(connections[i] == glob_opt.spdy_connection) |
| { |
| glob_opt.spdy_connection = NULL; |
| } |
| else |
| { |
| DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]); |
| glob_opt.total_spdy_connections--; |
| } |
| for(proxy = connections[i]->proxies_head; NULL != proxy; proxy=proxy->next) |
| { |
| abort(); |
| DLL_remove(connections[i]->proxies_head, connections[i]->proxies_tail, proxy); |
| proxy->spdy_active = false; |
| proxy->spdy_error = true; |
| PRINT_INFO2("spdy_free_connection for id %i", proxy->id); |
| } |
| PRINT_INFO("spdy_free_connection in loop"); |
| spdy_free_connection(connections[i]); |
| } |
| } |
| else |
| PRINT_INFO("not called"); |
| } |
| } |
| */ |
| |
| void |
| spdy_run_select(fd_set * read_fd_set, |
| fd_set * write_fd_set, |
| fd_set * except_fd_set, |
| struct SPDY_Connection *connections[], |
| int size) |
| { |
| int i; |
| int ret; |
| |
| for(i=0; i<size; ++i) |
| { |
| // PRINT_INFO2("exec about to be called for %s", connections[i]->host); |
| if(FD_ISSET(connections[i]->fd, read_fd_set) || FD_ISSET(connections[i]->fd, write_fd_set) || FD_ISSET(connections[i]->fd, except_fd_set)) |
| { |
| //raise(SIGINT); |
| ret = spdy_exec_io(connections[i]); |
| |
| if(0 != ret) |
| { |
| glob_opt.streams_opened -= connections[i]->streams_opened; |
| if(connections[i] == glob_opt.spdy_connection) |
| { |
| glob_opt.spdy_connection = NULL; |
| } |
| else |
| { |
| DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connections[i]); |
| glob_opt.total_spdy_connections--; |
| } |
| PRINT_INFO("in spdy_run_select"); |
| spdy_free_connection(connections[i]); |
| } |
| } |
| else |
| { |
| PRINT_INFO("not called"); |
| //PRINT_INFO2("connection->want_io %i",connections[i]->want_io); |
| //PRINT_INFO2("read %i",spdylay_session_want_read(connections[i]->session)); |
| //PRINT_INFO2("write %i",spdylay_session_want_write(connections[i]->session)); |
| //raise(SIGINT); |
| } |
| } |
| } |