udphole

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

commit 09b174ac49da221c68eaaedecba2bea56c9f9ac1
parent 4882b57353a03a465ced0b4b52e03e8ee5851090
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Sun,  1 Mar 2026 15:26:25 +0100

More closely follow hexagonal architecture

Diffstat:
Asrc/common/resp.c | 437+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/common/resp.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/domain/config.c | 40++++++++++++++++++++++++++++++++++++++++
Asrc/domain/config.h | 23+++++++++++++++++++++++
Msrc/domain/session.c | 514+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/domain/session.h | 17+++++++++++++++--
Msrc/infrastructure/config.c | 2+-
Msrc/infrastructure/config.h | 2+-
Dsrc/infrastructure/resp.c | 332-------------------------------------------------------------------------------
Dsrc/infrastructure/resp.h | 47-----------------------------------------------
Msrc/interface/api/server.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/interface/api/server.h | 8++++++--
Msrc/interface/cli/command/daemon.c | 38++++++++++++++++++++++++++++++++++++++
Msrc/interface/cli/main.c | 9++++++---
14 files changed, 1037 insertions(+), 597 deletions(-)

diff --git a/src/common/resp.c b/src/common/resp.c @@ -0,0 +1,437 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> + +#include "rxi/log.h" +#include "common/resp.h" + +#define MAX_BULK_LEN (256 * 1024) +#define LINE_BUF 4096 + +static void resp_free_internal(resp_object *o); + +static int resp_read_byte_from_buf(const char **buf, size_t *len) { + if (*len < 1) return -1; + unsigned char c = (unsigned char)(*buf)[0]; + *buf += 1; + *len -= 1; + return (int)c; +} + +static int resp_read_line_from_buf(const char **buf, size_t *len, char *out, size_t out_size) { + size_t i = 0; + int prev = -1; + while (i + 1 < out_size) { + if (*len < 1) return -1; + int b = (int)(unsigned char)(*buf)[0]; + *buf += 1; + *len -= 1; + if (prev == '\r' && b == '\n') { + out[i - 1] = '\0'; + return 0; + } + prev = b; + out[i++] = (char)b; + } + return -1; +} + +resp_object *resp_read_buf(const char *buf, size_t len) { + 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; + resp_object *o = calloc(1, sizeof(resp_object)); + if (!o) return NULL; + 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; } + o->u.s = strdup(line); + break; + case '-': + o->type = RESPT_ERROR; + if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); return NULL; } + o->u.s = strdup(line); + break; + case ':': { + if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); return NULL; } + o->type = RESPT_INT; + o->u.i = (long long)strtoll(line, NULL, 10); + break; + } + case '$': { + if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); return NULL; } + long blen = strtol(line, NULL, 10); + if (blen < 0 || blen > (long)MAX_BULK_LEN) { free(o); return NULL; } + o->type = RESPT_BULK; + if (blen == 0) { + o->u.s = strdup(""); + if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o->u.s); free(o); return NULL; } + } else { + if ((size_t)blen > remaining) { free(o); return NULL; } + o->u.s = malloc((size_t)blen + 1); + if (!o->u.s) { free(o); return NULL; } + memcpy(o->u.s, p, (size_t)blen); + p += blen; + remaining -= (size_t)blen; + o->u.s[blen] = '\0'; + if (remaining < 2) { free(o->u.s); free(o); return NULL; } + if (p[0] != '\r' || p[1] != '\n') { free(o->u.s); free(o); return NULL; } + p += 2; + remaining -= 2; + } + break; + } + case '*': { + if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) { free(o); return NULL; } + long n = strtol(line, NULL, 10); + if (n < 0 || n > 65536) { free(o); return NULL; } + 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; } + for (size_t i = 0; i < (size_t)n; i++) { + resp_object *sub = resp_read_buf(p, remaining); + if (!sub) { + 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; + } + p += remaining - (sub ? remaining : 0); + remaining = 0; + o->u.arr.elem[i] = *sub; + free(sub); + } + break; + } + default: + free(o); + return NULL; + } + return o; +} + +static int resp_read_byte(int fd) { + unsigned char c; + ssize_t n = read(fd, &c, 1); + if (n != 1) { + if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + return -2; + return -1; + } + return (int)c; +} + +static int resp_read_line(int fd, char *buf, size_t buf_size) { + size_t i = 0; + int prev = -1; + while (i + 1 < buf_size) { + int b = resp_read_byte(fd); + if (b < 0) return -1; + if (prev == '\r' && b == '\n') { + buf[i - 1] = '\0'; + return 0; + } + prev = b; + buf[i++] = (char)b; + } + return -1; +} + +resp_object *resp_read(int fd) { + int type_c = resp_read_byte(fd); + if (type_c < 0) return NULL; + if (type_c == -2) return NULL; + resp_object *o = calloc(1, sizeof(resp_object)); + if (!o) return NULL; + char line[LINE_BUF]; + switch ((char)type_c) { + case '+': + o->type = RESPT_SIMPLE; + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } + o->u.s = strdup(line); + break; + case '-': + o->type = RESPT_ERROR; + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } + o->u.s = strdup(line); + break; + case ':': { + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } + o->type = RESPT_INT; + o->u.i = (long long)strtoll(line, NULL, 10); + break; + } + case '$': { + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } + long len = strtol(line, NULL, 10); + if (len < 0 || len > (long)MAX_BULK_LEN) { free(o); return NULL; } + o->type = RESPT_BULK; + if (len == 0) { + o->u.s = strdup(""); + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o->u.s); free(o); return NULL; } + } else { + o->u.s = malloc((size_t)len + 1); + if (!o->u.s) { free(o); return NULL; } + if (read(fd, o->u.s, (size_t)len) != (ssize_t)len) { free(o->u.s); free(o); return NULL; } + o->u.s[len] = '\0'; + if (resp_read_byte(fd) != '\r' || resp_read_byte(fd) != '\n') { free(o->u.s); free(o); return NULL; } + } + break; + } + case '*': { + if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } + long n = strtol(line, NULL, 10); + if (n < 0 || n > 65536) { free(o); return NULL; } + 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; } + for (size_t i = 0; i < (size_t)n; i++) { + resp_object *sub = resp_read(fd); + if (!sub) { + 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; + } + o->u.arr.elem[i] = *sub; + free(sub); + } + break; + } + default: + free(o); + return NULL; + } + return o; +} + +static void resp_free_internal(resp_object *o) { + if (!o) return; + if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) { + free(o->u.s); + } else if (o->type == RESPT_ARRAY) { + for (size_t i = 0; i < o->u.arr.n; i++) + resp_free_internal(&o->u.arr.elem[i]); + free(o->u.arr.elem); + } +} + +void resp_free(resp_object *o) { + resp_free_internal(o); + free(o); +} + +resp_object *resp_deep_copy(const resp_object *o) { + if (!o) return NULL; + resp_object *c = (resp_object *)calloc(1, sizeof(resp_object)); + if (!c) return NULL; + c->type = o->type; + if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) { + c->u.s = o->u.s ? strdup(o->u.s) : NULL; + if (o->u.s && !c->u.s) { free(c); return NULL; } + return c; + } + if (o->type == RESPT_INT) { + c->u.i = o->u.i; + return c; + } + if (o->type == RESPT_ARRAY) { + c->u.arr.n = o->u.arr.n; + c->u.arr.elem = o->u.arr.n ? (resp_object *)calloc(o->u.arr.n, sizeof(resp_object)) : NULL; + if (o->u.arr.n && !c->u.arr.elem) { free(c); return NULL; } + for (size_t i = 0; i < o->u.arr.n; i++) { + resp_object *sub = resp_deep_copy(&o->u.arr.elem[i]); + if (!sub) { + for (size_t j = 0; j < i; j++) resp_free_internal(&c->u.arr.elem[j]); + free(c->u.arr.elem); + free(c); + return NULL; + } + c->u.arr.elem[i] = *sub; + free(sub); + } + return c; + } + free(c); + return NULL; +} + +resp_object *resp_map_get(const resp_object *o, const char *key) { + if (!o || !key || o->type != RESPT_ARRAY) return NULL; + size_t n = o->u.arr.n; + if (n & 1) return NULL; + for (size_t i = 0; i < n; i += 2) { + const resp_object *k = &o->u.arr.elem[i]; + const char *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL; + if (s && strcmp(s, key) == 0 && i + 1 < n) + return (resp_object *)&o->u.arr.elem[i + 1]; + } + return NULL; +} + +const char *resp_map_get_string(const resp_object *o, const char *key) { + resp_object *val = resp_map_get(o, key); + if (!val) return NULL; + if (val->type == RESPT_BULK || val->type == RESPT_SIMPLE) + return val->u.s; + return NULL; +} + +void resp_map_set(resp_object *o, const char *key, resp_object *value) { + if (!o || !key || o->type != RESPT_ARRAY) return; + for (size_t i = 0; i + 1 < o->u.arr.n; i += 2) { + const resp_object *k = &o->u.arr.elem[i]; + const char *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL; + if (s && strcmp(s, key) == 0 && i + 1 < o->u.arr.n) { + resp_free(&o->u.arr.elem[i + 1]); + o->u.arr.elem[i + 1] = *value; + free(value); + return; + } + } + resp_array_append_bulk(o, key); + resp_array_append_obj(o, value); +} + +static int resp_append_object(char **buf, size_t *cap, size_t *len, const resp_object *o) { + if (!o) return -1; + size_t need = *len + 256; + if (o->type == RESPT_BULK || o->type == RESPT_SIMPLE || o->type == RESPT_ERROR) { + size_t slen = o->u.s ? strlen(o->u.s) : 0; + need = *len + 32 + slen + 2; + } else if (o->type == RESPT_ARRAY) { + need = *len + 32; + for (size_t i = 0; i < o->u.arr.n; i++) + need += 64; + } + if (need > *cap) { + size_t newcap = need + 4096; + char *n = realloc(*buf, newcap); + if (!n) return -1; + *buf = n; + *cap = newcap; + } + switch (o->type) { + case RESPT_SIMPLE: { + const char *s = o->u.s ? o->u.s : ""; + *len += (size_t)snprintf(*buf + *len, *cap - *len, "+%s\r\n", s); + break; + } + case RESPT_ERROR: { + const char *s = o->u.s ? o->u.s : ""; + *len += (size_t)snprintf(*buf + *len, *cap - *len, "-%s\r\n", s); + break; + } + case RESPT_INT: + *len += (size_t)snprintf(*buf + *len, *cap - *len, ":%lld\r\n", (long long)o->u.i); + break; + case RESPT_BULK: { + const char *s = o->u.s ? o->u.s : ""; + size_t slen = strlen(s); + *len += (size_t)snprintf(*buf + *len, *cap - *len, "$%zu\r\n%s\r\n", slen, s); + break; + } + case RESPT_ARRAY: { + size_t n = o->u.arr.n; + *len += (size_t)snprintf(*buf + *len, *cap - *len, "*%zu\r\n", n); + for (size_t i = 0; i < n; i++) { + if (resp_append_object(buf, cap, len, &o->u.arr.elem[i]) != 0) + return -1; + } + break; + } + default: + return -1; + } + return 0; +} + +int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len) { + size_t cap = 64; + size_t len = 0; + char *buf = malloc(cap); + if (!buf) return -1; + len += (size_t)snprintf(buf + len, cap - len, "*%d\r\n", argc); + if (len >= cap) { free(buf); return -1; } + for (int i = 0; i < argc; i++) { + if (resp_append_object(&buf, &cap, &len, argv[i]) != 0) { + free(buf); + return -1; + } + } + *out_buf = buf; + *out_len = len; + return 0; +} + +int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len) { + size_t cap = 64; + size_t len = 0; + char *buf = malloc(cap); + if (!buf) return -1; + if (resp_append_object(&buf, &cap, &len, o) != 0) { + free(buf); + return -1; + } + *out_buf = buf; + *out_len = len; + return 0; +} + +resp_object *resp_array_init(void) { + resp_object *o = calloc(1, sizeof(resp_object)); + if (!o) return NULL; + o->type = RESPT_ARRAY; + o->u.arr.n = 0; + o->u.arr.elem = NULL; + return o; +} + +int resp_array_append_obj(resp_object *destination, resp_object *value) { + if (!destination || destination->type != RESPT_ARRAY || !value) return -1; + size_t n = destination->u.arr.n; + resp_object *new_elem = realloc(destination->u.arr.elem, (n + 1) * sizeof(resp_object)); + if (!new_elem) return -1; + destination->u.arr.elem = new_elem; + destination->u.arr.elem[n] = *value; + destination->u.arr.n++; + free(value); + return 0; +} + +int resp_array_append_simple(resp_object *destination, const char *str) { + resp_object *o = calloc(1, sizeof(resp_object)); + if (!o) return -1; + o->type = RESPT_SIMPLE; + o->u.s = strdup(str ? str : ""); + if (!o->u.s) { free(o); return -1; } + if (resp_array_append_obj(destination, o) != 0) { free(o->u.s); free(o); return -1; } + return 0; +} + +int resp_array_append_bulk(resp_object *destination, const char *str) { + resp_object *o = calloc(1, sizeof(resp_object)); + if (!o) return -1; + o->type = RESPT_BULK; + o->u.s = strdup(str ? str : ""); + if (!o->u.s) { free(o); return -1; } + if (resp_array_append_obj(destination, o) != 0) { free(o->u.s); free(o); return -1; } + return 0; +} + +int resp_array_append_int(resp_object *destination, long long i) { + resp_object *o = malloc(sizeof(resp_object)); + if (!o) return -1; + o->type = RESPT_INT; + o->u.i = i; + return resp_array_append_obj(destination, o); +} diff --git a/src/common/resp.h b/src/common/resp.h @@ -0,0 +1,48 @@ +#ifndef UDPHOLE_RESP_H +#define UDPHOLE_RESP_H + +#include <stddef.h> + +#define RESPT_SIMPLE 0 +#define RESPT_ERROR 1 +#define RESPT_BULK 2 +#define RESPT_INT 3 +#define RESPT_ARRAY 4 + +typedef struct resp_object resp_object; +struct resp_object { + int type; + union { + char *s; + long long i; + struct { resp_object *elem; size_t n; } arr; + } u; +}; + +void resp_free(resp_object *o); + +resp_object *resp_deep_copy(const resp_object *o); + +resp_object *resp_map_get(const resp_object *o, const char *key); + +const char *resp_map_get_string(const resp_object *o, const char *key); + +void resp_map_set(resp_object *map, const char *key, resp_object *value); + +resp_object *resp_read(int fd); + +resp_object *resp_read_buf(const char *buf, size_t len); + +int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len); + +int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len); + +resp_object *resp_array_init(void); + +int resp_array_append_obj(resp_object *destination, resp_object *value); + +int resp_array_append_simple(resp_object *destination, const char *str); +int resp_array_append_bulk(resp_object *destination, const char *str); +int resp_array_append_int(resp_object *destination, long long i); + +#endif diff --git a/src/domain/config.c b/src/domain/config.c @@ -0,0 +1,40 @@ +#include <stdlib.h> +#include <string.h> + +#include "domain/config.h" + +udphole_config_t *domain_cfg = NULL; + +void domain_config_init(void) { + if (domain_cfg) { + domain_config_free(); + } + domain_cfg = calloc(1, sizeof(udphole_config_t)); + if (domain_cfg) { + domain_cfg->port_low = 7000; + domain_cfg->port_high = 7999; + domain_cfg->port_cur = 7000; + } +} + +void domain_config_set_ports(int low, int high) { + if (!domain_cfg) domain_config_init(); + if (!domain_cfg) return; + domain_cfg->port_low = low > 0 ? low : 7000; + domain_cfg->port_high = high > domain_cfg->port_low ? high : domain_cfg->port_low + 999; + domain_cfg->port_cur = domain_cfg->port_low; +} + +void domain_config_set_advertise(const char *addr) { + if (!domain_cfg) domain_config_init(); + if (!domain_cfg) return; + free(domain_cfg->advertise_addr); + domain_cfg->advertise_addr = addr ? strdup(addr) : NULL; +} + +void domain_config_free(void) { + if (!domain_cfg) return; + free(domain_cfg->advertise_addr); + free(domain_cfg); + domain_cfg = NULL; +} diff --git a/src/domain/config.h b/src/domain/config.h @@ -0,0 +1,23 @@ +#ifndef UDPHOLE_DOMAIN_CONFIG_H +#define UDPHOLE_DOMAIN_CONFIG_H + +#include <stdint.h> + +typedef struct { + int port_low; + int port_high; + int port_cur; + char *advertise_addr; +} udphole_config_t; + +extern udphole_config_t *domain_cfg; + +void domain_config_init(void); + +void domain_config_set_ports(int low, int high); + +void domain_config_set_advertise(const char *addr); + +void domain_config_free(void); + +#endif diff --git a/src/domain/session.c b/src/domain/session.c @@ -15,11 +15,11 @@ #include "rxi/log.h" #include "domain/protothreads.h" #include "domain/scheduler.h" +#include "domain/config.h" #include "common/socket_util.h" -#include "infrastructure/config.h" +#include "common/resp.h" #include "tidwall/hashmap.h" #include "session.h" -#include "interface/api/server.h" #define SESSION_HASH_SIZE 256 #define BUFFER_SIZE 4096 @@ -60,10 +60,6 @@ typedef struct session { static session_t **sessions = NULL; static size_t sessions_count = 0; -static char *advertise_addr = NULL; -static int port_low = 7000; -static int port_high = 7999; -static int port_cur = 7000; static int running = 0; static session_t *find_session(const char *session_id) { @@ -98,11 +94,12 @@ static socket_t *find_socket(session_t *s, const char *socket_id) { } static int alloc_port(void) { - for (int i = 0; i < port_high - port_low; i++) { - int port = port_cur + i; - if (port > port_high) port = port_low; - port_cur = port + 1; - if (port_cur > port_high) port_cur = port_low; + if (!domain_cfg) return 0; + for (int i = 0; i < domain_cfg->port_high - domain_cfg->port_low; i++) { + int port = domain_cfg->port_cur + i; + if (port > domain_cfg->port_high) port = domain_cfg->port_low; + domain_cfg->port_cur = port + 1; + if (domain_cfg->port_cur > domain_cfg->port_high) domain_cfg->port_cur = domain_cfg->port_low; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); @@ -515,285 +512,459 @@ static void spawn_session_pt(session_t *s) { s->task = (struct pt_task *)(intptr_t)domain_schedmod_pt_create(session_pt, s); } -static char cmd_session_create(api_client_t *c, char **args, int nargs) { - if (nargs < 2) { - return api_write_err(c, "wrong number of arguments for 'session.create'") ? 1 : 0; +resp_object *domain_session_create(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 2) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.create'"); + return err; + } + + const char *session_id = NULL; + if (args->u.arr.n > 1 && args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + + if (!session_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.create'"); + return err; } - const char *session_id = args[1]; int idle_expiry = 0; - if (nargs >= 3 && args[2] && args[2][0] != '\0') { - idle_expiry = atoi(args[2]); + if (args->u.arr.n >= 3 && args->u.arr.elem[2].type == RESPT_BULK && args->u.arr.elem[2].u.s) { + idle_expiry = atoi(args->u.arr.elem[2].u.s); } session_t *s = create_session(session_id, idle_expiry); if (!s) { - return api_write_err(c, "failed to create session") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR failed to create session"); + return err; } spawn_session_pt(s); - return api_write_ok(c) ? 1 : 0; + + resp_object *res = resp_array_init(); + resp_array_append_simple(res, "OK"); + return res; } -static char cmd_session_list(api_client_t *c, char **args, int nargs) { +resp_object *domain_session_list(resp_object *args) { (void)args; - if (nargs != 1) { - return api_write_err(c, "wrong number of arguments for 'session.list'") ? 1 : 0; - } - - if (!sessions) { - return api_write_array(c, 0) ? 1 : 0; - } - if (!api_write_array(c, sessions_count)) return 0; + resp_object *res = resp_array_init(); + if (!res) return NULL; for (size_t i = 0; i < sessions_count; i++) { session_t *s = sessions[i]; if (!s) continue; - if (!api_write_bulk_cstr(c, s->session_id)) return 0; + resp_array_append_bulk(res, s->session_id); } - return 1; + return res; } -static char cmd_session_info(api_client_t *c, char **args, int nargs) { - if (nargs != 2) { - return api_write_err(c, "wrong number of arguments for 'session.info'") ? 1 : 0; +resp_object *domain_session_info(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 2) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.info'"); + return err; + } + + const char *session_id = NULL; + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + + if (!session_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.info'"); + return err; } - const char *session_id = args[1]; session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } - size_t num_items = 8; - if (!api_write_array(c, num_items)) return 0; + resp_object *res = resp_array_init(); + if (!res) return NULL; - if (!api_write_bulk_cstr(c, "session_id")) return 0; - if (!api_write_bulk_cstr(c, s->session_id)) return 0; + resp_array_append_bulk(res, "session_id"); + resp_array_append_bulk(res, s->session_id); - if (!api_write_bulk_cstr(c, "created")) return 0; - if (!api_write_bulk_int(c, (int)s->created)) return 0; + resp_array_append_bulk(res, "created"); + resp_array_append_int(res, (long long)s->created); - if (!api_write_bulk_cstr(c, "last_activity")) return 0; - if (!api_write_bulk_int(c, (int)s->last_activity)) return 0; + resp_array_append_bulk(res, "last_activity"); + resp_array_append_int(res, (long long)s->last_activity); - if (!api_write_bulk_cstr(c, "idle_expiry")) return 0; - if (!api_write_bulk_int(c, (int)s->idle_expiry)) return 0; + resp_array_append_bulk(res, "idle_expiry"); + resp_array_append_int(res, (long long)s->idle_expiry); - if (!api_write_bulk_cstr(c, "sockets")) return 0; - if (!api_write_array(c, s->sockets_count)) return 0; + resp_array_append_bulk(res, "sockets"); + resp_object *sockets_arr = resp_array_init(); for (size_t i = 0; i < s->sockets_count; i++) { socket_t *sock = s->sockets[i]; if (!sock) continue; - if (!api_write_bulk_cstr(c, sock->socket_id)) return 0; + resp_array_append_bulk(sockets_arr, sock->socket_id); } + resp_array_append_obj(res, sockets_arr); - if (!api_write_bulk_cstr(c, "forwards")) return 0; - if (!api_write_array(c, s->forwards_count * 2)) return 0; + resp_array_append_bulk(res, "forwards"); + resp_object *forwards_arr = resp_array_init(); for (size_t i = 0; i < s->forwards_count; i++) { - if (!api_write_bulk_cstr(c, s->forwards[i].src_socket_id)) return 0; - if (!api_write_bulk_cstr(c, s->forwards[i].dst_socket_id)) return 0; + resp_array_append_bulk(forwards_arr, s->forwards[i].src_socket_id); + resp_array_append_bulk(forwards_arr, s->forwards[i].dst_socket_id); } + resp_array_append_obj(res, forwards_arr); - if (!api_write_bulk_cstr(c, "marked_for_deletion")) return 0; - if (!api_write_int(c, s->marked_for_deletion)) return 0; + resp_array_append_bulk(res, "marked_for_deletion"); + resp_array_append_int(res, s->marked_for_deletion); - return 1; + return res; } -static char cmd_session_destroy(api_client_t *c, char **args, int nargs) { - if (nargs != 2) { - return api_write_err(c, "wrong number of arguments for 'session.destroy'") ? 1 : 0; +resp_object *domain_session_destroy(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 2) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.destroy'"); + return err; + } + + const char *session_id = NULL; + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + + if (!session_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.destroy'"); + return err; } - const char *session_id = args[1]; session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } destroy_session(s); - return api_write_ok(c) ? 1 : 0; + + resp_object *res = resp_array_init(); + resp_array_append_simple(res, "OK"); + return res; } -static char cmd_socket_create_listen(api_client_t *c, char **args, int nargs) { - if (nargs != 3) { - return api_write_err(c, "wrong number of arguments for 'session.socket.create.listen'") ? 1 : 0; +resp_object *domain_socket_create_listen(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 3) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.create.listen'"); + return err; + } + + const char *session_id = NULL; + const char *socket_id = NULL; + + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + if (args->u.arr.elem[2].type == RESPT_BULK) { + socket_id = args->u.arr.elem[2].u.s; } - const char *session_id = args[1]; - const char *socket_id = args[2]; + if (!session_id || !socket_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.create.listen'"); + return err; + } session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } socket_t *sock = create_listen_socket(s, socket_id); if (!sock) { - return api_write_err(c, "failed to create socket") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR failed to create socket"); + return err; } - if (!api_write_array(c, 2)) return 0; - if (!api_write_bulk_int(c, sock->local_port)) return 0; - if (!api_write_bulk_cstr(c, advertise_addr)) return 0; - - return 1; + resp_object *res = resp_array_init(); + resp_array_append_int(res, sock->local_port); + resp_array_append_bulk(res, domain_cfg && domain_cfg->advertise_addr ? domain_cfg->advertise_addr : ""); + return res; } -static char cmd_socket_create_connect(api_client_t *c, char **args, int nargs) { - if (nargs != 5) { - return api_write_err(c, "wrong number of arguments for 'session.socket.create.connect'") ? 1 : 0; +resp_object *domain_socket_create_connect(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 5) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.create.connect'"); + return err; + } + + const char *session_id = NULL; + const char *socket_id = NULL; + const char *ip = NULL; + const char *port_str = NULL; + + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + if (args->u.arr.elem[2].type == RESPT_BULK) { + socket_id = args->u.arr.elem[2].u.s; + } + if (args->u.arr.elem[3].type == RESPT_BULK) { + ip = args->u.arr.elem[3].u.s; + } + if (args->u.arr.elem[4].type == RESPT_BULK) { + port_str = args->u.arr.elem[4].u.s; + } + + if (!session_id || !socket_id || !ip || !port_str) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.create.connect'"); + return err; } - const char *session_id = args[1]; - const char *socket_id = args[2]; - const char *ip = args[3]; - int port = atoi(args[4]); + int port = atoi(port_str); session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } socket_t *sock = create_connect_socket(s, socket_id, ip, port); if (!sock) { - return api_write_err(c, "failed to create socket") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR failed to create socket"); + return err; } - if (!api_write_array(c, 2)) return 0; - if (!api_write_bulk_int(c, sock->local_port)) return 0; - if (!api_write_bulk_cstr(c, advertise_addr)) return 0; - - return 1; + resp_object *res = resp_array_init(); + resp_array_append_int(res, sock->local_port); + resp_array_append_bulk(res, domain_cfg && domain_cfg->advertise_addr ? domain_cfg->advertise_addr : ""); + return res; } -static char cmd_socket_destroy(api_client_t *c, char **args, int nargs) { - if (nargs != 3) { - return api_write_err(c, "wrong number of arguments for 'session.socket.destroy'") ? 1 : 0; +resp_object *domain_socket_destroy(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 3) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.destroy'"); + return err; } - const char *session_id = args[1]; - const char *socket_id = args[2]; + const char *session_id = NULL; + const char *socket_id = NULL; + + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + if (args->u.arr.elem[2].type == RESPT_BULK) { + socket_id = args->u.arr.elem[2].u.s; + } + + if (!session_id || !socket_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.socket.destroy'"); + return err; + } session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } if (destroy_socket(s, socket_id) != 0) { - return api_write_err(c, "socket not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR socket not found"); + return err; } - return api_write_ok(c) ? 1 : 0; + resp_object *res = resp_array_init(); + resp_array_append_simple(res, "OK"); + return res; } -static char cmd_forward_list(api_client_t *c, char **args, int nargs) { - if (nargs != 2) { - return api_write_err(c, "wrong number of arguments for 'session.forward.list'") ? 1 : 0; +resp_object *domain_forward_list(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 2) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.list'"); + return err; + } + + const char *session_id = NULL; + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + + if (!session_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.list'"); + return err; } - const char *session_id = args[1]; session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } - if (!api_write_array(c, s->forwards_count * 2)) return 0; + resp_object *res = resp_array_init(); for (size_t i = 0; i < s->forwards_count; i++) { - if (!api_write_bulk_cstr(c, s->forwards[i].src_socket_id)) return 0; - if (!api_write_bulk_cstr(c, s->forwards[i].dst_socket_id)) return 0; + resp_array_append_bulk(res, s->forwards[i].src_socket_id); + resp_array_append_bulk(res, s->forwards[i].dst_socket_id); } - - return 1; + return res; } -static char cmd_forward_create(api_client_t *c, char **args, int nargs) { - if (nargs != 4) { - return api_write_err(c, "wrong number of arguments for 'session.forward.create'") ? 1 : 0; +resp_object *domain_forward_create(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 4) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.create'"); + return err; + } + + const char *session_id = NULL; + const char *src_socket_id = NULL; + const char *dst_socket_id = NULL; + + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + if (args->u.arr.elem[2].type == RESPT_BULK) { + src_socket_id = args->u.arr.elem[2].u.s; + } + if (args->u.arr.elem[3].type == RESPT_BULK) { + dst_socket_id = args->u.arr.elem[3].u.s; } - const char *session_id = args[1]; - const char *src_socket_id = args[2]; - const char *dst_socket_id = args[3]; + if (!session_id || !src_socket_id || !dst_socket_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.create'"); + return err; + } session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } socket_t *src = find_socket(s, src_socket_id); if (!src) { - return api_write_err(c, "source socket not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR source socket not found"); + return err; } socket_t *dst = find_socket(s, dst_socket_id); if (!dst) { - return api_write_err(c, "destination socket not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR destination socket not found"); + return err; } if (add_forward(s, src_socket_id, dst_socket_id) != 0) { - return api_write_err(c, "failed to add forward") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR failed to add forward"); + return err; } log_debug("udphole: created forward %s -> %s in session %s", src_socket_id, dst_socket_id, session_id); - return api_write_ok(c) ? 1 : 0; + + resp_object *res = resp_array_init(); + resp_array_append_simple(res, "OK"); + return res; } -static char cmd_forward_destroy(api_client_t *c, char **args, int nargs) { - if (nargs != 4) { - return api_write_err(c, "wrong number of arguments for 'session.forward.destroy'") ? 1 : 0; +resp_object *domain_forward_destroy(resp_object *args) { + if (!args || args->type != RESPT_ARRAY || args->u.arr.n < 4) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.destroy'"); + return err; } - const char *session_id = args[1]; - const char *src_socket_id = args[2]; - const char *dst_socket_id = args[3]; + const char *session_id = NULL; + const char *src_socket_id = NULL; + const char *dst_socket_id = NULL; + + if (args->u.arr.elem[1].type == RESPT_BULK) { + session_id = args->u.arr.elem[1].u.s; + } + if (args->u.arr.elem[2].type == RESPT_BULK) { + src_socket_id = args->u.arr.elem[2].u.s; + } + if (args->u.arr.elem[3].type == RESPT_BULK) { + dst_socket_id = args->u.arr.elem[3].u.s; + } + + if (!session_id || !src_socket_id || !dst_socket_id) { + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR wrong number of arguments for 'session.forward.destroy'"); + return err; + } session_t *s = find_session(session_id); if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR session not found"); + return err; } if (remove_forward(s, src_socket_id, dst_socket_id) != 0) { - return api_write_err(c, "forward not found") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR forward not found"); + return err; } - return api_write_ok(c) ? 1 : 0; + resp_object *res = resp_array_init(); + resp_array_append_simple(res, "OK"); + return res; } -static char cmd_system_load(api_client_t *c, char **args, int nargs) { +resp_object *domain_system_load(resp_object *args) { (void)args; - if (nargs != 1) { - return api_write_err(c, "wrong number of arguments for 'system.load'") ? 1 : 0; - } double loadavg[3]; if (getloadavg(loadavg, 3) != 3) { - return api_write_err(c, "failed to get load average") ? 1 : 0; + resp_object *err = resp_array_init(); + resp_array_append_simple(err, "ERR failed to get load average"); + return err; } - if (!api_write_array(c, 6)) return 0; - if (!api_write_bulk_cstr(c, "1min")) return 0; + resp_object *res = resp_array_init(); char buf[64]; + + resp_array_append_bulk(res, "1min"); snprintf(buf, sizeof(buf), "%.2f", loadavg[0]); - if (!api_write_bulk_cstr(c, buf)) return 0; - if (!api_write_bulk_cstr(c, "5min")) return 0; + resp_array_append_bulk(res, buf); + + resp_array_append_bulk(res, "5min"); snprintf(buf, sizeof(buf), "%.2f", loadavg[1]); - if (!api_write_bulk_cstr(c, buf)) return 0; - if (!api_write_bulk_cstr(c, "15min")) return 0; + resp_array_append_bulk(res, buf); + + resp_array_append_bulk(res, "15min"); snprintf(buf, sizeof(buf), "%.2f", loadavg[2]); - if (!api_write_bulk_cstr(c, buf)) return 0; + resp_array_append_bulk(res, buf); - return 1; + return res; } -static char cmd_session_count(api_client_t *c, char **args, int nargs) { +resp_object *domain_session_count(resp_object *args) { (void)args; - if (nargs != 1) { - return api_write_err(c, "wrong number of arguments for 'session.count'") ? 1 : 0; - } size_t count = 0; for (size_t i = 0; i < sessions_count; i++) { @@ -802,23 +973,11 @@ static char cmd_session_count(api_client_t *c, char **args, int nargs) { } } - return api_write_int(c, (int)count) ? 1 : 0; -} - -static void register_session_commands(void) { - api_register_cmd("session.create", cmd_session_create); - api_register_cmd("session.list", cmd_session_list); - api_register_cmd("session.info", cmd_session_info); - api_register_cmd("session.destroy", cmd_session_destroy); - api_register_cmd("session.socket.create.listen", cmd_socket_create_listen); - api_register_cmd("session.socket.create.connect", cmd_socket_create_connect); - api_register_cmd("session.socket.destroy", cmd_socket_destroy); - api_register_cmd("session.forward.list", cmd_forward_list); - api_register_cmd("session.forward.create", cmd_forward_create); - api_register_cmd("session.forward.destroy", cmd_forward_destroy); - api_register_cmd("session.count", cmd_session_count); - api_register_cmd("system.load", cmd_system_load); - log_info("udphole: registered session.* commands"); + resp_object *res = malloc(sizeof(resp_object)); + if (!res) return NULL; + res->type = RESPT_INT; + res->u.i = (long long)count; + return res; } PT_THREAD(session_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { @@ -826,30 +985,10 @@ PT_THREAD(session_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *t log_trace("session_manager: protothread entry"); PT_BEGIN(pt); - PT_WAIT_UNTIL(pt, global_cfg); + PT_WAIT_UNTIL(pt, domain_cfg); - resp_object *cfg_sec = resp_map_get(global_cfg, "udphole"); - if (!cfg_sec) { - log_info("udphole: no [udphole] section in config, not starting"); - PT_EXIT(pt); - } - - const char *ports_str = resp_map_get_string(cfg_sec, "ports"); - if (ports_str) { - sscanf(ports_str, "%d-%d", &port_low, &port_high); - if (port_low <= 0) port_low = 7000; - if (port_high <= port_low) port_high = port_low + 999; - } - port_cur = port_low; - - const char *advertise_cfg = resp_map_get_string(cfg_sec, "advertise"); - if (advertise_cfg) { - advertise_addr = strdup(advertise_cfg); - } - - register_session_commands(); running = 1; - log_info("udphole: manager started with port range %d-%d", port_low, port_high); + log_info("udphole: manager started with port range %d-%d", domain_cfg->port_low, domain_cfg->port_high); int64_t last_cleanup = 0; @@ -871,18 +1010,5 @@ PT_THREAD(session_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *t } } - free(advertise_addr); - advertise_addr = NULL; - PT_END(pt); -} - -void domain_setup(void) { - extern void cli_register_command(const char *name, const char *description, int (*fn)(int, const char **)); - extern int cli_cmd_daemon(int argc, const char **argv); - cli_register_command( - "daemon", - "Run the udphole daemon", - cli_cmd_daemon - ); } \ No newline at end of file diff --git a/src/domain/session.h b/src/domain/session.h @@ -4,7 +4,21 @@ #include <stdint.h> #include "domain/scheduler.h" +#include "common/resp.h" PT_THREAD(session_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); -#endif // UDPHOLE_SESSION_H -\ No newline at end of file +resp_object *domain_session_create(resp_object *args); +resp_object *domain_session_list(resp_object *args); +resp_object *domain_session_info(resp_object *args); +resp_object *domain_session_destroy(resp_object *args); +resp_object *domain_socket_create_listen(resp_object *args); +resp_object *domain_socket_create_connect(resp_object *args); +resp_object *domain_socket_destroy(resp_object *args); +resp_object *domain_forward_list(resp_object *args); +resp_object *domain_forward_create(resp_object *args); +resp_object *domain_forward_destroy(resp_object *args); +resp_object *domain_session_count(resp_object *args); +resp_object *domain_system_load(resp_object *args); + +#endif diff --git a/src/infrastructure/config.c b/src/infrastructure/config.c @@ -3,7 +3,7 @@ #include "benhoyt/inih.h" #include "rxi/log.h" #include "infrastructure/config.h" -#include "infrastructure/resp.h" +#include "common/resp.h" resp_object *global_cfg = NULL; resp_object *pending_cfg = NULL; diff --git a/src/infrastructure/config.h b/src/infrastructure/config.h @@ -1,7 +1,7 @@ #ifndef UDPHOLE_CONFIG_H #define UDPHOLE_CONFIG_H -#include "infrastructure/resp.h" +#include "common/resp.h" extern resp_object *global_cfg; extern resp_object *pending_cfg; diff --git a/src/infrastructure/resp.c b/src/infrastructure/resp.c @@ -1,331 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <unistd.h> -#include <errno.h> - -#include "rxi/log.h" -#include "infrastructure/resp.h" - -#define MAX_BULK_LEN (256 * 1024) -#define LINE_BUF 4096 - -static void resp_free_internal(resp_object *o); - -static int resp_read_byte(int fd) { - unsigned char c; - ssize_t n = read(fd, &c, 1); - if (n != 1) { - if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) - return -2; - return -1; - } - return (int)c; -} - -static int resp_read_line(int fd, char *buf, size_t buf_size) { - size_t i = 0; - int prev = -1; - while (i + 1 < buf_size) { - int b = resp_read_byte(fd); - if (b < 0) return -1; - if (prev == '\r' && b == '\n') { - buf[i - 1] = '\0'; - return 0; - } - prev = b; - buf[i++] = (char)b; - } - return -1; -} - -resp_object *resp_read(int fd) { - int type_c = resp_read_byte(fd); - if (type_c < 0) return NULL; - if (type_c == -2) return NULL; - resp_object *o = calloc(1, sizeof(resp_object)); - if (!o) return NULL; - char line[LINE_BUF]; - switch ((char)type_c) { - case '+': - o->type = RESPT_SIMPLE; - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } - o->u.s = strdup(line); - break; - case '-': - o->type = RESPT_ERROR; - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } - o->u.s = strdup(line); - break; - case ':': { - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } - o->type = RESPT_INT; - o->u.i = (long long)strtoll(line, NULL, 10); - break; - } - case '$': { - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } - long len = strtol(line, NULL, 10); - if (len < 0 || len > (long)MAX_BULK_LEN) { free(o); return NULL; } - o->type = RESPT_BULK; - if (len == 0) { - o->u.s = strdup(""); - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o->u.s); free(o); return NULL; } - } else { - o->u.s = malloc((size_t)len + 1); - if (!o->u.s) { free(o); return NULL; } - if (read(fd, o->u.s, (size_t)len) != (ssize_t)len) { free(o->u.s); free(o); return NULL; } - o->u.s[len] = '\0'; - if (resp_read_byte(fd) != '\r' || resp_read_byte(fd) != '\n') { free(o->u.s); free(o); return NULL; } - } - break; - } - case '*': { - if (resp_read_line(fd, line, sizeof(line)) != 0) { free(o); return NULL; } - long n = strtol(line, NULL, 10); - if (n < 0 || n > 65536) { free(o); return NULL; } - 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; } - for (size_t i = 0; i < (size_t)n; i++) { - resp_object *sub = resp_read(fd); - if (!sub) { - 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; - } - o->u.arr.elem[i] = *sub; - free(sub); - } - break; - } - default: - free(o); - return NULL; - } - return o; -} - -static void resp_free_internal(resp_object *o) { - if (!o) return; - if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) { - free(o->u.s); - } else if (o->type == RESPT_ARRAY) { - for (size_t i = 0; i < o->u.arr.n; i++) - resp_free_internal(&o->u.arr.elem[i]); - free(o->u.arr.elem); - } -} - -void resp_free(resp_object *o) { - resp_free_internal(o); - free(o); -} - -resp_object *resp_deep_copy(const resp_object *o) { - if (!o) return NULL; - resp_object *c = (resp_object *)calloc(1, sizeof(resp_object)); - if (!c) return NULL; - c->type = o->type; - if (o->type == RESPT_SIMPLE || o->type == RESPT_ERROR || o->type == RESPT_BULK) { - c->u.s = o->u.s ? strdup(o->u.s) : NULL; - if (o->u.s && !c->u.s) { free(c); return NULL; } - return c; - } - if (o->type == RESPT_INT) { - c->u.i = o->u.i; - return c; - } - if (o->type == RESPT_ARRAY) { - c->u.arr.n = o->u.arr.n; - c->u.arr.elem = o->u.arr.n ? (resp_object *)calloc(o->u.arr.n, sizeof(resp_object)) : NULL; - if (o->u.arr.n && !c->u.arr.elem) { free(c); return NULL; } - for (size_t i = 0; i < o->u.arr.n; i++) { - resp_object *sub = resp_deep_copy(&o->u.arr.elem[i]); - if (!sub) { - for (size_t j = 0; j < i; j++) resp_free_internal(&c->u.arr.elem[j]); - free(c->u.arr.elem); - free(c); - return NULL; - } - c->u.arr.elem[i] = *sub; - free(sub); - } - return c; - } - free(c); - return NULL; -} - -resp_object *resp_map_get(const resp_object *o, const char *key) { - if (!o || !key || o->type != RESPT_ARRAY) return NULL; - size_t n = o->u.arr.n; - if (n & 1) return NULL; - for (size_t i = 0; i < n; i += 2) { - const resp_object *k = &o->u.arr.elem[i]; - const char *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL; - if (s && strcmp(s, key) == 0 && i + 1 < n) - return (resp_object *)&o->u.arr.elem[i + 1]; - } - return NULL; -} - -const char *resp_map_get_string(const resp_object *o, const char *key) { - resp_object *val = resp_map_get(o, key); - if (!val) return NULL; - if (val->type == RESPT_BULK || val->type == RESPT_SIMPLE) - return val->u.s; - return NULL; -} - -void resp_map_set(resp_object *o, const char *key, resp_object *value) { - if (!o || !key || o->type != RESPT_ARRAY) return; - for (size_t i = 0; i + 1 < o->u.arr.n; i += 2) { - const resp_object *k = &o->u.arr.elem[i]; - const char *s = (k->type == RESPT_BULK || k->type == RESPT_SIMPLE) ? k->u.s : NULL; - if (s && strcmp(s, key) == 0 && i + 1 < o->u.arr.n) { - resp_free(&o->u.arr.elem[i + 1]); - o->u.arr.elem[i + 1] = *value; - free(value); - return; - } - } - resp_array_append_bulk(o, key); - resp_array_append_obj(o, value); -} - -static int resp_append_object(char **buf, size_t *cap, size_t *len, const resp_object *o) { - if (!o) return -1; - size_t need = *len + 256; - if (o->type == RESPT_BULK || o->type == RESPT_SIMPLE || o->type == RESPT_ERROR) { - size_t slen = o->u.s ? strlen(o->u.s) : 0; - need = *len + 32 + slen + 2; - } else if (o->type == RESPT_ARRAY) { - need = *len + 32; - for (size_t i = 0; i < o->u.arr.n; i++) - need += 64; - } - if (need > *cap) { - size_t newcap = need + 4096; - char *n = realloc(*buf, newcap); - if (!n) return -1; - *buf = n; - *cap = newcap; - } - switch (o->type) { - case RESPT_SIMPLE: { - const char *s = o->u.s ? o->u.s : ""; - *len += (size_t)snprintf(*buf + *len, *cap - *len, "+%s\r\n", s); - break; - } - case RESPT_ERROR: { - const char *s = o->u.s ? o->u.s : ""; - *len += (size_t)snprintf(*buf + *len, *cap - *len, "-%s\r\n", s); - break; - } - case RESPT_INT: - *len += (size_t)snprintf(*buf + *len, *cap - *len, ":%lld\r\n", (long long)o->u.i); - break; - case RESPT_BULK: { - const char *s = o->u.s ? o->u.s : ""; - size_t slen = strlen(s); - *len += (size_t)snprintf(*buf + *len, *cap - *len, "$%zu\r\n%s\r\n", slen, s); - break; - } - case RESPT_ARRAY: { - size_t n = o->u.arr.n; - *len += (size_t)snprintf(*buf + *len, *cap - *len, "*%zu\r\n", n); - for (size_t i = 0; i < n; i++) { - if (resp_append_object(buf, cap, len, &o->u.arr.elem[i]) != 0) - return -1; - } - break; - } - default: - return -1; - } - return 0; -} - -int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len) { - size_t cap = 64; - size_t len = 0; - char *buf = malloc(cap); - if (!buf) return -1; - len += (size_t)snprintf(buf + len, cap - len, "*%d\r\n", argc); - if (len >= cap) { free(buf); return -1; } - for (int i = 0; i < argc; i++) { - if (resp_append_object(&buf, &cap, &len, argv[i]) != 0) { - free(buf); - return -1; - } - } - *out_buf = buf; - *out_len = len; - return 0; -} - -int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len) { - size_t cap = 64; - size_t len = 0; - char *buf = malloc(cap); - if (!buf) return -1; - if (resp_append_object(&buf, &cap, &len, o) != 0) { - free(buf); - return -1; - } - *out_buf = buf; - *out_len = len; - return 0; -} - -resp_object *resp_array_init(void) { - resp_object *o = calloc(1, sizeof(resp_object)); - if (!o) return NULL; - o->type = RESPT_ARRAY; - o->u.arr.n = 0; - o->u.arr.elem = NULL; - return o; -} - -int resp_array_append_obj(resp_object *destination, resp_object *value) { - if (!destination || destination->type != RESPT_ARRAY || !value) return -1; - size_t n = destination->u.arr.n; - resp_object *new_elem = realloc(destination->u.arr.elem, (n + 1) * sizeof(resp_object)); - if (!new_elem) return -1; - destination->u.arr.elem = new_elem; - destination->u.arr.elem[n] = *value; - destination->u.arr.n++; - free(value); - return 0; -} - -int resp_array_append_simple(resp_object *destination, const char *str) { - resp_object *o = calloc(1, sizeof(resp_object)); - if (!o) return -1; - o->type = RESPT_SIMPLE; - o->u.s = strdup(str ? str : ""); - if (!o->u.s) { free(o); return -1; } - if (resp_array_append_obj(destination, o) != 0) { free(o->u.s); free(o); return -1; } - return 0; -} - -int resp_array_append_bulk(resp_object *destination, const char *str) { - resp_object *o = calloc(1, sizeof(resp_object)); - if (!o) return -1; - o->type = RESPT_BULK; - o->u.s = strdup(str ? str : ""); - if (!o->u.s) { free(o); return -1; } - if (resp_array_append_obj(destination, o) != 0) { free(o->u.s); free(o); return -1; } - return 0; -} - -int resp_array_append_int(resp_object *destination, long long i) { - resp_object *o = malloc(sizeof(resp_object)); - if (!o) return -1; - o->type = RESPT_INT; - o->u.i = i; - return resp_array_append_obj(destination, o); -} -\ No newline at end of file diff --git a/src/infrastructure/resp.h b/src/infrastructure/resp.h @@ -1,46 +0,0 @@ -#ifndef UDPHOLE_RESP_H -#define UDPHOLE_RESP_H - -#include <stddef.h> - -#define RESPT_SIMPLE 0 -#define RESPT_ERROR 1 -#define RESPT_BULK 2 -#define RESPT_INT 3 -#define RESPT_ARRAY 4 - -typedef struct resp_object resp_object; -struct resp_object { - int type; - union { - char *s; - long long i; - struct { resp_object *elem; size_t n; } arr; - } u; -}; - -void resp_free(resp_object *o); - -resp_object *resp_deep_copy(const resp_object *o); - -resp_object *resp_map_get(const resp_object *o, const char *key); - -const char *resp_map_get_string(const resp_object *o, const char *key); - -void resp_map_set(resp_object *map, const char *key, resp_object *value); - -resp_object *resp_read(int fd); - -int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len); - -int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len); - -resp_object *resp_array_init(void); - -int resp_array_append_obj(resp_object *destination, resp_object *value); - -int resp_array_append_simple(resp_object *destination, const char *str); -int resp_array_append_bulk(resp_object *destination, const char *str); -int resp_array_append_int(resp_object *destination, long long i); - -#endif -\ No newline at end of file diff --git a/src/interface/api/server.c b/src/interface/api/server.c @@ -26,7 +26,7 @@ #include "common/socket_util.h" #include "infrastructure/config.h" #include "interface/api/server.h" -#include "infrastructure/resp.h" +#include "common/resp.h" struct pt_task; PT_THREAD(api_client_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); @@ -56,8 +56,14 @@ typedef struct { char (*func)(api_client_t *c, char **args, int nargs); } api_cmd_entry; +typedef struct { + const char *name; + domain_cmd_fn func; +} domain_cmd_entry; + static char *current_listen = NULL; static struct hashmap *cmd_map = NULL; +static struct hashmap *domain_cmd_map = NULL; typedef struct { int *server_fds; @@ -308,6 +314,25 @@ void api_register_cmd(const char *name, char (*func)(api_client_t *, char **, in log_trace("api: registered command '%s'", name); } +static uint64_t domain_cmd_hash(const void *item, uint64_t seed0, uint64_t seed1) { + const domain_cmd_entry *cmd = item; + return hashmap_sip(cmd->name, strlen(cmd->name), seed0, seed1); +} + +static int domain_cmd_compare(const void *a, const void *b, void *udata) { + (void)udata; + const domain_cmd_entry *ca = a; + const domain_cmd_entry *cb = b; + return strcasecmp(ca->name, cb->name); +} + +void api_register_domain_cmd(const char *name, domain_cmd_fn func) { + if (!domain_cmd_map) + domain_cmd_map = hashmap_new(sizeof(domain_cmd_entry), 0, 0, 0, domain_cmd_hash, domain_cmd_compare, NULL, NULL); + hashmap_set(domain_cmd_map, &(domain_cmd_entry){ .name = name, .func = func }); + log_trace("api: registered domain command '%s'", name); +} + static char cmdAUTH(api_client_t *c, char **args, int nargs) { if (nargs != 3) { api_write_err(c, "wrong number of arguments for 'auth' command (AUTH username password)"); @@ -349,25 +374,44 @@ static bool is_builtin(const char *name); static char cmdCOMMAND(api_client_t *c, char **args, int nargs) { (void)args; - if (!cmd_map) + if (!cmd_map && !domain_cmd_map) return api_write_array(c, 0) ? 1 : 0; resp_object *result = resp_array_init(); if (!result) return 0; - size_t iter = 0; - void *item; - while (hashmap_iter(cmd_map, &iter, &item)) { - const api_cmd_entry *e = item; - if (!is_builtin(e->name) && !user_has_permit(c, e->name)) - continue; + if (domain_cmd_map) { + size_t iter = 0; + void *item; + while (hashmap_iter(domain_cmd_map, &iter, &item)) { + const domain_cmd_entry *e = item; + if (!user_has_permit(c, e->name)) + continue; - resp_array_append_bulk(result, e->name); - resp_object *meta = resp_array_init(); - if (!meta) { resp_free(result); return 0; } - resp_array_append_bulk(meta, "summary"); - resp_array_append_bulk(meta, "UDP hole proxy command"); - resp_array_append_obj(result, meta); + resp_array_append_bulk(result, e->name); + resp_object *meta = resp_array_init(); + if (!meta) { resp_free(result); return 0; } + resp_array_append_bulk(meta, "summary"); + resp_array_append_bulk(meta, "UDP hole proxy command"); + resp_array_append_obj(result, meta); + } + } + + if (cmd_map) { + size_t iter = 0; + void *item; + while (hashmap_iter(cmd_map, &iter, &item)) { + const api_cmd_entry *e = item; + if (!is_builtin(e->name) && !user_has_permit(c, e->name)) + continue; + + resp_array_append_bulk(result, e->name); + resp_object *meta = resp_array_init(); + if (!meta) { resp_free(result); return 0; } + resp_array_append_bulk(meta, "summary"); + resp_array_append_bulk(meta, "UDP hole proxy command"); + resp_array_append_obj(result, meta); + } } char *out_buf = NULL; @@ -402,6 +446,44 @@ static void dispatch_command(api_client_t *c, char **args, int nargs) { for (char *p = args[0]; *p; p++) *p = (char)tolower((unsigned char)*p); + const domain_cmd_entry *dcmd = hashmap_get(domain_cmd_map, &(domain_cmd_entry){ .name = args[0] }); + if (dcmd) { + if (!is_builtin(args[0])) { + if (!user_has_permit(c, args[0])) { + api_write_err(c, "no permission"); + return; + } + } + + resp_object *domain_args = resp_array_init(); + if (!domain_args) return; + + for (int i = 0; i < nargs; i++) { + resp_array_append_bulk(domain_args, args[i]); + } + + resp_object *result = dcmd->func(domain_args); + resp_free(domain_args); + + if (!result) { + api_write_err(c, "command failed"); + return; + } + + char *out_buf = NULL; + size_t out_len = 0; + if (resp_serialize(result, &out_buf, &out_len) != 0 || !out_buf) { + resp_free(result); + api_write_err(c, "command failed"); + return; + } + resp_free(result); + + api_write_raw(c, out_buf, out_len); + free(out_buf); + return; + } + const api_cmd_entry *cmd = hashmap_get(cmd_map, &(api_cmd_entry){ .name = args[0] }); if (!cmd) { api_write_err(c, "unknown command"); @@ -497,9 +579,10 @@ PT_THREAD(api_server_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) init_builtins(); udata->server_fds = create_listen_socket(current_listen); if (!udata->server_fds) { + log_fatal("api: failed to listen on %s", current_listen); free(current_listen); current_listen = NULL; - PT_EXIT(pt); + exit(1); } } @@ -532,6 +615,10 @@ PT_THREAD(api_server_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) hashmap_free(cmd_map); cmd_map = NULL; } + if (domain_cmd_map) { + hashmap_free(domain_cmd_map); + domain_cmd_map = NULL; + } PT_END(pt); } diff --git a/src/interface/api/server.h b/src/interface/api/server.h @@ -5,14 +5,19 @@ #include <stdbool.h> #include "domain/scheduler.h" +#include "common/resp.h" struct api_client_state; typedef struct api_client_state api_client_t; +typedef resp_object *(*domain_cmd_fn)(resp_object *args); + PT_THREAD(api_server_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); void api_register_cmd(const char *name, char (*func)(api_client_t *, char **, int)); +void api_register_domain_cmd(const char *name, domain_cmd_fn func); + bool api_write_ok(api_client_t *c); bool api_write_err(api_client_t *c, const char *msg); bool api_write_array(api_client_t *c, size_t nitems); @@ -20,4 +25,4 @@ bool api_write_bulk_cstr(api_client_t *c, const char *s); bool api_write_bulk_int(api_client_t *c, int val); bool api_write_int(api_client_t *c, int val); -#endif // UDPHOLE_API_SERVER_H -\ No newline at end of file +#endif diff --git a/src/interface/cli/command/daemon.c b/src/interface/cli/command/daemon.c @@ -9,12 +9,30 @@ #include "rxi/log.h" #include "infrastructure/config.h" +#include "common/resp.h" #include "../common.h" #include "domain/scheduler.h" +#include "domain/config.h" #include "daemon.h" #include "interface/api/server.h" #include "domain/session.h" +static void register_domain_commands(void) { + api_register_domain_cmd("session.create", domain_session_create); + api_register_domain_cmd("session.list", domain_session_list); + api_register_domain_cmd("session.info", domain_session_info); + api_register_domain_cmd("session.destroy", domain_session_destroy); + api_register_domain_cmd("session.socket.create.listen", domain_socket_create_listen); + api_register_domain_cmd("session.socket.create.connect", domain_socket_create_connect); + api_register_domain_cmd("session.socket.destroy", domain_socket_destroy); + api_register_domain_cmd("session.forward.list", domain_forward_list); + api_register_domain_cmd("session.forward.create", domain_forward_create); + api_register_domain_cmd("session.forward.destroy", domain_forward_destroy); + api_register_domain_cmd("session.count", domain_session_count); + api_register_domain_cmd("system.load", domain_system_load); + log_info("udphole: registered session.* commands"); +} + static int do_daemonize(void) { pid_t pid = fork(); if (pid < 0) { @@ -67,6 +85,26 @@ int cli_cmd_daemon(int argc, const char **argv) { do_daemonize(); } + domain_config_init(); + + if (global_cfg) { + resp_object *cfg_sec = resp_map_get(global_cfg, "udphole"); + if (cfg_sec) { + const char *ports_str = resp_map_get_string(cfg_sec, "ports"); + if (ports_str) { + int port_low = 7000, port_high = 7999; + sscanf(ports_str, "%d-%d", &port_low, &port_high); + domain_config_set_ports(port_low, port_high); + } + const char *advertise = resp_map_get_string(cfg_sec, "advertise"); + if (advertise) { + domain_config_set_advertise(advertise); + } + } + } + + register_domain_commands(); + log_info("udphole: starting daemon"); domain_schedmod_pt_create(api_server_pt, NULL); diff --git a/src/interface/cli/main.c b/src/interface/cli/main.c @@ -13,14 +13,13 @@ extern "C" { #include "interface/cli/common.h" #include "interface/cli/command/list_commands.h" +#include "interface/cli/command/daemon.h" #include "infrastructure/config.h" #ifdef __cplusplus } #endif -extern void domain_setup(void); - #define INCBIN_SILENCE_BITCODE_WARNING #include "graphitemaster/incbin.h" INCTXT(License, "LICENSE.md"); @@ -175,7 +174,11 @@ int main(int argc, const char **argv) { cli_cmd_list_commands ); - domain_setup(); + cli_register_command( + "daemon", + "Run the udphole daemon", + cli_cmd_daemon + ); struct argparse argparse; struct argparse_option options[] = {