LWS_VISIBLE void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; time_t now; lws_pt_lock(pt); time(&now); if (reason && !wsi->timeout_list_prev) { /* our next guy is current first guy */ wsi->timeout_list = pt->timeout_list; /* if there is a next guy, set his prev ptr to our next ptr */ if (wsi->timeout_list) wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; /* our prev ptr is first ptr */ wsi->timeout_list_prev = &pt->timeout_list; /* set the first guy to be us */ *wsi->timeout_list_prev = wsi; } lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); wsi->pending_timeout_limit = now + secs; wsi->pending_timeout = reason; lws_pt_unlock(pt); if (!reason) lws_remove_from_timeout_list(wsi); }
int lws_change_pollfd(struct lws *wsi, int _and, int _or) { struct lws_context_per_thread *pt; struct lws_context *context; struct lws_pollargs pa; int ret = 0; if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0) return 1; context = lws_get_context(wsi); if (!context) return 1; if (context->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *) &pa, 0)) return -1; pt = &context->pt[(int)wsi->tsi]; lws_pt_lock(pt); ret = _lws_change_pollfd(wsi, _and, _or, &pa); lws_pt_unlock(pt); if (context->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *) &pa, 0)) ret = -1; return ret; }
LWS_VISIBLE void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; time_t now; lws_pt_lock(pt); time(&now); if (!wsi->pending_timeout && reason) { wsi->timeout_list = pt->timeout_list; if (wsi->timeout_list) wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; wsi->timeout_list_prev = &pt->timeout_list; *wsi->timeout_list_prev = wsi; } wsi->pending_timeout_limit = now + secs; wsi->pending_timeout = reason; lws_pt_unlock(pt); if (!reason) lws_remove_from_timeout_list(wsi); }
int insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) { struct lws_pollargs pa = { wsi->sock, LWS_POLLIN, 0 }; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; int ret = 0; #ifndef LWS_NO_SERVER struct lws_pollargs pa1; #endif lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n", __func__, wsi, wsi->tsi, wsi->sock, pt->fds_count); if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) { lwsl_err("Too many fds (%d)\n", context->max_fds); return 1; } #if !defined(_WIN32) && !defined(MBED_OPERATORS) if (wsi->sock >= context->max_fds) { lwsl_err("Socket fd %d is too high (%d)\n", wsi->sock, context->max_fds); return 1; } #endif assert(wsi); assert(lws_socket_is_valid(wsi->sock)); if (context->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *) &pa, 1)) return -1; lws_pt_lock(pt); pt->count_conns++; insert_wsi(context, wsi); wsi->position_in_fds_table = pt->fds_count; pt->fds[pt->fds_count].fd = wsi->sock; pt->fds[pt->fds_count].events = LWS_POLLIN; pa.events = pt->fds[pt->fds_count].events; lws_plat_insert_socket_into_fds(context, wsi); /* external POLL support via protocol 0 */ if (context->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD, wsi->user_space, (void *) &pa, 0)) ret = -1; #ifndef LWS_NO_SERVER /* if no more room, defeat accepts on this thread */ if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1) _lws_change_pollfd(pt->wsi_listening, LWS_POLLIN, 0, &pa1); #endif lws_pt_unlock(pt); if (context->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *)&pa, 1)) ret = -1; return ret; }
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; }
static void lws_uv_hrtimer_cb(uv_timer_t *timer #if UV_VERSION_MAJOR == 0 , int status #endif ) { struct lws_context_per_thread *pt = lws_container_of(timer, struct lws_context_per_thread, uv.hrtimer); lws_usec_t us; lws_pt_lock(pt, __func__); us = __lws_hrtimer_service(pt); if (us != LWS_HRTIMER_NOWAIT) uv_timer_start(&pt->uv.hrtimer, lws_uv_hrtimer_cb, us / 1000, 0); lws_pt_unlock(pt); }
static void lws_remove_from_timeout_list(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; if (!wsi->timeout_list_prev) return; lws_pt_lock(pt); if (wsi->timeout_list) wsi->timeout_list->timeout_list_prev = wsi->timeout_list_prev; *wsi->timeout_list_prev = wsi->timeout_list; wsi->timeout_list_prev = NULL; wsi->timeout_list = NULL; lws_pt_unlock(pt); }
static void lws_remove_from_timeout_list(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; if (!wsi->timeout_list_prev) /* ie, not part of the list */ return; lws_pt_lock(pt); /* if we have a next guy, set his prev to our prev */ if (wsi->timeout_list) wsi->timeout_list->timeout_list_prev = wsi->timeout_list_prev; /* set our prev guy to our next guy instead of us */ *wsi->timeout_list_prev = wsi->timeout_list; /* we're out of the list, we should not point anywhere any more */ wsi->timeout_list_prev = NULL; wsi->timeout_list = NULL; lws_pt_unlock(pt); }
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); }
static void lws_uv_idle(uv_idle_t *handle #if UV_VERSION_MAJOR == 0 , int status #endif ) { struct lws_context_per_thread *pt = lws_container_of(handle, struct lws_context_per_thread, uv.idle); lws_usec_t us; lws_service_do_ripe_rxflow(pt); /* * is there anybody with pending stuff that needs service forcing? */ if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) { /* -1 timeout means just do forced service */ _lws_plat_service_tsi(pt->context, -1, pt->tid); /* still somebody left who wants forced service? */ if (!lws_service_adjust_timeout(pt->context, 1, pt->tid)) /* yes... come back again later */ return; } /* account for hrtimer */ lws_pt_lock(pt, __func__); us = __lws_hrtimer_service(pt); if (us != LWS_HRTIMER_NOWAIT) uv_timer_start(&pt->uv.hrtimer, lws_uv_hrtimer_cb, us / 1000, 0); lws_pt_unlock(pt); /* there is nobody who needs service forcing, shut down idle */ uv_idle_stop(handle); }
int remove_wsi_socket_from_fds(struct lws *wsi) { struct lws_pollargs pa = { wsi->sock, 0, 0 }; #ifndef LWS_NO_SERVER struct lws_pollargs pa1; #endif struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct lws *end_wsi; int m, ret = 0; #if !defined(_WIN32) && !defined(MBED_OPERATORS) if (wsi->sock > context->max_fds) { lwsl_err("fd %d too high (%d)\n", wsi->sock, context->max_fds); return 1; } #endif if (context->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *)&pa, 1)) return -1; lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); lws_pt_lock(pt); lwsl_info("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n", __func__, wsi, wsi->sock, wsi->position_in_fds_table, pt->fds_count, pt->fds[pt->fds_count].fd); /* the guy who is to be deleted's slot index in pt->fds */ m = wsi->position_in_fds_table; /* have the last guy take up the now vacant slot */ pt->fds[m] = pt->fds[pt->fds_count - 1]; lws_plat_delete_socket_from_fds(context, wsi, m); /* end guy's "position in fds table" is now the deletion guy's old one */ end_wsi = wsi_from_fd(context, pt->fds[pt->fds_count].fd); assert(end_wsi); end_wsi->position_in_fds_table = m; /* deletion guy's lws_lookup entry needs nuking */ delete_from_fd(context, wsi->sock); /* removed wsi has no position any more */ wsi->position_in_fds_table = -1; /* remove also from external POLL support via protocol 0 */ if (lws_socket_is_valid(wsi->sock)) if (context->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD, wsi->user_space, (void *) &pa, 0)) ret = -1; #ifndef LWS_NO_SERVER /* if this made some room, accept connects on this thread */ if ((unsigned int)pt->fds_count < context->fd_limit_per_thread - 1) _lws_change_pollfd(pt->wsi_listening, 0, LWS_POLLIN, &pa1); #endif lws_pt_unlock(pt); if (context->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *) &pa, 1)) ret = -1; return ret; }
LWS_VISIBLE int lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) { struct lws_context *context = wsi->context; struct lws_vhost *vh; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; int n; char buf[256]; (void)buf; if (!LWS_SSL_ENABLED(wsi->vhost)) return 0; switch (lwsi_state(wsi)) { case LRS_SSL_INIT: if (wsi->tls.ssl) lwsl_err("%s: leaking ssl\n", __func__); if (accept_fd == LWS_SOCK_INVALID) assert(0); if (context->simultaneous_ssl_restriction && context->simultaneous_ssl >= context->simultaneous_ssl_restriction) { lwsl_notice("unable to deal with SSL connection\n"); return 1; } if (lws_tls_server_new_nonblocking(wsi, accept_fd)) { if (accept_fd != LWS_SOCK_INVALID) compatible_close(accept_fd); goto fail; } if (context->simultaneous_ssl_restriction && ++context->simultaneous_ssl == context->simultaneous_ssl_restriction) /* that was the last allowed SSL connection */ lws_gate_accepts(context, 0); #if defined(LWS_WITH_STATS) context->updated = 1; #endif /* * we are not accepted yet, but we need to enter ourselves * as a live connection. That way we can retry when more * pieces come if we're not sorted yet */ lwsi_set_state(wsi, LRS_SSL_ACK_PENDING); lws_pt_lock(pt, __func__); if (__insert_wsi_socket_into_fds(context, wsi)) { lwsl_err("%s: failed to insert into fds\n", __func__); goto fail; } lws_pt_unlock(pt); lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, context->timeout_secs); lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n"); /* fallthru */ case LRS_SSL_ACK_PENDING: if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_err("%s: lws_change_pollfd failed\n", __func__); goto fail; } lws_latency_pre(context, wsi); if (wsi->vhost->tls.allow_non_ssl_on_ssl_port) { n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, context->pt_serv_buf_size, MSG_PEEK); /* * optionally allow non-SSL connect on SSL listening socket * This is disabled by default, if enabled it goes around any * SSL-level access control (eg, client-side certs) so leave * it disabled unless you know it's not a problem for you */ if (n >= 1 && pt->serv_buf[0] >= ' ') { /* * TLS content-type for Handshake is 0x16, and * for ChangeCipherSpec Record, it's 0x14 * * A non-ssl session will start with the HTTP * method in ASCII. If we see it's not a legit * SSL handshake kill the SSL for this * connection and try to handle as a HTTP * connection upgrade directly. */ wsi->tls.use_ssl = 0; lws_tls_server_abort_connection(wsi); /* * care... this creates wsi with no ssl * when ssl is enabled and normally * mandatory */ wsi->tls.ssl = NULL; if (lws_check_opt(context->options, LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) wsi->tls.redirect_to_https = 1; lwsl_debug("accepted as non-ssl\n"); goto accepted; } if (!n) { /* * connection is gone, fail out */ lwsl_debug("PEEKed 0\n"); goto fail; } if (n < 0 && (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK)) { /* * well, we get no way to know ssl or not * so go around again waiting for something * to come and give us a hint, or timeout the * connection. */ if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { lwsl_info("%s: change_pollfd failed\n", __func__); return -1; } lwsl_info("SSL_ERROR_WANT_READ\n"); return 0; } } /* normal SSL connection processing path */ #if defined(LWS_WITH_STATS) /* only set this the first time around */ if (!wsi->accept_start_us) wsi->accept_start_us = lws_time_in_microseconds(); #endif errno = 0; lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, 1); n = lws_tls_server_accept(wsi); lws_latency(context, wsi, "SSL_accept LRS_SSL_ACK_PENDING\n", n, n == 1); lwsl_info("SSL_accept says %d\n", n); switch (n) { case LWS_SSL_CAPABLE_DONE: break; case LWS_SSL_CAPABLE_ERROR: lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); lwsl_info("SSL_accept failed socket %u: %d\n", wsi->desc.sockfd, n); wsi->socket_is_permanently_unusable = 1; goto fail; default: /* MORE_SERVICE */ return 0; } lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1); #if defined(LWS_WITH_STATS) if (wsi->accept_start_us) lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, lws_time_in_microseconds() - wsi->accept_start_us); wsi->accept_start_us = lws_time_in_microseconds(); #endif accepted: /* adapt our vhost to match the SNI SSL_CTX that was chosen */ vh = context->vhost_list; while (vh) { if (!vh->being_destroyed && wsi->tls.ssl && vh->tls.ssl_ctx == lws_tls_ctx_from_wsi(wsi)) { lwsl_info("setting wsi to vh %s\n", vh->name); lws_vhost_bind_wsi(vh, wsi); break; } vh = vh->vhost_next; } /* OK, we are accepted... give him some time to negotiate */ lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, context->timeout_secs); lwsi_set_state(wsi, LRS_ESTABLISHED); if (lws_tls_server_conn_alpn(wsi)) goto fail; lwsl_debug("accepted new SSL conn\n"); break; default: break; } return 0; fail: return 1; }
/* * guys that need POLLIN service again without waiting for network action * can force POLLIN here if not flowcontrolled, so they will get service. * * Return nonzero if anybody got their POLLIN faked */ int lws_service_flag_pending(struct lws_context *context, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; #if defined(LWS_WITH_TLS) struct lws *wsi, *wsi_next; #endif int forced = 0; lws_pt_lock(pt, __func__); /* * 1) If there is any wsi with a buflist and in a state to process * it, we should not wait in poll */ lws_start_foreach_dll(struct lws_dll_lws *, d, pt->dll_head_buflist.next) { struct lws *wsi = lws_container_of(d, struct lws, dll_buflist); if (lwsi_state(wsi) != LRS_DEFERRING_ACTION) { forced = 1; break; } } lws_end_foreach_dll(d); #if defined(LWS_ROLE_WS) forced |= role_ops_ws.service_flag_pending(context, tsi); #endif #if defined(LWS_WITH_TLS) /* * 2) For all guys with buffered SSL read data already saved up, if they * are not flowcontrolled, fake their POLLIN status so they'll get * service to use up the buffered incoming data, even though their * network socket may have nothing */ wsi = pt->tls.pending_read_list; while (wsi) { wsi_next = wsi->tls.pending_read_list_next; pt->fds[wsi->position_in_fds_table].revents |= pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { forced = 1; /* * he's going to get serviced now, take him off the * list of guys with buffered SSL. If he still has some * at the end of the service, he'll get put back on the * list then. */ __lws_ssl_remove_wsi_from_buffered_list(wsi); } wsi = wsi_next; } #endif lws_pt_unlock(pt); return forced; }
static void lws_libuv_closewsi(uv_handle_t* handle) { struct lws *wsi = (struct lws *)handle->data; struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; #if !defined(LWS_WITHOUT_SERVER) int lspd = 0; #endif lwsl_info("%s: %p\n", __func__, wsi); /* * We get called back here for every wsi that closes */ #if !defined(LWS_WITHOUT_SERVER) if (wsi->role_ops == &role_ops_listen && wsi->context->deprecated) { lspd = 1; context->deprecation_pending_listen_close_count--; if (!context->deprecation_pending_listen_close_count) lspd = 2; } #endif lws_pt_lock(pt, __func__); __lws_close_free_wsi_final(wsi); lws_pt_unlock(pt); /* it's our job to close the handle finally */ lws_free(handle); #if !defined(LWS_WITHOUT_SERVER) if (lspd == 2 && context->deprecation_cb) { lwsl_notice("calling deprecation callback\n"); context->deprecation_cb(); } #endif lwsl_info("%s: sa left %d: dyn left: %d (rk %d)\n", __func__, context->count_event_loop_static_asset_handles, context->count_wsi_allocated, context->requested_kill); /* * eventually, we closed all the wsi... */ if (context->requested_kill && !context->count_wsi_allocated) { struct lws_vhost *vh = context->vhost_list; int m; /* * Start Closing Phase 2: close of static handles */ lwsl_info("%s: all lws dynamic handles down, closing static\n", __func__); for (m = 0; m < context->count_threads; m++) elops_destroy_pt_uv(context, m); /* protocols may have initialized libuv objects */ while (vh) { lws_vhost_destroy1(vh); vh = vh->vhost_next; } if (!context->count_event_loop_static_asset_handles && context->pt[0].event_loop_foreign) { lwsl_info("%s: call lws_context_destroy2\n", __func__); lws_context_destroy2(context); } } }
static int lws_service_periodic_checks(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 *wsi; int timed_out = 0; time_t now; #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct allocated_headers *ah; int m; #endif if (!context->protocol_init_done) if (lws_protocol_init(context)) return -1; time(&now); /* * handle case that system time was uninitialized when lws started * at boot, and got initialized a little later */ if (context->time_up < 1464083026 && now > 1464083026) context->time_up = now; if (context->last_timeout_check_s && now - context->last_timeout_check_s > 100) { /* * There has been a discontiguity. Any stored time that is * less than context->time_discontiguity should have context-> * time_fixup added to it. * * Some platforms with no RTC will experience this as a normal * event when ntp sets their clock, but we can have started * long before that with a 0-based unix time. */ context->time_discontiguity = now; context->time_fixup = now - context->last_timeout_check_s; lwsl_notice("time discontiguity: at old time %llus, " "new time %llus: +%llus\n", (unsigned long long)context->last_timeout_check_s, (unsigned long long)context->time_discontiguity, (unsigned long long)context->time_fixup); context->last_timeout_check_s = now - 1; } if (!lws_compare_time_t(context, context->last_timeout_check_s, now)) return 0; context->last_timeout_check_s = now; #if defined(LWS_WITH_STATS) if (!tsi && now - context->last_dump > 10) { lws_stats_log_dump(context); context->last_dump = now; } #endif lws_plat_service_periodic(context); lws_check_deferred_free(context, 0); #if defined(LWS_WITH_PEER_LIMITS) lws_peer_cull_peer_wait_list(context); #endif /* retire unused deprecated context */ #if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32) #if !defined(_WIN32) if (context->deprecated && !context->count_wsi_allocated) { lwsl_notice("%s: ending deprecated context\n", __func__); kill(getpid(), SIGINT); return 0; } #endif #endif /* global timeout check once per second */ if (pollfd) our_fd = pollfd->fd; /* * Phase 1: check every wsi on the timeout check list */ lws_pt_lock(pt, __func__); lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, context->pt[tsi].dll_head_timeout.next) { wsi = lws_container_of(d, struct lws, dll_timeout); tmp_fd = wsi->desc.sockfd; if (__lws_service_timeout_check(wsi, 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 */ } } lws_end_foreach_dll_safe(d, d1); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* * Phase 2: double-check active ah timeouts independent of wsi * timeout status */ ah = pt->http.ah_list; while (ah) { int len; char buf[256]; const unsigned char *c; if (!ah->in_use || !ah->wsi || !ah->assigned || (ah->wsi->vhost && lws_compare_time_t(context, now, ah->assigned) < ah->wsi->vhost->timeout_secs_ah_idle + 360)) { ah = ah->next; continue; } /* * a single ah session somehow got held for * an unreasonable amount of time. * * Dump info on the connection... */ wsi = ah->wsi; buf[0] = '\0'; #if !defined(LWS_PLAT_OPTEE) lws_get_peer_simple(wsi, buf, sizeof(buf)); #else buf[0] = '\0'; #endif lwsl_notice("ah excessive hold: wsi %p\n" " peer address: %s\n" " ah pos %u\n", wsi, buf, ah->pos); buf[0] = '\0'; m = 0; do { c = lws_token_to_string(m); if (!c) break; if (!(*c)) break; len = lws_hdr_total_length(wsi, m); if (!len || len > (int)sizeof(buf) - 1) { m++; continue; } if (lws_hdr_copy(wsi, buf, sizeof buf, m) > 0) { buf[sizeof(buf) - 1] = '\0'; lwsl_notice(" %s = %s\n", (const char *)c, buf); } m++; } while (1); /* explicitly detach the ah */ lws_header_table_detach(wsi, 0); /* ... and then drop the connection */ m = 0; if (wsi->desc.sockfd == our_fd) { m = timed_out; /* it was the guy we came to service! */ timed_out = 1; } if (!m) /* if he didn't already timeout */ __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "excessive ah"); ah = pt->http.ah_list; } #endif lws_pt_unlock(pt); #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 /* * Phase 3: vhost / protocol timer callbacks */ wsi = NULL; lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { struct lws_timed_vh_protocol *nx; if (v->timed_vh_protocol_list) { lws_start_foreach_ll(struct lws_timed_vh_protocol *, q, v->timed_vh_protocol_list) { if (now >= q->time) { if (!wsi) wsi = lws_zalloc(sizeof(*wsi), "cbwsi"); wsi->context = context; wsi->vhost = v; wsi->protocol = q->protocol; lwsl_debug("timed cb: vh %s, protocol %s, reason %d\n", v->name, q->protocol->name, q->reason); q->protocol->callback(wsi, q->reason, NULL, NULL, 0); nx = q->next; lws_timed_callback_remove(v, q); q = nx; continue; /* we pointed ourselves to the next from the now-deleted guy */ } } lws_end_foreach_ll(q, next); } } lws_end_foreach_ll(v, vhost_next);
int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct _lws_header_related hdr; struct allocated_headers *ah; int protocol_len, n, hit; char protocol_list[128]; char protocol_name[32]; char *p; assert(len < 10000000); assert(wsi->u.hdr.ah); while (len--) { wsi->more_rx_waiting = !!len; if (wsi->mode != LWSCM_HTTP_SERVING && wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED) { lwsl_err("%s: bad wsi mode %d\n", __func__, wsi->mode); goto bail_nuke_ah; } if (lws_parse(wsi, *(*buf)++)) { lwsl_info("lws_parse failed\n"); goto bail_nuke_ah; } if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) continue; lwsl_parser("%s: lws_parse sees parsing complete\n", __func__); lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, wsi->more_rx_waiting); /* select vhost */ if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { struct lws_vhost *vhost = lws_select_vhost( context, wsi->vhost->listen_port, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); if (vhost) wsi->vhost = vhost; } wsi->vhost->trans++; if (!wsi->conn_stat_done) { wsi->vhost->conn++; wsi->conn_stat_done = 1; } wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* is this websocket protocol or normal http 1.0? */ if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "websocket")) { wsi->vhost->ws_upgrades++; lwsl_info("Upgrade to ws\n"); goto upgrade_ws; } #ifdef LWS_USE_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c")) { wsi->vhost->http2_upgrades++; lwsl_info("Upgrade to h2c\n"); goto upgrade_h2c; } #endif lwsl_err("Unknown upgrade\n"); /* dunno what he wanted to upgrade to */ goto bail_nuke_ah; } /* no upgrade ack... he remained as HTTP */ lwsl_info("No upgrade\n"); ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); wsi->state = LWSS_HTTP; wsi->u.http.fd = LWS_INVALID_FILE; /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, (void *)wsi->u.hdr.ah); n = lws_http_action(wsi); return n; #ifdef LWS_USE_HTTP2 upgrade_h2c: if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { lwsl_err("missing http2_settings\n"); goto bail_nuke_ah; } lwsl_err("h2c upgrade...\n"); p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); /* convert the peer's HTTP-Settings */ n = lws_b64_decode_string(p, protocol_list, sizeof(protocol_list)); if (n < 0) { lwsl_parser("HTTP2_SETTINGS too long\n"); return 1; } /* adopt the header info */ ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP2_SERVING); /* http2 union member has http union struct at start */ wsi->u.http.ah = ah; lws_http2_init(&wsi->u.http2.peer_settings); lws_http2_init(&wsi->u.http2.my_settings); /* HTTP2 union */ lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, (unsigned char *)protocol_list, n); strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Upgrade: h2c\x0d\x0a\x0d\x0a"); n = lws_issue_raw(wsi, (unsigned char *)protocol_list, strlen(protocol_list)); if (n != strlen(protocol_list)) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE; return 0; #endif upgrade_ws: if (!wsi->protocol) lwsl_err("NULL protocol at lws_read\n"); /* * It's websocket * * Select the first protocol we support from the list * the client sent us. * * Copy it to remove header fragmentation */ if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, WSI_TOKEN_PROTOCOL) < 0) { lwsl_err("protocol list too long"); goto bail_nuke_ah; } protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); protocol_list[protocol_len] = '\0'; p = protocol_list; hit = 0; while (*p && !hit) { n = 0; while (n < sizeof(protocol_name) - 1 && *p && *p !=',') protocol_name[n++] = *p++; protocol_name[n] = '\0'; if (*p) p++; lwsl_info("checking %s\n", protocol_name); n = 0; while (wsi->vhost->protocols[n].callback) { if (wsi->vhost->protocols[n].name && !strcmp(wsi->vhost->protocols[n].name, protocol_name)) { wsi->protocol = &wsi->vhost->protocols[n]; hit = 1; break; } n++; } } /* we didn't find a protocol he wanted? */ if (!hit) { if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { lwsl_err("No protocol from \"%s\" supported\n", protocol_list); goto bail_nuke_ah; } /* * some clients only have one protocol and * do not sent the protocol list header... * allow it and match to protocol 0 */ lwsl_info("defaulting to prot 0 handler\n"); n = 0; wsi->protocol = &wsi->vhost->protocols[0]; } /* allocate wsi->user storage */ if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; /* * Give the user code a chance to study the request and * have the opportunity to deny it */ if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, wsi->user_space, lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { lwsl_warn("User code denied connection\n"); goto bail_nuke_ah; } /* * stitch protocol choice into the vh protocol linked list */ 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; /* * Perform the handshake according to the protocol version the * client announced */ switch (wsi->ietf_spec_revision) { case 13: lwsl_parser("lws_parse calling handshake_04\n"); if (handshake_0405(context, wsi)) { lwsl_info("hs0405 has failed the connection\n"); goto bail_nuke_ah; } break; default: lwsl_warn("Unknown client spec version %d\n", wsi->ietf_spec_revision); goto bail_nuke_ah; } /* we are upgrading to ws, so http/1.1 and keepalive + * pipelined header considerations about keeping the ah around * no longer apply. However it's common for the first ws * protocol data to have been coalesced with the browser * upgrade request and to already be in the ah rx buffer. */ lwsl_info("%s: %p: inheriting ah in ws mode (rxpos:%d, rxlen:%d)\n", __func__, wsi, wsi->u.hdr.ah->rxpos, wsi->u.hdr.ah->rxlen); lws_pt_lock(pt); hdr = wsi->u.hdr; lws_union_transition(wsi, LWSCM_WS_SERVING); /* * first service is WS mode will notice this, use the RX and * then detach the ah (caution: we are not in u.hdr union * mode any more then... ah_temp member is at start the same * though) * * Because rxpos/rxlen shows something in the ah, we will get * service guaranteed next time around the event loop * * All union members begin with hdr, so we can use it even * though we transitioned to ws union mode (the ah detach * code uses it anyway). */ wsi->u.hdr = hdr; lws_pt_unlock(pt); /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, use * a big default for compatibility */ n = 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); return 1; } wsi->u.ws.rx_ubuf_alloc = n; lwsl_info("Allocating RX buffer %d\n", n); #if LWS_POSIX if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); return 1; } #endif lwsl_parser("accepted v%02d connection\n", wsi->ietf_spec_revision); return 0; } /* while all chars are handled */ return 0; bail_nuke_ah: /* drop the header info */ /* we're closing, losing some rx is OK */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; lws_header_table_detach(wsi, 1); return 1; }
int lws_context_init_server(struct lws_context_creation_info *info, struct lws_context *context) { #ifdef LWS_POSIX int n, opt = 1, limit = 1; #endif lws_sockfd_type sockfd; struct lws *wsi; int m = 0; /* set up our external listening socket we serve on */ if (info->port == CONTEXT_PORT_NO_LISTEN) return 0; #if LWS_POSIX #if defined(__linux__) limit = context->count_threads; #endif for (m = 0; m < limit; m++) { #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { #else sockfd = mbed3_create_tcp_stream_socket(); if (!lws_sockfd_valid(sockfd)) { #endif lwsl_err("ERROR opening socket\n"); return 1; } #if LWS_POSIX /* * allow us to restart even if old sockets in TIME_WAIT */ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); return 1; } #if defined(__linux__) && defined(SO_REUSEPORT) && LWS_MAX_SMP > 1 if (context->count_threads > 1) if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); return 1; } #endif #endif lws_plat_set_socket_options(context, sockfd); #if LWS_POSIX n = lws_socket_bind(context, sockfd, info->port, info->iface); if (n < 0) goto bail; info->port = n; #endif context->listen_port = info->port; wsi = lws_zalloc(sizeof(struct lws)); if (wsi == NULL) { lwsl_err("Out of mem\n"); goto bail; } wsi->context = context; wsi->sock = sockfd; wsi->mode = LWSCM_SERVER_LISTENER; wsi->protocol = context->protocols; wsi->tsi = m; context->pt[m].wsi_listening = wsi; if (insert_wsi_socket_into_fds(context, wsi)) goto bail; context->count_wsi_allocated++; context->pt[m].lserv_fd = sockfd; #if LWS_POSIX listen(wsi->sock, LWS_SOMAXCONN); } /* for each thread able to independently lister */ #else mbed3_tcp_stream_bind(wsi->sock, info->port, wsi); #endif lwsl_notice(" Listening on port %d\n", info->port); return 0; bail: compatible_close(sockfd); return 1; } int _lws_server_listen_accept_flow_control(struct lws *twsi, int on) { struct lws_context_per_thread *pt = &twsi->context->pt[(int)twsi->tsi]; struct lws *wsi = pt->wsi_listening; int n; if (!wsi || twsi->context->being_destroyed) return 0; lwsl_debug("%s: Thr %d: LISTEN wsi %p: state %d\n", __func__, twsi->tsi, (void *)wsi, on); if (on) n = lws_change_pollfd(wsi, 0, LWS_POLLIN); else n = lws_change_pollfd(wsi, LWS_POLLIN, 0); return n; } int lws_http_action(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; enum http_connection_type connection_type; enum http_version request_version; char content_length_str[32]; unsigned int n, count = 0; char http_version_str[10]; char http_conn_str[20]; int http_version_len; char *uri_ptr = NULL; int uri_len = 0; static const unsigned char methods[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, WSI_TOKEN_OPTIONS_URI, WSI_TOKEN_PUT_URI, WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, #ifdef LWS_USE_HTTP2 WSI_TOKEN_HTTP_COLON_PATH, #endif }; #ifdef _DEBUG static const char * const method_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", #ifdef LWS_USE_HTTP2 ":path", #endif }; #endif /* it's not websocket.... shall we accept it as http? */ for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) count++; if (!count) { lwsl_warn("Missing URI in HTTP request\n"); goto bail_nuke_ah; } if (count != 1) { lwsl_warn("multiple methods?\n"); goto bail_nuke_ah; } if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) { uri_ptr = lws_hdr_simple_ptr(wsi, methods[n]); uri_len = lws_hdr_total_length(wsi, methods[n]); lwsl_info("Method: %s request for '%s'\n", method_names[n], uri_ptr); break; } /* HTTP header had a content length? */ wsi->u.http.content_length = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) wsi->u.http.content_length = 100 * 1024 * 1024; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lws_hdr_copy(wsi, content_length_str, sizeof(content_length_str) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH); wsi->u.http.content_length = atoi(content_length_str); } /* http_version? Default to 1.0, override with token: */ request_version = HTTP_VERSION_1_0; /* Works for single digit HTTP versions. : */ http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); if (http_version_len > 7) { lws_hdr_copy(wsi, http_version_str, sizeof(http_version_str) - 1, WSI_TOKEN_HTTP); if (http_version_str[5] == '1' && http_version_str[7] == '1') request_version = HTTP_VERSION_1_1; } wsi->u.http.request_version = request_version; /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ if (request_version == HTTP_VERSION_1_1) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else connection_type = HTTP_CONNECTION_CLOSE; /* Override default if http "Connection:" header: */ if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1, WSI_TOKEN_CONNECTION); http_conn_str[sizeof(http_conn_str) - 1] = '\0'; if (!strcasecmp(http_conn_str, "keep-alive")) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else if (!strcasecmp(http_conn_str, "close")) connection_type = HTTP_CONNECTION_CLOSE; } wsi->u.http.connection_type = connection_type; n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } /* * if there is content supposed to be coming, * put a timeout on it having arrived */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); if (wsi->redirect_to_https) { /* * we accepted http:// only so we could redirect to * https://, so issue the redirect. Create the redirection * URI from the host: header and ignore the path part */ unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + 512; if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) goto bail_nuke_ah; if (lws_add_http_header_status(wsi, 301, &p, end)) goto bail_nuke_ah; n = sprintf((char *)end, "https://%s/", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, end, n, &p, end)) goto bail_nuke_ah; if (lws_finalize_http_header(wsi, &p, end)) goto bail_nuke_ah; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) goto bail_nuke_ah; return lws_http_transaction_completed(wsi); } n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } /* * If we're not issuing a file, check for content_length or * HTTP keep-alive. No keep-alive header allocation for * ISSUING_FILE, as this uses HTTP/1.0. * * In any case, return 0 and let lws_read decide how to * proceed based on state */ if (wsi->state != LWSS_HTTP_ISSUING_FILE) /* Prepare to read body if we have a content length: */ if (wsi->u.http.content_length > 0) wsi->state = LWSS_HTTP_BODY; return 0; bail_nuke_ah: /* we're closing, losing some rx is OK */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; lws_header_table_detach(wsi, 1); return 1; } int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct _lws_header_related hdr; struct allocated_headers *ah; int protocol_len, n, hit; char protocol_list[128]; char protocol_name[32]; char *p; assert(len < 10000000); assert(wsi->u.hdr.ah); while (len--) { wsi->more_rx_waiting = !!len; assert(wsi->mode == LWSCM_HTTP_SERVING); if (lws_parse(wsi, *(*buf)++)) { lwsl_info("lws_parse failed\n"); goto bail_nuke_ah; } if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) continue; lwsl_parser("%s: lws_parse sees parsing complete\n", __func__); lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, wsi->more_rx_waiting); wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* is this websocket protocol or normal http 1.0? */ if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "websocket")) { lwsl_info("Upgrade to ws\n"); goto upgrade_ws; } #ifdef LWS_USE_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c-14")) { lwsl_info("Upgrade to h2c-14\n"); goto upgrade_h2c; } #endif lwsl_err("Unknown upgrade\n"); /* dunno what he wanted to upgrade to */ goto bail_nuke_ah; } /* no upgrade ack... he remained as HTTP */ lwsl_info("No upgrade\n"); ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); wsi->state = LWSS_HTTP; wsi->u.http.fd = LWS_INVALID_FILE; /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, (void *)wsi->u.hdr.ah); n = lws_http_action(wsi); return n; #ifdef LWS_USE_HTTP2 upgrade_h2c: if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { lwsl_err("missing http2_settings\n"); goto bail_nuke_ah; } lwsl_err("h2c upgrade...\n"); p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); /* convert the peer's HTTP-Settings */ n = lws_b64_decode_string(p, protocol_list, sizeof(protocol_list)); if (n < 0) { lwsl_parser("HTTP2_SETTINGS too long\n"); return 1; } /* adopt the header info */ ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP2_SERVING); /* http2 union member has http union struct at start */ wsi->u.http.ah = ah; lws_http2_init(&wsi->u.http2.peer_settings); lws_http2_init(&wsi->u.http2.my_settings); /* HTTP2 union */ lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, (unsigned char *)protocol_list, n); strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Upgrade: h2c\x0d\x0a\x0d\x0a"); n = lws_issue_raw(wsi, (unsigned char *)protocol_list, strlen(protocol_list)); if (n != strlen(protocol_list)) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE; return 0; #endif upgrade_ws: if (!wsi->protocol) lwsl_err("NULL protocol at lws_read\n"); /* * It's websocket * * Select the first protocol we support from the list * the client sent us. * * Copy it to remove header fragmentation */ if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, WSI_TOKEN_PROTOCOL) < 0) { lwsl_err("protocol list too long"); goto bail_nuke_ah; } protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); protocol_list[protocol_len] = '\0'; p = protocol_list; hit = 0; while (*p && !hit) { unsigned int n = 0; while (n < sizeof(protocol_name) - 1 && *p && *p !=',') protocol_name[n++] = *p++; protocol_name[n] = '\0'; if (*p) p++; lwsl_info("checking %s\n", protocol_name); n = 0; while (context->protocols[n].callback) { if (context->protocols[n].name && !strcmp(context->protocols[n].name, protocol_name)) { lwsl_info("prot match %d\n", n); wsi->protocol = &context->protocols[n]; hit = 1; break; } n++; } } /* we didn't find a protocol he wanted? */ if (!hit) { if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { lwsl_err("No protocol from \"%s\" supported\n", protocol_list); goto bail_nuke_ah; } /* * some clients only have one protocol and * do not sent the protocol list header... * allow it and match to protocol 0 */ lwsl_info("defaulting to prot 0 handler\n"); wsi->protocol = &context->protocols[0]; } /* allocate wsi->user storage */ if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; /* * Give the user code a chance to study the request and * have the opportunity to deny it */ if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, wsi->user_space, lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { lwsl_warn("User code denied connection\n"); goto bail_nuke_ah; } /* * Perform the handshake according to the protocol version the * client announced */ switch (wsi->ietf_spec_revision) { case 13: lwsl_parser("lws_parse calling handshake_04\n"); if (handshake_0405(context, wsi)) { lwsl_info("hs0405 has failed the connection\n"); goto bail_nuke_ah; } break; default: lwsl_warn("Unknown client spec version %d\n", wsi->ietf_spec_revision); goto bail_nuke_ah; } /* we are upgrading to ws, so http/1.1 and keepalive + * pipelined header considerations about keeping the ah around * no longer apply. However it's common for the first ws * protocol data to have been coalesced with the browser * upgrade request and to already be in the ah rx buffer. */ lwsl_info("%s: %p: inheriting ah in ws mode (rxpos:%d, rxlen:%d)\n", __func__, wsi, wsi->u.hdr.ah->rxpos, wsi->u.hdr.ah->rxlen); lws_pt_lock(pt); hdr = wsi->u.hdr; lws_union_transition(wsi, LWSCM_WS_SERVING); /* * first service is WS mode will notice this, use the RX and * then detach the ah (caution: we are not in u.hdr union * mode any more then... ah_temp member is at start the same * though) * * Because rxpos/rxlen shows something in the ah, we will get * service guaranteed next time around the event loop * * All union members begin with hdr, so we can use it even * though we transitioned to ws union mode (the ah detach * code uses it anyway). */ wsi->u.hdr = hdr; lws_pt_unlock(pt); /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, use * a big default for compatibility */ n = 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); return 1; } wsi->u.ws.rx_ubuf_alloc = n; lwsl_info("Allocating RX buffer %d\n", n); #if LWS_POSIX if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); return 1; } #endif lwsl_parser("accepted v%02d connection\n", wsi->ietf_spec_revision); return 0; } /* while all chars are handled */ return 0; bail_nuke_ah: /* drop the header info */ /* we're closing, losing some rx is OK */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; lws_header_table_detach(wsi, 1); return 1; } static int lws_get_idlest_tsi(struct lws_context *context) { unsigned int lowest = ~0; int n = 0, hit = -1; for (; n < context->count_threads; n++) { if ((unsigned int)context->pt[n].fds_count != context->fd_limit_per_thread - 1 && (unsigned int)context->pt[n].fds_count < lowest) { lowest = context->pt[n].fds_count; hit = n; } } return hit; }
LWS_EXTERN int _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) { struct lws_context_per_thread *pt; WSANETWORKEVENTS networkevents; struct lws_pollfd *pfd; struct lws *wsi; unsigned int i; DWORD ev; int n; /* stay dead once we are dead */ if (context == NULL || !context->vhost_list) return 1; pt = &context->pt[tsi]; if (!pt->service_tid_detected) { struct lws _lws; memset(&_lws, 0, sizeof(_lws)); _lws.context = context; pt->service_tid = context->vhost_list-> protocols[0].callback(&_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); pt->service_tid_detected = 1; } if (timeout_ms < 0) { if (lws_service_flag_pending(context, tsi)) { /* any socket with events to service? */ for (n = 0; n < (int)pt->fds_count; n++) { int m; if (!pt->fds[n].revents) continue; m = lws_service_fd_tsi(context, &pt->fds[n], tsi); if (m < 0) return -1; /* if something closed, retry this slot */ if (m) n--; } } return 0; } if (context->event_loop_ops->run_pt) context->event_loop_ops->run_pt(context, tsi); for (i = 0; i < pt->fds_count; ++i) { pfd = &pt->fds[i]; if (!(pfd->events & LWS_POLLOUT)) continue; wsi = wsi_from_fd(context, pfd->fd); if (!wsi || wsi->listener) continue; if (wsi->sock_send_blocking) continue; pfd->revents = LWS_POLLOUT; n = lws_service_fd(context, pfd); if (n < 0) return -1; /* * Force WSAWaitForMultipleEvents() to check events * and then return immediately. */ timeout_ms = 0; /* if something closed, retry this slot */ if (n) i--; } /* * is there anybody with pending stuff that needs service forcing? */ if (!lws_service_adjust_timeout(context, 1, tsi)) { /* -1 timeout means just do forced service */ _lws_plat_service_tsi(context, -1, pt->tid); /* still somebody left who wants forced service? */ if (!lws_service_adjust_timeout(context, 1, pt->tid)) /* yes... come back again quickly */ timeout_ms = 0; } if (timeout_ms) { lws_usec_t t; lws_pt_lock(pt, __func__); /* don't stay in poll wait longer than next hr timeout */ t = __lws_hrtimer_service(pt); if ((lws_usec_t)timeout_ms * 1000 > t) timeout_ms = (int)(t / 1000); lws_pt_unlock(pt); } for (n = 0; n < (int)pt->fds_count; n++) WSAEventSelect(pt->fds[n].fd, pt->events, FD_READ | (!!(pt->fds[n].events & LWS_POLLOUT) * FD_WRITE) | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE | FD_QOS | FD_ROUTING_INTERFACE_CHANGE | FD_ADDRESS_LIST_CHANGE); ev = WSAWaitForMultipleEvents(1, &pt->events, FALSE, timeout_ms, FALSE); if (ev == WSA_WAIT_EVENT_0) { unsigned int eIdx; #if defined(LWS_WITH_TLS) if (pt->context->tls_ops && pt->context->tls_ops->fake_POLLIN_for_buffered) pt->context->tls_ops->fake_POLLIN_for_buffered(pt); #endif for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) { unsigned int err; if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, pt->events, &networkevents) == SOCKET_ERROR) { lwsl_err("WSAEnumNetworkEvents() failed " "with error %d\n", LWS_ERRNO); return -1; } if (!networkevents.lNetworkEvents) networkevents.lNetworkEvents = LWS_POLLOUT; pfd = &pt->fds[eIdx]; pfd->revents = (short)networkevents.lNetworkEvents; err = networkevents.iErrorCode[FD_CONNECT_BIT]; if ((networkevents.lNetworkEvents & FD_CONNECT) && err && err != LWS_EALREADY && err != LWS_EINPROGRESS && err != LWS_EWOULDBLOCK && err != WSAEINVAL) { lwsl_debug("Unable to connect errno=%d\n", err); pfd->revents |= LWS_POLLHUP; } if (pfd->revents & LWS_POLLOUT) { wsi = wsi_from_fd(context, pfd->fd); if (wsi) wsi->sock_send_blocking = 0; } /* if something closed, retry this slot */ if (pfd->revents & LWS_POLLHUP) --eIdx; if (pfd->revents) { recv(pfd->fd, NULL, 0, 0); lws_service_fd_tsi(context, pfd, tsi); } } } else if (ev == WSA_WAIT_TIMEOUT) { lws_service_fd(context, NULL); } else if (ev == WSA_WAIT_FAILED) return 0; return 0; }
LWS_EXTERN int _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) { volatile struct lws_foreign_thread_pollfd *ftp, *next; volatile struct lws_context_per_thread *vpt; struct lws_context_per_thread *pt; int n = -1, m, c; /* stay dead once we are dead */ if (!context || !context->vhost_list) return 1; pt = &context->pt[tsi]; vpt = (volatile struct lws_context_per_thread *)pt; lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1); if (timeout_ms < 0) goto faked_service; if (context->event_loop_ops->run_pt) context->event_loop_ops->run_pt(context, tsi); if (!pt->service_tid_detected) { struct lws _lws; memset(&_lws, 0, sizeof(_lws)); _lws.context = context; pt->service_tid = context->vhost_list->protocols[0].callback( &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); pt->service_tid_detected = 1; } /* * is there anybody with pending stuff that needs service forcing? */ if (!lws_service_adjust_timeout(context, 1, tsi)) { /* -1 timeout means just do forced service */ _lws_plat_service_tsi(context, -1, pt->tid); /* still somebody left who wants forced service? */ if (!lws_service_adjust_timeout(context, 1, pt->tid)) /* yes... come back again quickly */ timeout_ms = 0; } if (timeout_ms) { lws_pt_lock(pt, __func__); /* don't stay in poll wait longer than next hr timeout */ lws_usec_t t = __lws_hrtimer_service(pt); if ((lws_usec_t)timeout_ms * 1000 > t) timeout_ms = t / 1000; lws_pt_unlock(pt); } vpt->inside_poll = 1; lws_memory_barrier(); n = poll(pt->fds, pt->fds_count, timeout_ms); vpt->inside_poll = 0; lws_memory_barrier(); /* Collision will be rare and brief. Just spin until it completes */ while (vpt->foreign_spinlock) ; /* * At this point we are not inside a foreign thread pollfd change, * and we have marked ourselves as outside the poll() wait. So we * are the only guys that can modify the lws_foreign_thread_pollfd * list on the pt. Drain the list and apply the changes to the * affected pollfds in the correct order. */ lws_pt_lock(pt, __func__); ftp = vpt->foreign_pfd_list; //lwsl_notice("cleared list %p\n", ftp); while (ftp) { struct lws *wsi; struct lws_pollfd *pfd; next = ftp->next; pfd = &vpt->fds[ftp->fd_index]; if (lws_socket_is_valid(pfd->fd)) { wsi = wsi_from_fd(context, pfd->fd); if (wsi) __lws_change_pollfd(wsi, ftp->_and, ftp->_or); } lws_free((void *)ftp); ftp = next; } vpt->foreign_pfd_list = NULL; lws_memory_barrier(); /* we have come out of a poll wait... check the hrtimer list */ __lws_hrtimer_service(pt); lws_pt_unlock(pt); m = 0; #if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) m |= !!pt->ws.rx_draining_ext_list; #endif if (pt->context->tls_ops && pt->context->tls_ops->fake_POLLIN_for_buffered) m |= pt->context->tls_ops->fake_POLLIN_for_buffered(pt); if (!m && !n) { /* nothing to do */ lws_service_fd_tsi(context, NULL, tsi); lws_service_do_ripe_rxflow(pt); return 0; } faked_service: m = lws_service_flag_pending(context, tsi); if (m) c = -1; /* unknown limit */ else if (n < 0) { if (LWS_ERRNO != LWS_EINTR) return -1; return 0; } else c = n; /* any socket with events to service? */ for (n = 0; n < (int)pt->fds_count && c; n++) { if (!pt->fds[n].revents) continue; c--; m = lws_service_fd_tsi(context, &pt->fds[n], tsi); if (m < 0) { lwsl_err("%s: lws_service_fd_tsi returned %d\n", __func__, m); return -1; } /* if something closed, retry this slot */ if (m) n--; } lws_service_do_ripe_rxflow(pt); return 0; }