udphole

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

commit c279042febc43e585c879eb358e10c036db1f2e6
parent f599de3088433d4ce5a8837f782cafc80753941b
Author: Robin Bron <robin.bron@yourhosting.nl>
Date:   Sun,  1 Mar 2026 05:42:13 +0100

Restructure

Diffstat:
MMakefile | 29+++--------------------------
Mconfig.ini.example | 1-
Mpackage.ini | 1-
Dsrc/AppModule/api/server.c | 646-------------------------------------------------------------------------------
Dsrc/AppModule/api/server.h | 23-----------------------
Dsrc/AppModule/command/daemon.c | 78------------------------------------------------------------------------------
Dsrc/AppModule/command/daemon.h | 6------
Dsrc/AppModule/rtp/server.c | 831-------------------------------------------------------------------------------
Dsrc/AppModule/rtp/server.h | 11-----------
Dsrc/AppModule/setup.c | 11-----------
Dsrc/AppModule/setup.h | 6------
Dsrc/CliModule/command/list_commands.c | 43-------------------------------------------
Dsrc/CliModule/command/list_commands.h | 6------
Dsrc/CliModule/common.c | 79-------------------------------------------------------------------------------
Dsrc/CliModule/common.h | 41-----------------------------------------
Dsrc/CliModule/execute_command.c | 16----------------
Dsrc/CliModule/execute_command.h | 6------
Dsrc/CliModule/register_command.c | 18------------------
Dsrc/CliModule/register_command.h | 6------
Dsrc/CliModule/setup.c | 11-----------
Dsrc/CliModule/setup.h | 6------
Dsrc/RespModule/resp.c | 335-------------------------------------------------------------------------------
Dsrc/RespModule/resp.h | 61-------------------------------------------------------------
Dsrc/SchedulerModule/protothreads.h | 409-------------------------------------------------------------------------------
Dsrc/SchedulerModule/scheduler.c | 138-------------------------------------------------------------------------------
Dsrc/SchedulerModule/scheduler.h | 30------------------------------
Dsrc/common/digest_auth.c | 54------------------------------------------------------
Dsrc/common/digest_auth.h | 27---------------------------
Dsrc/common/hexdump.c | 35-----------------------------------
Dsrc/common/hexdump.h | 12------------
Dsrc/common/md5.c | 147-------------------------------------------------------------------------------
Dsrc/common/md5.h | 20--------------------
Msrc/common/socket_util.h | 4++--
Dsrc/common/util/hex.c | 31-------------------------------
Dsrc/common/util/hex.h | 12------------
Dsrc/config.c | 62--------------------------------------------------------------
Dsrc/config.h | 15---------------
Asrc/domain/protothreads.h | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/domain/scheduler.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/domain/scheduler.h | 31+++++++++++++++++++++++++++++++
Asrc/domain/session.c | 825+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/domain/session.h | 11+++++++++++
Asrc/infrastructure/config.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/infrastructure/config.h | 16++++++++++++++++
Asrc/infrastructure/resp.c | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/infrastructure/resp.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/interface/api/server.c | 616+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/interface/api/server.h | 24++++++++++++++++++++++++
Asrc/interface/cli/command/daemon.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/interface/cli/command/daemon.h | 7+++++++
Asrc/interface/cli/command/list_commands.c | 34++++++++++++++++++++++++++++++++++
Asrc/interface/cli/command/list_commands.h | 7+++++++
Asrc/interface/cli/common.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/interface/cli/common.h | 34++++++++++++++++++++++++++++++++++
Asrc/interface/cli/main.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/main.c | 278-------------------------------------------------------------------------------
56 files changed, 2719 insertions(+), 3540 deletions(-)

