websocket: add option to disable auto-pong reply

This adds another bitflag on CURLOPT_WS_OPTIONS (CURLWS_NOAUTOPONG) that
disables the default and automatic PONG reply in the WebSocket layer.

Assisted-by: Calvin Ruocco

Closes #16744
This commit is contained in:
Brian Chrzanowski 2024-07-23 22:15:23 -04:00 committed by Daniel Stenberg
parent 21fd64645b
commit c0df01fd94
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
15 changed files with 204 additions and 9 deletions

View File

@ -39,7 +39,8 @@ WebSocket with libcurl can be done two ways.
The new options to `curl_easy_setopt()`:
`CURLOPT_WS_OPTIONS` - to control specific behavior. `CURLWS_RAW_MODE` makes
libcurl provide all WebSocket traffic raw in the callback.
libcurl provide all WebSocket traffic raw in the callback. `CURLWS_NOAUTOPONG`
disables automatic `PONG` replies.
The new function calls:

View File

@ -106,7 +106,8 @@ This is a ping message. It may contain up to 125 bytes of payload text.
libcurl does not verify that the payload is valid UTF-8.
Upon receiving a ping message, libcurl automatically responds with a pong
message unless the **CURLWS_RAW_MODE** bit of CURLOPT_WS_OPTIONS(3) is set.
message unless the **CURLWS_NOAUTOPONG** or **CURLWS_RAW_MODE** bit of
CURLOPT_WS_OPTIONS(3) is set.
## CURLWS_PONG

View File

@ -88,7 +88,8 @@ unidirectional heartbeat.
libcurl automatically responds to server PING messages with a PONG that echoes
the payload of the PING message. libcurl does neither send any PING messages
nor any unsolicited PONG messages automatically.
nor any unsolicited PONG messages automatically. The automatic reply to PING
messages can be disabled through CURLOPT_WS_OPTIONS(3).
# MODELS

View File

@ -44,6 +44,12 @@ callback.
In raw mode, libcurl does not handle pings or any other frame for the
application.
## CURLWS_NOAUTOPONG (2)
Disable the automatic reply to PING messages. This means users must
send a PONG message with curl_ws_send(3). This feature is added with
version 8.14.0.
# DEFAULT
0

View File

@ -1155,6 +1155,7 @@ CURLWARNING 7.66.0
CURLWS_BINARY 7.86.0
CURLWS_CLOSE 7.86.0
CURLWS_CONT 7.86.0
CURLWS_NOAUTOPONG 8.14.0
CURLWS_OFFSET 7.86.0
CURLWS_PING 7.86.0
CURLWS_PONG 7.86.0

View File

@ -73,7 +73,8 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
unsigned int flags);
/* bits for the CURLOPT_WS_OPTIONS bitmask: */
#define CURLWS_RAW_MODE (1<<0)
#define CURLWS_RAW_MODE (1<<0)
#define CURLWS_NOAUTOPONG (1<<1)
CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *curl);

View File

@ -1384,7 +1384,8 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option,
#endif /* ! CURL_DISABLE_ALTSVC */
#ifndef CURL_DISABLE_WEBSOCKETS
case CURLOPT_WS_OPTIONS:
data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE);
data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE);
data->set.ws_no_auto_pong = (bool)(arg & CURLWS_NOAUTOPONG);
break;
#endif
case CURLOPT_QUICK_EXIT:

View File

@ -480,6 +480,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
memset(&set->priority, 0, sizeof(set->priority));
#endif
set->quick_exit = 0L;
#ifndef CURL_DISABLE_WEBSOCKETS
set->ws_raw_mode = FALSE;
set->ws_no_auto_pong = FALSE;
#endif
return result;
}

View File

@ -1802,6 +1802,7 @@ struct UserDefined {
BIT(http09_allowed); /* allow HTTP/0.9 responses */
#ifndef CURL_DISABLE_WEBSOCKETS
BIT(ws_raw_mode);
BIT(ws_no_auto_pong);
#endif
};

View File

