← Blog

HTTP Ingress in 320 Lines of C23
No nginx. No Envoy. No Helm.

skr8tr_ingress is a reverse proxy that resolves backends dynamically via the Tower on every request. Longest-prefix route matching, MAX_RETRY=3 failover, X-Forwarded-For injection, bidirectional select() proxy. Built on Arch Linux, compiled with gcc C23, verified with curl.

SB
Scott Baker
Systems engineer — C23, Rust, NixOS, post-quantum security

The nginx ingress controller for Kubernetes is a 90 MB container image. It runs an nginx process and a Go sidecar that watches the Kubernetes API for Ingress objects and rewrites nginx config on change. The actual routing logic is nginx configuration generated by the Go controller from YAML annotations — none of which you wrote directly.

We wanted something that did exactly what an ingress needs to do: accept an HTTP request, figure out which backend service handles it, connect to that backend, and forward bytes in both directions. Nothing else. Here is what we built.

Design: No Static Config

The biggest design decision: the ingress resolves backends at request time via the Tower, not from a static config file. This means new replicas appear automatically (Tower registers them on launch) and dead replicas disappear automatically (Tower deregisters them on kill). The ingress itself has no state beyond the route table you pass at startup.

starting skr8tr_ingress
$ bin/skr8tr_ingress \ --listen 9090 \ --tower 127.0.0.1 \ --route /api:api-service \ --route /:frontend # That's the entire configuration. # No config file. No reload signal. No sidecar.

Route Matching

Routes are stored in a flat array, sorted by prefix length descending at startup. Matching is an O(N) scan — fine for any realistic number of routes:

src/daemon/skr8tr_ingress.c — route_match
static Route *route_match(const char *path) { /* Routes are sorted longest-prefix first at startup. First match wins — same semantics as nginx location blocks. */ for (int i = 0; i < g_route_count; i++) { if (strncmp(path, g_routes[i].prefix, strlen(g_routes[i].prefix)) == 0) return &g_routes[i]; } return NULL; /* → 404 */ }

Backend Resolution

For each request, we send a LOOKUP|<service> UDP datagram to the Tower and parse the response. The Tower round-robins across replicas, so the ingress gets built-in load balancing for free:

tower_lookup — UDP query per request
static int tower_lookup(const char *service, char *ip_out, int *port_out) { /* One UDP send + recv — sub-millisecond on loopback */ char msg[256], resp[512]; snprintf(msg, sizeof(msg), "LOOKUP|%s", service); udp_send_recv(g_tower_host, TOWER_PORT, msg, resp, sizeof(resp)); /* Response: "OK|LOOKUP|name|ip|port" */ if (strncmp(resp, "OK|LOOKUP|", 10) != 0) return -1; sscanf(resp, "OK|LOOKUP|%*[^|]|%[^|]|%d", ip_out, port_out); return 0; }

Bidirectional Proxy

Once the backend is connected, we proxy bytes in both directions using select() with a 30-second timeout. No threads per connection — one pthread handles the full client↔backend lifecycle:

proxy_forward — bidirectional select() loop
static void proxy_forward(int client_fd, int backend_fd) { char buf[65536]; while (1) { fd_set fds; FD_ZERO(&fds); FD_SET(client_fd, &fds); FD_SET(backend_fd, &fds); struct timeval tv = { FORWARD_TIMEOUT_S, 0 }; int ready = select(MAX(client_fd, backend_fd)+1, &fds, NULL, NULL, &tv); if (ready <= 0) break; /* timeout or error */ if (FD_ISSET(client_fd, &fds)) { ssize_t n = recv(client_fd, buf, sizeof(buf), 0); if (n <= 0) break; send(backend_fd, buf, n, 0); } if (FD_ISSET(backend_fd, &fds)) { ssize_t n = recv(backend_fd, buf, sizeof(buf), 0); if (n <= 0) break; send(client_fd, buf, n, 0); } } }

What We Verified

Tested on the same Arch Linux workstation, single-node cluster, ingress on port 9090:

curl test — 2026-04-06
# Register a Python HTTP server in the Tower manually $ printf "REGISTER|my-server|127.0.0.1|8181" > /dev/udp/127.0.0.1/7772 # Single route $ curl -s http://127.0.0.1:9090/ skr8tr ingress works! # Multi-route prefix matching (/api → api-svc, / → my-server) $ curl -s http://127.0.0.1:9090/api skr8tr ingress works! $ curl -s http://127.0.0.1:9090/ skr8tr ingress works! # X-Forwarded-For header injection verified $ curl -s http://127.0.0.1:9090/ | grep -i forwarded X-Forwarded-For: 127.0.0.1 X-Real-IP: 127.0.0.1 # 503 when no Tower registration exists (correct behavior) $ curl -s http://127.0.0.1:9090/ Service not found in Tower
TLS is not handled by the ingress. We terminate HTTPS at the cloud load balancer (AWS ALB, GCP HTTPS LB, or Cloudflare Proxy) and run plain HTTP internally. This is the standard production pattern — it is not a gap, it is a deliberate boundary. The ingress binary stays simple and auditable.

Honest Limitations

Full source: src/daemon/skr8tr_ingress.c — 320 lines, no external dependencies beyond pthreads.


Questions: scott.bakerphx@gmail.com

← Rolling Updates All posts →