diff --git a/lib/core-net/close.c b/lib/core-net/close.c index 074e1fac0..b72b98a78 100644 --- a/lib/core-net/close.c +++ b/lib/core-net/close.c @@ -326,7 +326,6 @@ just_kill_connection: if (wsi->role_ops->close_kill_connection) wsi->role_ops->close_kill_connection(wsi, reason); - lws_remove_child_from_any_parent(wsi); n = 0; if (!wsi->told_user_closed && wsi->user_space && @@ -469,6 +468,7 @@ just_kill_connection: } async_close: + lws_remove_child_from_any_parent(wsi); wsi->socket_is_permanently_unusable = 1; if (wsi->context->event_loop_ops->wsi_logical_close) diff --git a/lib/core-net/dummy-callback.c b/lib/core-net/dummy-callback.c index e66395361..94b28b160 100644 --- a/lib/core-net/dummy-callback.c +++ b/lib/core-net/dummy-callback.c @@ -83,6 +83,143 @@ stream_close(struct lws *wsi) #endif +struct lws_proxy_pkt { + struct lws_dll pkt_list; + size_t len; + char binary; + char first; + char final; + + /* data follows */ +}; + +#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS) +int +lws_callback_ws_proxy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_proxy_pkt *pkt; + struct lws_dll *dll; + + switch (reason) { + + /* h1 ws proxying... child / client / onward */ + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + if (!wsi->h1_ws_proxied || !wsi->parent) + break; + + lws_process_ws_upgrade2(wsi->parent); + +#if defined(LWS_WITH_HTTP2) + if (wsi->parent->http2_substream) + lwsl_info("%s: proxied h2 -> h1 ws established\n", __func__); +#endif + break; + + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + case LWS_CALLBACK_CLIENT_CLOSED: + lwsl_user("%s: client closed: parent %p\n", __func__, wsi->parent); + if (wsi->parent) + lws_set_timeout(wsi->parent, 1, LWS_TO_KILL_ASYNC); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__); + if (!pkt) + return -1; + + pkt->pkt_list.prev = pkt->pkt_list.next = NULL; + pkt->len = len; + pkt->first = lws_is_first_fragment(wsi); + pkt->final = lws_is_final_fragment(wsi); + pkt->binary = lws_frame_is_binary(wsi); + + memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len); + + lws_dll_add_tail(&pkt->pkt_list, &wsi->parent->ws->proxy_head); + lws_callback_on_writable(wsi->parent); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + dll = lws_dll_get_tail(&wsi->ws->proxy_head); + if (!dll) + break; + + pkt = (struct lws_proxy_pkt *)dll; + if (lws_write(wsi, ((unsigned char *)&pkt[1]) + + LWS_PRE, pkt->len, lws_write_ws_flags( + pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, + pkt->first, pkt->final)) < 0) + return -1; + + lws_dll_remove_track_tail(dll, &wsi->ws->proxy_head); + lws_free(pkt); + + if (lws_dll_get_tail(&wsi->ws->proxy_head)) + lws_callback_on_writable(wsi); + break; + + /* h1 ws proxying... parent / server / incoming */ + + case LWS_CALLBACK_CLOSED: + lwsl_user("%s: closed\n", __func__); + return -1; + + case LWS_CALLBACK_RECEIVE: + pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__); + if (!pkt) + return -1; + + pkt->pkt_list.prev = pkt->pkt_list.next = NULL; + pkt->len = len; + pkt->first = lws_is_first_fragment(wsi); + pkt->final = lws_is_final_fragment(wsi); + pkt->binary = lws_frame_is_binary(wsi); + + memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len); + + lws_dll_add_tail(&pkt->pkt_list, &wsi->child_list->ws->proxy_head); + lws_callback_on_writable(wsi->child_list); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + dll = lws_dll_get_tail(&wsi->ws->proxy_head); + if (!dll) + break; + + pkt = (struct lws_proxy_pkt *)dll; + if (lws_write(wsi, ((unsigned char *)&pkt[1]) + + LWS_PRE, pkt->len, lws_write_ws_flags( + pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT, + pkt->first, pkt->final)) < 0) + return -1; + + lws_dll_remove_track_tail(dll, &wsi->ws->proxy_head); + lws_free(pkt); + + if (lws_dll_get_tail(&wsi->ws->proxy_head)) + lws_callback_on_writable(wsi); + break; + + default: + return 0; + } + + return 0; +} + +const struct lws_protocols lws_ws_proxy = { + "lws-ws-proxy", + lws_callback_ws_proxy, + 0, + 8192, + 8192, NULL, 0 +}; + +#endif + LWS_VISIBLE int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) @@ -269,8 +406,8 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, return -1; break; } + /* h1 http proxying... */ - /* this handles the proxy case... */ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { unsigned char *start, *p, *end; diff --git a/lib/core-net/private.h b/lib/core-net/private.h index ae8c85ab0..feade9f36 100644 --- a/lib/core-net/private.h +++ b/lib/core-net/private.h @@ -577,6 +577,8 @@ struct lws { unsigned int protocol_bind_balance:1; unsigned int unix_skt:1; unsigned int close_when_buffered_out_drained:1; + unsigned int h1_ws_proxied; + unsigned int proxied_ws_parent; unsigned int could_have_pending:1; /* detect back-to-back writes */ unsigned int outer_will_close:1; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index b66d13a70..2a00c1257 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -425,7 +425,7 @@ lws_create_vhost(struct lws_context *context, struct lws_plugin *plugin = context->plugin_list; #endif struct lws_protocols *lwsp; - int m, f = !info->pvo; + int m, f = !info->pvo, fx = 0; char buf[20]; #if !defined(LWS_WITHOUT_CLIENT) && defined(LWS_HAVE_GETENV) char *p; @@ -461,6 +461,11 @@ lws_create_vhost(struct lws_context *context, vh->bind_iface = info->bind_iface; #endif + /* + * let's figure out how many protocols the user is handing us, using the + * old or new way depending on what he gave us + */ + if (!pcols) for (vh->count_protocols = 0; info->pprotocols[vh->count_protocols]; @@ -529,12 +534,16 @@ lws_create_vhost(struct lws_context *context, } #endif +#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS) + fx = 1; +#endif + /* * give the vhost a unified list of protocols including the * ones that came from plugins */ lwsp = lws_zalloc(sizeof(struct lws_protocols) * (vh->count_protocols + - context->plugin_protocol_count + 1), + context->plugin_protocol_count + fx + 1), "vhost-specific plugin table"); if (!lwsp) { lwsl_err("OOM\n"); @@ -548,7 +557,8 @@ lws_create_vhost(struct lws_context *context, } else memcpy(lwsp, pcols, sizeof(struct lws_protocols) * m); - /* for compatibility, all protocols enabled on vhost if only + /* + * For compatibility, all protocols enabled on vhost if only * the default vhost exists. Otherwise only vhosts who ask * for a protocol get it enabled. */ @@ -579,6 +589,11 @@ lws_create_vhost(struct lws_context *context, } #endif +#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS) + memcpy(&lwsp[m++], &lws_ws_proxy, sizeof(lws_ws_proxy)); + vh->count_protocols++; +#endif + if (!pcols || #ifdef LWS_WITH_PLUGINS (context->plugin_list) || @@ -978,6 +993,7 @@ __lws_vhost_destroy2(struct lws_vhost *vh) n = 0; while (n < vh->count_protocols) { wsi.protocol = protocol; + if (protocol->callback) protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY, NULL, NULL, 0); diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 888eb5375..9bf320ca4 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -2213,22 +2213,36 @@ lws_h2_ws_handshake(struct lws *wsi) if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) > 64) return -1; - /* we can only return the protocol header if: - * - one came in, and ... */ - if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && - /* - it is not an empty string */ - 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)) - return -1; + if (wsi->proxied_ws_parent && wsi->child_list) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL, + (uint8_t *)lws_hdr_simple_ptr(wsi, + WSI_TOKEN_PROTOCOL), + strlen(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_PROTOCOL)), + &p, end)) + return -1; + } + } else { + + /* we can only return the protocol header if: + * - one came in, and ... */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && + /* - it is not an empty string */ + 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)) + return -1; + } } if (lws_finalize_http_header(wsi, &p, end)) return -1; m = lws_ptr_diff(p, start); + // lwsl_hexdump_notice(start, m); n = lws_write(wsi, start, m, LWS_WRITE_HTTP_HEADERS); if (n != m) { lwsl_err("_write returned %d from %d\n", n, m); diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h index 08fd105de..891409ed4 100644 --- a/lib/roles/http/private.h +++ b/lib/roles/http/private.h @@ -298,3 +298,7 @@ _lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) int lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len); + +int +lws_http_proxy_start(struct lws *wsi, const struct lws_http_mount *hit, + char *uri_ptr, char ws); diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index cbf83866d..fdc5cf00a 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -982,6 +982,173 @@ lws_check_basic_auth(struct lws *wsi, const char *basic_auth_login_file) return LCBA_CONTINUE; } +#if defined(LWS_WITH_HTTP_PROXY) +/* + * Set up an onward http proxy connection according to the mount this + * uri falls under. Notice this can also be starting the proxying of what was + * originally an incoming h1 upgrade, or an h2 ws "upgrade". + */ +int +lws_http_proxy_start(struct lws *wsi, const struct lws_http_mount *hit, + char *uri_ptr, char ws) +{ + char ads[96], rpath[256], host[96], *pcolon, *pslash, unix_skt = 0; + struct lws_client_connect_info i; + struct lws *cwsi; + int n, na; + + if (ws) + /* + * Neither our inbound ws upgrade request side, nor our onward + * ws client connection on our side can bind to the actual + * protocol that only the remote inbound side and the remote + * onward side understand. + * + * Instead these are both bound to our built-in "lws-ws-proxy" + * protocol, which understands how to proxy between the two + * sides. + * + * We bind the parent, inbound part here and our side of the + * onward client connection is bound to the same handler using + * the .local_protocol_name. + */ + lws_bind_protocol(wsi, &lws_ws_proxy, __func__); + + memset(&i, 0, sizeof(i)); + i.context = lws_get_context(wsi); + + if (hit->origin[0] == '+') + unix_skt = 1; + + pcolon = strchr(hit->origin, ':'); + pslash = strchr(hit->origin, '/'); + if (!pslash) { + lwsl_err("Proxy mount origin '%s' must have /\n", hit->origin); + return -1; + } + + if (unix_skt) { + if (!pcolon) { + lwsl_err("Proxy mount origin for unix skt must " + "have address delimited by :\n"); + + return -1; + } + n = lws_ptr_diff(pcolon, hit->origin); + pslash = pcolon; + } else { + if (pcolon > pslash) + pcolon = NULL; + + if (pcolon) + n = (int)(pcolon - hit->origin); + else + n = (int)(pslash - hit->origin); + + if (n >= (int)sizeof(ads) - 2) + n = sizeof(ads) - 2; + } + + memcpy(ads, hit->origin, n); + ads[n] = '\0'; + + i.address = ads; + i.port = 80; + if (hit->origin_protocol == LWSMPRO_HTTPS) { + i.port = 443; + i.ssl_connection = 1; + } + if (pcolon) + i.port = atoi(pcolon + 1); + + n = lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", + pslash + 1, uri_ptr + hit->mountpoint_len) - 2; + lws_clean_url(rpath); + na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS); + if (na) { + char *p = rpath + n; + + if (na >= (int)sizeof(rpath) - n - 2) { + lwsl_info("%s: query string %d longer " + "than we can handle\n", __func__, + na); + + return -1; + } + + *p++ = '?'; + if (lws_hdr_copy(wsi, p, + (int)(&rpath[sizeof(rpath) - 1] - p), + WSI_TOKEN_HTTP_URI_ARGS) > 0) + while (na--) { + if (*p == '\0') + *p = '&'; + p++; + } + *p = '\0'; + } + + i.path = rpath; + if (i.address[0] != '+' || + !lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)) + i.host = i.address; + else + i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST); + i.origin = NULL; + if (!ws) + i.method = "GET"; + + lws_snprintf(host, sizeof(host), "%s:%d", i.address, i.port); + i.host = host; + + i.alpn = "http/1.1"; + i.parent_wsi = wsi; + i.pwsi = &cwsi; + i.protocol = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); + if (ws) + i.local_protocol_name = "lws-ws-proxy"; + +// i.uri_replace_from = hit->origin; +// i.uri_replace_to = hit->mountpoint; + + lwsl_info("proxying to %s port %d url %s, ssl %d, from %s, to %s\n", + i.address, i.port, i.path, i.ssl_connection, + i.uri_replace_from, i.uri_replace_to); + + if (!lws_client_connect_via_info(&i)) { + lwsl_err("proxy connect fail\n"); + + /* + * ... we can't do the proxy action, but we can + * cleanly return him a 503 and a description + */ + + lws_return_http_status(wsi, + HTTP_STATUS_SERVICE_UNAVAILABLE, + "

