# gatuna A raw-Ethernet tunnel for reaching services on a peer machine when a WFP killswitch on the local machine blocks normal IP traffic. Two programs carry bytes between their respective loopbacks over a private L2 protocol, bypassing the IP stack (and therefore the killswitch) entirely. ## Why this works WireGuard-for-Windows installs its killswitch as WFP filters at the ALE layers only (`ALE_AUTH_CONNECT_V4/V6`, `ALE_AUTH_RECV_ACCEPT_V4/V6`). It never installs MAC-layer callouts — the L2 code in `tunnel/firewall/` is commented out. Raw Ethernet frames sent via a packet driver (Npcap on Windows, `AF_PACKET` on Linux) never enter the IP stack, so they never reach the ALE classify path and pass unimpeded. See `wireguard-windows/tunnel/firewall/blocker.go:156`. ## Components - `gatunad` — Rust server. Runs on the peer (Linux) that owns the real services. Announces upstreams and relays TCP between the tunnel and `127.0.0.1:`. - `gatuna` (planned) — .NET WinForms client. Runs on the killswitched Windows box. Discovers the server, presents its upstreams as local loopback listeners, and hauls bytes over the same L2 protocol. This repository builds `gatunad` first. ## Transport - **Medium:** raw Ethernet frames on a shared L2 segment. - **Ethertype:** `0x6969` (hardcoded). - **No IP stack involvement.** Frames carry only our 6-byte header + payload. - **BPF:** the server attaches a classic BPF filter `ether proto 0x6969` to its `AF_PACKET` socket so it only wakes on our ethertype. No eBPF authoring. ## Protocol See [`PROTOCOL.md`](PROTOCOL.md) for the full wire format. Summary: - 6-byte header, big-endian: `[ version:1 ][ type:1 ][ session_id:4 ][ payload:N ]`. - `version` = `1`. No length field (frame length comes from the capture). No CRC. Ethertype discriminates our frames from everything else. - Discovery: client broadcasts `DISCOVER`; server unicasts `MANIFEST` back. - Sessions: `OPEN` → `OPEN_ACK` (or `OPEN_NAK`) → `DATA`* ↔ `DATA`* → `CLOSE`. - v1 ships TCP only. UDP frame types are reserved but unimplemented. ## `gatunad` usage ``` gatunad IFACE PORT [PORT...] ``` - `IFACE` — L2 interface name (e.g. `eth0`). - `PORT` — `int[:label]`; a TCP upstream relayed to `127.0.0.1:int`. The label is optional, max 255 bytes, carried in the MANIFEST for the client to display. At least one required. Upstream ID = 1-based positional index in cmdline order. Example: ``` sudo ./gatunad eth0 22 8800:site-a 8080:site-b ``` Exposes upstreams `1→127.0.0.1:22`, `2→127.0.0.1:8800` (label `site-a`), `3→127.0.0.1:8080` (label `site-b`). ## Privileges `AF_PACKET` requires `CAP_NET_RAW`. Run as root, or grant the binary the capability once: ``` sudo setcap cap_net_raw+ep ./target/release/gatunad ``` ## Logging Errors only, to stdout. Normal lifecycle (DISCOVER/OPEN/CLOSE) is silent. ## v1 limitations - TCP only. UDP wire types reserved, code paths stubbed. - No retransmit at the L2 layer. A dropped `DATA` frame breaks the TCP session irrecoverably because the localhost socket already ACKed the bytes. Acceptable on a healthy switched link. - No auth/crypto. Anyone on the same L2 segment can `DISCOVER` and `OPEN`. - Single server instance per interface. ## License CC0 1.0 Universal. See [`LICENSE`](LICENSE).