Files
gatuna/PROTOCOL.md
T
mute 25f00b0deb add gatunad server and winforms client v1 (TCP-only)
Raw-Ethernet tunnel over ethertype 0x6969 to bypass WFP killswitches.
Server (Rust, AF_PACKET + classic BPF) relays TCP to 127.0.0.1 services.
Client (.NET 8 WinForms, SharpPcap/Npcap) discovers upstreams and exposes
local loopback listeners. Wire protocol: 6-byte header, 7 frame types,
MANIFEST with labeled upstreams. UDP types reserved, unimplemented.
2026-08-12 18:20:51 +00:00

185 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# gatuna wire protocol
All frames ride Ethernet with ethertype `0x6969`. The ethertype is the sole
discriminator; there is no magic number inside the payload.
## Frame layout
```
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------------------------------+
| version | type | session_id |
+---------------+---------------+-------------------------------+
| payload ... |
+---------------------------------------------------------------+
```
- **version** (u8): protocol version. Currently `1`.
- **type** (u8): frame type, see table below.
- **session_id** (u32, big-endian): `0` for non-session frames; the
server-assigned ID for session-scoped frames.
- **payload** (variable): type-dependent. No length field is carried — the
Ethernet frame length from the capture gives the payload extent.
No CRC, no retransmit, no ordering at this layer. TCP reliability is handled by
the endpoints' TCP stacks; UDP (reserved) will rely on application-level
mechanisms.
Maximum payload: 1500 (Ethernet MTU) 14 (eth header) 6 (our header) =
**1480 bytes**. Larger payloads are not emitted in v1.
## Frame types
| Type | Name | Direction | session_id | Payload |
|------|-------------|---------------|------------|----------------------------------|
| 0x01 | DISCOVER | C → broadcast | 0 | empty |
| 0x02 | MANIFEST | S → C | 0 | `id:1, proto:1, port:2` × N |
| 0x03 | OPEN | C → S | 0 | `upstream_id:1` |
| 0x04 | OPEN_ACK | S → C | assigned | `upstream_id:1` |
| 0x05 | OPEN_NAK | S → C | 0 | `upstream_id:1, reason:1` |
| 0x06 | DATA | both | session | raw bytes (≤1480) |
| 0x07 | CLOSE | both | session | optional `reason:1` |
Reserved (unimplemented in v1; parse returns Err, encode unimplemented):
| Type | Name |
|------|-------------|
| 0x08 | UDP_OPEN |
| 0x09 | UDP_DATA |
| 0x0A | UDP_CLOSE |
## Payload field encodings
### MANIFEST payload
Variable-length entries, parsed sequentially until the payload is consumed.
```
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------------------------------+
| id | proto | port (big-endian) |
+---------------+---------------+-------------------------------+
| label_len | label (UTF-8, label_len bytes) ... |
+---------------+-----------------------------------------------+
```
- **id** (u8): upstream identifier (1-based positional index from `gatunad`
cmdline).
- **proto** (u8): `1 = TCP`, `2 = UDP` (reserved; not emitted in v1).
- **port** (u16, big-endian): the real port on the server's `127.0.0.1`.
- **label_len** (u8): length in bytes of the label that follows. `0` means no
label.
- **label** (`label_len` bytes, UTF-8): human-readable name for the upstream,
taken from the `PORT[:label]` cmdline argument. Maximum 255 bytes.
To parse: read the 5-byte fixed prefix, then `label_len` bytes, and repeat until
the payload is exhausted. The number of entries is not carried explicitly.
### OPEN payload
```
+---------------+
| upstream_id |
+---------------+
```
- **upstream_id** (u8): which MANIFEST entry to open.
### OPEN_ACK payload
```
+---------------+
| upstream_id |
+---------------+
```
- **upstream_id** (u8): echoes the requested upstream. The session is
identified by the `session_id` field in the header, not the payload.
### OPEN_NAK payload
```
+---------------+---------------+
| upstream_id | reason |
+---------------+---------------+
```
- **upstream_id** (u8): echoes the requested upstream.
- **reason** (u8): see reason codes.
### DATA payload
Raw application bytes. Up to 1480 bytes per frame. The `session_id` header
field identifies which session the bytes belong to.
### CLOSE payload
```
+---------------+
| reason? |
+---------------+
```
- **reason** (u8, optional): present iff payload length ≥ 1. See reason codes.
## Reason codes
| Value | Meaning |
|-------|-------------------|
| 0 | unspecified |
| 1 | unknown_upstream |
| 2 | connect_failed |
| 3 | oversize |
| 4 | unknown_session |
## Discovery flow
```
client server
| |
| DISCOVER (dst = broadcast) |
|-------------------------------->|
| |
| MANIFEST (unicast) |
|<--------------------------------|
| |
```
The server learns the client's MAC from the DISCOVER frame's source address and
unicasts the MANIFEST back. The server never speaks unsolicited.
## TCP session lifecycle
```
client server
| |
| OPEN { upstream_id } |
|-------------------------------->|
| | TcpStream::connect(127.0.0.1:port)
| |
| OPEN_ACK { session_id } |
|<--------------------------------| (on success)
| OR |
| OPEN_NAK { reason } |
|<--------------------------------| (on failure)
| |
| DATA { session_id, bytes } |
|<------------------------------->| DATA { session_id, bytes }
| |
| CLOSE { session_id, reason? } |
|<------------------------------->| (on EOF, RST, or error)
| |
```
- `session_id` is allocated by the server as a monotonically increasing u32
(starting at 1) from an atomic counter. Collision by wraparound is ignored.
- Either side may send `CLOSE`. The side receiving `CLOSE` tears down its half
and stops emitting frames for that session.
- The server's socket→tunnel pump reads `TcpStream` in 1480-byte chunks and
emits one `DATA` frame per chunk. On `read` returning 0 (FIN) or an error,
it emits `CLOSE` and exits.
- The server's tunnel→socket path writes `DATA` payloads to the `TcpStream`
with `write_all`. On error it emits `CLOSE` and drops the session.