LWS_VISIBLE int libwebsocket_service_fd(struct libwebsocket_context *context, struct libwebsocket_pollfd *pollfd) { struct libwebsocket *wsi; int n; int m; int listen_socket_fds_index = 0; time_t now; int timed_out = 0; int our_fd = 0; char draining_flow = 0; int more; struct lws_tokens eff_buf; if (context->listen_service_fd) listen_socket_fds_index = context->lws_lookup[ context->listen_service_fd]->position_in_fds_table; /* * 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; for (n = 0; n < context->fds_count; n++) { m = context->fds[n].fd; wsi = context->lws_lookup[m]; if (!wsi) continue; if (libwebsocket_service_timeout_check(context, wsi, now)) /* he did time out... */ if (m == our_fd) { /* it was the guy we came to service! */ timed_out = 1; /* mark as handled */ if (pollfd) pollfd->revents = 0; } } } /* the socket we came to service timed out, nothing to do */ if (timed_out) return 0; /* just here for timeout management? */ if (pollfd == NULL) return 0; /* no, here to service a socket descriptor */ wsi = context->lws_lookup[pollfd->fd]; if (wsi == NULL) /* 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 */ /* * deal with listen service piggybacking * every listen_service_modulo services of other fds, we * sneak one in to service the listen socket if there's anything waiting * * To handle connection storms, as found in ab, if we previously saw a * pending connection here, it causes us to check again next time. */ if (context->listen_service_fd && pollfd != &context->fds[listen_socket_fds_index]) { context->listen_service_count++; if (context->listen_service_extraseen || context->listen_service_count == context->listen_service_modulo) { context->listen_service_count = 0; m = 1; if (context->listen_service_extraseen > 5) m = 2; while (m--) { /* * even with extpoll, we prepared this * internal fds for listen */ n = lws_poll_listen_fd(&context->fds[listen_socket_fds_index]); if (n > 0) { /* there's a conn waiting for us */ libwebsocket_service_fd(context, &context-> fds[listen_socket_fds_index]); context->listen_service_extraseen++; } else { if (context->listen_service_extraseen) context-> listen_service_extraseen--; break; } } } } /* handle session socket closed */ if ((!(pollfd->revents & LWS_POLLIN)) && (pollfd->revents & LWS_POLLHUP)) { lwsl_debug("Session Socket %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto close_and_handled; } /* okay, what we came here to do... */ switch (wsi->mode) { case LWS_CONNMODE_HTTP_SERVING: case LWS_CONNMODE_HTTP_SERVING_ACCEPTED: case LWS_CONNMODE_SERVER_LISTENER: case LWS_CONNMODE_SSL_ACK_PENDING: n = lws_server_socket_service(context, wsi, pollfd); if (n < 0) goto close_and_handled; goto handled; case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: case LWS_CONNMODE_HTTP2_SERVING: /* the guy requested a callback when it was OK to write */ if ((pollfd->revents & LWS_POLLOUT) && (wsi->state == WSI_STATE_ESTABLISHED || wsi->state == WSI_STATE_HTTP2_ESTABLISHED || wsi->state == WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS || wsi->state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) && lws_handle_POLLOUT_event(context, wsi, pollfd)) { lwsl_info("libwebsocket_service_fd: closing\n"); goto close_and_handled; } if (wsi->rxflow_buffer && (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { lwsl_info("draining rxflow\n"); /* 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; } /* any incoming data ready? */ if (!(pollfd->revents & LWS_POLLIN)) break; eff_buf.token_len = lws_ssl_capable_read(context, wsi, context->service_buffer, sizeof(context->service_buffer)); 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 *)context->service_buffer; drain: do { more = 0; m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_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) { n = libwebsocket_read(context, 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); if (draining_flow && wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { lwsl_info("flow buffer: drained\n"); lws_free2(wsi->rxflow_buffer); /* having drained the rxflow buffer, can rearm POLLIN */ _libwebsocket_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); goto handled; #endif } n = 0; goto handled; close_and_handled: lwsl_debug("Close and handled\n"); libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); n = 1; handled: pollfd->revents = 0; return n; }
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 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);