diff --git a/lib/handshake.c b/lib/handshake.c index 6a07d51a..7c8310f6 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -686,6 +686,7 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi break; + case WSI_STATE_AWAITING_CLOSE_ACK: case WSI_STATE_ESTABLISHED: switch (wsi->mode) { case LWS_CONNMODE_WS_CLIENT: diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index c6aca204..fdfaa460 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -158,7 +158,49 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, if (old_state == WSI_STATE_DEAD_SOCKET) return; - /* remove this fd from wsi mapping hashtable */ + wsi->close_reason = reason; + + /* + * signal we are closing, libsocket_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) { + n = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], + 0, LWS_WRITE_CLOSE); + if (!n) { + /* + * 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 */ + + libwebsocket_set_timeout(wsi, + PENDING_TIMEOUT_CLOSE_ACK, 5); + + fprintf(stderr, "sent close indication, awaiting ack\n"); + + return; + } + + /* else, the send failed and we should just hang up */ + } + + /* + * we won't be servicing or receiving anything further from this guy + * remove this fd from wsi mapping hashtable + */ delete_from_fd(context, wsi->sock); @@ -181,20 +223,6 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, context->protocols[0].callback(context, wsi, LWS_CALLBACK_DEL_POLL_FD, (void *)(long)wsi->sock, NULL, 0); - wsi->close_reason = reason; - - /* - * signal we are closing, libsocket_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 - */ - - if (old_state == WSI_STATE_ESTABLISHED) - libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], 0, - LWS_WRITE_CLOSE); - wsi->state = WSI_STATE_DEAD_SOCKET; /* tell the user it's all over for this guy */ @@ -586,9 +614,11 @@ libwebsocket_service_fd(struct libwebsocket_context *context, * connection */ - if (tv.tv_sec > wsi->pending_timeout_limit) + if (tv.tv_sec > wsi->pending_timeout_limit) { + fprintf(stderr, "TIMEDOUT WAITING\n"); libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); + } } } @@ -1607,10 +1637,12 @@ bail2: /* the guy requested a callback when it was OK to write */ - if (pollfd->revents & POLLOUT) - if (lws_handle_POLLOUT_event(context, wsi, pollfd) < 0) { - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NORMAL); + if ((pollfd->revents & POLLOUT) && + wsi->state == WSI_STATE_ESTABLISHED) + if (lws_handle_POLLOUT_event(context, wsi, + pollfd) < 0) { + libwebsocket_close_and_free_session( + context, wsi, LWS_CLOSE_STATUS_NORMAL); return 1; } diff --git a/lib/parsers.c b/lib/parsers.c index fd26cb7b..b116dddd 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -617,6 +617,16 @@ spill: switch (wsi->opcode) { case LWS_WS_OPCODE_04__CLOSE: + /* is this an acknowledgement of our close? */ + if (wsi->state == WSI_STATE_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + fprintf(stderr, "seen client close ack\n"); + return -1; + } + fprintf(stderr, "server sees client close packet\n"); /* parrot the close packet payload back */ n = libwebsocket_write(wsi, (unsigned char *) &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], @@ -940,10 +950,21 @@ spill: switch (wsi->opcode) { case LWS_WS_OPCODE_04__CLOSE: + /* is this an acknowledgement of our close? */ + if (wsi->state == WSI_STATE_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + fprintf(stderr, "seen server's close ack\n"); + return -1; + } + fprintf(stderr, "client sees server close packet len = %d\n", wsi->rx_user_buffer_head); /* parrot the close packet payload back */ n = libwebsocket_write(wsi, (unsigned char *) &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], wsi->rx_user_buffer_head, LWS_WRITE_CLOSE); + fprintf(stderr, "client writing close ack returned %d\n", n); wsi->state = WSI_STATE_RETURNED_CLOSE_ALREADY; /* close the connection */ return -1; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index fa61f497..e93b0f0c 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -129,6 +129,7 @@ enum lws_connection_states { WSI_STATE_ESTABLISHED, WSI_STATE_CLIENT_UNCONNECTED, WSI_STATE_RETURNED_CLOSE_ALREADY, + WSI_STATE_AWAITING_CLOSE_ACK }; enum lws_rx_parse_state { @@ -214,6 +215,7 @@ enum pending_timeout { PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, PENDING_TIMEOUT_AWAITING_PING, + PENDING_TIMEOUT_CLOSE_ACK, };