@ -502,10 +502,12 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen,
struct ws_cw_dec_ctx *ctx = user_data;
struct Curl_easy *data = ctx->data;
struct websocket *ws = ctx->ws;
bool auto_pong = !data->set.ws_no_auto_pong;
curl_off_t remain = (payload_len - (payload_offset + buflen));
(void)frame_age;
if((frame_flags & CURLWS_PING) && !remain) {
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
infof(data, "WS: auto-respond to PING with a PONG");
@ -949,6 +951,8 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
CURLcode *err)
{
struct ws_collect *ctx = userp;
struct Curl_easy *data = ctx->data;
bool auto_pong = !data->set.ws_no_auto_pong;
size_t nwritten;
curl_off_t remain = (payload_len - (payload_offset + buflen));
@ -960,7 +964,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
ctx->payload_len = payload_len;
}
if((frame_flags & CURLWS_PING) && !remain) {
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
infof(ctx->data, "WS: auto-respond to PING with a PONG");

View File

@ -469,6 +469,8 @@
*
d CURLWS_RAW_MODE...
d c X'00000001'
d CURLWS_NOAUTOPONG...
d c X'00000002'
*
**************************************************************************
* Types

View File

@ -259,7 +259,7 @@ test2100 test2101 test2102 \
test2200 test2201 test2202 test2203 test2204 test2205 \
\
test2300 test2301 test2302 test2303 test2304 test2305 test2306 test2307 \
test2308 test2309 test2310 test2311 \
test2308 test2309 test2310 test2311 test2312 \
\
test2400 test2401 test2402 test2403 test2404 test2405 test2406 \
\

66
tests/data/test2312 Normal file
View File

@ -0,0 +1,66 @@
<testcase>
<info>
<keywords>
WebSockets
</keywords>
</info>
#
# Server-side
<reply>
<data nocheck="yes" nonewline="yes">
HTTP/1.1 101 Switching to WebSockets swsclose
Server: test-server/fake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
%hex[%89%00]hex%
</data>
# allow upgrade
<servercmd>
upgrade
</servercmd>
</reply>
#
# Client-side
<client>
# for the forced CURL_ENTROPY
<features>
debug
ws
</features>
<server>
http
</server>
<name>
WebSockets no auto ping
</name>
<tool>
lib%TESTNUMBER
</tool>
<command>
ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol nocheck="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: webbie-sox/3
Accept: */*
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==
</protocol>
<errorcode>
0
</errorcode>
</verify>
</testcase>

View File

@ -74,7 +74,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq \
lib1960 lib1964 \
lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \
lib2301 lib2302 lib2304 lib2305 lib2306 lib2308 lib2309 lib2310 \
lib2311 \
lib2311 lib2312 \
lib2402 lib2404 lib2405 \
lib2502 \
lib3010 lib3025 lib3026 lib3027 \
@ -713,6 +713,9 @@ lib2310_LDADD = $(TESTUTIL_LIBS)
lib2311_SOURCES = lib2311.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
lib2311_LDADD = $(TESTUTIL_LIBS)
lib2312_SOURCES = lib2312.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
lib2312_LDADD = $(TESTUTIL_LIBS)
lib2402_SOURCES = lib2402.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib2402_LDADD = $(TESTUTIL_LIBS)

102
tests/libtest/lib2312.c Normal file
View File

@ -0,0 +1,102 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "test.h"
#ifdef USE_WEBSOCKETS
struct ping_check {
CURL *curl;
int pinged;
};
static size_t write_cb(char *b, size_t size, size_t nitems, void *p)
{
struct ping_check *ping_check = p;
CURL *curl = ping_check->curl;
const struct curl_ws_frame *frame = curl_ws_meta(curl);
size_t sent = 0;
size_t i = 0;
/* upon ping, respond with input data, disconnect, mark a success */
if(frame->flags & CURLWS_PING) {
fprintf(stderr, "write_cb received ping with %zd bytes\n",
size * nitems);
fprintf(stderr, "\n");
for(i = 0; i < size * nitems; i++) {
fprintf(stderr, "%02X%s", (int)b[i],
(i % 10 == 0 && i != 0) ? "\n" : " ");
}
fprintf(stderr, "\n");
fprintf(stderr, "write_cb sending pong response\n");
curl_ws_send(curl, b, size * nitems, &sent, 0, CURLWS_PONG);
fprintf(stderr, "write_cb closing websocket\n");
curl_ws_send(curl, NULL, 0, &sent, 0, CURLWS_CLOSE);
ping_check->pinged = 1;
}
else {
fprintf(stderr, "ping_check_cb: non-ping message, frame->flags %x\n",
frame->flags);
}
return size * nitems;
}
CURLcode test(char *URL)
{
CURL *curl;
CURLcode res = CURLE_OK;
struct ping_check state;
global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
state.curl = curl;
state.pinged = 0;
curl_easy_setopt(curl, CURLOPT_URL, URL);
/* use the callback style, without auto-pong */
curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3");
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, (long)CURLWS_NOAUTOPONG);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &state);
res = curl_easy_perform(curl);
fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res);
res = state.pinged ? 0 : 1;
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return res;
}
#else /* no websockets */
NO_SUPPORT_BUILT_IN
#endif