LWS_VISIBLE void lws_libuv_stop(struct lws_context *context) { struct lws_context_per_thread *pt; int n, m; if (context->requested_kill) return; context->requested_kill = 1; m = context->count_threads; context->being_destroyed = 1; while (m--) { pt = &context->pt[m]; for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); if (!wsi) continue; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY /* no protocol close */); n--; } } lwsl_info("%s: feels everything closed\n", __func__); if (context->count_wsi_allocated == 0) lws_libuv_kill(context); }
static void esp8266_cb_sent(void *arg) { struct espconn *conn = arg; struct lws *wsi = conn->reverse; struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; // lwsl_err("%s: wsi %p (psc %d) wsi->position_in_fds_table=%d\n", __func__, wsi, wsi->pending_send_completion, wsi->position_in_fds_table); wsi->pending_send_completion--; if (wsi->close_is_pending_send_completion && !wsi->pending_send_completion && !lws_partial_buffered(wsi)) { lwsl_notice("doing delayed close\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); } if (pt->fds[wsi->position_in_fds_table].events & LWS_POLLOUT) { struct lws_pollfd pollfd; pollfd.fd = arg; pollfd.events = LWS_POLLOUT; pollfd.revents = LWS_POLLOUT; // lwsl_notice("informing POLLOUT\n"); lws_service_fd(lws_get_context(wsi), &pollfd); } }
static struct lws * lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd) { struct lws_context *context = vh->context; struct lws *new_wsi = lws_create_new_server_wsi(vh); if (!new_wsi) { compatible_close(accept_fd); return NULL; } lwsl_info("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, accept_fd); new_wsi->sock = accept_fd; /* the transport is accepted... give him time to negotiate */ lws_set_timeout(new_wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, context->timeout_secs); #if LWS_POSIX == 0 mbed3_tcp_stream_accept(accept_fd, new_wsi); #endif /* * A new connection was accepted. Give the user a chance to * set properties of the newly created wsi. There's no protocol * selected yet so we issue this to protocols[0] */ if ((context->vhost_list->protocols[0].callback)(new_wsi, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, NULL, NULL, 0)) { compatible_close(new_wsi->sock); lws_free(new_wsi); return NULL; } lws_libev_accept(new_wsi, new_wsi->sock); lws_libuv_accept(new_wsi, new_wsi->sock); if (!LWS_SSL_ENABLED(new_wsi->vhost)) { if (insert_wsi_socket_into_fds(context, new_wsi)) { lwsl_err("%s: fail inserting socket\n", __func__); goto fail; } } else { new_wsi->mode = LWSCM_SSL_INIT; if (lws_server_socket_service_ssl(new_wsi, accept_fd)) { lwsl_err("%s: fail ssl negotiation\n", __func__); goto fail; } } return new_wsi; fail: lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; }
int lws_service_timeout_check(struct lws *wsi, unsigned int sec) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; struct lws **pwsi; /* * if extensions want in on it (eg, we are a mux parent) * give them a chance to service child timeouts */ if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0) return 0; if (!wsi->pending_timeout) return 0; /* * if we went beyond the allowed time, kill the * connection */ if ((time_t)sec > wsi->pending_timeout_limit) { #if LWS_POSIX lwsl_notice("wsi %p: TIMEDOUT WAITING on %d (did hdr %d, ah %p, wl %d, pfd events %d)\n", (void *)wsi, wsi->pending_timeout, wsi->hdr_parsing_completed, wsi->u.hdr.ah, pt->ah_wait_list_length, pt->fds[wsi->sock].events); #endif lws_pt_lock(pt); pwsi = &pt->ah_wait_list; while (*pwsi) { if (*pwsi == wsi) break; pwsi = &(*pwsi)->u.hdr.ah_wait_list; } lws_pt_unlock(pt); if (!*pwsi) lwsl_err("*** not on ah wait list ***\n"); /* * Since he failed a timeout, he already had a chance to do * something and was unable to... that includes situations like * half closed connections. So process this "failed timeout" * close as a violent death and don't try to do protocol * cleanup like flush partials. */ wsi->socket_is_permanently_unusable = 1; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 1; } return 0; }
/* * This does not actually stop the event loop. The reason is we have to pass * libuv handle closures through its event loop. So this tries to close all * wsi, and set a flag; when all the wsi closures are finalized then we * actually stop the libuv event loops. */ static void lws_libuv_stop(struct lws_context *context) { struct lws_context_per_thread *pt; int n, m; lwsl_err("%s\n", __func__); if (context->requested_kill) { lwsl_err("%s: ignoring\n", __func__); return; } context->requested_kill = 1; m = context->count_threads; context->being_destroyed = 1; /* * Phase 1: start the close of every dynamic uv handle */ while (m--) { pt = &context->pt[m]; if (pt->pipe_wsi) { uv_poll_stop(pt->pipe_wsi->w_read.uv.pwatcher); lws_destroy_event_pipe(pt->pipe_wsi); pt->pipe_wsi = NULL; } for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); if (!wsi) continue; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY, __func__ /* no protocol close */); n--; } } lwsl_info("%s: started closing all wsi\n", __func__); /* we cannot have completed... there are at least the cancel pipes */ }
static void esp8266_cb_disconnected(void *arg) { struct espconn *conn = arg; struct lws *wsi = conn->reverse; int n; lwsl_notice("%s: %p\n", __func__, wsi); for (n = 0; n < hacky_context->max_fds; n++) if (hacky_context->connpool[n] == arg) { hacky_context->connpool[n] = NULL; lwsl_info(" freed connpool %d\n", n); } if (wsi) { conn->reverse = NULL; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_notice("closed ok\n"); } }
void lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt) { struct lws_pollfd pfd; if (!pt->dll_head_buflist.next) return; /* * service all guys with pending rxflow that reached a state they can * accept the pending data */ lws_pt_lock(pt, __func__); lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, pt->dll_head_buflist.next) { struct lws *wsi = lws_container_of(d, struct lws, dll_buflist); pfd.events = LWS_POLLIN; pfd.revents = LWS_POLLIN; pfd.fd = -1; lwsl_debug("%s: rxflow processing: %p 0x%x\n", __func__, wsi, wsi->wsistate); if (!lws_is_flowcontrolled(wsi) && lwsi_state(wsi) != LRS_DEFERRING_ACTION && (wsi->role_ops->handle_POLLIN)(pt, wsi, &pfd) == LWS_HPI_RET_PLEASE_CLOSE_ME) lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "close_and_handled"); } lws_end_foreach_dll_safe(d, d1); lws_pt_unlock(pt); }
LWS_VISIBLE void lws_context_destroy(struct lws_context *context) { const struct lws_protocols *protocol = NULL; struct lws_context_per_thread *pt; struct lws_vhost *vh = NULL, *vh1; struct lws wsi; int n, m; lwsl_notice("%s\n", __func__); if (!context) return; m = context->count_threads; context->being_destroyed = 1; memset(&wsi, 0, sizeof(wsi)); wsi.context = context; #ifdef LWS_LATENCY if (context->worst_latency_info[0]) lwsl_notice("Worst latency: %s\n", context->worst_latency_info); #endif while (m--) { pt = &context->pt[m]; for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); if (!wsi) continue; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY /* no protocol close */); n--; } lws_pt_mutex_destroy(pt); } /* * give all extensions a chance to clean up any per-context * allocations they might have made */ n = lws_ext_cb_all_exts(context, NULL, LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0); n = lws_ext_cb_all_exts(context, NULL, LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0); /* * inform all the protocols that they are done and will have no more * callbacks. * * We can't free things until after the event loop shuts down. */ if (context->protocol_init_done) vh = context->vhost_list; while (vh) { wsi.vhost = vh; protocol = vh->protocols; if (protocol) { n = 0; while (n < vh->count_protocols) { wsi.protocol = protocol; protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY, NULL, NULL, 0); protocol++; n++; } } vh = vh->vhost_next; } for (n = 0; n < context->count_threads; n++) { pt = &context->pt[n]; lws_libev_destroyloop(context, n); lws_libuv_destroyloop(context, n); lws_free_set_NULL(context->pt[n].serv_buf); if (pt->ah_pool) lws_free(pt->ah_pool); if (pt->http_header_data) lws_free(pt->http_header_data); } lws_plat_context_early_destroy(context); lws_ssl_context_destroy(context); if (context->pt[0].fds) lws_free_set_NULL(context->pt[0].fds); /* free all the vhost allocations */ vh = context->vhost_list; while (vh) { protocol = vh->protocols; if (protocol) { n = 0; while (n < vh->count_protocols) { if (vh->protocol_vh_privs && vh->protocol_vh_privs[n]) { lws_free(vh->protocol_vh_privs[n]); vh->protocol_vh_privs[n] = NULL; } protocol++; n++; } } if (vh->protocol_vh_privs) lws_free(vh->protocol_vh_privs); lws_ssl_SSL_CTX_destroy(vh); lws_free(vh->same_vh_protocol_list); #ifdef LWS_WITH_PLUGINS if (context->plugin_list) lws_free((void *)vh->protocols); #ifndef LWS_NO_EXTENSIONS if (context->plugin_extension_count) lws_free((void *)vh->extensions); #endif #endif #ifdef LWS_WITH_ACCESS_LOG if (vh->log_fd != (int)LWS_INVALID_FILE) close(vh->log_fd); #endif vh1 = vh->vhost_next; lws_free(vh); vh = vh1; } lws_plat_context_late_destroy(context); lws_free(context); }
struct lws * lws_client_connect_2(struct lws *wsi) { #ifdef LWS_USE_IPV6 struct sockaddr_in6 server_addr6; struct sockaddr_in6 client_addr6; struct addrinfo hints, *result; #endif struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct sockaddr_in server_addr4; struct sockaddr_in client_addr4; struct lws_pollfd pfd; struct sockaddr *v; int n, plen = 0; const char *ads; lwsl_client("%s\n", __func__); /* proxy? */ if (context->http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), wsi->u.hdr.ah->c_port); if (context->proxy_basic_auth_token[0]) plen += sprintf((char *)pt->serv_buf + plen, "Proxy-authorization: basic %s\x0d\x0a", context->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); ads = context->http_proxy_address; #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) { memset(&server_addr6, 0, sizeof(struct sockaddr_in6)); server_addr6.sin6_port = htons(context->http_proxy_port); } else #endif server_addr4.sin_port = htons(context->http_proxy_port); } else { ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) { memset(&server_addr6, 0, sizeof(struct sockaddr_in6)); server_addr6.sin6_port = htons(wsi->u.hdr.ah->c_port); } else #endif server_addr4.sin_port = htons(wsi->u.hdr.ah->c_port); } /* * prepare the actual connection (to the proxy, if any) */ lwsl_client("%s: address %s\n", __func__, ads); #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) { memset(&hints, 0, sizeof(struct addrinfo)); #if !defined(__ANDROID__) hints.ai_family = AF_INET6; hints.ai_flags = AI_V4MAPPED; #endif n = getaddrinfo(ads, NULL, &hints, &result); if (n) { #ifdef _WIN32 lwsl_err("getaddrinfo: %ls\n", gai_strerrorW(n)); #else lwsl_err("getaddrinfo: %s\n", gai_strerror(n)); #endif goto oom4; } server_addr6.sin6_family = AF_INET6; switch (result->ai_family) { #if defined(__ANDROID__) case AF_INET: /* map IPv4 to IPv6 */ bzero((char *)&server_addr6.sin6_addr, sizeof(struct in6_addr)); server_addr6.sin6_addr.s6_addr[10] = 0xff; server_addr6.sin6_addr.s6_addr[11] = 0xff; memcpy(&server_addr6.sin6_addr.s6_addr[12], &((struct sockaddr_in *)result->ai_addr)->sin_addr, sizeof(struct in_addr)); break; #endif case AF_INET6: memcpy(&server_addr6.sin6_addr, &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, sizeof(struct in6_addr)); break; default: lwsl_err("Unknown address family\n"); freeaddrinfo(result); goto oom4; } freeaddrinfo(result); } else #endif { struct addrinfo ai, *res, *result; void *p = NULL; memset (&ai, 0, sizeof ai); ai.ai_family = PF_UNSPEC; ai.ai_socktype = SOCK_STREAM; ai.ai_flags = AI_CANONNAME; if (getaddrinfo(ads, NULL, &ai, &result)) goto oom4; res = result; while (!p && res) { switch (res->ai_family) { case AF_INET: p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; break; } res = res->ai_next; } if (!p) { freeaddrinfo(result); goto oom4; } server_addr4.sin_family = AF_INET; server_addr4.sin_addr = *((struct in_addr *)p); bzero(&server_addr4.sin_zero, 8); freeaddrinfo(result); } if (!lws_socket_is_valid(wsi->sock)) { #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) wsi->sock = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->sock = socket(AF_INET, SOCK_STREAM, 0); if (!lws_socket_is_valid(wsi->sock)) { lwsl_warn("Unable to open socket\n"); goto oom4; } if (lws_plat_set_socket_options(context, wsi->sock)) { lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->sock); goto oom4; } wsi->mode = LWSCM_WSCL_WAITING_CONNECT; lws_libev_accept(wsi, wsi->sock); if (insert_wsi_socket_into_fds(context, wsi)) { compatible_close(wsi->sock); goto oom4; } /* * past here, we can't simply free the structs as error * handling as oom4 does. We have to run the whole close flow. */ lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, AWAITING_TIMEOUT); #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) { v = (struct sockaddr *)&client_addr6; n = sizeof(client_addr6); bzero((char *)v, n); client_addr6.sin6_family = AF_INET6; } else #endif { v = (struct sockaddr *)&client_addr4; n = sizeof(client_addr4); bzero((char *)v, n); client_addr4.sin_family = AF_INET; } if (context->iface) { if (interface_to_sa(context, context->iface, (struct sockaddr_in *)v, n) < 0) { lwsl_err("Unable to find interface %s\n", context->iface); goto failed; } if (bind(wsi->sock, v, n) < 0) { lwsl_err("Error binding to interface %s", context->iface); goto failed; } } } #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) { v = (struct sockaddr *)&server_addr6; n = sizeof(struct sockaddr_in6); } else #endif { v = (struct sockaddr *)&server_addr4; n = sizeof(struct sockaddr); } if (connect(wsi->sock, v, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || LWS_ERRNO == LWS_EWOULDBLOCK #ifdef _WIN32 || LWS_ERRNO == WSAEINVAL #endif ) { lwsl_client("nonblocking connect retry\n"); /* * must do specifically a POLLOUT poll to hear * about the connect completion */ if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) goto failed; lws_libev_io(wsi, LWS_EV_START | LWS_EV_WRITE); return wsi; } if (LWS_ERRNO != LWS_EISCONN) { lwsl_debug("Connect failed errno=%d\n", LWS_ERRNO); goto failed; } } lwsl_client("connected\n"); /* we are connected to server, or proxy */ if (context->http_proxy_port) { /* * OK from now on we talk via the proxy, so connect to that * * (will overwrite existing pointer, * leaving old string/frag there but unreferenced) */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, context->http_proxy_address)) goto failed; wsi->u.hdr.ah->c_port = context->http_proxy_port; n = send(wsi->sock, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; return wsi; } /* * provoke service to issue the handshake directly * we need to do it this way because in the proxy case, this is the * next state and executed only if and when we get a good proxy * response inside the state machine... but notice in SSL case this * may not have sent anything yet with 0 return, and won't until some * many retries from main loop. To stop that becoming endless, * cover with a timeout. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; pfd.fd = wsi->sock; pfd.revents = LWS_POLLIN; n = lws_service_fd(context, &pfd); if (n < 0) goto failed; if (n) /* returns 1 on failure after closing wsi */ return NULL; return wsi; oom4: lws_free_header_table(wsi); lws_free(wsi); return NULL; failed: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; }
/** * lws_context_destroy() - Destroy the websocket context * @context: Websocket context * * This function closes any active connections and then frees the * context. After calling this, any further use of the context is * undefined. */ LWS_VISIBLE void lws_context_destroy(struct lws_context *context) { const struct lws_protocols *protocol = NULL; struct lws wsi; int n; lwsl_notice("%s\n", __func__); if (!context) return; memset(&wsi, 0, sizeof(wsi)); wsi.context = context; #ifdef LWS_LATENCY if (context->worst_latency_info[0]) lwsl_notice("Worst latency: %s\n", context->worst_latency_info); #endif for (n = 0; n < context->fds_count; n++) { struct lws *wsi = wsi_from_fd(context, context->fds[n].fd); if (!wsi) continue; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY /* no protocol close */); n--; } /* * give all extensions a chance to clean up any per-context * allocations they might have made */ n = lws_ext_cb_all_exts(context, NULL, LWS_EXT_CALLBACK_SERVER_CONTEXT_DESTRUCT, NULL, 0); n = lws_ext_cb_all_exts(context, NULL, LWS_EXT_CALLBACK_CLIENT_CONTEXT_DESTRUCT, NULL, 0); /* * inform all the protocols that they are done and will have no more * callbacks */ protocol = context->protocols; if (protocol) { while (protocol->callback) { protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY, NULL, NULL, 0); protocol++; } } #ifdef LWS_USE_LIBEV ev_io_stop(context->io_loop, &context->w_accept.watcher); if(context->use_ev_sigint) ev_signal_stop(context->io_loop, &context->w_sigint.watcher); #endif /* LWS_USE_LIBEV */ 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); lws_free(context); }
void lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) { struct lws_context_per_thread *pt; struct lws **pwsi, *wsi1, *wsi2; struct lws_context *context; struct lws_tokens eff_buf; int n, m, ret; if (!wsi) return; context = wsi->context; pt = &context->pt[(int)wsi->tsi]; /* if we have children, close them first */ if (wsi->child_list) { wsi2 = wsi->child_list; while (wsi2) { //lwsl_notice("%s: closing %p: close child %p\n", // __func__, wsi, wsi2); wsi1 = wsi2->sibling_list; //lwsl_notice("%s: closing %p: next sibling %p\n", // __func__, wsi2, wsi1); wsi2->parent = NULL; /* stop it doing shutdown processing */ wsi2->socket_is_permanently_unusable = 1; lws_close_free_wsi(wsi2, reason); wsi2 = wsi1; } wsi->child_list = NULL; } #ifdef LWS_WITH_CGI if (wsi->mode == LWSCM_CGI) { /* we are not a network connection, but a handler for CGI io */ if (wsi->parent && wsi->parent->cgi) /* end the binding between us and master */ wsi->parent->cgi->stdwsi[(int)wsi->cgi_channel] = NULL; wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } if (wsi->cgi) { /* we have a cgi going, we must kill it */ wsi->cgi->being_closed = 1; lws_cgi_kill(wsi); } #endif if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED && wsi->u.http.fd != LWS_INVALID_FILE) { lws_plat_file_close(wsi, wsi->u.http.fd); wsi->u.http.fd = LWS_INVALID_FILE; context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); } if (wsi->socket_is_permanently_unusable || reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || wsi->state == LWSS_SHUTDOWN) goto just_kill_connection; wsi->state_pre_close = wsi->state; switch (wsi->state_pre_close) { case LWSS_DEAD_SOCKET: return; /* we tried the polite way... */ case LWSS_AWAITING_CLOSE_ACK: goto just_kill_connection; case LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE: if (wsi->trunc_len) { lws_callback_on_writable(wsi); return; } lwsl_info("wsi %p completed LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); goto just_kill_connection; default: if (wsi->trunc_len) { lwsl_info("wsi %p entering LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); return; } break; } if (wsi->mode == LWSCM_WSCL_WAITING_CONNECT || wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE) goto just_kill_connection; if (wsi->mode == LWSCM_HTTP_SERVING) context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); if (wsi->mode == LWSCM_HTTP_CLIENT) context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_CLIENT_HTTP, wsi->user_space, NULL, 0); /* * are his extensions okay with him closing? Eg he might be a mux * parent and just his ch1 aspect is closing? */ if (lws_ext_cb_active(wsi, LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE, NULL, 0) > 0) { lwsl_ext("extension vetoed close\n"); return; } /* * flush any tx pending from extensions, since we may send close packet * if there are problems with send, just nuke the connection */ do { ret = 0; eff_buf.token = NULL; eff_buf.token_len = 0; /* show every extension the new incoming data */ m = lws_ext_cb_active(wsi, LWS_EXT_CB_FLUSH_PENDING_TX, &eff_buf, 0); if (m < 0) { lwsl_ext("Extension reports fatal error\n"); goto just_kill_connection; } if (m) /* * at least one extension told us he has more * to spill, so we will go around again after */ ret = 1; /* assuming they left us something to send, send it */ if (eff_buf.token_len) if (lws_issue_raw(wsi, (unsigned char *)eff_buf.token, eff_buf.token_len) != eff_buf.token_len) { lwsl_debug("close: ext spill failed\n"); goto just_kill_connection; } } while (ret); /* * signal we are closing, lws_write will * add any necessary version-specific stuff. If the write fails, * no worries we are closing anyway. If we didn't initiate this * close, then our state has been changed to * LWSS_RETURNED_CLOSE_ALREADY and we will skip this. * * Likewise if it's a second call to close this connection after we * sent the close indication to the peer already, we are in state * LWSS_AWAITING_CLOSE_ACK and will skip doing this a second time. */ if (wsi->state_pre_close == LWSS_ESTABLISHED && (wsi->u.ws.close_in_ping_buffer_len || /* already a reason */ (reason != LWS_CLOSE_STATUS_NOSTATUS && (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) { lwsl_debug("sending close indication...\n"); /* if no prepared close reason, use 1000 and no aux data */ if (!wsi->u.ws.close_in_ping_buffer_len) { wsi->u.ws.close_in_ping_buffer_len = 2; wsi->u.ws.ping_payload_buf[LWS_PRE] = (reason >> 16) & 0xff; wsi->u.ws.ping_payload_buf[LWS_PRE + 1] = reason & 0xff; }
struct lws * lws_client_connect_2(struct lws *wsi) { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; const char *adsin; struct lws *wsi_piggyback = NULL; struct lws_pollfd pfd; ssize_t plen = 0; #endif struct addrinfo *result; #if defined(LWS_WITH_UNIX_SOCK) struct sockaddr_un sau; char unix_skt = 0; #endif const char *ads; sockaddr46 sa46; const struct sockaddr *psa; int n, port; const char *cce = "", *iface; const char *meth = NULL; #ifdef LWS_WITH_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); #if defined(__ANDROID__) ipv6only = 0; #endif #endif lwsl_client("%s: %p\n", __func__, wsi); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) if (!wsi->http.ah) { cce = "ah was NULL at cc2"; lwsl_err("%s\n", cce); goto oom4; } /* we can only piggyback GET or POST */ meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (meth && strcmp(meth, "GET") && strcmp(meth, "POST")) goto create_new_conn; /* we only pipeline connections that said it was okay */ if (!wsi->client_pipeline) goto create_new_conn; /* * let's take a look first and see if there are any already-active * client connections we can piggy-back on. */ adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, wsi->vhost->dll_active_client_conns.next) { struct lws *w = lws_container_of(d, struct lws, dll_active_client_conns); lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin, w->client_hostname_copy, wsi->c_port, w->c_port); if (w != wsi && w->client_hostname_copy && !strcmp(adsin, w->client_hostname_copy) && #if defined(LWS_WITH_TLS) (wsi->tls.use_ssl & LCCSCF_USE_SSL) == (w->tls.use_ssl & LCCSCF_USE_SSL) && #endif wsi->c_port == w->c_port) { /* someone else is already connected to the right guy */ /* do we know for a fact pipelining won't fly? */ if (w->keepalive_rejected) { lwsl_info("defeating pipelining due to no " "keepalive on server\n"); lws_vhost_unlock(wsi->vhost); /* } ---------- */ goto create_new_conn; } #if defined (LWS_WITH_HTTP2) /* * h2: in usable state already: just use it without * going through the queue */ if (w->client_h2_alpn && (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || lwsi_state(w) == LRS_ESTABLISHED)) { lwsl_info("%s: just join h2 directly\n", __func__); wsi->client_h2_alpn = 1; lws_wsi_h2_adopt(w, wsi); lws_vhost_unlock(wsi->vhost); /* } ---------- */ return wsi; } #endif lwsl_info("applying %p to txn queue on %p (wsistate 0x%x)\n", wsi, w, w->wsistate); /* * ...let's add ourselves to his transaction queue... * we are adding ourselves at the HEAD */ lws_dll_lws_add_front(&wsi->dll_client_transaction_queue, &w->dll_client_transaction_queue_head); /* * h1: pipeline our headers out on him, * and wait for our turn at client transaction_complete * to take over parsing the rx. */ wsi_piggyback = w; lws_vhost_unlock(wsi->vhost); /* } ---------- */ goto send_hs; } } lws_end_foreach_dll_safe(d, d1); lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */ create_new_conn: #endif /* * clients who will create their own fresh connection keep a copy of * the hostname they originally connected to, in case other connections * want to use it too */ if (!wsi->client_hostname_copy) wsi->client_hostname_copy = lws_strdup(lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS)); /* * If we made our own connection, and we're doing a method that can take * a pipeline, we are an "active client connection". * * Add ourselves to the vhost list of those so that others can * piggyback on our transaction queue */ if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) && lws_dll_is_null(&wsi->dll_client_transaction_queue) && lws_dll_is_null(&wsi->dll_active_client_conns)) { lws_vhost_lock(wsi->vhost); /* caution... we will have to unpick this on oom4 path */ lws_dll_lws_add_front(&wsi->dll_active_client_conns, &wsi->vhost->dll_active_client_conns); lws_vhost_unlock(wsi->vhost); } /* * unix socket destination? */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); #if defined(LWS_WITH_UNIX_SOCK) if (*ads == '+') { ads++; memset(&sau, 0, sizeof(sau)); sau.sun_family = AF_UNIX; strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); sau.sun_path[sizeof(sau.sun_path) - 1] = '\0'; lwsl_info("%s: Unix skt: %s\n", __func__, ads); if (sau.sun_path[0] == '@') sau.sun_path[0] = '\0'; unix_skt = 1; goto ads_known; } #endif /* * start off allowing ipv6 on connection if vhost allows it */ wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* Decide what it is we need to connect to: * * Priority 1: connect to http proxy */ if (wsi->vhost->http.http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), wsi->c_port); if (wsi->vhost->proxy_basic_auth_token[0]) plen += sprintf((char *)pt->serv_buf + plen, "Proxy-authorization: basic %s\x0d\x0a", wsi->vhost->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); ads = wsi->vhost->http.http_proxy_address; port = wsi->vhost->http.http_proxy_port; #else if (0) { #endif #if defined(LWS_WITH_SOCKS5) /* Priority 2: Connect to SOCK5 Proxy */ } else if (wsi->vhost->socks_proxy_port) { socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); lwsl_client("Sending SOCKS Greeting\n"); ads = wsi->vhost->socks_proxy_address; port = wsi->vhost->socks_proxy_port; #endif } else { /* Priority 3: Connect directly */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; } /* * prepare the actual connection * to whatever we decided to connect to */ lwsl_info("%s: %p: address %s\n", __func__, wsi, ads); n = lws_getaddrinfo46(wsi, ads, &result); #ifdef LWS_WITH_IPV6 if (wsi->ipv6) { struct sockaddr_in6 *sa6; if (n || !result) { /* lws_getaddrinfo46 failed, there is no usable result */ lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", __func__, n); cce = "ipv6 lws_getaddrinfo46 failed"; goto oom4; } sa6 = ((struct sockaddr_in6 *)result->ai_addr); memset(&sa46, 0, sizeof(sa46)); sa46.sa6.sin6_family = AF_INET6; switch (result->ai_family) { case AF_INET: if (ipv6only) break; /* map IPv4 to IPv6 */ bzero((char *)&sa46.sa6.sin6_addr, sizeof(sa46.sa6.sin6_addr)); sa46.sa6.sin6_addr.s6_addr[10] = 0xff; sa46.sa6.sin6_addr.s6_addr[11] = 0xff; memcpy(&sa46.sa6.sin6_addr.s6_addr[12], &((struct sockaddr_in *)result->ai_addr)->sin_addr, sizeof(struct in_addr)); lwsl_notice("uplevelling AF_INET to AF_INET6\n"); break; case AF_INET6: memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr, sizeof(struct in6_addr)); sa46.sa6.sin6_scope_id = sa6->sin6_scope_id; sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo; break; default: lwsl_err("Unknown address family\n"); freeaddrinfo(result); cce = "unknown address family"; goto oom4; } } else #endif /* use ipv6 */ /* use ipv4 */ { void *p = NULL; if (!n) { struct addrinfo *res = result; /* pick the first AF_INET (IPv4) result */ while (!p && res) { switch (res->ai_family) { case AF_INET: p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; break; } res = res->ai_next; } #if defined(LWS_FALLBACK_GETHOSTBYNAME) } else if (n == EAI_SYSTEM) { struct hostent *host; lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); host = gethostbyname(ads); if (host) { p = host->h_addr; } else { lwsl_err("gethostbyname failed\n"); cce = "gethostbyname (ipv4) failed"; goto oom4; } #endif } else { lwsl_err("getaddrinfo failed: %d\n", n); cce = "getaddrinfo failed"; goto oom4; } if (!p) { if (result) freeaddrinfo(result); lwsl_err("Couldn't identify address\n"); cce = "unable to lookup address"; goto oom4; } sa46.sa4.sin_family = AF_INET; sa46.sa4.sin_addr = *((struct in_addr *)p); bzero(&sa46.sa4.sin_zero, 8); } if (result) freeaddrinfo(result); #if defined(LWS_WITH_UNIX_SOCK) ads_known: #endif /* now we decided on ipv4 or ipv6, set the port */ if (!lws_socket_is_valid(wsi->desc.sockfd)) { if (wsi->context->event_loop_ops->check_client_connect_ok && wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { cce = "waiting for event loop watcher to close"; goto oom4; } #if defined(LWS_WITH_UNIX_SOCK) if (unix_skt) { wsi->unix_skt = 1; wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); } else #endif { #ifdef LWS_WITH_IPV6 if (wsi->ipv6) wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); } if (!lws_socket_is_valid(wsi->desc.sockfd)) { lwsl_warn("Unable to open socket\n"); cce = "unable to open socket"; goto oom4; } if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd, #if defined(LWS_WITH_UNIX_SOCK) unix_skt)) { #else 0)) { #endif lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->desc.sockfd); cce = "set socket opts failed"; goto oom4; } lwsi_set_state(wsi, LRS_WAITING_CONNECT); if (wsi->context->event_loop_ops->accept) if (wsi->context->event_loop_ops->accept(wsi)) { compatible_close(wsi->desc.sockfd); cce = "event loop accept failed"; goto oom4; } if (__insert_wsi_socket_into_fds(wsi->context, wsi)) { compatible_close(wsi->desc.sockfd); cce = "insert wsi failed"; goto oom4; } lws_change_pollfd(wsi, 0, LWS_POLLIN); /* * past here, we can't simply free the structs as error * handling as oom4 does. We have to run the whole close flow. */ if (!wsi->protocol) wsi->protocol = &wsi->vhost->protocols[0]; wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, wsi->user_space, NULL, 0); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, AWAITING_TIMEOUT); iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (iface) { n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); if (n < 0) { cce = "unable to bind socket"; goto failed; } } } #if defined(LWS_WITH_UNIX_SOCK) if (unix_skt) { psa = (const struct sockaddr *)&sau; n = sizeof(sau); } else #endif { #ifdef LWS_WITH_IPV6 if (wsi->ipv6) { sa46.sa6.sin6_port = htons(port); n = sizeof(struct sockaddr_in6); psa = (const struct sockaddr *)&sa46; } else #endif { sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); psa = (const struct sockaddr *)&sa46; } } if (connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || LWS_ERRNO == LWS_EWOULDBLOCK #ifdef _WIN32 || LWS_ERRNO == WSAEINVAL #endif ) { lwsl_client("nonblocking connect retry (errno = %d)\n", LWS_ERRNO); if (lws_plat_check_connection_error(wsi)) { cce = "socket connect failed"; goto failed; } /* * must do specifically a POLLOUT poll to hear * about the connect completion */ if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { cce = "POLLOUT set failed"; goto failed; } return wsi; } if (LWS_ERRNO != LWS_EISCONN) { lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); cce = "connect failed"; goto failed; } } lwsl_client("connected\n"); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* we are connected to server, or proxy */ /* http proxy */ if (wsi->vhost->http.http_proxy_port) { /* * OK from now on we talk via the proxy, so connect to that * * (will overwrite existing pointer, * leaving old string/frag there but unreferenced) */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, wsi->vhost->http.http_proxy_address)) goto failed; wsi->c_port = wsi->vhost->http.http_proxy_port; n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); cce = "proxy write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY); return wsi; } #endif #if defined(LWS_WITH_SOCKS5) /* socks proxy */ else if (wsi->vhost->socks_proxy_port) { n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing socks greeting\n"); cce = "socks write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, AWAITING_TIMEOUT); lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY); return wsi; } #endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) send_hs: if (wsi_piggyback && !lws_dll_is_null(&wsi->dll_client_transaction_queue)) { /* * We are pipelining on an already-established connection... * we can skip tls establishment. */ lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); /* * we can't send our headers directly, because they have to * be sent when the parent is writeable. The parent will check * for anybody on his client transaction queue that is in * LRS_H1C_ISSUE_HANDSHAKE2, and let them write. * * If we are trying to do this too early, before the master * connection has written his own headers, then it will just * wait in the queue until it's possible to send them. */ lws_callback_on_writable(wsi_piggyback); lwsl_info("%s: wsi %p: waiting to send headers (parent state %x)\n", __func__, wsi, lwsi_state(wsi_piggyback)); } else { lwsl_info("%s: wsi %p: client creating own connection\n", __func__, wsi); /* we are making our own connection */ lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE); /* * provoke service to issue the handshake directly. * * we need to do it this way because in the proxy case, this is * the next state and executed only if and when we get a good * proxy response inside the state machine... but notice in * SSL case this may not have sent anything yet with 0 return, * and won't until many retries from main loop. To stop that * becoming endless, cover with a timeout. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT); pfd.fd = wsi->desc.sockfd; pfd.events = LWS_POLLIN; pfd.revents = LWS_POLLIN; n = lws_service_fd(context, &pfd); if (n < 0) { cce = "first service failed"; goto failed; } if (n) /* returns 1 on failure after closing wsi */ return NULL; } #endif return wsi; oom4: if (lwsi_role_client(wsi) /* && lwsi_state_est(wsi) */) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; } /* take care that we might be inserted in fds already */ if (wsi->position_in_fds_table != LWS_NO_FDS_POS) goto failed1; /* * We can't be an active client connection any more, if we thought * that was what we were going to be doing. It should be if we are * failing by oom4 path, we are still called by * lws_client_connect_via_info() and will be returning NULL to that, * so nobody else should have had a chance to queue on us. */ { struct lws_vhost *vhost = wsi->vhost; lws_vhost_lock(vhost); __lws_free_wsi(wsi); lws_vhost_unlock(vhost); } return NULL; failed: wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; failed1: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); return NULL; }
int lws_client_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; const char *cce = NULL; unsigned char c; char *sb = p; int n = 0, len = 0; #if defined(LWS_WITH_SOCKS5) char conn_mode = 0, pending_timeout = 0; #endif switch (wsi->mode) { case LWSCM_WSCL_WAITING_CONNECT: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c */ if (!lws_client_connect_2(wsi)) { /* closed */ lwsl_client("closed\n"); return -1; } /* either still pending connection, or changed mode */ return 0; #if defined(LWS_WITH_SOCKS5) /* SOCKS Greeting Reply */ case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("SOCKS connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("SOCKS read returned EAGAIN..." "retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from SOCKS socket\n"); return 0; } /* processing greeting reply */ if (pt->serv_buf[0] == SOCKS_VERSION_5 && pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { lwsl_client("%s\n", "SOCKS greeting reply received " "- No Authentication Method"); socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len); conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; lwsl_client("%s\n", "Sending SOCKS connect command"); } else if (pt->serv_buf[0] == SOCKS_VERSION_5 && pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { lwsl_client("%s\n", "SOCKS greeting reply received " "- User Name Password Method"); socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD, (size_t *)&len); conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; lwsl_client("%s\n", "Sending SOCKS user/password"); } else { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR SOCKS greeting reply failed, method " "code: %d\n", pt->serv_buf[1]); return 0; } n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing socks command to socks proxy " "socket\n"); return 0; } lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); wsi->mode = conn_mode; break; /* SOCKS auth Reply */ case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("SOCKS connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("SOCKS read returned EAGAIN... " "retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from socks socket\n"); return 0; } /* processing auth reply */ if (pt->serv_buf[0] == SOCKS_SUBNEGOTIATION_VERSION_1 && pt->serv_buf[1] == SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) { lwsl_client("%s\n", "SOCKS password reply recieved - " "successful"); socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len); conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; lwsl_client("%s\n", "Sending SOCKS connect command"); } else { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR : SOCKS user/password reply failed, " "error code: %d\n", pt->serv_buf[1]); return 0; } n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing connect command to SOCKS " "socket\n"); return 0; } lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); wsi->mode = conn_mode; break; /* SOCKS connect command Reply */ case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("SOCKS connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("SOCKS read returned EAGAIN... " "retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from socks socket\n"); return 0; } /* processing connect reply */ if (pt->serv_buf[0] == SOCKS_VERSION_5 && pt->serv_buf[1] == SOCKS_REQUEST_REPLY_SUCCESS) { lwsl_client("%s\n", "SOCKS connect reply recieved - " "successful"); } else { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR SOCKS connect reply failed, error " "code: %d\n", pt->serv_buf[1]); return 0; } /* free stash since we are done with it */ lws_free_set_NULL(wsi->u.hdr.stash); if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, wsi->vhost->socks_proxy_address)) goto bail3; wsi->c_port = wsi->vhost->socks_proxy_port; /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); goto start_ws_hanshake; #endif case LWSCM_WSCL_WAITING_PROXY_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("Proxy connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("Proxy read returned EAGAIN... retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from proxy socket\n"); return 0; } pt->serv_buf[13] = '\0'; if (strcmp(sb, "HTTP/1.0 200 ") && strcmp(sb, "HTTP/1.1 200 ")) { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR proxy: %s\n", sb); return 0; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c * * take care of our lws_callback_on_writable * happening at a time when there's no real connection yet */ #if defined(LWS_WITH_SOCKS5) start_ws_hanshake: #endif if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return -1; #ifdef LWS_OPENSSL_SUPPORT /* we can retry this... just cook the SSL BIO the first time */ if (wsi->use_ssl && !wsi->ssl) { if (lws_ssl_client_bio_create(wsi)) return -1; } if (wsi->use_ssl) { n = lws_ssl_client_connect1(wsi); if (!n) return 0; if (n < 0) { cce = "lws_ssl_client_connect1 failed"; goto bail3; } } else wsi->ssl = NULL; /* fallthru */ case LWSCM_WSCL_WAITING_SSL: if (wsi->use_ssl) { n = lws_ssl_client_connect2(wsi); if (!n) return 0; if (n < 0) { cce = "lws_ssl_client_connect2 failed"; goto bail3; } } else wsi->ssl = NULL; #endif wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, context->timeout_secs); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { if (wsi->mode == LWSCM_RAW) return 0; lwsl_err("Failed to generate handshake for client\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } /* send our request to the server */ lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); lws_latency(context, wsi, "send lws_issue_raw", n, n == p - sb); switch (n) { case LWS_SSL_CAPABLE_ERROR: lwsl_debug("ERROR writing to client socket\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; case LWS_SSL_CAPABLE_MORE_SERVICE: lws_callback_on_writable(wsi); break; } if (wsi->client_http_body_pending) { wsi->mode = LWSCM_WSCL_ISSUE_HTTP_BODY; lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, context->timeout_secs); /* user code must ask for writable callback */ break; } goto client_http_body_sent; case LWSCM_WSCL_ISSUE_HTTP_BODY: if (wsi->client_http_body_pending) { lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, context->timeout_secs); /* user code must ask for writable callback */ break; } client_http_body_sent: wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.hdr.lextable_pos = 0; wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, context->timeout_secs); break; case LWSCM_WSCL_WAITING_SERVER_REPLY: /* handle server hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_debug("Server connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); cce = "Peer hung up"; goto bail3; } if (!(pollfd->revents & LWS_POLLIN)) break; /* interpret the server response */ /* * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== * Sec-WebSocket-Protocol: chat */ /* * we have to take some care here to only take from the * socket bytewise. The browser may (and has been seen to * in the case that onopen() performs websocket traffic) * coalesce both handshake response and websocket traffic * in one packet, since at that point the connection is * definitively ready from browser pov. */ len = 1; while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE && len > 0) { n = lws_ssl_capable_read(wsi, &c, 1); lws_latency(context, wsi, "send lws_issue_raw", n, n == 1); switch (n) { case 0: case LWS_SSL_CAPABLE_ERROR: cce = "read failed"; goto bail3; case LWS_SSL_CAPABLE_MORE_SERVICE: return 0; } if (lws_parse(wsi, c)) { lwsl_warn("problems parsing header\n"); goto bail3; } } /* * hs may also be coming in multiple packets, there is a 5-sec * libwebsocket timeout still active here too, so if parsing did * not complete just wait for next packet coming in this state */ if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) break; /* * otherwise deal with the handshake. If there's any * packet traffic already arrived we'll trigger poll() again * right away and deal with it that way */ return lws_client_interpret_server_handshake(wsi); bail3: lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); if (cce) lwsl_info("reason: %s\n", cce); wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); wsi->already_did_cce = 1; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return -1; case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n"); break; case LWSCM_WSCL_PENDING_CANDIDATE_CHILD: lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n"); break; default: break; } return 0; }
struct lws * lws_client_connect_2(struct lws *wsi) { sockaddr46 sa46; struct addrinfo *result; struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct lws_pollfd pfd; const char *cce = "", *iface; int n, port; ssize_t plen = 0; const char *ads; #ifdef LWS_USE_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); #if defined(__ANDROID__) ipv6only = 0; #endif #endif lwsl_client("%s\n", __func__); if (!wsi->u.hdr.ah) { cce = "ah was NULL at cc2"; lwsl_err("%s\n", cce); goto oom4; } /* * start off allowing ipv6 on connection if vhost allows it */ wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); /* Decide what it is we need to connect to: * * Priority 1: connect to http proxy */ if (wsi->vhost->http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), wsi->c_port); if (wsi->vhost->proxy_basic_auth_token[0]) plen += sprintf((char *)pt->serv_buf + plen, "Proxy-authorization: basic %s\x0d\x0a", wsi->vhost->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); ads = wsi->vhost->http_proxy_address; port = wsi->vhost->http_proxy_port; #if defined(LWS_WITH_SOCKS5) /* Priority 2: Connect to SOCK5 Proxy */ } else if (wsi->vhost->socks_proxy_port) { socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); lwsl_client("Sending SOCKS Greeting\n"); ads = wsi->vhost->socks_proxy_address; port = wsi->vhost->socks_proxy_port; #endif } else { /* Priority 3: Connect directly */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; } /* * prepare the actual connection * to whatever we decided to connect to */ lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads); n = lws_getaddrinfo46(wsi, ads, &result); #ifdef LWS_USE_IPV6 if (wsi->ipv6) { if (n) { /* lws_getaddrinfo46 failed, there is no usable result */ lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", __func__, n); cce = "ipv6 lws_getaddrinfo46 failed"; goto oom4; } memset(&sa46, 0, sizeof(sa46)); sa46.sa6.sin6_family = AF_INET6; switch (result->ai_family) { case AF_INET: if (ipv6only) break; /* map IPv4 to IPv6 */ bzero((char *)&sa46.sa6.sin6_addr, sizeof(sa46.sa6.sin6_addr)); sa46.sa6.sin6_addr.s6_addr[10] = 0xff; sa46.sa6.sin6_addr.s6_addr[11] = 0xff; memcpy(&sa46.sa6.sin6_addr.s6_addr[12], &((struct sockaddr_in *)result->ai_addr)->sin_addr, sizeof(struct in_addr)); lwsl_notice("uplevelling AF_INET to AF_INET6\n"); break; case AF_INET6: memcpy(&sa46.sa6.sin6_addr, &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, sizeof(struct in6_addr)); sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id; sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo; break; default: lwsl_err("Unknown address family\n"); freeaddrinfo(result); cce = "unknown address family"; goto oom4; } } else #endif /* use ipv6 */ /* use ipv4 */ { void *p = NULL; if (!n) { struct addrinfo *res = result; /* pick the first AF_INET (IPv4) result */ while (!p && res) { switch (res->ai_family) { case AF_INET: p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; break; } res = res->ai_next; } #if defined(LWS_FALLBACK_GETHOSTBYNAME) } else if (n == EAI_SYSTEM) { struct hostent *host; lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); host = gethostbyname(ads); if (host) { p = host->h_addr; } else { lwsl_err("gethostbyname failed\n"); cce = "gethostbyname (ipv4) failed"; goto oom4; } #endif } else { lwsl_err("getaddrinfo failed\n"); cce = "getaddrinfo failed"; goto oom4; } if (!p) { if (result) freeaddrinfo(result); lwsl_err("Couldn't identify address\n"); cce = "unable to lookup address"; goto oom4; } sa46.sa4.sin_family = AF_INET; sa46.sa4.sin_addr = *((struct in_addr *)p); bzero(&sa46.sa4.sin_zero, 8); } if (result) freeaddrinfo(result); /* now we decided on ipv4 or ipv6, set the port */ if (!lws_socket_is_valid(wsi->desc.sockfd)) { #if defined(LWS_USE_LIBUV) if (LWS_LIBUV_ENABLED(context)) if (lws_libuv_check_watcher_active(wsi)) { lwsl_warn("Waiting for libuv watcher to close\n"); cce = "waiting for libuv watcher to close"; goto oom4; } #endif #ifdef LWS_USE_IPV6 if (wsi->ipv6) wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); if (!lws_socket_is_valid(wsi->desc.sockfd)) { lwsl_warn("Unable to open socket\n"); cce = "unable to open socket"; goto oom4; } if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->desc.sockfd); cce = "set socket opts failed"; goto oom4; } wsi->mode = LWSCM_WSCL_WAITING_CONNECT; lws_libev_accept(wsi, wsi->desc); lws_libuv_accept(wsi, wsi->desc); lws_libevent_accept(wsi, wsi->desc); if (insert_wsi_socket_into_fds(context, wsi)) { compatible_close(wsi->desc.sockfd); cce = "insert wsi failed"; goto oom4; } lws_change_pollfd(wsi, 0, LWS_POLLIN); /* * past here, we can't simply free the structs as error * handling as oom4 does. We have to run the whole close flow. */ if (!wsi->protocol) wsi->protocol = &wsi->vhost->protocols[0]; wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, wsi->user_space, NULL, 0); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, AWAITING_TIMEOUT); iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (iface) { n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); if (n < 0) { cce = "unable to bind socket"; goto failed; } } } #ifdef LWS_USE_IPV6 if (wsi->ipv6) { sa46.sa6.sin6_port = htons(port); n = sizeof(struct sockaddr_in6); } else #endif { sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); } if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || LWS_ERRNO == LWS_EWOULDBLOCK #ifdef _WIN32 || LWS_ERRNO == WSAEINVAL #endif ) { lwsl_client("nonblocking connect retry (errno = %d)\n", LWS_ERRNO); if (lws_plat_check_connection_error(wsi)) { cce = "socket connect failed"; goto failed; } /* * must do specifically a POLLOUT poll to hear * about the connect completion */ if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { cce = "POLLOUT set failed"; goto failed; } return wsi; } if (LWS_ERRNO != LWS_EISCONN) { lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); cce = "connect failed"; goto failed; } } lwsl_client("connected\n"); /* we are connected to server, or proxy */ /* http proxy */ if (wsi->vhost->http_proxy_port) { /* * OK from now on we talk via the proxy, so connect to that * * (will overwrite existing pointer, * leaving old string/frag there but unreferenced) */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, wsi->vhost->http_proxy_address)) goto failed; wsi->c_port = wsi->vhost->http_proxy_port; n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); cce = "proxy write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; return wsi; } #if defined(LWS_WITH_SOCKS5) /* socks proxy */ else if (wsi->vhost->socks_proxy_port) { n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing socks greeting\n"); cce = "socks write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY; return wsi; } #endif /* * provoke service to issue the handshake directly * we need to do it this way because in the proxy case, this is the * next state and executed only if and when we get a good proxy * response inside the state machine... but notice in SSL case this * may not have sent anything yet with 0 return, and won't until some * many retries from main loop. To stop that becoming endless, * cover with a timeout. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; pfd.fd = wsi->desc.sockfd; pfd.events = LWS_POLLIN; pfd.revents = LWS_POLLIN; n = lws_service_fd(context, &pfd); if (n < 0) { cce = "first service failed"; goto failed; } if (n) /* returns 1 on failure after closing wsi */ return NULL; return wsi; oom4: /* we're closing, losing some rx is OK */ lws_header_table_force_to_detachable_state(wsi); if (wsi->mode == LWSCM_HTTP_CLIENT || wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED || wsi->mode == LWSCM_WSCL_WAITING_CONNECT) { wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; } /* take care that we might be inserted in fds already */ if (wsi->position_in_fds_table != -1) goto failed1; lws_remove_from_timeout_list(wsi); lws_header_table_detach(wsi, 0); lws_free(wsi); return NULL; failed: wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; failed1: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; }
int lws_client_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; char *sb = p; unsigned char c; int n, len; switch (wsi->mode) { case LWSCM_WSCL_WAITING_CONNECT: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c */ if (!lws_client_connect_2(wsi)) { /* closed */ lwsl_client("closed\n"); return -1; } /* either still pending connection, or changed mode */ return 0; case LWSCM_WSCL_WAITING_PROXY_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("Proxy connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->sock, sb, LWS_MAX_SOCKET_IO_BUF, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("Proxy read returned EAGAIN... retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from proxy socket\n"); return 0; } pt->serv_buf[13] = '\0'; if (strcmp(sb, "HTTP/1.0 200 ") && strcmp(sb, "HTTP/1.1 200 ")) { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR proxy: %s\n", sb); return 0; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c * * take care of our lws_callback_on_writable * happening at a time when there's no real connection yet */ if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return -1; lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); #ifdef LWS_OPENSSL_SUPPORT /* we can retry this... just cook the SSL BIO the first time */ if (wsi->use_ssl && !wsi->ssl) { #if defined(CYASSL_SNI_HOST_NAME) || defined(WOLFSSL_SNI_HOST_NAME) || defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) const char *hostname = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST); #endif wsi->ssl = SSL_new(context->ssl_client_ctx); #ifndef USE_WOLFSSL SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif /* * use server name indication (SNI), if supported, * when establishing connection */ #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL #ifdef CYASSL_SNI_HOST_NAME CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname)); #endif #else #ifdef WOLFSSL_SNI_HOST_NAME wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname)); #endif #endif #else #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME SSL_set_tlsext_host_name(wsi->ssl, hostname); #endif #endif #ifdef USE_WOLFSSL /* * wolfSSL/CyaSSL does certificate verification differently * from OpenSSL. * If we should ignore the certificate, we need to set * this before SSL_new and SSL_connect is called. * Otherwise the connect will simply fail with error * code -155 */ #ifdef USE_OLD_CYASSL if (wsi->use_ssl == 2) CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); #else if (wsi->use_ssl == 2) wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); #endif #endif /* USE_WOLFSSL */ wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE); SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL CyaSSL_set_using_nonblock(wsi->ssl, 1); #else wolfSSL_set_using_nonblock(wsi->ssl, 1); #endif #else BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ #endif SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, context); } if (wsi->use_ssl) { lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); if (n < 0) { n = SSL_get_error(wsi->ssl, n); if (n == SSL_ERROR_WANT_READ) goto some_wait; if (n == SSL_ERROR_WANT_WRITE) { /* * wants us to retry connect due to * state of the underlying ssl layer... * but since it may be stalled on * blocked write, no incoming data may * arrive to trigger the retry. * Force (possibly many times if the SSL * state persists in returning the * condition code, but other sockets * are getting serviced inbetweentimes) * us to get called back when writable. */ lwsl_info("%s: WANT_WRITE... retrying\n", __func__); lws_callback_on_writable(wsi); some_wait: wsi->mode = LWSCM_WSCL_WAITING_SSL; return 0; /* no error */ } n = -1; } if (n <= 0) { /* * retry if new data comes until we * run into the connection timeout or win */ n = ERR_get_error(); if (n != SSL_ERROR_NONE) { lwsl_err("SSL connect error %lu: %s\n", n, ERR_error_string(n, sb)); return 0; } } } else wsi->ssl = NULL; /* fallthru */ case LWSCM_WSCL_WAITING_SSL: if (wsi->use_ssl) { if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); if (n < 0) { n = SSL_get_error(wsi->ssl, n); if (n == SSL_ERROR_WANT_READ) goto some_wait; if (n == SSL_ERROR_WANT_WRITE) { /* * wants us to retry connect due to * state of the underlying ssl layer... * but since it may be stalled on * blocked write, no incoming data may * arrive to trigger the retry. * Force (possibly many times if the SSL * state persists in returning the * condition code, but other sockets * are getting serviced inbetweentimes) * us to get called back when writable. */ lwsl_info("SSL_connect WANT_WRITE... retrying\n"); lws_callback_on_writable(wsi); goto some_wait; } n = -1; } if (n <= 0) { /* * retry if new data comes until we * run into the connection timeout or win */ n = ERR_get_error(); if (n != SSL_ERROR_NONE) { lwsl_err("SSL connect error %lu: %s\n", n, ERR_error_string(n, sb)); return 0; } } } #ifndef USE_WOLFSSL /* * See comment above about wolfSSL certificate * verification */ lws_latency_pre(context, wsi); n = SSL_get_verify_result(wsi->ssl); lws_latency(context, wsi, "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); if (n != X509_V_OK) { if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && wsi->use_ssl == 2) { lwsl_notice("accepting self-signed certificate\n"); } else { lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n", n, ERR_error_string(n, sb)); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } } #endif /* USE_WOLFSSL */ } else wsi->ssl = NULL; #endif wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, AWAITING_TIMEOUT); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { lwsl_err("Failed to generate handshake for client\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } /* send our request to the server */ lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); lws_latency(context, wsi, "send lws_issue_raw", n, n == p - sb); switch (n) { case LWS_SSL_CAPABLE_ERROR: lwsl_debug("ERROR writing to client socket\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; case LWS_SSL_CAPABLE_MORE_SERVICE: lws_callback_on_writable(wsi); break; } wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.hdr.lextable_pos = 0; wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, AWAITING_TIMEOUT); break; case LWSCM_WSCL_WAITING_SERVER_REPLY: /* handle server hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_debug("Server connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto bail3; } if (!(pollfd->revents & LWS_POLLIN)) break; /* interpret the server response */ /* * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== * Sec-WebSocket-Protocol: chat */ /* * we have to take some care here to only take from the * socket bytewise. The browser may (and has been seen to * in the case that onopen() performs websocket traffic) * coalesce both handshake response and websocket traffic * in one packet, since at that point the connection is * definitively ready from browser pov. */ len = 1; while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE && len > 0) { n = lws_ssl_capable_read(wsi, &c, 1); lws_latency(context, wsi, "send lws_issue_raw", n, n == 1); switch (n) { case 0: case LWS_SSL_CAPABLE_ERROR: goto bail3; case LWS_SSL_CAPABLE_MORE_SERVICE: return 0; } if (lws_parse(wsi, c)) { lwsl_warn("problems parsing header\n"); goto bail3; } } /* * hs may also be coming in multiple packets, there is a 5-sec * libwebsocket timeout still active here too, so if parsing did * not complete just wait for next packet coming in this state */ if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) break; /* * otherwise deal with the handshake. If there's any * packet traffic already arrived we'll trigger poll() again * right away and deal with it that way */ return lws_client_interpret_server_handshake(wsi); bail3: lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return -1; case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n"); break; case LWSCM_WSCL_PENDING_CANDIDATE_CHILD: lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n"); break; default: break; } return 0; }
int lws_client_interpret_server_handshake(struct lws *wsi) { struct lws_context *context = wsi->context; int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; int n, len, okay = 0, isErrorCodeReceived = 0; const char *pc; char *p; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; char ext_name[128]; const char *c; int more = 1; void *v; #endif /* * well, what the server sent looked reasonable for syntax. * Now let's confirm it sent all the necessary headers */ if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { lwsl_info("no ACCEPT\n"); p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); isErrorCodeReceived = 1; goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); if (!p) { lwsl_info("no URI\n"); goto bail3; } if (p && strncmp(p, "101", 3)) { lwsl_warn( "lws_client_handshake: got bad HTTP response '%s'\n", p); goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); if (!p) { lwsl_info("no UPGRADE\n"); goto bail3; } strtolower(p); if (strcmp(p, "websocket")) { lwsl_warn( "lws_client_handshake: got bad Upgrade header '%s'\n", p); goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); if (!p) { lwsl_info("no Connection hdr\n"); goto bail3; } strtolower(p); if (strcmp(p, "upgrade")) { lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); goto bail3; } pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); if (!pc) { lwsl_parser("lws_client_int_s_hs: no protocol list\n"); } else lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); /* * confirm the protocol the server wants to talk was in the list * of protocols we offered */ len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); if (!len) { lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n"); /* * no protocol name to work from, * default to first protocol */ wsi->protocol = &context->protocols[0]; goto check_extensions; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); len = strlen(p); while (pc && *pc && !okay) { if (!strncmp(pc, p, len) && (pc[len] == ',' || pc[len] == '\0')) { okay = 1; continue; } while (*pc && *pc++ != ',') ; while (*pc && *pc == ' ') pc++; } if (!okay) { lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p); goto bail2; } /* * identify the selected protocol struct and set it */ n = 0; wsi->protocol = NULL; while (context->protocols[n].callback && !wsi->protocol) { if (strcmp(p, context->protocols[n].name) == 0) { wsi->protocol = &context->protocols[n]; break; } n++; } if (wsi->protocol == NULL) { lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p); goto bail2; } check_extensions: #ifndef LWS_NO_EXTENSIONS /* instantiate the accepted extensions */ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { lwsl_ext("no client extenstions allowed by server\n"); goto check_accept; } /* * break down the list of server accepted extensions * and go through matching them or identifying bogons */ if (lws_hdr_copy(wsi, (char *)context->serv_buf, sizeof(context->serv_buf), WSI_TOKEN_EXTENSIONS) < 0) { lwsl_warn("ext list from server failed to copy\n"); goto bail2; } c = (char *)context->serv_buf; n = 0; while (more) { if (*c && (*c != ',' && *c != ' ' && *c != '\t')) { ext_name[n] = *c++; if (n < sizeof(ext_name) - 1) n++; continue; } ext_name[n] = '\0'; if (!*c) more = 0; else { c++; if (!n) continue; } /* check we actually support it */ lwsl_ext("checking client ext %s\n", ext_name); n = 0; ext = lws_get_context(wsi)->extensions; while (ext && ext->callback) { if (strcmp(ext_name, ext->name)) { ext++; continue; } n = 1; lwsl_ext("instantiating client ext %s\n", ext_name); /* instantiate the extension on this conn */ wsi->active_extensions_user[ wsi->count_active_extensions] = lws_zalloc(ext->per_session_data_size); if (wsi->active_extensions_user[ wsi->count_active_extensions] == NULL) { lwsl_err("Out of mem\n"); goto bail2; } wsi->active_extensions[ wsi->count_active_extensions] = ext; /* allow him to construct his context */ ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, wsi->active_extensions_user[ wsi->count_active_extensions], NULL, 0); wsi->count_active_extensions++; ext++; } if (n == 0) { lwsl_warn("Unknown ext '%s'!\n", ext_name); goto bail2; } n = 0; } check_accept: #endif /* * Confirm his accept token is the one we precomputed */ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) { lwsl_warn("lws_client_int_s_hs: accept %s wrong vs %s\n", p, wsi->u.hdr.ah->initial_handshake_hash_base64); goto bail2; } /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); goto bail2; } /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) 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_union_transition(wsi, LWSCM_WS_CLIENT); wsi->state = LWSS_ESTABLISHED; wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, then * use a big default for compatibility */ n = wsi->protocol->rx_buffer_size; if (!n) n = LWS_MAX_SOCKET_IO_BUF; n += LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING; wsi->u.ws.rx_user_buffer = lws_malloc(n); if (!wsi->u.ws.rx_user_buffer) { lwsl_err("Out of Mem allocating rx buffer %d\n", n); goto bail2; } lwsl_info("Allocating client RX buffer %d\n", n); if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); goto bail3; } lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, wsi->user_space, NULL, 0)) goto bail3; #ifndef LWS_NO_EXTENSIONS /* * inform all extensions, not just active ones since they * already know */ ext = context->extensions; while (ext && ext->callback) { v = NULL; for (n = 0; n < wsi->count_active_extensions; n++) if (wsi->active_extensions[n] == ext) v = wsi->active_extensions_user[n]; ext->callback(context, ext, wsi, LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0); ext++; } #endif return 0; bail3: lws_free_set_NULL(wsi->u.ws.rx_user_buffer); close_reason = LWS_CLOSE_STATUS_NOSTATUS; bail2: if (wsi->protocol) { if (isErrorCodeReceived && p) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, p, (unsigned int)strlen(p)); } else { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0); } } lwsl_info("closing connection due to bail2 connection error\n"); /* free up his parsing allocations */ lws_free_set_NULL(wsi->u.hdr.ah); lws_close_free_wsi(wsi, close_reason); return 1; }
char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { char buf[128], hash[20], key_b64[40], *p = pkt; struct lws_context *context = wsi->context; const char *meth; int n; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; int ext_count = 0; #endif const char *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { meth = "GET"; wsi->do_ws = 1; } else { wsi->do_ws = 0; } if (!strcmp(meth, "RAW")) { lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); lwsl_notice("client transition to raw\n"); if (pp) { const struct lws_protocols *pr; pr = lws_vhost_name_to_protocol(wsi->vhost, pp); if (!pr) { lwsl_err("protocol %s not enabled on vhost\n", pp); return NULL; } lws_bind_protocol(wsi, pr); } if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, wsi->user_space, NULL, 0)) return NULL; lws_header_table_force_to_detachable_state(wsi); lws_union_transition(wsi, LWSCM_RAW); lws_header_table_detach(wsi, 1); return NULL; } if (wsi->do_ws) { /* * create the random key */ n = lws_get_random(context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); } /* * 04 example client handshake * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 4 */ p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) p += sprintf(p, "Origin: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); else p += sprintf(p, "Origin: http://%s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); } if (wsi->do_ws) { p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Key: "); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ #ifndef LWS_NO_EXTENSIONS ext = wsi->vhost->extensions; while (ext && ext->callback) { n = lws_ext_cb_all_exts(context, wsi, LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, (char *)ext->name, 0); if (n) { /* an extension vetos us */ lwsl_ext("ext %s vetoed\n", (char *)ext->name); ext++; continue; } n = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means * go ahead and allow the extension, * it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; else p += sprintf(p, "Sec-WebSocket-Extensions: "); p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } if (ext_count) p += sprintf(p, "\x0d\x0a"); #endif if (wsi->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ietf_spec_revision); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->u.hdr.ah->initial_handshake_hash_base64, sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); } /* give userland a chance to append, eg, cookies */ wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12); p += sprintf(p, "\x0d\x0a"); return p; }
int lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, struct lws *wsi_conn) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; struct lws *w; #if defined(LWS_WITH_TLS) char ebuf[128]; #endif const char *cce = NULL; #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) ssize_t len = 0; unsigned char c; #endif char *sb = p; int n = 0; #if defined(LWS_WITH_SOCKS5) char conn_mode = 0, pending_timeout = 0; #endif if ((pollfd->revents & LWS_POLLOUT) && wsi->keepalive_active && wsi->dll_client_transaction_queue_head.next) { struct lws *wfound = NULL; lwsl_debug("%s: pollout HANDSHAKE2\n", __func__); /* * We have a transaction queued that wants to pipeline. * * We have to allow it to send headers strictly in the order * that it was queued, ie, tail-first. */ lws_vhost_lock(wsi->vhost); lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, wsi->dll_client_transaction_queue_head.next) { struct lws *w = lws_container_of(d, struct lws, dll_client_transaction_queue); lwsl_debug("%s: %p states 0x%x\n", __func__, w, w->wsistate); if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) wfound = w; } lws_end_foreach_dll_safe(d, d1); if (wfound) { /* * pollfd has the master sockfd in it... we * need to use that in HANDSHAKE2 to understand * which wsi to actually write on */ lws_client_socket_service(wfound, pollfd, wsi); lws_callback_on_writable(wsi); } else lwsl_debug("%s: didn't find anything in txn q in HS2\n", __func__); lws_vhost_unlock(wsi->vhost); return 0; } switch (lwsi_state(wsi)) { case LRS_WAITING_CONNECT: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c */ if (!lws_client_connect_2(wsi)) { /* closed */ lwsl_client("closed\n"); return -1; } /* either still pending connection, or changed mode */ return 0; #if defined(LWS_WITH_SOCKS5) /* SOCKS Greeting Reply */ case LRS_WAITING_SOCKS_GREETING_REPLY: case LRS_WAITING_SOCKS_AUTH_REPLY: case LRS_WAITING_SOCKS_CONNECT_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("SOCKS connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto bail3; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("SOCKS read EAGAIN, retrying\n"); return 0; } lwsl_err("ERROR reading from SOCKS socket\n"); goto bail3; } switch (lwsi_state(wsi)) { case LRS_WAITING_SOCKS_GREETING_REPLY: if (pt->serv_buf[0] != SOCKS_VERSION_5) goto socks_reply_fail; if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { lwsl_client("SOCKS GR: No Auth Method\n"); socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; goto socks_send; } if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { lwsl_client("SOCKS GR: User/Pw Method\n"); socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD, &len); conn_mode = LRS_WAITING_SOCKS_AUTH_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; goto socks_send; } goto socks_reply_fail; case LRS_WAITING_SOCKS_AUTH_REPLY: if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) goto socks_reply_fail; lwsl_client("SOCKS password OK, sending connect\n"); socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; socks_send: n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to socks proxy\n"); goto bail3; } lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); lwsi_set_state(wsi, conn_mode); break; socks_reply_fail: lwsl_notice("socks reply: v%d, err %d\n", pt->serv_buf[0], pt->serv_buf[1]); goto bail3; case LRS_WAITING_SOCKS_CONNECT_REPLY: if (pt->serv_buf[0] != SOCKS_VERSION_5 || pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) goto socks_reply_fail; lwsl_client("socks connect OK\n"); /* free stash since we are done with it */ lws_client_stash_destroy(wsi); if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, wsi->vhost->socks_proxy_address)) goto bail3; wsi->c_port = wsi->vhost->socks_proxy_port; /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); goto start_ws_handshake; } break; #endif case LRS_WAITING_PROXY_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("Proxy connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto bail3; } n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("Proxy read EAGAIN... retrying\n"); return 0; } lwsl_err("ERROR reading from proxy socket\n"); goto bail3; } pt->serv_buf[13] = '\0'; if (strcmp(sb, "HTTP/1.0 200 ") && strcmp(sb, "HTTP/1.1 200 ")) { lwsl_err("ERROR proxy: %s\n", sb); goto bail3; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* fallthru */ case LRS_H1C_ISSUE_HANDSHAKE: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c * * take care of our lws_callback_on_writable * happening at a time when there's no real connection yet */ #if defined(LWS_WITH_SOCKS5) start_ws_handshake: #endif if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return -1; #if defined(LWS_WITH_TLS) /* we can retry this... just cook the SSL BIO the first time */ if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !wsi->tls.ssl && lws_ssl_client_bio_create(wsi) < 0) { cce = "bio_create failed"; goto bail3; } if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { n = lws_ssl_client_connect1(wsi); if (!n) return 0; if (n < 0) { cce = "lws_ssl_client_connect1 failed"; goto bail3; } } else wsi->tls.ssl = NULL; /* fallthru */ case LRS_WAITING_SSL: if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf)); if (!n) return 0; if (n < 0) { cce = ebuf; goto bail3; } } else wsi->tls.ssl = NULL; #endif #if defined (LWS_WITH_HTTP2) if (wsi->client_h2_alpn) { /* * We connected to the server and set up tls, and * negotiated "h2". * * So this is it, we are an h2 master client connection * now, not an h1 client connection. */ lws_tls_server_conn_alpn(wsi); /* send the H2 preface to legitimize the connection */ if (lws_h2_issue_preface(wsi)) { cce = "error sending h2 preface"; goto bail3; } break; } #endif lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, context->timeout_secs); /* fallthru */ case LRS_H1C_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { if (wsi->role_ops == &role_ops_raw_skt || wsi->role_ops == &role_ops_raw_file) return 0; lwsl_err("Failed to generate handshake for client\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "chs"); return 0; } /* send our request to the server */ lws_latency_pre(context, wsi); w = _lws_client_wsi_master(wsi); lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p (wsistate 0x%x 0x%x)\n", __func__, wsi, w, wsi->wsistate, w->wsistate); n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb)); lws_latency(context, wsi, "send lws_issue_raw", n, n == p - sb); switch (n) { case LWS_SSL_CAPABLE_ERROR: lwsl_debug("ERROR writing to client socket\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cws"); return 0; case LWS_SSL_CAPABLE_MORE_SERVICE: lws_callback_on_writable(wsi); break; } if (wsi->client_http_body_pending) { lwsi_set_state(wsi, LRS_ISSUE_HTTP_BODY); lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, context->timeout_secs); /* user code must ask for writable callback */ break; } lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); wsi->hdr_parsing_completed = 0; if (lwsi_state(w) == LRS_IDLING) { lwsi_set_state(w, LRS_WAITING_SERVER_REPLY); w->hdr_parsing_completed = 0; #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) w->http.ah->parser_state = WSI_TOKEN_NAME_PART; w->http.ah->lextable_pos = 0; /* If we're (re)starting on headers, need other implied init */ wsi->http.ah->ues = URIES_IDLE; #endif } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, wsi->context->timeout_secs); lws_callback_on_writable(w); goto client_http_body_sent; case LRS_ISSUE_HTTP_BODY: if (wsi->client_http_body_pending) { //lws_set_timeout(wsi, // PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, // context->timeout_secs); /* user code must ask for writable callback */ break; } client_http_body_sent: #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* prepare ourselves to do the parsing */ wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; wsi->http.ah->lextable_pos = 0; #endif lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, context->timeout_secs); break; case LRS_WAITING_SERVER_REPLY: /* * handle server hanging up on us... * but if there is POLLIN waiting, handle that first */ if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == LWS_POLLHUP) { lwsl_debug("Server connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); cce = "Peer hung up"; goto bail3; } if (!(pollfd->revents & LWS_POLLIN)) break; #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* interpret the server response * * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== * Sec-WebSocket-Protocol: chat * * we have to take some care here to only take from the * socket bytewise. The browser may (and has been seen to * in the case that onopen() performs websocket traffic) * coalesce both handshake response and websocket traffic * in one packet, since at that point the connection is * definitively ready from browser pov. */ len = 1; while (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE && len > 0) { int plen = 1; n = lws_ssl_capable_read(wsi, &c, 1); lws_latency(context, wsi, "send lws_issue_raw", n, n == 1); switch (n) { case 0: case LWS_SSL_CAPABLE_ERROR: cce = "read failed"; goto bail3; case LWS_SSL_CAPABLE_MORE_SERVICE: return 0; } if (lws_parse(wsi, &c, &plen)) { lwsl_warn("problems parsing header\n"); goto bail3; } } /* * hs may also be coming in multiple packets, there is a 5-sec * libwebsocket timeout still active here too, so if parsing did * not complete just wait for next packet coming in this state */ if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE) break; #endif /* * otherwise deal with the handshake. If there's any * packet traffic already arrived we'll trigger poll() again * right away and deal with it that way */ return lws_client_interpret_server_handshake(wsi); bail3: lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); if (cce) lwsl_info("reason: %s\n", cce); wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); wsi->already_did_cce = 1; lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cbail3"); return -1; default: break; } return 0; }
int lws_client_interpret_server_handshake(struct lws *wsi) { int n, port = 0, ssl = 0; int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; const char *prot, *ads = NULL, *path, *cce = NULL; struct allocated_headers *ah = NULL; struct lws *w = lws_client_wsi_effective(wsi); char *p, *q; char new_path[300]; lws_client_stash_destroy(wsi); ah = wsi->http.ah; if (!wsi->do_ws) { /* we are being an http client... */ #if defined(LWS_ROLE_H2) if (wsi->client_h2_alpn || wsi->client_h2_substream) { lwsl_debug("%s: %p: transitioning to h2 client\n", __func__, wsi); lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_h2); } else #endif { #if defined(LWS_ROLE_H1) { lwsl_debug("%s: %p: transitioning to h1 client\n", __func__, wsi); lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_h1); } #else return -1; #endif } wsi->http.ah = ah; ah->http_response = 0; } /* * well, what the server sent looked reasonable for syntax. * Now let's confirm it sent all the necessary headers * * http (non-ws) client will expect something like this * * HTTP/1.0.200 * server:.libwebsockets * content-type:.text/html * content-length:.17703 * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 */ wsi->http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; if (!wsi->client_h2_substream) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); if (wsi->do_ws && !p) { lwsl_info("no URI\n"); cce = "HS: URI missing"; goto bail3; } if (!p) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); wsi->http.connection_type = HTTP_CONNECTION_CLOSE; } if (!p) { cce = "HS: URI missing"; lwsl_info("no URI\n"); goto bail3; } } else { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS); if (!p) { cce = "HS: :status missing"; lwsl_info("no status\n"); goto bail3; } } n = atoi(p); if (ah) ah->http_response = n; if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); if (!p) { cce = "HS: Redirect code but no Location"; goto bail3; } /* Relative reference absolute path */ if (p[0] == '/') { #if defined(LWS_WITH_TLS) ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; /* +1 as lws_client_reset expects leading / omitted */ path = p + 1; } /* Absolute (Full) URI */ else if (strchr(p, ':')) { if (lws_parse_uri(p, &prot, &ads, &port, &path)) { cce = "HS: URI did not parse"; goto bail3; } if (!strcmp(prot, "wss") || !strcmp(prot, "https")) ssl = 1; } /* Relative reference relative path */ else { /* This doesn't try to calculate an absolute path, * that will be left to the server */ #if defined(LWS_WITH_TLS) ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; /* +1 as lws_client_reset expects leading / omitted */ path = new_path + 1; lws_strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); q = strrchr(new_path, '/'); if (q) lws_strncpy(q + 1, p, sizeof(new_path) - (q - new_path)); else path = p; } #if defined(LWS_WITH_TLS) if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !ssl) { cce = "HS: Redirect attempted SSL downgrade"; goto bail3; } #endif if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { /* there are two ways to fail out with NULL return... * simple, early problem where the wsi is intact, or * we went through with the reconnect attempt and the * wsi is already closed. In the latter case, the wsi * has beet set to NULL additionally. */ lwsl_err("Redirect failed\n"); cce = "HS: Redirect failed"; if (wsi) goto bail3; return 1; } return 0; } if (!wsi->do_ws) { /* if h1 KA is allowed, enable the queued pipeline guys */ if (!wsi->client_h2_alpn && !wsi->client_h2_substream && w == wsi) { /* ie, coming to this for the first time */ if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) wsi->keepalive_active = 1; else { /* * Ugh... now the main http connection has seen * both sides, we learn the server doesn't * support keepalive. * * That means any guys queued on us are going * to have to be restarted from connect2 with * their own connections. */ /* * stick around telling any new guys they can't * pipeline to this server */ wsi->keepalive_rejected = 1; lws_vhost_lock(wsi->vhost); lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, wsi->dll_client_transaction_queue_head.next) { struct lws *ww = lws_container_of(d, struct lws, dll_client_transaction_queue); /* remove him from our queue */ lws_dll_lws_remove(&ww->dll_client_transaction_queue); /* give up on pipelining */ ww->client_pipeline = 0; /* go back to "trying to connect" state */ lws_role_transition(ww, LWSIFR_CLIENT, LRS_UNCONNECTED, #if defined(LWS_ROLE_H1) &role_ops_h1); #else #if defined (LWS_ROLE_H2) &role_ops_h2); #else &role_ops_raw); #endif #endif ww->user_space = NULL; } lws_end_foreach_dll_safe(d, d1); lws_vhost_unlock(wsi->vhost); } } #ifdef LWS_WITH_HTTP_PROXY wsi->http.perform_rewrite = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), "text/html", 9)) wsi->http.perform_rewrite = 1; } #endif /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); cce = "HS: OOM"; goto bail2; } /* he may choose to send us stuff in chunked transfer-coding */ wsi->chunked = 0; wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING), "chunked"); /* first thing is hex, after payload there is crlf */ wsi->chunk_parser = ELCP_HEX; } if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { wsi->http.rx_content_length = atoll(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); lwsl_info("%s: incoming content length %llu\n", __func__, (unsigned long long) wsi->http.rx_content_length); wsi->http.rx_content_remain = wsi->http.rx_content_length; } else /* can't do 1.1 without a content length or chunked */ if (!wsi->chunked) wsi->http.connection_type = HTTP_CONNECTION_CLOSE; /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) { cce = "HS: disallowed by client filter"; goto bail2; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, wsi->user_space, NULL, 0)) { cce = "HS: disallowed at ESTABLISHED"; goto bail3; } /* * for pipelining, master needs to keep his ah... guys who * queued on him can drop it now though. */ if (w != wsi) /* free up parsing allocations for queued guy */ lws_header_table_detach(w, 0); lwsl_info("%s: client connection up\n", __func__); return 0; } #if defined(LWS_ROLE_WS) switch (lws_client_ws_upgrade(wsi, &cce)) { case 2: goto bail2; case 3: goto bail3; } return 0; #endif bail3: close_reason = LWS_CLOSE_STATUS_NOSTATUS; bail2: if (wsi->protocol) { n = 0; if (cce) n = (int)strlen(cce); wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, (unsigned int)n); } wsi->already_did_cce = 1; lwsl_info("closing connection due to bail2 connection error\n"); /* closing will free up his parsing allocations */ lws_close_free_wsi(wsi, close_reason, "c hs interp"); return 1; }
LWS_VISIBLE int 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; switch (wsi->state) { #ifdef LWS_USE_HTTP2 case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: case LWSS_HTTP2_ESTABLISHED: n = 0; while (n < len) { /* * we were accepting input but now we stopped doing so */ if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { lws_rxflow_cache(wsi, buf, n, len); return 1; } /* account for what we're using in rxflow buffer */ if (wsi->rxflow_buffer) wsi->rxflow_pos++; if (lws_http2_parser(wsi, buf[n++])) { lwsl_debug("%s: http2_parser bailed\n", __func__); goto bail; } } break; #endif case LWSS_HTTP_ISSUING_FILE: return 0; case LWSS_CLIENT_HTTP_ESTABLISHED: break; case LWSS_HTTP: wsi->hdr_parsing_completed = 0; /* fallthru */ case LWSS_HTTP_HEADERS: if (!wsi->u.hdr.ah) { lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__); assert(0); } lwsl_parser("issuing %d bytes to parser\n", (int)len); if (lws_handshake_client(wsi, &buf, (size_t)len)) goto bail; last_char = buf; if (lws_handshake_server(wsi, &buf, (size_t)len)) /* Handshake indicates this session is done. */ goto bail; /* we might have transitioned to RAW */ if (wsi->mode == LWSCM_RAW) /* we gave the read buffer to RAW handler already */ goto read_ok; /* * It's possible that we've exhausted our data already, or * rx flow control has stopped us dealing with this early, * but lws_handshake_server doesn't update len for us. * Figure out how much was read, so that we can proceed * appropriately: */ len -= (buf - last_char); lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); if (!wsi->hdr_parsing_completed) /* More header content on the way */ goto read_ok; switch (wsi->state) { case LWSS_HTTP: case LWSS_HTTP_HEADERS: goto read_ok; case LWSS_HTTP_ISSUING_FILE: goto read_ok; case LWSS_HTTP_BODY: wsi->u.http.content_remain = wsi->u.http.content_length; if (wsi->u.http.content_remain) goto http_postbody; /* there is no POST content */ goto postbody_completion; default: break; } break; case LWSS_HTTP_BODY: http_postbody: while (len && wsi->u.http.content_remain) { /* Copy as much as possible, up to the limit of: * what we have in the read buffer (len) * remaining portion of the POST body (content_remain) */ body_chunk_len = min(wsi->u.http.content_remain,len); wsi->u.http.content_remain -= body_chunk_len; len -= body_chunk_len; #ifdef LWS_WITH_CGI if (wsi->cgi) { struct lws_cgi_args args; args.ch = LWS_STDIN; args.stdwsi = &wsi->cgi->stdwsi[0]; args.data = buf; args.len = body_chunk_len; /* returns how much used */ n = user_callback_handle_rxflow( wsi->protocol->callback, wsi, LWS_CALLBACK_CGI_STDIN_DATA, wsi->user_space, (void *)&args, 0); if ((int)n < 0) goto bail; } else { #endif n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY, wsi->user_space, buf, (size_t)body_chunk_len); if (n) goto bail; n = (size_t)body_chunk_len; #ifdef LWS_WITH_CGI } #endif buf += n; if (wsi->u.http.content_remain) { lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); break; } /* he sent all the content in time */ postbody_completion: #ifdef LWS_WITH_CGI /* * If we're running a cgi, we can't let him off the * hook just because he sent his POST data */ if (wsi->cgi) lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, wsi->context->timeout_secs); else #endif lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); #ifdef LWS_WITH_CGI if (!wsi->cgi) #endif { n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY_COMPLETION, wsi->user_space, NULL, 0); if (n) goto bail; } break; } break; case LWSS_ESTABLISHED: case LWSS_AWAITING_CLOSE_ACK: case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: case LWSS_SHUTDOWN: if (lws_handshake_client(wsi, &buf, (size_t)len)) goto bail; switch (wsi->mode) { case LWSCM_WS_SERVING: if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) { lwsl_info("interpret_incoming_packet has bailed\n"); goto bail; } break; } break; default: lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state); break; } read_ok: /* Nothing more to do for now */ lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf)); return buf - oldbuf; bail: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return -1; }
LWS_VISIBLE int lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; lws_sockfd_type our_fd = 0, tmp_fd; struct lws_tokens eff_buf; unsigned int pending = 0; struct lws *wsi, *wsi1; char draining_flow = 0; int timed_out = 0; time_t now; int n, m; int more; /* * you can call us with pollfd = NULL to just allow the once-per-second * global timeout checks; if less than a second since the last check * it returns immediately then. */ time(&now); /* TODO: if using libev, we should probably use timeout watchers... */ if (context->last_timeout_check_s != now) { context->last_timeout_check_s = now; lws_plat_service_periodic(context); /* global timeout check once per second */ if (pollfd) our_fd = pollfd->fd; wsi = context->pt[tsi].timeout_list; while (wsi) { /* we have to take copies, because he may be deleted */ wsi1 = wsi->timeout_list; tmp_fd = wsi->sock; if (lws_service_timeout_check(wsi, (unsigned int)now)) { /* he did time out... */ if (tmp_fd == our_fd) /* it was the guy we came to service! */ timed_out = 1; /* he's gone, no need to mark as handled */ } wsi = wsi1; } #if 0 { char s[300], *p = s; for (n = 0; n < context->count_threads; n++) p += sprintf(p, " %7lu (%5d), ", context->pt[n].count_conns, context->pt[n].fds_count); lwsl_notice("load: %s\n", s); } #endif } /* the socket we came to service timed out, nothing to do */ if (timed_out) return 0; /* just here for timeout management? */ if (!pollfd) return 0; /* no, here to service a socket descriptor */ wsi = wsi_from_fd(context, pollfd->fd); if (!wsi) /* not lws connection ... leave revents alone and return */ return 0; /* * so that caller can tell we handled, past here we need to * zero down pollfd->revents after handling */ #if LWS_POSIX /* handle session socket closed */ if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && (pollfd->revents & LWS_POLLHUP)) { wsi->socket_is_permanently_unusable = 1; lwsl_debug("Session Socket %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto close_and_handled; } #ifdef _WIN32 if (pollfd->revents & LWS_POLLOUT) wsi->sock_send_blocking = FALSE; #endif #endif /* okay, what we came here to do... */ switch (wsi->mode) { case LWSCM_HTTP_SERVING: case LWSCM_HTTP_SERVING_ACCEPTED: case LWSCM_SERVER_LISTENER: case LWSCM_SSL_ACK_PENDING: n = lws_server_socket_service(context, wsi, pollfd); if (n) /* closed by above */ return 1; pending = lws_ssl_pending(wsi); if (pending) goto handle_pending; goto handled; case LWSCM_WS_SERVING: case LWSCM_WS_CLIENT: case LWSCM_HTTP2_SERVING: /* 1: something requested a callback when it was OK to write */ if ((pollfd->revents & LWS_POLLOUT) && (wsi->state == LWSS_ESTABLISHED || wsi->state == LWSS_HTTP2_ESTABLISHED || wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS || wsi->state == LWSS_RETURNED_CLOSE_ALREADY || wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) && lws_handle_POLLOUT_event(wsi, pollfd)) { if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; lwsl_info("lws_service_fd: closing\n"); goto close_and_handled; } #if 1 if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || wsi->state == LWSS_AWAITING_CLOSE_ACK) { /* * we stopped caring about anything except control * packets. Force flow control off, defeat tx * draining. */ lws_rx_flow_control(wsi, 1); wsi->u.ws.tx_draining_ext = 0; } #endif if (wsi->u.ws.tx_draining_ext) { /* we cannot deal with new RX until the TX ext * path has been drained. It's because new * rx will, eg, crap on the wsi rx buf that * may be needed to retain state. * * TX ext drain path MUST go through event loop * to avoid blocking. */ break; } if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) /* We cannot deal with any kind of new RX * because we are RX-flowcontrolled. */ break; /* 2: RX Extension needs to be drained */ if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.rx_draining_ext) { lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__); #ifndef LWS_NO_CLIENT if (wsi->mode == LWSCM_WS_CLIENT) { n = lws_client_rx_sm(wsi, 0); if (n < 0) /* we closed wsi */ n = 0; } else #endif n = lws_rx_sm(wsi, 0); goto handled; } if (wsi->u.ws.rx_draining_ext) /* * We have RX EXT content to drain, but can't do it * right now. That means we cannot do anything lower * priority either. */ break; /* 3: RX Flowcontrol buffer needs to be drained */ if (wsi->rxflow_buffer) { lwsl_info("draining rxflow (len %d)\n", wsi->rxflow_len - wsi->rxflow_pos ); /* well, drain it */ eff_buf.token = (char *)wsi->rxflow_buffer + wsi->rxflow_pos; eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; draining_flow = 1; goto drain; } /* 4: any incoming data ready? * notice if rx flow going off raced poll(), rx flow wins */ if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) break; read: eff_buf.token_len = lws_ssl_capable_read(wsi, pt->serv_buf, pending ? pending : LWS_MAX_SOCKET_IO_BUF); switch (eff_buf.token_len) { case 0: lwsl_info("service_fd: closing due to 0 length read\n"); goto close_and_handled; case LWS_SSL_CAPABLE_MORE_SERVICE: lwsl_info("SSL Capable more service\n"); n = 0; goto handled; case LWS_SSL_CAPABLE_ERROR: lwsl_info("Closing when error\n"); goto close_and_handled; } /* * give any active extensions a chance to munge the buffer * before parse. We pass in a pointer to an lws_tokens struct * prepared with the default buffer and content length that's in * there. Rather than rewrite the default buffer, extensions * that expect to grow the buffer can adapt .token to * point to their own per-connection buffer in the extension * user allocation. By default with no extensions or no * extension callback handling, just the normal input buffer is * used then so it is efficient. */ eff_buf.token = (char *)pt->serv_buf; drain: do { more = 0; m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE, &eff_buf, 0); if (m < 0) goto close_and_handled; if (m) more = 1; /* service incoming data */ if (eff_buf.token_len) { /* * if draining from rxflow buffer, not * critical to track what was used since at the * use it bumps wsi->rxflow_pos. If we come * around again it will pick up from where it * left off. */ n = lws_read(wsi, (unsigned char *)eff_buf.token, eff_buf.token_len); if (n < 0) { /* we closed wsi */ n = 0; goto handled; } } eff_buf.token = NULL; eff_buf.token_len = 0; } while (more); pending = lws_ssl_pending(wsi); if (pending) { handle_pending: pending = pending > LWS_MAX_SOCKET_IO_BUF ? LWS_MAX_SOCKET_IO_BUF : pending; goto read; } if (draining_flow && wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { lwsl_info("flow buffer: drained\n"); lws_free_set_NULL(wsi->rxflow_buffer); /* having drained the rxflow buffer, can rearm POLLIN */ #ifdef LWS_NO_SERVER n = #endif _lws_rx_flow_control(wsi); /* n ignored, needed for NO_SERVER case */ } break; default: #ifdef LWS_NO_CLIENT break; #else n = lws_client_socket_service(context, wsi, pollfd); if (n) return 1; goto handled; #endif } n = 0; goto handled; close_and_handled: lwsl_debug("Close and handled\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); /* * pollfd may point to something else after the close * due to pollfd swapping scheme on delete on some platforms * we can't clear revents now because it'd be the wrong guy's revents */ return 1; handled: pollfd->revents = 0; return n; }
static int rops_handle_POLLIN_raw_proxy(struct lws_context_per_thread *pt, struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_tokens ebuf; int n, buffered; /* pending truncated sends have uber priority */ if (lws_has_buffered_out(wsi)) { if (!(pollfd->revents & LWS_POLLOUT)) return LWS_HPI_RET_HANDLED; /* drain the output buflist */ if (lws_issue_raw(wsi, NULL, 0) < 0) goto fail; /* * we can't afford to allow input processing to send * something new, so spin around he event loop until * he doesn't have any partials */ return LWS_HPI_RET_HANDLED; } if ((pollfd->revents & pollfd->events & LWS_POLLIN) && /* any tunnel has to have been established... */ lwsi_state(wsi) != LRS_SSL_ACK_PENDING && !(wsi->favoured_pollin && (pollfd->revents & pollfd->events & LWS_POLLOUT))) { buffered = lws_buflist_aware_read(pt, wsi, &ebuf); switch (ebuf.len) { case 0: lwsl_info("%s: read 0 len\n", __func__); wsi->seen_zero_length_recv = 1; lws_change_pollfd(wsi, LWS_POLLIN, 0); /* * we need to go to fail here, since it's the only * chance we get to understand that the socket has * closed */ // goto try_pollout; goto fail; case LWS_SSL_CAPABLE_ERROR: goto fail; case LWS_SSL_CAPABLE_MORE_SERVICE: goto try_pollout; } n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, lwsi_role_client(wsi) ? LWS_CALLBACK_RAW_PROXY_CLI_RX : LWS_CALLBACK_RAW_PROXY_SRV_RX, wsi->user_space, ebuf.token, ebuf.len); if (n < 0) { lwsl_info("LWS_CALLBACK_RAW_PROXY_*_RX fail\n"); goto fail; } if (lws_buflist_aware_consume(wsi, &ebuf, ebuf.len, buffered)) return LWS_HPI_RET_PLEASE_CLOSE_ME; } else if (wsi->favoured_pollin && (pollfd->revents & pollfd->events & LWS_POLLOUT)) /* we balanced the last favouring of pollin */ wsi->favoured_pollin = 0; try_pollout: if (!(pollfd->revents & LWS_POLLOUT)) return LWS_HPI_RET_HANDLED; if (lws_handle_POLLOUT_event(wsi, pollfd)) { lwsl_debug("POLLOUT event closed it\n"); return LWS_HPI_RET_PLEASE_CLOSE_ME; } #if !defined(LWS_NO_CLIENT) if (lws_client_socket_service(wsi, pollfd, NULL)) return LWS_HPI_RET_WSI_ALREADY_DIED; #endif return LWS_HPI_RET_HANDLED; fail: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "raw svc fail"); return LWS_HPI_RET_WSI_ALREADY_DIED; }
int lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) { int write_type = LWS_WRITE_PONG; struct lws_tokens eff_buf; #ifdef LWS_USE_HTTP2 struct lws *wsi2; #endif int ret, m, n; /* * user callback is lowest priority to get these notifications * actually, since other pending things cannot be disordered */ /* Priority 1: pending truncated sends are incomplete ws fragments * If anything else sent first the protocol would be * corrupted. */ if (wsi->trunc_len) { if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, wsi->trunc_len) < 0) { lwsl_info("%s signalling to close\n", __func__); return -1; } /* leave POLLOUT active either way */ return 0; } else if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) return -1; /* retry closing now */ #ifdef LWS_USE_HTTP2 /* Priority 2: protocol packets */ if (wsi->pps) { lwsl_info("servicing pps %d\n", wsi->pps); switch (wsi->pps) { case LWS_PPS_HTTP2_MY_SETTINGS: case LWS_PPS_HTTP2_ACK_SETTINGS: lws_http2_do_pps_send(lws_get_context(wsi), wsi); break; default: break; } wsi->pps = LWS_PPS_NONE; lws_rx_flow_control(wsi, 1); return 0; /* leave POLLOUT active */ } #endif /* Priority 3: pending control packets (pong or close) */ if ((wsi->state == LWSS_ESTABLISHED && wsi->u.ws.ping_pending_flag) || (wsi->state == LWSS_RETURNED_CLOSE_ALREADY && wsi->u.ws.payload_is_close)) { if (wsi->u.ws.payload_is_close) write_type = LWS_WRITE_CLOSE; n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], wsi->u.ws.ping_payload_len, write_type); if (n < 0) return -1; /* well he is sent, mark him done */ wsi->u.ws.ping_pending_flag = 0; if (wsi->u.ws.payload_is_close) /* oh... a close frame was it... then we are done */ return -1; /* otherwise for PING, leave POLLOUT active either way */ return 0; } /* Priority 4: if we are closing, not allowed to send more data frags * which means user callback or tx ext flush banned now */ if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) goto user_service; /* Priority 5: Tx path extension with more to send * * These are handled as new fragments each time around * So while we must block new writeable callback to enforce * payload ordering, but since they are always complete * fragments control packets can interleave OK. */ if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { lwsl_ext("SERVICING TX EXT DRAINING\n"); if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0) return -1; /* leave POLLOUT active */ return 0; } /* Priority 6: user can get the callback */ m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0); #ifndef LWS_NO_EXTENSIONS if (!wsi->extension_data_pending) goto user_service; #endif /* * check in on the active extensions, see if they * had pending stuff to spill... they need to get the * first look-in otherwise sequence will be disordered * * NULL, zero-length eff_buf means just spill pending */ ret = 1; while (ret == 1) { /* default to nobody has more to spill */ ret = 0; eff_buf.token = NULL; eff_buf.token_len = 0; /* give every extension a chance to spill */ m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0); if (m < 0) { lwsl_err("ext reports fatal error\n"); return -1; } if (m) /* * at least one extension told us he has more * to spill, so we will go around again after */ ret = 1; /* assuming they gave us something to send, send it */ if (eff_buf.token_len) { n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, eff_buf.token_len); if (n < 0) { lwsl_info("closing from POLLOUT spill\n"); return -1; } /* * Keep amount spilled small to minimize chance of this */ if (n != eff_buf.token_len) { lwsl_err("Unable to spill ext %d vs %s\n", eff_buf.token_len, n); return -1; } } else continue; /* no extension has more to spill */ if (!ret) continue; /* * There's more to spill from an extension, but we just sent * something... did that leave the pipe choked? */ if (!lws_send_pipe_choked(wsi)) /* no we could add more */ continue; lwsl_info("choked in POLLOUT service\n"); /* * Yes, he's choked. Leave the POLLOUT masked on so we will * come back here when he is unchoked. Don't call the user * callback to enforce ordering of spilling, he'll get called * when we come back here and there's nothing more to spill. */ return 0; } #ifndef LWS_NO_EXTENSIONS wsi->extension_data_pending = 0; #endif user_service: /* one shot */ if (pollfd) { if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_info("failed at set pollfd\n"); return 1; } lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); } #ifdef LWS_USE_HTTP2 /* * we are the 'network wsi' for potentially many muxed child wsi with * no network connection of their own, who have to use us for all their * network actions. So we use a round-robin scheme to share out the * POLLOUT notifications to our children. * * But because any child could exhaust the socket's ability to take * writes, we can only let one child get notified each time. * * In addition children may be closed / deleted / added between POLLOUT * notifications, so we can't hold pointers */ if (wsi->mode != LWSCM_HTTP2_SERVING) { lwsl_info("%s: non http2\n", __func__); goto notify; } wsi->u.http2.requested_POLLOUT = 0; if (!wsi->u.http2.initialized) { lwsl_info("pollout on uninitialized http2 conn\n"); return 0; } lwsl_info("%s: doing children\n", __func__); wsi2 = wsi; do { wsi2 = wsi2->u.http2.next_child_wsi; lwsl_info("%s: child %p\n", __func__, wsi2); if (!wsi2) continue; if (!wsi2->u.http2.requested_POLLOUT) continue; wsi2->u.http2.requested_POLLOUT = 0; if (lws_calllback_as_writeable(wsi2)) { lwsl_debug("Closing POLLOUT child\n"); lws_close_free_wsi(wsi2, LWS_CLOSE_STATUS_NOSTATUS); } wsi2 = wsi; } while (wsi2 != NULL && !lws_send_pipe_choked(wsi)); lwsl_info("%s: completed\n", __func__); return 0; notify: #endif return lws_calllback_as_writeable(wsi); }
LWS_VISIBLE int lws_server_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; lws_sockfd_type accept_fd = LWS_SOCK_INVALID; struct allocated_headers *ah; #if LWS_POSIX struct sockaddr_in cli_addr; socklen_t clilen; #endif int n, len; switch (wsi->mode) { case LWSCM_HTTP_SERVING: case LWSCM_HTTP_SERVING_ACCEPTED: case LWSCM_HTTP2_SERVING: /* handle http headers coming in */ /* pending truncated sends have uber priority */ if (wsi->trunc_len) { if (!(pollfd->revents & LWS_POLLOUT)) break; if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, wsi->trunc_len) < 0) goto fail; /* * we can't afford to allow input processing to send * something new, so spin around he event loop until * he doesn't have any partials */ break; } /* any incoming data ready? */ if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) goto try_pollout; /* these states imply we MUST have an ah attached */ if (wsi->state == LWSS_HTTP || wsi->state == LWSS_HTTP_ISSUING_FILE || wsi->state == LWSS_HTTP_HEADERS) { if (!wsi->u.hdr.ah) /* no autoservice beacuse we will do it next */ if (lws_header_table_attach(wsi, 0)) goto try_pollout; ah = wsi->u.hdr.ah; lwsl_debug("%s: %p: rxpos:%d rxlen:%d\n", __func__, wsi, ah->rxpos, ah->rxlen); /* if nothing in ah rx buffer, get some fresh rx */ if (ah->rxpos == ah->rxlen) { ah->rxlen = lws_ssl_capable_read(wsi, ah->rx, sizeof(ah->rx)); ah->rxpos = 0; lwsl_debug("%s: wsi %p, ah->rxlen = %d\r\n", __func__, wsi, ah->rxlen); switch (ah->rxlen) { case 0: lwsl_info("%s: read 0 len\n", __func__); /* lwsl_info(" state=%d\n", wsi->state); */ // if (!wsi->hdr_parsing_completed) // lws_header_table_detach(wsi); /* fallthru */ case LWS_SSL_CAPABLE_ERROR: goto fail; case LWS_SSL_CAPABLE_MORE_SERVICE: ah->rxlen = ah->rxpos = 0; goto try_pollout; } } assert(ah->rxpos != ah->rxlen && ah->rxlen); /* just ignore incoming if waiting for close */ if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { n = lws_read(wsi, ah->rx + ah->rxpos, ah->rxlen - ah->rxpos); if (n < 0) /* we closed wsi */ return 1; if (wsi->u.hdr.ah) { if ( wsi->u.hdr.ah->rxlen) wsi->u.hdr.ah->rxpos += n; if (wsi->u.hdr.ah->rxpos == wsi->u.hdr.ah->rxlen && (wsi->mode != LWSCM_HTTP_SERVING && wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED && wsi->mode != LWSCM_HTTP2_SERVING)) lws_header_table_detach(wsi, 1); } break; } goto try_pollout; } len = lws_ssl_capable_read(wsi, pt->serv_buf, LWS_MAX_SOCKET_IO_BUF); lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len); switch (len) { case 0: lwsl_info("%s: read 0 len\n", __func__); /* lwsl_info(" state=%d\n", wsi->state); */ // if (!wsi->hdr_parsing_completed) // lws_header_table_detach(wsi); /* fallthru */ case LWS_SSL_CAPABLE_ERROR: goto fail; case LWS_SSL_CAPABLE_MORE_SERVICE: goto try_pollout; } /* just ignore incoming if waiting for close */ if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { /* * hm this may want to send * (via HTTP callback for example) */ n = lws_read(wsi, pt->serv_buf, len); if (n < 0) /* we closed wsi */ return 1; /* hum he may have used up the * writability above */ break; } try_pollout: /* this handles POLLOUT for http serving fragments */ if (!(pollfd->revents & LWS_POLLOUT)) break; /* one shot */ if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_notice("%s a\n", __func__); goto fail; } if (!wsi->hdr_parsing_completed) break; if (wsi->state != LWSS_HTTP_ISSUING_FILE) { n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_HTTP_WRITEABLE, wsi->user_space, NULL, 0); if (n < 0) { lwsl_info("writeable_fail\n"); goto fail; } break; } /* >0 == completion, <0 == error */ n = lws_serve_http_file_fragment(wsi); if (n < 0 || (n > 0 && lws_http_transaction_completed(wsi))) { lwsl_info("completed\n"); goto fail; } break; case LWSCM_SERVER_LISTENER: #if LWS_POSIX /* pollin means a client has connected to us then */ do { if (!(pollfd->revents & LWS_POLLIN) || !(pollfd->events & LWS_POLLIN)) break; /* listen socket got an unencrypted connection... */ clilen = sizeof(cli_addr); lws_latency_pre(context, wsi); accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, &clilen); lws_latency(context, wsi, "listener accept", accept_fd, accept_fd >= 0); if (accept_fd < 0) { if (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK) { lwsl_err("accept asks to try again\n"); break; } lwsl_err("ERROR on accept: %s\n", strerror(LWS_ERRNO)); break; } lws_plat_set_socket_options(wsi->vhost, accept_fd); lwsl_debug("accepted new conn port %u on fd=%d\n", ntohs(cli_addr.sin_port), accept_fd); #else /* not very beautiful... */ accept_fd = (lws_sockfd_type)pollfd; #endif /* * look at who we connected to and give user code a chance * to reject based on client IP. There's no protocol selected * yet so we issue this to protocols[0] */ if ((wsi->vhost->protocols[0].callback)(wsi, LWS_CALLBACK_FILTER_NETWORK_CONNECTION, NULL, (void *)(long)accept_fd, 0)) { lwsl_debug("Callback denied network connection\n"); compatible_close(accept_fd); break; } if (!lws_adopt_socket_vhost(wsi->vhost, accept_fd)) /* already closed cleanly as necessary */ return 1; #if LWS_POSIX } while (pt->fds_count < context->fd_limit_per_thread - 1 && lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); #endif return 0; default: break; } if (!lws_server_socket_service_ssl(wsi, accept_fd)) return 0; fail: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 1; }
LWS_VISIBLE int lws_server_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd) { lws_sockfd_type accept_fd = LWS_SOCK_INVALID; #if LWS_POSIX struct sockaddr_in cli_addr; socklen_t clilen; #endif struct lws *new_wsi = NULL; int n, len; switch (wsi->mode) { case LWSCM_HTTP_SERVING: case LWSCM_HTTP_SERVING_ACCEPTED: case LWSCM_HTTP2_SERVING: /* handle http headers coming in */ /* pending truncated sends have uber priority */ if (wsi->trunc_len) { if (!(pollfd->revents & LWS_POLLOUT)) break; if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, wsi->trunc_len) < 0) goto fail; /* * we can't afford to allow input processing send * something new, so spin around he event loop until * he doesn't have any partials */ break; } /* any incoming data ready? */ if (!(pollfd->revents & pollfd->events && LWS_POLLIN)) goto try_pollout; len = lws_ssl_capable_read(wsi, context->serv_buf, sizeof(context->serv_buf)); lwsl_debug("%s: read %d\r\n", __func__, len); switch (len) { case 0: lwsl_info("lws_server_skt_srv: read 0 len\n"); /* lwsl_info(" state=%d\n", wsi->state); */ if (!wsi->hdr_parsing_completed) lws_free_header_table(wsi); /* fallthru */ case LWS_SSL_CAPABLE_ERROR: goto fail; case LWS_SSL_CAPABLE_MORE_SERVICE: goto try_pollout; } /* just ignore incoming if waiting for close */ if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { /* * hm this may want to send * (via HTTP callback for example) */ n = lws_read(wsi, context->serv_buf, len); if (n < 0) /* we closed wsi */ return 1; /* hum he may have used up the * writability above */ break; } try_pollout: /* this handles POLLOUT for http serving fragments */ if (!(pollfd->revents & LWS_POLLOUT)) break; /* one shot */ if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) goto fail; lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); if (wsi->state != LWSS_HTTP_ISSUING_FILE) { n = user_callback_handle_rxflow( wsi->protocol->callback, wsi, LWS_CALLBACK_HTTP_WRITEABLE, wsi->user_space, NULL, 0); if (n < 0) goto fail; break; } /* >0 == completion, <0 == error */ n = lws_serve_http_file_fragment(wsi); if (n < 0 || (n > 0 && lws_http_transaction_completed(wsi))) goto fail; break; case LWSCM_SERVER_LISTENER: #if LWS_POSIX /* pollin means a client has connected to us then */ if (!(pollfd->revents & LWS_POLLIN)) break; /* listen socket got an unencrypted connection... */ clilen = sizeof(cli_addr); lws_latency_pre(context, wsi); accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, &clilen); lws_latency(context, wsi, "unencrypted accept LWSCM_SERVER_LISTENER", accept_fd, accept_fd >= 0); if (accept_fd < 0) { if (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK) { lwsl_debug("accept asks to try again\n"); break; } lwsl_warn("ERROR on accept: %s\n", strerror(LWS_ERRNO)); break; } lws_plat_set_socket_options(context, accept_fd); #else /* not very beautiful... */ accept_fd = (lws_sockfd_type)pollfd; #endif /* * look at who we connected to and give user code a chance * to reject based on client IP. There's no protocol selected * yet so we issue this to protocols[0] */ if ((context->protocols[0].callback)(wsi, LWS_CALLBACK_FILTER_NETWORK_CONNECTION, NULL, (void *)(long)accept_fd, 0)) { lwsl_debug("Callback denied network connection\n"); compatible_close(accept_fd); break; } new_wsi = lws_create_new_server_wsi(context); if (new_wsi == NULL) { compatible_close(accept_fd); break; } new_wsi->sock = accept_fd; /* the transport is accepted... give him time to negotiate */ lws_set_timeout(new_wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, AWAITING_TIMEOUT); #if LWS_POSIX == 0 mbed3_tcp_stream_accept(accept_fd, new_wsi); #endif /* * A new connection was accepted. Give the user a chance to * set properties of the newly created wsi. There's no protocol * selected yet so we issue this to protocols[0] */ (context->protocols[0].callback)(new_wsi, LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, NULL, NULL, 0); lws_libev_accept(new_wsi, accept_fd); if (!LWS_SSL_ENABLED(context)) { #if LWS_POSIX lwsl_debug("accepted new conn port %u on fd=%d\n", ntohs(cli_addr.sin_port), accept_fd); #endif if (insert_wsi_socket_into_fds(context, new_wsi)) goto fail; } break; default: break; } if (!lws_server_socket_service_ssl(&wsi, new_wsi, accept_fd, pollfd)) return 0; fail: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 1; }
int lws_client_interpret_server_handshake(struct lws *wsi) { int n, len, okay = 0, port = 0, ssl = 0; int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; struct lws_context *context = wsi->context; const char *pc, *prot, *ads = NULL, *path, *cce = NULL; struct allocated_headers *ah = NULL; char *p, *q; char new_path[300]; #ifndef LWS_NO_EXTENSIONS struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *sb = (char *)&pt->serv_buf[0]; const struct lws_ext_options *opts; const struct lws_extension *ext; char ext_name[128]; const char *c, *a; char ignore; int more = 1; void *v; #endif ah = wsi->u.hdr.ah; if (!wsi->do_ws) { /* we are being an http client... */ lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED); wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; wsi->u.http.ah = ah; ah->http_response = 0; } /* * well, what the server sent looked reasonable for syntax. * Now let's confirm it sent all the necessary headers * * http (non-ws) client will expect something like this * * HTTP/1.0.200 * server:.libwebsockets * content-type:.text/html * content-length:.17703 * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 * * * */ wsi->u.http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); if (wsi->do_ws && !p) { lwsl_info("no URI\n"); cce = "HS: URI missing"; goto bail3; } if (!p) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; } if (!p) { cce = "HS: URI missing"; lwsl_info("no URI\n"); goto bail3; } n = atoi(p); if (ah) ah->http_response = n; if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); if (!p) { cce = "HS: Redirect code but no Location"; goto bail3; } /* Relative reference absolute path */ if (p[0] == '/') { #ifdef LWS_OPENSSL_SUPPORT ssl = wsi->use_ssl; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; path = p + 1; /* +1 as lws_client_reset expects leading / to be omitted */ } /* Absolute (Full) URI */ else if (strchr(p, ':')) { if (lws_parse_uri(p, &prot, &ads, &port, &path)) { cce = "HS: URI did not parse"; goto bail3; } if (!strcmp(prot, "wss") || !strcmp(prot, "https")) ssl = 1; } /* Relative reference relative path */ else { /* This doesn't try to calculate an absolute path, that will be left to the server */ #ifdef LWS_OPENSSL_SUPPORT ssl = wsi->use_ssl; #endif ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; path = new_path + 1; /* +1 as lws_client_reset expects leading / to be omitted */ strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); new_path[sizeof(new_path) - 1] = '\0'; q = strrchr(new_path, '/'); if (q) { strncpy(q + 1, p, sizeof(new_path) - (q - new_path) - 1); new_path[sizeof(new_path) - 1] = '\0'; } else { path = p; } } #ifdef LWS_OPENSSL_SUPPORT if (wsi->use_ssl && !ssl) { cce = "HS: Redirect attempted SSL downgrade"; goto bail3; } #endif if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { /* there are two ways to fail out with NULL return... * simple, early problem where the wsi is intact, or * we went through with the reconnect attempt and the * wsi is already closed. In the latter case, the wsi * has beet set to NULL additionally. */ lwsl_err("Redirect failed\n"); cce = "HS: Redirect failed"; if (wsi) goto bail3; return 1; } return 0; } if (!wsi->do_ws) { if (n != 200 && n != 201 && n != 304 && n != 401) { lwsl_notice("Connection failed with code %d\n", n); cce = "HS: Server unrecognized response code"; goto bail2; } #ifdef LWS_WITH_HTTP_PROXY wsi->perform_rewrite = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), "text/html", 9)) wsi->perform_rewrite = 1; } #endif /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); cce = "HS: OOM"; goto bail2; } /* he may choose to send us stuff in chunked transfer-coding */ wsi->chunked = 0; wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING), "chunked"); /* first thing is hex, after payload there is crlf */ wsi->chunk_parser = ELCP_HEX; } if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { wsi->u.http.content_length = atoll(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); lwsl_notice("%s: incoming content length %llu\n", __func__, (unsigned long long)wsi->u.http.content_length); wsi->u.http.content_remain = wsi->u.http.content_length; } else /* can't do 1.1 without a content length or chunked */ if (!wsi->chunked) wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) { cce = "HS: disallowed by client filter"; goto bail2; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, wsi->user_space, NULL, 0)) { cce = "HS: disallowed at ESTABLISHED"; goto bail3; } /* free up his parsing allocations */ lws_header_table_detach(wsi, 0); lwsl_notice("%s: client connection up\n", __func__); return 0; } if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { lwsl_info("no ACCEPT\n"); cce = "HS: ACCEPT missing"; goto bail3; } if (p && strncmp(p, "101", 3)) { lwsl_warn( "lws_client_handshake: got bad HTTP response '%s'\n", p); cce = "HS: ws upgrade response not 101"; goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); if (!p) { lwsl_info("no UPGRADE\n"); cce = "HS: UPGRADE missing"; goto bail3; } strtolower(p); if (strcmp(p, "websocket")) { lwsl_warn( "lws_client_handshake: got bad Upgrade header '%s'\n", p); cce = "HS: Upgrade to something other than websocket"; goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); if (!p) { lwsl_info("no Connection hdr\n"); cce = "HS: CONNECTION missing"; goto bail3; } strtolower(p); if (strcmp(p, "upgrade")) { lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); cce = "HS: UPGRADE malformed"; goto bail3; } pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); if (!pc) { lwsl_parser("lws_client_int_s_hs: no protocol list\n"); } else lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); /* * confirm the protocol the server wants to talk was in the list * of protocols we offered */ len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); if (!len) { lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n"); /* * no protocol name to work from, * default to first protocol */ n = 0; wsi->protocol = &wsi->vhost->protocols[0]; goto check_extensions; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); len = strlen(p); while (pc && *pc && !okay) { if (!strncmp(pc, p, len) && (pc[len] == ',' || pc[len] == '\0')) { okay = 1; continue; } while (*pc && *pc++ != ',') ; while (*pc && *pc == ' ') pc++; } if (!okay) { lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p); cce = "HS: PROTOCOL malformed"; goto bail2; } /* * identify the selected protocol struct and set it */ n = 0; wsi->protocol = NULL; while (wsi->vhost->protocols[n].callback && !wsi->protocol) { if (strcmp(p, wsi->vhost->protocols[n].name) == 0) { wsi->protocol = &wsi->vhost->protocols[n]; break; } n++; } if (wsi->protocol == NULL) { lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p); cce = "HS: Cannot match protocol"; goto bail2; } check_extensions: /* * stitch protocol choice into the vh protocol linked list * We always insert ourselves at the start of the list * * X <-> B * X <-> pAn <-> pB */ //lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n", // __func__, // wsi->vhost->same_vh_protocol_list[n], // wsi->same_vh_protocol_prev); wsi->same_vh_protocol_prev = /* guy who points to us */ &wsi->vhost->same_vh_protocol_list[n]; wsi->same_vh_protocol_next = /* old first guy is our next */ wsi->vhost->same_vh_protocol_list[n]; /* we become the new first guy */ wsi->vhost->same_vh_protocol_list[n] = wsi; if (wsi->same_vh_protocol_next) /* old first guy points back to us now */ wsi->same_vh_protocol_next->same_vh_protocol_prev = &wsi->same_vh_protocol_next; #ifndef LWS_NO_EXTENSIONS /* instantiate the accepted extensions */ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { lwsl_ext("no client extensions allowed by server\n"); goto check_accept; } /* * break down the list of server accepted extensions * and go through matching them or identifying bogons */ if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) { lwsl_warn("ext list from server failed to copy\n"); cce = "HS: EXT: list too big"; goto bail2; } c = sb; n = 0; ignore = 0; a = NULL; while (more) { if (*c && (*c != ',' && *c != '\t')) { if (*c == ';') { ignore = 1; if (!a) a = c + 1; } if (ignore || *c == ' ') { c++; continue; } ext_name[n] = *c++; if (n < sizeof(ext_name) - 1) n++; continue; } ext_name[n] = '\0'; ignore = 0; if (!*c) more = 0; else { c++; if (!n) continue; } /* check we actually support it */ lwsl_notice("checking client ext %s\n", ext_name); n = 0; ext = wsi->vhost->extensions; while (ext && ext->callback) { if (strcmp(ext_name, ext->name)) { ext++; continue; } n = 1; lwsl_notice("instantiating client ext %s\n", ext_name); /* instantiate the extension on this conn */ wsi->active_extensions[wsi->count_act_ext] = ext; /* allow him to construct his ext instance */ if (ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_CLIENT_CONSTRUCT, (void *)&wsi->act_ext_user[wsi->count_act_ext], (void *)&opts, 0)) { lwsl_notice(" ext %s failed construction\n", ext_name); ext++; continue; } /* * allow the user code to override ext defaults if it * wants to */ ext_name[0] = '\0'; if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, (char *)ext->name, ext_name, sizeof(ext_name))) { cce = "HS: EXT: failed setting defaults"; goto bail2; } if (ext_name[0] && lws_ext_parse_options(ext, wsi, wsi->act_ext_user[ wsi->count_act_ext], opts, ext_name, strlen(ext_name))) { lwsl_err("%s: unable to parse user defaults '%s'", __func__, ext_name); cce = "HS: EXT: failed parsing defaults"; goto bail2; } /* * give the extension the server options */ if (a && lws_ext_parse_options(ext, wsi, wsi->act_ext_user[wsi->count_act_ext], opts, a, c - a)) { lwsl_err("%s: unable to parse remote def '%s'", __func__, a); cce = "HS: EXT: failed parsing options"; goto bail2; } if (ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_OPTION_CONFIRM, wsi->act_ext_user[wsi->count_act_ext], NULL, 0)) { lwsl_err("%s: ext %s rejects server options %s", __func__, ext->name, a); cce = "HS: EXT: Rejects server options"; goto bail2; } wsi->count_act_ext++; ext++; } if (n == 0) { lwsl_warn("Unknown ext '%s'!\n", ext_name); cce = "HS: EXT: unknown ext"; goto bail2; } a = NULL; n = 0; } check_accept: #endif /* * Confirm his accept token is the one we precomputed */ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) { lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, wsi->u.hdr.ah->initial_handshake_hash_base64); cce = "HS: Accept hash wrong"; goto bail2; } /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); cce = "HS: OOM"; goto bail2; } /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) { cce = "HS: Rejected by filter cb"; goto bail2; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* free up his parsing allocations */ lws_header_table_detach(wsi, 0); lws_union_transition(wsi, LWSCM_WS_CLIENT); wsi->state = LWSS_ESTABLISHED; lws_restart_ws_ping_pong_timer(wsi); wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, then * use a big default for compatibility */ n = wsi->protocol->rx_buffer_size; if (!n) n = context->pt_serv_buf_size; n += LWS_PRE; wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */); if (!wsi->u.ws.rx_ubuf) { lwsl_err("Out of Mem allocating rx buffer %d\n", n); cce = "HS: OOM"; goto bail2; } wsi->u.ws.rx_ubuf_alloc = n; lwsl_info("Allocating client RX buffer %d\n", n); #if !defined(LWS_WITH_ESP32) if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); cce = "HS: SO_SNDBUF failed"; goto bail3; } #endif lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, wsi->user_space, NULL, 0)) { cce = "HS: Rejected at CLIENT_ESTABLISHED"; goto bail3; } #ifndef LWS_NO_EXTENSIONS /* * inform all extensions, not just active ones since they * already know */ ext = wsi->vhost->extensions; while (ext && ext->callback) { v = NULL; for (n = 0; n < wsi->count_act_ext; n++) if (wsi->active_extensions[n] == ext) v = wsi->act_ext_user[n]; ext->callback(context, ext, wsi, LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); ext++; } #endif return 0; bail3: close_reason = LWS_CLOSE_STATUS_NOSTATUS; bail2: if (wsi->protocol) wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, (unsigned int)strlen(cce)); wsi->already_did_cce = 1; lwsl_info("closing connection due to bail2 connection error\n"); /* closing will free up his parsing allocations */ lws_close_free_wsi(wsi, close_reason); return 1; }
int lws_client_interpret_server_handshake(struct lws *wsi) { int n, len, okay = 0, isErrorCodeReceived = 0, port = 0, ssl = 0; struct lws_context *context = wsi->context; int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; const char *pc, *prot, *ads = NULL, *path; char *p; #ifndef LWS_NO_EXTENSIONS struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *sb = (char *)&pt->serv_buf[0]; const struct lws_ext_options *opts; const struct lws_extension *ext; char ext_name[128]; const char *c, *a; char ignore; int more = 1; void *v; #endif /* * well, what the server sent looked reasonable for syntax. * Now let's confirm it sent all the necessary headers */ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); if (!p) { lwsl_info("no URI\n"); goto bail3; } n = atoi(p); if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); if (!p) goto bail3; if (lws_parse_uri(p, &prot, &ads, &port, &path)) goto bail3; if (!strcmp(prot, "wss://") || !strcmp(prot, "https://")) ssl = 1; if (lws_client_reset(wsi, ssl, ads, port, path, ads)) { lwsl_err("Redirect failed\n"); goto bail3; } return 0; } if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { lwsl_info("no ACCEPT\n"); isErrorCodeReceived = 1; goto bail3; } if (p && strncmp(p, "101", 3)) { lwsl_warn( "lws_client_handshake: got bad HTTP response '%s'\n", p); goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); if (!p) { lwsl_info("no UPGRADE\n"); goto bail3; } strtolower(p); if (strcmp(p, "websocket")) { lwsl_warn( "lws_client_handshake: got bad Upgrade header '%s'\n", p); goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); if (!p) { lwsl_info("no Connection hdr\n"); goto bail3; } strtolower(p); if (strcmp(p, "upgrade")) { lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); goto bail3; } pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); if (!pc) { lwsl_parser("lws_client_int_s_hs: no protocol list\n"); } else lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); /* * confirm the protocol the server wants to talk was in the list * of protocols we offered */ len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); if (!len) { lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n"); /* * no protocol name to work from, * default to first protocol */ wsi->protocol = &context->protocols[0]; goto check_extensions; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); len = strlen(p); while (pc && *pc && !okay) { if (!strncmp(pc, p, len) && (pc[len] == ',' || pc[len] == '\0')) { okay = 1; continue; } while (*pc && *pc++ != ',') ; while (*pc && *pc == ' ') pc++; } if (!okay) { lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p); goto bail2; } /* * identify the selected protocol struct and set it */ n = 0; wsi->protocol = NULL; while (context->protocols[n].callback && !wsi->protocol) { if (strcmp(p, context->protocols[n].name) == 0) { wsi->protocol = &context->protocols[n]; break; } n++; } if (wsi->protocol == NULL) { lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p); goto bail2; } check_extensions: #ifndef LWS_NO_EXTENSIONS /* instantiate the accepted extensions */ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { lwsl_ext("no client extenstions allowed by server\n"); goto check_accept; } /* * break down the list of server accepted extensions * and go through matching them or identifying bogons */ if (lws_hdr_copy(wsi, sb, LWS_MAX_SOCKET_IO_BUF, WSI_TOKEN_EXTENSIONS) < 0) { lwsl_warn("ext list from server failed to copy\n"); goto bail2; } c = sb; n = 0; ignore = 0; a = NULL; while (more) { if (*c && (*c != ',' && *c != '\t')) { if (*c == ';') { ignore = 1; if (!a) a = c + 1; } if (ignore || *c == ' ') { c++; continue; } ext_name[n] = *c++; if (n < sizeof(ext_name) - 1) n++; continue; } ext_name[n] = '\0'; ignore = 0; if (!*c) more = 0; else { c++; if (!n) continue; } /* check we actually support it */ lwsl_notice("checking client ext %s\n", ext_name); n = 0; ext = lws_get_context(wsi)->extensions; while (ext && ext->callback) { if (strcmp(ext_name, ext->name)) { ext++; continue; } n = 1; lwsl_notice("instantiating client ext %s\n", ext_name); /* instantiate the extension on this conn */ wsi->active_extensions[wsi->count_act_ext] = ext; /* allow him to construct his ext instance */ ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_CLIENT_CONSTRUCT, (void *)&wsi->act_ext_user[wsi->count_act_ext], (void *)&opts, 0); /* * allow the user code to override ext defaults if it * wants to */ ext_name[0] = '\0'; if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, (char *)ext->name, ext_name, sizeof(ext_name))) goto bail2; if (ext_name[0] && lws_ext_parse_options(ext, wsi, wsi->act_ext_user[ wsi->count_act_ext], opts, ext_name, strlen(ext_name))) { lwsl_err("%s: unable to parse user defaults '%s'", __func__, ext_name); goto bail2; } /* * give the extension the server options */ if (a && lws_ext_parse_options(ext, wsi, wsi->act_ext_user[wsi->count_act_ext], opts, a, c - a)) { lwsl_err("%s: unable to parse remote def '%s'", __func__, a); goto bail2; } if (ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_OPTION_CONFIRM, wsi->act_ext_user[wsi->count_act_ext], NULL, 0)) { lwsl_err("%s: ext %s rejects server options %s", ext->name, a); goto bail2; } wsi->count_act_ext++; ext++; } if (n == 0) { lwsl_warn("Unknown ext '%s'!\n", ext_name); goto bail2; } a = NULL; n = 0; } check_accept: #endif /* * Confirm his accept token is the one we precomputed */ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) { lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, wsi->u.hdr.ah->initial_handshake_hash_base64); goto bail2; } /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); goto bail2; } /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) goto bail2; /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* free up his parsing allocations */ lws_free_header_table(wsi); lws_union_transition(wsi, LWSCM_WS_CLIENT); wsi->state = LWSS_ESTABLISHED; wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, then * use a big default for compatibility */ n = wsi->protocol->rx_buffer_size; if (!n) n = LWS_MAX_SOCKET_IO_BUF; n += LWS_PRE; wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */); if (!wsi->u.ws.rx_ubuf) { lwsl_err("Out of Mem allocating rx buffer %d\n", n); goto bail2; } wsi->u.ws.rx_ubuf_alloc = n; lwsl_info("Allocating client RX buffer %d\n", n); if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); goto bail3; } lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, wsi->user_space, NULL, 0)) goto bail3; #ifndef LWS_NO_EXTENSIONS /* * inform all extensions, not just active ones since they * already know */ ext = context->extensions; while (ext && ext->callback) { v = NULL; for (n = 0; n < wsi->count_act_ext; n++) if (wsi->active_extensions[n] == ext) v = wsi->act_ext_user[n]; ext->callback(context, ext, wsi, LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); ext++; } #endif return 0; bail3: close_reason = LWS_CLOSE_STATUS_NOSTATUS; bail2: if (wsi->protocol && wsi->state == LWSS_ESTABLISHED) { if (isErrorCodeReceived && p) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, p, (unsigned int)strlen(p)); } else { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0); } } lwsl_info("closing connection due to bail2 connection error\n"); /* closing will free up his parsing allocations */ lws_close_free_wsi(wsi, close_reason); return 1; }
LWS_VISIBLE LWS_EXTERN struct lws * lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, const char *readbuf, size_t len) { struct lws *wsi = lws_adopt_socket(context, accept_fd); struct lws_context_per_thread *pt; struct allocated_headers *ah; struct lws_pollfd *pfd; if (!wsi) return NULL; if (!readbuf) return wsi; if (len > sizeof(ah->rx)) { lwsl_err("%s: rx in too big\n", __func__); goto bail; } /* * we can't process the initial read data until we can attach an ah. * * if one is available, get it and place the data in his ah rxbuf... * wsi with ah that have pending rxbuf get auto-POLLIN service. * * no autoservice because we didn't get a chance to attach the * readbuf data to wsi or ah yet, and we will do it next if we get * the ah. */ if (!lws_header_table_attach(wsi, 0)) { ah = wsi->u.hdr.ah; memcpy(ah->rx, readbuf, len); ah->rxpos = 0; ah->rxlen = len; lwsl_notice("%s: calling service on readbuf ah\n", __func__); pt = &context->pt[(int)wsi->tsi]; /* unlike a normal connect, we have the headers already * (or the first part of them anyway). * libuv won't come back and service us without a network * event, so we need to do the header service right here. */ pfd = &pt->fds[wsi->position_in_fds_table]; pfd->revents |= LWS_POLLIN; lwsl_err("%s: calling service\n", __func__); if (lws_service_fd_tsi(context, pfd, wsi->tsi)) /* service closed us */ return NULL; return wsi; } lwsl_err("%s: deferring handling ah\n", __func__); /* * hum if no ah came, we are on the wait list and must defer * dealing with this until the ah arrives. * * later successful lws_header_table_attach() will apply the * below to the rx buffer (via lws_header_table_reset()). */ wsi->u.hdr.preamble_rx = lws_malloc(len); memcpy(wsi->u.hdr.preamble_rx, readbuf, len); wsi->u.hdr.preamble_rx_len = len; return wsi; bail: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; }
char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { char buf[128], hash[20], key_b64[40], *p = pkt; struct lws_context *context = wsi->context; int n; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; int ext_count = 0; #endif /* * create the random key */ n = lws_get_random(context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); /* * 00 example client handshake * * GET /socket.io/websocket HTTP/1.1 * Upgrade: WebSocket * Connection: Upgrade * Host: 127.0.0.1:9999 * Origin: http://127.0.0.1 * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 * Cookie: socketio=websocket * * (Á®Ä0¶†≥ * * 04 example client handshake * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 4 */ p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Key: "); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) p += sprintf(p, "Origin: http://%s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ p += sprintf(p, "Sec-WebSocket-Extensions: "); #ifndef LWS_NO_EXTENSIONS ext = context->extensions; while (ext && ext->callback) { n = lws_ext_cb_all_exts(context, wsi, LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, (char *)ext->name, 0); if (n) { /* an extension vetos us */ lwsl_ext("ext %s vetoed\n", (char *)ext->name); ext++; continue; } n = context->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means * go ahead and allow the extension, * it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } #endif p += sprintf(p, "\x0d\x0a"); if (wsi->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ietf_spec_revision); /* give userland a chance to append, eg, cookies */ context->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, NULL, &p, (pkt + LWS_MAX_SOCKET_IO_BUF) - p - 12); p += sprintf(p, "\x0d\x0a"); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->u.hdr.ah->initial_handshake_hash_base64, sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); return p; }
LWS_VISIBLE int lws_read(struct lws *wsi, unsigned char *buf, size_t len) { unsigned char *last_char, *oldbuf = buf; int body_chunk_len; size_t n; lwsl_debug("%s: incoming len %d\n", __func__, (int)len); switch (wsi->state) { #ifdef LWS_USE_HTTP2 case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: case LWSS_HTTP2_ESTABLISHED: n = 0; while (n < len) { /* * we were accepting input but now we stopped doing so */ if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { lws_rxflow_cache(wsi, buf, n, len); return 1; } /* account for what we're using in rxflow buffer */ if (wsi->rxflow_buffer) wsi->rxflow_pos++; if (lws_http2_parser(wsi, buf[n++])) goto bail; } break; #endif case LWSS_HTTP: wsi->hdr_parsing_completed = 0; /* fallthru */ case LWSS_HTTP_ISSUING_FILE: wsi->state = LWSS_HTTP_HEADERS; wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.hdr.lextable_pos = 0; /* fallthru */ case LWSS_HTTP_HEADERS: assert(wsi->u.hdr.ah); lwsl_parser("issuing %d bytes to parser\n", (int)len); if (lws_handshake_client(wsi, &buf, len)) goto bail; last_char = buf; if (lws_handshake_server(wsi, &buf, len)) /* Handshake indicates this session is done. */ goto bail; /* * It's possible that we've exhausted our data already, or * rx flow control has stopped us dealing with this early, * but lws_handshake_server doesn't update len for us. * Figure out how much was read, so that we can proceed * appropriately: */ len -= (buf - last_char); lwsl_debug("%s: thinks we have used %d\n", __func__, len); if (!wsi->hdr_parsing_completed) /* More header content on the way */ goto read_ok; switch (wsi->state) { case LWSS_HTTP: case LWSS_HTTP_HEADERS: goto http_complete; case LWSS_HTTP_ISSUING_FILE: goto read_ok; case LWSS_HTTP_BODY: wsi->u.http.content_remain = wsi->u.http.content_length; if (wsi->u.http.content_remain) goto http_postbody; /* there is no POST content */ goto postbody_completion; default: break; } break; case LWSS_HTTP_BODY: http_postbody: while (len && wsi->u.http.content_remain) { /* Copy as much as possible, up to the limit of: * what we have in the read buffer (len) * remaining portion of the POST body (content_remain) */ body_chunk_len = min(wsi->u.http.content_remain,len); wsi->u.http.content_remain -= body_chunk_len; len -= body_chunk_len; n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY, wsi->user_space, buf, body_chunk_len); if (n) goto bail; buf += body_chunk_len; if (wsi->u.http.content_remain) { lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); break; } /* he sent all the content in time */ postbody_completion: lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BODY_COMPLETION, wsi->user_space, NULL, 0); if (n) goto bail; goto http_complete; } break; case LWSS_ESTABLISHED: case LWSS_AWAITING_CLOSE_ACK: case LWSS_SHUTDOWN: if (lws_handshake_client(wsi, &buf, len)) goto bail; switch (wsi->mode) { case LWSCM_WS_SERVING: if (lws_interpret_incoming_packet(wsi, &buf, len) < 0) { lwsl_info("interpret_incoming_packet has bailed\n"); goto bail; } break; } break; default: lwsl_err("%s: Unhandled state\n", __func__); break; } read_ok: /* Nothing more to do for now */ lwsl_info("%s: read_ok, used %d\n", __func__, buf - oldbuf); return buf - oldbuf; http_complete: lwsl_debug("%s: http_complete\n", __func__); #ifndef LWS_NO_SERVER /* Did the client want to keep the HTTP connection going? */ if (lws_http_transaction_completed(wsi)) goto bail; #endif /* we may have next header set already, but return to event loop first * so a heaily-pipelined http/1.1 connection cannot monopolize the * service thread with GET hugefile.bin GET hugefile.bin etc */ goto read_ok; bail: lwsl_debug("closing connection at lws_read bail:\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return -1; }