Server reads its hostname via gethostname(2) and includes it as a length-prefixed UTF-8 string at the start of the MANIFEST payload. Client displays 'Server: <hostname> — <MAC>' in a label above the upstream list. Both sides updated for the new MANIFEST format: [hostname_len:1][hostname:N][entries...] Protocol version unchanged (still 1); MANIFEST payload layout change is backward-incompatible but both sides ship together.
7.3 KiB
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_len (big-endian) | payload ... |
+-------------------------------+ +
| |
+---------------------------------------------------------------+
- version (u8): protocol version. Currently
1. - type (u8): frame type, see table below.
- session_id (u32, big-endian):
0for non-session frames; the server-assigned ID for session-scoped frames. - payload_len (u16, big-endian): number of payload bytes that follow. The receiver reads exactly this many bytes and ignores any trailing Ethernet padding (frames under 60 bytes are zero-padded by the NIC to meet the minimum Ethernet frame size).
- payload (
payload_lenbytes): type-dependent.
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) − 8 (our header) = 1492 bytes. In practice we cap at 1480 to stay conservative. 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
A hostname prefix followed by 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
+---------------+-----------------------------------------------+
| hostname_len | hostname (UTF-8, hostname_len bytes) ... |
+---------------+-----------------------------------------------+
| id | proto | port (big-endian) |
+---------------+---------------+-------------------------------+
| label_len | label (UTF-8, label_len bytes) ... |
+---------------+-----------------------------------------------+
| ... repeated ... |
+---------------------------------------------------------------+
- hostname_len (u8): length in bytes of the server's hostname.
0is valid (unknown hostname). - hostname (
hostname_lenbytes, UTF-8): the server's hostname, read viagethostname(2)at startup. Maximum 255 bytes. - id (u8): upstream identifier (1-based positional index from
gatunadcmdline). - 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.
0means no label. - label (
label_lenbytes, UTF-8): human-readable name for the upstream, taken from thePORT[:label]cmdline argument. Maximum 255 bytes.
To parse: read hostname_len, then hostname_len bytes of hostname, then read
5-byte fixed entry prefixes + label_len bytes of label each, repeating 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_idfield 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_idis 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 receivingCLOSEtears down its half and stops emitting frames for that session. - The server's socket→tunnel pump reads
TcpStreamin 1480-byte chunks and emits oneDATAframe per chunk. Onreadreturning 0 (FIN) or an error, it emitsCLOSEand exits. - The server's tunnel→socket path writes
DATApayloads to theTcpStreamwithwrite_all. On error it emitsCLOSEand drops the session.