XDP · eBPF · Rust

The ingress shield that lives in the kernel.

equinox validates and load-balances traffic inside the NIC driver with XDP — before a packet ever reaches your host stack. No userspace proxy in the hot path, no extra hop, no per-connection overhead.

XDP
Runs in the driver, before SKB allocation
0 ms
Downtime on reload — atomic buffer switch
650
Backends per buffer via Maglev hashing
L4
In-kernel validation & DNAT

Core features

Everything you need to put a kernel-speed shield in front of your services.

In-kernel validation

Truncated packets and illegal TCP flag combinations (NULL, SYN+FIN, SYN+RST, FIN+RST, XMAS) are dropped at the NIC. Unrelated traffic — SSH, everything off your listen ports — passes straight through, untouched.

Maglev load balancing

Consistent hashing with a 65,537-entry table keeps flows pinned to backends even as the set changes. Packets are DNAT'd and re-transmitted with XDP_TX — no userspace round trip.

Zero-downtime hot reload

Routing tables are double-buffered. The control plane fills a standby buffer and flips a single atomic value to switch over. The XDP program is never detached; traffic never stops.

Abuse protection

Per-source-IP sliding windows count requests and malformed packets. Cross the limit and the source is blacklisted in-kernel for five minutes — all of it happening in the data plane, not after the fact.

Active health checking

Backends are TCP-probed every reload. A crashed instance is evicted from the table within a cycle and re-added automatically when it recovers. No traffic black-holed.

Flexible discovery

Find backends statically, through the Docker socket, or via DNS. Ports are optional and override per route. Edit the config and it reloads live.

Opt-in observability

Set one address and get Prometheus /metrics — routed and dropped-by-reason counters, backend gauges — plus a /healthz readiness probe. Off by default, zero setup to skip it.

Native XDP, with fallback

Attaches in native driver mode where the NIC supports it (Intel, Mellanox, virtio-net) and falls back to SKB mode everywhere else. One config line to force a mode.

Why it's fast

A traditional load balancer copies every packet into userspace, makes a decision, and copies it back. equinox decides inside the NIC driver.

  • Before the stack. The XDP hook runs before skb allocation — the kernel hasn't paid for the packet yet.
  • No proxy in the path. Routing is a hash lookup and a checksum fixup, then XDP_TX straight back out the wire.
  • No connection state. Maglev means consistent routing without tracking every flow, so memory and CPU stay flat under load.
  • RFC 1624 incremental checksums. Only the changed bytes are recomputed, never the whole packet.

Quickstart

Run it in Docker (recommended) or directly on the host — pick a path and drop a config.yaml next to it. Edit the file live to hot-reload.

terminal
# 1. Pull the image
$ docker pull ghcr.io/you/equinox:latest

# 2. Run it — zero config, starts on the baked-in default
$ docker run --rm --network host --privileged \
    ghcr.io/you/equinox:latest

# To customise: mount the current dir. A config.yaml template is
# seeded for you on first run — edit it and it hot-reloads.
$ docker run --rm --network host --privileged \
    -v "$(pwd):/app" ghcr.io/typicallhavok/equinox:latest

# …or bring up the shield + demo backends together
$ docker compose up --build

No volume flag needed to start — the image ships a default config. The interface is auto-detected from the default route (override with -e IFACE=eth0), and --cap-add NET_ADMIN SYS_ADMIN BPF works in place of --privileged on most kernels.

Configuration

A single YAML file, read on start and re-read on every edit. Here is a complete example with every block; only gateway and discovery are required.

config.yaml
gateway:
  listen_ports: [80, 443]   # ports to shield; all else passes through
  xdp_mode: "auto"          # auto | skb | drv | hw

discovery:
  strategy: "docker"       # static | docker | dns
  sync_interval_ms: 3000   # re-discover this often
  drop_unmatched: false    # drop vs. pass when no backend
  network: "equinox_backends"
  # static_routes: [{ ip: "172.18.0.10", port: 3000 }]

protection:               # per-source-IP abuse limits
  enabled: true
  rate_limit_per_sec: 5000
  malformed_limit: 20
  window_ms: 1000
  block_duration_secs: 300  # blacklist for 5 min

health_check:             # evict dead backends
  enabled: true
  timeout_ms: 500

# observability:             # opt-in; omit to keep off
#   metrics_addr: "0.0.0.0:9100"
Setting Default What it does
listen_ports 80, 443 Ports intercepted & validated; everything else passes straight to the host.
xdp_mode auto Attach mode. auto tries native and falls back to SKB.
strategy Where backends come from: static, docker socket, or dns.
sync_interval_ms 3000 How often discovery re-runs to pick up scaled or crashed instances.
drop_unmatched false Drop validated packets with no backend instead of passing them.
protection.enabled true In-kernel per-source rate & malformed limiting with timed blacklist.
block_duration_secs 300 How long a tripped source stays blacklisted.
health_check.enabled true TCP-probe backends each reload; only healthy ones get traffic.
metrics_addr off Set it to expose Prometheus /metrics + /healthz.

Put the shield in front of your services.

Linux + Docker. Apache-2.0 control plane, GPL-2.0 data plane.