WIP: request and connection machine

Just need a simple request logging phase to wrap this up

Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2026-05-04 20:29:35 +00:00
parent 19fafc6a8c
commit 19cb63b614
12 changed files with 1180 additions and 605 deletions

3
.clangd Normal file
View file

@ -0,0 +1,3 @@
CompileFlags:
Compiler: gcc
Add: ["-xc", "-std=c2x"]

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
libmuninn.so
libmuninn.dylib
nginx/.cache
.cache
mun_pfx

View file

@ -10,23 +10,22 @@ ifneq ($(MUNINN_NGINX_DEBUG),)
MUNINN_NGINX_DEBUG="--with-debug"
endif
CURDIR = $(.CURDIR)
.PHONY: all clean build
.PHONY: all clean
all: nginx/objs/nginx
nginx/objs/Makefile:
nginx/objs/Makefile: config Makefile
make clean
cd $(NGINX_SOURCE_DIR) && \
./auto/configure \
$(MUNINN_NGINX_DEBUG) \
--with-http_ssl_module \
--with-compat \
--add-module=$(.CURDIR)
--add-module=$(shell pwd)
nginx/objs/nginx: nginx/objs/Makefile
nginx/objs/nginx: nginx/objs/Makefile muninn_core.c muninn_daemon.c muninn.h
$(MUNIN_BEAR) make -C $(NGINX_SOURCE_DIR)
clean:
make -C $(NGINX_SOURCE_DIR) clean
-make -C $(NGINX_SOURCE_DIR) clean

View file

@ -22,13 +22,65 @@ The loose plan to implement Muninn is as follows:
9. Muninn can identify as authoritatively owning a certain DN (and Peers abide).
10. Something other than logging is done for conflicts over who owns what DN
Currently Muninn is on phase 2.
Currently Muninn is working on phase 3.
## Building Muninn
simply run `make`.
Muninn is implemented as a statically linked NGINX Module. The output of the
provided build process is an NGINX binary that contains Muninn functionality. To
build Muninn simply run `make`. The resulting binary will be in the build tree
at `$(pwd)/nginx/objs/nginx`.
## Running Muninn
TODO
Muninn may be ran in any way which NGINX is currently run. See the configuration
section for more details. To add Muninn to an existing running NGINX simply
compile Muninn, copy the output binary over your NGINX executable, and follow
the existing NGINX binary upgrade process.
## Configuring Muninn
TODO
Muninn uses an NGINX global configuration block similar to the existing NGINX
HTTP module. To configure Muninn begin a standard NGINX configuration and open
a Muninn block:
```nginx
error_log /dev/stdout info;
pid /tmp/munin_pid;
daemon off;
events {}
muninn {
}
```
To serve DNS over UDP on port 53 add a `dns_listener` directive like below.
Currently Muninn only supports UDP, but plans to provide for TCP and DoH as
development progresses.
```nginx
error_log /dev/stdout info;
pid /tmp/munin_pid;
daemon off;
events {}
muninn {
dns_listen 0.0.0.0:53;
}
```
The default Muninn DNS over UDP server provides for 1024 simultaneous
connections. To change this add the `dns_connection_pool_count` like so:
```nginx
error_log /dev/stdout info;
pid /tmp/munin_pid;
daemon off;
events {}
muninn {
dns_listen 0.0.0.0:53;
dns_connection_pool_count 24;
}
```

File diff suppressed because it is too large Load diff

16
config
View file

@ -1,5 +1,15 @@
ngx_module_name=muninn
ngx_module_srcs="$ngx_addon_dir/muninn.c"
ngx_module_link=YES
ngx_module_type=CORE
ngx_module_name=ngx_muninn_core_module
ngx_module_srcs="$ngx_addon_dir/muninn_core.c"
ngx_module_link=ADDON
. auto/module
ngx_module_type=CORE
ngx_module_name=ngx_muninn_daemon_module
ngx_module_srcs="$ngx_addon_dir/muninn_daemon.c"
ngx_module_link=ADDON
. auto/module
ngx_addon_name="muninn"

View file

