579 lines
14 KiB
Markdown
579 lines
14 KiB
Markdown
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.
|
||
|
||
I already talked a bit with different AIs about how to do this, here's the current 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.
|
||
|
||
```text
|
||
Windows game
|
||
⇄ Windows TAP adapter
|
||
⇄ lanparty-client.exe
|
||
⇄ QUIC datagrams
|
||
⇄ public relay
|
||
⇄ QUIC datagrams
|
||
⇄ Linux LAN gateway
|
||
⇄ physical Ethernet LAN
|
||
```
|
||
|
||
No WireGuard.
|
||
No Npcap.
|
||
No Windows bridge.
|
||
No packet rewriting from the user’s real NIC.
|
||
No tunnel fragmentation for MVP.
|
||
|
||
## Goal
|
||
|
||
The remote player should do this:
|
||
|
||
```text
|
||
1. Install client.
|
||
2. Start it.
|
||
3. Enter domain / room code.
|
||
4. Click Connect.
|
||
5. Game sees a normal LAN adapter.
|
||
```
|
||
|
||
The physical LAN party host does this:
|
||
|
||
```text
|
||
1. Plug Linux gateway PC into the LAN with wired Ethernet.
|
||
2. Run lanparty-gateway --iface eth0 --room ABCD.
|
||
3. Done.
|
||
```
|
||
|
||
The public server does this:
|
||
|
||
```text
|
||
lanparty-relay --listen 443/udp
|
||
```
|
||
|
||
UDP/443 is a good default, but the port must be configurable because some networks block QUIC/UDP.
|
||
|
||
## Components
|
||
|
||
### 1. Windows client: `lanparty-client.exe`
|
||
|
||
Written in Rust.
|
||
|
||
Responsibilities:
|
||
|
||
```text
|
||
- create/open TAP adapter
|
||
- give the TAP adapter a unique stable MAC address
|
||
- set TAP MTU to a safe small value
|
||
- connect to the relay via QUIC
|
||
- read Ethernet frames from TAP
|
||
- send one Ethernet frame per QUIC datagram
|
||
- receive Ethernet frames from QUIC datagrams
|
||
- write frames back into TAP
|
||
- 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])
|
||
|
||
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 player’s LAN-party identity.
|
||
|
||
```text
|
||
Game binds to TAP
|
||
TAP gets DHCP from real LAN via tunnel
|
||
Game sends ARP/broadcast/multicast through TAP
|
||
Client tunnels the Ethernet frames
|
||
```
|
||
|
||
### 2. Linux gateway: `lanparty-gateway`
|
||
|
||
Runs on the physical LAN party machine.
|
||
|
||
Responsibilities:
|
||
|
||
```text
|
||
- connect outbound to relay
|
||
- open raw L2 socket on the wired LAN interface
|
||
- capture Ethernet frames from the LAN
|
||
- inject remote Ethernet frames onto the LAN
|
||
- learn remote MAC addresses
|
||
- apply safety filters
|
||
- 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])
|
||
|
||
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 host’s 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.
|
||
|
||
### 3. Public relay: `lanparty-relay`
|
||
|
||
Runs on VPS/public server.
|
||
|
||
Responsibilities:
|
||
|
||
```text
|
||
- accept QUIC connections
|
||
- group clients and gateway into rooms
|
||
- forward Ethernet datagrams
|
||
- enforce room limits
|
||
- reject duplicate MACs
|
||
- rate-limit abuse
|
||
- later: auth / invite codes / E2E overlay encryption
|
||
```
|
||
|
||
For MVP, the relay is the full data path, not merely NAT traversal.
|
||
|
||
That gives the best UX:
|
||
|
||
```text
|
||
client → outbound QUIC → relay
|
||
gateway → outbound QUIC → relay
|
||
```
|
||
|
||
No port forwarding. No NAT traversal pain. Direct P2P can come later.
|
||
|
||
## Transport
|
||
|
||
Use QUIC.
|
||
|
||
Use reliable QUIC streams for control:
|
||
|
||
```text
|
||
hello
|
||
join room
|
||
role = client | gateway
|
||
version negotiation
|
||
assigned peer id
|
||
announced MAC
|
||
MTU negotiation
|
||
stats
|
||
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])
|
||
|
||
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
|
||
|
||
Do **not** fragment Ethernet frames inside the overlay.
|
||
|
||
Instead:
|
||
|
||
```text
|
||
small TAP MTU
|
||
one TAP Ethernet frame = one QUIC datagram
|
||
```
|
||
|
||
Startup flow:
|
||
|
||
```text
|
||
1. establish QUIC connection
|
||
2. verify QUIC DATAGRAM support
|
||
3. query max_datagram_size()
|
||
4. compute safe inner MTU
|
||
5. configure TAP MTU
|
||
6. bring TAP up
|
||
```
|
||
|
||
MVP default:
|
||
|
||
```text
|
||
TAP MTU: 1200 or 1280-ish
|
||
hard fail if QUIC datagram budget is too small
|
||
```
|
||
|
||
Formula:
|
||
|
||
```text
|
||
tap_mtu <= quic_max_datagram_size
|
||
- overlay_header_len
|
||
- ethernet_header_len
|
||
- 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.
|
||
|
||
## Overlay frame format
|
||
|
||
Keep the outer routing header small and stable.
|
||
|
||
Example:
|
||
|
||
```text
|
||
magic: u32
|
||
version: u8
|
||
type: u8 // frame, control, keepalive
|
||
room_id: u64
|
||
peer_id: u32
|
||
flags: u16
|
||
payload_len: u16
|
||
payload: Ethernet frame bytes
|
||
```
|
||
|
||
For future relay-blind encryption, split this mentally into:
|
||
|
||
```text
|
||
clear routing header
|
||
encrypted Ethernet payload
|
||
```
|
||
|
||
MVP can skip payload encryption beyond QUIC, but the wire format should not make later E2E encryption painful.
|
||
|
||
## 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.
|
||
|
||
That is acceptable for a LAN-party MVP, but it should be explicitly documented.
|
||
|
||
Future version:
|
||
|
||
```text
|
||
client/gateway share room key
|
||
Ethernet payload is AEAD-encrypted before QUIC
|
||
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
|
||
|
||
Treat the whole thing as a tiny user-space Ethernet switch.
|
||
|
||
Maintain:
|
||
|
||
```text
|
||
MAC -> peer_id
|
||
peer_id -> QUIC connection
|
||
last_seen timestamp
|
||
```
|
||
|
||
Forwarding rules:
|
||
|
||
```text
|
||
source MAC from client:
|
||
learn source MAC -> client
|
||
|
||
known unicast:
|
||
forward only to target peer/gateway
|
||
|
||
broadcast/multicast:
|
||
flood to gateway and relevant clients
|
||
|
||
unknown unicast:
|
||
flood initially, later rate-limit
|
||
|
||
never reflect frame back to ingress peer
|
||
```
|
||
|
||
For MVP, simplify:
|
||
|
||
```text
|
||
remote client frames mostly go to gateway
|
||
LAN frames go to matching remote client or all clients if broadcast/multicast
|
||
```
|
||
|
||
But MAC learning belongs in the real design.
|
||
|
||
## MAC identity
|
||
|
||
Each Windows client needs a unique locally administered unicast MAC.
|
||
|
||
Example range:
|
||
|
||
```text
|
||
02:xx:xx:xx:xx:xx
|
||
```
|
||
|
||
Generate once per install or per profile. Store it. Configure TAP with it. Announce it during join.
|
||
|
||
Relay must reject:
|
||
|
||
```text
|
||
- duplicate MAC in same room
|
||
- broadcast/multicast source MAC
|
||
- obviously invalid MAC
|
||
- too many source MACs per client
|
||
```
|
||
|
||
Default policy:
|
||
|
||
```text
|
||
1 MAC per client
|
||
maybe 2 later for weird cases
|
||
```
|
||
|
||
This is your responsibility, not the user’s.
|
||
|
||
## Linux gateway CAM-table refresh
|
||
|
||
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 client’s source MAC.
|
||
|
||
But switch CAM entries age out. So the gateway should periodically refresh them.
|
||
|
||
Every ~60 seconds:
|
||
|
||
```text
|
||
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 gateway’s physical port.
|
||
|
||
Phase 1 success criterion:
|
||
|
||
```text
|
||
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
|
||
|
||
Remote clients must not be allowed to spray arbitrary L2 control-plane junk onto the real LAN.
|
||
|
||
Drop remote → LAN unconditionally:
|
||
|
||
```text
|
||
- EAPOL / 802.1X
|
||
- STP / BPDUs
|
||
- LLDP
|
||
- LACP
|
||
- DHCP server replies
|
||
- IPv6 Router Advertisements
|
||
- jumbo frames
|
||
- frames from unauthorized source MACs
|
||
```
|
||
|
||
Also drop LAN → remote:
|
||
|
||
```text
|
||
- EAPOL
|
||
- STP
|
||
- LLDP
|
||
- LACP
|
||
```
|
||
|
||
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.
|
||
|
||
Add rate limits:
|
||
|
||
```text
|
||
- broadcast/multicast per client
|
||
- unknown unicast per client
|
||
- total bandwidth per client
|
||
- malformed packet disconnect threshold
|
||
```
|
||
|
||
## 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.
|
||
|
||
Client startup should:
|
||
|
||
```text
|
||
1. resolve relay domain before TAP is active
|
||
2. remember current real default gateway/interface
|
||
3. add explicit host route to relay IP via real NIC
|
||
4. bring TAP up
|
||
5. set TAP interface metric appropriately
|
||
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.
|
||
|
||
Also strongly recommend uncommon LAN party subnets:
|
||
|
||
```text
|
||
good: 10.73.42.0/24
|
||
bad: 192.168.0.0/24
|
||
bad: 192.168.1.0/24
|
||
bad: 192.168.178.0/24
|
||
```
|
||
|
||
Duplicate subnet with a remote user’s home LAN will be painful.
|
||
|
||
## Relay placement / latency
|
||
|
||
Relay-as-data-path is the right MVP. It makes the product work through NAT immediately.
|
||
|
||
But latency becomes:
|
||
|
||
```text
|
||
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.
|
||
|
||
Design the room protocol so future modes are possible:
|
||
|
||
```text
|
||
mode = relay
|
||
mode = direct-p2p
|
||
mode = direct-failed-relay-fallback
|
||
```
|
||
|
||
## Logging / diagnostics
|
||
|
||
Phase 1 should log heavily.
|
||
|
||
Gateway frame log:
|
||
|
||
```text
|
||
direction
|
||
src MAC
|
||
dst MAC
|
||
ethertype
|
||
length
|
||
peer id
|
||
action = forwarded | dropped | filtered | rate-limited
|
||
```
|
||
|
||
Client diagnostics:
|
||
|
||
```text
|
||
relay reachable: yes/no
|
||
QUIC datagram support: yes/no
|
||
max datagram size
|
||
TAP adapter found: yes/no
|
||
TAP MAC
|
||
TAP MTU
|
||
TAP IP from DHCP
|
||
relay route pinned: yes/no
|
||
frames rx/tx
|
||
drops
|
||
```
|
||
|
||
User-facing diagnostics should eventually say things like:
|
||
|
||
```text
|
||
Connected to relay
|
||
Connected to LAN gateway
|
||
DHCP received: 10.73.42.51
|
||
Gateway latency: 23 ms
|
||
Broadcast traffic flowing
|
||
Warning: TAP received default route, adjusted metric
|
||
```
|
||
|
||
## Phase plan
|
||
|
||
### Phase 1: prove the illusion
|
||
|
||
Manual, ugly, real.
|
||
|
||
```text
|
||
- manual TAP install on Windows
|
||
- Rust Windows client opens TAP
|
||
- fixed TAP MTU, e.g. 1200
|
||
- Linux gateway opens AF_PACKET on wired eth0
|
||
- relay forwards one client
|
||
- no auth except room string
|
||
- no fragmentation
|
||
- heavy frame logging
|
||
```
|
||
|
||
Success criteria:
|
||
|
||
```text
|
||
- Windows TAP gets DHCP from real LAN
|
||
- Windows client can ARP LAN host
|
||
- Windows client can ping LAN host
|
||
- remote MAC appears in switch MAC table on gateway port
|
||
- one real LAN game discovers or joins a LAN server
|
||
```
|
||
|
||
### Phase 2: multi-client
|
||
|
||
```text
|
||
- multiple Windows clients
|
||
- unique MAC generation
|
||
- duplicate MAC rejection
|
||
- MAC learning
|
||
- broadcast/multicast fanout
|
||
- CAM refresh frames
|
||
- reconnect handling
|
||
```
|
||
|
||
### Phase 3: safety and correctness
|
||
|
||
```text
|
||
- L2 control-plane filters
|
||
- DHCP server reply filtering
|
||
- IPv6 RA filtering
|
||
- MAC limits
|
||
- rate limits
|
||
- route/metric protection
|
||
- better malformed-frame handling
|
||
```
|
||
|
||
### Phase 4: product UX
|
||
|
||
```text
|
||
- Windows installer
|
||
- TAP driver install/check
|
||
- simple GUI
|
||
- room code / domain field
|
||
- diagnostics screen
|
||
- configurable relay port
|
||
- 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])
|
||
|
||
### Phase 5: better security and latency
|
||
|
||
```text
|
||
- invite tokens / auth
|
||
- room ACLs
|
||
- optional room-key E2E payload encryption
|
||
- direct QUIC path attempt
|
||
- relay fallback
|
||
- regional relay selection
|
||
```
|
||
|
||
## Explicit non-goals
|
||
|
||
For MVP, do not build:
|
||
|
||
```text
|
||
- Npcap mode
|
||
- WinDivert mode
|
||
- source-IP rewriting
|
||
- Windows bridge
|
||
- Hyper-V virtual switch
|
||
- WireGuard underlay
|
||
- custom Ethernet fragmentation
|
||
- Wi-Fi LAN gateway support
|
||
- full internet VPN mode
|
||
```
|
||
|
||
## 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.
|
||
|
||
[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.
|