From df7881f07f53b041dc0568be8528e9dbb74994cc Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:04:49 -0800 Subject: Code drop from //branches/cupcake/...@124589 --- proxy/proxy_common.c | 345 +++++++------ proxy/proxy_common.h | 18 +- proxy/proxy_http.c | 235 ++------- proxy/proxy_http_connector.c | 213 ++++++++ proxy/proxy_http_int.h | 38 ++ proxy/proxy_http_rewriter.c | 1132 ++++++++++++++++++++++++++++++++++++++++++ proxy/proxy_int.h | 146 ++++-- 7 files changed, 1727 insertions(+), 400 deletions(-) create mode 100644 proxy/proxy_http_connector.c create mode 100644 proxy/proxy_http_int.h create mode 100644 proxy/proxy_http_rewriter.c (limited to 'proxy') diff --git a/proxy/proxy_common.c b/proxy/proxy_common.c index c5762dc..0e45481 100644 --- a/proxy/proxy_common.c +++ b/proxy/proxy_common.c @@ -26,6 +26,7 @@ proxy_LOG(const char* fmt, ...) va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); + fprintf(stderr, "\n"); } void @@ -39,49 +40,39 @@ proxy_set_verbose(int mode) static ProxyConnection s_connections[1]; +#define MAX_HEX_DUMP 512 + static void hex_dump( void* base, int size, const char* prefix ) { - uint8_t* p = (uint8_t*)base; - const int max_count = 16; - - while (size > 0) { - int count = size > max_count ? max_count : size; - int n; - const char* space = prefix; - - for (n = 0; n < count; n++) { - proxy_LOG( "%s%02x", space, p[n] ); - space = " "; - } - - proxy_LOG( "%-*s", 4 + 3*(max_count-n), "" ); - - for (n = 0; n < count; n++) { - int c = p[n]; - - if (c < 32 || c > 127) - c = '.'; - proxy_LOG( "%c", c ); - } - proxy_LOG( "\n" ); - size -= count; - p += count; - } + STRALLOC_DEFINE(s); + if (size > MAX_HEX_DUMP) + size = MAX_HEX_DUMP; + stralloc_add_hexdump(s, base, size, prefix); + proxy_LOG( "%s", stralloc_cstr(s) ); + stralloc_reset(s); } - void -proxy_connection_init( ProxyConnection* conn, - int socket, - struct sockaddr_in* address, - ProxyService* service ) +proxy_connection_init( ProxyConnection* conn, + int socket, + struct sockaddr_in* address, + ProxyService* service, + ProxyConnectionFreeFunc conn_free, + ProxyConnectionSelectFunc conn_select, + ProxyConnectionPollFunc conn_poll ) { conn->socket = socket; conn->address = address[0]; conn->service = service; conn->next = NULL; + conn->conn_free = conn_free; + conn->conn_select = conn_select; + conn->conn_poll = conn_poll; + + socket_set_nonblock(socket); + { uint32_t ip = ntohl(address->sin_addr.s_addr); uint16_t port = ntohs(address->sin_port); @@ -98,122 +89,154 @@ proxy_connection_init( ProxyConnection* conn, conn->name[sizeof(conn->name)-1] = 0; } - conn->buffer_pos = 0; - conn->buffer_len = 0; - conn->buffer = conn->buffer0; + stralloc_reset(conn->str); + conn->str_pos = 0; } void proxy_connection_done( ProxyConnection* conn ) { - if (conn->buffer != conn->buffer0) { - qemu_free(conn->buffer); + stralloc_reset( conn->str ); + if (conn->socket >= 0) { + socket_close(conn->socket); + conn->socket = -1; } } -int -proxy_connection_send( ProxyConnection* conn ) +void +proxy_connection_rewind( ProxyConnection* conn ) { - int result = -1; - int fd = conn->socket; - int avail = conn->buffer_len - conn->buffer_pos; + stralloc_t* str = conn->str; + + /* only keep a small buffer in the heap */ + conn->str_pos = 0; + str->n = 0; + if (str->a > 1024) + stralloc_reset(str); +} + +DataStatus +proxy_connection_send( ProxyConnection* conn, int fd ) +{ + stralloc_t* str = conn->str; + int avail = str->n - conn->str_pos; + + conn->str_sent = 0; + + if (avail <= 0) + return 1; if (proxy_log) { - PROXY_LOG("%s: sending %d bytes:\n", conn->name, avail ); - hex_dump( conn->buffer + conn->buffer_pos, avail, ">> " ); + PROXY_LOG("%s: sending %d bytes:", conn->name, avail ); + hex_dump( str->s + conn->str_pos, avail, ">> " ); } while (avail > 0) { - int n = send(fd, conn->buffer + conn->buffer_pos, avail, 0); + int n = send(fd, str->s + conn->str_pos, avail, 0); + if (n == 0) { + PROXY_LOG("%s: connection reset by peer (send)", + conn->name); + return DATA_ERROR; + } if (n < 0) { - if (errno == EINTR) + if (socket_errno == EINTR) continue; - if (errno == EWOULDBLOCK || errno == EAGAIN) - return 0; - PROXY_LOG("%s: error: %s\n", conn->name, strerror(errno)); - return -1; + + if (socket_errno == EWOULDBLOCK || socket_errno == EAGAIN) + return DATA_NEED_MORE; + + PROXY_LOG("%s: error: %s", conn->name, socket_errstr()); + return DATA_ERROR; } - conn->buffer_pos += n; - avail -= n; + conn->str_pos += n; + conn->str_sent += n; + avail -= n; } - return 1; + + proxy_connection_rewind(conn); + return DATA_COMPLETED; } -int -proxy_connection_receive( ProxyConnection* conn ) + +DataStatus +proxy_connection_receive( ProxyConnection* conn, int fd, int wanted ) { - int result = -1; - int fd = conn->socket; - int avail = conn->buffer_len - conn->buffer_pos; + stralloc_t* str = conn->str; - while (avail > 0) { - int n = recv(fd, conn->buffer + conn->buffer_pos, avail, 0); + conn->str_recv = 0; + + while (wanted > 0) { + int n; + + stralloc_readyplus( str, wanted ); + n = recv(fd, str->s + str->n, wanted, 0); + if (n == 0) { + PROXY_LOG("%s: connection reset by peer (receive)", + conn->name); + return DATA_ERROR; + } if (n < 0) { - if (errno == EINTR) + if (socket_errno == EINTR) continue; - if (errno == EWOULDBLOCK || errno == EAGAIN) - return 0; - PROXY_LOG("%s: error: %s\n", conn->name, strerror(errno)); - return -1; + + if (socket_errno == EWOULDBLOCK || socket_errno == EAGAIN) + return DATA_NEED_MORE; + + PROXY_LOG("%s: error: %s", conn->name, socket_errstr()); + return DATA_ERROR; } if (proxy_log) { - PROXY_LOG("%s: received %d bytes:\n", conn->name, n ); - hex_dump( conn->buffer + conn->buffer_pos, n, ">> " ); + PROXY_LOG("%s: received %d bytes:", conn->name, n ); + hex_dump( str->s + str->n, n, "<< " ); } - conn->buffer_pos += n; - avail -= n; + str->n += n; + wanted -= n; + conn->str_recv += n; } - return 1; + return DATA_COMPLETED; } -int -proxy_connection_receive_line( ProxyConnection* conn ) + +DataStatus +proxy_connection_receive_line( ProxyConnection* conn, int fd ) { - int result = -1; - int fd = conn->socket; + stralloc_t* str = conn->str; for (;;) { char c; int n = recv(fd, &c, 1, 0); if (n == 0) { - PROXY_LOG("%s: disconnected from server\n", conn->name ); - return -1; + PROXY_LOG("%s: disconnected from server", conn->name ); + return DATA_ERROR; } if (n < 0) { - if (errno == EINTR) + if (socket_errno == EINTR) continue; - if (errno == EWOULDBLOCK || errno == EAGAIN) { - PROXY_LOG("%s: blocked\n", conn->name); - return 0; + + if (socket_errno == EWOULDBLOCK || socket_errno == EAGAIN) { + PROXY_LOG("%s: blocked", conn->name); + return DATA_NEED_MORE; } - PROXY_LOG("%s: error: %s\n", conn->name, strerror(errno)); - return -1; + PROXY_LOG("%s: error: %s", conn->name, socket_errstr()); + return DATA_ERROR; } + stralloc_add_c(str, c); if (c == '\n') { - if (conn->buffer_pos > 0 && conn->buffer[conn->buffer_pos-1] == '\r') - conn->buffer_pos -= 1; - - conn->buffer[conn->buffer_pos] = 0; + str->s[--str->n] = 0; + if (str->n > 0 && str->s[str->n-1] == '\r') + str->s[--str->n] = 0; - PROXY_LOG("%s: received '%.*s'\n", conn->name, - conn->buffer_pos, conn->buffer); - return 1; - } - - conn->buffer[ conn->buffer_pos++ ] = c; - if (conn->buffer_pos == conn->buffer_len) { - PROXY_LOG("%s: line received from proxy is too long\n", conn->name); - return -1; + PROXY_LOG("%s: received '%s'", conn->name, + quote_bytes(str->s, str->n)); + return DATA_COMPLETED; } } } - - static void proxy_connection_insert( ProxyConnection* conn, ProxyConnection* after ) { @@ -276,7 +299,7 @@ proxy_manager_atexit( void ) /* free all proxy connections */ while (conn != s_connections) { ProxyConnection* next = conn->next; - conn->service->conn_free( conn ); + conn->conn_free( conn ); conn = next; } conn->next = conn; @@ -293,6 +316,7 @@ proxy_manager_atexit( void ) void proxy_connection_free( ProxyConnection* conn, + int keep_alive, ProxyEvent event ) { if (conn) { @@ -301,18 +325,21 @@ proxy_connection_free( ProxyConnection* conn, proxy_connection_remove(conn); if (event != PROXY_EVENT_NONE) - conn->ev_func( conn->ev_opaque, event ); + conn->ev_func( conn->ev_opaque, fd, event ); - conn->service->conn_free(conn); + if (keep_alive) + conn->socket = -1; + + conn->conn_free(conn); } } int -proxy_manager_add( int socket, - struct sockaddr_in* address, - void* ev_opaque, - ProxyEventFunc ev_func ) +proxy_manager_add( struct sockaddr_in* address, + int sock_type, + ProxyEventFunc ev_func, + void* ev_opaque ) { int n; @@ -320,16 +347,14 @@ proxy_manager_add( int socket, proxy_manager_init(); } - socket_set_nonblock(socket); - for (n = 0; n < s_num_services; n++) { ProxyService* service = s_services[n]; ProxyConnection* conn = service->serv_connect( service->opaque, - socket, + sock_type, address ); if (conn != NULL) { - conn->ev_opaque = ev_opaque; conn->ev_func = ev_func; + conn->ev_opaque = ev_opaque; proxy_connection_insert(conn, s_connections->prev); return 0; } @@ -343,50 +368,83 @@ proxy_manager_add( int socket, * the connection accept/refusal occured */ void -proxy_manager_del( int socket ) +proxy_manager_del( void* ev_opaque ) { ProxyConnection* conn = s_connections->next; for ( ; conn != s_connections; conn = conn->next ) { - if (conn->socket == socket) { - int fd = conn->socket; + if (conn->ev_opaque == ev_opaque) { proxy_connection_remove(conn); - conn->service->conn_free(conn); - socket_close(fd); + conn->conn_free(conn); return; } } } +void +proxy_select_set( ProxySelect* sel, + int fd, + unsigned flags ) +{ + if (fd < 0 || !flags) + return; + + if (*sel->pcount < fd+1) + *sel->pcount = fd+1; + + if (flags & PROXY_SELECT_READ) { + FD_SET( fd, sel->reads ); + } else { + FD_CLR( fd, sel->reads ); + } + if (flags & PROXY_SELECT_WRITE) { + FD_SET( fd, sel->writes ); + } else { + FD_CLR( fd, sel->writes ); + } + if (flags & PROXY_SELECT_ERROR) { + FD_SET( fd, sel->errors ); + } else { + FD_CLR( fd, sel->errors ); + } +} + +unsigned +proxy_select_poll( ProxySelect* sel, int fd ) +{ + unsigned flags = 0; + + if (fd >= 0) { + if ( FD_ISSET(fd, sel->reads) ) + flags |= PROXY_SELECT_READ; + if ( FD_ISSET(fd, sel->writes) ) + flags |= PROXY_SELECT_WRITE; + if ( FD_ISSET(fd, sel->errors) ) + flags |= PROXY_SELECT_ERROR; + } + return flags; +} + /* this function is called to update the select file descriptor sets * with those of the proxified connection sockets that are currently managed */ void proxy_manager_select_fill( int *pcount, fd_set* read_fds, fd_set* write_fds, fd_set* err_fds) { ProxyConnection* conn; + ProxySelect sel[1]; if (!s_init) proxy_manager_init(); - conn = s_connections->next; - for ( ; conn != s_connections; conn = conn->next ) { - unsigned flags = conn->service->conn_select(conn); - int fd = conn->socket; - - if (!flags) - continue; + sel->pcount = pcount; + sel->reads = read_fds; + sel->writes = write_fds; + sel->errors = err_fds; - if (*pcount < fd+1) - *pcount = fd+1; - - if (flags & PROXY_SELECT_READ) { - FD_SET( fd, read_fds ); - } - if (flags & PROXY_SELECT_WRITE) { - FD_SET( fd, write_fds ); - } - if (flags & PROXY_SELECT_ERROR) { - FD_SET( fd, err_fds ); - } + conn = s_connections->next; + while (conn != s_connections) { + ProxyConnection* next = conn->next; + conn->conn_select(conn, sel); + conn = next; } } @@ -395,21 +453,16 @@ void proxy_manager_poll( fd_set* read_fds, fd_set* write_fds, fd_set* err_fds ) { ProxyConnection* conn = s_connections->next; - while (conn != s_connections) { - ProxyConnection* next = conn->next; - int fd = conn->socket; - unsigned flags = 0; + ProxySelect sel[1]; - if ( FD_ISSET(fd, read_fds) ) - flags |= PROXY_SELECT_READ; - if ( FD_ISSET(fd, write_fds) ) - flags |= PROXY_SELECT_WRITE; - if ( FD_ISSET(fd, err_fds) ) - flags |= PROXY_SELECT_ERROR; + sel->pcount = NULL; + sel->reads = read_fds; + sel->writes = write_fds; + sel->errors = err_fds; - if (flags != 0) { - conn->service->conn_poll( conn, flags ); - } + while (conn != s_connections) { + ProxyConnection* next = conn->next; + conn->conn_poll( conn, sel ); conn = next; } } @@ -480,7 +533,7 @@ proxy_resolve_server( struct sockaddr_in* addr, host = gethostbyname(name); if (host == NULL) { - PROXY_LOG("%s: can't resolve proxy server name '%s'\n", + PROXY_LOG("%s: can't resolve proxy server name '%s'", __FUNCTION__, name); goto Exit; } @@ -488,7 +541,7 @@ proxy_resolve_server( struct sockaddr_in* addr, addr->sin_addr = *(struct in_addr*)host->h_addr; { uint32_t a = ntohl(addr->sin_addr.s_addr); - PROXY_LOG("server name '%s' resolved to %d.%d.%d.%d\n", name, (a>>24)&255, (a>>16)&255,(a>>8)&255,a&255); + PROXY_LOG("server name '%s' resolved to %d.%d.%d.%d", name, (a>>24)&255, (a>>16)&255,(a>>8)&255,a&255); } result = 0; diff --git a/proxy/proxy_common.h b/proxy/proxy_common.h index 54889cf..57f224d 100644 --- a/proxy/proxy_common.h +++ b/proxy/proxy_common.h @@ -29,7 +29,7 @@ typedef enum { } ProxyEvent; /* event can't be NONE when this callback is called */ -typedef void (*ProxyEventFunc)( void* opaque, ProxyEvent event ); +typedef void (*ProxyEventFunc)( void* opaque, int fd, ProxyEvent event ); extern void proxy_set_verbose(int mode); @@ -61,19 +61,27 @@ typedef struct { * * returns 0 on success, or -1 if there is no proxy service for this type of connection */ -extern int proxy_manager_add( int socket, struct sockaddr_in* address, void* ev_opaque, ProxyEventFunc ev_func ); +extern int proxy_manager_add( struct sockaddr_in* address, + int sock_type, + ProxyEventFunc ev_func, + void* ev_opaque ); /* remove an on-going proxified socket connection from the manager's list. * this is only necessary when the socket connection must be canceled before * the connection accept/refusal occured */ -extern void proxy_manager_del( int socket ); +extern void proxy_manager_del( void* ev_opaque ); /* this function is called to update the select file descriptor sets * with those of the proxified connection sockets that are currently managed */ -extern void proxy_manager_select_fill( int *pcount, fd_set* read_fds, fd_set* write_fds, fd_set* err_fds); +extern void proxy_manager_select_fill( int *pcount, + fd_set* read_fds, + fd_set* write_fds, + fd_set* err_fds); /* this function is called to act on proxified connection sockets when network events arrive */ -extern void proxy_manager_poll( fd_set* read_fds, fd_set* write_fds, fd_set* err_fds ); +extern void proxy_manager_poll( fd_set* read_fds, + fd_set* write_fds, + fd_set* err_fds ); #endif /* END */ diff --git a/proxy/proxy_http.c b/proxy/proxy_http.c index 83982a7..c3d663c 100644 --- a/proxy/proxy_http.c +++ b/proxy/proxy_http.c @@ -10,200 +10,18 @@ ** GNU General Public License for more details. */ #include "proxy_int.h" -#include "proxy_http.h" +#include "proxy_http_int.h" #include #include #include #include "vl.h" -typedef enum { - HTTP_NONE = 0, - HTTP_CONNECTING, /* connecting to the server */ - HTTP_SEND_HEADER, /* connected, sending header to the server */ - HTTP_RECEIVE_ANSWER_LINE1, - HTTP_RECEIVE_ANSWER_LINE2 /* connected, reading server's answer */ -} HttpConnectionState; - - -typedef struct { - ProxyConnection root[1]; - HttpConnectionState state; -} HttpConnection; - - -typedef struct { - ProxyService root[1]; - struct sockaddr_in server_addr; /* server address and port */ - char* footer; /* the footer contains the static parts of the */ - int footer_len; /* connection header, we generate it only once */ - char footer0[512]; -} HttpService; - - -static void -http_connection_free( HttpConnection* conn ) -{ - proxy_connection_done(conn->root); - qemu_free(conn); -} - - #define HTTP_VERSION "1.1" -static int -http_connection_init( HttpConnection* conn ) -{ - HttpService* service = (HttpService*) conn->root->service; - ProxyConnection* root = conn->root; - char* p = root->buffer0; - char* end = p + sizeof(root->buffer0); - int wlen, ret; - uint32_t address = ntohl(conn->root->address.sin_addr.s_addr); - int port = ntohs(conn->root->address.sin_port); - - root->buffer_pos = 0; - root->buffer = p; - - p += snprintf(p, end-p, "CONNECT %d.%d.%d.%d:%d HTTP/" HTTP_VERSION "\r\n", - (address >> 24) & 0xff, (address >> 16) & 0xff, - (address >> 8) & 0xff, address & 0xff, port); - if (p >= end) goto Overflow; - - p += snprintf(p, end-p, "%.*s", service->footer_len, service->footer); - - if (p >= end) { - Overflow: - PROXY_LOG("%s: buffer overflow in proxy connection header\n", root->name); - return -1; - } - - root->buffer_len = (p - root->buffer); - - ret = connect( root->socket, - (struct sockaddr*) &service->server_addr, - sizeof(service->server_addr) ); - if (ret == 0) { - /* immediate connection ?? */ - conn->state = HTTP_SEND_HEADER; - PROXY_LOG("%s: immediate connection\n", root->name); - } - else { - if (socket_errno == EINPROGRESS || socket_errno == EWOULDBLOCK) { - conn->state = HTTP_CONNECTING; - PROXY_LOG("%s: connecting\n", conn->root->name); - } - else { - PROXY_LOG("%s: cannot connect to proxy: %s\n", root->name, strerror(errno)); - return -1; - } - } - return 0; -} - - -static unsigned -http_connection_select( HttpConnection* conn ) -{ - unsigned flags; - - switch (conn->state) { - case HTTP_RECEIVE_ANSWER_LINE1: - case HTTP_RECEIVE_ANSWER_LINE2: - flags = PROXY_SELECT_READ; - break; - - case HTTP_CONNECTING: - case HTTP_SEND_HEADER: - flags = PROXY_SELECT_WRITE; - break; - - default: - flags = 0; - }; - return flags; -} - -static void -http_connection_poll( HttpConnection* conn, - unsigned flags ) -{ - int ret; - ProxyConnection* root = conn->root; - - switch (conn->state) - { - case HTTP_CONNECTING: - PROXY_LOG("%s: connected to http proxy, sending header\n", root->name); - conn->state = HTTP_SEND_HEADER; - break; - - case HTTP_SEND_HEADER: - { - int ret = proxy_connection_send(root); - - if (ret < 0) { - proxy_connection_free( root, PROXY_EVENT_SERVER_ERROR ); - return; - } - if (ret == 0) - return; - - root->buffer_len = sizeof(root->buffer0); - root->buffer_pos = 0; - conn->state = HTTP_RECEIVE_ANSWER_LINE1; - PROXY_LOG("%s: header sent, receiving first answer line\n", root->name); - } - break; - - case HTTP_RECEIVE_ANSWER_LINE1: - case HTTP_RECEIVE_ANSWER_LINE2: - { - int ret = proxy_connection_receive_line(root); - - if (ret < 0) { - proxy_connection_free( root, PROXY_EVENT_SERVER_ERROR ); - return; - } - if (ret == 0) - return; - - if (conn->state == HTTP_RECEIVE_ANSWER_LINE1) { - int http1, http2, codenum; - - if ( sscanf(root->buffer, "HTTP/%d.%d %d", &http1, &http2, &codenum) != 3 ) { - PROXY_LOG( "%s: invalid answer from proxy: '%s'\n", - root->name, root->buffer ); - proxy_connection_free( root, PROXY_EVENT_SERVER_ERROR ); - return; - } - - /* success is 2xx */ - if (codenum/2 != 100) { - PROXY_LOG( "%s: connection refused, error=%d\n", - root->name, codenum ); - proxy_connection_free( root, PROXY_EVENT_CONNECTION_REFUSED ); - return; - } - PROXY_LOG("%s: receiving second answer line\n", root->name); - conn->state = HTTP_RECEIVE_ANSWER_LINE2; - root->buffer_pos = 0; - } else { - /* ok, we're connected */ - PROXY_LOG("%s: connection succeeded\n", root->name); - proxy_connection_free( root, PROXY_EVENT_CONNECTED ); - } - } - break; - - default: - PROXY_LOG("%s: invalid state for read event: %d\n", root->name, conn->state); - } -} - static void http_service_free( HttpService* service ) { - PROXY_LOG("%s\n", __FUNCTION__); + PROXY_LOG("%s", __FUNCTION__); if (service->footer != service->footer0) qemu_free(service->footer); qemu_free(service); @@ -212,11 +30,11 @@ http_service_free( HttpService* service ) static ProxyConnection* http_service_connect( HttpService* service, - int socket, + int sock_type, struct sockaddr_in* address ) { - HttpConnection* conn = qemu_mallocz(sizeof(*conn)); - int sock_type = socket_get_type(socket); + uint32_t addr; + int port; /* the HTTP proxy can only handle TCP connections */ if (sock_type != SOCK_STREAM) @@ -227,14 +45,25 @@ http_service_connect( HttpService* service, address->sin_port == service->server_addr.sin_port) return NULL; - proxy_connection_init( conn->root, socket, address, service->root ); - - if ( http_connection_init( conn ) < 0 ) { - http_connection_free( conn ); - return NULL; + addr = ntohl(address->sin_addr.s_addr); + port = ntohs(address->sin_port); + + PROXY_LOG("%s: trying to connect to %d.%d.%d.%d on port %d", + __FUNCTION__, + (addr >> 24) & 255, + (addr >> 16) & 255, + (addr >> 8) & 255, + addr & 255, + port ); + + if (port == 80) { + /* use the rewriter for HTTP */ + PROXY_LOG("%s: using HTTP rewriter", __FUNCTION__); + return http_rewriter_connect(service, address); + } else { + PROXY_LOG("%s: using HTTP rewriter", __FUNCTION__); + return http_connector_connect(service, address); } - - return conn->root; } @@ -256,7 +85,7 @@ proxy_http_setup( const char* servername, if (servernamelen < 0) servernamelen = strlen(servername); - PROXY_LOG( "%s: creating http proxy service connecting to: %.*s:%d\n", + PROXY_LOG( "%s: creating http proxy service connecting to: %.*s:%d", __FUNCTION__, servernamelen, servername, serverport ); /* resolve server address */ @@ -269,7 +98,7 @@ proxy_http_setup( const char* servername, /* create service object */ service = qemu_mallocz(sizeof(*service)); if (service == NULL) { - PROXY_LOG("%s: not enough memory to allocate new proxy service\n", __FUNCTION__); + PROXY_LOG("%s: not enough memory to allocate new proxy service", __FUNCTION__); return -1; } @@ -322,7 +151,7 @@ proxy_http_setup( const char* servername, wlen = proxy_base64_encode(user_pass, uplen, encoded, (int)sizeof(encoded)); if (wlen < 0) { - PROXY_LOG( "could not base64 encode '%.*s'\n", uplen, user_pass); + PROXY_LOG( "could not base64 encode '%.*s'", uplen, user_pass); goto FooterOverflow; } @@ -341,7 +170,7 @@ proxy_http_setup( const char* servername, if (p >= end) { FooterOverflow: - PROXY_LOG( "%s: buffer overflow when creating connection footer\n", + PROXY_LOG( "%s: buffer overflow when creating connection footer", __FUNCTION__); http_service_free(service); return -1; @@ -351,18 +180,16 @@ proxy_http_setup( const char* servername, service->footer_len = (p - service->footer); } - PROXY_LOG( "%s: creating HTTP Proxy Service Footer is (len=%d):\n'%.*s'\n", + PROXY_LOG( "%s: creating HTTP Proxy Service Footer is (len=%d):\n'%.*s'", __FUNCTION__, service->footer_len, service->footer_len, service->footer ); service->root->opaque = service; - service->root->serv_free = (ProxyServiceFreeFunc) http_service_free; - service->root->serv_connect = (ProxyServiceConnectFunc) http_service_connect; - service->root->conn_free = (ProxyConnectionFreeFunc) http_connection_free; - service->root->conn_select = (ProxyConnectionSelectFunc) http_connection_select; - service->root->conn_poll = (ProxyConnectionPollFunc) http_connection_poll; + service->root->serv_free = (ProxyServiceFreeFunc) http_service_free; + service->root->serv_connect = (ProxyServiceConnectFunc) http_service_connect; if (proxy_manager_add_service( service->root ) < 0) { + PROXY_LOG("%s: could not register service ?", __FUNCTION__); http_service_free(service); return -1; } diff --git a/proxy/proxy_http_connector.c b/proxy/proxy_http_connector.c new file mode 100644 index 0000000..1b2ba3e --- /dev/null +++ b/proxy/proxy_http_connector.c @@ -0,0 +1,213 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** 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. +*/ +#include "proxy_http_int.h" +#include +#include +#include +#include "vl.h" + +/* A HttpConnector implements a non-HTTP proxied connection + * through the CONNECT method. Many firewalls are configured + * to reject these for port 80, so these connections should + * use a HttpRewriter instead. + */ + +typedef enum { + STATE_NONE = 0, + STATE_CONNECTING, /* connecting to the server */ + STATE_SEND_HEADER, /* connected, sending header to the server */ + STATE_RECEIVE_ANSWER_LINE1, + STATE_RECEIVE_ANSWER_LINE2 /* connected, reading server's answer */ +} ConnectorState; + +typedef struct Connection { + ProxyConnection root[1]; + ConnectorState state; +} Connection; + + +static void +connection_free( ProxyConnection* root ) +{ + proxy_connection_done(root); + qemu_free(root); +} + + + +#define HTTP_VERSION "1.1" + +static int +connection_init( Connection* conn ) +{ + HttpService* service = (HttpService*) conn->root->service; + ProxyConnection* root = conn->root; + stralloc_t* str = root->str; + int ret; + uint32_t address = ntohl(root->address.sin_addr.s_addr); + int port = ntohs(root->address.sin_port); + + proxy_connection_rewind(root); + stralloc_add_format(str, "CONNECT %d.%d.%d.%d:%d HTTP/" HTTP_VERSION "\r\n", + (address >> 24) & 0xff, (address >> 16) & 0xff, + (address >> 8) & 0xff, address & 0xff, port); + + stralloc_add_bytes(str, service->footer, service->footer_len); + + do { + ret = connect( root->socket, + (struct sockaddr*) &service->server_addr, + sizeof(service->server_addr) ); + } while (ret < 0 && socket_errno == EINTR); + + if (ret == 0) { + /* immediate connection ?? */ + conn->state = STATE_SEND_HEADER; + PROXY_LOG("%s: immediate connection", root->name); + } + else { + if (socket_errno == EINPROGRESS || socket_errno == EWOULDBLOCK) { + conn->state = STATE_CONNECTING; + PROXY_LOG("%s: connecting", root->name); + } + else { + PROXY_LOG("%s: cannot connect to proxy: %s", root->name, socket_errstr()); + return -1; + } + } + return 0; +} + + +static void +connection_select( ProxyConnection* root, + ProxySelect* sel ) +{ + unsigned flags; + Connection* conn = (Connection*)root; + + switch (conn->state) { + case STATE_RECEIVE_ANSWER_LINE1: + case STATE_RECEIVE_ANSWER_LINE2: + flags = PROXY_SELECT_READ; + break; + + case STATE_CONNECTING: + case STATE_SEND_HEADER: + flags = PROXY_SELECT_WRITE; + break; + + default: + flags = 0; + }; + proxy_select_set(sel, root->socket, flags); +} + +static void +connection_poll( ProxyConnection* root, + ProxySelect* sel ) +{ + DataStatus ret = DATA_NEED_MORE; + Connection* conn = (Connection*)root; + int fd = root->socket; + + if (!proxy_select_poll(sel, fd)) + return; + + switch (conn->state) + { + case STATE_CONNECTING: + PROXY_LOG("%s: connected to http proxy, sending header", root->name); + conn->state = STATE_SEND_HEADER; + break; + + case STATE_SEND_HEADER: + ret = proxy_connection_send(root, fd); + if (ret == DATA_COMPLETED) { + conn->state = STATE_RECEIVE_ANSWER_LINE1; + PROXY_LOG("%s: header sent, receiving first answer line", root->name); + } + break; + + case STATE_RECEIVE_ANSWER_LINE1: + case STATE_RECEIVE_ANSWER_LINE2: + ret = proxy_connection_receive_line(root, root->socket); + if (ret == DATA_COMPLETED) { + if (conn->state == STATE_RECEIVE_ANSWER_LINE1) { + int http1, http2, codenum; + const char* line = root->str->s; + + if ( sscanf(line, "HTTP/%d.%d %d", &http1, &http2, &codenum) != 3 ) { + PROXY_LOG( "%s: invalid answer from proxy: '%s'", + root->name, line ); + ret = DATA_ERROR; + break; + } + + /* success is 2xx */ + if (codenum/2 != 100) { + PROXY_LOG( "%s: connection refused, error=%d", + root->name, codenum ); + proxy_connection_free( root, 0, PROXY_EVENT_CONNECTION_REFUSED ); + return; + } + PROXY_LOG("%s: receiving second answer line", root->name); + conn->state = STATE_RECEIVE_ANSWER_LINE2; + proxy_connection_rewind(root); + } else { + /* ok, we're connected */ + PROXY_LOG("%s: connection succeeded", root->name); + proxy_connection_free( root, 1, PROXY_EVENT_CONNECTED ); + } + } + break; + + default: + PROXY_LOG("%s: invalid state for read event: %d", root->name, conn->state); + } + + if (ret == DATA_ERROR) { + proxy_connection_free( root, 0, PROXY_EVENT_SERVER_ERROR ); + } +} + + + +ProxyConnection* +http_connector_connect( HttpService* service, + struct sockaddr_in* address ) +{ + Connection* conn; + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + return NULL; + + conn = qemu_mallocz(sizeof(*conn)); + if (conn == NULL) { + socket_close(s); + return NULL; + } + + proxy_connection_init( conn->root, s, address, service->root, + connection_free, + connection_select, + connection_poll ); + + if ( connection_init( conn ) < 0 ) { + connection_free( conn->root ); + return NULL; + } + + return conn->root; +} diff --git a/proxy/proxy_http_int.h b/proxy/proxy_http_int.h new file mode 100644 index 0000000..d0a6bc0 --- /dev/null +++ b/proxy/proxy_http_int.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** 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. +*/ +#ifndef _PROXY_HTTP_INT_H +#define _PROXY_HTTP_INT_H + +#include "proxy_http.h" +#include "proxy_int.h" + +/* the HttpService object */ +typedef struct HttpService { + ProxyService root[1]; + struct sockaddr_in server_addr; /* server address and port */ + char* footer; /* the footer contains the static parts of the */ + int footer_len; /* connection header, we generate it only once */ + char footer0[512]; +} HttpService; + +/* create a CONNECT connection (for port != 80) */ +extern ProxyConnection* http_connector_connect( + HttpService* service, + struct sockaddr_in* address ); + +/* create a HTTP rewriting connection (for port == 80) */ +extern ProxyConnection* http_rewriter_connect( + HttpService* service, + struct sockaddr_in* address ); + + +#endif /* _PROXY_HTTP_INT_H */ diff --git a/proxy/proxy_http_rewriter.c b/proxy/proxy_http_rewriter.c new file mode 100644 index 0000000..3e98557 --- /dev/null +++ b/proxy/proxy_http_rewriter.c @@ -0,0 +1,1132 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** 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. +*/ +#include "proxy_http_int.h" +#include "android_utils.h" +#include +#include +#include "vl.h" + +/* this implements a transparent HTTP rewriting proxy + * + * this is needed because the HTTP spec mandates that + * any query made to a proxy uses an absolute URI as + * in: + * + * GET http://www.example.com/index.html HTTP/1.1 + * + * while the Android browser will think it's talking to + * a normal web server and will issue a: + * + * GET /index.html HTTP/1.1 + * Host: www.example.com + * + * what we do here is thus the following: + * + * - read the request header + * - rewrite the request's URI to use absolute URI + * - send the rewritten header to the proxy + * - then read the rest of the request, and tunnel it to the + * proxy as well + * - read the answer as-is and send it back to the system + * + * this sounds all easy, but the rules for computing the + * sizes of HTTP Message Bodies makes the implementation + * a *bit* funky. + */ + +/* define D_ACTIVE to 1 to dump additionnal debugging + * info when -debug-proxy is used. These are only needed + * when debugging the proxy code. + */ +#define D_ACTIVE 1 + +#if D_ACTIVE +# define D(...) PROXY_LOG(__VA_ARGS__) +#else +# define D(...) ((void)0) +#endif + + +/** ************************************************************* + ** + ** HTTP HEADERS + ** + **/ + +typedef struct HttpHeader { + struct HttpHeader* next; + const char* key; + const char* value; +} HttpHeader; + +static void +http_header_free( HttpHeader* h ) +{ + if (h) { + qemu_free((char*)h->value); + qemu_free(h); + } +} + +static int +http_header_append( HttpHeader* h, const char* value ) +{ + int old = strlen(h->value); + int new = strlen(value); + char* s = realloc((char*)h->value, old+new+1); + if (s == NULL) + return -1; + memcpy(s + old, value, new+1); + h->value = (const char*)s; + return 0; +} + +static HttpHeader* +http_header_alloc( const char* key, const char* value ) +{ + int len = strlen(key)+1; + HttpHeader* h = malloc(sizeof(*h) + len+1); + if (h) { + h->next = NULL; + h->key = (const char*)(h+1); + memcpy( (char*)h->key, key, len ); + h->value = qemu_strdup(value); + } + return h; +} + +typedef struct { + HttpHeader* first; + HttpHeader* last; +} HttpHeaderList; + +static void +http_header_list_init( HttpHeaderList* l ) +{ + l->first = l->last = NULL; +} + +static void +http_header_list_done( HttpHeaderList* l ) +{ + while (l->first) { + HttpHeader* h = l->first; + l->first = h->next; + http_header_free(h); + } + l->last = NULL; +} + +static void +http_header_list_add( HttpHeaderList* l, + HttpHeader* h ) +{ + if (!l->first) { + l->first = h; + } else { + l->last->next = h; + } + h->next = NULL; + l->last = h; +} + +static const char* +http_header_list_find( HttpHeaderList* l, + const char* key ) +{ + HttpHeader* h; + for (h = l->first; h; h = h->next) + if (!strcasecmp(h->key, key)) + return h->value; + + return NULL; +} + +/** ************************************************************* + ** + ** HTTP REQUEST AND REPLY + ** + **/ + +typedef enum { + HTTP_REQUEST_UNSUPPORTED = 0, + HTTP_REQUEST_GET, + HTTP_REQUEST_HEAD, + HTTP_REQUEST_POST, + HTTP_REQUEST_PUT, + HTTP_REQUEST_DELETE, +} HttpRequestType; + +typedef struct { + HttpRequestType req_type; + char* req_method; + char* req_uri; + char* req_version; + char* rep_version; + int rep_code; + char* rep_readable; + HttpHeaderList headers[1]; +} HttpRequest; + + +static HttpRequest* +http_request_alloc( const char* method, + const char* uri, + const char* version ) +{ + HttpRequest* r = malloc(sizeof(*r)); + + r->req_method = qemu_strdup(method); + r->req_uri = qemu_strdup(uri); + r->req_version = qemu_strdup(version); + r->rep_version = NULL; + r->rep_code = -1; + r->rep_readable = NULL; + + if (!strcmp(method,"GET")) { + r->req_type = HTTP_REQUEST_GET; + } else if (!strcmp(method,"POST")) { + r->req_type = HTTP_REQUEST_POST; + } else if (!strcmp(method,"HEAD")) { + r->req_type = HTTP_REQUEST_HEAD; + } else if (!strcmp(method,"PUT")) { + r->req_type = HTTP_REQUEST_PUT; + } else if (!strcmp(method,"DELETE")) { + r->req_type = HTTP_REQUEST_DELETE; + } else + r->req_type = HTTP_REQUEST_UNSUPPORTED; + + http_header_list_init(r->headers); + return r; +} + +static void +http_request_replace_uri( HttpRequest* r, + const char* uri ) +{ + const char* old = r->req_uri; + r->req_uri = qemu_strdup(uri); + qemu_free((char*)old); +} + +static void +http_request_free( HttpRequest* r ) +{ + if (r) { + http_header_list_done(r->headers); + + qemu_free(r->req_method); + qemu_free(r->req_uri); + qemu_free(r->req_version); + qemu_free(r->rep_version); + qemu_free(r->rep_readable); + qemu_free(r); + } +} + +static char* +http_request_find_header( HttpRequest* r, + const char* key ) +{ + return (char*)http_header_list_find(r->headers, key); +} + + +static int +http_request_add_header( HttpRequest* r, + const char* key, + const char* value ) +{ + HttpHeader* h = http_header_alloc(key,value); + if (h) { + http_header_list_add(r->headers, h); + return 0; + } + return -1; +} + +static int +http_request_add_to_last_header( HttpRequest* r, + const char* line ) +{ + if (r->headers->last) { + return http_header_append( r->headers->last, line ); + } else { + return -1; + } +} + +static int +http_request_set_reply( HttpRequest* r, + const char* version, + const char* code, + const char* readable ) +{ + if (strcmp(version,"HTTP/1.0") && strcmp(version,"HTTP/1.1")) { + PROXY_LOG("%s: bad reply protocol: %s", __FUNCTION__, version); + return -1; + } + r->rep_code = atoi(code); + if (r->rep_code == 0) { + PROXY_LOG("%s: bad reply code: %d", __FUNCTION__, code); + return -1; + } + + r->rep_version = qemu_strdup(version); + r->rep_readable = qemu_strdup(readable); + + /* reset the list of headers */ + http_header_list_done(r->headers); + return 0; +} + +/** ************************************************************* + ** + ** REWRITER CONNECTION + ** + **/ + +typedef enum { + STATE_CONNECTING = 0, + STATE_CREATE_SOCKET_PAIR, + STATE_REQUEST_FIRST_LINE, + STATE_REQUEST_HEADERS, + STATE_REQUEST_SEND, + STATE_REQUEST_BODY, + STATE_REPLY_FIRST_LINE, + STATE_REPLY_HEADERS, + STATE_REPLY_SEND, + STATE_REPLY_BODY, +} ConnectionState; + +/* root->socket is connected to the proxy server. while + * slirp_fd is connected to the slirp code through a + * socket_pair() we created for this specific purpose. + */ + +typedef enum { + BODY_NONE = 0, + BODY_KNOWN_LENGTH, + BODY_UNTIL_CLOSE, + BODY_CHUNKED, + BODY_MODE_MAX +} BodyMode; + +static const char* const body_mode_str[BODY_MODE_MAX] = { + "NONE", "KNOWN_LENGTH", "UNTIL_CLOSE", "CHUNKED" +}; + +typedef struct { + ProxyConnection root[1]; + int slirp_fd; + ConnectionState state; + HttpRequest* request; + BodyMode body_mode; + int64_t body_length; + int64_t body_total; + int64_t body_sent; + int64_t chunk_length; + int64_t chunk_total; + char body_has_data; + char body_is_full; + char body_is_closed; + char parse_chunk_header; + char parse_chunk_trailer; +} RewriteConnection; + + +static void +rewrite_connection_free( ProxyConnection* root ) +{ + RewriteConnection* conn = (RewriteConnection*)root; + + if (conn->slirp_fd >= 0) { + socket_close(conn->slirp_fd); + conn->slirp_fd = -1; + } + http_request_free(conn->request); + proxy_connection_done(root); + qemu_free(conn); +} + + +static int +rewrite_connection_init( RewriteConnection* conn ) +{ + HttpService* service = (HttpService*) conn->root->service; + ProxyConnection* root = conn->root; + int ret; + + conn->slirp_fd = -1; + conn->state = STATE_CONNECTING; + + do { + ret = connect( root->socket, + (struct sockaddr*) &service->server_addr, + sizeof(service->server_addr) ); + } while (ret < 0 && socket_errno == EINTR); + + if (ret < 0) { + if (socket_errno == EINPROGRESS || socket_errno == EWOULDBLOCK) { + PROXY_LOG("%s: connecting", conn->root->name); + } + else { + PROXY_LOG("%s: cannot connect to proxy: %s", root->name, socket_errstr()); + return -1; + } + } + else { + PROXY_LOG("%s: immediate connection", root->name); + conn->state = STATE_CREATE_SOCKET_PAIR; + } + return 0; +} + +static int +rewrite_connection_create_sockets( RewriteConnection* conn ) +{ + /* immediate connection to the proxy. now create a socket + * pair and send a 'success' event to slirp */ + int slirp_1; + ProxyConnection* root = conn->root; + + if (socket_pair( &slirp_1, &conn->slirp_fd ) < 0) { + PROXY_LOG("%s: coult not create socket pair: %s", + root->name, socket_errstr()); + return -1; + } + + root->ev_func( root->ev_opaque, slirp_1, PROXY_EVENT_CONNECTED ); + conn->state = STATE_REQUEST_FIRST_LINE; + return 0; +} + + +/* read the first line of a given HTTP request. returns -1/0/+1 */ +static DataStatus +rewrite_connection_read_request( RewriteConnection* conn ) +{ + ProxyConnection* root = conn->root; + DataStatus ret; + + ret = proxy_connection_receive_line(root, conn->slirp_fd); + if (ret == DATA_COMPLETED) { + /* now parse the first line to see if we can handle it */ + char* line = root->str->s; + char* method; + char* uri; + char* version; + char* p = line; + + method = strsep(&p, " "); + if (p == NULL) { + PROXY_LOG("%s: can't parse method in '%'", + root->name, line); + return DATA_ERROR; + } + uri = strsep(&p, " "); + if (p == NULL) { + PROXY_LOG( "%s: can't parse URI in '%s'", + root->name, line); + return DATA_ERROR; + } + version = strsep(&p, " "); + if (p != NULL) { + PROXY_LOG( "%s: extra data after version in '%s'", + root->name, line); + return DATA_ERROR; + } + if (conn->request) + http_request_free(conn->request); + + conn->request = http_request_alloc( method, uri, version ); + if (!conn->request) + return DATA_ERROR; + + proxy_connection_rewind(root); + } + return ret; +} + + +static DataStatus +rewrite_connection_read_reply( RewriteConnection* conn ) +{ + ProxyConnection* root = conn->root; + DataStatus ret; + + ret = proxy_connection_receive_line( root, root->socket ); + if (ret == DATA_COMPLETED) { + HttpRequest* request = conn->request; + + char* line = stralloc_cstr( root->str ); + char* p = line; + char* protocol; + char* number; + char* readable; + + protocol = strsep(&p, " "); + if (p == NULL) { + PROXY_LOG("%s: can't parse response protocol: '%s'", + root->name, line); + return DATA_ERROR; + } + number = strsep(&p, " "); + if (p == NULL) { + PROXY_LOG("%s: can't parse response number: '%s'", + root->name, line); + return DATA_ERROR; + } + readable = p; + + if (http_request_set_reply(request, protocol, number, readable) < 0) + return DATA_ERROR; + + proxy_connection_rewind(root); + } + return ret; +} + + +static DataStatus +rewrite_connection_read_headers( RewriteConnection* conn, + int fd ) +{ + int ret; + ProxyConnection* root = conn->root; + + for (;;) { + char* line; + stralloc_t* str = root->str; + + ret = proxy_connection_receive_line(root, fd); + if (ret != DATA_COMPLETED) + break; + + str->n = 0; + line = str->s; + + if (line[0] == 0) { + /* an empty line means the end of headers */ + ret = 1; + break; + } + + /* it this a continuation ? */ + if (line[0] == ' ' || line[0] == '\t') { + ret = http_request_add_to_last_header( conn->request, line ); + } + else { + char* key; + char* value; + + value = line; + key = strsep(&value, ":"); + if (value == NULL) { + PROXY_LOG("%s: can't parse header '%s'", root->name, line); + ret = -1; + break; + } + value += strspn(value, " "); + if (http_request_add_header(conn->request, key, value) < 0) + ret = -1; + } + if (ret == DATA_ERROR) + break; + } + return ret; +} + +static int +rewrite_connection_rewrite_request( RewriteConnection* conn ) +{ + ProxyConnection* root = conn->root; + HttpService* service = (HttpService*) root->service; + HttpRequest* r = conn->request; + stralloc_t* str = root->str; + HttpHeader* h; + + proxy_connection_rewind(conn->root); + + /* only rewrite the URI if it is not absolute */ + if (r->req_uri[0] == '/') { + char* host = http_request_find_header(r, "Host"); + if (host == NULL) { + PROXY_LOG("%s: uh oh, not Host: in request ?", root->name); + } else { + /* now create new URI */ + stralloc_add_str(str, "http://"); + stralloc_add_str(str, host); + stralloc_add_str(str, r->req_uri); + http_request_replace_uri(r, stralloc_cstr(str)); + proxy_connection_rewind(root); + } + } + + stralloc_format( str, "%s %s %s\r\n", r->req_method, r->req_uri, r->req_version ); + for (h = r->headers->first; h; h = h->next) { + stralloc_add_format( str, "%s: %s\r\n", h->key, h->value ); + } + /* add the service's footer - includes final \r\n */ + stralloc_add_bytes( str, service->footer, service->footer_len ); + + return 0; +} + +static int +rewrite_connection_rewrite_reply( RewriteConnection* conn ) +{ + HttpRequest* r = conn->request; + ProxyConnection* root = conn->root; + stralloc_t* str = root->str; + HttpHeader* h; + + proxy_connection_rewind(root); + stralloc_format(str, "%s %d %s\r\n", r->rep_version, r->rep_code, r->rep_readable); + for (h = r->headers->first; h; h = h->next) { + stralloc_add_format(str, "%s: %s\r\n", h->key, h->value); + } + stralloc_add_str(str, "\r\n"); + + return 0; +} + + +static int +rewrite_connection_get_body_length( RewriteConnection* conn, + int is_request ) +{ + HttpRequest* r = conn->request; + ProxyConnection* root = conn->root; + char* content_length; + char* transfer_encoding; + + conn->body_mode = BODY_NONE; + conn->body_length = 0; + conn->body_total = 0; + conn->body_sent = 0; + conn->body_is_closed = 0; + conn->body_is_full = 0; + conn->body_has_data = 0; + + proxy_connection_rewind(root); + + if (is_request) { + /* only POST and PUT should have a body */ + if (r->req_type != HTTP_REQUEST_POST && + r->req_type != HTTP_REQUEST_PUT) + { + return 0; + } + } else { + /* HTTP 1.1 Section 4.3 Message Body states that HEAD requests must not have + * a message body, as well as any 1xx, 204 and 304 replies */ + if (r->req_type == HTTP_REQUEST_HEAD || r->rep_code/100 == 1 || + r->rep_code == 204 || r->rep_code == 304) + return 0; + } + + content_length = http_request_find_header(r, "Content-Length"); + if (content_length != NULL) { + char* end; + int64_t body_len = strtoll( content_length, &end, 10 ); + if (*end != '\0' || *content_length == '\0' || body_len < 0) { + PROXY_LOG("%s: bad content length: %s", root->name, content_length); + return DATA_ERROR; + } + if (body_len > 0) { + conn->body_mode = BODY_KNOWN_LENGTH; + conn->body_length = body_len; + } + } else { + char* connection = http_request_find_header(r, "Proxy-Connection"); + + if (!connection) + connection = http_request_find_header(r, "Connection"); + + if (!connection || strcasecmp(connection, "Close")) { + /* hum, we can't support this at all */ + PROXY_LOG("%s: can't determine content length, and client wants" + " to keep connection opened", + root->name); + return -1; + } + /* a negative value means that the data ends when the client + * disconnects the connection. + */ + conn->body_mode = BODY_UNTIL_CLOSE; + } + transfer_encoding = http_request_find_header(r, "Transfer-Encoding"); + if (transfer_encoding && !strcasecmp(transfer_encoding, "Chunked")) { + conn->body_mode = BODY_CHUNKED; + conn->parse_chunk_header = 0; + conn->parse_chunk_trailer = 0; + conn->chunk_length = -1; + conn->chunk_total = 0; + } + D("%s: body_length=%lld body_mode=%s", + root->name, conn->body_length, + body_mode_str[conn->body_mode]); + + proxy_connection_rewind(root); + return 0; +} + +#define MAX_BODY_BUFFER 65536 + +static DataStatus +rewrite_connection_read_body( RewriteConnection* conn, int fd ) +{ + ProxyConnection* root = conn->root; + stralloc_t* str = root->str; + int wanted = 0, current, avail; + DataStatus ret; + + if (conn->body_is_closed) { + return DATA_NEED_MORE; + } + + /* first, determine how many bytes we want to read. */ + switch (conn->body_mode) { + case BODY_NONE: + D("%s: INTERNAL ERROR: SHOULDN'T BE THERE", root->name); + return DATA_COMPLETED; + + case BODY_KNOWN_LENGTH: + { + if (conn->body_length == 0) + return DATA_COMPLETED; + + if (conn->body_length > MAX_BODY_BUFFER) + wanted = MAX_BODY_BUFFER; + else + wanted = (int)conn->body_length; + } + break; + + case BODY_UNTIL_CLOSE: + wanted = MAX_BODY_BUFFER; + break; + + case BODY_CHUNKED: + if (conn->chunk_length < 0) { + /* chunk_length < 0 means we need to read a chunk header */ + /* ensure that 'str' is flushed before doing this */ + if (!conn->parse_chunk_header) { + if (conn->body_has_data) + return DATA_NEED_MORE; + D("%s: waiting chunk header", root->name); + conn->parse_chunk_header = 1; + } + ret = proxy_connection_receive_line(root, fd); + if (ret == DATA_COMPLETED) { + char* line = str->s; + char* end; + long long length; + + length = strtoll(line, &end, 16); + if (line[0] == ' ' || (end[0] != '\0' && end[0] != ';')) { + PROXY_LOG("%s: invalid chunk header: %s", + root->name, line); + return DATA_ERROR; + } + if (length < 0) { + PROXY_LOG("%s: invalid chunk length %lld", + root->name, length); + return DATA_ERROR; + } + conn->chunk_length = length; + conn->chunk_total = 0; + if (length == 0) { + /* the last chunk, no we need to add the trailer */ + conn->parse_chunk_trailer = 0; + } + conn->parse_chunk_header = 0; + } + } + + if (conn->chunk_length == 0) { + /* chunk_length == 0 means we're reading the chunk trailer */ + /* ensure that 'str' is flushed before reading the trailer */ + if (!conn->parse_chunk_trailer) { + if (conn->body_has_data) + return DATA_NEED_MORE; + conn->parse_chunk_trailer = 1; + } + ret = rewrite_connection_read_headers(conn, fd); + if (ret == DATA_COMPLETED) { + conn->body_is_closed = 1; + } + return ret; + } + + /* if we get here, body_length > 0 */ + if (conn->chunk_length > MAX_BODY_BUFFER) + wanted = MAX_BODY_BUFFER; + else + wanted = (int)conn->chunk_length; + break; + + default: + ; + } + + /* we don't want more than MAX_BODY_BUFFER bytes in the + * buffer we used to pass the body */ + current = str->n; + avail = MAX_BODY_BUFFER - current; + if (avail <= 0) { + /* wait for some flush */ + conn->body_is_full = 1; + D("%s: waiting to flush %d bytes", + root->name, current); + return DATA_NEED_MORE; + } + + if (wanted > avail) + wanted = avail; + + ret = proxy_connection_receive(root, fd, wanted); + conn->body_has_data = (str->n > 0); + conn->body_is_full = (str->n == MAX_BODY_BUFFER); + + if (ret == DATA_ERROR) { + if (conn->body_mode == BODY_UNTIL_CLOSE) { + /* a disconnection here is normal and signals the + * end of the body */ + conn->body_total += root->str_recv; + D("%s: body completed by close (%lld bytes)", + root->name, conn->body_total); + conn->body_is_closed = 1; + ret = DATA_COMPLETED; + } + } else { + avail = root->str_recv; + ret = DATA_NEED_MORE; /* we're not really done yet */ + + switch (conn->body_mode) { + case BODY_CHUNKED: + conn->chunk_total += avail; + conn->chunk_length -= avail; + + if (conn->chunk_length == 0) { + D("%s: chunk completed (%lld bytes)", + root->name, conn->chunk_length); + conn->body_total += conn->chunk_total; + conn->chunk_total = 0; + conn->chunk_length = -1; + } + break; + + case BODY_KNOWN_LENGTH: + conn->body_length -= avail; + conn->body_total += avail; + + if (conn->body_length == 0) { + D("%s: body completed (%lld bytes)", + root->name, conn->body_total); + conn->body_is_closed = 1; + ret = DATA_COMPLETED; + } + break; + + case BODY_UNTIL_CLOSE: + conn->body_total += avail; + break; + + default: + ; + } + } + return ret; +} + +static DataStatus +rewrite_connection_send_body( RewriteConnection* conn, int fd ) +{ + ProxyConnection* root = conn->root; + stralloc_t* str = root->str; + DataStatus ret = DATA_NEED_MORE; + + if (conn->body_has_data) { + ret = proxy_connection_send(root, fd); + if (ret != DATA_ERROR) { + int pos = root->str_pos; + + memmove(str->s, str->s+pos, str->n-pos); + str->n -= pos; + root->str_pos = 0; + conn->body_is_full = (str->n == MAX_BODY_BUFFER); + conn->body_has_data = (str->n > 0); + conn->body_sent += root->str_sent; + + /* ensure that we return DATA_COMPLETED only when + * we have sent everything, and there is no more + * body pieces to read */ + if (ret == DATA_COMPLETED) { + if (!conn->body_is_closed || conn->body_has_data) + ret = DATA_NEED_MORE; + else { + D("%s: sent all body (%lld bytes)", + root->name, conn->body_sent); + } + } + D("%s: sent closed=%d data=%d n=%d ret=%d", + root->name, conn->body_is_closed, + conn->body_has_data, str->n, + ret); + } + } + return ret; +} + + +static void +rewrite_connection_select( ProxyConnection* root, + ProxySelect* sel ) +{ + RewriteConnection* conn = (RewriteConnection*)root; + int slirp = conn->slirp_fd; + int proxy = root->socket; + + switch (conn->state) { + case STATE_CONNECTING: + case STATE_CREATE_SOCKET_PAIR: + /* try to connect to the proxy server */ + proxy_select_set( sel, proxy, PROXY_SELECT_WRITE ); + break; + + case STATE_REQUEST_FIRST_LINE: + case STATE_REQUEST_HEADERS: + proxy_select_set( sel, slirp, PROXY_SELECT_READ ); + break; + + case STATE_REQUEST_SEND: + proxy_select_set( sel, proxy, PROXY_SELECT_WRITE ); + break; + + case STATE_REQUEST_BODY: + if (!conn->body_is_closed && !conn->body_is_full) + proxy_select_set( sel, slirp, PROXY_SELECT_READ ); + + if (conn->body_has_data) + proxy_select_set( sel, proxy, PROXY_SELECT_WRITE ); + break; + + case STATE_REPLY_FIRST_LINE: + case STATE_REPLY_HEADERS: + proxy_select_set( sel, proxy, PROXY_SELECT_READ ); + break; + + case STATE_REPLY_SEND: + proxy_select_set( sel, slirp, PROXY_SELECT_WRITE ); + break; + + case STATE_REPLY_BODY: + if (conn->body_has_data) + proxy_select_set( sel, slirp, PROXY_SELECT_WRITE ); + + if (!conn->body_is_closed && !conn->body_is_full) + proxy_select_set( sel, proxy, PROXY_SELECT_READ ); + break; + default: + ; + }; +} + +static void +rewrite_connection_poll( ProxyConnection* root, + ProxySelect* sel ) +{ + RewriteConnection* conn = (RewriteConnection*)root; + + int slirp = conn->slirp_fd; + int proxy = root->socket; + int has_slirp = proxy_select_poll(sel, slirp); + int has_proxy = proxy_select_poll(sel, proxy); + DataStatus ret = DATA_NEED_MORE; + + switch (conn->state) { + case STATE_CONNECTING: + if (has_proxy) { + PROXY_LOG("%s: connected to proxy", root->name); + conn->state = STATE_CREATE_SOCKET_PAIR; + } + break; + + case STATE_CREATE_SOCKET_PAIR: + if (has_proxy) { + if (rewrite_connection_create_sockets(conn) < 0) { + ret = DATA_ERROR; + } else { + D("%s: socket pair created", root->name); + conn->state = STATE_REQUEST_FIRST_LINE; + } + } + break; + + case STATE_REQUEST_FIRST_LINE: + if (has_slirp) { + ret = rewrite_connection_read_request(conn); + if (ret == DATA_COMPLETED) { + PROXY_LOG("%s: request first line ok", root->name); + conn->state = STATE_REQUEST_HEADERS; + } + } + break; + + case STATE_REQUEST_HEADERS: + if (has_slirp) { + ret = rewrite_connection_read_headers(conn, slirp); + if (ret == DATA_COMPLETED) { + PROXY_LOG("%s: request headers ok", root->name); + if (rewrite_connection_rewrite_request(conn) < 0) + ret = DATA_ERROR; + else + conn->state = STATE_REQUEST_SEND; + } + } + break; + + case STATE_REQUEST_SEND: + if (has_proxy) { + ret = proxy_connection_send(root, proxy); + if (ret == DATA_COMPLETED) { + if (rewrite_connection_get_body_length(conn, 1) < 0) { + ret = DATA_ERROR; + } else if (conn->body_mode != BODY_NONE) { + PROXY_LOG("%s: request sent, waiting for body", + root->name); + conn->state = STATE_REQUEST_BODY; + } else { + PROXY_LOG("%s: request sent, waiting for reply", + root->name); + conn->state = STATE_REPLY_FIRST_LINE; + } + } + } + break; + + case STATE_REQUEST_BODY: + if (has_slirp) { + ret = rewrite_connection_read_body(conn, slirp); + } + if (ret != DATA_ERROR && has_proxy) { + ret = rewrite_connection_send_body(conn, proxy); + if (ret == DATA_COMPLETED) { + PROXY_LOG("%s: request body ok, waiting for reply", + root->name); + conn->state = STATE_REPLY_FIRST_LINE; + } + } + break; + + case STATE_REPLY_FIRST_LINE: + if (has_proxy) { + ret = rewrite_connection_read_reply(conn); + if (ret == DATA_COMPLETED) { + PROXY_LOG("%s: reply first line ok", root->name); + conn->state = STATE_REPLY_HEADERS; + } + } + break; + + case STATE_REPLY_HEADERS: + if (has_proxy) { + ret = rewrite_connection_read_headers(conn, proxy); + if (ret == DATA_COMPLETED) { + PROXY_LOG("%s: reply headers ok", root->name); + if (rewrite_connection_rewrite_reply(conn) < 0) + ret = DATA_ERROR; + else + conn->state = STATE_REPLY_SEND; + } + } + break; + + case STATE_REPLY_SEND: + if (has_slirp) { + ret = proxy_connection_send(conn->root, slirp); + if (ret == DATA_COMPLETED) { + if (rewrite_connection_get_body_length(conn, 0) < 0) { + ret = DATA_ERROR; + } else if (conn->body_mode != BODY_NONE) { + PROXY_LOG("%s: reply sent, waiting for body", + root->name); + conn->state = STATE_REPLY_BODY; + } else { + PROXY_LOG("%s: reply sent, looping to waiting request", + root->name); + conn->state = STATE_REQUEST_FIRST_LINE; + } + } + } + break; + + case STATE_REPLY_BODY: + if (has_proxy) { + ret = rewrite_connection_read_body(conn, proxy); + } + if (ret != DATA_ERROR && has_slirp) { + ret = rewrite_connection_send_body(conn, slirp); + if (ret == DATA_COMPLETED) { + if (conn->body_mode == BODY_UNTIL_CLOSE) { + PROXY_LOG("%s: closing connection", root->name); + ret = DATA_ERROR; + } else { + PROXY_LOG("%s: reply body ok, looping to waiting request", + root->name); + conn->state = STATE_REQUEST_FIRST_LINE; + } + } + } + break; + + default: + ; + } + if (ret == DATA_ERROR) + proxy_connection_free(root, 0, PROXY_EVENT_NONE); + + return; +} + + +ProxyConnection* +http_rewriter_connect( HttpService* service, + struct sockaddr_in* address ) +{ + RewriteConnection* conn; + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + return NULL; + + conn = qemu_mallocz(sizeof(*conn)); + if (conn == NULL) { + socket_close(s); + return NULL; + } + + proxy_connection_init( conn->root, s, address, service->root, + rewrite_connection_free, + rewrite_connection_select, + rewrite_connection_poll ); + + if ( rewrite_connection_init( conn ) < 0 ) { + rewrite_connection_free( conn->root ); + return NULL; + } + + return conn->root; +} diff --git a/proxy/proxy_int.h b/proxy/proxy_int.h index 9d91169..e71d9d6 100644 --- a/proxy/proxy_int.h +++ b/proxy/proxy_int.h @@ -14,6 +14,7 @@ #include "proxy_common.h" #include "sockets.h" +#include "android_utils.h" extern int proxy_log; @@ -24,11 +25,44 @@ proxy_LOG(const char* fmt, ...); do { if (proxy_log) proxy_LOG(__VA_ARGS__); } while (0) +/* ProxySelect is used to handle events */ + +enum { + PROXY_SELECT_READ = (1 << 0), + PROXY_SELECT_WRITE = (1 << 1), + PROXY_SELECT_ERROR = (1 << 2) +}; + +typedef struct { + int* pcount; + fd_set* reads; + fd_set* writes; + fd_set* errors; +} ProxySelect; + +extern void proxy_select_set( ProxySelect* sel, + int fd, + unsigned flags ); + +extern unsigned proxy_select_poll( ProxySelect* sel, int fd ); + + /* sockets proxy manager internals */ typedef struct ProxyConnection ProxyConnection; typedef struct ProxyService ProxyService; +/* free a given proxified connection */ +typedef void (*ProxyConnectionFreeFunc) ( ProxyConnection* conn ); + +/* modify the ProxySelect to tell which events to listen to */ +typedef void (*ProxyConnectionSelectFunc) ( ProxyConnection* conn, + ProxySelect* sel ); + +/* action a proxy connection when select() returns certain events for its socket */ +typedef void (*ProxyConnectionPollFunc) ( ProxyConnection* conn, + ProxySelect* sel ); + /* root ProxyConnection object */ struct ProxyConnection { @@ -42,51 +76,93 @@ struct ProxyConnection { /* the following is useful for all types of services */ char name[64]; /* for debugging purposes */ - int buffer_pos; - int buffer_len; - char* buffer; - char buffer0[ 1024 ]; - /* rest of data depend on ProxyService */ + stralloc_t str[1]; /* network buffer (dynamic) */ + int str_pos; /* see proxy_connection_send() */ + int str_sent; /* see proxy_connection_send() */ + int str_recv; /* see proxy_connection_receive() */ + + /* connection methods */ + ProxyConnectionFreeFunc conn_free; + ProxyConnectionSelectFunc conn_select; + ProxyConnectionPollFunc conn_poll; + + /* rest of data depend on exact implementation */ }; extern void -proxy_connection_init( ProxyConnection* conn, - int socket, - struct sockaddr_in* address, - ProxyService* service ); +proxy_connection_init( ProxyConnection* conn, + int socket, + struct sockaddr_in* address, + ProxyService* service, + ProxyConnectionFreeFunc conn_free, + ProxyConnectionSelectFunc conn_select, + ProxyConnectionPollFunc conn_poll ); extern void proxy_connection_done( ProxyConnection* conn ); +/* free the proxy connection object. this will also + * close the corresponding socket unless the + * 'keep_alive' flag is set to TRUE. + */ extern void proxy_connection_free( ProxyConnection* conn, + int keep_alive, ProxyEvent event ); -/* tries to send data from the connection's buffer to the proxy. - * returns 1 when all data has been sent (i.e. buffer_pos == buffer_len), - * 0 if there is still some data to send, or -1 in case of error +/* status of data transfer operations */ +typedef enum { + DATA_ERROR = -1, + DATA_NEED_MORE = 0, + DATA_COMPLETED = 1 +} DataStatus; + +/* try to send data from the connection's buffer to a socket. + * starting from offset conn->str_pos in the buffer + * + * returns DATA_COMPLETED if everything could be written + * returns DATA_ERROR for a socket disconnection or error + * returns DATA_NEED_MORE if all data could not be sent. + * + * on exit, conn->str_sent contains the number of bytes + * that were really sent. conn->str_pos will be incremented + * by conn->str_sent as well. + * + * note that in case of success (DATA_COMPLETED), this also + * performs a proxy_connection_rewind which sets conn->str_pos + * to 0. */ -extern int -proxy_connection_send( ProxyConnection* conn ); - -/* tries to receive data from the connection's buffer from the proxy - * returns 1 when all data has been received (buffer_pos == buffer_len) - * returns 0 if there is still some data to receive - * returns -1 in case of error +extern DataStatus +proxy_connection_send( ProxyConnection* conn, int fd ); + +/* try to read 'wanted' bytes into conn->str from a socket + * + * returns DATA_COMPLETED if all bytes could be read + * returns DATA_NEED_MORE if not all bytes could be read + * returns DATA_ERROR in case of socket disconnection or error + * + * on exit, the amount of data received is in conn->str_recv */ -extern int -proxy_connection_receive( ProxyConnection* conn ); +extern DataStatus +proxy_connection_receive( ProxyConnection* conn, int fd, int wanted ); -/* tries to receive a line of text from the proxy +/* tries to receive a line of text from the proxy. + * when an entire line is read, the trailing \r\n is stripped + * and replaced by a terminating zero. str->n will be the + * lenght of the line, exclusing the terminating zero. * returns 1 when a line has been received * returns 0 if there is still some data to receive * returns -1 in case of error */ -extern int -proxy_connection_receive_line( ProxyConnection* conn ); +extern DataStatus +proxy_connection_receive_line( ProxyConnection* conn, int fd ); + +/* rewind the string buffer for a new operation */ +extern void +proxy_connection_rewind( ProxyConnection* conn ); /* base64 encode a source string, returns size of encoded result, * or -1 if there was not enough room in the destination buffer @@ -103,38 +179,19 @@ proxy_resolve_server( struct sockaddr_in* addr, /* a ProxyService is really a proxy server and associated options */ -enum { - PROXY_SELECT_READ = (1 << 0), - PROXY_SELECT_WRITE = (1 << 1), - PROXY_SELECT_ERROR = (1 << 2) -}; - /* destroy a given proxy service */ typedef void (*ProxyServiceFreeFunc) ( void* opaque ); /* tries to create a new proxified connection, returns NULL if the service can't * handle this address */ typedef ProxyConnection* (*ProxyServiceConnectFunc)( void* opaque, - int socket, + int socket_type, struct sockaddr_in* address ); -/* free a given proxified connection */ -typedef void (*ProxyConnectionFreeFunc) ( ProxyConnection* conn ); - -/* return flags corresponding to the select() events to wait to a proxified connection */ -typedef unsigned (*ProxyConnectionSelectFunc) ( ProxyConnection* conn ); - -/* action a proxy connection when select() returns certain events for its socket */ -typedef void (*ProxyConnectionPollFunc) ( ProxyConnection* conn, - unsigned select_flags ); - struct ProxyService { void* opaque; ProxyServiceFreeFunc serv_free; ProxyServiceConnectFunc serv_connect; - ProxyConnectionFreeFunc conn_free; - ProxyConnectionSelectFunc conn_select; - ProxyConnectionPollFunc conn_poll; }; extern int @@ -142,4 +199,3 @@ proxy_manager_add_service( ProxyService* service ); #endif /* _PROXY_INT_H */ - -- cgit v1.1