int libwebsocket_service_timeout_check(struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned int sec) { /* * if extensions want in on it (eg, we are a mux parent) * give them a chance to service child timeouts */ if (lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_1HZ, NULL, sec) < 0) return 0; if (!wsi->pending_timeout) return 0; /* * if we went beyond the allowed time, kill the * connection */ if (sec > wsi->pending_timeout_limit) { lwsl_info("TIMEDOUT WAITING on %d\n", wsi->pending_timeout); libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); return 1; } return 0; }
int libwebsocket_service_timeout_check(struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned int sec) { /* * if extensions want in on it (eg, we are a mux parent) * give them a chance to service child timeouts */ if (lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_1HZ, NULL, sec) < 0) return 0; if (!wsi->pending_timeout) return 0; /* * if we went beyond the allowed time, kill the * connection */ if (sec > wsi->pending_timeout_limit) { lwsl_info("TIMEDOUT WAITING on %d\n", wsi->pending_timeout); /* * 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; libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); return 1; } return 0; }
LWS_VISIBLE int libwebsocket_callback_on_writable(struct libwebsocket_context *context, struct libwebsocket *wsi) { #ifdef LWS_USE_HTTP2 struct libwebsocket *network_wsi, *wsi2; int already; lwsl_info("%s: %p\n", __func__, wsi); if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING) goto network_sock; if (wsi->u.http2.requested_POLLOUT) { lwsl_info("already pending writable\n"); return 1; } if (wsi->u.http2.tx_credit <= 0) { /* * other side is not able to cope with us sending * anything so no matter if we have POLLOUT on our side. * * Delay waiting for our POLLOUT until peer indicates he has * space for more using tx window command in http2 layer */ lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi, wsi->u.http2.tx_credit); wsi->u.http2.waiting_tx_credit = 1; return 0; } network_wsi = lws_http2_get_network_wsi(wsi); already = network_wsi->u.http2.requested_POLLOUT; /* mark everybody above him as requesting pollout */ wsi2 = wsi; while (wsi2) { wsi2->u.http2.requested_POLLOUT = 1; lwsl_info("mark %p pending writable\n", wsi2); wsi2 = wsi2->u.http2.parent_wsi; } /* for network action, act only on the network wsi */ wsi = network_wsi; if (already) return 1; network_sock: #endif (void)context; if (lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE, NULL, 0)) return 1; if (wsi->position_in_fds_table < 0) { lwsl_err("%s: failed to find socket %d\n", __func__, wsi->sock); return -1; } if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) return -1; lws_libev_io(context, wsi, LWS_EV_START | LWS_EV_WRITE); return 1; }
void libwebsocket_close_and_free_session(struct libwebsocket_context *context, struct libwebsocket *wsi, enum lws_close_status reason) { int n, m, ret; int old_state; unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 2 + LWS_SEND_BUFFER_POST_PADDING]; struct lws_tokens eff_buf; if (!wsi) return; old_state = wsi->state; if (wsi->socket_is_permanently_unusable || reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY) goto just_kill_connection; switch (old_state) { case WSI_STATE_DEAD_SOCKET: return; /* we tried the polite way... */ case WSI_STATE_AWAITING_CLOSE_ACK: goto just_kill_connection; case WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE: if (wsi->truncated_send_len) { libwebsocket_callback_on_writable(context, wsi); return; } lwsl_info("wsi %p completed WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); goto just_kill_connection; default: if (wsi->truncated_send_len) { lwsl_info("wsi %p entering WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); wsi->state = WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE; libwebsocket_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); return; } break; } wsi->u.ws.close_reason = reason; if (wsi->mode == LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT || wsi->mode == LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE) { context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0); goto just_kill_connection; } if (wsi->mode == LWS_CONNMODE_HTTP_SERVING) context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED) { if (wsi->u.http.fd != LWS_INVALID_FILE) { // TODO: If we're just closing with LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY this file descriptor might leak? lwsl_debug("closing http file\n"); compatible_file_close(wsi->u.http.fd); wsi->u.http.fd = LWS_INVALID_FILE; context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLOSED_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_callback_for_each_active(wsi, LWS_EXT_CALLBACK_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_callback_for_each_active(wsi, LWS_EXT_CALLBACK_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, libwebsocket_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 * WSI_STATE_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 * WSI_STATE_AWAITING_CLOSE_ACK and will skip doing this a second time. */ if (old_state == WSI_STATE_ESTABLISHED && reason != LWS_CLOSE_STATUS_NOSTATUS && reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY) { lwsl_debug("sending close indication...\n"); /* make valgrind happy */ memset(buf, 0, sizeof(buf)); n = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING + 2], 0, LWS_WRITE_CLOSE); if (n >= 0) { /* * we have sent a nice protocol level indication we * now wish to close, we should not send anything more */ wsi->state = WSI_STATE_AWAITING_CLOSE_ACK; /* * ...and we should wait for a reply for a bit * out of politeness */ libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 1); lwsl_debug("sent close indication, awaiting ack\n"); return; } lwsl_info("close: sending close packet failed, hanging up\n"); /* else, the send failed and we should just hang up */ } just_kill_connection: lwsl_debug("close: just_kill_connection\n"); /* * we won't be servicing or receiving anything further from this guy * delete socket from the internal poll list if still present */ lws_ssl_remove_wsi_from_buffered_list(context, wsi); // checking return redundant since we anyway close remove_wsi_socket_from_fds(context, wsi); wsi->state = WSI_STATE_DEAD_SOCKET; lws_free2(wsi->rxflow_buffer); lws_free_header_table(wsi); if ((old_state == WSI_STATE_ESTABLISHED || wsi->mode == LWS_CONNMODE_WS_SERVING || wsi->mode == LWS_CONNMODE_WS_CLIENT)) { lws_free2(wsi->u.ws.rx_user_buffer); if (wsi->truncated_send_malloc) { /* not going to be completed... nuke it */ lws_free2(wsi->truncated_send_malloc); wsi->truncated_send_len = 0; } if (wsi->u.ws.ping_payload_buf) { lws_free2(wsi->u.ws.ping_payload_buf); wsi->u.ws.ping_payload_alloc = 0; wsi->u.ws.ping_payload_len = 0; wsi->u.ws.ping_pending_flag = 0; } } /* tell the user it's all over for this guy */ if (wsi->protocol && wsi->protocol->callback && ((old_state == WSI_STATE_ESTABLISHED) || (old_state == WSI_STATE_RETURNED_CLOSE_ALREADY) || (old_state == WSI_STATE_AWAITING_CLOSE_ACK) || (old_state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE))) { lwsl_debug("calling back CLOSED\n"); wsi->protocol->callback(context, wsi, LWS_CALLBACK_CLOSED, wsi->user_space, NULL, 0); } else if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED) { lwsl_debug("calling back CLOSED_HTTP\n"); context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0 ); } else if (wsi->mode == LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY || wsi->mode == LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT) { lwsl_debug("Connection closed before server reply\n"); context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0 ); } else lwsl_debug("not calling back closed mode=%d state=%d\n", wsi->mode, old_state); /* deallocate any active extension contexts */ if (lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_DESTROY, NULL, 0) < 0) lwsl_warn("extension destruction failed\n"); #ifndef LWS_NO_EXTENSIONS for (n = 0; n < wsi->count_active_extensions; n++) lws_free(wsi->active_extensions_user[n]); #endif /* * inform all extensions in case they tracked this guy out of band * even though not active on him specifically */ if (lws_ext_callback_for_each_extension_type(context, wsi, LWS_EXT_CALLBACK_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0) lwsl_warn("ext destroy wsi failed\n"); /* lwsl_info("closing fd=%d\n", wsi->sock); */ if (!lws_ssl_close(wsi) && wsi->sock >= 0) { n = shutdown(wsi->sock, SHUT_RDWR); if (n) lwsl_debug("closing: shutdown ret %d\n", LWS_ERRNO); n = compatible_close(wsi->sock); if (n) lwsl_debug("closing: close ret %d\n", LWS_ERRNO); } /* outermost destroy notification for wsi (user_space still intact) */ context->protocols[0].callback(context, wsi, LWS_CALLBACK_WSI_DESTROY, wsi->user_space, NULL, 0); lws_free_wsi(wsi); }
int lws_issue_raw(struct libwebsocket *wsi, unsigned char *buf, size_t len) { struct libwebsocket_context *context = wsi->protocol->owning_server; int n; size_t real_len = len; int m; if (!len) return 0; /* just ignore sends after we cleared the truncation buffer */ if (wsi->state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE && !wsi->truncated_send_len) return len; if (wsi->truncated_send_len && (buf < wsi->truncated_send_malloc || buf > (wsi->truncated_send_malloc + wsi->truncated_send_len + wsi->truncated_send_offset))) { lwsl_err("****** %x Sending new, pending truncated ...\n", wsi); assert(0); } m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_PACKET_TX_DO_SEND, &buf, len); if (m < 0) return -1; if (m) /* handled */ { n = m; goto handle_truncated_send; } if (wsi->sock < 0) lwsl_warn("** error invalid sock but expected to send\n"); /* * nope, send it on the socket directly */ lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, buf, len); lws_latency(context, wsi, "send lws_issue_raw", n, n == len); switch (n) { case LWS_SSL_CAPABLE_ERROR: /* we're going to close, let close know sends aren't possible */ wsi->socket_is_permanently_unusable = 1; return -1; case LWS_SSL_CAPABLE_MORE_SERVICE: /* nothing got sent, not fatal, retry the whole thing later */ n = 0; break; } handle_truncated_send: /* * we were already handling a truncated send? */ if (wsi->truncated_send_len) { lwsl_info("***** %x partial send moved on by %d (vs %d)\n", wsi, n, real_len); wsi->truncated_send_offset += n; wsi->truncated_send_len -= n; if (!wsi->truncated_send_len) { lwsl_info("***** %x partial send completed\n", wsi); /* done with it, but don't free it */ n = real_len; if (wsi->state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) { lwsl_info("***** %x signalling to close now\n", wsi); return -1; /* retry closing now */ } } /* always callback on writeable */ libwebsocket_callback_on_writable( wsi->protocol->owning_server, wsi); return n; } if (n == real_len) /* what we just sent went out cleanly */ return n; if (n && wsi->u.ws.clean_buffer) /* * This buffer unaffected by extension rewriting. * It means the user code is expected to deal with * partial sends. (lws knows the header was already * sent, so on next send will just resume sending * payload) */ return n; /* * Newly truncated send. Buffer the remainder (it will get * first priority next time the socket is writable) */ lwsl_info("***** %x new partial sent %d from %d total\n", wsi, n, real_len); /* * - if we still have a suitable malloc lying around, use it * - or, if too small, reallocate it * - or, if no buffer, create it */ if (!wsi->truncated_send_malloc || real_len - n > wsi->truncated_send_allocation) { lws_free(wsi->truncated_send_malloc); wsi->truncated_send_allocation = real_len - n; wsi->truncated_send_malloc = lws_malloc(real_len - n); if (!wsi->truncated_send_malloc) { lwsl_err("truncated send: unable to malloc %d\n", real_len - n); return -1; } } wsi->truncated_send_offset = 0; wsi->truncated_send_len = real_len - n; memcpy(wsi->truncated_send_malloc, buf + n, real_len - n); /* since something buffered, force it to get another chance to send */ libwebsocket_callback_on_writable(wsi->protocol->owning_server, wsi); return real_len; }
LWS_VISIBLE int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, size_t len, enum libwebsocket_write_protocol protocol) { int n; int pre = 0; int post = 0; int masked7 = wsi->mode == LWS_CONNMODE_WS_CLIENT; unsigned char *dropmask = NULL; unsigned char is_masked_bit = 0; size_t orig_len = len; struct lws_tokens eff_buf; if (len == 0 && protocol != LWS_WRITE_CLOSE && protocol != LWS_WRITE_PING && protocol != LWS_WRITE_PONG) { lwsl_warn("zero length libwebsocket_write attempt\n"); return 0; } if (protocol == LWS_WRITE_HTTP || protocol == LWS_WRITE_HTTP_FINAL || protocol == LWS_WRITE_HTTP_HEADERS) goto send_raw; /* websocket protocol, either binary or text */ if (wsi->state != WSI_STATE_ESTABLISHED) return -1; /* if we are continuing a frame that already had its header done */ if (wsi->u.ws.inside_frame) goto do_more_inside_frame; wsi->u.ws.clean_buffer = 1; /* * give a chance to the extensions to modify payload * pre-TX mangling is not allowed to truncate */ eff_buf.token = (char *)buf; eff_buf.token_len = len; switch (protocol) { case LWS_WRITE_PING: case LWS_WRITE_PONG: case LWS_WRITE_CLOSE: break; default: if (lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_PAYLOAD_TX, &eff_buf, 0) < 0) return -1; } /* * an extension did something we need to keep... for example, if * compression extension, it has already updated its state according * to this being issued */ if ((char *)buf != eff_buf.token) /* * extension recreated it: * need to buffer this if not all sent */ wsi->u.ws.clean_buffer = 0; buf = (unsigned char *)eff_buf.token; len = eff_buf.token_len; switch (wsi->ietf_spec_revision) { case 13: if (masked7) { pre += 4; dropmask = &buf[0 - pre]; is_masked_bit = 0x80; } switch (protocol & 0xf) { case LWS_WRITE_TEXT: n = LWS_WS_OPCODE_07__TEXT_FRAME; break; case LWS_WRITE_BINARY: n = LWS_WS_OPCODE_07__BINARY_FRAME; break; case LWS_WRITE_CONTINUATION: n = LWS_WS_OPCODE_07__CONTINUATION; break; case LWS_WRITE_CLOSE: n = LWS_WS_OPCODE_07__CLOSE; /* * 06+ has a 2-byte status code in network order * we can do this because we demand post-buf */ if (wsi->u.ws.close_reason) { /* reason codes count as data bytes */ buf -= 2; buf[0] = wsi->u.ws.close_reason >> 8; buf[1] = wsi->u.ws.close_reason; len += 2; } break; case LWS_WRITE_PING: n = LWS_WS_OPCODE_07__PING; break; case LWS_WRITE_PONG: n = LWS_WS_OPCODE_07__PONG; break; default: lwsl_warn("lws_write: unknown write opc / protocol\n"); return -1; } if (!(protocol & LWS_WRITE_NO_FIN)) n |= 1 << 7; if (len < 126) { pre += 2; buf[-pre] = n; buf[-pre + 1] = len | is_masked_bit; } else { if (len < 65536) { pre += 4; buf[-pre] = n; buf[-pre + 1] = 126 | is_masked_bit; buf[-pre + 2] = len >> 8; buf[-pre + 3] = len; } else {
int lws_handle_POLLOUT_event(struct libwebsocket_context *context, struct libwebsocket *wsi, struct libwebsocket_pollfd *pollfd) { int n; struct lws_tokens eff_buf; #ifdef LWS_USE_HTTP2 struct libwebsocket *wsi2; #endif int ret; int m; int handled = 0; /* pending truncated sends have uber priority */ if (wsi->truncated_send_len) { if (lws_issue_raw(wsi, wsi->truncated_send_malloc + wsi->truncated_send_offset, wsi->truncated_send_len) < 0) { lwsl_info("lws_handle_POLLOUT_event signalling to close\n"); return -1; } /* leave POLLOUT active either way */ return 0; } else if (wsi->state == WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) { lwsl_info("***** %x signalling to close in POLLOUT handler\n", wsi); return -1; /* retry closing now */ } #ifdef LWS_USE_HTTP2 /* protocol packets are next */ 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(context, wsi); break; default: break; } wsi->pps = LWS_PPS_NONE; libwebsocket_rx_flow_control(wsi, 1); return 0; /* leave POLLOUT active */ } #endif /* pending control packets have next priority */ if (wsi->state == WSI_STATE_ESTABLISHED && wsi->u.ws.ping_payload_len) { n = libwebsocket_write(wsi, &wsi->u.ws.ping_payload_buf[ LWS_SEND_BUFFER_PRE_PADDING], wsi->u.ws.ping_payload_len, LWS_WRITE_PONG); if (n < 0) return -1; /* well he is sent, mark him done */ wsi->u.ws.ping_payload_len = 0; /* leave POLLOUT active either way */ return 0; } /* if nothing critical, user can get the callback */ m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_IS_WRITEABLE, NULL, 0); #ifndef LWS_NO_EXTENSIONS if (!wsi->extension_data_pending || handled == 2) 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_callback_for_each_active(wsi, LWS_EXT_CALLBACK_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; user_service: #endif /* one shot */ if (pollfd) { if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_info("failled at set pollfd\n"); return 1; } lws_libev_io(context, 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 != LWS_CONNMODE_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(context, wsi2)) { lwsl_debug("Closing POLLOUT child\n"); libwebsocket_close_and_free_session(context, 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(context, wsi); }
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; }
int lws_issue_raw_ext_access(struct libwebsocket *wsi, unsigned char *buf, size_t len) { int ret; struct lws_tokens eff_buf; int m; int n = 0; eff_buf.token = (char *)buf; eff_buf.token_len = len; /* * while we have original buf to spill ourselves, or extensions report * more in their pipeline */ ret = 1; while (ret == 1) { /* default to nobody has more to spill */ ret = 0; /* show every extension the new incoming data */ m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_PACKET_TX_PRESEND, &eff_buf, 0); if (m < 0) return -1; if (m) /* handled */ ret = 1; if ((char *)buf != eff_buf.token) /* * extension recreated it: * need to buffer this if not all sent */ wsi->u.ws.clean_buffer = 0; /* assuming they left 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 ext access\n"); return -1; } /* always either sent it all or privately buffered */ if (wsi->u.ws.clean_buffer) len = n; } lwsl_parser("written %d bytes to client\n", n); /* no extension has more to spill? Then we can go */ if (!ret) break; /* we used up what we had */ eff_buf.token = NULL; eff_buf.token_len = 0; /* * Did that leave the pipe choked? * Or we had to hold on to some of it? */ if (!lws_send_pipe_choked(wsi) && !wsi->truncated_send_len) /* no we could add more, lets's do that */ continue; lwsl_debug("choked\n"); /* * Yes, he's choked. Don't spill the rest now get a callback * when he is ready to send and take care of it there */ libwebsocket_callback_on_writable( wsi->protocol->owning_server, wsi); wsi->extension_data_pending = 1; ret = 0; } return len; }
int lws_handle_POLLOUT_event(struct libwebsocket_context *context, struct libwebsocket *wsi, struct libwebsocket_pollfd *pollfd) { int n; struct lws_tokens eff_buf; int ret; int m; int handled = 0; /* pending truncated sends have uber priority */ if (wsi->truncated_send_len) { lws_issue_raw(wsi, wsi->truncated_send_malloc + wsi->truncated_send_offset, wsi->truncated_send_len); /* leave POLLOUT active either way */ return 0; } m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_IS_WRITEABLE, NULL, 0); if (handled == 1) goto notify_action; #ifndef LWS_NO_EXTENSIONS if (!wsi->extension_data_pending || handled == 2) 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_callback_for_each_active(wsi, LWS_EXT_CALLBACK_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) 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; user_service: #endif /* one shot */ if (pollfd) { if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return 1; #ifdef LWS_USE_LIBEV if (LWS_LIBEV_ENABLED(context)) ev_io_stop(context->io_loop, (struct ev_io *)&wsi->w_write); #endif /* LWS_USE_LIBEV */ } notify_action: if (wsi->mode == LWS_CONNMODE_WS_CLIENT) n = LWS_CALLBACK_CLIENT_WRITEABLE; else n = LWS_CALLBACK_SERVER_WRITEABLE; return user_callback_handle_rxflow(wsi->protocol->callback, context, wsi, (enum libwebsocket_callback_reasons) n, wsi->user_space, NULL, 0); }
int lws_issue_raw(struct libwebsocket *wsi, unsigned char *buf, size_t len) { struct libwebsocket_context *context = wsi->protocol->owning_server; int n; size_t real_len = len; int m; if (!len) return 0; if (wsi->truncated_send_len && (buf < wsi->truncated_send_malloc || buf > (wsi->truncated_send_malloc + wsi->truncated_send_len + wsi->truncated_send_offset))) { lwsl_err("****** %x Sending new, pending truncated ...\n", wsi); assert(0); } m = lws_ext_callback_for_each_active(wsi, LWS_EXT_CALLBACK_PACKET_TX_DO_SEND, &buf, len); if (m < 0) return -1; if (m) /* handled */ { n = m; goto handle_truncated_send; } if (wsi->sock < 0) lwsl_warn("** error invalid sock but expected to send\n"); /* * nope, send it on the socket directly */ lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, buf, len); lws_latency(context, wsi, "send lws_issue_raw", n, n == len); switch (n) { case LWS_SSL_CAPABLE_ERROR: return -1; case LWS_SSL_CAPABLE_MORE_SERVICE: n = 0; goto handle_truncated_send; } handle_truncated_send: /* * already handling a truncated send? */ if (wsi->truncated_send_len) { lwsl_info("***** %x partial send moved on by %d (vs %d)\n", wsi, n, real_len); wsi->truncated_send_offset += n; wsi->truncated_send_len -= n; if (!wsi->truncated_send_len) { lwsl_info("***** %x partial send completed\n", wsi); /* done with it, but don't free it */ n = real_len; } else libwebsocket_callback_on_writable( wsi->protocol->owning_server, wsi); return n; } if (n < real_len) { if (n && wsi->u.ws.clean_buffer) /* * This buffer unaffected by extension rewriting. * It means the user code is expected to deal with * partial sends. (lws knows the header was already * sent, so on next send will just resume sending * payload) */ return n; /* * Newly truncated send. Buffer the remainder (it will get * first priority next time the socket is writable) */ lwsl_info("***** %x new partial sent %d from %d total\n", wsi, n, real_len); /* * - if we still have a suitable malloc lying around, use it * - or, if too small, reallocate it * - or, if no buffer, create it */ if (!wsi->truncated_send_malloc || real_len - n > wsi->truncated_send_allocation) { if (wsi->truncated_send_malloc) free(wsi->truncated_send_malloc); wsi->truncated_send_allocation = real_len - n; wsi->truncated_send_malloc = malloc(real_len - n); if (!wsi->truncated_send_malloc) { lwsl_err( "truncated send: unable to malloc %d\n", real_len - n); return -1; } } wsi->truncated_send_offset = 0; wsi->truncated_send_len = real_len - n; memcpy(wsi->truncated_send_malloc, buf + n, real_len - n); libwebsocket_callback_on_writable( wsi->protocol->owning_server, wsi); return real_len; } return n; }