Service Temporarily Unavailable

" + "The server is temporarily unable to service " + "your request due to maintenance downtime or " + "capacity problems. Please try again later."); + + return 1; + } + + lwsl_info("%s: setting proxy clientside on %p (parent %p)\n", + __func__, cwsi, lws_get_parent(cwsi)); + + cwsi->http.proxy_clientside = 1; + if (ws) { + wsi->proxied_ws_parent = 1; + cwsi->h1_ws_proxied = 1; + if (i.protocol) { + lwsl_debug("%s: (requesting '%s')\n", __func__, i.protocol); + } + } + + return 0; +} +#endif + static const char * const oprot[] = { "http://", "https://" }; @@ -1238,131 +1405,8 @@ lws_http_action(struct lws *wsi) // lwsl_notice("%s: origin_protocol: %d\n", __func__, hit->origin_protocol); if (hit->origin_protocol == LWSMPRO_HTTPS || - hit->origin_protocol == LWSMPRO_HTTP) { - char ads[96], rpath[256], *pcolon, *pslash, unix_skt = 0; - struct lws_client_connect_info i; - struct lws *cwsi; - int n, na; - - memset(&i, 0, sizeof(i)); - i.context = lws_get_context(wsi); - - if (hit->origin[0] == '+') - unix_skt = 1; - - pcolon = strchr(hit->origin, ':'); - pslash = strchr(hit->origin, '/'); - if (!pslash) { - lwsl_err("Proxy mount origin '%s' must have /\n", - hit->origin); - return -1; - } - - if (unix_skt) { - if (!pcolon) { - lwsl_err("Proxy mount origin for unix skt must " - "have address delimited by :\n"); - - return -1; - } - n = lws_ptr_diff(pcolon, hit->origin); - pslash = pcolon; - } else { - if (pcolon > pslash) - pcolon = NULL; - - if (pcolon) - n = (int)(pcolon - hit->origin); - else - n = (int)(pslash - hit->origin); - - if (n >= (int)sizeof(ads) - 2) - n = sizeof(ads) - 2; - } - - memcpy(ads, hit->origin, n); - ads[n] = '\0'; - - i.address = ads; - i.port = 80; - if (hit->origin_protocol == LWSMPRO_HTTPS) { - i.port = 443; - i.ssl_connection = 1; - } - if (pcolon) - i.port = atoi(pcolon + 1); - - n = lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", - pslash + 1, uri_ptr + hit->mountpoint_len) - 2; - lws_clean_url(rpath); - na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS); - if (na) { - char *p = rpath + n; - - if (na >= (int)sizeof(rpath) - n - 2) { - lwsl_info("%s: query string %d longer " - "than we can handle\n", __func__, - na); - - return -1; - } - - *p++ = '?'; - if (lws_hdr_copy(wsi, p, - (int)(&rpath[sizeof(rpath) - 1] - p), - WSI_TOKEN_HTTP_URI_ARGS) > 0) - while (na--) { - if (*p == '\0') - *p = '&'; - p++; - } - *p = '\0'; - } - - i.path = rpath; - if (i.address[0] != '+' || - !lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)) - i.host = i.address; - else - i.host = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST); - i.origin = NULL; - i.method = "GET"; - i.alpn = "http/1.1"; - i.parent_wsi = wsi; - i.pwsi = &cwsi; - - // i.uri_replace_from = hit->origin; - // i.uri_replace_to = hit->mountpoint; - - lwsl_info("proxying to %s port %d url %s, ssl %d, " - "from %s, to %s\n", - i.address, i.port, i.path, i.ssl_connection, - i.uri_replace_from, i.uri_replace_to); - - if (!lws_client_connect_via_info(&i)) { - lwsl_err("proxy connect fail\n"); - - /* - * ... we can't do the proxy action, but we can - * cleanly return him a 503 and a description - */ - - lws_return_http_status(wsi, - HTTP_STATUS_SERVICE_UNAVAILABLE, - "

