udphole

Basic UDP wormhole proxy
git clone git://git.finwo.net/app/udphole
Log | Files | Refs | README | LICENSE

commit 45c909599635c6365456c2b57beae193a2d66b68
parent dbb36807a4481ac1e804b81b88e737e032e9d590
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Fri,  6 Mar 2026 21:39:39 +0100

Fix handling incomplete input

Diffstat:
MMakefile | 2+-
Msrc/common/resp.c | 60++++++++++++++++++++++++++++++++++--------------------------
Msrc/common/resp.h | 7+++++--
Msrc/interface/api/server.c | 36+++++++++++++++++++++++++++++++-----
4 files changed, 71 insertions(+), 34 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,7 +7,7 @@ SRC:= # UNAME_SYSTEM=$(call lc,$(shell uname -s)) BIN?=udphole -VERSION?=1.3.8 +VERSION?=1.3.9 CC:=gcc CPP:=g++ diff --git a/src/common/resp.c b/src/common/resp.c @@ -39,22 +39,27 @@ static int resp_read_line_from_buf(const char **buf, size_t *len, char *out, siz return -1; } -resp_object *resp_read_buf(const char *buf, size_t len) { - const char *p = buf; - size_t remaining = len; +int resp_read_buf(const char *buf, size_t len, resp_object **out_obj) { + if (!out_obj) return -1; + + const char *start = buf; + const char *p = buf; + size_t remaining = len; int type_c = resp_read_byte_from_buf(&p, &remaining); - if (type_c < 0) return NULL; - if (type_c == -2) return NULL; + if (type_c < 0) return 0; // no data yet + if (type_c == -2) return 0; // no data yet + resp_object *o = calloc(1, sizeof(resp_object)); - if (!o) return NULL; + if (!o) return -1; + char line[LINE_BUF]; switch ((char)type_c) { case '+': o->type = RESPT_SIMPLE; if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); - return NULL; + return -1; } o->u.s = strdup(line); break; @@ -62,7 +67,7 @@ resp_object *resp_read_buf(const char *buf, size_t len) { o->type = RESPT_ERROR; if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); - return NULL; + return -1; } o->u.s = strdup(line); break; @@ -70,7 +75,7 @@ resp_object *resp_read_buf(const char *buf, size_t len) { { if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); - return NULL; + return -1; } o->type = RESPT_INT; o->u.i = (long long)strtoll(line, NULL, 10); @@ -80,12 +85,12 @@ resp_object *resp_read_buf(const char *buf, size_t len) { { if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); - return NULL; + return -1; } long blen = strtol(line, NULL, 10); if (blen < 0 || blen > (long)MAX_BULK_LEN) { free(o); - return NULL; + return -1; } o->type = RESPT_BULK; if (blen == 0) { @@ -93,17 +98,17 @@ resp_object *resp_read_buf(const char *buf, size_t len) { if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o->u.s); free(o); - return NULL; + return -1; } } else { if ((size_t)blen > remaining) { free(o); - return NULL; + return -1; } o->u.s = malloc((size_t)blen + 1); if (!o->u.s) { free(o); - return NULL; + return -1; } memcpy(o->u.s, p, (size_t)blen); p += blen; @@ -112,12 +117,12 @@ resp_object *resp_read_buf(const char *buf, size_t len) { if (remaining < 2) { free(o->u.s); free(o); - return NULL; + return -1; } if (p[0] != '\r' || p[1] != '\n') { free(o->u.s); free(o); - return NULL; + return -1; } p += 2; remaining -= 2; @@ -128,30 +133,31 @@ resp_object *resp_read_buf(const char *buf, size_t len) { { if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); - return NULL; + return -1; } long n = strtol(line, NULL, 10); if (n < 0 || n > 65536) { free(o); - return NULL; + return -1; } o->type = RESPT_ARRAY; o->u.arr.n = (size_t)n; o->u.arr.elem = n ? calloc((size_t)n, sizeof(resp_object)) : NULL; if (n && !o->u.arr.elem) { free(o); - return NULL; + return -1; } for (size_t i = 0; i < (size_t)n; i++) { - resp_object *sub = resp_read_buf(p, remaining); - if (!sub) { + resp_object *sub = NULL; + int consumed = resp_read_buf(p, remaining, &sub); + if (consumed <= 0) { for (size_t j = 0; j < i; j++) resp_free_internal(&o->u.arr.elem[j]); free(o->u.arr.elem); free(o); - return NULL; + return -1; } - p += remaining - (sub ? remaining : 0); - remaining = 0; + p += consumed; + remaining -= (size_t)consumed; o->u.arr.elem[i] = *sub; free(sub); } @@ -159,9 +165,11 @@ resp_object *resp_read_buf(const char *buf, size_t len) { } default: free(o); - return NULL; + return -1; } - return o; + + *out_obj = o; + return (int)(p - start); } static int resp_read_byte(int fd) { diff --git a/src/common/resp.h b/src/common/resp.h @@ -40,8 +40,11 @@ void resp_map_set(resp_object *map, const char *key, resp_object *value); resp_object *resp_read(int fd); /* Returns new object: caller owns the result, must call resp_free() */ -resp_object *resp_read_buf(const char *buf, size_t len); -/* Returns new object: caller owns the result, must call resp_free() */ +int resp_read_buf(const char *buf, size_t len, resp_object **out_obj); +/* Returns 0=no data yet, <0=incomplete (need more data), >0=bytes consumed from buffer */ +/* If out_obj is NULL, returns -1 */ +/* If *out_obj is NULL, creates new object */ +/* If *out_obj exists, appends to it (for arrays) */ int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len); /* Returns allocated string in out_buf: caller must free() the string */ diff --git a/src/interface/api/server.c b/src/interface/api/server.c @@ -46,6 +46,7 @@ struct api_client_state { char *username; char rbuf[READ_BUF_SIZE]; size_t rlen; + resp_object *partial_obj; char *wbuf; size_t wlen; size_t wcap; @@ -603,19 +604,43 @@ int api_client_pt(int64_t timestamp, struct pt_task *task) { return SCHED_RUNNING; } - char buf[1]; - ssize_t n = recv(state->fd, buf, 1, MSG_PEEK); - if (n <= 0) { + // Read data into receive buffer + if (state->rlen >= READ_BUF_SIZE) { + // Buffer full, protocol error goto cleanup; } - resp_object *cmd = resp_read(state->fd); - if (!cmd) { + ssize_t n = recv(state->fd, state->rbuf + state->rlen, READ_BUF_SIZE - state->rlen, 0); + if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return SCHED_RUNNING; } goto cleanup; } + if (n == 0) { + goto cleanup; + } + state->rlen += (size_t)n; + + // Try to parse RESP object from buffer + resp_object *cmd = NULL; + int consumed = resp_read_buf(state->rbuf, state->rlen, &cmd); + if (consumed > 0) { + // Successfully parsed - consume bytes from buffer + memmove(state->rbuf, state->rbuf + consumed, state->rlen - consumed); + state->rlen -= consumed; + state->partial_obj = NULL; + } else if (consumed < 0) { + // Incomplete - need more data + return SCHED_RUNNING; + } else { + // No data - shouldn't happen, but continue + return SCHED_RUNNING; + } + + if (!cmd) { + return SCHED_RUNNING; + } if (cmd->type != RESPT_ARRAY || cmd->u.arr.n == 0) { resp_free(cmd); @@ -660,6 +685,7 @@ cleanup: free(state->fds); free(state->wbuf); free(state->username); + resp_free(state->partial_obj); free(state); return SCHED_DONE; }