@ -1,27 +0,0 @@
#include <ngx_config.h>
#include <ngx_core.h>
static ngx_command_t ngx_muninn_commands[] = {};
static ngx_core_module_t ngx_muninn_module_ctx = {
ngx_string("muninn"),
NULL, // create conf
NULL // init conf
};
ngx_module_t ngx_muninn_core_module = {
NGX_MODULE_V1,
&ngx_muninn_module_ctx, /* module context */
ngx_muninn_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

18
muninn.h Normal file
View file

@ -0,0 +1,18 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#define NGX_MUNINN_MODULE 0x42000000
#define NGX_MUNINN_CONF 0x00000086
#define MUN_DNS_LEGACY_UDP_SZ 512
#define MUN_DEFAULT_CONN_POOL_SZ 1024
typedef struct {
ngx_uint_t read_state, finalized;
ngx_chain_t *in, *out, *read_cur;
//ngx_pool_t *pool; use conn->pool;
//ngx_log_t *log; use conn->log;
ngx_connection_t *conn;
} mun_dns_request;

55
muninn_core.c Normal file
View file

@ -0,0 +1,55 @@
#include "muninn.h"
static char *mun_conf_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *mun_core_create_conf(ngx_cycle_t *cycle);
static ngx_command_t ngx_muninn_core_commands[] = {
{ ngx_string("muninn"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
mun_conf_server,
0, 0, NULL },
ngx_null_command
};
static ngx_core_module_t ngx_muninn_core_module_ctx = {
ngx_string("muninn_core"),
mun_core_create_conf,
NULL,
};
ngx_module_t ngx_muninn_core_module = {
NGX_MODULE_V1,
&ngx_muninn_core_module_ctx,
ngx_muninn_core_commands,
NGX_CORE_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static void *mun_core_create_conf(ngx_cycle_t *cycle) {
ngx_int_t *conf;
if (!(conf = ngx_pcalloc(cycle->pool, sizeof(ngx_int_t))))
return NULL;
return conf;
}
static char *mun_conf_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
if (!conf) return "no conf";
if (**(ngx_int_t **) conf) return "duplicate block";
**(ngx_int_t **)conf = 1;
cf->module_type = NGX_MUNINN_MODULE;
cf->cmd_type = NGX_MUNINN_CONF;
return ngx_conf_parse(cf, NULL);
}

362
muninn_daemon.c Normal file
View file

@ -0,0 +1,362 @@
#include "ngx_conf_file.h"
#ifdef __clang__
#pragma clang diagnostic ignored "-Wdangling-else"
#else
#pragma GCC diagnostic ignored "-Wdangling-else"
#pragma GCC diagnostic ignored "-Wmultistatement-macros"
#endif
#include "muninn.h"
#include <sys/socket.h>
#include <netinet/in.h>
// Configuration functions
static char *mun_conf_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *mun_conf_connection_ct(ngx_conf_t *cf, ngx_command_t *c, void *v);
// Connection lifecycle
static void mun_init_connection(ngx_connection_t *c);
static void mun_close_connection(ngx_connection_t *c);
static void mun_handle_conn_readable(ngx_event_t *ev);
static void mun_handle_conn_writeable(ngx_event_t *ev);
// Request lifecycle functions
static ngx_int_t mun_log_request(mun_dns_request *r);
// connection pool flag stored here
static ngx_int_t mun_daemon_dns_connection_pool_ct = MUN_DEFAULT_CONN_POOL_SZ;
// Module definitions
static ngx_command_t ngx_muninn_daemon_commands[] = {
{ ngx_string("dns_listen"),
NGX_MUNINN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,
mun_conf_listen,
0, 0, NULL },
{ ngx_string("dns_connection_pool_count"),
NGX_MUNINN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,
mun_conf_connection_ct,
0, 0, NULL },
ngx_null_command
};
static ngx_core_module_t ngx_muninn_daemon_module_ctx = {
ngx_string("muninn_daemon"), NULL, NULL
};
ngx_module_t ngx_muninn_daemon_module = {
NGX_MODULE_V1,
&ngx_muninn_daemon_module_ctx,
ngx_muninn_daemon_commands,
NGX_MUNINN_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
// Muninn request handling state machine
static ngx_int_t (*mun_read_handler_list[])(mun_dns_request *) = {
mun_log_request, // test req state machine logging handler
// TODO: request parsing
// TODO: request logging
// TODO: request cache lookup
// TODO: request record(s) fetch
// TODO: request response generation
// TODO: error handling phase
};
/* ADJACENCY LIST
* This matrix defines the entire request processing state machine and changes
* All numbers in this list should correspond to mun_read_handler_list indexes
* The entries in this list are linked to their same index in the handler list
*
* Entries:
* { NGX_OK next handler - intended to indidicate phase is complete
* NGX_ERROR next handler - intended to trap error states
* NGX_BUSY next handler - intended to trap waiting state
* NGX_DECLINED next handler } intended to trap service refusal
*
* Other handler returns:
* NGX_DONE: this return code means a request is ready to be finalized
* nothing further will be returned.
* NGX_AGAIN: this return code causes same function to be called again.
* primarily used when more input needed than what is available.
* NGX_ABORT: this return code causes all further action to stop for request
*
* For more information, see how this is used in mun_handle_conn_readable
*/
static ngx_uint_t mun_read_handler_adjacency[][4] = {
{0, 0, 0, 0}
};
static char *mun_conf_connection_ct(
ngx_conf_t *cf,
ngx_command_t *cmd,
void *conf
) {
ngx_int_t argc = cf->args->nelts;
ngx_str_t *argv = cf->args->elts;
ngx_int_t num;
if (argc < 1) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
"dns_connection_pool_count requires at least one argument");
return NGX_CONF_ERROR;
}
if ((num = ngx_atoi(argv[1].data, argv[1].len)) == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
"invalid integer passed to dns_connection_pool_count");
return NGX_CONF_ERROR;
}
if (num < 1) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
"dns_connection_pool_count must be set to at least 1");
return NGX_CONF_ERROR;
}
mun_daemon_dns_connection_pool_ct = num;
return NGX_CONF_OK;
}
static char *mun_conf_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *in) {
ngx_url_t *u;
ngx_int_t argc;
ngx_str_t *argv;
ngx_listening_t *listening;
argc = cf->args->nelts;
argv = cf->args->elts;
if (argc < 1) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
"dns_listen needs at least one argument");
return NGX_CONF_ERROR;
}
if (!(u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t)))) return NGX_CONF_ERROR;
u->url = argv[1];
u->listen = 1;
u->default_port = 53;
if (ngx_parse_url(cf->pool, u) != NGX_OK) {
if (u->err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in \"%V\" of the \"dns_listen\" directive",
u->err, &u->url);
}
return NGX_CONF_ERROR;
}
listening = ngx_create_listening(cf, (struct sockaddr *) &u->sockaddr,
u->socklen);
if (!listening) return NGX_CONF_ERROR;
listening->addr_ntop = 1;
listening->pool_size = mun_daemon_dns_connection_pool_ct;
listening->handler = mun_init_connection;
listening->logp = cf->log;
listening->log.data = &listening->addr_text;
listening->log.handler = ngx_accept_log_error;
listening->sndbuf = 1;
listening->rcvbuf = 1;
listening->backlog = mun_daemon_dns_connection_pool_ct * 2;
listening->protocol = IPPROTO_UDP;
listening->type = SOCK_DGRAM;
// listener added to cycle at creation
ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "muninn DNS server enabled");
fprintf(stdout, "MUNINN DNS ON");
return NGX_CONF_OK;
}
static void mun_init_connection(ngx_connection_t *c) {
mun_dns_request *r;
if (!(r = ngx_pcalloc(c->pool, sizeof(mun_dns_request)))) {
mun_close_connection(c);
return;
}
c->data = r;
c->log->connection = c->number;
c->log_error = NGX_ERROR_INFO;
c->read->handler = mun_handle_conn_readable;
c->write->handler = mun_handle_conn_writeable;
if (c->read->ready) ngx_post_event(c->read, &ngx_posted_events)
ngx_log_error(NGX_LOG_NOTICE, c->log, 0, "muninn: new request accepted");
}
static void mun_close_connection(ngx_connection_t *c) {
c->destroyed = 1;
ngx_close_connection(c);
ngx_destroy_pool(c->pool);
}
static void mun_handle_conn_readable(ngx_event_t *ev) {
ngx_connection_t *c;
mun_dns_request *r;
ngx_chain_t *cur;
ngx_int_t ret, idx, space, filled;
c = ev->data;
r = c->data;
ret = 0;
filled = 0;
if (r && r->finalized) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"muninn: read called for finalized request");
return;
}
if (ev->timedout) {
// TODO: handle timedout according to DNS proto
mun_close_connection(c);
return;
}
if (!r->in) {
if (!(r->in = ngx_alloc_chain_link(c->pool))) {
ret = NGX_ERROR;
goto content;
}
if (!(r->in->buf = ngx_create_temp_buf(c->pool,
MUN_DNS_LEGACY_UDP_SZ))) {
ret = NGX_ERROR;
goto content;
}
cur = r->in;
cur->buf->last_buf = 1;
cur->next = NULL;
space = MUN_DNS_LEGACY_UDP_SZ;
} else {
for (cur = r->in; cur && !cur->buf->last_buf; cur = cur->next) {}
if (!cur) {
ngx_log_error(NGX_LOG_ERR, r->conn->log, 0,
"muninn: failed to find next buffer to read into");
}
space = cur->buf->end - cur->buf->last;
}
if (!r->read_cur) {
r->read_cur = r->in;
}
while (1) {
filled = c->recv(c, cur->buf->last, space);
if (c->read->error || c->read->eof) {
// TODO: log
ret = NGX_ERROR;
goto content;
}
if (filled == space) {
cur->buf->last_buf = 0;
if (!(cur = (cur->next = ngx_alloc_chain_link(c->pool))) ||
!(cur->buf = ngx_create_temp_buf(c->pool,
MUN_DNS_LEGACY_UDP_SZ)) ||
!(space = MUN_DNS_LEGACY_UDP_SZ)) break;
cur->next = NULL;
cur->buf->last_buf = 1;
continue;
}
break;
}
content:
if (!ret) ret = mun_read_handler_list[r->read_state](r);
idx = mun_read_handler_adjacency[r->read_state][0 - ret];
r->read_state = idx;
switch (ret) {
case NGX_OK:
// we could still have read data to process in next stage
break;
case NGX_ERROR:
case NGX_BUSY:
case NGX_DECLINED:
ngx_post_event(c->write, &ngx_posted_events);
/* fallthrough */
case NGX_ABORT:
case NGX_AGAIN:
return;
case NGX_DONE:
// TODO: perform request finalization
r->finalized = 1;
return;
}
ngx_post_event(ev, &ngx_posted_events);
}
static void mun_handle_conn_writeable(ngx_event_t *ev) {
ngx_chain_t *tmp;
ngx_connection_t *c;
mun_dns_request *r;
c = ev->data;
r = c->data;
tmp = r->out;
/* this event posted for one of two reasons:
* 1. mun_handle_request_read has reached end of request processing
* 2. socket newly available after incomplete write
*/
// write wouldnt work here anyways. event will retrigger when ready
if (!ev->ready) return;
if (!r->out) {
ngx_log_debug(NGX_LOG_ALERT, r->conn->log, 0,
"muninn: request processed but no output");
return;
}
r->out = c->send_chain(c, tmp, MUN_DNS_LEGACY_UDP_SZ);
for (/* tmp */; tmp != r->out; tmp = tmp->next) {
if (tmp->buf->pos == tmp->buf->last) ngx_free_chain(c->pool, tmp);
}
// request lifecycle complete condition
if (!r->out) return mun_close_connection(c);
// event will retrigger when write possible
if (!ev->ready) return;
// more can be written but we want to yield to other events on task loop
ngx_post_event(ev, &ngx_posted_events);
}
static ngx_int_t mun_log_request(mun_dns_request *r) {
ngx_log_error(NGX_LOG_INFO, r->conn->log, 0,
"muninn: request state machine entered");
return NGX_DONE;
}

View file

@ -0,0 +1,9 @@
error_log info;
pid /tmp/munin_pid;
events {}
muninn {
dns_connection_pool_count 5;
dns_listen 0.0.0.0:8053;
}

20
snippets/test-muninn.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/zsh
set -ex
alias muninn="nginx/objs/nginx -p $(pwd)/mun_pfx -c muninn-test.conf"
# step 1: build
make all -j24
# step 2: set up test pfx
mkdir -p $(pwd)/mun_pfx/logs/
cp $(pwd)/snippets/muninn-test.conf mun_pfx/
# step 3: test configuration
muninn -T
#step 4: test runtime
muninn
sleep 1
muninn -s quit