diff --git a/Makefile b/Makefile @@ -94,7 +94,7 @@ doc/license.man: LICENSE.md @mkdir -p doc pandoc LICENSE.md -f markdown -t man --standalone=false -o doc/license.man -$(BIN).1: doc/upbx.1.in doc/cli_doc.man doc/license.man +$(BIN).1: doc/udphole.1.in doc/cli_doc.man doc/license.man VERSION=$(VERSION) envsubst '$$VERSION' < doc/upbx.1.in | sed '/__COMMANDS_MAN__/r doc/cli_doc.man' | sed '/__COMMANDS_MAN__/d' | sed '/__LICENSE_MAN__/r doc/license.man' | sed '/__LICENSE_MAN__/d' > $(BIN).1 # .cc.o: @@ -106,37 +106,14 @@ $(BIN).1: doc/upbx.1.in doc/cli_doc.man doc/license.man $(BIN): $(OBJ) ${CC} ${OBJ} ${CFLAGS} ${LDFLAGS} -o $@ -# ---- Test targets (finwo/assert) ---- -TEST_BINS:=test_md5 test_sdp_parse test_sip_parse test_config test_resp test_digest_auth - -test_md5: src/test/test_md5.o src/common/md5.o - $(CC) $^ $(CFLAGS) -o $@ - -test_sdp_parse: src/test/test_sdp_parse.o src/AppModule/util/sdp_parse.o - $(CC) $^ $(CFLAGS) -o $@ - -test_sip_parse: src/test/test_sip_parse.o src/AppModule/sip_parse.o src/AppModule/pbx/registration.o src/config.o src/RespModule/resp.o src/PluginModule/plugin.o lib/benhoyt/inih/ini.o lib/cofyc/argparse/argparse.o lib/rxi/log/src/log.o src/common/socket_util.o - $(CC) $^ $(CFLAGS) -o $@ - -test_config: src/test/test_config.o src/config.o src/RespModule/resp.o lib/benhoyt/inih/ini.o lib/rxi/log/src/log.o - $(CC) $^ $(CFLAGS) -o $@ - -test_resp: src/test/test_resp.o src/RespModule/resp.o - $(CC) $^ $(CFLAGS) -o $@ - -test_digest_auth: src/test/test_digest_auth.o src/common/digest_auth.o src/common/md5.o - $(CC) $^ $(CFLAGS) -o $@ - .PHONY: test -test: +test: $(BIN) @node test/basic-forwarding.js @sleep 2 @node test/listen-relearn.js .PHONY: clean clean: - rm -rf $(BIN) $(BIN).1 $(TEST_BINS) + rm -rf $(BIN) $(BIN).1 rm -rf $(OBJ) - rm -rf $(patsubst %,src/test/%.o,$(TEST_BINS:test_%=test_%)) - rm -rf src/test/*.o rm -rf doc/cli_doc.md doc/cli_doc.man doc/license.man diff --git a/config.ini.example b/config.ini.example @@ -1,5 +1,4 @@ [udphole] -mode = builtin ports = 7000-7999 listen = :12345 diff --git a/package.ini b/package.ini @@ -1,7 +1,6 @@ [dependencies] benhoyt/inih=master cofyc/argparse=master -finwo/assert=main graphitemaster/incbin=main rxi/log=master tidwall/hashmap=master diff --git a/src/AppModule/api/server.c b/src/AppModule/api/server.c @@ -1,646 +0,0 @@ -/* - * Generic RESP2 API server: TCP listener, connection management, RESP2 - * parsing/writing, command hashmap, authentication with per-user permit - * checking, and built-in commands (auth, ping, quit, command). - * - * Runs as a protothread in the main select() loop. - */ - -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <ctype.h> -#include <unistd.h> -#include <errno.h> -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <netdb.h> - -#include "rxi/log.h" -#include "tidwall/hashmap.h" -#include "SchedulerModule/protothreads.h" -#include "SchedulerModule/scheduler.h" -#include "common/socket_util.h" -#include "config.h" -#include "server.h" -#include "RespModule/resp.h" - -struct pt_task; -PT_THREAD(api_client_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); - -/* Constants */ - -#define API_MAX_CLIENTS 8 -#define READ_BUF_SIZE 4096 -#define WRITE_BUF_INIT 4096 -#define MAX_ARGS 32 - -/* Per-client connection state (stored in pt udata) */ - -struct api_client_state { - int fd; - int *fds; - int *ready_fds; - int ready_fd; - char *username; - char rbuf[READ_BUF_SIZE]; - size_t rlen; - char *wbuf; - size_t wlen; - size_t wcap; -}; - -typedef struct api_client_state api_client_t; - -/* Command entry */ - -typedef struct { - const char *name; - char (*func)(api_client_t *c, char **args, int nargs); -} api_cmd_entry; - -/* Module state */ - -static char *current_listen = NULL; -static struct hashmap *cmd_map = NULL; - -typedef struct { - int *server_fds; - int *ready_fds; -} api_server_udata_t; - -/* Write helpers */ - -bool api_write_raw(api_client_t *c, const void *data, size_t len) { - if (c->fd < 0) return false; - if (c->wlen + len > c->wcap) { - size_t need = c->wlen + len; - size_t ncap = c->wcap ? c->wcap : WRITE_BUF_INIT; - while (ncap < need) ncap *= 2; - char *nb = realloc(c->wbuf, ncap); - if (!nb) return false; - c->wbuf = nb; - c->wcap = ncap; - } - memcpy(c->wbuf + c->wlen, data, len); - c->wlen += len; - return true; -} - -bool api_write_cstr(api_client_t *c, const char *s) { - return api_write_raw(c, s, strlen(s)); -} - -bool api_write_ok(api_client_t *c) { - return api_write_cstr(c, "+OK\r\n"); -} - -bool api_write_err(api_client_t *c, const char *msg) { - if (!api_write_cstr(c, "-ERR ")) return false; - if (!api_write_cstr(c, msg)) return false; - return api_write_cstr(c, "\r\n"); -} - -bool api_write_nil(api_client_t *c) { - return api_write_cstr(c, "$-1\r\n"); -} - -bool api_write_int(api_client_t *c, int value) { - char buf[32]; - snprintf(buf, sizeof(buf), ":%d\r\n", value); - return api_write_cstr(c, buf); -} - -bool api_write_array(api_client_t *c, size_t nitems) { - char buf[32]; - snprintf(buf, sizeof(buf), "*%zu\r\n", nitems); - return api_write_cstr(c, buf); -} - -bool api_write_bulk_cstr(api_client_t *c, const char *s) { - if (!s) return api_write_nil(c); - size_t len = strlen(s); - char prefix[32]; - snprintf(prefix, sizeof(prefix), "$%zu\r\n", len); - if (!api_write_cstr(c, prefix)) return false; - if (!api_write_raw(c, s, len)) return false; - return api_write_cstr(c, "\r\n"); -} - -bool api_write_bulk_int(api_client_t *c, int val) { - char buf[32]; - snprintf(buf, sizeof(buf), "%d", val); - return api_write_bulk_cstr(c, buf); -} - -/* Client lifecycle */ - -static void client_close(api_client_t *c) { - if (c->fd >= 0) { - close(c->fd); - c->fd = -1; - } - free(c->wbuf); - c->wbuf = NULL; - c->wlen = c->wcap = 0; - c->rlen = 0; - free(c->username); - c->username = NULL; -} - -static void client_flush(api_client_t *c) { - if (c->fd < 0 || c->wlen == 0) return; - ssize_t n = send(c->fd, c->wbuf, c->wlen, 0); - if (n > 0) { - if ((size_t)n < c->wlen) - memmove(c->wbuf, c->wbuf + n, c->wlen - (size_t)n); - c->wlen -= (size_t)n; - } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - client_close(c); - } -} - -/* RESP2 inline (telnet) parser */ - -static int parse_inline(const char *line, size_t len, char **args, int max_args) { - int nargs = 0; - const char *p = line; - const char *end = line + len; - while (p < end && nargs < max_args) { - while (p < end && (*p == ' ' || *p == '\t')) p++; - if (p >= end) break; - const char *start; - const char *tok_end; - if (*p == '"' || *p == '\'') { - char quote = *p++; - start = p; - while (p < end && *p != quote) p++; - tok_end = p; - if (p < end) p++; - } else { - start = p; - while (p < end && *p != ' ' && *p != '\t') p++; - tok_end = p; - } - size_t tlen = (size_t)(tok_end - start); - char *arg = malloc(tlen + 1); - if (!arg) return -1; - memcpy(arg, start, tlen); - arg[tlen] = '\0'; - args[nargs++] = arg; - } - return nargs; -} - -/* RESP2 multibulk parser */ - -static int parse_resp_command(api_client_t *c, char **args, int max_args, int *nargs) { - *nargs = 0; - if (c->rlen == 0) return 0; - - if (c->rbuf[0] != '*') { - char *nl = memchr(c->rbuf, '\n', c->rlen); - if (!nl) return 0; - size_t line_len = (size_t)(nl - c->rbuf); - size_t trim = line_len; - if (trim > 0 && c->rbuf[trim - 1] == '\r') trim--; - int n = parse_inline(c->rbuf, trim, args, max_args); - if (n < 0) return -1; - *nargs = n; - size_t consumed = line_len + 1; - c->rlen -= consumed; - if (c->rlen > 0) memmove(c->rbuf, c->rbuf + consumed, c->rlen); - return n > 0 ? 1 : 0; - } - - size_t pos = 0; - char *nl = memchr(c->rbuf + pos, '\n', c->rlen - pos); - if (!nl) return 0; - int count = atoi(c->rbuf + 1); - if (count <= 0 || count > max_args) return -1; - pos = (size_t)(nl - c->rbuf) + 1; - - for (int i = 0; i < count; i++) { - if (pos >= c->rlen) return 0; - if (c->rbuf[pos] != '$') return -1; - nl = memchr(c->rbuf + pos, '\n', c->rlen - pos); - if (!nl) return 0; - int blen = atoi(c->rbuf + pos + 1); - if (blen < 0) return -1; - size_t hdr_end = (size_t)(nl - c->rbuf) + 1; - if (hdr_end + (size_t)blen + 2 > c->rlen) return 0; - char *arg = malloc((size_t)blen + 1); - if (!arg) return -1; - memcpy(arg, c->rbuf + hdr_end, (size_t)blen); - arg[blen] = '\0'; - args[i] = arg; - pos = hdr_end + (size_t)blen + 2; - } - - *nargs = count; - c->rlen -= pos; - if (c->rlen > 0) memmove(c->rbuf, c->rbuf + pos, c->rlen); - return 1; -} - -/* Permission checking */ - -/* Check if a command name matches a single permit pattern. - * Supports: "*" matches everything, "foo.*" matches "foo.anything", - * exact match otherwise. */ -static bool permit_matches(const char *pattern, const char *cmd) { - size_t plen = strlen(pattern); - if (plen == 1 && pattern[0] == '*') - return true; - if (plen >= 2 && pattern[plen - 1] == '*') { - return strncasecmp(pattern, cmd, plen - 1) == 0; - } - return strcasecmp(pattern, cmd) == 0; -} - -/* Check if client has permission for a command. Uses live config: api:<username> and api:* permit keys. */ -static bool user_has_permit(api_client_t *c, const char *cmd) { - char section[128]; - const char *uname = (c->username && c->username[0]) ? c->username : "*"; - snprintf(section, sizeof(section), "user:%s", uname); - resp_object *sec = resp_map_get(global_cfg, section); - if (sec && sec->type == RESPT_ARRAY) { - for (size_t i = 0; i < sec->u.arr.n; i += 2) { - if (i + 1 < sec->u.arr.n) { - resp_object *key = &sec->u.arr.elem[i]; - resp_object *val = &sec->u.arr.elem[i + 1]; - if (key->type == RESPT_BULK && key->u.s && strcmp(key->u.s, "permit") == 0) { - if (val->type == RESPT_ARRAY) { - for (size_t j = 0; j < val->u.arr.n; j++) { - resp_object *p = &val->u.arr.elem[j]; - if (p->type == RESPT_BULK && p->u.s && permit_matches(p->u.s, cmd)) - return true; - } - } else if (val->type == RESPT_BULK && val->u.s && permit_matches(val->u.s, cmd)) { - return true; - } - } - } - } - } - if (strcmp(uname, "*") != 0) { - resp_object *anon = resp_map_get(global_cfg, "user:*"); - if (anon && anon->type == RESPT_ARRAY) { - for (size_t i = 0; i < anon->u.arr.n; i += 2) { - if (i + 1 < anon->u.arr.n) { - resp_object *key = &anon->u.arr.elem[i]; - resp_object *val = &anon->u.arr.elem[i + 1]; - if (key->type == RESPT_BULK && key->u.s && strcmp(key->u.s, "permit") == 0) { - if (val->type == RESPT_ARRAY) { - for (size_t j = 0; j < val->u.arr.n; j++) { - resp_object *p = &val->u.arr.elem[j]; - if (p->type == RESPT_BULK && p->u.s && permit_matches(p->u.s, cmd)) - return true; - } - } else if (val->type == RESPT_BULK && val->u.s && permit_matches(val->u.s, cmd)) { - return true; - } - } - } - } - } - } - return false; -} - -/* Hashmap callbacks */ - -static uint64_t cmd_hash(const void *item, uint64_t seed0, uint64_t seed1) { - const api_cmd_entry *cmd = item; - return hashmap_sip(cmd->name, strlen(cmd->name), seed0, seed1); -} - -static int cmd_compare(const void *a, const void *b, void *udata) { - (void)udata; - const api_cmd_entry *ca = a; - const api_cmd_entry *cb = b; - return strcasecmp(ca->name, cb->name); -} - -/* Public: register a command */ - -void api_register_cmd(const char *name, char (*func)(api_client_t *, char **, int)) { - if (!cmd_map) - cmd_map = hashmap_new(sizeof(api_cmd_entry), 0, 0, 0, cmd_hash, cmd_compare, NULL, NULL); - hashmap_set(cmd_map, &(api_cmd_entry){ .name = name, .func = func }); - log_trace("api: registered 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)"); - return 1; - } - const char *uname = args[1]; - const char *pass = args[2]; - char section[128]; - snprintf(section, sizeof(section), "user:%s", uname); - resp_object *sec = resp_map_get(global_cfg, section); - const char *secret = sec ? resp_map_get_string(sec, "secret") : NULL; - if (secret && pass && strcmp(secret, pass) == 0) { - free(c->username); - c->username = strdup(uname); - if (c->username) { - log_debug("api: client authenticated as '%s'", uname); - return api_write_ok(c) ? 1 : 0; - } - } - return api_write_err(c, "invalid credentials") ? 1 : 0; -} - -static char cmdPING(api_client_t *c, char **args, int nargs) { - (void)args; - if (nargs == 1) - return api_write_cstr(c, "+PONG\r\n") ? 1 : 0; - if (nargs == 2) - return api_write_bulk_cstr(c, args[1]) ? 1 : 0; - return api_write_err(c, "wrong number of arguments for 'ping' command") ? 1 : 0; -} - -static char cmdQUIT(api_client_t *c, char **args, int nargs) { - (void)args; (void)nargs; - api_write_ok(c); - return 0; -} - -static bool is_builtin(const char *name); - -static char cmdCOMMAND(api_client_t *c, char **args, int nargs) { - (void)args; - if (!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; - - 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; - size_t out_len = 0; - if (resp_serialize(result, &out_buf, &out_len) != 0 || !out_buf) { - resp_free(result); - return 0; - } - resp_free(result); - - api_write_raw(c, out_buf, out_len); - free(out_buf); - return 1; -} - -/* Command dispatch */ - -static void init_builtins(void) { - api_register_cmd("auth", cmdAUTH); - api_register_cmd("ping", cmdPING); - api_register_cmd("quit", cmdQUIT); - api_register_cmd("command", cmdCOMMAND); -} - -/* Check if a command is a built-in that bypasses auth/permit checks */ -static bool is_builtin(const char *name) { - return (strcasecmp(name, "auth") == 0 || - strcasecmp(name, "ping") == 0 || - strcasecmp(name, "quit") == 0 || - strcasecmp(name, "command") == 0); -} - -static void dispatch_command(api_client_t *c, char **args, int nargs) { - if (nargs <= 0) return; - - for (char *p = args[0]; *p; p++) *p = (char)tolower((unsigned char)*p); - - const api_cmd_entry *cmd = hashmap_get(cmd_map, &(api_cmd_entry){ .name = args[0] }); - if (!cmd) { - api_write_err(c, "unknown command"); - return; - } - - if (!is_builtin(args[0])) { - if (!user_has_permit(c, args[0])) { - api_write_err(c, "no permission"); - return; - } - } - - char result = cmd->func(c, args, nargs); - if (!result) { - client_flush(c); - client_close(c); - } -} - -/* TCP listener */ - -static int *create_listen_socket(const char *listen_addr) { - const char *default_port = "6379"; - resp_object *api_sec = resp_map_get(global_cfg, "udphole"); - if (api_sec) { - const char *cfg_port = resp_map_get_string(api_sec, "port"); - if (cfg_port && cfg_port[0]) default_port = cfg_port; - } - int *fds = tcp_listen(listen_addr, NULL, default_port); - if (!fds) { - return NULL; - } - log_info("api: listening on %s", listen_addr); - return fds; -} - -static void handle_accept(int ready_fd) { - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(addr); - int fd = accept(ready_fd, (struct sockaddr *)&addr, &addrlen); - if (fd < 0) return; - set_socket_nonblocking(fd, 1); - - api_client_t *state = calloc(1, sizeof(*state)); - if (!state) { - const char *msg = "-ERR out of memory\r\n"; - send(fd, msg, strlen(msg), 0); - close(fd); - return; - } - state->fd = fd; - - schedmod_pt_create(api_client_pt, state); - log_trace("api: accepted connection, spawned client pt"); -} - -PT_THREAD(api_server_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { - api_server_udata_t *udata = task->udata; - log_trace("api_server: protothread entry"); - PT_BEGIN(pt); - - if (!udata) { - udata = calloc(1, sizeof(api_server_udata_t)); - if (!udata) { - PT_EXIT(pt); - } - task->udata = udata; - } - - resp_object *api_sec = resp_map_get(global_cfg, "udphole"); - const char *listen_str = api_sec ? resp_map_get_string(api_sec, "listen") : NULL; - if (!listen_str || !listen_str[0]) { - log_info("api: no listen address configured, API server disabled"); - PT_EXIT(pt); - } - - if (!current_listen) { - current_listen = strdup(listen_str); - if (!current_listen) { - PT_EXIT(pt); - } - init_builtins(); - udata->server_fds = create_listen_socket(current_listen); - if (!udata->server_fds) { - free(current_listen); - current_listen = NULL; - PT_EXIT(pt); - } - } - - for (;;) { - (void)timestamp; - if (udata->server_fds && udata->server_fds[0] > 0) { - PT_WAIT_UNTIL(pt, schedmod_has_data(udata->server_fds, &udata->ready_fds) > 0); - if (udata->ready_fds && udata->ready_fds[0] > 0) { - for (int i = 1; i <= udata->ready_fds[0]; i++) { - handle_accept(udata->ready_fds[i]); - } - } - if (udata->ready_fds) free(udata->ready_fds); - udata->ready_fds = NULL; - } else { - PT_YIELD(pt); - } - } - if (udata->server_fds) { - for (int i = 1; i <= udata->server_fds[0]; i++) { - close(udata->server_fds[i]); - } - free(udata->server_fds); - } - free(udata->ready_fds); - free(udata); - free(current_listen); - current_listen = NULL; - if (cmd_map) { - hashmap_free(cmd_map); - cmd_map = NULL; - } - - PT_END(pt); -} - -PT_THREAD(api_client_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { - (void)timestamp; - api_client_t *state = task->udata; - - log_trace("api_client: protothread entry fd=%d", state->fd); - PT_BEGIN(pt); - - state->fds = malloc(sizeof(int) * 2); - if (!state->fds) { - free(state); - PT_EXIT(pt); - } - state->fds[0] = 1; - state->fds[1] = state->fd; - - for (;;) { - state->ready_fds = NULL; - PT_WAIT_UNTIL(pt, schedmod_has_data(state->fds, &state->ready_fds) > 0); - - state->ready_fd = -1; - if (state->ready_fds && state->ready_fds[0] > 0) { - for (int i = 1; i <= state->ready_fds[0]; i++) { - if (state->ready_fds[i] == state->fd) { - state->ready_fd = state->fd; - break; - } - } - } - free(state->ready_fds); - state->ready_fds = NULL; - - char buf[1]; - ssize_t n = recv(state->fd, buf, 1, MSG_PEEK); - if (n <= 0) { - break; - } - - resp_object *cmd = resp_read(state->fd); - if (!cmd) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - PT_YIELD(pt); - continue; - } - break; - } - - if (cmd->type != RESPT_ARRAY || cmd->u.arr.n == 0) { - resp_free(cmd); - api_write_err(state, "Protocol error"); - client_flush(state); - continue; - } - - char *args[MAX_ARGS]; - int nargs = 0; - for (size_t i = 0; i < cmd->u.arr.n && nargs < MAX_ARGS; i++) { - resp_object *elem = &cmd->u.arr.elem[i]; - if (elem->type == RESPT_BULK && elem->u.s) { - args[nargs++] = elem->u.s; - elem->u.s = NULL; - } else if (elem->type == RESPT_SIMPLE) { - args[nargs++] = elem->u.s ? elem->u.s : ""; - } - } - - if (nargs > 0) { - dispatch_command(state, args, nargs); - } - - for (int j = 0; j < nargs; j++) { - free(args[j]); - } - resp_free(cmd); - - client_flush(state); - - if (state->fd < 0) break; - } - - if (state->fd >= 0) { - close(state->fd); - } - free(state->fds); - free(state->wbuf); - free(state->username); - free(state); - - PT_END(pt); -} diff --git a/src/AppModule/api/server.h b/src/AppModule/api/server.h @@ -1,23 +0,0 @@ -#ifndef __APPMODULE_API_SERVER_H__ -#define __APPMODULE_API_SERVER_H__ - -#include <stdint.h> -#include <stdbool.h> - -#include "SchedulerModule/scheduler.h" - -struct api_client_state; -typedef struct api_client_state api_client_t; - -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)); - -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); -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 // __APPMODULE_API_SERVER_H__ diff --git a/src/AppModule/command/daemon.c b/src/AppModule/command/daemon.c @@ -1,78 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/stat.h> - -#include "cofyc/argparse.h" -#include "rxi/log.h" - -#include "config.h" -#include "CliModule/common.h" -#include "SchedulerModule/scheduler.h" -#include "AppModule/command/daemon.h" -#include "AppModule/api/server.h" -#include "AppModule/rtp/server.h" - -static int do_daemonize(void) { - pid_t pid = fork(); - if (pid < 0) { - log_fatal("fork: %m"); - return -1; - } - if (pid > 0) - _exit(0); - if (setsid() < 0) { - log_fatal("setsid: %m"); - _exit(1); - } - pid = fork(); - if (pid < 0) { - log_fatal("fork: %m"); - _exit(1); - } - if (pid > 0) - _exit(0); - if (chdir("/") != 0) {} - int fd; - for (fd = 0; fd < 3; fd++) - (void)close(fd); - fd = open("/dev/null", O_RDWR); - if (fd >= 0) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > 2) - close(fd); - } - return 0; -} - -int appmodule_cmd_daemon(int argc, const char **argv) { - int daemonize_flag = 0; - int no_daemonize_flag = 0; - - struct argparse argparse; - struct argparse_option options[] = { - OPT_HELP(), - OPT_BOOLEAN('d', "daemonize", &daemonize_flag, "run in background", NULL, 0, 0), - OPT_BOOLEAN('D', "no-daemonize", &no_daemonize_flag, "force foreground", NULL, 0, 0), - OPT_END(), - }; - argparse_init(&argparse, options, (const char *const[]){"udphole daemon", NULL}, ARGPARSE_STOP_AT_NON_OPTION); - argparse_parse(&argparse, argc, argv); - - if (!no_daemonize_flag && (daemonize_flag || 0)) { - do_daemonize(); - } - - log_info("udphole: starting daemon"); - - schedmod_pt_create(api_server_pt, NULL); - schedmod_pt_create(udphole_manager_pt, NULL); - - log_info("udphole: daemon started"); - - return schedmod_main(); -} diff --git a/src/AppModule/command/daemon.h b/src/AppModule/command/daemon.h @@ -1,6 +0,0 @@ -#ifndef __APPMODULE_COMMAND_DAEMON_H__ -#define __APPMODULE_COMMAND_DAEMON_H__ - -int appmodule_cmd_daemon(int argc, const char **argv); - -#endif diff --git a/src/AppModule/rtp/server.c b/src/AppModule/rtp/server.c @@ -1,831 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <stdarg.h> -#include <unistd.h> -#include <errno.h> -#include <fcntl.h> -#include <time.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <sys/stat.h> -#include <netinet/in.h> -#include <arpa/inet.h> - -#include "rxi/log.h" -#include "SchedulerModule/protothreads.h" -#include "SchedulerModule/scheduler.h" -#include "common/socket_util.h" -#include "config.h" -#include "tidwall/hashmap.h" -#include "server.h" -#include "AppModule/api/server.h" - -#define RTP_SESSION_HASH_SIZE 256 -#define RTP_BUFFER_SIZE 4096 -#define DEFAULT_IDLE_EXPIRY 60 - -typedef struct rtp_socket { - char *socket_id; - int *fds; - int local_port; - int mode; - struct sockaddr_storage remote_addr; - socklen_t remote_addrlen; - int learned_valid; - struct sockaddr_storage learned_addr; - socklen_t learned_addrlen; -} rtp_socket_t; - -typedef struct rtp_forward { - char *src_socket_id; - char *dst_socket_id; -} rtp_forward_t; - -typedef struct rtp_session { - char *session_id; - time_t idle_expiry; - time_t created; - time_t last_activity; - rtp_socket_t **sockets; - size_t sockets_count; - rtp_forward_t *forwards; - size_t forwards_count; - int marked_for_deletion; - int *ready_fds; - int *all_fds; - struct pt pt; - struct pt_task *task; -} rtp_session_t; - -static rtp_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 rtp_session_t *find_session(const char *session_id) { - for (size_t i = 0; i < sessions_count; i++) { - if (strcmp(sessions[i]->session_id, session_id) == 0) { - return sessions[i]; - } - } - return NULL; -} - -static uint64_t socket_hash(const void *item, uint64_t seed0, uint64_t seed1) { - const rtp_socket_t *s = item; - return hashmap_sip(s->socket_id, strlen(s->socket_id), seed0, seed1); -} - -static int socket_compare(const void *a, const void *b, void *udata) { - (void)udata; - const rtp_socket_t *sa = a; - const rtp_socket_t *sb = b; - return strcmp(sa->socket_id, sb->socket_id); -} - -static rtp_socket_t *find_socket(rtp_session_t *s, const char *socket_id) { - if (!s || !s->sockets || !socket_id) return NULL; - for (size_t i = 0; i < s->sockets_count; i++) { - if (s->sockets[i] && strcmp(s->sockets[i]->socket_id, socket_id) == 0) { - return s->sockets[i]; - } - } - return NULL; -} - -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; - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(port); - - int udp_fd = socket(AF_INET, SOCK_DGRAM, 0); - if (udp_fd < 0) continue; - int ok = (bind(udp_fd, (struct sockaddr *)&addr, sizeof(addr)) == 0); - close(udp_fd); - if (!ok) continue; - - return port; - } - return 0; -} - -static int parse_ip_addr(const char *ip_str, int port, struct sockaddr_storage *addr, socklen_t *addrlen) { - memset(addr, 0, sizeof(*addr)); - - struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; - if (inet_pton(AF_INET, ip_str, &addr4->sin_addr) == 1) { - addr4->sin_family = AF_INET; - addr4->sin_port = htons(port); - *addrlen = sizeof(*addr4); - return 0; - } - - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; - if (inet_pton(AF_INET6, ip_str, &addr6->sin6_addr) == 1) { - addr6->sin6_family = AF_INET6; - addr6->sin6_port = htons(port); - *addrlen = sizeof(*addr6); - return 0; - } - - return -1; -} - -static void close_socket(rtp_socket_t *sock) { - if (!sock || !sock->fds) return; - for (int i = 1; i <= sock->fds[0]; i++) { - if (sock->fds[i] >= 0) { - close(sock->fds[i]); - } - } - free(sock->fds); - sock->fds = NULL; -} - -static void free_socket(rtp_socket_t *sock) { - if (!sock) return; - close_socket(sock); - free(sock->socket_id); - free(sock); -} - -static void destroy_session(rtp_session_t *s) { - if (!s) return; - s->marked_for_deletion = 1; - for (size_t i = 0; i < sessions_count; i++) { - if (sessions[i] == s) { - sessions[i] = NULL; - break; - } - } -} - -static rtp_session_t *create_session(const char *session_id, int idle_expiry) { - const rtp_session_t *cs = find_session(session_id); - if (cs) return (rtp_session_t *)cs; - - rtp_session_t *s = calloc(1, sizeof(*s)); - if (!s) return NULL; - - s->session_id = strdup(session_id); - s->created = time(NULL); - s->last_activity = s->created; - s->idle_expiry = idle_expiry > 0 ? idle_expiry : DEFAULT_IDLE_EXPIRY; - - sessions = realloc(sessions, sizeof(rtp_session_t *) * (sessions_count + 1)); - sessions[sessions_count++] = s; - - return s; -} - -static void cleanup_expired_sessions(void) { - if (!sessions) return; - time_t now = time(NULL); - - for (size_t i = 0; i < sessions_count; i++) { - rtp_session_t *s = sessions[i]; - if (!s) continue; - if (now - s->last_activity > s->idle_expiry) { - log_debug("udphole: session %s expired (idle %ld > expiry %ld)", - s->session_id, (long)(now - s->last_activity), (long)s->idle_expiry); - destroy_session(s); - } - } -} - -static int add_forward(rtp_session_t *s, const char *src_id, const char *dst_id) { - for (size_t i = 0; i < s->forwards_count; i++) { - if (strcmp(s->forwards[i].src_socket_id, src_id) == 0 && - strcmp(s->forwards[i].dst_socket_id, dst_id) == 0) { - return 0; - } - } - - rtp_forward_t *new_forwards = realloc(s->forwards, sizeof(rtp_forward_t) * (s->forwards_count + 1)); - if (!new_forwards) return -1; - s->forwards = new_forwards; - - s->forwards[s->forwards_count].src_socket_id = strdup(src_id); - s->forwards[s->forwards_count].dst_socket_id = strdup(dst_id); - s->forwards_count++; - - return 0; -} - -static int remove_forward(rtp_session_t *s, const char *src_id, const char *dst_id) { - for (size_t i = 0; i < s->forwards_count; i++) { - if (strcmp(s->forwards[i].src_socket_id, src_id) == 0 && - strcmp(s->forwards[i].dst_socket_id, dst_id) == 0) { - free(s->forwards[i].src_socket_id); - free(s->forwards[i].dst_socket_id); - for (size_t j = i; j < s->forwards_count - 1; j++) { - s->forwards[j] = s->forwards[j + 1]; - } - s->forwards_count--; - return 0; - } - } - return -1; -} - -static rtp_socket_t *create_listen_socket(rtp_session_t *sess, const char *socket_id) { - rtp_socket_t *existing = find_socket(sess, socket_id); - if (existing) return existing; - - int port = alloc_port(); - if (!port) { - log_error("udphole: no ports available"); - return NULL; - } - - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%d", port); - int *fds = udp_recv(port_str, NULL, NULL); - if (!fds || fds[0] == 0) { - log_error("udphole: failed to create UDP socket on port %d", port); - free(fds); - return NULL; - } - - rtp_socket_t *sock = calloc(1, sizeof(*sock)); - if (!sock) { - free(fds); - return NULL; - } - - sock->socket_id = strdup(socket_id); - sock->fds = fds; - sock->local_port = port; - sock->mode = 0; - sock->learned_valid = 0; - - sess->sockets = realloc(sess->sockets, sizeof(rtp_socket_t *) * (sess->sockets_count + 1)); - sess->sockets[sess->sockets_count++] = sock; - - log_debug("udphole: created listen socket %s in session %s on port %d", - socket_id, sess->session_id, port); - return sock; -} - -static rtp_socket_t *create_connect_socket(rtp_session_t *sess, const char *socket_id, - const char *ip, int port) { - rtp_socket_t *existing = find_socket(sess, socket_id); - if (existing) return existing; - - int local_port = alloc_port(); - if (!local_port) { - log_error("udphole: no ports available"); - return NULL; - } - - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%d", local_port); - int *fds = udp_recv(port_str, NULL, NULL); - if (!fds || fds[0] == 0) { - log_error("udphole: failed to create UDP socket on port %d", local_port); - free(fds); - return NULL; - } - - struct sockaddr_storage remote_addr; - socklen_t remote_addrlen; - if (parse_ip_addr(ip, port, &remote_addr, &remote_addrlen) != 0) { - log_error("udphole: invalid remote address %s:%d", ip, port); - free(fds); - return NULL; - } - - rtp_socket_t *sock = calloc(1, sizeof(*sock)); - if (!sock) { - free(fds); - return NULL; - } - - sock->socket_id = strdup(socket_id); - sock->fds = fds; - sock->local_port = local_port; - sock->mode = 1; - sock->remote_addr = remote_addr; - sock->remote_addrlen = remote_addrlen; - sock->learned_valid = 0; - - sess->sockets = realloc(sess->sockets, sizeof(rtp_socket_t *) * (sess->sockets_count + 1)); - sess->sockets[sess->sockets_count++] = sock; - - log_debug("udphole: created connect socket %s in session %s on port %d -> %s:%d", - socket_id, sess->session_id, local_port, ip, port); - return sock; -} - -static int destroy_socket(rtp_session_t *sess, const char *socket_id) { - rtp_socket_t *sock = find_socket(sess, socket_id); - if (!sock) return -1; - - for (size_t i = 0; i < sess->sockets_count; i++) { - if (sess->sockets[i] == sock) { - sess->sockets[i] = NULL; - break; - } - } - free_socket(sock); - - for (size_t i = 0; i < sess->forwards_count; ) { - if (strcmp(sess->forwards[i].src_socket_id, socket_id) == 0 || - strcmp(sess->forwards[i].dst_socket_id, socket_id) == 0) { - free(sess->forwards[i].src_socket_id); - free(sess->forwards[i].dst_socket_id); - for (size_t j = i; j < sess->forwards_count - 1; j++) { - sess->forwards[j] = sess->forwards[j + 1]; - } - sess->forwards_count--; - } else { - i++; - } - } - - return 0; -} - -static rtp_socket_t *find_socket_by_fd(rtp_session_t *s, int fd) { - if (!s || !s->sockets) return NULL; - for (size_t j = 0; j < s->sockets_count; j++) { - rtp_socket_t *sock = s->sockets[j]; - if (!sock || !sock->fds) continue; - for (int i = 1; i <= sock->fds[0]; i++) { - if (sock->fds[i] == fd) { - return sock; - } - } - } - return NULL; -} - -PT_THREAD(rtp_session_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { - rtp_session_t *s = task->udata; - - (void)timestamp; - PT_BEGIN(pt); - - char buffer[RTP_BUFFER_SIZE]; - - for (;;) { - if (s->marked_for_deletion) { - break; - } - - if (!s->sockets || s->sockets_count == 0) { - PT_YIELD(pt); - continue; - } - - s->all_fds = realloc(s->all_fds, sizeof(int) * (s->sockets_count * 2 + 1)); - if (!s->all_fds) { - PT_YIELD(pt); - continue; - } - s->all_fds[0] = 0; - - for (size_t j = 0; j < s->sockets_count; j++) { - rtp_socket_t *sock = s->sockets[j]; - if (!sock || !sock->fds) continue; - for (int i = 1; i <= sock->fds[0]; i++) { - s->all_fds[++s->all_fds[0]] = sock->fds[i]; - } - } - - if (s->all_fds[0] == 0) { - PT_YIELD(pt); - continue; - } - - PT_WAIT_UNTIL(pt, schedmod_has_data(s->all_fds, &s->ready_fds) > 0); - - if (!s->ready_fds || s->ready_fds[0] == 0) { - PT_YIELD(pt); - continue; - } - - for (int r = 1; r <= s->ready_fds[0]; r++) { - int ready_fd = s->ready_fds[r]; - - rtp_socket_t *src_sock = find_socket_by_fd(s, ready_fd); - if (!src_sock) continue; - - struct sockaddr_storage from_addr; - socklen_t from_len = sizeof(from_addr); - ssize_t n = recvfrom(ready_fd, buffer, sizeof(buffer) - 1, 0, - (struct sockaddr *)&from_addr, &from_len); - - if (n <= 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - log_warn("udphole: recvfrom error on socket %s: %s", - src_sock->socket_id, strerror(errno)); - } - continue; - } - - s->last_activity = time(NULL); - - if (src_sock->mode == 0 && !src_sock->learned_valid) { - src_sock->learned_addr = from_addr; - src_sock->learned_addrlen = from_len; - src_sock->learned_valid = 1; - log_debug("udphole: socket %s learned remote address", src_sock->socket_id); - } - - for (size_t i = 0; i < s->forwards_count; i++) { - if (strcmp(s->forwards[i].src_socket_id, src_sock->socket_id) != 0) { - continue; - } - - rtp_socket_t *dst_sock = find_socket(s, s->forwards[i].dst_socket_id); - if (!dst_sock || !dst_sock->fds || dst_sock->fds[0] == 0) continue; - - struct sockaddr *dest_addr = NULL; - socklen_t dest_addrlen = 0; - - if (dst_sock->mode == 1) { - dest_addr = (struct sockaddr *)&dst_sock->remote_addr; - dest_addrlen = dst_sock->remote_addrlen; - } else if (dst_sock->learned_valid) { - dest_addr = (struct sockaddr *)&dst_sock->learned_addr; - dest_addrlen = dst_sock->learned_addrlen; - } - - if (dest_addr && dest_addrlen > 0) { - int dst_fd = dst_sock->fds[1]; - ssize_t sent = sendto(dst_fd, buffer, n, 0, dest_addr, dest_addrlen); - if (sent < 0) { - log_warn("udphole: forward failed %s -> %s: %s", - src_sock->socket_id, dst_sock->socket_id, strerror(errno)); - } - } - } - } - - } - - log_debug("udphole: session %s protothread exiting", s->session_id); - - if (s->all_fds) { - free(s->all_fds); - s->all_fds = NULL; - } - if (s->ready_fds) { - free(s->ready_fds); - s->ready_fds = NULL; - } - - PT_END(pt); -} - -static void spawn_session_pt(rtp_session_t *s) { - s->task = (struct pt_task *)(intptr_t)schedmod_pt_create(rtp_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; - } - - const char *session_id = args[1]; - int idle_expiry = 0; - if (nargs >= 3 && args[2] && args[2][0] != '\0') { - idle_expiry = atoi(args[2]); - } - - rtp_session_t *s = create_session(session_id, idle_expiry); - if (!s) { - return api_write_err(c, "failed to create session") ? 1 : 0; - } - - spawn_session_pt(s); - return api_write_ok(c) ? 1 : 0; -} - -static char cmd_session_list(api_client_t *c, char **args, int nargs) { - (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; - - for (size_t i = 0; i < sessions_count; i++) { - rtp_session_t *s = sessions[i]; - if (!s) continue; - if (!api_write_bulk_cstr(c, s->session_id)) return 0; - } - - return 1; -} - -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; - } - - const char *session_id = args[1]; - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - size_t num_items = 8; - if (!api_write_array(c, num_items)) return 0; - - if (!api_write_bulk_cstr(c, "session_id")) return 0; - if (!api_write_bulk_cstr(c, s->session_id)) return 0; - - if (!api_write_bulk_cstr(c, "created")) return 0; - if (!api_write_bulk_int(c, (int)s->created)) return 0; - - if (!api_write_bulk_cstr(c, "last_activity")) return 0; - if (!api_write_bulk_int(c, (int)s->last_activity)) return 0; - - if (!api_write_bulk_cstr(c, "idle_expiry")) return 0; - if (!api_write_bulk_int(c, (int)s->idle_expiry)) return 0; - - if (!api_write_bulk_cstr(c, "sockets")) return 0; - if (!api_write_array(c, s->sockets_count)) return 0; - for (size_t i = 0; i < s->sockets_count; i++) { - rtp_socket_t *sock = s->sockets[i]; - if (!sock) continue; - if (!api_write_bulk_cstr(c, sock->socket_id)) return 0; - } - - if (!api_write_bulk_cstr(c, "forwards")) return 0; - if (!api_write_array(c, s->forwards_count * 2)) return 0; - 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; - } - - if (!api_write_bulk_cstr(c, "marked_for_deletion")) return 0; - if (!api_write_int(c, s->marked_for_deletion)) return 0; - - return 1; -} - -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; - } - - const char *session_id = args[1]; - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - destroy_session(s); - return api_write_ok(c) ? 1 : 0; -} - -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; - } - - const char *session_id = args[1]; - const char *socket_id = args[2]; - - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - rtp_socket_t *sock = create_listen_socket(s, socket_id); - if (!sock) { - return api_write_err(c, "failed to create socket") ? 1 : 0; - } - - 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; -} - -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; - } - - const char *session_id = args[1]; - const char *socket_id = args[2]; - const char *ip = args[3]; - int port = atoi(args[4]); - - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - rtp_socket_t *sock = create_connect_socket(s, socket_id, ip, port); - if (!sock) { - return api_write_err(c, "failed to create socket") ? 1 : 0; - } - - 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; -} - -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; - } - - const char *session_id = args[1]; - const char *socket_id = args[2]; - - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - if (destroy_socket(s, socket_id) != 0) { - return api_write_err(c, "socket not found") ? 1 : 0; - } - - return api_write_ok(c) ? 1 : 0; -} - -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; - } - - const char *session_id = args[1]; - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - if (!api_write_array(c, s->forwards_count * 2)) return 0; - 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; - } - - return 1; -} - -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; - } - - const char *session_id = args[1]; - const char *src_socket_id = args[2]; - const char *dst_socket_id = args[3]; - - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - rtp_socket_t *src = find_socket(s, src_socket_id); - if (!src) { - return api_write_err(c, "source socket not found") ? 1 : 0; - } - - rtp_socket_t *dst = find_socket(s, dst_socket_id); - if (!dst) { - return api_write_err(c, "destination socket not found") ? 1 : 0; - } - - if (add_forward(s, src_socket_id, dst_socket_id) != 0) { - return api_write_err(c, "failed to add forward") ? 1 : 0; - } - - 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; -} - -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; - } - - const char *session_id = args[1]; - const char *src_socket_id = args[2]; - const char *dst_socket_id = args[3]; - - rtp_session_t *s = find_session(session_id); - if (!s) { - return api_write_err(c, "session not found") ? 1 : 0; - } - - if (remove_forward(s, src_socket_id, dst_socket_id) != 0) { - return api_write_err(c, "forward not found") ? 1 : 0; - } - - return api_write_ok(c) ? 1 : 0; -} - -static void register_rtp_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); - log_info("udphole: registered session.* commands"); -} - -PT_THREAD(udphole_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { - (void)timestamp; - log_trace("udphole_manager: protothread entry"); - PT_BEGIN(pt); - - PT_WAIT_UNTIL(pt, global_cfg); - - resp_object *rtp_sec = resp_map_get(global_cfg, "udphole"); - if (!rtp_sec) { - log_info("udphole: no [udphole] section in config, not starting"); - PT_EXIT(pt); - } - - const char *mode = resp_map_get_string(rtp_sec, "mode"); - if (!mode || strcmp(mode, "builtin") != 0) { - log_info("udphole: mode is '%s', not starting builtin server", mode ? mode : "(null)"); - PT_EXIT(pt); - } - - const char *ports_str = resp_map_get_string(rtp_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(rtp_sec, "advertise"); - if (advertise_cfg) { - advertise_addr = strdup(advertise_cfg); - } - - register_rtp_commands(); - running = 1; - log_info("udphole: manager started with port range %d-%d", port_low, port_high); - - int64_t last_cleanup = 0; - - for (;;) { - if (global_cfg) { - resp_object *new_rtp_sec = resp_map_get(global_cfg, "udphole"); - if (new_rtp_sec) { - const char *new_mode = resp_map_get_string(new_rtp_sec, "mode"); - if (new_mode && strcmp(new_mode, "builtin") != 0) { - log_info("udphole: mode changed to '%s', shutting down", new_mode); - break; - } - } - } - - int64_t now = (int64_t)(time(NULL)); - if (now - last_cleanup >= 1) { - cleanup_expired_sessions(); - last_cleanup = now; - } - - PT_YIELD(pt); - } - - running = 0; - - for (size_t i = 0; i < sessions_count; i++) { - if (sessions[i]) { - sessions[i]->marked_for_deletion = 1; - } - } - - free(advertise_addr); - advertise_addr = NULL; - - PT_END(pt); -} diff --git a/src/AppModule/rtp/server.h b/src/AppModule/rtp/server.h @@ -1,10 +0,0 @@ -#ifndef __APPMODULE_RTP_SERVER_H__ -#define __APPMODULE_RTP_SERVER_H__ - -#include <stdint.h> - -#include "SchedulerModule/scheduler.h" - -PT_THREAD(udphole_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); - -#endif // __APPMODULE_RTP_SERVER_H__ -\ No newline at end of file diff --git a/src/AppModule/setup.c b/src/AppModule/setup.c @@ -1,11 +0,0 @@ -#include "AppModule/setup.h" -#include "AppModule/command/daemon.h" -#include "CliModule/register_command.h" - -void appmodule_setup(void) { - climodule_register_command( - "daemon", - "Run the udphole daemon", - appmodule_cmd_daemon - ); -} diff --git a/src/AppModule/setup.h b/src/AppModule/setup.h @@ -1,6 +0,0 @@ -#ifndef __APPMODULE_SETUP_H__ -#define __APPMODULE_SETUP_H__ - -void appmodule_setup(void); - -#endif diff --git a/src/CliModule/command/list_commands.c b/src/CliModule/command/list_commands.c @@ -1,43 +0,0 @@ -/// <!-- path: src/CliModule/command/list_commands.c --> -/// # LIST-COMMANDS -/// **list-commands** is the command that lists all available CLI commands and their short descriptions. It has no subcommands or options. -/// -/// **Synopsis** -/// -/// **upbx** [global options] **list-commands** -/// -/// **Description** -/// -/// Prints a two-column layout (command name, description). Use it to discover **daemon**, **extension**, **trunk**, **api-user**, **completion**, and any other registered commands. -/// -#include <stdio.h> -#include <string.h> - -#include "../common.h" -#include "list_commands.h" - -int climodule_cmd_list_commands(int argc, const char **argv) { - int len; - int name_longest = 0; - struct climodule_command *cmd = climodule_commands; - while(cmd) { - len = (int)strlen(cmd->cmd); - if (len > name_longest) { name_longest = len; } - cmd = cmd->next; - } - - int width = cli_get_output_width(80); - int left_col = name_longest + 3; /* " " + name + " " */ - - printf("\n"); - printf("Available commands:\n"); - cmd = climodule_commands; - while (cmd) { - printf("\n %*s ", name_longest, cmd->cmd); - cli_print_wrapped(stdout, cmd->desc, width, left_col); - cmd = cmd->next; - } - printf("\n\n"); - - return 0; -} diff --git a/src/CliModule/command/list_commands.h b/src/CliModule/command/list_commands.h @@ -1,6 +0,0 @@ -#ifndef __CLIMODULE_LIST_COMMANDS_H__ -#define __CLIMODULE_LIST_COMMANDS_H__ - -int climodule_cmd_list_commands(int, const char **); - -#endif // __CLIMODULE_LIST_COMMANDS_H__ diff --git a/src/CliModule/common.c b/src/CliModule/common.c @@ -1,79 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/ioctl.h> - -#include "CliModule/common.h" - -#ifndef NULL -#define NULL (void*)0 -#endif - -struct climodule_command *climodule_commands = NULL; - -const char *cli_find_arg(int argc, const char **argv, const char *name) { - for (int i = 0; i < argc - 1; i++) - if (strcmp(argv[i], name) == 0) - return argv[i + 1]; - return NULL; -} - -size_t cli_collect_positional(int argc, const char **argv, int start, - const char **out, size_t max_out) { - size_t n = 0; - for (int i = start; i < argc && n < max_out; i++) { - if (argv[i][0] == '-' && argv[i][1] == '-' && argv[i][2] != '\0') { - i++; /* skip value of --flag */ - continue; - } - out[n++] = argv[i]; - } - return n; -} - -const char *cli_resolve_default_config(void) { - static char buf[1024]; - const char *home = getenv("HOME"); - if (home) { - snprintf(buf, sizeof(buf), "%s/.config/udphole.conf", home); - if (access(buf, R_OK) == 0) return buf; - snprintf(buf, sizeof(buf), "%s/.udphole.conf", home); - if (access(buf, R_OK) == 0) return buf; - } - if (access("/etc/udphole/udphole.conf", R_OK) == 0) return "/etc/udphole/udphole.conf"; - if (access("/etc/udphole.conf", R_OK) == 0) return "/etc/udphole.conf"; - return NULL; -} - -int cli_get_output_width(int default_width) { - if (!isatty(STDOUT_FILENO)) - return default_width; - struct winsize w; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0 || w.ws_col <= 0) - return default_width; - return (int)w.ws_col; -} - -void cli_print_wrapped(FILE *out, const char *text, int width, int left_col_width) { - if (!text || width <= left_col_width) - return; - char *copy = strdup(text); - if (!copy) - return; - int len = left_col_width; - char *tok = strtok(copy, " "); - while (tok) { - int toklen = (int)strlen(tok); - if (len + 1 + toklen > width) { - fprintf(out, "\n%*s", left_col_width, ""); - len = left_col_width; - } - if (len > left_col_width) - fputc(' ', out); - fputs(tok, out); - len += (len > left_col_width ? 1 : 0) + toklen; - tok = strtok(NULL, " "); - } - free(copy); -} diff --git a/src/CliModule/common.h b/src/CliModule/common.h @@ -1,41 +0,0 @@ -#ifndef __CLIMODULE_COMMON_H__ -#define __CLIMODULE_COMMON_H__ - -#include <stddef.h> -#include <stdio.h> - -struct climodule_command { - void *next; - const char *cmd; - const char *desc; - int (*fn)(int, const char **); -}; - -extern struct climodule_command *climodule_commands; - -/* Find the value of a named argument (e.g. "--name" "value"). Returns NULL if not found. */ -const char *cli_find_arg(int argc, const char **argv, const char *name); - -/* Collect positional arguments (those not part of --flag value pairs). - * Scans argv[start..argc), skipping --flag value pairs. Returns count, fills out[]. */ -size_t cli_collect_positional(int argc, const char **argv, int start, - const char **out, size_t max_out); - -/* Set/get the global config file path (resolved from -f or default locations). */ -void cli_set_config_path(const char *path); -const char *cli_config_path(void); - -/* Resolve default config path by checking standard locations: - * $HOME/.config/upbx.conf, $HOME/.upbx.conf, /etc/upbx/upbx.conf, /etc/upbx.conf - * Returns pointer to a static buffer, or NULL if no config found. */ -const char *cli_resolve_default_config(void); - -/* Get terminal width for stdout (TIOCGWINSZ when stdout is a tty). Returns default_width if not a tty or ioctl fails. */ -int cli_get_output_width(int default_width); - -/* Word-wrap text to stream. Does not mutate text (works on a copy). - * width: total line width. - * left_col_width: on first line we assume this many columns are already used (e.g. command name); continuation lines are indented by this many spaces. Use 0 for full-width (e.g. paragraphs). */ -void cli_print_wrapped(FILE *out, const char *text, int width, int left_col_width); - -#endif // __CLIMODULE_COMMON_H__ diff --git a/src/CliModule/execute_command.c b/src/CliModule/execute_command.c @@ -1,16 +0,0 @@ -#include <stdio.h> -#include <string.h> - -#include "common.h" - -int climodule_execute_command(int argc, const char **argv) { - struct climodule_command *cmd = climodule_commands; - - while(cmd) { - if (!strcmp(cmd->cmd, argv[0])) return cmd->fn(argc, argv); - cmd = cmd->next; - } - - fprintf(stderr, "Unknown command: %s\n", argv[0]); - return 1; -} diff --git a/src/CliModule/execute_command.h b/src/CliModule/execute_command.h @@ -1,6 +0,0 @@ -#ifndef __CLIMODULE_EXECUTE_COMMAND_H__ -#define __CLIMODULE_EXECUTE_COMMAND_H__ - -int climodule_execute_command(int, const char **); - -#endif // __CLIMODULE_EXECUTE_COMMAND_H__ diff --git a/src/CliModule/register_command.c b/src/CliModule/register_command.c @@ -1,18 +0,0 @@ -#include <stdlib.h> -#include <string.h> - -#include "common.h" -#include "register_command.h" - -void climodule_register_command( - const char *name, - const char *description, - int (*fn)(int, const char **) -) { - struct climodule_command *cmd = malloc(sizeof(struct climodule_command)); - cmd->next = climodule_commands; - cmd->cmd = strdup(name); - cmd->desc = strdup(description); - cmd->fn = fn; - climodule_commands = cmd; -} diff --git a/src/CliModule/register_command.h b/src/CliModule/register_command.h @@ -1,6 +0,0 @@ -#ifndef __CLIMODULE_REGISTER_COMMAND_H__ -#define __CLIMODULE_REGISTER_COMMAND_H__ - -void climodule_register_command(const char *name, const char *description, int (*fn)(int, const char **)); - -#endif // __CLIMODULE_REGISTER_COMMAND_H__ diff --git a/src/CliModule/setup.c b/src/CliModule/setup.c @@ -1,11 +0,0 @@ -#include "setup.h" -#include "command/list_commands.h" -#include "register_command.h" - -void climodule_setup() { - climodule_register_command( - "list-commands", - "Displays known commands and their descriptions", - climodule_cmd_list_commands - ); -} diff --git a/src/CliModule/setup.h b/src/CliModule/setup.h @@ -1,6 +0,0 @@ -#ifndef __CLIMODULE_SETUP_H__ -#define __CLIMODULE_SETUP_H__ - -void climodule_setup(); - -#endif // __CLIMODULE_SETUP_H__ diff --git a/src/RespModule/resp.c b/src/RespModule/resp.c @@ -1,335 +0,0 @@ -/* - * RESP encoding and decoding implementation. - */ -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <unistd.h> -#include <errno.h> - -#include "rxi/log.h" -#include "RespModule/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; // EAGAIN - no data (non-blocking) - 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); -} - -/* Append one RESP-encoded object to buf; realloc as needed. Returns 0 on success. */ -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/RespModule/resp.h b/src/RespModule/resp.h @@ -1,61 +0,0 @@ -/* - * RESP (Redis Serialization Protocol) encoding and decoding. - * Types and helpers for building/parsing RESP values. - */ -#ifndef RESPMODULE_RESP_H -#define RESPMODULE_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 - -/* RESP value. Call resp_free when done. */ -typedef struct resp_object resp_object; -struct resp_object { - int type; /* RESPT_* */ - union { - char *s; - long long i; - struct { resp_object *elem; size_t n; } arr; - } u; -}; - -void resp_free(resp_object *o); - -/* Deep-copy resp_object. Caller must resp_free result. Returns NULL on alloc failure. */ -resp_object *resp_deep_copy(const resp_object *o); - -/* Map helper: array as map (even-length: key, value, ...). Returns value element or NULL. Valid until resp freed. */ -resp_object *resp_map_get(const resp_object *o, const char *key); - -/* Map + key → string value (BULK/SIMPLE), or NULL. Valid until resp freed. */ -const char *resp_map_get_string(const resp_object *o, const char *key); - -/* Map helper: set key to value in array-as-map. Takes ownership of value. */ -void resp_map_set(resp_object *map, const char *key, resp_object *value); - -/* Decode one RESP value from fd. Caller must resp_free result. Returns NULL on error. */ -resp_object *resp_read(int fd); - -/* Encode array of argc objects. Caller must free(*out_buf). Returns 0 on success. */ -int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len); - -/* Serialize single object. Caller must free(*out_buf). Returns 0 on success. */ -int resp_serialize(const resp_object *o, char **out_buf, size_t *out_len); - -/* Allocate an empty RESPT_ARRAY. Caller must resp_free. Returns NULL on alloc failure. */ -resp_object *resp_array_init(void); - -/* Append value to array (destination must be RESPT_ARRAY). Takes ownership of value; no clone. Returns 0 on success. */ -int resp_array_append_obj(resp_object *destination, resp_object *value); - -/* Append string/integer to array (clone string for simple/bulk). Return 0 on success. */ -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/SchedulerModule/protothreads.h b/src/SchedulerModule/protothreads.h @@ -1,409 +0,0 @@ -/* - * Protothreads: cooperative multithreading for C (single header). - * - * Combined from: - * - pt.h (protothreads core) - * - pt-sem.h (counting semaphores) - * - lc-addrlabels.h (local continuations, GCC "labels as values") - * - * Original copyright (c) 2004-2006, Swedish Institute of Computer Science. - * All rights reserved. See Contiki and protothreads licensing terms. - * Author: Adam Dunkels <adam@sics.se> - * - * This implementation uses GCC's "labels as values" extension for - * local continuations. No separate lc.h / lc-addrlabels.h is required; - * continuation state is stored in struct pt and used only by PT_* macros. - */ - -#ifndef __PT_H__ -#define __PT_H__ - -#include <stddef.h> - -/* ========================================================================== - * Internal: unique labels for continuation points (GCC addrlabels). - * Not for direct use; merged into PT_BEGIN / PT_WAIT_UNTIL / PT_YIELD etc. - * ========================================================================== */ -#define PT_CONCAT2(s1, s2) s1##s2 -#define PT_CONCAT(s1, s2) PT_CONCAT2(s1, s2) - -/** Resume continuation for \a pt if set; otherwise fall through. */ - -#define PT_RESUME(pt) \ - do { \ - if ((pt)->lc != NULL) \ - goto *(pt)->lc; \ - } while (0) - -/** Set continuation for \a pt at current line. */ -#define PT_SET(pt) \ - do { \ - PT_CONCAT(PT_LABEL, __LINE__): \ - (pt)->lc = &&PT_CONCAT(PT_LABEL, __LINE__); \ - } while (0) - -/* ========================================================================== - * Protothread control structure - * ========================================================================== */ - -/** Protothread state. Only the PT_* macros should touch \a lc. */ -struct pt { - void *lc; /**< Continuation (GCC label pointer); NULL = start. */ -}; - -/** Return values from a protothread function. */ -#define PT_WAITING 0 /**< Blocked waiting. */ -#define PT_YIELDED 1 /**< Yielded voluntarily. */ -#define PT_EXITED 2 /**< Exited (PT_EXIT). */ -#define PT_ENDED 3 /**< Reached PT_END. */ - -/** - * \name Initialization - * @{ - */ - -/** - * Initialize a protothread. - * Must be called before the first PT_SCHEDULE of this protothread. - * \param pt Pointer to the protothread control structure. - */ -#define PT_INIT(pt) ((pt)->lc = NULL) - -/** @} */ - -/** - * \name Declaration and definition - * @{ - */ - -/** - * Declare a protothread function. - * Use as the function return type, e.g. PT_THREAD(worker(struct pt *pt)). - * \param name_args Function name and parameter list. - */ -#define PT_THREAD(name_args) char name_args - -/** - * Start of a protothread body. - * Place at the top of the function; statements above are run every schedule. - * Opens a block; PT_END closes it. PT_YIELD_FLAG is in scope between them. - * \param pt Pointer to the protothread control structure. - */ -#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; (void)PT_YIELD_FLAG; PT_RESUME((pt)) - -/** - * End of a protothread. - * Closes the block opened by PT_BEGIN and returns PT_ENDED. - * \param pt Pointer to the protothread control structure. - */ -#define PT_END(pt) PT_INIT(pt); PT_YIELD_FLAG = 0; return PT_ENDED; } - -/** @} */ - -/** - * \name Blocked wait - * @{ - */ - -/** - * Block until \a condition is true. - * \param pt Pointer to the protothread control structure. - * \param condition Expression; protothread resumes when it is non-zero. - */ -#define PT_WAIT_UNTIL(pt, condition) \ - do { \ - PT_SET(pt); \ - if (!(condition)) \ - return PT_WAITING; \ - } while (0) - -/** - * Block while \a cond is true (i.e. until !(cond)). - * \param pt Pointer to the protothread control structure. - * \param cond Expression. - */ -#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) - -/** @} */ - -/** - * \name Hierarchical protothreads - * @{ - */ - -/** - * Block until child protothread \a thread completes. - * The child must be initialized with PT_INIT before use. - * \param pt Pointer to the parent protothread control structure. - * \param thread Child protothread call, e.g. child_pt(&pt_child). - */ -#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) - -/** - * Spawn a child protothread and wait until it exits. - * Initializes the child and waits; use within a protothread only. - * \param pt Pointer to the parent protothread control structure. - * \param child Pointer to the child's struct pt. - * \param thread Child protothread call, e.g. child_fn(child). - */ -#define PT_SPAWN(pt, child, thread) \ - do { \ - PT_INIT((child)); \ - PT_WAIT_THREAD((pt), (thread)); \ - } while (0) - -/** @} */ - -/** - * \name Exiting and restarting - * @{ - */ - -/** - * Restart the protothread from PT_BEGIN. - * \param pt Pointer to the protothread control structure. - */ -#define PT_RESTART(pt) \ - do { \ - PT_INIT(pt); \ - return PT_WAITING; \ - } while (0) - -/** - * Exit the protothread. - * If it was spawned, the parent becomes unblocked. - * \param pt Pointer to the protothread control structure. - */ -#define PT_EXIT(pt) \ - do { \ - PT_INIT(pt); \ - return PT_EXITED; \ - } while (0) - -/** @} */ - -/** - * \name Scheduling - * @{ - */ - -/** - * Schedule a protothread. - * \param f Call to the protothread function, e.g. my_pt(&pt_state). - * \return Non-zero if still running, 0 if exited or ended. - */ -#define PT_SCHEDULE(f) ((f) < PT_EXITED) - -/** @} */ - -/** - * \name Yielding - * @{ - */ - -/** - * Yield once, then continue. - * \param pt Pointer to the protothread control structure. - */ -#define PT_YIELD(pt) \ - do { \ - PT_YIELD_FLAG = 0; \ - PT_SET(pt); \ - if (PT_YIELD_FLAG == 0) \ - return PT_YIELDED; \ - } while (0) - -/** - * Yield until \a cond is true. - * \param pt Pointer to the protothread control structure. - * \param cond Expression; protothread continues when it is non-zero. - */ -#define PT_YIELD_UNTIL(pt, cond) \ - do { \ - PT_YIELD_FLAG = 0; \ - PT_SET(pt); \ - if ((PT_YIELD_FLAG == 0) || !(cond)) \ - return PT_YIELDED; \ - } while (0) - -/** @} */ - -/* =========================================================================== - * Counting semaphores (pt-sem) - * =========================================================================== */ - -/** - * Counting semaphore for use with protothreads. - * Wait blocks while count is 0; signal increments count. - */ -struct pt_sem { - unsigned int count; -}; - -/** - * Initialize a semaphore. - * \param s Pointer to the semaphore. - * \param c Initial count (unsigned). - */ -#define PT_SEM_INIT(s, c) ((s)->count = (c)) - -/** - * Wait for the semaphore (decrement when > 0). - * Blocks the protothread while count is 0. - * \param pt Pointer to the protothread control structure. - * \param s Pointer to the semaphore. - */ -#define PT_SEM_WAIT(pt, s) \ - do { \ - PT_WAIT_UNTIL(pt, (s)->count > 0); \ - (s)->count--; \ - } while (0) - -/** - * Signal the semaphore (increment count). - * \param pt Pointer to the protothread control structure (unused; for API consistency). - * \param s Pointer to the semaphore. - */ -#define PT_SEM_SIGNAL(pt, s) ((s)->count++) - -/* =========================================================================== - * EXAMPLES (for reference; block not compiled when header is included) - * =========================================================================== - * - * Example 1: Minimal protothread - * - * #include "pt.h" - * - * static struct pt pt_worker; - * - * static PT_THREAD(worker(struct pt *pt)) - * { - * PT_BEGIN(pt); - * while (1) { - * do_something(); - * PT_WAIT_UNTIL(pt, data_ready()); - * process(data); - * } - * PT_END(pt); - * } - * - * int main(void) - * { - * PT_INIT(&pt_worker); - * while (PT_SCHEDULE(worker(&pt_worker))) - * ; - * return 0; - * } - * - * Example 2: Producer-consumer with semaphores (bounded buffer) - * - * #include "pt.h" - * - * #define NUM_ITEMS 32 - * #define BUFSIZE 8 - * - * static struct pt_sem mutex, full, empty; - * static struct pt pt_producer, pt_consumer, pt_driver; - * - * static PT_THREAD(producer(struct pt *pt)) - * { - * static unsigned int produced; - * - * PT_BEGIN(pt); - * for (produced = 0; produced < NUM_ITEMS; produced++) { - * PT_SEM_WAIT(pt, &full); - * PT_SEM_WAIT(pt, &mutex); - * add_to_buffer(produce_item()); - * PT_SEM_SIGNAL(pt, &mutex); - * PT_SEM_SIGNAL(pt, &empty); - * } - * PT_END(pt); - * } - * - * static PT_THREAD(consumer(struct pt *pt)) - * { - * static unsigned int consumed; - * - * PT_BEGIN(pt); - * for (consumed = 0; consumed < NUM_ITEMS; consumed++) { - * PT_SEM_WAIT(pt, &empty); - * PT_SEM_WAIT(pt, &mutex); - * consume_item(get_from_buffer()); - * PT_SEM_SIGNAL(pt, &mutex); - * PT_SEM_SIGNAL(pt, &full); - * } - * PT_END(pt); - * } - * - * static PT_THREAD(driver_thread(struct pt *pt)) - * { - * PT_BEGIN(pt); - * - * PT_SEM_INIT(&empty, 0); - * PT_SEM_INIT(&full, BUFSIZE); - * PT_SEM_INIT(&mutex, 1); - * - * PT_INIT(&pt_producer); - * PT_INIT(&pt_consumer); - * - * PT_WAIT_THREAD(pt, producer(&pt_producer) & consumer(&pt_consumer)); - * - * PT_END(pt); - * } - * - * int main(void) - * { - * PT_INIT(&pt_driver); - * while (PT_SCHEDULE(driver_thread(&pt_driver))) - * ; - * return 0; - * } - * - * Note: producer(&pt_producer) & consumer(&pt_consumer) runs both threads - * each schedule round and blocks until both have exited (return value - * >= PT_EXITED). Implement add_to_buffer, get_from_buffer, produce_item, - * consume_item, and the buffer as needed. - * - * Example 3: Spawning a child protothread - * - * static struct pt pt_parent, pt_child; - * - * static PT_THREAD(child(struct pt *pt)) - * { - * PT_BEGIN(pt); - * do_work(); - * PT_END(pt); - * } - * - * static PT_THREAD(parent(struct pt *pt)) - * { - * PT_BEGIN(pt); - * PT_SPAWN(pt, &pt_child, child(&pt_child)); - * PT_END(pt); - * } - * - * int main(void) - * { - * PT_INIT(&pt_parent); - * while (PT_SCHEDULE(parent(&pt_parent))) - * ; - * return 0; - * } - * - * Example 4: Yielding - * - * static PT_THREAD(periodic(struct pt *pt)) - * { - * PT_BEGIN(pt); - * for (;;) { - * do_work(); - * PT_YIELD(pt); - * } - * PT_END(pt); - * } - * - * PT_YIELD(pt) runs one step then yields; the scheduler can run other - * threads. PT_YIELD_UNTIL(pt, cond) yields until cond is true. - */ - -#endif /* __PT_H__ */ diff --git a/src/SchedulerModule/scheduler.c b/src/SchedulerModule/scheduler.c @@ -1,138 +0,0 @@ -#include <stdlib.h> -#include <sys/select.h> -#include <sys/time.h> - -#include "rxi/log.h" -#include "scheduler.h" - -#ifndef NULL -#define NULL ((void*)0) -#endif - -pt_task_t *pt_first = NULL; -fd_set g_select_result; -static fd_set g_want_fds; - -int schedmod_pt_create(pt_task_fn fn, void *udata) { - if (!fn) return 1; - - pt_task_t *node = calloc(1, sizeof(pt_task_t)); - node->next = pt_first; - node->func = fn; - node->udata = udata; - PT_INIT(&node->pt); - node->is_active = 1; - pt_first = node; - - log_trace("scheduler: created task %p (func=%p, udata=%p), pt_first=%p", (void*)node, (void*)fn, udata, (void*)pt_first); - - return 0; -} - -int schedmod_pt_remove(pt_task_t *task) { - if (!task) return 1; - - pt_task_t *curr = pt_first; - pt_task_t *prev = NULL; - - while (curr) { - if (curr == task) { - if (prev) { - prev->next = curr->next; - } else { - pt_first = curr->next; - } - free(curr); - return 0; - } - prev = curr; - curr = curr->next; - } - - return 1; -} - -int schedmod_has_data(int *in_fds, int **out_fds) { - if (!in_fds || in_fds[0] == 0) return 0; - log_trace("schedmod_has_data: in_fds[0]=%d", in_fds[0]); - - for (int i = 1; i <= in_fds[0]; i++) { - int fd = in_fds[i]; - if (fd >= 0) { - FD_SET(fd, &g_want_fds); - } - } - - if (*out_fds) free(*out_fds); - *out_fds = NULL; - - int count = 0; - for (int i = 1; i <= in_fds[0]; i++) { - if (in_fds[i] >= 0 && FD_ISSET(in_fds[i], &g_select_result)) { - count++; - } - } - - if (count == 0) return 0; - - *out_fds = malloc(sizeof(int) * (count + 1)); - if (!*out_fds) return 0; - - (*out_fds)[0] = count; - int idx = 1; - for (int i = 1; i <= in_fds[0]; i++) { - if (in_fds[i] >= 0 && FD_ISSET(in_fds[i], &g_select_result)) { - (*out_fds)[idx++] = in_fds[i]; - FD_CLR(in_fds[i], &g_select_result); - } - } - - return count; -} - -int schedmod_main() { - if (!pt_first) return 0; - - struct timeval tv; - int maxfd = -1; - - for(;;) { - maxfd = -1; - for (int fd = 0; fd < FD_SETSIZE; fd++) { - if (FD_ISSET(fd, &g_want_fds)) { - if (fd > maxfd) maxfd = fd; - } - } - - - tv.tv_sec = 0; - tv.tv_usec = 100000; - select(maxfd + 1, &g_want_fds, NULL, NULL, &tv); - log_trace("scheduler: select returned"); - - struct timeval now; - gettimeofday(&now, NULL); - int64_t timestamp = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; - g_select_result = g_want_fds; - - FD_ZERO(&g_want_fds); - - pt_task_t *task = pt_first; - while (task) { - pt_task_t *next = task->next; - log_trace("scheduler: about to run task %p (func=%p, is_active=%d)", (void*)task, (void*)task->func, task->is_active); - task->is_active = PT_SCHEDULE(task->func(&task->pt, timestamp, task)); - log_trace("scheduler: task %p (func=%p) returned, is_active=%d, next=%p", (void*)task, (void*)task->func, task->is_active, (void*)next); - if (!task->is_active) { - log_trace("scheduler: removing inactive task %p", (void*)task); - schedmod_pt_remove(task); - } - task = next; - } - log_trace("scheduler: loop done, pt_first=%p", (void*)pt_first); - - if (!pt_first) break; - } - - return 0; -} diff --git a/src/SchedulerModule/scheduler.h b/src/SchedulerModule/scheduler.h @@ -1,30 +0,0 @@ -#ifndef __SCHEDULERMODULE_SCHEDULER_H__ -#define __SCHEDULERMODULE_SCHEDULER_H__ - -#include "protothreads.h" - -#include <stdint.h> -#include <sys/select.h> - -struct pt_task; - -typedef char (*pt_task_fn)(struct pt *pt, int64_t timestamp, struct pt_task *task); - -typedef struct pt_task { - struct pt pt; - struct pt_task *next; - pt_task_fn func; - void *udata; - char is_active; - int maxfd; -} pt_task_t; - -int schedmod_pt_create(pt_task_fn fn, void *udata); -int schedmod_pt_remove(pt_task_t *task); -int schedmod_main(); - -extern fd_set g_select_result; - -int schedmod_has_data(int *in_fds, int **out_fds); - -#endif // __SCHEDULERMODULE_SCHEDULER_H__ diff --git a/src/common/digest_auth.c b/src/common/digest_auth.c @@ -1,54 +0,0 @@ -/* - * Digest authentication helpers (RFC 2069). - * Simplified digest auth: response = MD5(HA1:nonce:HA2) - */ -#include "common/digest_auth.h" -#include "common/md5.h" -#include <string.h> - -void cvt_hex(const unsigned char *bin, HASHHEX hex) { - for (int i = 0; i < DIGEST_HASHLEN; i++) { - unsigned char j = (bin[i] >> 4) & 0xf; - hex[i * 2] = (char)(j <= 9 ? j + '0' : j + 'a' - 10); - j = bin[i] & 0xf; - hex[i * 2 + 1] = (char)(j <= 9 ? j + '0' : j + 'a' - 10); - } - hex[DIGEST_HASHHEXLEN] = '\0'; -} - -void digest_calc_ha1(const char *user, const char *realm, const char *password, HASHHEX out) { - MD5_CTX ctx; - HASH ha1; - MD5_Init(&ctx); - if (user) MD5_Update(&ctx, (const unsigned char *)user, strlen(user)); - MD5_Update(&ctx, (const unsigned char *)":", 1); - if (realm) MD5_Update(&ctx, (const unsigned char *)realm, strlen(realm)); - MD5_Update(&ctx, (const unsigned char *)":", 1); - if (password) MD5_Update(&ctx, (const unsigned char *)password, strlen(password)); - MD5_Final(ha1, &ctx); - cvt_hex(ha1, out); -} - -void digest_calc_ha2(const char *method, const char *uri, HASHHEX out) { - MD5_CTX ctx; - HASH ha2; - MD5_Init(&ctx); - if (method) MD5_Update(&ctx, (const unsigned char *)method, strlen(method)); - MD5_Update(&ctx, (const unsigned char *)":", 1); - if (uri) MD5_Update(&ctx, (const unsigned char *)uri, strlen(uri)); - MD5_Final(ha2, &ctx); - cvt_hex(ha2, out); -} - -void digest_calc_response(HASHHEX ha1, const char *nonce, HASHHEX ha2, HASHHEX out) { - MD5_CTX ctx; - HASH resphash; - MD5_Init(&ctx); - MD5_Update(&ctx, (const unsigned char *)ha1, DIGEST_HASHHEXLEN); - MD5_Update(&ctx, (const unsigned char *)":", 1); - if (nonce) MD5_Update(&ctx, (const unsigned char *)nonce, strlen(nonce)); - MD5_Update(&ctx, (const unsigned char *)":", 1); - MD5_Update(&ctx, (const unsigned char *)ha2, DIGEST_HASHHEXLEN); - MD5_Final(resphash, &ctx); - cvt_hex(resphash, out); -} diff --git a/src/common/digest_auth.h b/src/common/digest_auth.h @@ -1,27 +0,0 @@ -/* - * Digest authentication helpers (RFC 2069). - * Simplified digest auth: response = MD5(HA1:nonce:HA2) - * Shared between SIP server (extension auth) and trunk registration (client auth). - */ -#ifndef UPBX_DIGEST_AUTH_H -#define UPBX_DIGEST_AUTH_H - -#define DIGEST_HASHLEN 16 -#define DIGEST_HASHHEXLEN 32 - -typedef unsigned char HASH[DIGEST_HASHLEN]; -typedef unsigned char HASHHEX[DIGEST_HASHHEXLEN + 1]; - -/* Convert raw 16-byte hash to 32-char lowercase hex string (NUL-terminated). */ -void cvt_hex(const unsigned char *bin, HASHHEX hex); - -/* HA1 = MD5(username:realm:password) */ -void digest_calc_ha1(const char *user, const char *realm, const char *password, HASHHEX out); - -/* HA2 = MD5(method:uri) */ -void digest_calc_ha2(const char *method, const char *uri, HASHHEX out); - -/* response = MD5(HA1:nonce:HA2) */ -void digest_calc_response(HASHHEX ha1, const char *nonce, HASHHEX ha2, HASHHEX out); - -#endif diff --git a/src/common/hexdump.c b/src/common/hexdump.c @@ -1,35 +0,0 @@ -/* - * Canonical hexdump at trace level; shared by sip_server and trunk_reg. - */ -#include <stddef.h> -#include <stdio.h> - -#include "rxi/log.h" - -void log_hexdump_trace(const char *buf, size_t len) { - log_trace("%s", __func__); - char line[96]; - const size_t row = 16; - for (size_t off = 0; off < len; off += row) { - size_t n = row; - if (off + n > len) - n = len - off; - size_t o = 0; - o += (size_t)snprintf(line + o, sizeof(line) - o, "%08zx ", off); - for (size_t i = 0; i < row; i++) { - if (i < n) - o += (size_t)snprintf(line + o, sizeof(line) - o, "%02x ", (unsigned char)buf[off + i]); - else - o += (size_t)snprintf(line + o, sizeof(line) - o, " "); - if (i == 7) - o += (size_t)snprintf(line + o, sizeof(line) - o, " "); - } - o += (size_t)snprintf(line + o, sizeof(line) - o, " |"); - for (size_t i = 0; i < n; i++) { - unsigned char c = (unsigned char)buf[off + i]; - line[o++] = (c >= 0x20 && c < 0x7f) ? (char)c : '.'; - } - o += (size_t)snprintf(line + o, sizeof(line) - o, "|"); - log_trace("%s", line); - } -} diff --git a/src/common/hexdump.h b/src/common/hexdump.h @@ -1,12 +0,0 @@ -/* - * Canonical hexdump utility for debugging network buffers. - */ -#ifndef UPBX_HEXDUMP_H -#define UPBX_HEXDUMP_H - -#include <stddef.h> - -/* Log buf[0..len) as canonical hexdump at trace level. */ -void log_hexdump_trace(const char *buf, size_t len); - -#endif diff --git a/src/common/md5.c b/src/common/md5.c @@ -1,147 +0,0 @@ -/* - * MD5 (RFC 1321) -- reference implementation in C. - * Public domain. No external dependencies. - */ -#include "common/md5.h" -#include <string.h> -#include <stdint.h> - -#define F(x, y, z) U32(((x) & (y)) | ((~(x)) & (z))) -#define G(x, y, z) U32(((x) & (z)) | ((y) & (~(z)))) -#define H(x, y, z) U32((x) ^ (y) ^ (z)) -#define I(x, y, z) U32((y) ^ ((x) | (~(z)))) - -#define U32(x) ((x) & 0xffffffffu) -#define ROTL32(v, n) (U32(((v) << (n)) | ((v) >> (32 - (n))))) - -#define STEP(f, a, b, c, d, x, t, s) do { \ - (a) = U32((a) + f((b), (c), (d)) + (x) + (t)); \ - (a) = ROTL32((a), (s)); \ - (a) = U32((a) + (b)); \ -} while (0) - -static const unsigned int T[64] = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, -}; - -static void md5_transform(unsigned int state[4], const unsigned char block[64]) { - uint32_t a = (uint32_t)state[0], b = (uint32_t)state[1], c = (uint32_t)state[2], d = (uint32_t)state[3]; - uint32_t X[16]; - int i; - - for (i = 0; i < 16; i++) - X[i] = (uint32_t)block[i*4] | ((uint32_t)block[i*4+1] << 8) | - ((uint32_t)block[i*4+2] << 16) | ((uint32_t)block[i*4+3] << 24); - - /* Round 1 */ - STEP(F, a, b, c, d, X[ 0], T[ 0], 7); STEP(F, d, a, b, c, X[ 1], T[ 1], 12); - STEP(F, c, d, a, b, X[ 2], T[ 2], 17); STEP(F, b, c, d, a, X[ 3], T[ 3], 22); - STEP(F, a, b, c, d, X[ 4], T[ 4], 7); STEP(F, d, a, b, c, X[ 5], T[ 5], 12); - STEP(F, c, d, a, b, X[ 6], T[ 6], 17); STEP(F, b, c, d, a, X[ 7], T[ 7], 22); - STEP(F, a, b, c, d, X[ 8], T[ 8], 7); STEP(F, d, a, b, c, X[ 9], T[ 9], 12); - STEP(F, c, d, a, b, X[10], T[10], 17); STEP(F, b, c, d, a, X[11], T[11], 22); - STEP(F, a, b, c, d, X[12], T[12], 7); STEP(F, d, a, b, c, X[13], T[13], 12); - STEP(F, c, d, a, b, X[14], T[14], 17); STEP(F, b, c, d, a, X[15], T[15], 22); - /* Round 2 */ - STEP(G, a, b, c, d, X[ 1], T[16], 5); STEP(G, d, a, b, c, X[ 6], T[17], 9); - STEP(G, c, d, a, b, X[11], T[18], 14); STEP(G, b, c, d, a, X[ 0], T[19], 20); - STEP(G, a, b, c, d, X[ 5], T[20], 5); STEP(G, d, a, b, c, X[10], T[21], 9); - STEP(G, c, d, a, b, X[15], T[22], 14); STEP(G, b, c, d, a, X[ 4], T[23], 20); - STEP(G, a, b, c, d, X[ 9], T[24], 5); STEP(G, d, a, b, c, X[14], T[25], 9); - STEP(G, c, d, a, b, X[ 3], T[26], 14); STEP(G, b, c, d, a, X[ 8], T[27], 20); - STEP(G, a, b, c, d, X[13], T[28], 5); STEP(G, d, a, b, c, X[ 2], T[29], 9); - STEP(G, c, d, a, b, X[ 7], T[30], 14); STEP(G, b, c, d, a, X[12], T[31], 20); - /* Round 3 */ - STEP(H, a, b, c, d, X[ 5], T[32], 4); STEP(H, d, a, b, c, X[ 8], T[33], 11); - STEP(H, c, d, a, b, X[11], T[34], 16); STEP(H, b, c, d, a, X[14], T[35], 23); - STEP(H, a, b, c, d, X[ 1], T[36], 4); STEP(H, d, a, b, c, X[ 4], T[37], 11); - STEP(H, c, d, a, b, X[ 7], T[38], 16); STEP(H, b, c, d, a, X[10], T[39], 23); - STEP(H, a, b, c, d, X[13], T[40], 4); STEP(H, d, a, b, c, X[ 0], T[41], 11); - STEP(H, c, d, a, b, X[ 3], T[42], 16); STEP(H, b, c, d, a, X[ 6], T[43], 23); - STEP(H, a, b, c, d, X[ 9], T[44], 4); STEP(H, d, a, b, c, X[12], T[45], 11); - STEP(H, c, d, a, b, X[15], T[46], 16); STEP(H, b, c, d, a, X[ 2], T[47], 23); - /* Round 4 */ - STEP(I, a, b, c, d, X[ 0], T[48], 6); STEP(I, d, a, b, c, X[ 7], T[49], 10); - STEP(I, c, d, a, b, X[14], T[50], 15); STEP(I, b, c, d, a, X[ 5], T[51], 21); - STEP(I, a, b, c, d, X[12], T[52], 6); STEP(I, d, a, b, c, X[ 3], T[53], 10); - STEP(I, c, d, a, b, X[10], T[54], 15); STEP(I, b, c, d, a, X[ 1], T[55], 21); - STEP(I, a, b, c, d, X[ 8], T[56], 6); STEP(I, d, a, b, c, X[15], T[57], 10); - STEP(I, c, d, a, b, X[ 6], T[58], 15); STEP(I, b, c, d, a, X[13], T[59], 21); - STEP(I, a, b, c, d, X[ 4], T[60], 6); STEP(I, d, a, b, c, X[11], T[61], 10); - STEP(I, c, d, a, b, X[ 2], T[62], 15); STEP(I, b, c, d, a, X[ 9], T[63], 21); - - state[0] = (unsigned int)(state[0] + a); state[1] = (unsigned int)(state[1] + b); - state[2] = (unsigned int)(state[2] + c); state[3] = (unsigned int)(state[3] + d); -} - -void MD5_Init(MD5_CTX *ctx) { - ctx->state[0] = 0x67452301u; - ctx->state[1] = 0xefcdab89u; - ctx->state[2] = 0x98badcfeu; - ctx->state[3] = 0x10325476u; - ctx->count[0] = ctx->count[1] = 0; -} - -void MD5_Update(MD5_CTX *ctx, const void *data, size_t len) { - const unsigned char *p = (const unsigned char *)data; - unsigned int i, n; - - i = (unsigned int)((ctx->count[0] >> 3) & 63u); - ctx->count[0] += (unsigned int)(len << 3); - if (ctx->count[0] < (len << 3)) - ctx->count[1]++; - ctx->count[1] += (unsigned int)(len >> 29); - - n = 64u - i; - if (len >= n) { - memcpy(ctx->buf + i, p, n); - md5_transform(ctx->state, ctx->buf); - for (; n + 63 < len; n += 64) - md5_transform(ctx->state, p + n); - i = 0; - p += n; - len -= n; - } - if (len) - memcpy(ctx->buf + i, p, len); -} - -void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) { - static const unsigned char padding[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - unsigned char bits[8]; - unsigned int i, index, padLen; - - /* Encode count into bits (little-endian, low word first) */ - bits[0] = (unsigned char)(ctx->count[0] ); - bits[1] = (unsigned char)(ctx->count[0] >> 8); - bits[2] = (unsigned char)(ctx->count[0] >> 16); - bits[3] = (unsigned char)(ctx->count[0] >> 24); - bits[4] = (unsigned char)(ctx->count[1] ); - bits[5] = (unsigned char)(ctx->count[1] >> 8); - bits[6] = (unsigned char)(ctx->count[1] >> 16); - bits[7] = (unsigned char)(ctx->count[1] >> 24); - - index = (unsigned int)((ctx->count[0] >> 3) & 63u); - padLen = (index < 56) ? (56 - index) : (120 - index); - MD5_Update(ctx, padding, padLen); - MD5_Update(ctx, bits, 8); - - for (i = 0; i < 4; i++) { - digest[i*4 ] = (unsigned char)(ctx->state[i] ); - digest[i*4+1] = (unsigned char)(ctx->state[i] >> 8); - digest[i*4+2] = (unsigned char)(ctx->state[i] >> 16); - digest[i*4+3] = (unsigned char)(ctx->state[i] >> 24); - } -} diff --git a/src/common/md5.h b/src/common/md5.h @@ -1,20 +0,0 @@ -/* - * Minimal MD5 (RFC 1321) for Digest auth. No external crypto library. - * API compatible with OpenSSL MD5_* for drop-in use. - */ -#ifndef UPBX_MD5_H -#define UPBX_MD5_H - -#include <stddef.h> - -typedef struct { - unsigned int state[4]; - unsigned int count[2]; - unsigned char buf[64]; -} MD5_CTX; - -void MD5_Init(MD5_CTX *ctx); -void MD5_Update(MD5_CTX *ctx, const void *data, size_t len); -void MD5_Final(unsigned char digest[16], MD5_CTX *ctx); - -#endif diff --git a/src/common/socket_util.h b/src/common/socket_util.h @@ -1,5 +1,5 @@ -#ifndef UPBX_SOCKET_UTIL_H -#define UPBX_SOCKET_UTIL_H +#ifndef UDPHOLE_SOCKET_UTIL_H +#define UDPHOLE_SOCKET_UTIL_H /* Set socket blocking (0) or non-blocking (1). Returns 0 on success, -1 on error. */ int set_socket_nonblocking(int fd, int nonblock); diff --git a/src/common/util/hex.c b/src/common/util/hex.c @@ -1,31 +0,0 @@ -#include "common/util/hex.h" - -void hex_bytes_to_str(const unsigned char *bytes, size_t len, char *out) { - static const char hex[] = "0123456789abcdef"; - for (size_t i = 0; i < len; i++) { - out[i * 2] = hex[(bytes[i] >> 4) & 0x0f]; - out[i * 2 + 1] = hex[bytes[i] & 0x0f]; - } - out[len * 2] = '\0'; -} - -int hex_char_to_val(char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return -1; -} - -int hex_str_to_bytes(const char *hex, size_t hex_len, unsigned char *out, size_t out_size) { - if (hex_len % 2 != 0) return -1; - size_t bytes = hex_len / 2; - if (bytes > out_size) return -1; - - for (size_t i = 0; i < bytes; i++) { - int hi = hex_char_to_val(hex[i * 2]); - int lo = hex_char_to_val(hex[i * 2 + 1]); - if (hi < 0 || lo < 0) return -1; - out[i] = (unsigned char)((hi << 4) | lo); - } - return 0; -} diff --git a/src/common/util/hex.h b/src/common/util/hex.h @@ -1,12 +0,0 @@ -#ifndef UPBX_COMMON_UTIL_HEX_H -#define UPBX_COMMON_UTIL_HEX_H - -#include <stddef.h> - -void hex_bytes_to_str(const unsigned char *bytes, size_t len, char *out); - -int hex_char_to_val(char c); - -int hex_str_to_bytes(const char *hex, size_t hex_len, unsigned char *out, size_t out_size); - -#endif diff --git a/src/config.c b/src/config.c @@ -1,62 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "benhoyt/inih.h" -#include "rxi/log.h" -#include "config.h" -#include "RespModule/resp.h" - -resp_object *global_cfg = NULL; -resp_object *pending_cfg = NULL; - -static const char *stored_config_path = NULL; - -static int config_handler(void *user, const char *section, const char *name, const char *value, int lineno) { - (void)lineno; - resp_object *cfg = (resp_object *)user; - resp_object *sec = resp_map_get(cfg, section); - if (!sec || sec->type != RESPT_ARRAY) { - sec = resp_array_init(); - resp_map_set(cfg, section, sec); - sec = resp_map_get(cfg, section); - } - resp_array_append_bulk(sec, name); - resp_array_append_bulk(sec, value); - return 1; -} - -void config_init(void) { - global_cfg = resp_array_init(); - config_load(global_cfg, config_get_path()); -} - -int config_load(resp_object *cfg, const char *path) { - return ini_parse(path, config_handler, cfg); -} - -void config_pending_init(void) { - pending_cfg = resp_array_init(); -} - -void config_swap(void) { - resp_object *old = global_cfg; - global_cfg = pending_cfg; - pending_cfg = old; - if (old) resp_free(old); -} - -int config_reload(void) { - config_pending_init(); - int r = config_load(pending_cfg, config_get_path()); - if (r < 0) return -1; - config_swap(); - return 0; -} - -void config_set_path(const char *path) { - if (stored_config_path) free((void*)stored_config_path); - stored_config_path = path ? strdup(path) : NULL; -} - -const char *config_get_path(void) { - return stored_config_path; -} diff --git a/src/config.h b/src/config.h @@ -1,15 +0,0 @@ -#ifndef UPBX_CONFIG_H -#define UPBX_CONFIG_H - -#include "RespModule/resp.h" - -extern resp_object *global_cfg; -extern resp_object *pending_cfg; - -void config_init(void); -int config_load(resp_object *cfg, const char *path); -int config_reload(void); -void config_set_path(const char *path); -const char *config_get_path(void); - -#endif diff --git a/src/domain/protothreads.h b/src/domain/protothreads.h @@ -0,0 +1,99 @@ +#ifndef UDPHOLE_PROTOTHREADS_H +#define UDPHOLE_PROTOTHREADS_H + +#include <stddef.h> + +#define PT_CONCAT2(s1, s2) s1##s2 +#define PT_CONCAT(s1, s2) PT_CONCAT2(s1, s2) + +#define PT_RESUME(pt) \ + do { \ + if ((pt)->lc != NULL) \ + goto *(pt)->lc; \ + } while (0) + +#define PT_SET(pt) \ + do { \ + PT_CONCAT(PT_LABEL, __LINE__): \ + (pt)->lc = &&PT_CONCAT(PT_LABEL, __LINE__); \ + } while (0) + +struct pt { + void *lc; +}; + +#define PT_WAITING 0 +#define PT_YIELDED 1 +#define PT_EXITED 2 +#define PT_ENDED 3 + +#define PT_INIT(pt) ((pt)->lc = NULL) + +#define PT_THREAD(name_args) char name_args + +#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; (void)PT_YIELD_FLAG; PT_RESUME((pt)) + +#define PT_END(pt) PT_INIT(pt); PT_YIELD_FLAG = 0; return PT_ENDED; } + +#define PT_WAIT_UNTIL(pt, condition) \ + do { \ + PT_SET(pt); \ + if (!(condition)) \ + return PT_WAITING; \ + } while (0) + +#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) + +#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) + +#define PT_SPAWN(pt, child, thread) \ + do { \ + PT_INIT((child)); \ + PT_WAIT_THREAD((pt), (thread)); \ + } while (0) + +#define PT_RESTART(pt) \ + do { \ + PT_INIT(pt); \ + return PT_WAITING; \ + } while (0) + +#define PT_EXIT(pt) \ + do { \ + PT_INIT(pt); \ + return PT_EXITED; \ + } while (0) + +#define PT_SCHEDULE(f) ((f) < PT_EXITED) + +#define PT_YIELD(pt) \ + do { \ + PT_YIELD_FLAG = 0; \ + PT_SET(pt); \ + if (PT_YIELD_FLAG == 0) \ + return PT_YIELDED; \ + } while (0) + +#define PT_YIELD_UNTIL(pt, cond) \ + do { \ + PT_YIELD_FLAG = 0; \ + PT_SET(pt); \ + if ((PT_YIELD_FLAG == 0) || !(cond)) \ + return PT_YIELDED; \ + } while (0) + +struct pt_sem { + unsigned int count; +}; + +#define PT_SEM_INIT(s, c) ((s)->count = (c)) + +#define PT_SEM_WAIT(pt, s) \ + do { \ + PT_WAIT_UNTIL(pt, (s)->count > 0); \ + (s)->count--; \ + } while (0) + +#define PT_SEM_SIGNAL(pt, s) ((s)->count++) + +#endif // UDPHOLE_PROTOTHREADS_H +\ No newline at end of file diff --git a/src/domain/scheduler.c b/src/domain/scheduler.c @@ -0,0 +1,138 @@ +#include <stdlib.h> +#include <sys/select.h> +#include <sys/time.h> + +#include "rxi/log.h" +#include "domain/scheduler.h" + +#ifndef NULL +#define NULL ((void*)0) +#endif + +pt_task_t *pt_first = NULL; +fd_set g_select_result; +static fd_set g_want_fds; + +int domain_schedmod_pt_create(pt_task_fn fn, void *udata) { + if (!fn) return 1; + + pt_task_t *node = calloc(1, sizeof(pt_task_t)); + node->next = pt_first; + node->func = fn; + node->udata = udata; + PT_INIT(&node->pt); + node->is_active = 1; + pt_first = node; + + log_trace("scheduler: created task %p (func=%p, udata=%p), pt_first=%p", (void*)node, (void*)fn, udata, (void*)pt_first); + + return 0; +} + +int domain_schedmod_pt_remove(pt_task_t *task) { + if (!task) return 1; + + pt_task_t *curr = pt_first; + pt_task_t *prev = NULL; + + while (curr) { + if (curr == task) { + if (prev) { + prev->next = curr->next; + } else { + pt_first = curr->next; + } + free(curr); + return 0; + } + prev = curr; + curr = curr->next; + } + + return 1; +} + +int domain_schedmod_has_data(int *in_fds, int **out_fds) { + if (!in_fds || in_fds[0] == 0) return 0; + log_trace("domain_schedmod_has_data: in_fds[0]=%d", in_fds[0]); + + for (int i = 1; i <= in_fds[0]; i++) { + int fd = in_fds[i]; + if (fd >= 0) { + FD_SET(fd, &g_want_fds); + } + } + + if (*out_fds) free(*out_fds); + *out_fds = NULL; + + int count = 0; + for (int i = 1; i <= in_fds[0]; i++) { + if (in_fds[i] >= 0 && FD_ISSET(in_fds[i], &g_select_result)) { + count++; + } + } + + if (count == 0) return 0; + + *out_fds = malloc(sizeof(int) * (count + 1)); + if (!*out_fds) return 0; + + (*out_fds)[0] = count; + int idx = 1; + for (int i = 1; i <= in_fds[0]; i++) { + if (in_fds[i] >= 0 && FD_ISSET(in_fds[i], &g_select_result)) { + (*out_fds)[idx++] = in_fds[i]; + FD_CLR(in_fds[i], &g_select_result); + } + } + + return count; +} + +int domain_schedmod_main(void) { + if (!pt_first) return 0; + + struct timeval tv; + int maxfd = -1; + + for(;;) { + maxfd = -1; + for (int fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &g_want_fds)) { + if (fd > maxfd) maxfd = fd; + } + } + + + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(maxfd + 1, &g_want_fds, NULL, NULL, &tv); + log_trace("scheduler: select returned"); + + struct timeval now; + gettimeofday(&now, NULL); + int64_t timestamp = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + g_select_result = g_want_fds; + + FD_ZERO(&g_want_fds); + + pt_task_t *task = pt_first; + while (task) { + pt_task_t *next = task->next; + log_trace("scheduler: about to run task %p (func=%p, is_active=%d)", (void*)task, (void*)task->func, task->is_active); + task->is_active = PT_SCHEDULE(task->func(&task->pt, timestamp, task)); + log_trace("scheduler: task %p (func=%p) returned, is_active=%d, next=%p", (void*)task, (void*)task->func, task->is_active, (void*)next); + if (!task->is_active) { + log_trace("scheduler: removing inactive task %p", (void*)task); + domain_schedmod_pt_remove(task); + } + task = next; + } + log_trace("scheduler: loop done, pt_first=%p", (void*)pt_first); + + if (!pt_first) break; + } + + return 0; +} +\ No newline at end of file diff --git a/src/domain/scheduler.h b/src/domain/scheduler.h @@ -0,0 +1,30 @@ +#ifndef UDPHOLE_SCHEDULER_H +#define UDPHOLE_SCHEDULER_H + +#include "domain/protothreads.h" + +#include <stdint.h> +#include <sys/select.h> + +struct pt_task; + +typedef char (*pt_task_fn)(struct pt *pt, int64_t timestamp, struct pt_task *task); + +typedef struct pt_task { + struct pt pt; + struct pt_task *next; + pt_task_fn func; + void *udata; + char is_active; + int maxfd; +} pt_task_t; + +int domain_schedmod_pt_create(pt_task_fn fn, void *udata); +int domain_schedmod_pt_remove(pt_task_t *task); +int domain_schedmod_main(void); + +extern fd_set g_select_result; + +int domain_schedmod_has_data(int *in_fds, int **out_fds); + +#endif // UDPHOLE_SCHEDULER_H +\ No newline at end of file diff --git a/src/domain/session.c b/src/domain/session.c @@ -0,0 +1,824 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "rxi/log.h" +#include "domain/protothreads.h" +#include "domain/scheduler.h" +#include "common/socket_util.h" +#include "infrastructure/config.h" +#include "tidwall/hashmap.h" +#include "session.h" +#include "interface/api/server.h" + +#define SESSION_HASH_SIZE 256 +#define BUFFER_SIZE 4096 +#define DEFAULT_IDLE_EXPIRY 60 + +typedef struct socket { + char *socket_id; + int *fds; + int local_port; + int mode; + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + int learned_valid; + struct sockaddr_storage learned_addr; + socklen_t learned_addrlen; +} socket_t; + +typedef struct forward { + char *src_socket_id; + char *dst_socket_id; +} forward_t; + +typedef struct session { + char *session_id; + time_t idle_expiry; + time_t created; + time_t last_activity; + socket_t **sockets; + size_t sockets_count; + forward_t *forwards; + size_t forwards_count; + int marked_for_deletion; + int *ready_fds; + int *all_fds; + struct pt pt; + struct pt_task *task; +} session_t; + +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) { + for (size_t i = 0; i < sessions_count; i++) { + if (strcmp(sessions[i]->session_id, session_id) == 0) { + return sessions[i]; + } + } + return NULL; +} + +static uint64_t socket_hash(const void *item, uint64_t seed0, uint64_t seed1) { + const socket_t *s = item; + return hashmap_sip(s->socket_id, strlen(s->socket_id), seed0, seed1); +} + +static int socket_compare(const void *a, const void *b, void *udata) { + (void)udata; + const socket_t *sa = a; + const socket_t *sb = b; + return strcmp(sa->socket_id, sb->socket_id); +} + +static socket_t *find_socket(session_t *s, const char *socket_id) { + if (!s || !s->sockets || !socket_id) return NULL; + for (size_t i = 0; i < s->sockets_count; i++) { + if (s->sockets[i] && strcmp(s->sockets[i]->socket_id, socket_id) == 0) { + return s->sockets[i]; + } + } + return NULL; +} + +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; + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + int udp_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_fd < 0) continue; + int ok = (bind(udp_fd, (struct sockaddr *)&addr, sizeof(addr)) == 0); + close(udp_fd); + if (!ok) continue; + + return port; + } + return 0; +} + +static int parse_ip_addr(const char *ip_str, int port, struct sockaddr_storage *addr, socklen_t *addrlen) { + memset(addr, 0, sizeof(*addr)); + + struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; + if (inet_pton(AF_INET, ip_str, &addr4->sin_addr) == 1) { + addr4->sin_family = AF_INET; + addr4->sin_port = htons(port); + *addrlen = sizeof(*addr4); + return 0; + } + + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + if (inet_pton(AF_INET6, ip_str, &addr6->sin6_addr) == 1) { + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(port); + *addrlen = sizeof(*addr6); + return 0; + } + + return -1; +} + +static void close_socket(socket_t *sock) { + if (!sock || !sock->fds) return; + for (int i = 1; i <= sock->fds[0]; i++) { + if (sock->fds[i] >= 0) { + close(sock->fds[i]); + } + } + free(sock->fds); + sock->fds = NULL; +} + +static void free_socket(socket_t *sock) { + if (!sock) return; + close_socket(sock); + free(sock->socket_id); + free(sock); +} + +static void destroy_session(session_t *s) { + if (!s) return; + s->marked_for_deletion = 1; + for (size_t i = 0; i < sessions_count; i++) { + if (sessions[i] == s) { + sessions[i] = NULL; + break; + } + } +} + +static session_t *create_session(const char *session_id, int idle_expiry) { + const session_t *cs = find_session(session_id); + if (cs) return (session_t *)cs; + + session_t *s = calloc(1, sizeof(*s)); + if (!s) return NULL; + + s->session_id = strdup(session_id); + s->created = time(NULL); + s->last_activity = s->created; + s->idle_expiry = idle_expiry > 0 ? idle_expiry : DEFAULT_IDLE_EXPIRY; + + sessions = realloc(sessions, sizeof(session_t *) * (sessions_count + 1)); + sessions[sessions_count++] = s; + + return s; +} + +static void cleanup_expired_sessions(void) { + if (!sessions) return; + time_t now = time(NULL); + + for (size_t i = 0; i < sessions_count; i++) { + session_t *s = sessions[i]; + if (!s) continue; + if (now - s->last_activity > s->idle_expiry) { + log_debug("udphole: session %s expired (idle %ld > expiry %ld)", + s->session_id, (long)(now - s->last_activity), (long)s->idle_expiry); + destroy_session(s); + } + } +} + +static int add_forward(session_t *s, const char *src_id, const char *dst_id) { + for (size_t i = 0; i < s->forwards_count; i++) { + if (strcmp(s->forwards[i].src_socket_id, src_id) == 0 && + strcmp(s->forwards[i].dst_socket_id, dst_id) == 0) { + return 0; + } + } + + forward_t *new_forwards = realloc(s->forwards, sizeof(forward_t) * (s->forwards_count + 1)); + if (!new_forwards) return -1; + s->forwards = new_forwards; + + s->forwards[s->forwards_count].src_socket_id = strdup(src_id); + s->forwards[s->forwards_count].dst_socket_id = strdup(dst_id); + s->forwards_count++; + + return 0; +} + +static int remove_forward(session_t *s, const char *src_id, const char *dst_id) { + for (size_t i = 0; i < s->forwards_count; i++) { + if (strcmp(s->forwards[i].src_socket_id, src_id) == 0 && + strcmp(s->forwards[i].dst_socket_id, dst_id) == 0) { + free(s->forwards[i].src_socket_id); + free(s->forwards[i].dst_socket_id); + for (size_t j = i; j < s->forwards_count - 1; j++) { + s->forwards[j] = s->forwards[j + 1]; + } + s->forwards_count--; + return 0; + } + } + return -1; +} + +static socket_t *create_listen_socket(session_t *sess, const char *socket_id) { + socket_t *existing = find_socket(sess, socket_id); + if (existing) return existing; + + int port = alloc_port(); + if (!port) { + log_error("udphole: no ports available"); + return NULL; + } + + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", port); + int *fds = udp_recv(port_str, NULL, NULL); + if (!fds || fds[0] == 0) { + log_error("udphole: failed to create UDP socket on port %d", port); + free(fds); + return NULL; + } + + socket_t *sock = calloc(1, sizeof(*sock)); + if (!sock) { + free(fds); + return NULL; + } + + sock->socket_id = strdup(socket_id); + sock->fds = fds; + sock->local_port = port; + sock->mode = 0; + sock->learned_valid = 0; + + sess->sockets = realloc(sess->sockets, sizeof(socket_t *) * (sess->sockets_count + 1)); + sess->sockets[sess->sockets_count++] = sock; + + log_debug("udphole: created listen socket %s in session %s on port %d", + socket_id, sess->session_id, port); + return sock; +} + +static socket_t *create_connect_socket(session_t *sess, const char *socket_id, + const char *ip, int port) { + socket_t *existing = find_socket(sess, socket_id); + if (existing) return existing; + + int local_port = alloc_port(); + if (!local_port) { + log_error("udphole: no ports available"); + return NULL; + } + + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", local_port); + int *fds = udp_recv(port_str, NULL, NULL); + if (!fds || fds[0] == 0) { + log_error("udphole: failed to create UDP socket on port %d", local_port); + free(fds); + return NULL; + } + + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + if (parse_ip_addr(ip, port, &remote_addr, &remote_addrlen) != 0) { + log_error("udphole: invalid remote address %s:%d", ip, port); + free(fds); + return NULL; + } + + socket_t *sock = calloc(1, sizeof(*sock)); + if (!sock) { + free(fds); + return NULL; + } + + sock->socket_id = strdup(socket_id); + sock->fds = fds; + sock->local_port = local_port; + sock->mode = 1; + sock->remote_addr = remote_addr; + sock->remote_addrlen = remote_addrlen; + sock->learned_valid = 0; + + sess->sockets = realloc(sess->sockets, sizeof(socket_t *) * (sess->sockets_count + 1)); + sess->sockets[sess->sockets_count++] = sock; + + log_debug("udphole: created connect socket %s in session %s on port %d -> %s:%d", + socket_id, sess->session_id, local_port, ip, port); + return sock; +} + +static int destroy_socket(session_t *sess, const char *socket_id) { + socket_t *sock = find_socket(sess, socket_id); + if (!sock) return -1; + + for (size_t i = 0; i < sess->sockets_count; i++) { + if (sess->sockets[i] == sock) { + sess->sockets[i] = NULL; + break; + } + } + free_socket(sock); + + for (size_t i = 0; i < sess->forwards_count; ) { + if (strcmp(sess->forwards[i].src_socket_id, socket_id) == 0 || + strcmp(sess->forwards[i].dst_socket_id, socket_id) == 0) { + free(sess->forwards[i].src_socket_id); + free(sess->forwards[i].dst_socket_id); + for (size_t j = i; j < sess->forwards_count - 1; j++) { + sess->forwards[j] = sess->forwards[j + 1]; + } + sess->forwards_count--; + } else { + i++; + } + } + + return 0; +} + +static socket_t *find_socket_by_fd(session_t *s, int fd) { + if (!s || !s->sockets) return NULL; + for (size_t j = 0; j < s->sockets_count; j++) { + socket_t *sock = s->sockets[j]; + if (!sock || !sock->fds) continue; + for (int i = 1; i <= sock->fds[0]; i++) { + if (sock->fds[i] == fd) { + return sock; + } + } + } + return NULL; +} + +PT_THREAD(session_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { + session_t *s = task->udata; + + (void)timestamp; + PT_BEGIN(pt); + + char buffer[BUFFER_SIZE]; + + for (;;) { + if (s->marked_for_deletion) { + break; + } + + if (!s->sockets || s->sockets_count == 0) { + PT_YIELD(pt); + continue; + } + + s->all_fds = realloc(s->all_fds, sizeof(int) * (s->sockets_count * 2 + 1)); + if (!s->all_fds) { + PT_YIELD(pt); + continue; + } + s->all_fds[0] = 0; + + for (size_t j = 0; j < s->sockets_count; j++) { + socket_t *sock = s->sockets[j]; + if (!sock || !sock->fds) continue; + for (int i = 1; i <= sock->fds[0]; i++) { + s->all_fds[++s->all_fds[0]] = sock->fds[i]; + } + } + + if (s->all_fds[0] == 0) { + PT_YIELD(pt); + continue; + } + + PT_WAIT_UNTIL(pt, domain_schedmod_has_data(s->all_fds, &s->ready_fds) > 0); + + if (!s->ready_fds || s->ready_fds[0] == 0) { + PT_YIELD(pt); + continue; + } + + for (int r = 1; r <= s->ready_fds[0]; r++) { + int ready_fd = s->ready_fds[r]; + + socket_t *src_sock = find_socket_by_fd(s, ready_fd); + if (!src_sock) continue; + + struct sockaddr_storage from_addr; + socklen_t from_len = sizeof(from_addr); + ssize_t n = recvfrom(ready_fd, buffer, sizeof(buffer) - 1, 0, + (struct sockaddr *)&from_addr, &from_len); + + if (n <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + log_warn("udphole: recvfrom error on socket %s: %s", + src_sock->socket_id, strerror(errno)); + } + continue; + } + + s->last_activity = time(NULL); + + if (src_sock->mode == 0 && !src_sock->learned_valid) { + src_sock->learned_addr = from_addr; + src_sock->learned_addrlen = from_len; + src_sock->learned_valid = 1; + log_debug("udphole: socket %s learned remote address", src_sock->socket_id); + } + + for (size_t i = 0; i < s->forwards_count; i++) { + if (strcmp(s->forwards[i].src_socket_id, src_sock->socket_id) != 0) { + continue; + } + + socket_t *dst_sock = find_socket(s, s->forwards[i].dst_socket_id); + if (!dst_sock || !dst_sock->fds || dst_sock->fds[0] == 0) continue; + + struct sockaddr *dest_addr = NULL; + socklen_t dest_addrlen = 0; + + if (dst_sock->mode == 1) { + dest_addr = (struct sockaddr *)&dst_sock->remote_addr; + dest_addrlen = dst_sock->remote_addrlen; + } else if (dst_sock->learned_valid) { + dest_addr = (struct sockaddr *)&dst_sock->learned_addr; + dest_addrlen = dst_sock->learned_addrlen; + } + + if (dest_addr && dest_addrlen > 0) { + int dst_fd = dst_sock->fds[1]; + ssize_t sent = sendto(dst_fd, buffer, n, 0, dest_addr, dest_addrlen); + if (sent < 0) { + log_warn("udphole: forward failed %s -> %s: %s", + src_sock->socket_id, dst_sock->socket_id, strerror(errno)); + } + } + } + } + + } + + log_debug("udphole: session %s protothread exiting", s->session_id); + + if (s->all_fds) { + free(s->all_fds); + s->all_fds = NULL; + } + if (s->ready_fds) { + free(s->ready_fds); + s->ready_fds = NULL; + } + + PT_END(pt); +} + +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; + } + + const char *session_id = args[1]; + int idle_expiry = 0; + if (nargs >= 3 && args[2] && args[2][0] != '\0') { + idle_expiry = atoi(args[2]); + } + + session_t *s = create_session(session_id, idle_expiry); + if (!s) { + return api_write_err(c, "failed to create session") ? 1 : 0; + } + + spawn_session_pt(s); + return api_write_ok(c) ? 1 : 0; +} + +static char cmd_session_list(api_client_t *c, char **args, int nargs) { + (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; + + 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; + } + + return 1; +} + +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; + } + + 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; + } + + size_t num_items = 8; + if (!api_write_array(c, num_items)) return 0; + + if (!api_write_bulk_cstr(c, "session_id")) return 0; + if (!api_write_bulk_cstr(c, s->session_id)) return 0; + + if (!api_write_bulk_cstr(c, "created")) return 0; + if (!api_write_bulk_int(c, (int)s->created)) return 0; + + if (!api_write_bulk_cstr(c, "last_activity")) return 0; + if (!api_write_bulk_int(c, (int)s->last_activity)) return 0; + + if (!api_write_bulk_cstr(c, "idle_expiry")) return 0; + if (!api_write_bulk_int(c, (int)s->idle_expiry)) return 0; + + if (!api_write_bulk_cstr(c, "sockets")) return 0; + if (!api_write_array(c, s->sockets_count)) return 0; + 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; + } + + if (!api_write_bulk_cstr(c, "forwards")) return 0; + if (!api_write_array(c, s->forwards_count * 2)) return 0; + 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; + } + + if (!api_write_bulk_cstr(c, "marked_for_deletion")) return 0; + if (!api_write_int(c, s->marked_for_deletion)) return 0; + + return 1; +} + +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; + } + + 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; + } + + destroy_session(s); + return api_write_ok(c) ? 1 : 0; +} + +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; + } + + const char *session_id = args[1]; + const char *socket_id = args[2]; + + session_t *s = find_session(session_id); + if (!s) { + return api_write_err(c, "session not found") ? 1 : 0; + } + + socket_t *sock = create_listen_socket(s, socket_id); + if (!sock) { + return api_write_err(c, "failed to create socket") ? 1 : 0; + } + + 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; +} + +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; + } + + const char *session_id = args[1]; + const char *socket_id = args[2]; + const char *ip = args[3]; + int port = atoi(args[4]); + + session_t *s = find_session(session_id); + if (!s) { + return api_write_err(c, "session not found") ? 1 : 0; + } + + socket_t *sock = create_connect_socket(s, socket_id, ip, port); + if (!sock) { + return api_write_err(c, "failed to create socket") ? 1 : 0; + } + + 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; +} + +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; + } + + const char *session_id = args[1]; + const char *socket_id = args[2]; + + session_t *s = find_session(session_id); + if (!s) { + return api_write_err(c, "session not found") ? 1 : 0; + } + + if (destroy_socket(s, socket_id) != 0) { + return api_write_err(c, "socket not found") ? 1 : 0; + } + + return api_write_ok(c) ? 1 : 0; +} + +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; + } + + 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; + } + + if (!api_write_array(c, s->forwards_count * 2)) return 0; + 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; + } + + return 1; +} + +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; + } + + const char *session_id = args[1]; + const char *src_socket_id = args[2]; + const char *dst_socket_id = args[3]; + + session_t *s = find_session(session_id); + if (!s) { + return api_write_err(c, "session not found") ? 1 : 0; + } + + socket_t *src = find_socket(s, src_socket_id); + if (!src) { + return api_write_err(c, "source socket not found") ? 1 : 0; + } + + socket_t *dst = find_socket(s, dst_socket_id); + if (!dst) { + return api_write_err(c, "destination socket not found") ? 1 : 0; + } + + if (add_forward(s, src_socket_id, dst_socket_id) != 0) { + return api_write_err(c, "failed to add forward") ? 1 : 0; + } + + 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; +} + +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; + } + + const char *session_id = args[1]; + const char *src_socket_id = args[2]; + const char *dst_socket_id = args[3]; + + session_t *s = find_session(session_id); + if (!s) { + return api_write_err(c, "session not found") ? 1 : 0; + } + + if (remove_forward(s, src_socket_id, dst_socket_id) != 0) { + return api_write_err(c, "forward not found") ? 1 : 0; + } + + return api_write_ok(c) ? 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); + log_info("udphole: registered session.* commands"); +} + +PT_THREAD(session_manager_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { + (void)timestamp; + log_trace("session_manager: protothread entry"); + PT_BEGIN(pt); + + PT_WAIT_UNTIL(pt, global_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); + + int64_t last_cleanup = 0; + + for (;;) { + int64_t now = (int64_t)(time(NULL)); + if (now - last_cleanup >= 1) { + cleanup_expired_sessions(); + last_cleanup = now; + } + + PT_YIELD(pt); + } + + running = 0; + + for (size_t i = 0; i < sessions_count; i++) { + if (sessions[i]) { + sessions[i]->marked_for_deletion = 1; + } + } + + 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 @@ -0,0 +1,10 @@ +#ifndef UDPHOLE_SESSION_H +#define UDPHOLE_SESSION_H + +#include <stdint.h> + +#include "domain/scheduler.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 diff --git a/src/infrastructure/config.c b/src/infrastructure/config.c @@ -0,0 +1,62 @@ +#include <stdlib.h> +#include <string.h> +#include "benhoyt/inih.h" +#include "rxi/log.h" +#include "infrastructure/config.h" +#include "infrastructure/resp.h" + +resp_object *global_cfg = NULL; +resp_object *pending_cfg = NULL; + +static const char *stored_config_path = NULL; + +static int config_handler(void *user, const char *section, const char *name, const char *value, int lineno) { + (void)lineno; + resp_object *cfg = (resp_object *)user; + resp_object *sec = resp_map_get(cfg, section); + if (!sec || sec->type != RESPT_ARRAY) { + sec = resp_array_init(); + resp_map_set(cfg, section, sec); + sec = resp_map_get(cfg, section); + } + resp_array_append_bulk(sec, name); + resp_array_append_bulk(sec, value); + return 1; +} + +void config_init(void) { + global_cfg = resp_array_init(); + config_load(global_cfg, config_get_path()); +} + +int config_load(resp_object *cfg, const char *path) { + return ini_parse(path, config_handler, cfg); +} + +void config_pending_init(void) { + pending_cfg = resp_array_init(); +} + +void config_swap(void) { + resp_object *old = global_cfg; + global_cfg = pending_cfg; + pending_cfg = old; + if (old) resp_free(old); +} + +int config_reload(void) { + config_pending_init(); + int r = config_load(pending_cfg, config_get_path()); + if (r < 0) return -1; + config_swap(); + return 0; +} + +void config_set_path(const char *path) { + if (stored_config_path) free((void*)stored_config_path); + stored_config_path = path ? strdup(path) : NULL; +} + +const char *config_get_path(void) { + return stored_config_path; +} +\ No newline at end of file diff --git a/src/infrastructure/config.h b/src/infrastructure/config.h @@ -0,0 +1,15 @@ +#ifndef UDPHOLE_CONFIG_H +#define UDPHOLE_CONFIG_H + +#include "infrastructure/resp.h" + +extern resp_object *global_cfg; +extern resp_object *pending_cfg; + +void config_init(void); +int config_load(resp_object *cfg, const char *path); +int config_reload(void); +void config_set_path(const char *path); +const char *config_get_path(void); + +#endif +\ No newline at end of file diff --git a/src/infrastructure/resp.c b/src/infrastructure/resp.c @@ -0,0 +1,331 @@ +#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 @@ -0,0 +1,46 @@ +#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 @@ -0,0 +1,615 @@ +/* + * Generic RESP2 API server: TCP listener, connection management, RESP2 + * parsing/writing, command hashmap, authentication with per-user permit + * checking, and built-in commands (auth, ping, quit, command). + * + * Runs as a protothread in the main select() loop. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "rxi/log.h" +#include "tidwall/hashmap.h" +#include "domain/protothreads.h" +#include "domain/scheduler.h" +#include "common/socket_util.h" +#include "infrastructure/config.h" +#include "interface/api/server.h" +#include "infrastructure/resp.h" + +struct pt_task; +PT_THREAD(api_client_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)); + +#define API_MAX_CLIENTS 8 +#define READ_BUF_SIZE 4096 +#define WRITE_BUF_INIT 4096 +#define MAX_ARGS 32 + +struct api_client_state { + int fd; + int *fds; + int *ready_fds; + int ready_fd; + char *username; + char rbuf[READ_BUF_SIZE]; + size_t rlen; + char *wbuf; + size_t wlen; + size_t wcap; +}; + +typedef struct api_client_state api_client_t; + +typedef struct { + const char *name; + char (*func)(api_client_t *c, char **args, int nargs); +} api_cmd_entry; + +static char *current_listen = NULL; +static struct hashmap *cmd_map = NULL; + +typedef struct { + int *server_fds; + int *ready_fds; +} api_server_udata_t; + +bool api_write_raw(api_client_t *c, const void *data, size_t len) { + if (c->fd < 0) return false; + if (c->wlen + len > c->wcap) { + size_t need = c->wlen + len; + size_t ncap = c->wcap ? c->wcap : WRITE_BUF_INIT; + while (ncap < need) ncap *= 2; + char *nb = realloc(c->wbuf, ncap); + if (!nb) return false; + c->wbuf = nb; + c->wcap = ncap; + } + memcpy(c->wbuf + c->wlen, data, len); + c->wlen += len; + return true; +} + +bool api_write_cstr(api_client_t *c, const char *s) { + return api_write_raw(c, s, strlen(s)); +} + +bool api_write_ok(api_client_t *c) { + return api_write_cstr(c, "+OK\r\n"); +} + +bool api_write_err(api_client_t *c, const char *msg) { + if (!api_write_cstr(c, "-ERR ")) return false; + if (!api_write_cstr(c, msg)) return false; + return api_write_cstr(c, "\r\n"); +} + +bool api_write_nil(api_client_t *c) { + return api_write_cstr(c, "$-1\r\n"); +} + +bool api_write_int(api_client_t *c, int value) { + char buf[32]; + snprintf(buf, sizeof(buf), ":%d\r\n", value); + return api_write_cstr(c, buf); +} + +bool api_write_array(api_client_t *c, size_t nitems) { + char buf[32]; + snprintf(buf, sizeof(buf), "*%zu\r\n", nitems); + return api_write_cstr(c, buf); +} + +bool api_write_bulk_cstr(api_client_t *c, const char *s) { + if (!s) return api_write_nil(c); + size_t len = strlen(s); + char prefix[32]; + snprintf(prefix, sizeof(prefix), "$%zu\r\n", len); + if (!api_write_cstr(c, prefix)) return false; + if (!api_write_raw(c, s, len)) return false; + return api_write_cstr(c, "\r\n"); +} + +bool api_write_bulk_int(api_client_t *c, int val) { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", val); + return api_write_bulk_cstr(c, buf); +} + +static void client_close(api_client_t *c) { + if (c->fd >= 0) { + close(c->fd); + c->fd = -1; + } + free(c->wbuf); + c->wbuf = NULL; + c->wlen = c->wcap = 0; + c->rlen = 0; + free(c->username); + c->username = NULL; +} + +static void client_flush(api_client_t *c) { + if (c->fd < 0 || c->wlen == 0) return; + ssize_t n = send(c->fd, c->wbuf, c->wlen, 0); + if (n > 0) { + if ((size_t)n < c->wlen) + memmove(c->wbuf, c->wbuf + n, c->wlen - (size_t)n); + c->wlen -= (size_t)n; + } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + client_close(c); + } +} + +static int parse_inline(const char *line, size_t len, char **args, int max_args) { + int nargs = 0; + const char *p = line; + const char *end = line + len; + while (p < end && nargs < max_args) { + while (p < end && (*p == ' ' || *p == '\t')) p++; + if (p >= end) break; + const char *start; + const char *tok_end; + if (*p == '"' || *p == '\'') { + char quote = *p++; + start = p; + while (p < end && *p != quote) p++; + tok_end = p; + if (p < end) p++; + } else { + start = p; + while (p < end && *p != ' ' && *p != '\t') p++; + tok_end = p; + } + size_t tlen = (size_t)(tok_end - start); + char *arg = malloc(tlen + 1); + if (!arg) return -1; + memcpy(arg, start, tlen); + arg[tlen] = '\0'; + args[nargs++] = arg; + } + return nargs; +} + +static int parse_resp_command(api_client_t *c, char **args, int max_args, int *nargs) { + *nargs = 0; + if (c->rlen == 0) return 0; + + if (c->rbuf[0] != '*') { + char *nl = memchr(c->rbuf, '\n', c->rlen); + if (!nl) return 0; + size_t line_len = (size_t)(nl - c->rbuf); + size_t trim = line_len; + if (trim > 0 && c->rbuf[trim - 1] == '\r') trim--; + int n = parse_inline(c->rbuf, trim, args, max_args); + if (n < 0) return -1; + *nargs = n; + size_t consumed = line_len + 1; + c->rlen -= consumed; + if (c->rlen > 0) memmove(c->rbuf, c->rbuf + consumed, c->rlen); + return n > 0 ? 1 : 0; + } + + size_t pos = 0; + char *nl = memchr(c->rbuf + pos, '\n', c->rlen - pos); + if (!nl) return 0; + int count = atoi(c->rbuf + 1); + if (count <= 0 || count > max_args) return -1; + pos = (size_t)(nl - c->rbuf) + 1; + + for (int i = 0; i < count; i++) { + if (pos >= c->rlen) return 0; + if (c->rbuf[pos] != '$') return -1; + nl = memchr(c->rbuf + pos, '\n', c->rlen - pos); + if (!nl) return 0; + int blen = atoi(c->rbuf + pos + 1); + if (blen < 0) return -1; + size_t hdr_end = (size_t)(nl - c->rbuf) + 1; + if (hdr_end + (size_t)blen + 2 > c->rlen) return 0; + char *arg = malloc((size_t)blen + 1); + if (!arg) return -1; + memcpy(arg, c->rbuf + hdr_end, (size_t)blen); + arg[blen] = '\0'; + args[i] = arg; + pos = hdr_end + (size_t)blen + 2; + } + + *nargs = count; + c->rlen -= pos; + if (c->rlen > 0) memmove(c->rbuf, c->rbuf + pos, c->rlen); + return 1; +} + +static bool permit_matches(const char *pattern, const char *cmd) { + size_t plen = strlen(pattern); + if (plen == 1 && pattern[0] == '*') + return true; + if (plen >= 2 && pattern[plen - 1] == '*') { + return strncasecmp(pattern, cmd, plen - 1) == 0; + } + return strcasecmp(pattern, cmd) == 0; +} + +static bool user_has_permit(api_client_t *c, const char *cmd) { + char section[128]; + const char *uname = (c->username && c->username[0]) ? c->username : "*"; + snprintf(section, sizeof(section), "user:%s", uname); + resp_object *sec = resp_map_get(global_cfg, section); + if (sec && sec->type == RESPT_ARRAY) { + for (size_t i = 0; i < sec->u.arr.n; i += 2) { + if (i + 1 < sec->u.arr.n) { + resp_object *key = &sec->u.arr.elem[i]; + resp_object *val = &sec->u.arr.elem[i + 1]; + if (key->type == RESPT_BULK && key->u.s && strcmp(key->u.s, "permit") == 0) { + if (val->type == RESPT_ARRAY) { + for (size_t j = 0; j < val->u.arr.n; j++) { + resp_object *p = &val->u.arr.elem[j]; + if (p->type == RESPT_BULK && p->u.s && permit_matches(p->u.s, cmd)) + return true; + } + } else if (val->type == RESPT_BULK && val->u.s && permit_matches(val->u.s, cmd)) { + return true; + } + } + } + } + } + if (strcmp(uname, "*") != 0) { + resp_object *anon = resp_map_get(global_cfg, "user:*"); + if (anon && anon->type == RESPT_ARRAY) { + for (size_t i = 0; i < anon->u.arr.n; i += 2) { + if (i + 1 < anon->u.arr.n) { + resp_object *key = &anon->u.arr.elem[i]; + resp_object *val = &anon->u.arr.elem[i + 1]; + if (key->type == RESPT_BULK && key->u.s && strcmp(key->u.s, "permit") == 0) { + if (val->type == RESPT_ARRAY) { + for (size_t j = 0; j < val->u.arr.n; j++) { + resp_object *p = &val->u.arr.elem[j]; + if (p->type == RESPT_BULK && p->u.s && permit_matches(p->u.s, cmd)) + return true; + } + } else if (val->type == RESPT_BULK && val->u.s && permit_matches(val->u.s, cmd)) { + return true; + } + } + } + } + } + } + return false; +} + +static uint64_t cmd_hash(const void *item, uint64_t seed0, uint64_t seed1) { + const api_cmd_entry *cmd = item; + return hashmap_sip(cmd->name, strlen(cmd->name), seed0, seed1); +} + +static int cmd_compare(const void *a, const void *b, void *udata) { + (void)udata; + const api_cmd_entry *ca = a; + const api_cmd_entry *cb = b; + return strcasecmp(ca->name, cb->name); +} + +void api_register_cmd(const char *name, char (*func)(api_client_t *, char **, int)) { + if (!cmd_map) + cmd_map = hashmap_new(sizeof(api_cmd_entry), 0, 0, 0, cmd_hash, cmd_compare, NULL, NULL); + hashmap_set(cmd_map, &(api_cmd_entry){ .name = name, .func = func }); + log_trace("api: registered 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)"); + return 1; + } + const char *uname = args[1]; + const char *pass = args[2]; + char section[128]; + snprintf(section, sizeof(section), "user:%s", uname); + resp_object *sec = resp_map_get(global_cfg, section); + const char *secret = sec ? resp_map_get_string(sec, "secret") : NULL; + if (secret && pass && strcmp(secret, pass) == 0) { + free(c->username); + c->username = strdup(uname); + if (c->username) { + log_debug("api: client authenticated as '%s'", uname); + return api_write_ok(c) ? 1 : 0; + } + } + return api_write_err(c, "invalid credentials") ? 1 : 0; +} + +static char cmdPING(api_client_t *c, char **args, int nargs) { + (void)args; + if (nargs == 1) + return api_write_cstr(c, "+PONG\r\n") ? 1 : 0; + if (nargs == 2) + return api_write_bulk_cstr(c, args[1]) ? 1 : 0; + return api_write_err(c, "wrong number of arguments for 'ping' command") ? 1 : 0; +} + +static char cmdQUIT(api_client_t *c, char **args, int nargs) { + (void)args; (void)nargs; + api_write_ok(c); + return 0; +} + +static bool is_builtin(const char *name); + +static char cmdCOMMAND(api_client_t *c, char **args, int nargs) { + (void)args; + if (!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; + + 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; + size_t out_len = 0; + if (resp_serialize(result, &out_buf, &out_len) != 0 || !out_buf) { + resp_free(result); + return 0; + } + resp_free(result); + + api_write_raw(c, out_buf, out_len); + free(out_buf); + return 1; +} + +static void init_builtins(void) { + api_register_cmd("auth", cmdAUTH); + api_register_cmd("ping", cmdPING); + api_register_cmd("quit", cmdQUIT); + api_register_cmd("command", cmdCOMMAND); +} + +static bool is_builtin(const char *name) { + return (strcasecmp(name, "auth") == 0 || + strcasecmp(name, "ping") == 0 || + strcasecmp(name, "quit") == 0 || + strcasecmp(name, "command") == 0); +} + +static void dispatch_command(api_client_t *c, char **args, int nargs) { + if (nargs <= 0) return; + + for (char *p = args[0]; *p; p++) *p = (char)tolower((unsigned char)*p); + + const api_cmd_entry *cmd = hashmap_get(cmd_map, &(api_cmd_entry){ .name = args[0] }); + if (!cmd) { + api_write_err(c, "unknown command"); + return; + } + + if (!is_builtin(args[0])) { + if (!user_has_permit(c, args[0])) { + api_write_err(c, "no permission"); + return; + } + } + + char result = cmd->func(c, args, nargs); + if (!result) { + client_flush(c); + client_close(c); + } +} + +static int *create_listen_socket(const char *listen_addr) { + const char *default_port = "6379"; + resp_object *api_sec = resp_map_get(global_cfg, "udphole"); + if (api_sec) { + const char *cfg_port = resp_map_get_string(api_sec, "port"); + if (cfg_port && cfg_port[0]) default_port = cfg_port; + } + int *fds = tcp_listen(listen_addr, NULL, default_port); + if (!fds) { + return NULL; + } + log_info("api: listening on %s", listen_addr); + return fds; +} + +static void handle_accept(int ready_fd) { + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int fd = accept(ready_fd, (struct sockaddr *)&addr, &addrlen); + if (fd < 0) return; + set_socket_nonblocking(fd, 1); + + api_client_t *state = calloc(1, sizeof(*state)); + if (!state) { + const char *msg = "-ERR out of memory\r\n"; + send(fd, msg, strlen(msg), 0); + close(fd); + return; + } + state->fd = fd; + + domain_schedmod_pt_create(api_client_pt, state); + log_trace("api: accepted connection, spawned client pt"); +} + +PT_THREAD(api_server_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { + api_server_udata_t *udata = task->udata; + log_trace("api_server: protothread entry"); + PT_BEGIN(pt); + + if (!udata) { + udata = calloc(1, sizeof(api_server_udata_t)); + if (!udata) { + PT_EXIT(pt); + } + task->udata = udata; + } + + resp_object *api_sec = resp_map_get(global_cfg, "udphole"); + const char *listen_str = api_sec ? resp_map_get_string(api_sec, "listen") : NULL; + if (!listen_str || !listen_str[0]) { + log_info("api: no listen address configured, API server disabled"); + PT_EXIT(pt); + } + + if (!current_listen) { + current_listen = strdup(listen_str); + if (!current_listen) { + PT_EXIT(pt); + } + init_builtins(); + udata->server_fds = create_listen_socket(current_listen); + if (!udata->server_fds) { + free(current_listen); + current_listen = NULL; + PT_EXIT(pt); + } + } + + for (;;) { + (void)timestamp; + if (udata->server_fds && udata->server_fds[0] > 0) { + PT_WAIT_UNTIL(pt, domain_schedmod_has_data(udata->server_fds, &udata->ready_fds) > 0); + if (udata->ready_fds && udata->ready_fds[0] > 0) { + for (int i = 1; i <= udata->ready_fds[0]; i++) { + handle_accept(udata->ready_fds[i]); + } + } + if (udata->ready_fds) free(udata->ready_fds); + udata->ready_fds = NULL; + } else { + PT_YIELD(pt); + } + } + if (udata->server_fds) { + for (int i = 1; i <= udata->server_fds[0]; i++) { + close(udata->server_fds[i]); + } + free(udata->server_fds); + } + free(udata->ready_fds); + free(udata); + free(current_listen); + current_listen = NULL; + if (cmd_map) { + hashmap_free(cmd_map); + cmd_map = NULL; + } + + PT_END(pt); +} + +PT_THREAD(api_client_pt(struct pt *pt, int64_t timestamp, struct pt_task *task)) { + (void)timestamp; + api_client_t *state = task->udata; + + log_trace("api_client: protothread entry fd=%d", state->fd); + PT_BEGIN(pt); + + state->fds = malloc(sizeof(int) * 2); + if (!state->fds) { + free(state); + PT_EXIT(pt); + } + state->fds[0] = 1; + state->fds[1] = state->fd; + + for (;;) { + state->ready_fds = NULL; + PT_WAIT_UNTIL(pt, domain_schedmod_has_data(state->fds, &state->ready_fds) > 0); + + state->ready_fd = -1; + if (state->ready_fds && state->ready_fds[0] > 0) { + for (int i = 1; i <= state->ready_fds[0]; i++) { + if (state->ready_fds[i] == state->fd) { + state->ready_fd = state->fd; + break; + } + } + } + free(state->ready_fds); + state->ready_fds = NULL; + + char buf[1]; + ssize_t n = recv(state->fd, buf, 1, MSG_PEEK); + if (n <= 0) { + break; + } + + resp_object *cmd = resp_read(state->fd); + if (!cmd) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + PT_YIELD(pt); + continue; + } + break; + } + + if (cmd->type != RESPT_ARRAY || cmd->u.arr.n == 0) { + resp_free(cmd); + api_write_err(state, "Protocol error"); + client_flush(state); + continue; + } + + char *args[MAX_ARGS]; + int nargs = 0; + for (size_t i = 0; i < cmd->u.arr.n && nargs < MAX_ARGS; i++) { + resp_object *elem = &cmd->u.arr.elem[i]; + if (elem->type == RESPT_BULK && elem->u.s) { + args[nargs++] = elem->u.s; + elem->u.s = NULL; + } else if (elem->type == RESPT_SIMPLE) { + args[nargs++] = elem->u.s ? elem->u.s : ""; + } + } + + if (nargs > 0) { + dispatch_command(state, args, nargs); + } + + for (int j = 0; j < nargs; j++) { + free(args[j]); + } + resp_free(cmd); + + client_flush(state); + + if (state->fd < 0) break; + } + + if (state->fd >= 0) { + close(state->fd); + } + free(state->fds); + free(state->wbuf); + free(state->username); + free(state); + + PT_END(pt); +} +\ No newline at end of file diff --git a/src/interface/api/server.h b/src/interface/api/server.h @@ -0,0 +1,23 @@ +#ifndef UDPHOLE_API_SERVER_H +#define UDPHOLE_API_SERVER_H + +#include <stdint.h> +#include <stdbool.h> + +#include "domain/scheduler.h" + +struct api_client_state; +typedef struct api_client_state api_client_t; + +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)); + +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); +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 diff --git a/src/interface/cli/command/daemon.c b/src/interface/cli/command/daemon.c @@ -0,0 +1,78 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include "cofyc/argparse.h" +#include "rxi/log.h" + +#include "infrastructure/config.h" +#include "../common.h" +#include "domain/scheduler.h" +#include "daemon.h" +#include "interface/api/server.h" +#include "domain/session.h" + +static int do_daemonize(void) { + pid_t pid = fork(); + if (pid < 0) { + log_fatal("fork: %m"); + return -1; + } + if (pid > 0) + _exit(0); + if (setsid() < 0) { + log_fatal("setsid: %m"); + _exit(1); + } + pid = fork(); + if (pid < 0) { + log_fatal("fork: %m"); + _exit(1); + } + if (pid > 0) + _exit(0); + if (chdir("/") != 0) {} + int fd; + for (fd = 0; fd < 3; fd++) + (void)close(fd); + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + return 0; +} + +int cli_cmd_daemon(int argc, const char **argv) { + int daemonize_flag = 0; + int no_daemonize_flag = 0; + + struct argparse argparse; + struct argparse_option options[] = { + OPT_HELP(), + OPT_BOOLEAN('d', "daemonize", &daemonize_flag, "run in background", NULL, 0, 0), + OPT_BOOLEAN('D', "no-daemonize", &no_daemonize_flag, "force foreground", NULL, 0, 0), + OPT_END(), + }; + argparse_init(&argparse, options, (const char *const[]){"udphole daemon", NULL}, ARGPARSE_STOP_AT_NON_OPTION); + argparse_parse(&argparse, argc, argv); + + if (!no_daemonize_flag && daemonize_flag) { + do_daemonize(); + } + + log_info("udphole: starting daemon"); + + domain_schedmod_pt_create(api_server_pt, NULL); + domain_schedmod_pt_create(session_manager_pt, NULL); + + log_info("udphole: daemon started"); + + return domain_schedmod_main(); +} +\ No newline at end of file diff --git a/src/interface/cli/command/daemon.h b/src/interface/cli/command/daemon.h @@ -0,0 +1,6 @@ +#ifndef UDPHOLE_CLI_DAEMON_H +#define UDPHOLE_CLI_DAEMON_H + +int cli_cmd_daemon(int argc, const char **argv); + +#endif // UDPHOLE_CLI_DAEMON_H +\ No newline at end of file diff --git a/src/interface/cli/command/list_commands.c b/src/interface/cli/command/list_commands.c @@ -0,0 +1,33 @@ +#include <stdio.h> +#include <string.h> + +#include "../common.h" +#include "list_commands.h" + +int cli_cmd_list_commands(int argc, const char **argv) { + (void)argc; + (void)argv; + int len; + int name_longest = 0; + struct cli_command *cmd = cli_commands; + while(cmd) { + len = (int)strlen(cmd->cmd); + if (len > name_longest) { name_longest = len; } + cmd = cmd->next; + } + + int width = cli_get_output_width(80); + int left_col = name_longest + 3; + + printf("\n"); + printf("Available commands:\n"); + cmd = cli_commands; + while (cmd) { + printf("\n %*s ", name_longest, cmd->cmd); + cli_print_wrapped(stdout, cmd->desc, width, left_col); + cmd = cmd->next; + } + printf("\n\n"); + + return 0; +} +\ No newline at end of file diff --git a/src/interface/cli/command/list_commands.h b/src/interface/cli/command/list_commands.h @@ -0,0 +1,6 @@ +#ifndef UDPHOLE_CLI_LIST_COMMANDS_H +#define UDPHOLE_CLI_LIST_COMMANDS_H + +int cli_cmd_list_commands(int argc, const char **argv); + +#endif // UDPHOLE_CLI_LIST_COMMANDS_H +\ No newline at end of file diff --git a/src/interface/cli/common.c b/src/interface/cli/common.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#include "common.h" + +#ifndef NULL +#define NULL (void*)0 +#endif + +struct cli_command *cli_commands = NULL; + +const char *cli_find_arg(int argc, const char **argv, const char *name) { + for (int i = 0; i < argc - 1; i++) + if (strcmp(argv[i], name) == 0) + return argv[i + 1]; + return NULL; +} + +size_t cli_collect_positional(int argc, const char **argv, int start, + const char **out, size_t max_out) { + size_t n = 0; + for (int i = start; i < argc && n < max_out; i++) { + if (argv[i][0] == '-' && argv[i][1] == '-' && argv[i][2] != '\0') { + i++; + continue; + } + out[n++] = argv[i]; + } + return n; +} + +const char *cli_resolve_default_config(void) { + static char buf[1024]; + const char *home = getenv("HOME"); + if (home) { + snprintf(buf, sizeof(buf), "%s/.config/udphole.conf", home); + if (access(buf, R_OK) == 0) return buf; + snprintf(buf, sizeof(buf), "%s/.udphole.conf", home); + if (access(buf, R_OK) == 0) return buf; + } + if (access("/etc/udphole/udphole.conf", R_OK) == 0) return "/etc/udphole/udphole.conf"; + if (access("/etc/udphole.conf", R_OK) == 0) return "/etc/udphole.conf"; + return NULL; +} + +int cli_get_output_width(int default_width) { + if (!isatty(STDOUT_FILENO)) + return default_width; + struct winsize w; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0 || w.ws_col <= 0) + return default_width; + return (int)w.ws_col; +} + +void cli_print_wrapped(FILE *out, const char *text, int width, int left_col_width) { + if (!text || width <= left_col_width) + return; + char *copy = strdup(text); + if (!copy) + return; + int len = left_col_width; + char *tok = strtok(copy, " "); + while (tok) { + int toklen = (int)strlen(tok); + if (len + 1 + toklen > width) { + fprintf(out, "\n%*s", left_col_width, ""); + len = left_col_width; + } + if (len > left_col_width) + fputc(' ', out); + fputs(tok, out); + len += (len > left_col_width ? 1 : 0) + toklen; + tok = strtok(NULL, " "); + } + free(copy); +} + +void cli_register_command(const char *name, const char *description, int (*fn)(int, const char **)) { + struct cli_command *cmd = malloc(sizeof(*cmd)); + cmd->cmd = name; + cmd->desc = description; + cmd->fn = fn; + cmd->next = cli_commands; + cli_commands = cmd; +} + +int cli_execute_command(int argc, const char **argv) { + struct cli_command *cmd = cli_commands; + + while(cmd) { + if (!strcmp(cmd->cmd, argv[0])) return cmd->fn(argc, argv); + cmd = cmd->next; + } + + fprintf(stderr, "Unknown command: %s\n", argv[0]); + return 1; +} +\ No newline at end of file diff --git a/src/interface/cli/common.h b/src/interface/cli/common.h @@ -0,0 +1,33 @@ +#ifndef UDPHOLE_CLI_COMMON_H +#define UDPHOLE_CLI_COMMON_H + +#include <stddef.h> +#include <stdio.h> + +struct cli_command { + void *next; + const char *cmd; + const char *desc; + int (*fn)(int, const char **); +}; + +extern struct cli_command *cli_commands; + +const char *cli_find_arg(int argc, const char **argv, const char *name); + +size_t cli_collect_positional(int argc, const char **argv, int start, + const char **out, size_t max_out); + +void cli_set_config_path(const char *path); +const char *cli_config_path(void); + +const char *cli_resolve_default_config(void); + +int cli_get_output_width(int default_width); + +void cli_print_wrapped(FILE *out, const char *text, int width, int left_col_width); + +void cli_register_command(const char *name, const char *description, int (*fn)(int, const char **)); +int cli_execute_command(int argc, const char **argv); + +#endif // UDPHOLE_CLI_COMMON_H +\ No newline at end of file diff --git a/src/interface/cli/main.c b/src/interface/cli/main.c @@ -0,0 +1,247 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <signal.h> + +#include "cofyc/argparse.h" +#include "rxi/log.h" + +#include "interface/cli/common.h" +#include "interface/cli/command/list_commands.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"); + +static const char *const usages[] = { + "udphole [global] command [local]", + "udphole list-commands", + "udphole --license", + NULL, +}; + +static FILE *log_file; +static char *log_path; +static volatile sig_atomic_t sighup_received; + +static void logfile_callback(log_Event *ev) { + if (sighup_received) { + sighup_received = 0; + if (log_path && log_file) { + fclose(log_file); + log_file = fopen(log_path, "a"); + } + config_reload(); + } + if (log_file) { + char buf[64]; + buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0'; + fprintf(log_file, "%s %-5s %s:%d: ", buf, log_level_string(ev->level), ev->file, ev->line); + vfprintf(log_file, ev->fmt, ev->ap); + fprintf(log_file, "\n"); + fflush(log_file); + } +} + +static void sighup_handler(int sig) { + (void)sig; + sighup_received = 1; +} + +#define MARKER_PARAGRAPH "<!-- paragraph -->" +#define MARKER_LIST_START "<!-- list:start -->" +#define MARKER_LIST_END "<!-- list:end -->" + +static void skip_whitespace(const char **p) { + while (**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++; +} + +static void print_license_paragraph(const char *start, const char *end, int width) { + if (start >= end) return; + while (start < end && (*start == ' ' || *start == '\n' || *start == '\r')) start++; + while (end > start && (end[-1] == ' ' || end[-1] == '\n' || end[-1] == '\r')) end--; + if (start >= end) return; + static char buf[4096]; + size_t n = (size_t)(end - start); + if (n >= sizeof(buf)) n = sizeof(buf) - 1; + memcpy(buf, start, n); + buf[n] = '\0'; + for (size_t i = 0; i < n; i++) + if (buf[i] == '\n') buf[i] = ' '; + cli_print_wrapped(stdout, buf, width, 0); + fputc('\n', stdout); + fputc('\n', stdout); +} + +static void print_license_list(const char *start, const char *end, int width) { + const int left_col = 5; + const char *p = start; + while (p < end) { + skip_whitespace(&p); + if (p >= end) break; + if (*p < '0' || *p > '9') { p++; continue; } + const char *num_start = p; + while (p < end && *p >= '0' && *p <= '9') p++; + if (p >= end || *p != '.') continue; + p++; + skip_whitespace(&p); + const char *text_start = p; + const char *item_end = p; + while (item_end < end) { + const char *next = item_end; + while (next < end && *next != '\n') next++; + if (next < end) next++; + if (next >= end) { item_end = end; break; } + skip_whitespace(&next); + if (next < end && *next >= '0' && *next <= '9') { + const char *q = next; + while (q < end && *q >= '0' && *q <= '9') q++; + if (q < end && *q == '.') break; + } + item_end = next; + } + while (item_end > text_start && (item_end[-1] == ' ' || item_end[-1] == '\n' || item_end[-1] == '\r')) item_end--; + int num = atoi(num_start); + fprintf(stdout, " %2d. ", num); + static char buf[1024]; + size_t n = (size_t)(item_end - text_start); + if (n >= sizeof(buf)) n = sizeof(buf) - 1; + memcpy(buf, text_start, n); + buf[n] = '\0'; + for (size_t i = 0; i < n; i++) + if (buf[i] == '\n' || buf[i] == '\r') buf[i] = ' '; + cli_print_wrapped(stdout, buf, width, left_col); + fputc('\n', stdout); + p = item_end; + } + fputc('\n', stdout); +} + +static void cli_print_license(FILE *out, const char *text, int width) { + const char *p = text; + (void)out; + while (*p) { + const char *q = strstr(p, "<!-- "); + if (!q) { + print_license_paragraph(p, p + strlen(p), width); + break; + } + if (q > p) + print_license_paragraph(p, q, width); + q += 5; + if (strncmp(q, "paragraph -->", 13) == 0) { + q += 13; + skip_whitespace(&q); + const char *r = strstr(q, "<!-- "); + if (!r) r = q + strlen(q); + print_license_paragraph(q, r, width); + p = r; + continue; + } + if (strncmp(q, "list:start -->", 14) == 0) { + q += 14; + skip_whitespace(&q); + const char *r = strstr(q, "<!-- list:end -->"); + if (r) + print_license_list(q, r, width); + p = r ? r + 17 : q + strlen(q); + continue; + } + p = q; + } +} + +int main(int argc, const char **argv) { + const char *loglevel = "info"; + const char *logfile_path = NULL; + const char *config_path = NULL; + static int license_flag = 0; + + cli_register_command( + "list-commands", + "Displays known commands and their descriptions", + cli_cmd_list_commands + ); + + domain_setup(); + + struct argparse argparse; + struct argparse_option options[] = { + OPT_HELP(), + OPT_STRING('f', "config", &config_path, "config file path (default: auto-detect)", NULL, 0, 0), + OPT_STRING('v', "verbosity", &loglevel, "log verbosity: fatal,error,warn,info,debug,trace (default: info)", NULL, 0, 0), + OPT_STRING(0, "log", &logfile_path, "also write log to file (SIGHUP reopens for logrotate)", NULL, 0, 0), + OPT_BOOLEAN(0, "license", &license_flag, "print license and exit", NULL, 0, 0), + OPT_END(), + }; + argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); + argc = argparse_parse(&argparse, argc, argv); + + if (license_flag) { + cli_print_license(stdout, (const char *)gLicenseData, cli_get_output_width(120)); + return 0; + } + + if (argc < 1) { + argparse_usage(&argparse); + return 1; + } + + if (!config_path || !config_path[0]) + config_path = cli_resolve_default_config(); + config_set_path(config_path); + config_init(); + + int level = LOG_INFO; + if (0) { + (void)0; + } else if (!strcasecmp(loglevel, "trace")) { + level = LOG_TRACE; + } else if (!strcasecmp(loglevel, "debug")) { + level = LOG_DEBUG; + } else if (!strcasecmp(loglevel, "info")) { + level = LOG_INFO; + } else if (!strcasecmp(loglevel, "warn")) { + level = LOG_WARN; + } else if (!strcasecmp(loglevel, "error")) { + level = LOG_ERROR; + } else if (!strcasecmp(loglevel, "fatal")) { + level = LOG_FATAL; + } else { + fprintf(stderr, "Unknown log level: %s\n", loglevel); + return 1; + } + log_set_level(level); + setvbuf(stderr, NULL, _IOLBF, 0); + + log_file = NULL; + log_path = NULL; + sighup_received = 0; + + if (logfile_path && logfile_path[0]) { + log_path = strdup(logfile_path); + log_file = fopen(log_path, "a"); + if (log_file) { + log_add_callback(logfile_callback, log_path, level); + signal(SIGHUP, sighup_handler); + } else { + fprintf(stderr, "Could not open log file: %s\n", logfile_path); + free(log_path); + log_path = NULL; + } + } + + return cli_execute_command(argc, argv); +} +\ No newline at end of file diff --git a/src/main.c b/src/main.c @@ -1,278 +0,0 @@ -/// <!-- path: src/main.c --> -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <strings.h> -#include <signal.h> - -#include "cofyc/argparse.h" -#include "rxi/log.h" - -#include "CliModule/setup.h" -#include "CliModule/execute_command.h" -#include "CliModule/common.h" -#include "config.h" - -#ifdef __cplusplus -} -#endif - -extern void appmodule_setup(void); - -#define INCBIN_SILENCE_BITCODE_WARNING -#include "graphitemaster/incbin.h" -INCTXT(License, "LICENSE.md"); - -/// # GLOBAL OPTIONS -/// Global options apply to every **udphole** run. They must appear **before** the command name; everything after the command is passed to that command. -/// -/// **Synopsis** -/// -/// **udphole** [global options] &lt;command&gt; [command options and arguments] -/// **udphole** `-h` | `--help` -/// **udphole** `--license` -/// **udphole** `list-commands` -/// -/// **Options** -/// -/// `-h`, `--help` -/// Print the usage strings and options for this layer to stderr and exit. Shows the usages and the global options; does not list commands. -/// -/// `-f`, `--config` &lt;path&gt; -/// Config file path. If omitted, the following are tried in order: **$HOME/.config/udphole.conf**, **$HOME/.udphole.conf**, **/etc/udphole/udphole.conf**, **/etc/udphole.conf**. Used by all commands that read or write config (daemon). -/// -/// `-v`, `--verbosity` &lt;level&gt; -/// Log verbosity: **fatal**, **error**, **warn**, **info**, **debug**, **trace**. Default: **info**. Applies to the daemon and any command that uses the logging subsystem. -/// -/// `--log` &lt;path&gt; -/// In addition to stderr, append logs to the given file. Send SIGHUP to the process to reopen the file (e.g. after logrotate). Useful when running the daemon. -/// -/// `--license` -/// Print the full license text to stdout (paragraphs and list, wrapped to terminal width) and exit with 0. No config is loaded and no command is run. -/// -/// **Top-level commands** -/// -/// - [**daemon**](#daemon) — Run the UDP hole server. -/// - [**list-commands**](#list-commands) — List available commands and short descriptions. -/// -static const char *const usages[] = { - "udphole [global] command [local]", - "udphole list-commands", - "udphole --license", - NULL, -}; - -/* Log file state for --log and SIGHUP re-open */ -static FILE *log_file; -static char *log_path; -static volatile sig_atomic_t sighup_received; - -static void logfile_callback(log_Event *ev) { - if (sighup_received) { - sighup_received = 0; - if (log_path && log_file) { - fclose(log_file); - log_file = fopen(log_path, "a"); - } - config_reload(); - } - if (log_file) { - char buf[64]; - buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0'; - fprintf(log_file, "%s %-5s %s:%d: ", buf, log_level_string(ev->level), ev->file, ev->line); - vfprintf(log_file, ev->fmt, ev->ap); - fprintf(log_file, "\n"); - fflush(log_file); - } -} - -static void sighup_handler(int sig) { - (void)sig; - sighup_received = 1; -} - -#define MARKER_PARAGRAPH "<!-- paragraph -->" -#define MARKER_LIST_START "<!-- list:start -->" -#define MARKER_LIST_END "<!-- list:end -->" - -static void skip_whitespace(const char **p) { - while (**p == ' ' || **p == '\t' || **p == '\n' || **p == '\r') (*p)++; -} - -static void print_license_paragraph(const char *start, const char *end, int width) { - if (start >= end) return; - while (start < end && (*start == ' ' || *start == '\n' || *start == '\r')) start++; - while (end > start && (end[-1] == ' ' || end[-1] == '\n' || end[-1] == '\r')) end--; - if (start >= end) return; - static char buf[4096]; - size_t n = (size_t)(end - start); - if (n >= sizeof(buf)) n = sizeof(buf) - 1; - memcpy(buf, start, n); - buf[n] = '\0'; - for (size_t i = 0; i < n; i++) - if (buf[i] == '\n') buf[i] = ' '; - cli_print_wrapped(stdout, buf, width, 0); - fputc('\n', stdout); - fputc('\n', stdout); -} - -static void print_license_list(const char *start, const char *end, int width) { - const int left_col = 5; - const char *p = start; - while (p < end) { - skip_whitespace(&p); - if (p >= end) break; - if (*p < '0' || *p > '9') { p++; continue; } - const char *num_start = p; - while (p < end && *p >= '0' && *p <= '9') p++; - if (p >= end || *p != '.') continue; - p++; - skip_whitespace(&p); - const char *text_start = p; - const char *item_end = p; - while (item_end < end) { - const char *next = item_end; - while (next < end && *next != '\n') next++; - if (next < end) next++; - if (next >= end) { item_end = end; break; } - skip_whitespace(&next); - if (next < end && *next >= '0' && *next <= '9') { - const char *q = next; - while (q < end && *q >= '0' && *q <= '9') q++; - if (q < end && *q == '.') break; - } - item_end = next; - } - while (item_end > text_start && (item_end[-1] == ' ' || item_end[-1] == '\n' || item_end[-1] == '\r')) item_end--; - int num = atoi(num_start); - fprintf(stdout, " %2d. ", num); - static char buf[1024]; - size_t n = (size_t)(item_end - text_start); - if (n >= sizeof(buf)) n = sizeof(buf) - 1; - memcpy(buf, text_start, n); - buf[n] = '\0'; - for (size_t i = 0; i < n; i++) - if (buf[i] == '\n' || buf[i] == '\r') buf[i] = ' '; - cli_print_wrapped(stdout, buf, width, left_col); - fputc('\n', stdout); - p = item_end; - } - fputc('\n', stdout); -} - -static void cli_print_license(FILE *out, const char *text, int width) { - const char *p = text; - (void)out; - while (*p) { - const char *q = strstr(p, "<!-- "); - if (!q) { - print_license_paragraph(p, p + strlen(p), width); - break; - } - if (q > p) - print_license_paragraph(p, q, width); - q += 5; - if (strncmp(q, "paragraph -->", 13) == 0) { - q += 13; - skip_whitespace(&q); - const char *r = strstr(q, "<!-- "); - if (!r) r = q + strlen(q); - print_license_paragraph(q, r, width); - p = r; - continue; - } - if (strncmp(q, "list:start -->", 14) == 0) { - q += 14; - skip_whitespace(&q); - const char *r = strstr(q, "<!-- list:end -->"); - if (r) - print_license_list(q, r, width); - p = r ? r + 17 : q + strlen(q); - continue; - } - p = q; - } -} - -int main(int argc, const char **argv) { - const char *loglevel = "info"; - const char *logfile_path = NULL; - const char *config_path = NULL; - static int license_flag = 0; - - climodule_setup(); - appmodule_setup(); - - struct argparse argparse; - struct argparse_option options[] = { - OPT_HELP(), - OPT_STRING('f', "config", &config_path, "config file path (default: auto-detect)", NULL, 0, 0), - OPT_STRING('v', "verbosity", &loglevel, "log verbosity: fatal,error,warn,info,debug,trace (default: info)", NULL, 0, 0), - OPT_STRING(0, "log", &logfile_path, "also write log to file (SIGHUP reopens for logrotate)", NULL, 0, 0), - OPT_BOOLEAN(0, "license", &license_flag, "print license and exit", NULL, 0, 0), - OPT_END(), - }; - argparse_init(&argparse, options, usages, ARGPARSE_STOP_AT_NON_OPTION); - argc = argparse_parse(&argparse, argc, argv); - - if (license_flag) { - cli_print_license(stdout, (const char *)gLicenseData, cli_get_output_width(120)); - return 0; - } - - if (argc < 1) { - argparse_usage(&argparse); - return 1; - } - - /* Resolve config path: explicit -f, then default locations */ - if (!config_path || !config_path[0]) - config_path = cli_resolve_default_config(); - config_set_path(config_path); - config_init(); - - int level = LOG_INFO; - if (0) { - (void)0; - } else if (!strcasecmp(loglevel, "trace")) { - level = LOG_TRACE; - } else if (!strcasecmp(loglevel, "debug")) { - level = LOG_DEBUG; - } else if (!strcasecmp(loglevel, "info")) { - level = LOG_INFO; - } else if (!strcasecmp(loglevel, "warn")) { - level = LOG_WARN; - } else if (!strcasecmp(loglevel, "error")) { - level = LOG_ERROR; - } else if (!strcasecmp(loglevel, "fatal")) { - level = LOG_FATAL; - } else { - fprintf(stderr, "Unknown log level: %s\n", loglevel); - return 1; - } - log_set_level(level); - setvbuf(stderr, NULL, _IOLBF, 0); - - log_file = NULL; - log_path = NULL; - sighup_received = 0; - - if (logfile_path && logfile_path[0]) { - log_path = strdup(logfile_path); - log_file = fopen(log_path, "a"); - if (log_file) { - log_add_callback(logfile_callback, log_path, level); - signal(SIGHUP, sighup_handler); - } else { - fprintf(stderr, "Could not open log file: %s\n", logfile_path); - free(log_path); - log_path = NULL; - } - } - - return climodule_execute_command(argc, argv); -}