http header malloc pool implement pool

Signed-off-by: Andy Green <andy.green@linaro.org>
This commit is contained in:
Andy Green 2015-12-25 12:44:12 +08:00
parent b3d21f164d
commit 3df580066b
12 changed files with 243 additions and 75 deletions

View file

@ -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
=======================

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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;
}

View file

@ -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

View file

@ -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",

View file

@ -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;
}

View file

@ -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

View file

@ -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__);

View file

@ -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);