diff --git a/changelog b/changelog index 0ea7bba8..a7eac226 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,41 @@ Changelog --------- +User api additions +------------------ + +The info struct gained two new members + + - max_http_header_data: 0 for default (1024) or set the maximum amount of known + http header payload that lws can deal with. Payload in unknown http + headers is dropped silently. If for some reason you need to send huge + cookies or other HTTP-level headers, you can now increase this at context- + creation time. + + - max_http_header_pool: 0 for default (16) or set the maximum amount of http + headers that can be tracked by lws in this context. For the server, if + the header pool is completely in use then accepts on the listen socket + are disabled until one becomes free. For the client, if you simultaneously + have pending connects for more than this number of client connections, + additional connects will fail until some of the pending connections timeout + or complete. + +HTTP header processing in lws only exists until just after the first main +callback after the HTTP handshake... for ws connections that is ESTABLISHED and +for HTTP connections the HTTP callback. + +So these settings are not related to the maximum number of simultaneous +connections but the number of HTTP handshakes that may be expected or ongoing, +or have just completed, at one time. The reason it's useful is it changes the +memory allocation for header processing to be one-time at context creation +instead of every time there is a new connection, and gives you control over +the peak allocation. + +Setting max_http_header_pool to 1 is fine it will just queue incoming +connections before the accept as necessary, you can still have as many +simultaneous post-header connections as you like. + + v1.6.0-chrome48-firefox42 ======================= diff --git a/lib/client-handshake.c b/lib/client-handshake.c index 43580460..77915ce5 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -303,7 +303,7 @@ lws_client_connect_2(struct lws *wsi) return wsi; oom4: - lws_free(wsi->u.hdr.ah); + lws_free_header_table(wsi); lws_free(wsi); return NULL; @@ -423,10 +423,10 @@ lws_client_connect(struct lws_context *context, const char *address, } lwsl_client("lws_client_connect: direct conn\n"); - return lws_client_connect_2(wsi); + return lws_client_connect_2(wsi); bail1: - lws_free(wsi->u.hdr.ah); + lws_free_header_table(wsi); bail: lws_free(wsi); diff --git a/lib/client.c b/lib/client.c index d9a1af37..86ce7223 100644 --- a/lib/client.c +++ b/lib/client.c @@ -724,12 +724,10 @@ check_accept: goto bail2; /* clear his proxy connection timeout */ - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* free up his parsing allocations */ - - lws_free(wsi->u.hdr.ah); + lws_free_header_table(wsi); lws_union_transition(wsi, LWSCM_WS_CLIENT); wsi->state = LWSS_ESTABLISHED; @@ -809,7 +807,7 @@ bail2: lwsl_info("closing connection due to bail2 connection error\n"); /* free up his parsing allocations */ - lws_free_set_NULL(wsi->u.hdr.ah); + lws_free_header_table(wsi); lws_close_free_wsi(wsi, close_reason); return 1; diff --git a/lib/context.c b/lib/context.c index 757fdd34..436216a7 100644 --- a/lib/context.c +++ b/lib/context.c @@ -80,6 +80,7 @@ lws_create_context(struct lws_context_creation_info *info) int pid_daemon = get_daemonize_pid(); #endif char *p; + int n; lwsl_notice("Initial logging level %d\n", log_level); @@ -150,11 +151,42 @@ lws_create_context(struct lws_context_creation_info *info) context->lws_ev_sigint_cb = &lws_sigint_cb; #endif /* LWS_USE_LIBEV */ - /* to reduce this allocation, */ + lwsl_info(" mem: context: %5u bytes\n", sizeof(struct lws_context)); + + /* + * allocate and initialize the pool of + * allocated_header structs + data + */ + if (info->max_http_header_data) + context->max_http_header_data = info->max_http_header_data; + else + context->max_http_header_data = LWS_MAX_HEADER_LEN; + if (info->max_http_header_pool) + context->max_http_header_pool = info->max_http_header_pool; + else + context->max_http_header_pool = LWS_MAX_HEADER_POOL; + + context->http_header_data = lws_malloc(context->max_http_header_data * + context->max_http_header_pool); + if (!context->http_header_data) + goto bail; + context->ah_pool = lws_zalloc(sizeof(struct allocated_headers) * + context->max_http_header_pool); + if (!context->ah_pool) + goto bail; + + for (n = 0; n < context->max_http_header_pool; n++) + context->ah_pool[n].data = (char *)context->http_header_data + + (n * context->max_http_header_data); + + /* this is per context */ + lwsl_info(" mem: http hdr rsvd: %5u bytes ((%u + %u) x %u)\n", + (context->max_http_header_data + sizeof(struct allocated_headers)) * + context->max_http_header_pool, + context->max_http_header_data, sizeof(struct allocated_headers), + context->max_http_header_pool); + context->max_fds = getdtablesize(); - lwsl_notice(" ctx mem: %u bytes\n", sizeof(struct lws_context) + - ((sizeof(struct lws_pollfd) + sizeof(struct lws *)) * - context->max_fds)); context->fds = lws_zalloc(sizeof(struct lws_pollfd) * context->max_fds); if (context->fds == NULL) { @@ -162,6 +194,9 @@ lws_create_context(struct lws_context_creation_info *info) goto bail; } + lwsl_info(" mem: pollfd map: %5u\n", + sizeof(struct lws_pollfd) * context->max_fds); + if (lws_plat_init(context, info)) goto bail; @@ -169,8 +204,10 @@ lws_create_context(struct lws_context_creation_info *info) context->user_space = info->user; - strcpy(context->canonical_hostname, "unknown"); + lwsl_notice(" mem: per-conn: %5u bytes + protocol rx buf\n", + sizeof(struct lws)); + strcpy(context->canonical_hostname, "unknown"); lws_server_get_canonical_hostname(context, info); /* either use proxy from info, or try get it from env var */ @@ -188,9 +225,6 @@ lws_create_context(struct lws_context_creation_info *info) #endif } - lwsl_notice(" per-conn mem: %u + %u headers + protocol rx buf\n", - sizeof(struct lws), sizeof(struct allocated_headers)); - if (lws_context_init_server_ssl(info, context)) goto bail; @@ -311,9 +345,12 @@ lws_context_destroy(struct lws_context *context) lws_plat_context_early_destroy(context); lws_ssl_context_destroy(context); - if (context->fds) lws_free(context->fds); + if (context->ah_pool) + lws_free(context->ah_pool); + if (context->http_header_data) + lws_free(context->http_header_data); lws_plat_context_late_destroy(context); diff --git a/lib/hpack.c b/lib/hpack.c index f311d700..2c5b1d94 100644 --- a/lib/hpack.c +++ b/lib/hpack.c @@ -221,7 +221,7 @@ static int lws_frag_append(struct lws *wsi, unsigned char c) ah->data[ah->pos++] = c; ah->frags[ah->nfrag].len++; - return ah->pos >= sizeof(ah->data); + return ah->pos >= wsi->context->max_http_header_data; } static int lws_frag_end(struct lws *wsi) diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6e63e8ec..58a5e204 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -52,7 +52,19 @@ lws_free_wsi(struct lws *wsi) lws_free_set_NULL(wsi->rxflow_buffer); lws_free_set_NULL(wsi->trunc_alloc); - lws_free_header_table(wsi); + /* + * These union members have an ah at the start + * + * struct _lws_http_mode_related http; + * struct _lws_http2_related http2; + * struct _lws_header_related hdr; + * + * basically ws-related union member does not + */ + if (wsi->mode != LWSCM_WS_CLIENT && + wsi->mode != LWSCM_WS_SERVING) + if (wsi->u.hdr.ah) + lws_free_header_table(wsi); lws_free(wsi); } @@ -222,7 +234,6 @@ just_kill_connection: wsi->state = LWSS_DEAD_SOCKET; lws_free_set_NULL(wsi->rxflow_buffer); - lws_free_header_table(wsi); if (old_state == LWSS_ESTABLISHED || wsi->mode == LWSCM_WS_SERVING || @@ -912,6 +923,7 @@ lws_get_peer_write_allowance(struct lws *wsi) LWS_VISIBLE void lws_union_transition(struct lws *wsi, enum connection_mode mode) { + lwsl_debug("%s: %p: mode %d\n", __func__, wsi, mode); memset(&wsi->u, 0, sizeof(wsi->u)); wsi->mode = mode; } diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 4855cd91..861fc2bc 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1249,6 +1249,13 @@ struct lws_extension { * implementation for the one provided by provided_ssl_ctx. * Libwebsockets no longer is responsible for freeing the context * if this option is selected. + * @max_http_header_data: The max amount of header payload that can be handled + * in an http request (unrecognized header payload is dropped) + * @max_http_header_pool: The max number of connections with http headers that + * can be processed simultaneously (the corresponding memory is + * allocated for the lifetime of the context). If the pool is + * busy new incoming connections must wait for accept until one + * becomes free. */ struct lws_context_creation_info { @@ -1274,9 +1281,12 @@ struct lws_context_creation_info { #ifdef LWS_OPENSSL_SUPPORT SSL_CTX *provided_client_ssl_ctx; #else /* maintain structure layout either way */ - void *provided_client_ssl_ctx; + void *provided_client_ssl_ctx; #endif + short max_http_header_data; + short max_http_header_pool; + /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility * @@ -1285,7 +1295,7 @@ struct lws_context_creation_info { * was not built against the newer headers. */ - void *_unused[9]; + void *_unused[8]; }; LWS_VISIBLE LWS_EXTERN void diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c index 23c11b09..d704c7af 100644 --- a/lib/lws-plat-unix.c +++ b/lib/lws-plat-unix.c @@ -498,6 +498,9 @@ lws_plat_init(struct lws_context *context, return 1; } + lwsl_notice(" mem: platform fd map: %5u bytes\n", + sizeof(struct lws *) * context->max_fds); + context->fd_random = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); if (context->fd_random < 0) { lwsl_err("Unable to open random device %s %d\n", diff --git a/lib/parsers.c b/lib/parsers.c index e1de3804..ae6c1db6 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -60,13 +60,47 @@ int lextable_decode(int pos, char c) int lws_allocate_header_table(struct lws *wsi) { - /* Be sure to free any existing header data to avoid mem leak: */ - lws_free_header_table(wsi); - wsi->u.hdr.ah = lws_malloc(sizeof(*wsi->u.hdr.ah)); - if (wsi->u.hdr.ah == NULL) { - lwsl_err("Out of memory\n"); + struct lws_context *context = wsi->context; + int n; + + lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, + (void *)wsi->u.hdr.ah); + + /* if we are already bound to one, just clear it down */ + if (wsi->u.hdr.ah) + goto reset; + /* + * server should have suppressed the accept of a new wsi before this + * became the case. If initiating multiple client connects, make sure + * the ah pool is big enough to cope, or be prepared to retry + */ + if (context->ah_count_in_use == context->max_http_header_pool) { + lwsl_err("No free ah\n"); return -1; } + + for (n = 0; n < context->max_http_header_pool; n++) + if (!context->ah_pool[n].in_use) + break; + + /* if the count of in use said something free... */ + assert(n != context->max_http_header_pool); + + wsi->u.hdr.ah = &context->ah_pool[n]; + wsi->u.hdr.ah->in_use = 1; + + context->ah_count_in_use++; + /* if we used up all the ah, defeat accepting new server connections */ + if (context->ah_count_in_use == context->max_http_header_pool) + if (_lws_server_listen_accept_flow_control(context, 0)) + return 1; + + lwsl_debug("%s: wsi %p: ah %p: count %d (on exit)\n", + __func__, (void *)wsi, (void *)wsi->u.hdr.ah, + context->ah_count_in_use); + +reset: + /* init the ah to reflect no headers or data have appeared yet */ memset(wsi->u.hdr.ah->frag_index, 0, sizeof(wsi->u.hdr.ah->frag_index)); wsi->u.hdr.ah->nfrag = 0; wsi->u.hdr.ah->pos = 0; @@ -76,10 +110,31 @@ int lws_allocate_header_table(struct lws *wsi) int lws_free_header_table(struct lws *wsi) { - lws_free_set_NULL(wsi->u.hdr.ah); + struct lws_context *context = wsi->context; + + lwsl_debug("%s: wsi %p: ah %p (count = %d)\n", __func__, (void *)wsi, + (void *)wsi->u.hdr.ah, context->ah_count_in_use); + + assert(wsi->u.hdr.ah); + if (!wsi->u.hdr.ah) + return 0; + + /* if we think we're freeing one, there should be one to free */ + assert(context->ah_count_in_use > 0); + + assert(wsi->u.hdr.ah->in_use); + wsi->u.hdr.ah->in_use = 0; + + /* if we just freed up one ah, allow new server connection */ + if (context->ah_count_in_use == context->max_http_header_pool) + if (_lws_server_listen_accept_flow_control(context, 1)) + return 1; + + context->ah_count_in_use--; wsi->u.hdr.ah = NULL; + return 0; -}; +} LWS_VISIBLE int lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx) @@ -130,7 +185,7 @@ LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len, if (wsi->u.hdr.ah->frags[f].len >= len) return -1; - memcpy(dst, &wsi->u.hdr.ah->data[wsi->u.hdr.ah->frags[f].offset], + memcpy(dst, wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[f].offset, wsi->u.hdr.ah->frags[f].len); dst[wsi->u.hdr.ah->frags[f].len] = '\0'; @@ -167,7 +222,7 @@ char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) if (!n) return NULL; - return &wsi->u.hdr.ah->data[wsi->u.hdr.ah->frags[n].offset]; + return wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[n].offset; } int lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, @@ -186,7 +241,7 @@ int lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].nfrag = 0; do { - if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) { + if (wsi->u.hdr.ah->pos == wsi->context->max_http_header_data) { lwsl_err("Ran out of header data space\n"); return -1; } @@ -216,7 +271,7 @@ static int issue_char(struct lws *wsi, unsigned char c) { unsigned short frag_len; - if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) { + if (wsi->u.hdr.ah->pos == wsi->context->max_http_header_data) { lwsl_warn("excessive header content\n"); return -1; } @@ -236,8 +291,8 @@ static int issue_char(struct lws *wsi, unsigned char c) /* Insert a null character when we *hit* the limit: */ if (frag_len == wsi->u.hdr.current_token_limit) { wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = '\0'; - lwsl_warn("header %i exceeds limit\n", - wsi->u.hdr.parser_state); + lwsl_warn("header %i exceeds limit %d\n", + wsi->u.hdr.parser_state, wsi->u.hdr.current_token_limit); } return 1; @@ -538,7 +593,8 @@ swallow: context->token_limits->token_limit[ wsi->u.hdr.parser_state]; else - wsi->u.hdr.current_token_limit = sizeof(ah->data); + wsi->u.hdr.current_token_limit = + wsi->context->max_http_header_data; if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) goto set_parsing_complete; @@ -569,7 +625,7 @@ excessive: n = ah->frags[n].nfrag; ah->frags[n].nfrag = ah->nfrag; - if (ah->pos == sizeof(ah->data)) { + if (ah->pos == wsi->context->max_http_header_data) { lwsl_warn("excessive header content\n"); return -1; } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 57dd9f92..5eaecd80 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -283,6 +283,9 @@ extern "C" { #ifndef LWS_MAX_HEADER_LEN #define LWS_MAX_HEADER_LEN 1024 #endif +#ifndef LWS_MAX_HEADER_POOL +#define LWS_MAX_HEADER_POOL 16 +#endif #ifndef LWS_MAX_PROTOCOLS #define LWS_MAX_PROTOCOLS 5 #endif @@ -451,6 +454,46 @@ struct lws_fd_hashtable { }; #endif +/* + * This is totally opaque to code using the library. It's exported as a + * forward-reference pointer-only declaration; the user can use the pointer with + * other APIs to get information out of it. + */ + +struct lws_fragments { + unsigned short offset; + unsigned short len; + unsigned char nfrag; /* which ah->frag[] continues this content, or 0 */ +}; + +/* + * these are assigned from a pool held in the context. + * Both client and server mode uses them for http header analysis + */ + +struct allocated_headers { + unsigned char in_use; + unsigned char nfrag; + unsigned short pos; + /* + * for each recognized token, frag_index says which frag[] his data + * starts in (0 means the token did not appear) + * the actual header data gets dumped as it comes in, into data[] + */ + unsigned char frag_index[WSI_TOKEN_COUNT]; + /* + * the randomly ordered fragments, indexed by frag_index and + * lws_fragments->nfrag for continuation. + */ + struct lws_fragments frags[WSI_TOKEN_COUNT * 2]; + char *data; /* prepared by context init to point to dedicated storage */ + +#ifndef LWS_NO_CLIENT + char initial_handshake_hash_base64[30]; + unsigned short c_port; +#endif +}; + struct lws_context { #ifdef _WIN32 WSAEVENT *events; @@ -539,6 +582,11 @@ struct lws_context { #ifndef LWS_NO_SERVER struct lws *wsi_listening; #endif + short max_http_header_data; + short max_http_header_pool; + short ah_count_in_use; + void *http_header_data; + struct allocated_headers *ah_pool; }; enum { @@ -593,18 +641,6 @@ enum uri_esc_states { URIES_SEEN_PERCENT_H1, }; -/* - * This is totally opaque to code using the library. It's exported as a - * forward-reference pointer-only declaration; the user can use the pointer with - * other APIs to get information out of it. - */ - -struct lws_fragments { - unsigned short offset; - unsigned short len; - unsigned char nfrag; /* which ah->frag[] continues this content, or 0 */ -}; - /* notice that these union members: * * hdr @@ -617,28 +653,6 @@ struct lws_fragments { * used interchangeably to access the same data */ -struct allocated_headers { - unsigned char nfrag; - unsigned short pos; - /* - * for each recognized token, frag_index says which frag[] his data - * starts in (0 means the token did not appear) - * the actual header data gets dumped as it comes in, into data[] - */ - unsigned char frag_index[WSI_TOKEN_COUNT]; - /* - * the randomly ordered fragments, indexed by frag_index and - * lws_fragments->nfrag for continuation. - */ - struct lws_fragments frags[WSI_TOKEN_COUNT * 2]; - char data[LWS_MAX_HEADER_LEN]; - -#ifndef LWS_NO_CLIENT - char initial_handshake_hash_base64[30]; - unsigned short c_port; -#endif -}; - struct _lws_http_mode_related { /* MUST be first in struct */ struct allocated_headers *ah; /* mirroring _lws_header_related */ @@ -1104,7 +1118,7 @@ lws_change_pollfd(struct lws *wsi, int _and, int _or); #ifndef LWS_NO_SERVER int lws_context_init_server(struct lws_context_creation_info *info, - struct lws_context *context); + struct lws_context *context);; LWS_EXTERN int handshake_0405(struct lws_context *context, struct lws *wsi); LWS_EXTERN int diff --git a/lib/server.c b/lib/server.c index 7fa1bdff..aacdeb96 100644 --- a/lib/server.c +++ b/lib/server.c @@ -111,7 +111,6 @@ int lws_context_init_server(struct lws_context_creation_info *info, else info->port = ntohs(sin.sin_port); #endif - context->listen_port = info->port; wsi = lws_zalloc(sizeof(struct lws)); @@ -191,6 +190,8 @@ _lws_server_listen_accept_flow_control(struct lws_context *context, int on) if (!wsi) return 0; + lwsl_debug("%s: wsi %p: state %d\n", __func__, (void *)wsi, on); + if (on) n = lws_change_pollfd(wsi, 0, LWS_POLLIN); else @@ -321,7 +322,7 @@ int lws_http_action(struct lws *wsi) } /* now drop the header info we kept a pointer to */ - lws_free_set_NULL(wsi->u.http.ah); + lws_free_header_table(wsi); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); @@ -343,8 +344,7 @@ int lws_http_action(struct lws *wsi) return 0; bail_nuke_ah: - /* drop the header info */ - lws_free_set_NULL(wsi->u.hdr.ah); + lws_free_header_table(wsi); return 1; } @@ -388,6 +388,7 @@ int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; + lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, (void *)wsi->u.hdr.ah); n = lws_http_action(wsi); @@ -667,6 +668,7 @@ lws_create_new_server_wsi(struct lws_context *context) LWS_VISIBLE int lws_http_transaction_completed(struct lws *wsi) { + lwsl_debug("%s: wsi %p\n", __func__, wsi); /* if we can't go back to accept new headers, drop the connection */ if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { lwsl_info("%s: close connection\n", __func__); diff --git a/test-server/test-server.c b/test-server/test-server.c index dcf5ec43..18c9bea0 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -284,6 +284,7 @@ int main(int argc, char **argv) } info.gid = -1; info.uid = -1; + info.max_http_header_pool = 1; info.options = opts; context = lws_create_context(&info);