Service Temporarily Unavailable

" - "The server is temporarily unable to service " - "your request due to maintenance downtime or " - "capacity problems. Please try again later."); - - return 1; - } - - lwsl_info("%s: setting proxy clientside on %p (parent %p)\n", - __func__, cwsi, lws_get_parent(cwsi)); - cwsi->http.proxy_clientside = 1; - - return 0; - } + hit->origin_protocol == LWSMPRO_HTTP) + return lws_http_proxy_start(wsi, hit, uri_ptr, 0); #endif /* diff --git a/lib/roles/ws/client-parser-ws.c b/lib/roles/ws/client-parser-ws.c index f5aaa6dbb..f6802953a 100644 --- a/lib/roles/ws/client-parser-ws.c +++ b/lib/roles/ws/client-parser-ws.c @@ -452,7 +452,7 @@ ping_drop: break; case LWSWSOPC_PONG: - lwsl_info("client received pong\n"); + lwsl_info("%s: client %p received pong\n", __func__, wsi); lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE], wsi->ws->rx_ubuf_head); diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c index d88833f38..cabca0fb7 100644 --- a/lib/roles/ws/client-ws.c +++ b/lib/roles/ws/client-ws.c @@ -334,6 +334,11 @@ bad_conn_format: goto bail2; } +#if defined(LWS_WITH_HTTP_PROXY) + lws_strncpy(wsi->ws->actual_protocol, p, + sizeof(wsi->ws->actual_protocol)); +#endif + /* * identify the selected protocol struct and set it */ diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 6247bdbeb..7f1d5e13e 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -1261,7 +1261,9 @@ int rops_handle_POLLOUT_ws(struct lws *wsi) if (!wsi->socket_is_permanently_unusable && wsi->ws->send_check_ping) { - lwsl_info("issuing ping on wsi %p\n", wsi); + lwsl_info("%s: issuing ping on wsi %p: %s %s h2: %d\n", __func__, wsi, + wsi->role_ops->name, wsi->protocol->name, + wsi->http2_substream); wsi->ws->send_check_ping = 0; n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], 0, LWS_WRITE_PING); @@ -1421,7 +1423,7 @@ rops_periodic_checks_ws(struct lws_context *context, int tsi, time_t now) struct lws *wsi = lws_container_of(d, struct lws, same_vh_protocol); - if (lwsi_role_ws(wsi) && + if (lwsi_role_ws(wsi) && !wsi->http2_substream && !wsi->socket_is_permanently_unusable && !wsi->ws->send_check_ping && wsi->ws->time_next_ping_check && @@ -1429,7 +1431,7 @@ rops_periodic_checks_ws(struct lws_context *context, int tsi, time_t now) wsi->ws->time_next_ping_check) > context->ws_ping_pong_interval) { - lwsl_info("req pp on wsi %p\n", wsi); + lwsl_info("%s: req pp on wsi %p\n", __func__, wsi); wsi->ws->send_check_ping = 1; lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING, diff --git a/lib/roles/ws/private.h b/lib/roles/ws/private.h index 71ffcaea9..e58264d6d 100644 --- a/lib/roles/ws/private.h +++ b/lib/roles/ws/private.h @@ -92,6 +92,12 @@ struct _lws_websocket_related { struct lws *rx_draining_ext_list; struct lws *tx_draining_ext_list; #endif + +#if defined(LWS_WITH_HTTP_PROXY) + struct lws_dll proxy_head; + char actual_protocol[16]; +#endif + /* Also used for close content... control opcode == < 128 */ uint8_t ping_payload_buf[128 - 3 + LWS_PRE]; uint8_t mask[4]; @@ -160,5 +166,11 @@ int handshake_0405(struct lws_context *context, struct lws *wsi); int lws_process_ws_upgrade(struct lws *wsi); + +int +lws_process_ws_upgrade2(struct lws *wsi); + +extern const struct lws_protocols lws_ws_proxy; + int lws_server_init_wsi_for_ws(struct lws *wsi); diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c index 688e81cc9..80e3e7794 100644 --- a/lib/roles/ws/server-ws.c +++ b/lib/roles/ws/server-ws.c @@ -249,145 +249,11 @@ lws_extension_server_handshake(struct lws *wsi, char **p, int budget) #endif int -lws_process_ws_upgrade(struct lws *wsi) +lws_process_ws_upgrade2(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; const struct lws_protocol_vhost_options *pvos = NULL; - const struct lws_protocols *pcol = NULL; const char *ws_prot_basic_auth = NULL; - char buf[128], name[64]; - struct lws_tokenize ts; - lws_tokenize_elem e; - - if (!wsi->protocol) - lwsl_err("NULL protocol at lws_read\n"); - - /* - * It's either websocket or h2->websocket - * - * If we are on h1, confirm we got the required "connection: upgrade" - * header. h2 / ws-over-h2 does not have this. - */ - -#if defined(LWS_WITH_HTTP2) - if (!wsi->http2_substream) { -#endif - - lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | - LWS_TOKENIZE_F_DOT_NONTERM | - LWS_TOKENIZE_F_RFC7230_DELIMS | - LWS_TOKENIZE_F_MINUS_NONTERM); - ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, - WSI_TOKEN_CONNECTION); - if (ts.len <= 0) - goto bad_conn_format; - - do { - e = lws_tokenize(&ts); - switch (e) { - case LWS_TOKZE_TOKEN: - if (!strcasecmp(ts.token, "upgrade")) - e = LWS_TOKZE_ENDED; - break; - - case LWS_TOKZE_DELIMITER: - break; - - default: /* includes ENDED */ - bad_conn_format: - lwsl_err("%s: malformed or absent conn hdr\n", - __func__); - - return 1; - } - } while (e > 0); - -#if defined(LWS_WITH_HTTP2) - } -#endif - - /* - * Select the first protocol we support from the list - * the client sent us. - */ - - lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | - LWS_TOKENIZE_F_MINUS_NONTERM | - LWS_TOKENIZE_F_DOT_NONTERM | - LWS_TOKENIZE_F_RFC7230_DELIMS); - ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_PROTOCOL); - if (ts.len < 0) { - lwsl_err("%s: protocol list too long\n", __func__); - return 1; - } - if (!ts.len) { - int n = wsi->vhost->default_protocol_index; - /* - * 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). - * - * Setting the vhost default protocol index to -1 or anything - * more than the actual number of protocols on the vhost causes - * these "no protocol" ws connections to be rejected. - */ - - if (n >= wsi->vhost->count_protocols) { - lwsl_notice("%s: rejecting ws upg with no protocol\n", - __func__); - - return 1; - } - - lwsl_info("%s: defaulting to prot handler %d\n", __func__, n); - - lws_bind_protocol(wsi, &wsi->vhost->protocols[n], - "ws upgrade default pcol"); - - goto alloc_ws; - } - - /* otherwise go through the user-provided protocol list */ - - do { - e = lws_tokenize(&ts); - switch (e) { - case LWS_TOKZE_TOKEN: - - if (lws_tokenize_cstr(&ts, name, sizeof(name))) { - lwsl_err("%s: pcol name too long\n", __func__); - - return 1; - } - lwsl_debug("checking %s\n", name); - pcol = lws_vhost_name_to_protocol(wsi->vhost, name); - if (pcol) { - /* if we know it, bind to it and stop looking */ - lws_bind_protocol(wsi, pcol, "ws upg pcol"); - e = LWS_TOKZE_ENDED; - } - break; - - case LWS_TOKZE_DELIMITER: - case LWS_TOKZE_ENDED: - break; - - default: - lwsl_err("%s: malformatted protocol list", __func__); - - return 1; - } - } while (e > 0); - - /* we didn't find a protocol he wanted? */ - - if (!pcol) { - lwsl_notice("No supported protocol \"%s\"\n", buf); - - return 1; - } - -alloc_ws: /* * Allow basic auth a look-in now we bound the wsi to the protocol. @@ -521,6 +387,174 @@ alloc_ws: return 0; } +int +lws_process_ws_upgrade(struct lws *wsi) +{ + const struct lws_protocols *pcol = NULL; + char buf[128], name[64]; + struct lws_tokenize ts; + lws_tokenize_elem e; + + if (!wsi->protocol) + lwsl_err("NULL protocol at lws_read\n"); + + /* + * It's either websocket or h2->websocket + * + * If we are on h1, confirm we got the required "connection: upgrade" + * header. h2 / ws-over-h2 does not have this. + */ + +#if defined(LWS_WITH_HTTP2) + if (!wsi->http2_substream) { +#endif + + lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_RFC7230_DELIMS | + LWS_TOKENIZE_F_MINUS_NONTERM); + ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, + WSI_TOKEN_CONNECTION); + if (ts.len <= 0) + goto bad_conn_format; + + do { + e = lws_tokenize(&ts); + switch (e) { + case LWS_TOKZE_TOKEN: + if (!strcasecmp(ts.token, "upgrade")) + e = LWS_TOKZE_ENDED; + break; + + case LWS_TOKZE_DELIMITER: + break; + + default: /* includes ENDED */ + bad_conn_format: + lwsl_err("%s: malformed or absent conn hdr\n", + __func__); + + return 1; + } + } while (e > 0); + +#if defined(LWS_WITH_HTTP2) + } +#endif + +#if defined(LWS_WITH_HTTP_PROXY) + { + const struct lws_http_mount *hit; + int uri_len = 0, meth; + char *uri_ptr; + + meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); + hit = lws_find_mount(wsi, uri_ptr, uri_len); + + if (hit && (meth == 0 || meth == 8) && + (hit->origin_protocol == LWSMPRO_HTTPS || + hit->origin_protocol == LWSMPRO_HTTP)) + /* + * We are an h1 ws upgrade on a urlpath that corresponds + * to a proxying mount. Don't try to deal with it + * locally, eg, we won't even have the right protocol + * handler since we're not the guy handling it, just a + * conduit. + * + * Instead open the related ongoing h1 connection + * according to the mount configuration and proxy + * whatever that has to say from now on. + */ + return lws_http_proxy_start(wsi, hit, uri_ptr, 1); + } +#endif + + /* + * Select the first protocol we support from the list + * the client sent us. + */ + + lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST | + LWS_TOKENIZE_F_MINUS_NONTERM | + LWS_TOKENIZE_F_DOT_NONTERM | + LWS_TOKENIZE_F_RFC7230_DELIMS); + ts.len = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_PROTOCOL); + if (ts.len < 0) { + lwsl_err("%s: protocol list too long\n", __func__); + return 1; + } + if (!ts.len) { + int n = wsi->vhost->default_protocol_index; + /* + * 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). + * + * Setting the vhost default protocol index to -1 or anything + * more than the actual number of protocols on the vhost causes + * these "no protocol" ws connections to be rejected. + */ + + if (n >= wsi->vhost->count_protocols) { + lwsl_notice("%s: rejecting ws upg with no protocol\n", + __func__); + + return 1; + } + + lwsl_info("%s: defaulting to prot handler %d\n", __func__, n); + + lws_bind_protocol(wsi, &wsi->vhost->protocols[n], + "ws upgrade default pcol"); + + goto alloc_ws; + } + + /* otherwise go through the user-provided protocol list */ + + do { + e = lws_tokenize(&ts); + switch (e) { + case LWS_TOKZE_TOKEN: + + if (lws_tokenize_cstr(&ts, name, sizeof(name))) { + lwsl_err("%s: pcol name too long\n", __func__); + + return 1; + } + lwsl_debug("checking %s\n", name); + pcol = lws_vhost_name_to_protocol(wsi->vhost, name); + if (pcol) { + /* if we know it, bind to it and stop looking */ + lws_bind_protocol(wsi, pcol, "ws upg pcol"); + e = LWS_TOKZE_ENDED; + } + break; + + case LWS_TOKZE_DELIMITER: + case LWS_TOKZE_ENDED: + break; + + default: + lwsl_err("%s: malformatted protocol list", __func__); + + return 1; + } + } while (e > 0); + + /* we didn't find a protocol he wanted? */ + + if (!pcol) { + lwsl_notice("No supported protocol \"%s\"\n", buf); + + return 1; + } + +alloc_ws: + + return lws_process_ws_upgrade2(wsi); +} + int handshake_0405(struct lws_context *context, struct lws *wsi) { @@ -585,8 +619,15 @@ handshake_0405(struct lws_context *context, struct lws *wsi) /* - it is not an empty string */ wsi->protocol->name && wsi->protocol->name[0]) { + const char *prot = wsi->protocol->name; + +#if defined(LWS_WITH_HTTP_PROXY) + if (wsi->proxied_ws_parent && wsi->child_list) + prot = wsi->child_list->ws->actual_protocol; +#endif + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); - p += lws_snprintf(p, 128, "%s", wsi->protocol->name); + p += lws_snprintf(p, 128, "%s", prot); } #if !defined(LWS_WITHOUT_EXTENSIONS)