/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "private-lib-core.h" #if defined(LWS_WITH_CLIENT) static int lws_close_trans_q_leader(struct lws_dll2 *d, void *user) { struct lws *w = lws_container_of(d, struct lws, dll2_cli_txn_queue); __lws_close_free_wsi(w, -1, "trans q leader closing"); return 0; } #endif void __lws_reset_wsi(struct lws *wsi) { if (!wsi) return; #if defined(LWS_WITH_CLIENT) lws_free_set_NULL(wsi->cli_hostname_copy); /* * if we have wsi in our transaction queue, if we are closing we * must go through and close all those first */ if (wsi->vhost) { /* we are no longer an active client connection that can piggyback */ lws_dll2_remove(&wsi->dll_cli_active_conns); lws_dll2_foreach_safe(&wsi->dll2_cli_txn_queue_owner, NULL, lws_close_trans_q_leader); /* * !!! If we are closing, but we have pending pipelined * transaction results we already sent headers for, that's going * to destroy sync for HTTP/1 and leave H2 stream with no live * swsi.` * * However this is normal if we are being closed because the * transaction queue leader is closing. */ lws_dll2_remove(&wsi->dll2_cli_txn_queue); } #endif if (wsi->vhost) lws_dll2_remove(&wsi->vh_awaiting_socket); /* * Protocol user data may be allocated either internally by lws * or by specified the user. We should only free what we allocated. */ if (wsi->protocol && wsi->protocol->per_session_data_size && wsi->user_space && !wsi->user_space_externally_allocated) lws_free_set_NULL(wsi->user_space); lws_buflist_destroy_all_segments(&wsi->buflist); lws_buflist_destroy_all_segments(&wsi->buflist_out); #if defined(LWS_WITH_UDP) lws_free_set_NULL(wsi->udp); #endif wsi->retry = 0; #if defined(LWS_WITH_CLIENT) lws_dll2_remove(&wsi->dll2_cli_txn_queue); lws_dll2_remove(&wsi->dll_cli_active_conns); #endif #if defined(LWS_WITH_SYS_ASYNC_DNS) lws_async_dns_cancel(wsi); #endif #if defined(LWS_WITH_HTTP_PROXY) if (wsi->http.buflist_post_body) lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body); #endif if (wsi->vhost && wsi->vhost->lserv_wsi == wsi) wsi->vhost->lserv_wsi = NULL; #if defined(LWS_WITH_CLIENT) if (wsi->vhost) lws_dll2_remove(&wsi->dll_cli_active_conns); #endif wsi->context->count_wsi_allocated--; __lws_same_vh_protocol_remove(wsi); #if defined(LWS_WITH_CLIENT) lws_free_set_NULL(wsi->stash); lws_free_set_NULL(wsi->cli_hostname_copy); #endif #if defined(LWS_WITH_PEER_LIMITS) lws_peer_track_wsi_close(wsi->context, wsi->peer); wsi->peer = NULL; #endif /* since we will destroy the wsi, make absolutely sure now */ #if defined(LWS_WITH_OPENSSL) __lws_ssl_remove_wsi_from_buffered_list(wsi); #endif __lws_wsi_remove_from_sul(wsi); if (wsi->role_ops->destroy_role) wsi->role_ops->destroy_role(wsi); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) __lws_header_table_detach(wsi, 0); #endif } void __lws_free_wsi(struct lws *wsi) { if (!wsi) return; __lws_reset_wsi(wsi); if (wsi->context->event_loop_ops->destroy_wsi) wsi->context->event_loop_ops->destroy_wsi(wsi); lws_vhost_unbind_wsi(wsi); lwsl_debug("%s: %p, remaining wsi %d, tsi fds count %d\n", __func__, wsi, wsi->context->count_wsi_allocated, wsi->context->pt[(int)wsi->tsi].fds_count); lws_free(wsi); } void lws_remove_child_from_any_parent(struct lws *wsi) { struct lws **pwsi; int seen = 0; if (!wsi->parent) return; /* detach ourselves from parent's child list */ pwsi = &wsi->parent->child_list; while (*pwsi) { if (*pwsi == wsi) { lwsl_info("%s: detach %p from parent %p\n", __func__, wsi, wsi->parent); if (wsi->parent->protocol) wsi->parent->protocol->callback(wsi, LWS_CALLBACK_CHILD_CLOSING, wsi->parent->user_space, wsi, 0); *pwsi = wsi->sibling_list; seen = 1; break; } pwsi = &(*pwsi)->sibling_list; } if (!seen) lwsl_err("%s: failed to detach from parent\n", __func__); wsi->parent = NULL; } #if defined(LWS_WITH_CLIENT) void lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len) { lws_addrinfo_clean(wsi); if (wsi->already_did_cce) return; wsi->already_did_cce = 1; lws_stats_bump(&wsi->context->pt[(int)wsi->tsi], LWSSTATS_C_CONNS_CLIENT_FAILED, 1); if (!wsi->protocol) return; wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, arg, len); } #endif void lws_addrinfo_clean(struct lws *wsi) { #if defined(LWS_WITH_CLIENT) if (!wsi->dns_results) return; #if defined(LWS_WITH_SYS_ASYNC_DNS) lws_async_dns_freeaddrinfo(&wsi->dns_results); #else freeaddrinfo((struct addrinfo *)wsi->dns_results); #endif wsi->dns_results = NULL; #endif } void __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) { struct lws_context_per_thread *pt; const struct lws_protocols *pro; struct lws_context *context; struct lws *wsi1, *wsi2; int n, ccb; lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller); if (!wsi) return; lws_access_log(wsi); if (!lws_dll2_is_detached(&wsi->dll_buflist)) { lwsl_info("%s: wsi %p: going down with stuff in buflist\n", __func__, wsi); } context = wsi->context; pt = &context->pt[(int)wsi->tsi]; lws_stats_bump(pt, LWSSTATS_C_API_CLOSE, 1); #if defined(LWS_WITH_CLIENT) lws_free_set_NULL(wsi->cli_hostname_copy); lws_addrinfo_clean(wsi); #endif #if defined(LWS_WITH_HTTP2) if (wsi->mux_stream_immortal) lws_http_close_immortal(wsi); #endif /* if we have children, close them first */ if (wsi->child_list) { wsi2 = wsi->child_list; while (wsi2) { wsi1 = wsi2->sibling_list; wsi2->parent = NULL; /* stop it doing shutdown processing */ wsi2->socket_is_permanently_unusable = 1; __lws_close_free_wsi(wsi2, reason, "general child recurse"); wsi2 = wsi1; } wsi->child_list = NULL; } #if defined(LWS_ROLE_RAW_FILE) if (wsi->role_ops == &role_ops_raw_file) { lws_remove_child_from_any_parent(wsi); __remove_wsi_socket_from_fds(wsi); if (wsi->protocol) wsi->protocol->callback(wsi, wsi->role_ops->close_cb[0], wsi->user_space, NULL, 0); goto async_close; } #endif wsi->wsistate_pre_close = wsi->wsistate; #ifdef LWS_WITH_CGI if (wsi->role_ops == &role_ops_cgi) { // lwsl_debug("%s: closing stdwsi index %d\n", __func__, (int)wsi->lsp_channel); /* we are not a network connection, but a handler for CGI io */ if (wsi->parent && wsi->parent->http.cgi) { if (wsi->parent->child_list == wsi && !wsi->sibling_list) lws_cgi_remove_and_kill(wsi->parent); /* end the binding between us and master */ if (wsi->parent->http.cgi) wsi->parent->http.cgi->lsp->stdwsi[(int)wsi->lsp_channel] = NULL; } wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } if (wsi->http.cgi) lws_cgi_remove_and_kill(wsi); #endif #if defined(LWS_WITH_CLIENT) lws_free_set_NULL(wsi->stash); #endif if (wsi->role_ops == &role_ops_raw_skt) { wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } #if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && wsi->http.fop_fd != NULL) lws_vfs_file_close(&wsi->http.fop_fd); #endif if (lwsi_state(wsi) == LRS_DEAD_SOCKET) return; if (wsi->socket_is_permanently_unusable || reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || lwsi_state(wsi) == LRS_SHUTDOWN) goto just_kill_connection; switch (lwsi_state_PRE_CLOSE(wsi)) { case LRS_DEAD_SOCKET: return; /* we tried the polite way... */ case LRS_WAITING_TO_SEND_CLOSE: case LRS_AWAITING_CLOSE_ACK: case LRS_RETURNED_CLOSE: goto just_kill_connection; case LRS_FLUSHING_BEFORE_CLOSE: if (lws_has_buffered_out(wsi) #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) || wsi->http.comp_ctx.buflist_comp || wsi->http.comp_ctx.may_have_more #endif ) { lws_callback_on_writable(wsi); return; } lwsl_info("%p: end LRS_FLUSHING_BEFORE_CLOSE\n", wsi); goto just_kill_connection; default: if (lws_has_buffered_out(wsi) #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) || wsi->http.comp_ctx.buflist_comp || wsi->http.comp_ctx.may_have_more #endif ) { lwsl_info("%p: LRS_FLUSHING_BEFORE_CLOSE\n", wsi); lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE); __lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); return; } break; } if (lwsi_state(wsi) == LRS_WAITING_CONNECT || lwsi_state(wsi) == LRS_WAITING_DNS || lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE) goto just_kill_connection; if (!wsi->told_user_closed && wsi->user_space && wsi->protocol && wsi->protocol_bind_balance) { wsi->protocol->callback(wsi, wsi->role_ops->protocol_unbind_cb[ !!lwsi_role_server(wsi)], wsi->user_space, (void *)__func__, 0); wsi->protocol_bind_balance = 0; } /* * signal we are closing, lws_write will * add any necessary version-specific stuff. If the write fails, * no worries we are closing anyway. If we didn't initiate this * close, then our state has been changed to * LRS_RETURNED_CLOSE 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 * LRS_AWAITING_CLOSE_ACK and will skip doing this a second time. */ if (wsi->role_ops->close_via_role_protocol && wsi->role_ops->close_via_role_protocol(wsi, reason)) return; just_kill_connection: #if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && wsi->http.fop_fd != NULL) lws_vfs_file_close(&wsi->http.fop_fd); #endif #if defined(LWS_WITH_SYS_ASYNC_DNS) lws_async_dns_cancel(wsi); #endif #if defined(LWS_WITH_HTTP_PROXY) if (wsi->http.buflist_post_body) lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body); #endif #if defined(LWS_WITH_UDP) if (wsi->udp) lws_free_set_NULL(wsi->udp); #endif if (wsi->role_ops->close_kill_connection) wsi->role_ops->close_kill_connection(wsi, reason); n = 0; if (!wsi->told_user_closed && wsi->user_space && wsi->protocol_bind_balance && wsi->protocol) { lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, wsi->protocol ? wsi->protocol->name: "NULL"); if (wsi->protocol) wsi->protocol->callback(wsi, wsi->role_ops->protocol_unbind_cb[ !!lwsi_role_server(wsi)], wsi->user_space, (void *)__func__, 0); wsi->protocol_bind_balance = 0; } #if defined(LWS_WITH_CLIENT) if ((lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY || lwsi_state(wsi) == LRS_WAITING_DNS || lwsi_state(wsi) == LRS_WAITING_CONNECT) && !wsi->already_did_cce && wsi->protocol) { static const char _reason[] = "closed before established"; lws_inform_client_conn_fail(wsi, (void *)_reason, sizeof(_reason)); } #endif /* * Testing with ab shows that we have to stage the socket close when * the system is under stress... shutdown any further TX, change the * state to one that won't emit anything more, and wait with a timeout * for the POLLIN to show a zero-size rx before coming back and doing * the actual close. */ if (wsi->role_ops != &role_ops_raw_skt && !lwsi_role_client(wsi) && lwsi_state(wsi) != LRS_SHUTDOWN && lwsi_state(wsi) != LRS_UNCONNECTED && reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && !wsi->socket_is_permanently_unusable) { #if defined(LWS_WITH_TLS) if (lws_is_ssl(wsi) && wsi->tls.ssl) { n = 0; switch (__lws_tls_shutdown(wsi)) { case LWS_SSL_CAPABLE_DONE: case LWS_SSL_CAPABLE_ERROR: case LWS_SSL_CAPABLE_MORE_SERVICE_READ: case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: case LWS_SSL_CAPABLE_MORE_SERVICE: break; } } else #endif { lwsl_info("%s: shutdown conn: %p (sk %d, state 0x%x)\n", __func__, wsi, (int)(long)wsi->desc.sockfd, lwsi_state(wsi)); if (!wsi->socket_is_permanently_unusable && lws_socket_is_valid(wsi->desc.sockfd)) { wsi->socket_is_permanently_unusable = 1; n = shutdown(wsi->desc.sockfd, SHUT_WR); } } if (n) lwsl_debug("closing: shutdown (state 0x%x) ret %d\n", lwsi_state(wsi), LWS_ERRNO); /* * This causes problems on WINCE / ESP32 with disconnection * when the events are half closing connection */ #if !defined(_WIN32_WCE) && !defined(LWS_PLAT_FREERTOS) /* libuv: no event available to guarantee completion */ if (!wsi->socket_is_permanently_unusable && lws_socket_is_valid(wsi->desc.sockfd) && lwsi_state(wsi) != LRS_SHUTDOWN && (context->event_loop_ops->flags & LELOF_ISPOLL)) { __lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); lwsi_set_state(wsi, LRS_SHUTDOWN); __lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, context->timeout_secs); return; } #endif } lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, wsi, wsi->desc.sockfd); #ifdef LWS_WITH_HUBBUB if (wsi->http.rw) { lws_rewrite_destroy(wsi->http.rw); wsi->http.rw = NULL; } #endif if (wsi->http.pending_return_headers) lws_free_set_NULL(wsi->http.pending_return_headers); /* * 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(wsi); __lws_wsi_remove_from_sul(wsi); //if (wsi->told_event_loop_closed) // cgi std close case (dummy-callback) // return; // lwsl_notice("%s: wsi %p, fd %d\n", __func__, wsi, wsi->desc.sockfd); /* checking return redundant since we anyway close */ if (wsi->desc.sockfd != LWS_SOCK_INVALID) __remove_wsi_socket_from_fds(wsi); else __lws_same_vh_protocol_remove(wsi); lwsi_set_state(wsi, LRS_DEAD_SOCKET); lws_buflist_destroy_all_segments(&wsi->buflist); lws_dll2_remove(&wsi->dll_buflist); if (wsi->role_ops->close_role) wsi->role_ops->close_role(pt, wsi); /* tell the user it's all over for this guy */ ccb = 0; if ((lwsi_state_est_PRE_CLOSE(wsi) || /* raw skt adopted but didn't complete tls hs should CLOSE */ (wsi->role_ops == &role_ops_raw_skt && !lwsi_role_client(wsi)) || lwsi_state_PRE_CLOSE(wsi) == LRS_WAITING_SERVER_REPLY) && !wsi->told_user_closed && wsi->role_ops->close_cb[lwsi_role_server(wsi)]) { if (!wsi->upgraded_to_http2 || !lwsi_role_client(wsi)) ccb = 1; /* * The network wsi for a client h2 connection shouldn't * call back for its role: the child stream connections * own the role. Otherwise h2 will call back closed * one too many times as the children do it and then * the closing network stream. */ } if (!wsi->told_user_closed && !lws_dll2_is_detached(&wsi->vh_awaiting_socket)) /* * He's a guy who go started with dns, but failed or is * caught with a shutdown before he got the result. We have * to issue him a close cb */ ccb = 1; pro = wsi->protocol; #if defined(LWS_WITH_CLIENT) if (!ccb && (lwsi_state_PRE_CLOSE(wsi) & LWSIFS_NOT_EST) && lwsi_role_client(wsi)) { lws_inform_client_conn_fail(wsi, "Closed before conn", 18); } #endif if (ccb) { if (!wsi->protocol && wsi->vhost && wsi->vhost->protocols) pro = &wsi->vhost->protocols[0]; if (pro) pro->callback(wsi, wsi->role_ops->close_cb[lwsi_role_server(wsi)], wsi->user_space, NULL, 0); wsi->told_user_closed = 1; } #if defined(LWS_ROLE_RAW_FILE) async_close: #endif lws_remove_child_from_any_parent(wsi); wsi->socket_is_permanently_unusable = 1; if (wsi->context->event_loop_ops->wsi_logical_close) if (wsi->context->event_loop_ops->wsi_logical_close(wsi)) return; __lws_close_free_wsi_final(wsi); } void __lws_close_free_wsi_final(struct lws *wsi) { int n; if (!wsi->shadow && lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { lwsl_debug("%s: wsi %p: fd %d\n", __func__, wsi, wsi->desc.sockfd); n = compatible_close(wsi->desc.sockfd); if (n) lwsl_debug("closing: close ret %d\n", LWS_ERRNO); wsi->desc.sockfd = LWS_SOCK_INVALID; } /* outermost destroy notification for wsi (user_space still intact) */ if (wsi->vhost) wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, wsi->user_space, NULL, 0); #ifdef LWS_WITH_CGI if (wsi->http.cgi) { lws_spawn_piped_destroy(&wsi->http.cgi->lsp); lws_free_set_NULL(wsi->http.cgi); } #endif __lws_free_wsi(wsi); } void lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; lws_pt_lock(pt, __func__); __lws_close_free_wsi(wsi, reason, caller); lws_pt_unlock(pt); }