diff --git a/README.md b/README.md index 93fac8e8..851c5b98 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,23 @@ libwebsockets News ---- +## Lws has the first official ws-over-h2 server support + +There's a new standard on the RFC track that enabled multiplexing ws connections +over an http/2 link. Compared to making individual tcp and tls connections for +each ws link back to the same server, this makes your site start up radically +faster, and since all the connections are in one tls tunnel, with much memory +reduction serverside. + +To enable it on master you just need -DLWS_WITH_HTTP2=1 at cmake. No changes to +existing code are necessary for either http/2 (if you use the official header creation +apis if you return your own headers, as shown in the test apps for several versions) +or to take advantage of ws-over-h2. When built with http/2 support, it automatically +falls back to http/1 and traditional ws upgrade if that's all the client can handle. + +Currently only Chrome Canary v67 supports this ws-over-h2 encapsulation but the other +browsers will catch up soon. + ## New "minimal examples" https://github.com/warmcat/libwebsockets/tree/master/minimal-examples diff --git a/lib/handshake.c b/lib/handshake.c index d18aa180..6c256bea 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -65,9 +65,12 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) unsigned char *last_char, *oldbuf = buf; lws_filepos_t body_chunk_len; size_t n; +#if defined(LWS_WITH_HTTP2) + int m; +#endif switch (wsi->state) { -#ifdef LWS_WITH_HTTP2 +#if defined(LWS_WITH_HTTP2) case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: case LWSS_HTTP2_ESTABLISHED: @@ -113,10 +116,17 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) * file transfers operate. */ - if (lws_h2_parser(wsi, buf, len, &body_chunk_len)) { + m = lws_h2_parser(wsi, buf, len, &body_chunk_len); + if (m && m != 2) { lwsl_debug("%s: http2_parser bailed\n", __func__); goto bail; } + if (m && m == 2) { + /* swsi has been closed */ + buf += body_chunk_len; + len -= body_chunk_len; + goto read_ok; + } /* account for what we're using in rxflow buffer */ if (wsi->rxflow_buffer) { @@ -277,12 +287,16 @@ postbody_completion: case LWSS_AWAITING_CLOSE_ACK: case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: case LWSS_SHUTDOWN: + case LWSS_SHUTDOWN | _LSF_POLLOUT | _LSF_CCB: if (lws_handshake_client(wsi, &buf, (size_t)len)) goto bail; + switch (wsi->mode) { case LWSCM_WS_SERVING: case LWSCM_HTTP2_WS_SERVING: - + /* + * for h2 we are on the swsi + */ if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) { lwsl_info("interpret_incoming_packet bailed\n"); @@ -296,6 +310,11 @@ postbody_completion: lwsl_debug("%s: LWSS_HTTP_DEFERRING_ACTION\n", __func__); break; + case LWSS_DEAD_SOCKET: + lwsl_err("%s: Unhandled state LWSS_DEAD_SOCKET\n", __func__); + assert(0); + /* fallthru */ + default: lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state); goto bail; @@ -311,8 +330,10 @@ read_ok: bail: /* * h2 / h2-ws calls us recursively in lws_read()->lws_h2_parser()-> - * lws_read() pattern. Make sure that only the outer lws_read() does - * the wsi close. + * lws_read() pattern, having stripped the h2 framing in the middle. + * + * When taking down the whole connection, make sure that only the + * outer lws_read() does the wsi close. */ if (!wsi->outer_will_close) lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "lws_read bail"); diff --git a/lib/http2/http2.c b/lib/http2/http2.c index 104042c3..7ece1fb1 100644 --- a/lib/http2/http2.c +++ b/lib/http2/http2.c @@ -99,6 +99,8 @@ static const char * const h2_setting_names[] = { "H2SET_INITIAL_WINDOW_SIZE", "H2SET_MAX_FRAME_SIZE", "H2SET_MAX_HEADER_LIST_SIZE", + "reserved", + "H2SET_ENABLE_CONNECT_PROTOCOL" }; void @@ -465,6 +467,9 @@ int lws_h2_frame_write(struct lws *wsi, int type, int flags, unsigned char *p = &buf[-LWS_H2_FRAME_HEADER_LENGTH]; int n; + //if (wsi->h2_stream_carries_ws) + // lwsl_hexdump_level(LLL_NOTICE, buf, len); + *p++ = len >> 16; *p++ = len >> 8; *p++ = len; @@ -658,7 +663,7 @@ int lws_h2_do_pps_send(struct lws *wsi) break; case LWS_H2_PPS_UPDATE_WINDOW: - lwsl_notice("Issuing LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n", + lwsl_debug("Issuing LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n", pps->u.update_window.sid, pps->u.update_window.credit); *p++ = pps->u.update_window.credit >> 24; @@ -714,10 +719,8 @@ lws_h2_parse_frame_header(struct lws *wsi) } /* let the network wsi live a bit longer if subs are active */ - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); - - /* let the network wsi live a bit longer if subs are active */ - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); + if (!wsi->ws_over_h2_count) + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); if (h2n->sid) h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid); @@ -737,6 +740,7 @@ lws_h2_parse_frame_header(struct lws *wsi) * peer sent us something bigger than we told * it we would allow */ + lwsl_notice("received oversize frame %d\n", h2n->length); lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR, "Peer ignored our frame size setting"); return 0; @@ -749,9 +753,14 @@ lws_h2_parse_frame_header(struct lws *wsi) else { /* if it's data, either way no swsi means CLOSED state */ if (h2n->type == LWS_H2_FRAME_TYPE_DATA) { - lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, + if (h2n->sid <= h2n->highest_sid_opened) { + lwsl_notice("ignoring straggling data\n"); + h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ + } else { + lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, "Data for nonexistent sid"); - return 0; + return 0; + } } /* if the sid is credible, treat as wsi for it closed */ if (h2n->sid > h2n->highest_sid_opened && @@ -838,6 +847,7 @@ lws_h2_parse_frame_header(struct lws *wsi) break; case LWS_H2_FRAME_TYPE_GOAWAY: + lwsl_debug("LWS_H2_FRAME_TYPE_GOAWAY received\n"); break; case LWS_H2_FRAME_TYPE_RST_STREAM: @@ -1023,6 +1033,8 @@ update_end_headers: } lwsl_info("LWS_H2_FRAME_TYPE_WINDOW_UPDATE\n"); break; + case LWS_H2_FRAME_TYPE_COUNT: + break; default: lwsl_info("%s: ILLEGAL FRAME TYPE %d\n", __func__, h2n->type); h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */ @@ -1303,6 +1315,11 @@ lws_h2_parse_end_of_frame(struct lws *wsi) return 1; + case LWS_H2_FRAME_TYPE_RST_STREAM: + lwsl_info("LWS_H2_FRAME_TYPE_RST_STREAM: sid %d: reason 0x%x\n", + h2n->sid, h2n->hpack_e_dep); + break; + case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */ break; } @@ -1322,6 +1339,9 @@ lws_h2_parse_end_of_frame(struct lws *wsi) * Therefore if we will send non-PPS, ie, lws_http_action() for a stream * wsi, we must change its state and handle it as a priority in the * POLLOUT handler instead of writing it here. + * + * About closing... for the main network wsi, it should return nonzero to + * close it all. If it needs to close an swsi, it can do it here. */ int lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, @@ -1468,7 +1488,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, /* let the network wsi live a bit longer if subs are active... * our frame may take a long time to chew through */ - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); + if (!wsi->ws_over_h2_count) + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); if (!h2n->swsi) break; @@ -1505,8 +1526,13 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, * can return 0 in POST body with content len * exhausted somehow. */ - if (n <= 0) - goto fail; + if (n <= 0) { + in += h2n->length - h2n->count; + h2n->inside = h2n->length; + h2n->count = h2n->length - 1; + lwsl_debug("%s: lws_read told %d\n", __func__, n); + goto close_swsi_and_return; + } inlen -= n - 1; in += n - 1; @@ -1562,6 +1588,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, break; case LWS_H2_FRAME_TYPE_RST_STREAM: + h2n->hpack_e_dep <<= 8; + h2n->hpack_e_dep |= c; break; case LWS_H2_FRAME_TYPE_PUSH_PROMISE: @@ -1593,7 +1621,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen, frame_end: if (h2n->count > h2n->length) - lwsl_notice("%d %d\n", h2n->count, h2n->length); + lwsl_notice("%s: count > length %d %d\n", + __func__, h2n->count, h2n->length); if (h2n->count != h2n->length) break; @@ -1651,6 +1680,17 @@ try_frame_start: return 0; +close_swsi_and_return: + + lws_close_free_wsi(h2n->swsi, 0, "close_swsi_and_return"); + h2n->swsi = NULL; + h2n->frame_state = 0; + h2n->count = 0; + + *inused = in - oldin; + + return 2; + fail: *inused = in - oldin; @@ -1660,10 +1700,11 @@ fail: int lws_h2_ws_handshake(struct lws *wsi) { - uint8_t buf[256], *p = buf, *start = p, *end = &buf[sizeof(buf) - 1]; + uint8_t buf[LWS_PRE + 384], *p = buf + LWS_PRE, *start = p, + *end = &buf[sizeof(buf) - 1]; const struct lws_http_mount *hit; const char * uri_ptr; - int n; + int n, m; if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) return -1; @@ -1678,18 +1719,18 @@ lws_h2_ws_handshake(struct lws *wsi) wsi->protocol->name && wsi->protocol->name[0]) { if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL, (unsigned char *)wsi->protocol->name, - (int)strlen(wsi->protocol->name), &p, end)) + (int)strlen(wsi->protocol->name), + &p, end)) return -1; } if (lws_finalize_http_header(wsi, &p, end)) return -1; - n = lws_write(wsi, start, lws_ptr_diff(p, start), - LWS_WRITE_HTTP_HEADERS); - if (n != lws_ptr_diff(p, start)) { - lwsl_err("_write returned %d from %d\n", n, - lws_ptr_diff(p, start)); + m = lws_ptr_diff(p, start); + n = lws_write(wsi, start, m, LWS_WRITE_HTTP_HEADERS); + if (n != m) { + lwsl_err("_write returned %d from %d\n", n, m); return -1; } diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 3393f70e..3e21aaf8 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -389,7 +389,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * struct lws_tokens eff_buf; int n, m, ret; - lwsl_debug("%s: %p: caller: %s\n", __func__, wsi, caller); + lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller); if (!wsi) return; @@ -411,88 +411,16 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * wsi2->parent = NULL; /* stop it doing shutdown processing */ wsi2->socket_is_permanently_unusable = 1; - lws_close_free_wsi(wsi2, reason, "general child recurse"); + __lws_close_free_wsi(wsi2, reason, "general child recurse"); wsi2 = wsi1; } wsi->child_list = NULL; } -#if defined(LWS_WITH_HTTP2) - - if (wsi->h2.parent_wsi) { - lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, - wsi->h2.parent_wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.parent_wsi->h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, h2.sibling_list); - } - - if (wsi->upgraded_to_http2 || wsi->http2_substream) { - lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi); - - if (wsi->h2.child_list) { - lwsl_info(" parent %p: closing children: list:\n", wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, h2.sibling_list); - /* trigger closing of all of our http2 children first */ - lws_start_foreach_llp(struct lws **, w, - wsi->h2.child_list) { - lwsl_info(" closing child %p\n", *w); - /* disconnect from siblings */ - wsi2 = (*w)->h2.sibling_list; - (*w)->h2.sibling_list = NULL; - (*w)->socket_is_permanently_unusable = 1; - lws_close_free_wsi(*w, reason, "h2 child recurse"); - *w = wsi2; - continue; - } lws_end_foreach_llp(w, h2.sibling_list); - } - } - - if (wsi->upgraded_to_http2) { - /* remove pps */ - struct lws_h2_protocol_send *w = wsi->h2.h2n->pps, *w1; - while (w) { - w1 = w->next; - free(w); - w = w1; - } - wsi->h2.h2n->pps = NULL; - } - - if (wsi->http2_substream && wsi->h2.parent_wsi) { - lwsl_info(" %p: disentangling from siblings\n", wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->h2.parent_wsi->h2.child_list) { - /* disconnect from siblings */ - if (*w == wsi) { - wsi2 = (*w)->h2.sibling_list; - (*w)->h2.sibling_list = NULL; - *w = wsi2; - lwsl_info(" %p disentangled from sibling %p\n", - wsi, wsi2); - break; - } - } lws_end_foreach_llp(w, h2.sibling_list); - wsi->h2.parent_wsi->h2.child_count--; - wsi->h2.parent_wsi = NULL; - if (wsi->h2.pending_status_body) - lws_free_set_NULL(wsi->h2.pending_status_body); - } - - if (wsi->upgraded_to_http2 && wsi->h2.h2n && - wsi->h2.h2n->rx_scratch) - lws_free_set_NULL(wsi->h2.h2n->rx_scratch); -#endif - if (wsi->mode == LWSCM_RAW_FILEDESC) { lws_remove_child_from_any_parent(wsi); __remove_wsi_socket_from_fds(wsi); - wsi->protocol->callback(wsi, - LWS_CALLBACK_RAW_CLOSE_FILE, + wsi->protocol->callback(wsi, LWS_CALLBACK_RAW_CLOSE_FILE, wsi->user_space, NULL, 0); goto async_close; } @@ -563,7 +491,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * if (wsi->trunc_len) { lwsl_info("%p: FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); wsi->state = LWSS_FLUSHING_SEND_BEFORE_CLOSE; - lws_set_timeout(wsi, + __lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); return; } @@ -662,7 +590,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * lwsl_debug("waiting for chance to send close\n"); wsi->waiting_to_send_close_frame = 1; wsi->state = LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION; - lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 2); + __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5); lws_callback_on_writable(wsi); return; @@ -670,6 +598,90 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * just_kill_connection: +#if defined(LWS_WITH_HTTP2) + + if (wsi->http2_substream && wsi->h2_stream_carries_ws) + lws_h2_rst_stream(wsi, 0, "none"); + + if (wsi->h2.parent_wsi) { + lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, + wsi->h2.parent_wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.parent_wsi->h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, h2.sibling_list); + } + + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi); + + if (wsi->h2.child_list) { + lwsl_info(" parent %p: closing children: list:\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, h2.sibling_list); + /* trigger closing of all of our http2 children first */ + lws_start_foreach_llp(struct lws **, w, + wsi->h2.child_list) { + lwsl_info(" closing child %p\n", *w); + /* disconnect from siblings */ + wsi2 = (*w)->h2.sibling_list; + (*w)->h2.sibling_list = NULL; + (*w)->socket_is_permanently_unusable = 1; + __lws_close_free_wsi(*w, reason, "h2 child recurse"); + *w = wsi2; + continue; + } lws_end_foreach_llp(w, h2.sibling_list); + } + } + + if (wsi->upgraded_to_http2) { + /* remove pps */ + struct lws_h2_protocol_send *w = wsi->h2.h2n->pps, *w1; + while (w) { + w1 = w->next; + free(w); + w = w1; + } + wsi->h2.h2n->pps = NULL; + } + + if (wsi->http2_substream && wsi->h2.parent_wsi) { + lwsl_info(" %p: disentangling from siblings\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->h2.parent_wsi->h2.child_list) { + /* disconnect from siblings */ + if (*w == wsi) { + wsi2 = (*w)->h2.sibling_list; + (*w)->h2.sibling_list = NULL; + *w = wsi2; + lwsl_info(" %p disentangled from sibling %p\n", + wsi, wsi2); + break; + } + } lws_end_foreach_llp(w, h2.sibling_list); + wsi->h2.parent_wsi->h2.child_count--; + wsi->h2.parent_wsi = NULL; + if (wsi->h2.pending_status_body) + lws_free_set_NULL(wsi->h2.pending_status_body); + } + + if (wsi->h2_stream_carries_ws) { + struct lws *nwsi = lws_get_network_wsi(wsi); + + nwsi->ws_over_h2_count++; + /* if no ws, then put a timeout on the parent wsi */ + if (!nwsi->ws_over_h2_count) + __lws_set_timeout(nwsi, + PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); + } + + if (wsi->upgraded_to_http2 && wsi->h2.h2n && + wsi->h2.h2n->rx_scratch) + lws_free_set_NULL(wsi->h2.h2n->rx_scratch); +#endif + lws_remove_child_from_any_parent(wsi); n = 0; @@ -719,7 +731,7 @@ just_kill_connection: #ifdef LWS_OPENSSL_SUPPORT if (lws_is_ssl(wsi) && wsi->ssl) { n = 0; - switch (lws_tls_shutdown(wsi)) { + switch (__lws_tls_shutdown(wsi)) { case LWS_SSL_CAPABLE_DONE: case LWS_SSL_CAPABLE_ERROR: case LWS_SSL_CAPABLE_MORE_SERVICE_READ: @@ -753,9 +765,9 @@ just_kill_connection: lws_sockfd_valid(wsi->desc.sockfd) && wsi->state != ((wsi->state & ~0x1f) | LWSS_SHUTDOWN) && !LWS_LIBUV_ENABLED(context)) { - lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); + __lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); wsi->state = (wsi->state & ~0x1f) | LWSS_SHUTDOWN; - lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, + __lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, context->timeout_secs); return; @@ -790,7 +802,9 @@ just_kill_connection: lws_free_set_NULL(wsi->rxflow_buffer); if (lws_state_is_ws(wsi->state_pre_close) || - wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT) { + wsi->mode == LWSCM_WS_SERVING || + wsi->mode == LWSCM_HTTP2_WS_SERVING || + wsi->mode == LWSCM_WS_CLIENT) { if (wsi->ws->rx_draining_ext) { struct lws **w = &pt->rx_draining_ext_list; @@ -833,8 +847,11 @@ just_kill_connection: /* tell the user it's all over for this guy */ - if (wsi->protocol && !wsi->told_user_closed && wsi->protocol->callback && - wsi->mode != LWSCM_RAW && (wsi->state_pre_close & _LSF_CCB)) { + if (wsi->protocol && + !wsi->told_user_closed && + wsi->protocol->callback && + wsi->mode != LWSCM_RAW && + (wsi->state_pre_close & _LSF_CCB)) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, wsi->user_space, NULL, 0); } else if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED) { @@ -2136,7 +2153,9 @@ lws_close_reason(struct lws *wsi, enum lws_close_status status, unsigned char *p, *start; int budget = sizeof(wsi->ws->ping_payload_buf) - LWS_PRE; - assert(wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT); + assert(wsi->mode == LWSCM_WS_SERVING || + wsi->mode == LWSCM_HTTP2_WS_SERVING || + wsi->mode == LWSCM_WS_CLIENT); start = p = &wsi->ws->ping_payload_buf[LWS_PRE]; diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index a5833475..aa614a3d 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -347,10 +347,10 @@ lwsl_timestamp(int level, char *p, int len); * lwsl_hexdump() - helper to hexdump a buffer * * \param level: one of LLL_ constants - * \param buf: buffer start to dump + * \param vbuf: buffer start to dump * \param len: length of buffer to dump * - * If \p level is visible, does a nice hexdump -C style dump of \p buf for + * If \p level is visible, does a nice hexdump -C style dump of \p vbuf for * \p len bytes. This can be extremely convenient while debugging. */ LWS_VISIBLE LWS_EXTERN void @@ -925,7 +925,11 @@ enum lws_callback_reasons { LWS_CALLBACK_ESTABLISHED = 0, /**< (VH) after the server completes a handshake with an incoming * client. If you built the library with ssl support, in is a - * pointer to the ssl struct associated with the connection or NULL.*/ + * pointer to the ssl struct associated with the connection or NULL. + * + * b0 of len is set if the connection was made using ws-over-h2 + * + * */ LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 1, /**< the request client connection has been unable to complete a * handshake with the remote server. If in is non-NULL, you can diff --git a/lib/output.c b/lib/output.c index 983e4860..49c03663 100644 --- a/lib/output.c +++ b/lib/output.c @@ -274,14 +274,15 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, wp1f == LWS_WRITE_HTTP_HEADERS) goto send_raw; - /* if not in a state to send stuff, then just send nothing */ + /* if not in a state to send ws stuff, then just send nothing */ if (!lws_state_is_ws(wsi->state) && ((wsi->state != LWSS_RETURNED_CLOSE_ALREADY && wsi->state != LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION && wsi->state != LWSS_AWAITING_CLOSE_ACK) || - wp != LWS_WRITE_CLOSE)) { - lwsl_debug("binning\n"); + wp1f != LWS_WRITE_CLOSE)) { + //assert(0); + lwsl_debug("binning %d %d\n", wsi->state, wp1f); return 0; } @@ -483,6 +484,12 @@ do_more_inside_frame: send_raw: switch (wp1f) { + case LWS_WRITE_TEXT: + case LWS_WRITE_BINARY: + case LWS_WRITE_CONTINUATION: + if (!wsi->h2_stream_carries_ws) + break; + /* fallthru */ case LWS_WRITE_CLOSE: /* lwsl_hexdump(&buf[-pre], len); */ case LWS_WRITE_HTTP: @@ -895,6 +902,7 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", len, wsi->desc.sockfd, n, LWS_ERRNO); + return LWS_SSL_CAPABLE_ERROR; } #endif diff --git a/lib/pollfd.c b/lib/pollfd.c index 0d68b6db..809fad6a 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -462,7 +462,7 @@ lws_callback_on_writable(struct lws *wsi) #endif #ifdef LWS_WITH_HTTP2 - lwsl_info("%s: %p\n", __func__, wsi); + lwsl_info("%s: %p (mode %d)\n", __func__, wsi, wsi->mode); if (wsi->mode != LWSCM_HTTP2_SERVING && wsi->mode != LWSCM_HTTP2_WS_SERVING) diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index fcd08dc3..e3636fc1 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -509,7 +509,8 @@ enum lws_connection_states { LWSS_HTTP2_ESTABLISHED = _LSF_CCB | 15 | _LSF_POLLOUT, LWSS_HTTP2_ESTABLISHED_WS = _LSF_CCB | 16 | - _LSF_WEBSOCKET, + _LSF_WEBSOCKET | + _LSF_POLLOUT, LWSS_CGI = 17, @@ -520,7 +521,7 @@ enum lws_connection_states { _LSF_POLLOUT, }; -#define lws_state_is_ws(s) (!!(s & _LSF_WEBSOCKET)) +#define lws_state_is_ws(s) (!!((s) & _LSF_WEBSOCKET)) enum http_version { HTTP_VERSION_1_0, @@ -872,6 +873,7 @@ struct lws_context_per_thread { short ah_count_in_use; unsigned char tid; + unsigned char lock_depth; #if LWS_MAX_SMP > 1 pthread_t lock_owner; #endif @@ -2018,6 +2020,7 @@ struct lws { #if defined(LWS_WITH_STATS) && defined(LWS_OPENSSL_SUPPORT) char seen_rx; #endif + uint8_t ws_over_h2_count; /* volatile to make sure code is aware other thread can change */ volatile char handling_pollout; volatile char leave_pollout_active; @@ -2188,6 +2191,8 @@ user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); #ifdef LWS_WITH_HTTP2 +int +lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason); struct lws * lws_h2_get_nth_child(struct lws *wsi, int n); LWS_EXTERN void lws_h2_init(struct lws *wsi); LWS_EXTERN int @@ -2428,7 +2433,7 @@ LWS_EXTERN enum lws_ssl_capable_status lws_tls_server_abort_connection(struct lws *wsi); LWS_EXTERN enum lws_ssl_capable_status -lws_tls_shutdown(struct lws *wsi); +__lws_tls_shutdown(struct lws *wsi); LWS_EXTERN enum lws_ssl_capable_status lws_tls_client_connect(struct lws *wsi); @@ -2477,8 +2482,8 @@ static LWS_INLINE void lws_pt_lock(struct lws_context_per_thread *pt, const char *reason) { if (pt->lock_owner == pthread_self()) { - lwsl_err("tid %d: lock collision: already held for %s, reacquiring for %s\n", pt->tid, pt->last_lock_reason, reason); - assert(0); + pt->lock_depth++; + return; } pthread_mutex_lock(&pt->lock); pt->last_lock_reason = reason; @@ -2489,6 +2494,10 @@ lws_pt_lock(struct lws_context_per_thread *pt, const char *reason) static LWS_INLINE void lws_pt_unlock(struct lws_context_per_thread *pt) { + if (pt->lock_depth) { + pt->lock_depth--; + return; + } pt->last_lock_reason ="free"; pt->lock_owner = 0; //lwsl_notice("tid %d: unlock %s\n", pt->tid, pt->last_lock_reason); diff --git a/lib/server/parsers.c b/lib/server/parsers.c index e305a7b3..4d850083 100644 --- a/lib/server/parsers.c +++ b/lib/server/parsers.c @@ -1567,7 +1567,7 @@ spill: switch (wsi->ws->opcode) { case LWSWSOPC_CLOSE: - /* is this an acknowledgement of our close? */ + /* is this an acknowledgment of our close? */ if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { /* * fine he has told us he is closing too, let's @@ -1729,7 +1729,6 @@ drain_extension: eff_buf.token[eff_buf.token_len] = '\0'; if (wsi->protocol->callback) { - if (callback_action == LWS_CALLBACK_RECEIVE_PONG) lwsl_info("Doing pong callback\n"); diff --git a/lib/server/server.c b/lib/server/server.c index 4793d57c..ade2e06e 100644 --- a/lib/server/server.c +++ b/lib/server/server.c @@ -716,6 +716,244 @@ int lws_clean_url(char *p) return 0; } +static int +lws_server_init_wsi_for_ws(struct lws *wsi) +{ + int n; + + wsi->state = LWSS_ESTABLISHED; + lws_restart_ws_ping_pong_timer(wsi); + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, use + * a big default for compatibility + */ + + n = (int)wsi->protocol->rx_buffer_size; + if (!n) + n = wsi->context->pt_serv_buf_size; + n += LWS_PRE; + wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf"); + if (!wsi->ws->rx_ubuf) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + return 1; + } + wsi->ws->rx_ubuf_alloc = n; + lwsl_debug("Allocating RX buffer %d\n", n); + +#if LWS_POSIX && !defined(LWS_WITH_ESP32) + if (!wsi->parent_carries_io && + !wsi->h2_stream_carries_ws) + if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, + (const char *)&n, sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + return 1; + } +#endif + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, +#ifdef LWS_OPENSSL_SUPPORT + wsi->ssl, +#else + NULL, +#endif + wsi->h2_stream_carries_ws)) + return 1; + + lwsl_debug("ws established\n"); + + return 0; +} + +static int +lws_process_ws_upgrade(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char protocol_list[128], protocol_name[64], *p; + int protocol_len, hit, n = 0, non_space_char_found = 0; + + if (!wsi->protocol) + lwsl_err("NULL protocol at lws_read\n"); + + /* + * It's either websocket or h2->websocket + * + * Select the first protocol we support from the list + * the client sent us. + * + * Copy it to remove header fragmentation + */ + + if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, + WSI_TOKEN_PROTOCOL) < 0) { + lwsl_err("protocol list too long"); + return 1; + } + + protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + protocol_list[protocol_len] = '\0'; + p = protocol_list; + hit = 0; + + while (*p && !hit) { + n = 0; + non_space_char_found = 0; + while (n < (int)sizeof(protocol_name) - 1 && + *p && *p != ',') { + /* ignore leading spaces */ + if (!non_space_char_found && *p == ' ') { + n++; + continue; + } + non_space_char_found = 1; + protocol_name[n++] = *p++; + } + protocol_name[n] = '\0'; + if (*p) + p++; + + lwsl_debug("checking %s\n", protocol_name); + + n = 0; + while (wsi->vhost->protocols[n].callback) { + lwsl_debug("try %s\n", + wsi->vhost->protocols[n].name); + + if (wsi->vhost->protocols[n].name && + !strcmp(wsi->vhost->protocols[n].name, + protocol_name)) { + wsi->protocol = &wsi->vhost->protocols[n]; + hit = 1; + break; + } + + n++; + } + } + + /* we didn't find a protocol he wanted? */ + + if (!hit) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { + lwsl_notice("No protocol from \"%s\" supported\n", + protocol_list); + return 1; + } + /* + * some clients only have one protocol and + * do not send the protocol list header... + * allow it and match to the vhost's default + * protocol (which itself defaults to zero) + */ + lwsl_info("defaulting to prot handler %d\n", + wsi->vhost->default_protocol_index); + n = wsi->vhost->default_protocol_index; + wsi->protocol = &wsi->vhost->protocols[ + (int)wsi->vhost->default_protocol_index]; + } + + /* allocate the ws struct for the wsi */ + wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct"); + if (!wsi->ws) { + lwsl_notice("OOM\n"); + return 1; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) + wsi->ws->ietf_spec_revision = + atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION)); + + /* allocate wsi->user storage */ + if (lws_ensure_user_space(wsi)) { + lwsl_notice("problem with user space\n"); + return 1; + } + + /* + * Give the user code a chance to study the request and + * have the opportunity to deny it + */ + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, + wsi->user_space, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { + lwsl_warn("User code denied connection\n"); + return 1; + } + + /* + * Perform the handshake according to the protocol version the + * client announced + */ + + switch (wsi->ws->ietf_spec_revision) { + default: + lwsl_notice("Unknown client spec version %d\n", + wsi->ws->ietf_spec_revision); + wsi->ws->ietf_spec_revision = 13; + //return 1; + /* fallthru */ + case 13: +#if defined(LWS_WITH_HTTP2) + if (wsi->h2_stream_carries_ws) { + if (lws_h2_ws_handshake(wsi)) { + lwsl_notice("h2 ws handshake failed\n"); + return 1; + } + } else +#endif + { + lwsl_parser("lws_parse calling handshake_04\n"); + if (handshake_0405(wsi->context, wsi)) { + lwsl_notice("hs0405 has failed the connection\n"); + return 1; + } + } + break; + } + + lws_same_vh_protocol_insert(wsi, n); + + /* we are upgrading to ws, so http/1.1 + h2 and keepalive + + * pipelined header considerations about keeping the ah around + * no longer apply. However it's common for the first ws + * protocol data to have been coalesced with the browser + * upgrade request and to already be in the ah rx buffer. + */ + + lwsl_debug("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", + __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); + lws_pt_lock(pt, __func__); + + if (wsi->h2_stream_carries_ws) + lws_union_transition(wsi, LWSCM_HTTP2_WS_SERVING); + else + lws_union_transition(wsi, LWSCM_WS_SERVING); + /* + * Because rxpos/rxlen shows something in the ah, we will get + * service guaranteed next time around the event loop + */ + + lws_pt_unlock(pt); + + lws_server_init_wsi_for_ws(wsi); + lwsl_parser("accepted v%02d connection\n", + wsi->ws->ietf_spec_revision); + + /* !!! drop ah unreservedly after ESTABLISHED */ + if (wsi->ah->rxpos == wsi->ah->rxlen ) { + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); + } + + return 0; +} + static const unsigned char methods[] = { WSI_TOKEN_GET_URI, @@ -773,6 +1011,9 @@ lws_http_action(struct lws *wsi) unsigned int n; char http_version_str[10]; char http_conn_str[20]; +#if defined(LWS_WITH_HTTP2) + char *p; +#endif int http_version_len; char *uri_ptr = NULL, *s; int uri_len = 0, meth; @@ -795,6 +1036,37 @@ lws_http_action(struct lws *wsi) lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], meth, uri_ptr); +#if defined(LWS_WITH_HTTP2) + /* + * with H2 there's also a way to upgrade a stream to something + * else... :method is CONNECT and :protocol says the name of + * the new protocol we want to carry. We have to have sent a + * SETTINGS saying that we support it though. + */ + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + if (wsi->vhost->set.s[H2SET_ENABLE_CONNECT_PROTOCOL] && + wsi->http2_substream && p && !strcmp(p, "CONNECT")) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL); + if (p && !strcmp(p, "websocket")) { + struct lws *nwsi = lws_get_network_wsi(wsi); + + wsi->vhost->conn_stats.ws_upg++; + lwsl_info("Upgrade h2 to ws\n"); + wsi->h2_stream_carries_ws = 1; + nwsi->ws_over_h2_count++; + if (lws_process_ws_upgrade(wsi)) + goto bail_nuke_ah; + + if (nwsi->ws_over_h2_count == 1) + lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0); + + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_info("Upgraded h2 to ws OK\n"); + return 0; + } + } +#endif + if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; @@ -1264,69 +1536,16 @@ transaction_result_n: #endif } -static int -lws_server_init_wsi_for_ws(struct lws *wsi) -{ - int n; - - wsi->state = LWSS_ESTABLISHED; - lws_restart_ws_ping_pong_timer(wsi); - - /* - * create the frame buffer for this connection according to the - * size mentioned in the protocol definition. If 0 there, use - * a big default for compatibility - */ - - n = (int)wsi->protocol->rx_buffer_size; - if (!n) - n = wsi->context->pt_serv_buf_size; - n += LWS_PRE; - wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf"); - if (!wsi->ws->rx_ubuf) { - lwsl_err("Out of Mem allocating rx buffer %d\n", n); - return 1; - } - wsi->ws->rx_ubuf_alloc = n; - lwsl_debug("Allocating RX buffer %d\n", n); - -#if LWS_POSIX && !defined(LWS_WITH_ESP32) - if (!wsi->parent_carries_io && - !wsi->h2_stream_carries_ws) - if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, - (const char *)&n, sizeof n)) { - lwsl_warn("Failed to set SNDBUF to %d", n); - return 1; - } -#endif - - /* notify user code that we're ready to roll */ - - if (wsi->protocol->callback) - if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, - wsi->user_space, -#ifdef LWS_OPENSSL_SUPPORT - wsi->ssl, -#else - NULL, -#endif - 0)) - return 1; - - return 0; -} - int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { struct lws_context *context = lws_get_context(wsi); - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int protocol_len, n = 0, hit, non_space_char_found = 0, m, i; unsigned char *obuf = *buf; - char protocol_list[128]; - char protocol_name[64]; +#if defined(LWS_WITH_HTTP2) + char tbuf[128], *p; +#endif size_t olen = len; - char *p; + int n = 0, m, i; if (len >= 10000000) { lwsl_err("%s: assert: len %ld\n", __func__, (long)len); @@ -1349,6 +1568,7 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) i = (int)len; m = lws_parse(wsi, *buf, &i); (*buf) += (int)len - i; + len = i; if (m) { if (m == 2) { /* @@ -1489,27 +1709,6 @@ raw_transition: goto bail_nuke_ah; } -#if defined(LWS_WITH_HTTP2) - /* - * with H2 there's also a way to upgrade a stream to something - * else... :method is CONNECT and :protocol says the name of - * the new protocol we want to carry. We have to have sent a - * SETTINGS saying that we support it though. - */ - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); - if (wsi->h2.h2n && - wsi->h2.h2n->set.s[H2SET_ENABLE_CONNECT_PROTOCOL] && - p && !strcmp(p, "CONNECT")) { - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_COLON_PROTOCOL); - if (p && !strcmp(p, "websocket")) { - wsi->vhost->conn_stats.ws_upg++; - lwsl_info("Upgrade h2 to ws\n"); - wsi->h2_stream_carries_ws = 1; - goto upgrade_ws; - } - } -#endif - /* no upgrade ack... he remained as HTTP */ lwsl_info("No upgrade\n"); @@ -1536,8 +1735,7 @@ upgrade_h2c: p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); /* convert the peer's HTTP-Settings */ - n = lws_b64_decode_string(p, protocol_list, - sizeof(protocol_list)); + n = lws_b64_decode_string(p, tbuf, sizeof(tbuf)); if (n < 0) { lwsl_parser("HTTP2_SETTINGS too long\n"); return 1; @@ -1558,18 +1756,17 @@ upgrade_h2c: /* HTTP2 union */ - lws_h2_settings(wsi, &wsi->h2.h2n->set, - (unsigned char *)protocol_list, n); + lws_h2_settings(wsi, &wsi->h2.h2n->set, (unsigned char *)tbuf, n); lws_hpack_dynamic_size(wsi, wsi->h2.h2n->set.s[ H2SET_HEADER_TABLE_SIZE]); - strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Upgrade: h2c\x0d\x0a\x0d\x0a"); - n = lws_issue_raw(wsi, (unsigned char *)protocol_list, - strlen(protocol_list)); - if (n != (int)strlen(protocol_list)) { + strcpy(tbuf, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); + m = (int)strlen(tbuf); + n = lws_issue_raw(wsi, (unsigned char *)tbuf, m); + if (n != m) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } @@ -1581,177 +1778,11 @@ upgrade_h2c: #endif upgrade_ws: - if (!wsi->protocol) - lwsl_err("NULL protocol at lws_read\n"); - - /* - * It's either websocket or h2->websocket - * - * Select the first protocol we support from the list - * the client sent us. - * - * Copy it to remove header fragmentation - */ - - if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, - WSI_TOKEN_PROTOCOL) < 0) { - lwsl_err("protocol list too long"); + if (lws_process_ws_upgrade(wsi)) goto bail_nuke_ah; - } - - protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); - protocol_list[protocol_len] = '\0'; - p = protocol_list; - hit = 0; - - while (*p && !hit) { - n = 0; - non_space_char_found = 0; - while (n < (int)sizeof(protocol_name) - 1 && - *p && *p != ',') { - /* ignore leading spaces */ - if (!non_space_char_found && *p == ' ') { - n++; - continue; - } - non_space_char_found = 1; - protocol_name[n++] = *p++; - } - protocol_name[n] = '\0'; - if (*p) - p++; - - lwsl_info("checking %s\n", protocol_name); - - n = 0; - while (wsi->vhost->protocols[n].callback) { - lwsl_info("try %s\n", - wsi->vhost->protocols[n].name); - - if (wsi->vhost->protocols[n].name && - !strcmp(wsi->vhost->protocols[n].name, - protocol_name)) { - wsi->protocol = &wsi->vhost->protocols[n]; - hit = 1; - break; - } - - n++; - } - } - - /* we didn't find a protocol he wanted? */ - - if (!hit) { - if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { - lwsl_info("No protocol from \"%s\" supported\n", - protocol_list); - goto bail_nuke_ah; - } - /* - * some clients only have one protocol and - * do not send the protocol list header... - * allow it and match to the vhost's default - * protocol (which itself defaults to zero) - */ - lwsl_info("defaulting to prot handler %d\n", - wsi->vhost->default_protocol_index); - n = wsi->vhost->default_protocol_index; - wsi->protocol = &wsi->vhost->protocols[ - (int)wsi->vhost->default_protocol_index]; - } - - /* allocate the ws struct for the wsi */ - wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct"); - if (!wsi->ws) { - lwsl_notice("OOM\n"); - goto bail_nuke_ah; - } - - /* set it from the parser temp */ - wsi->ws->ietf_spec_revision = wsi->rx_frame_type; - - /* allocate wsi->user storage */ - if (lws_ensure_user_space(wsi)) - goto bail_nuke_ah; - - /* - * Give the user code a chance to study the request and - * have the opportunity to deny it - */ - if ((wsi->protocol->callback)(wsi, - LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, - wsi->user_space, - lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { - lwsl_warn("User code denied connection\n"); - goto bail_nuke_ah; - } - - /* - * Perform the handshake according to the protocol version the - * client announced - */ - - switch (wsi->ws->ietf_spec_revision) { - case 13: -#if defined(LWS_WITH_HTTP2) - if (wsi->h2_stream_carries_ws) { - if (lws_h2_ws_handshake(wsi)) { - lwsl_info("h2 ws handshake failed\n"); - goto bail_nuke_ah; - } - } else -#endif - { - lwsl_parser("lws_parse calling handshake_04\n"); - if (handshake_0405(context, wsi)) { - lwsl_info("hs0405 has failed the connection\n"); - goto bail_nuke_ah; - } - } - break; - - default: - lwsl_info("Unknown client spec version %d\n", - wsi->ws->ietf_spec_revision); - goto bail_nuke_ah; - } - - lws_same_vh_protocol_insert(wsi, n); - - /* we are upgrading to ws, so http/1.1 + h2 and keepalive + - * pipelined header considerations about keeping the ah around - * no longer apply. However it's common for the first ws - * protocol data to have been coalesced with the browser - * upgrade request and to already be in the ah rx buffer. - */ - - lwsl_info("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", - __func__, wsi, wsi->ah->rxpos, wsi->ah->rxlen); - lws_pt_lock(pt, __func__); - - if (wsi->h2_stream_carries_ws) - lws_union_transition(wsi, LWSCM_HTTP2_WS_SERVING); - else - lws_union_transition(wsi, LWSCM_WS_SERVING); - /* - * Because rxpos/rxlen shows something in the ah, we will get - * service guaranteed next time around the event loop - */ - - lws_pt_unlock(pt); - - lws_server_init_wsi_for_ws(wsi); - lwsl_parser("accepted v%02d connection\n", - wsi->ws->ietf_spec_revision); - - /* !!! drop ah unreservedly after ESTABLISHED */ - if (wsi->ah->rxpos == wsi->ah->rxlen ) { - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 1); - } return 0; + } /* while all chars are handled */ return 0; diff --git a/lib/service.c b/lib/service.c index b01396f6..00e19d87 100644 --- a/lib/service.c +++ b/lib/service.c @@ -54,6 +54,7 @@ lws_calllback_as_writeable(struct lws *wsi) case LWSCM_WSCL_ISSUE_HTTP_BODY: n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE; break; + case LWSCM_HTTP2_WS_SERVING: case LWSCM_WS_SERVING: n = LWS_CALLBACK_SERVER_WRITEABLE; break; @@ -162,7 +163,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) LWS_WRITE_CLOSE); if (n >= 0) { wsi->state = LWSS_AWAITING_CLOSE_ACK; - lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 1); + lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 5); lwsl_debug("sent close indication, awaiting ack\n"); goto bail_ok; @@ -187,9 +188,11 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) /* well he is sent, mark him done */ wsi->ws->ping_pending_flag = 0; - if (wsi->ws->payload_is_close) + if (wsi->ws->payload_is_close) { + // assert(0); /* oh... a close frame was it... then we are done */ goto bail_die; + } /* otherwise for PING, leave POLLOUT active either way */ goto bail_ok; @@ -412,9 +415,9 @@ user_service_go_again: wsi2a = wsi->h2.child_list; while (wsi2a) { if (wsi2a->h2.requested_POLLOUT) - lwsl_debug(" * %p\n", wsi2a); + lwsl_debug(" * %p %s\n", wsi2a, wsi2a->protocol->name); else - lwsl_debug(" %p\n", wsi2a); + lwsl_debug(" %p %s\n", wsi2a, wsi2a->protocol->name); wsi2a = wsi2a->h2.sibling_list; } @@ -427,10 +430,8 @@ user_service_go_again: struct lws *w, **wa; wa = &(*wsi2)->h2.sibling_list; - if (!(*wsi2)->h2.requested_POLLOUT) { - lwsl_debug(" child %p doesn't want POLLOUT\n", *wsi2); + if (!(*wsi2)->h2.requested_POLLOUT) goto next_child; - } /* * we're going to do writable callback for this child. @@ -545,6 +546,57 @@ user_service_go_again: goto next_child; } + /* Notify peer that we decided to close */ + + if (w->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION) { + lwsl_debug("sending close packet\n"); + w->waiting_to_send_close_frame = 0; + n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], + w->ws->close_in_ping_buffer_len, + LWS_WRITE_CLOSE); + if (n >= 0) { + w->state = LWSS_AWAITING_CLOSE_ACK; + lws_set_timeout(w, PENDING_TIMEOUT_CLOSE_ACK, 5); + lwsl_debug("sent close indication, awaiting ack\n"); + } + + goto next_child; + } + + /* Acknowledge receipt of peer's notification he closed, + * then logically close ourself */ + + if ((lws_state_is_ws(w->state) && w->ws->ping_pending_flag) || + (w->state == LWSS_RETURNED_CLOSE_ALREADY && + w->ws->payload_is_close)) { + + if (w->ws->payload_is_close) + write_type = LWS_WRITE_CLOSE | LWS_WRITE_H2_STREAM_END; + + n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE], + w->ws->ping_payload_len, write_type); + if (n < 0) + goto bail_die; + + /* well he is sent, mark him done */ + w->ws->ping_pending_flag = 0; + if (w->ws->payload_is_close) { + /* oh... a close frame was it... then we are done */ + lwsl_debug("Acknowledged peer's close packet\n"); + w->ws->payload_is_close = 0; + w->state = LWSS_RETURNED_CLOSE_ALREADY; + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "returned close packet"); + wa = &wsi->h2.child_list; + goto next_child; + } + + lws_callback_on_writable(w); + (w)->h2.requested_POLLOUT = 1; + + /* otherwise for PING, leave POLLOUT active either way */ + goto next_child; + } + if (lws_calllback_as_writeable(w) || w->h2.send_END_STREAM) { lwsl_debug("Closing POLLOUT child\n"); lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 pollout handle"); @@ -638,7 +690,7 @@ __lws_service_timeout_check(struct lws *wsi, time_t sec) if (wsi->protocol && wsi->protocol->callback(wsi, LWS_CALLBACK_TIMER, wsi->user_space, NULL, 0)) { - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "timer cb errored"); return 1; @@ -1382,7 +1434,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, #ifdef LWS_OPENSSL_SUPPORT if (wsi->state == LWSS_SHUTDOWN && lws_is_ssl(wsi) && wsi->ssl) { n = 0; - switch (lws_tls_shutdown(wsi)) { + switch (__lws_tls_shutdown(wsi)) { case LWS_SSL_CAPABLE_DONE: case LWS_SSL_CAPABLE_ERROR: goto close_and_handled; diff --git a/lib/tls/mbedtls/mbedtls-server.c b/lib/tls/mbedtls/mbedtls-server.c index 0761a646..5f3daaa5 100644 --- a/lib/tls/mbedtls/mbedtls-server.c +++ b/lib/tls/mbedtls/mbedtls-server.c @@ -278,7 +278,7 @@ lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) int lws_tls_server_abort_connection(struct lws *wsi) { - lws_tls_shutdown(wsi); + __lws_tls_shutdown(wsi); SSL_free(wsi->ssl); return 0; diff --git a/lib/tls/mbedtls/ssl.c b/lib/tls/mbedtls/ssl.c index 55325448..1849d1d1 100644 --- a/lib/tls/mbedtls/ssl.c +++ b/lib/tls/mbedtls/ssl.c @@ -302,7 +302,7 @@ lws_tls_ctx_from_wsi(struct lws *wsi) } enum lws_ssl_capable_status -lws_tls_shutdown(struct lws *wsi) +__lws_tls_shutdown(struct lws *wsi) { int n = SSL_shutdown(wsi->ssl); @@ -314,7 +314,7 @@ lws_tls_shutdown(struct lws *wsi) return LWS_SSL_CAPABLE_DONE; case 0: /* needs a retry */ - lws_change_pollfd(wsi, 0, LWS_POLLIN); + __lws_change_pollfd(wsi, 0, LWS_POLLIN); return LWS_SSL_CAPABLE_MORE_SERVICE; default: /* fatal error, or WANT */ @@ -322,12 +322,12 @@ lws_tls_shutdown(struct lws *wsi) if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { if (SSL_want_read(wsi->ssl)) { lwsl_debug("(wants read)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLIN); + __lws_change_pollfd(wsi, 0, LWS_POLLIN); return LWS_SSL_CAPABLE_MORE_SERVICE_READ; } if (SSL_want_write(wsi->ssl)) { lwsl_debug("(wants write)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLOUT); + __lws_change_pollfd(wsi, 0, LWS_POLLOUT); return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; } } diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c index 9a45b3be..01309502 100644 --- a/lib/tls/openssl/ssl.c +++ b/lib/tls/openssl/ssl.c @@ -463,7 +463,7 @@ lws_tls_ctx_from_wsi(struct lws *wsi) } enum lws_ssl_capable_status -lws_tls_shutdown(struct lws *wsi) +__lws_tls_shutdown(struct lws *wsi) { int n; @@ -475,7 +475,7 @@ lws_tls_shutdown(struct lws *wsi) return LWS_SSL_CAPABLE_DONE; case 0: /* needs a retry */ - lws_change_pollfd(wsi, 0, LWS_POLLIN); + __lws_change_pollfd(wsi, 0, LWS_POLLIN); return LWS_SSL_CAPABLE_MORE_SERVICE; default: /* fatal error, or WANT */ @@ -483,12 +483,12 @@ lws_tls_shutdown(struct lws *wsi) if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { if (SSL_want_read(wsi->ssl)) { lwsl_debug("(wants read)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLIN); + __lws_change_pollfd(wsi, 0, LWS_POLLIN); return LWS_SSL_CAPABLE_MORE_SERVICE_READ; } if (SSL_want_write(wsi->ssl)) { lwsl_debug("(wants write)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLOUT); + __lws_change_pollfd(wsi, 0, LWS_POLLOUT); return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; } } diff --git a/test-apps/test-server.c b/test-apps/test-server.c index 03500070..4166ab16 100644 --- a/test-apps/test-server.c +++ b/test-apps/test-server.c @@ -376,7 +376,7 @@ int main(int argc, char **argv) #endif /* tell the library what debug level to emit and to send it to syslog */ - lws_set_log_level(debug_level, lwsl_emit_syslog); + lws_set_log_level(debug_level, NULL); lwsl_notice("libwebsockets test server - license LGPL2.1+SLE\n"); lwsl_notice("(C) Copyright 2010-2017 Andy Green \n");