justfile, AGENTS.md

This commit is contained in:
2026-08-16 10:36:27 +02:00
parent a66ea31ab3
commit 16d0886f36
8 changed files with 321 additions and 213 deletions
+120 -63
View File
@@ -1,14 +1,23 @@
# PLAN
What I want to do:
A simple one-click Layer 2 tunnel software (Windows 11 client) to bridge people who cannot participate in person at a LAN party to the LAN party. And a simple server endpoint (Linux) software that runs physically at the LAN party and bridges the tunneled traffic and the real LAN network.
A simple one-click Layer 2 tunnel software (Windows 11 client) to bridge people
who cannot participate in person at a LAN party to the LAN party. And a simple
server endpoint (Linux) software that runs physically at the LAN party and
bridges the tunneled traffic and the real LAN network.
I already talked a bit with different AIs about how to do this, here's the current plan:
I already talked a bit with different AIs about how to do this, here's the
current plan:
# LAN Party Tunnel Plan
## LAN Party Tunnel Plan
Build a **TAP-based L2-over-QUIC tunnel**.
The remote Windows client gets a real virtual Ethernet adapter. Ethernet frames from that adapter are sent over QUIC to a public relay. The relay forwards them to a Linux gateway at the LAN party. The Linux gateway injects those frames onto the physical LAN and captures replies.
The remote Windows client gets a real virtual Ethernet adapter. Ethernet frames
from that adapter are sent over QUIC to a public relay. The relay forwards them
to a Linux gateway at the LAN party. The Linux gateway injects those frames onto
the physical LAN and captures replies.
```text
Windows game
@@ -21,13 +30,10 @@ Windows game
⇄ physical Ethernet LAN
```
No WireGuard.
No Npcap.
No Windows bridge.
No packet rewriting from the users real NIC.
No tunnel fragmentation for MVP.
No WireGuard. No Npcap. No Windows bridge. No packet rewriting from the users
real NIC. No tunnel fragmentation for MVP.
## Goal
### Goal
The remote player should do this:
@@ -53,11 +59,12 @@ The public server does this:
lanparty-relay --listen 443/udp
```
UDP/443 is a good default, but the port must be configurable because some networks block QUIC/UDP.
UDP/443 is a good default, but the port must be configurable because some
networks block QUIC/UDP.
## Components
### Components
### 1. Windows client: `lanparty-client.exe`
#### 1. Windows client: `lanparty-client.exe`
Written in Rust.
@@ -75,9 +82,13 @@ Responsibilities:
- keep the relay connection routed through the real internet NIC
```
Use a real TAP/Ethernet adapter. `tap-windows6` is an NDIS TAP-Windows driver used by OpenVPN and other apps, which is the right class of device here because we need Ethernet frames, not just IP packets. ([GitHub][1])
Use a real TAP/Ethernet adapter. `tap-windows6` is an NDIS TAP-Windows driver
used by OpenVPN and other apps, which is the right class of device here because
we need Ethernet frames, not just IP packets. ([GitHub][1])
Do **not** use Wintun for this design. Wintun is L3/TUN-style and does not give you the Ethernet/L2 behavior needed for ARP, DHCP, broadcast discovery, and old LAN games.
Do **not** use Wintun for this design. Wintun is L3/TUN-style and does not give
you the Ethernet/L2 behavior needed for ARP, DHCP, broadcast discovery, and old
LAN games.
The TAP adapter is the remote players LAN-party identity.
@@ -88,7 +99,7 @@ Game sends ARP/broadcast/multicast through TAP
Client tunnels the Ethernet frames
```
### 2. Linux gateway: `lanparty-gateway`
#### 2. Linux gateway: `lanparty-gateway`
Runs on the physical LAN party machine.
@@ -104,15 +115,23 @@ Responsibilities:
- periodically refresh switch CAM table entries
```
Use Linux `AF_PACKET` / `SOCK_RAW` on the real wired NIC. Packet sockets operate at device-driver / OSI Layer 2 level, and `SOCK_RAW` includes the link-layer header, which is exactly what we need for Ethernet frames. ([man7.org][2])
Use Linux `AF_PACKET` / `SOCK_RAW` on the real wired NIC. Packet sockets operate
at device-driver / OSI Layer 2 level, and `SOCK_RAW` includes the link-layer
header, which is exactly what we need for Ethernet frames. ([man7.org][2])
For MVP, run as root. Later, reduce privileges. Opening raw sockets and changing/promiscuous network behavior needs elevated networking privileges; `CAP_NET_ADMIN` covers things like setting promiscuous mode, and `CAP_NET_RAW` covers raw packet access. ([man7.org][3])
For MVP, run as root. Later, reduce privileges. Opening raw sockets and
changing/promiscuous network behavior needs elevated networking privileges;
`CAP_NET_ADMIN` covers things like setting promiscuous mode, and `CAP_NET_RAW`
covers raw packet access. ([man7.org][3])
No Linux bridge is needed for MVP. No `br0`. No moving the hosts IP from `eth0` to a bridge. The gateway daemon directly captures and injects frames on the physical NIC.
No Linux bridge is needed for MVP. No `br0`. No moving the hosts IP from `eth0`
to a bridge. The gateway daemon directly captures and injects frames on the
physical NIC.
Wired Ethernet only. No Wi-Fi gateway mode for MVP. Managed Wi-Fi NICs are not reliable for arbitrary source-MAC injection.
Wired Ethernet only. No Wi-Fi gateway mode for MVP. Managed Wi-Fi NICs are not
reliable for arbitrary source-MAC injection.
### 3. Public relay: `lanparty-relay`
#### 3. Public relay: `lanparty-relay`
Runs on VPS/public server.
@@ -139,7 +158,7 @@ gateway → outbound QUIC → relay
No port forwarding. No NAT traversal pain. Direct P2P can come later.
## Transport
### Transport
Use QUIC.
@@ -158,11 +177,16 @@ disconnect reason
future auth
```
Use QUIC DATAGRAM for Ethernet frames. QUIC DATAGRAM is specifically the unreliable datagram extension for QUIC, which fits Ethernet/game traffic better than reliable streams because old frames should not block newer frames. ([IETF Datatracker][4])
Use QUIC DATAGRAM for Ethernet frames. QUIC DATAGRAM is specifically the
unreliable datagram extension for QUIC, which fits Ethernet/game traffic better
than reliable streams because old frames should not block newer frames. ([IETF
Datatracker][4])
Rust QUIC implementation: start with `quinn`. It exposes `Connection::max_datagram_size()`, which returns the maximum datagram payload size or `None` if datagrams are unsupported/disabled. ([Docs.rs][5])
Rust QUIC implementation: start with `quinn`. It exposes
`Connection::max_datagram_size()`, which returns the maximum datagram payload
size or `None` if datagrams are unsupported/disabled. ([Docs.rs][5])
## No fragmentation for MVP
### No fragmentation for MVP
Do **not** fragment Ethernet frames inside the overlay.
@@ -200,9 +224,10 @@ tap_mtu <= quic_max_datagram_size
- safety_margin
```
No fragment table. No reassembly timeout. No “one lost fragment kills the whole Ethernet frame.” Add fragmentation later only if testing proves it is necessary.
No fragment table. No reassembly timeout. No “one lost fragment kills the whole
Ethernet frame.” Add fragmentation later only if testing proves it is necessary.
## Overlay frame format
### Overlay frame format
Keep the outer routing header small and stable.
@@ -226,13 +251,16 @@ clear routing header
encrypted Ethernet payload
```
MVP can skip payload encryption beyond QUIC, but the wire format should not make later E2E encryption painful.
MVP can skip payload encryption beyond QUIC, but the wire format should not make
later E2E encryption painful.
## Trust model
### Trust model
MVP relay sees plaintext Ethernet frames.
QUIC encrypts traffic on the wire, but because the relay terminates QUIC connections, it decrypts frames from clients and re-encrypts them to the gateway.
QUIC encrypts traffic on the wire, but because the relay terminates QUIC
connections, it decrypts frames from clients and re-encrypts them to the
gateway.
That is acceptable for a LAN-party MVP, but it should be explicitly documented.
@@ -246,7 +274,7 @@ relay only sees room id, peer id, size, timing
Do not retrofit this into a bad packet format later. Reserve the shape now.
## Switching model
### Switching model
Treat the whole thing as a tiny user-space Ethernet switch.
@@ -285,7 +313,7 @@ LAN frames go to matching remote client or all clients if broadcast/multicast
But MAC learning belongs in the real design.
## MAC identity
### MAC identity
Each Windows client needs a unique locally administered unicast MAC.
@@ -295,7 +323,8 @@ Example range:
02:xx:xx:xx:xx:xx
```
Generate once per install or per profile. Store it. Configure TAP with it. Announce it during join.
Generate once per install or per profile. Store it. Configure TAP with it.
Announce it during join.
Relay must reject:
@@ -315,11 +344,13 @@ maybe 2 later for weird cases
This is your responsibility, not the users.
## Linux gateway CAM-table refresh
### Linux gateway CAM-table refresh
The physical LAN switch must learn that remote clients MACs live behind the gateway port.
The physical LAN switch must learn that remote clients MACs live behind the
gateway port.
That happens when the gateway injects frames onto the LAN using the remote clients source MAC.
That happens when the gateway injects frames onto the LAN using the remote
clients source MAC.
But switch CAM entries age out. So the gateway should periodically refresh them.
@@ -330,7 +361,8 @@ for each connected remote MAC:
inject a tiny harmless Ethernet frame with that MAC as source
```
The exact frame can be decided during implementation, but the goal is simple: keep the LAN switch mapping the remote MAC to the gateways physical port.
The exact frame can be decided during implementation, but the goal is simple:
keep the LAN switch mapping the remote MAC to the gateways physical port.
Phase 1 success criterion:
@@ -340,9 +372,10 @@ remote client MAC appears in the LAN switch MAC table on the gateway port
If that is false, the L2 illusion is broken.
## Safety filters
### Safety filters
Remote clients must not be allowed to spray arbitrary L2 control-plane junk onto the real LAN.
Remote clients must not be allowed to spray arbitrary L2 control-plane junk onto
the real LAN.
Drop remote → LAN unconditionally:
@@ -368,7 +401,8 @@ Also drop LAN → remote:
No remote Windows client needs to see switch/control-plane traffic.
EAPOL is especially important: remote clients should never be able to interfere with 802.1X or port authentication behavior on the physical switch.
EAPOL is especially important: remote clients should never be able to interfere
with 802.1X or port authentication behavior on the physical switch.
Add rate limits:
@@ -379,11 +413,12 @@ Add rate limits:
- malformed packet disconnect threshold
```
## Windows routing / metric handling
### Windows routing / metric handling
The TAP adapter may receive DHCP from the party LAN. That is good.
But if DHCP gives it a default gateway, Windows might try to route the relay connection through the tunnel itself. That would break the tunnel.
But if DHCP gives it a default gateway, Windows might try to route the relay
connection through the tunnel itself. That would break the tunnel.
Client startup should:
@@ -396,7 +431,8 @@ Client startup should:
6. detect and neutralize TAP default-route takeover
```
The TAP should be preferred for the party LAN subnet, but it must not steal general internet traffic.
The TAP should be preferred for the party LAN subnet, but it must not steal
general internet traffic.
Also strongly recommend uncommon LAN party subnets:
@@ -409,9 +445,10 @@ bad: 192.168.178.0/24
Duplicate subnet with a remote users home LAN will be painful.
## Relay placement / latency
### Relay placement / latency
Relay-as-data-path is the right MVP. It makes the product work through NAT immediately.
Relay-as-data-path is the right MVP. It makes the product work through NAT
immediately.
But latency becomes:
@@ -421,7 +458,10 @@ client → relay → gateway
So relay location matters.
For Europe/Germany-focused usage, put the relay near the expected players and LAN site, e.g. Frankfurt/Nuremberg/Amsterdam depending on hosting. Later, add direct QUIC path attempts with relay fallback, but do not block MVP on NAT traversal.
For Europe/Germany-focused usage, put the relay near the expected players and
LAN site, e.g. Frankfurt/Nuremberg/Amsterdam depending on hosting. Later, add
direct QUIC path attempts with relay fallback, but do not block MVP on NAT
traversal.
Design the room protocol so future modes are possible:
@@ -431,7 +471,7 @@ mode = direct-p2p
mode = direct-failed-relay-fallback
```
## Logging / diagnostics
### Logging / diagnostics
Phase 1 should log heavily.
@@ -473,9 +513,9 @@ Broadcast traffic flowing
Warning: TAP received default route, adjusted metric
```
## Phase plan
### Phase plan
### Phase 1: prove the illusion
#### Phase 1: prove the illusion
Manual, ugly, real.
@@ -500,7 +540,7 @@ Success criteria:
- one real LAN game discovers or joins a LAN server
```
### Phase 2: multi-client
#### Phase 2: multi-client
```text
- multiple Windows clients
@@ -512,7 +552,7 @@ Success criteria:
- reconnect handling
```
### Phase 3: safety and correctness
#### Phase 3: safety and correctness
```text
- L2 control-plane filters
@@ -524,7 +564,7 @@ Success criteria:
- better malformed-frame handling
```
### Phase 4: product UX
#### Phase 4: product UX
```text
- Windows installer
@@ -536,9 +576,11 @@ Success criteria:
- logs export button
```
Driver signing and TAP bundling must be validated early. `tap-windows6` is the right kind of driver, but Windows driver installation/signing is a product risk, not something to handwave. ([GitHub][1])
Driver signing and TAP bundling must be validated early. `tap-windows6` is the
right kind of driver, but Windows driver installation/signing is a product risk,
not something to handwave. ([GitHub][1])
### Phase 5: better security and latency
#### Phase 5: better security and latency
```text
- invite tokens / auth
@@ -549,7 +591,7 @@ Driver signing and TAP bundling must be validated early. `tap-windows6` is the r
- regional relay selection
```
## Explicit non-goals
### Explicit non-goals
For MVP, do not build:
@@ -565,14 +607,29 @@ For MVP, do not build:
- full internet VPN mode
```
## One-sentence version
### One-sentence version
Build a **Rust Windows TAP client + public QUIC relay + Linux AF_PACKET gateway** that carries one small-MTU Ethernet frame per QUIC datagram, gives each remote player a unique virtual MAC on the real LAN, filters dangerous L2 control traffic, and keeps the physical LAN gateway as the only machine touching the real LAN.
Build a **Rust Windows TAP client + public QUIC relay + Linux AF_PACKET
gateway** that carries one small-MTU Ethernet frame per QUIC datagram, gives
each remote player a unique virtual MAC on the real LAN, filters dangerous L2
control traffic, and keeps the physical LAN gateway as the only machine touching
the real LAN.
[1]: https://github.com/OpenVPN/tap-windows6?utm_source=chatgpt.com "OpenVPN/tap-windows6: Windows TAP driver (NDIS 6)"
[2]: https://man7.org/linux/man-pages/man7/packet.7.html?utm_source=chatgpt.com "packet(7) - Linux manual page"
[3]: https://man7.org/linux/man-pages/man7/capabilities.7.html?utm_source=chatgpt.com "capabilities(7) - Linux manual page"
[4]: https://datatracker.ietf.org/doc/html/rfc9221?utm_source=chatgpt.com "RFC 9221 - An Unreliable Datagram Extension to QUIC"
[5]: https://docs.rs/quinn/latest/quinn/struct.Connection.html?utm_source=chatgpt.com "Connection in quinn - Rust"
[1]:
<https://github.com/OpenVPN/tap-windows6?utm_source=chatgpt.com>
"OpenVPN/tap-windows6: Windows TAP driver (NDIS 6)"
[2]:
<https://man7.org/linux/man-pages/man7/packet.7.html?utm_source=chatgpt.com>
"packet(7) - Linux manual page"
[3]:
<https://man7.org/linux/man-pages/man7/capabilities.7.html?utm_source=chatgpt.com>
"capabilities(7) - Linux manual page"
[4]:
<https://datatracker.ietf.org/doc/html/rfc9221?utm_source=chatgpt.com>
"RFC 9221 - An Unreliable Datagram Extension to QUIC"
[5]:
<https://docs.rs/quinn/latest/quinn/struct.Connection.html?utm_source=chatgpt.com>
"Connection in quinn - Rust"
I want a mono-repo, Rust code, crates into a "crates" folder, one cargo workspace.
I want a mono-repo, Rust code, crates into a "crates" folder, one cargo
workspace.