1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-02 09:26:39 +02:00

JSON-RPC over Websockets: Fix bug with >64Kb responses.

Eg if there are 10.000 users online and you do user.list.
The old websocket framing assumed no response was >64Kb.

This also creates a new function websocket_create_packet_ex()
This commit is contained in:
Bram Matthys
2023-01-04 13:07:10 +01:00
parent d6a3db4ad2
commit b33628b765
6 changed files with 66 additions and 13 deletions
+2
View File
@@ -852,6 +852,7 @@ extern MODVAR void (*rpc_error)(Client *client, json_t *request, JsonRpcError er
extern MODVAR void (*rpc_error_fmt)(Client *client, json_t *request, JsonRpcError error_code, FORMAT_STRING(const char *fmt), ...) __attribute__((format(printf,4,5)));
extern MODVAR int (*websocket_handle_websocket)(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
extern MODVAR int (*websocket_create_packet)(int opcode, char **buf, int *len);
extern MODVAR int (*websocket_create_packet_ex)(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
extern MODVAR int (*websocket_create_packet_simple)(int opcode, const char **buf, int *len);
/* /Efuncs */
@@ -897,6 +898,7 @@ extern void rpc_error_default_handler(Client *client, json_t *request, JsonRpcEr
extern void rpc_error_fmt_default_handler(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...);
extern int websocket_handle_websocket_default_handler(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
extern int websocket_create_packet_default_handler(int opcode, char **buf, int *len);
extern int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
extern int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len);
/* End of default handlers for efunctions */
+1
View File
@@ -2538,6 +2538,7 @@ enum EfunctionType {
EFUNC_RPC_ERROR_FMT,
EFUNC_WEBSOCKET_HANDLE_WEBSOCKET,
EFUNC_WEBSOCKET_CREATE_PACKET,
EFUNC_WEBSOCKET_CREATE_PACKET_EX,
EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE,
};
+2
View File
@@ -147,6 +147,7 @@ void (*rpc_error)(Client *client, json_t *request, JsonRpcError error_code, cons
void (*rpc_error_fmt)(Client *client, json_t *request, JsonRpcError error_code, const char *fmt, ...);
int (*websocket_handle_websocket)(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
int (*websocket_create_packet)(int opcode, char **buf, int *len);
int (*websocket_create_packet_ex)(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
int (*websocket_create_packet_simple)(int opcode, const char **buf, int *len);
Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
@@ -430,5 +431,6 @@ void efunctions_init(void)
efunc_init_function(EFUNC_RPC_ERROR_FMT, rpc_error_fmt, rpc_error_fmt_default_handler);
efunc_init_function(EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, websocket_handle_websocket, websocket_handle_websocket_default_handler);
efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET, websocket_create_packet, websocket_create_packet_default_handler);
efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET_EX, websocket_create_packet_ex, websocket_create_packet_ex_default_handler);
efunc_init_function(EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, websocket_create_packet_simple, websocket_create_packet_simple_default_handler);
}
+5
View File
@@ -1421,6 +1421,11 @@ int websocket_create_packet_default_handler(int opcode, char **buf, int *len)
return -1;
}
int websocket_create_packet_ex_default_handler(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
{
return -1;
}
int websocket_create_packet_simple_default_handler(int opcode, const char **buf, int *len)
{
return -1;
+8 -3
View File
@@ -431,11 +431,16 @@ void rpc_sendto(Client *client, const char *buf, int len)
if (MyConnect(client) && IsRPC(client) && WSU(client) && WSU(client)->handshake_completed)
{
/* Websocket */
static char utf8buf[65535]; // TODO: dynamic!!!
char *newbuf = unrl_utf8_make_valid(buf, utf8buf, sizeof(utf8buf), 1);
int utf8bufsize = len*2 + 16;
char *utf8buf = safe_alloc(utf8bufsize);
char *newbuf = unrl_utf8_make_valid(buf, utf8buf, utf8bufsize, 1);
int newlen = strlen(newbuf);
websocket_create_packet(WSOP_TEXT, &newbuf, &newlen);
int ws_sendbufsize = newlen + 64 + ((newlen / 1024) * 64); // some random magic
char *ws_sendbuf = safe_alloc(ws_sendbufsize);
websocket_create_packet_ex(WSOP_TEXT, &newbuf, &newlen, ws_sendbuf, ws_sendbufsize);
dbuf_put(&client->local->sendQ, newbuf, newlen);
safe_free(ws_sendbuf);
safe_free(utf8buf);
} else {
/* Unix domain socket or HTTP */
dbuf_put(&client->local->sendQ, buf, len);
+48 -10
View File
@@ -40,6 +40,7 @@ struct HTTPForwardedHeader
/* Forward declarations - public functions */
int _websocket_handle_websocket(Client *client, WebRequest *web, const char *readbuf2, int length2, int callback(Client *client, char *buf, int len));
int _websocket_create_packet(int opcode, char **buf, int *len);
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize);
int _websocket_create_packet_simple(int opcode, const char **buf, int *len);
/* Forward declarations - other */
int websocket_handle_packet(Client *client, const char *readbuf, int length, int callback(Client *client, char *buf, int len));
@@ -57,6 +58,7 @@ MOD_TEST()
MARK_AS_OFFICIAL_MODULE(modinfo);
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_HANDLE_WEBSOCKET, _websocket_handle_websocket);
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET, _websocket_create_packet);
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_EX, _websocket_create_packet_ex);
EfunctionAdd(modinfo->handle, EFUNC_WEBSOCKET_CREATE_PACKET_SIMPLE, _websocket_create_packet_simple);
/* Init first, since we manage sockets */
@@ -289,7 +291,7 @@ int websocket_handle_packet_pong(Client *client, const char *buf, int len)
return 0;
}
/** Create a simple websocket packet that is ready to be send.
/** Create a simple websocket packet that is ready to be sent.
* This is the simple version that is used ONLY for WSOP_PONG,
* as it does not take \r\n into account.
*/
@@ -322,15 +324,18 @@ int _websocket_create_packet_simple(int opcode, const char **buf, int *len)
}
/** Create a websocket packet that is ready to be send.
* This is the more complex version that takes into account
* stripping off \r and \n, and possibly multi line due to
* labeled-response. It is used for WSOP_TEXT and WSOP_BINARY.
* This version takes into account stripping off \r and \n,
* and possibly multi line due to labeled-response.
* It is used for WSOP_TEXT and WSOP_BINARY.
* The end result is one or more websocket frames,
* all in a single packet *buf with size *len.
*
* This is the version that uses the specified buffer,
* it is used from the JSON-RPC code,
* and indirectly from websocket_create_packet().
*/
int _websocket_create_packet(int opcode, char **buf, int *len)
int _websocket_create_packet_ex(int opcode, char **buf, int *len, char *sendbuf, size_t sendbufsize)
{
static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
char *s = *buf; /* points to start of current line */
char *s2; /* used for searching of end of current line */
char *lastbyte = *buf + *len - 1; /* points to last byte in *buf that can be safely read */
@@ -359,10 +364,12 @@ int _websocket_create_packet(int opcode, char **buf, int *len)
if (bytes_to_copy < 126)
bytes_single_frame = 2 + bytes_to_copy;
else
else if (bytes_to_copy < 65536)
bytes_single_frame = 4 + bytes_to_copy;
else
bytes_single_frame = 10 + bytes_to_copy;
if (bytes_in_sendbuf + bytes_single_frame > sizeof(sendbuf))
if (bytes_in_sendbuf + bytes_single_frame > sendbufsize)
{
/* Overflow. This should never happen. */
unreal_log(ULOG_WARNING, "websocket", "BUG_WEBSOCKET_OVERFLOW", NULL,
@@ -370,7 +377,7 @@ int _websocket_create_packet(int opcode, char **buf, int *len)
"$bytes_in_sendbuf + $bytes_single_frame > $sendbuf_size",
log_data_integer("bytes_in_sendbuf", bytes_in_sendbuf),
log_data_integer("bytes_single_frame", bytes_single_frame),
log_data_integer("sendbuf_size", sizeof(sendbuf)));
log_data_integer("sendbuf_size", sendbufsize));
return -1;
}
@@ -382,12 +389,27 @@ int _websocket_create_packet(int opcode, char **buf, int *len)
/* Short payload */
o[1] = (char)bytes_to_copy;
memcpy(&o[2], s, bytes_to_copy);
} else {
} else
if (bytes_to_copy < 65536)
{
/* Long payload */
o[1] = 126;
o[2] = (char)((bytes_to_copy >> 8) & 0xFF);
o[3] = (char)(bytes_to_copy & 0xFF);
memcpy(&o[4], s, bytes_to_copy);
} else {
/* Longest payload */
// XXX: yeah we don't support sending more than 4GB.
o[1] = 127;
o[2] = 0;
o[3] = 0;
o[4] = 0;
o[5] = 0;
o[6] = (char)((bytes_to_copy >> 24) & 0xFF);
o[7] = (char)((bytes_to_copy >> 16) & 0xFF);
o[8] = (char)((bytes_to_copy >> 8) & 0xFF);
o[9] = (char)(bytes_to_copy & 0xFF);
memcpy(&o[10], s, bytes_to_copy);
}
/* Advance destination pointer and counter */
@@ -403,6 +425,22 @@ int _websocket_create_packet(int opcode, char **buf, int *len)
return 0;
}
/** Create a websocket packet that is ready to be send.
* This version takes into account stripping off \r and \n,
* and possibly multi line due to labeled-response.
* It is used for WSOP_TEXT and WSOP_BINARY.
* The end result is one or more websocket frames,
* all in a single packet *buf with size *len.
*
* This is the version that uses a static sendbuf buffer,
* it is used from IRC websockets.
*/
int _websocket_create_packet(int opcode, char **buf, int *len)
{
static char sendbuf[WEBSOCKET_SEND_BUFFER_SIZE];
return _websocket_create_packet_ex(opcode, buf, len, sendbuf, sizeof(sendbuf));
}
/** Create and send a WSOP_PONG frame */
int websocket_send_pong(Client *client, const char *buf, int len)
{