Compare commits

..

3 Commits

Author SHA1 Message Date
mute 2f61f2bb1b move win/gatuna-client to gatuna-win, rename project to gatuna
- Directory: win/gatuna-client/ -> gatuna-win/
- Project file: gatuna-client.csproj -> gatuna.csproj
- Assembly name: gatuna-client -> gatuna
- Root namespace: gatuna_client -> gatuna
- All .cs files: namespace gatuna_client -> gatuna
- app.manifest: assemblyIdentity name -> gatuna
- README: updated all paths and references
2026-08-13 08:43:11 +00:00
mute 5bbda4c4c4 finalise docs: accurate header size, PING/PONG, build instructions, repo layout
README:
- Fix header size (8 bytes, not 6) and payload_len field
- Mark client as built (not 'planned')
- Add PING/PONG to protocol summary
- Add client usage section (Discover, mirror ports, Test button, tray)
- Add building from source section (Rust + .NET)
- Add Npcap prerequisite explanation
- Add Windows privilege/UAC note
- Add full repository layout tree

PROTOCOL:
- Fix MANIFEST payload description in frame types table (hostname + entries)
- Add PING/PONG flow diagram and semantics section
2026-08-13 08:40:02 +00:00
mute 094d080a95 disable maximize button and form resizing 2026-08-13 08:34:59 +00:00
10 changed files with 148 additions and 26 deletions
+24 -1
View File
@@ -40,7 +40,7 @@ emitted in v1.
| Type | Name | Direction | session_id | Payload |
|------|-------------|---------------|------------|----------------------------------|
| 0x01 | DISCOVER | C → broadcast | 0 | empty |
| 0x02 | MANIFEST | S → C | 0 | `id:1, proto:1, port:2` × N |
| 0x02 | MANIFEST | S → C | 0 | `hostname_len:1, hostname:N, entries...` |
| 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` |
@@ -212,3 +212,26 @@ client server
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.
## Network test (PING/PONG)
```
client server
| |
| PING { nonce } |
|-------------------------------->|
| |
| PONG { nonce } |
|<--------------------------------|
| |
| (repeated at random intervals) |
| |
```
- The client sends `PING` frames at random 10100 ms intervals, each with a
unique `nonce`.
- The server echoes the nonce verbatim in a `PONG` frame.
- The client correlates `PONG` nonces with outstanding `PING` timestamps to
compute RTT, average latency, jitter (mean absolute delta of consecutive
RTTs), and drop rate (unanswered PINGs).
- PING/PONG frames use `session_id = 0`; they are independent of TCP sessions.
+113 -17
View File
@@ -16,32 +16,35 @@ 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
- **`gatunad`** — Rust server (`gatunad/`). Runs on the peer (Linux) that owns
the real services. Announces upstreams and relays TCP between the tunnel and
`127.0.0.1:<port>`.
- `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.
- **`gatuna`** — .NET 8 WinForms client (`gatuna-win/`). Runs on the
killswitched Windows box. Discovers the server, presents its upstreams as
local loopback listeners, and hauls bytes over the same L2 protocol. Includes
a network test (PING/PONG) for measuring latency, jitter, and loss.
## 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.
- **No IP stack involvement.** Frames carry only our 8-byte header + payload.
- **BPF:** both sides filter on ethertype — the server via classic BPF on
`AF_PACKET` (`SO_ATTACH_FILTER`), the client via Npcap's compiled filter.
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.
- 8-byte header, big-endian:
`[ version:1 ][ type:1 ][ session_id:4 ][ payload_len:2 ][ payload:N ]`.
- `version` = `1`. `payload_len` lets the receiver ignore Ethernet padding
(frames under 60 bytes are zero-padded by the NIC).
- Discovery: client broadcasts `DISCOVER`; server unicasts `MANIFEST` (with
hostname and upstream list) back.
- Sessions: `OPEN``OPEN_ACK` (or `OPEN_NAK`) → `DATA`* ↔ `DATA`* → `CLOSE`.
- Network test: `PING` (with 8-byte nonce) → `PONG` (nonce echoed).
- v1 ships TCP only. UDP frame types are reserved but unimplemented.
## `gatunad` usage
@@ -62,17 +65,82 @@ 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`).
## `gatuna` usage
1. Launch `gatuna.exe` (UAC prompt expected — Npcap requires admin).
2. Select the network adapter connected to the same L2 segment as the server.
3. Click **Discover**. The server's hostname and MAC appear; the upstream list
populates.
4. Check the upstreams you want to use. The **Mirror** column shows the local
loopback port to connect to.
5. Connect your app to `127.0.0.1:<mirror_port>`.
6. Click **Test** to run a PING/PONG network test (latency, jitter, loss).
7. Minimize to tray; close to exit.
### Mirror ports
Mirror ports are deterministic: `port ^ (mac[0]<<8 | mac[5])`, clamped to
≥1024. The same server + upstream always yields the same local port, so you
know where to connect without checking the UI. If the computed port is already
in use, the client falls back to an OS-assigned port.
## Building from source
### Prerequisites
- **Server:** Rust toolchain (edition 2021, MSRV 1.70+), Linux with `AF_PACKET`
support.
- **Client:** .NET 8 SDK with Windows Desktop workload, Npcap installed
(bundled with Wireshark or standalone from <https://npcap.com>).
### Build the server (Linux)
```sh
cd gatunad
cargo build --release
```
The binary is at `gatunad/target/release/gatunad`.
### Build the client (Windows)
```powershell
cd gatuna-win
dotnet build -c Release
```
Or run directly:
```powershell
dotnet run --project gatuna-win -c Release
```
### Npcap
The client requires Npcap's `wpcap.dll` and `Packet.dll` in the system path.
These are installed by the Npcap installer (also bundled with Wireshark). No
additional NuGet packages or driver installs are needed — SharpPcap talks to
the existing Npcap installation.
## Privileges
`AF_PACKET` requires `CAP_NET_RAW`. Run as root, or grant the binary the
capability once:
**Server (Linux):** `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
```
**Client (Windows):** Npcap requires admin by default (`AdminOnly=1` in
`HKLM\SYSTEM\CurrentControlSet\Services\npcap\Parameters`). The client's
`app.manifest` requests `requireAdministrator`, so a UAC prompt appears on
launch. See the [Npcap docs](https://npcap.com/guide/npcap-dev-guide-1.html)
for details on the `AdminOnly` flag if you want to change this.
## Logging
Errors only, to stdout. Normal lifecycle (DISCOVER/OPEN/CLOSE) is silent.
**Server:** errors only, to stdout. Normal lifecycle (DISCOVER/OPEN/CLOSE) is
silent.
**Client:** status line in the UI. Errors are not logged to disk.
## v1 limitations
@@ -82,6 +150,34 @@ Errors only, to stdout. Normal lifecycle (DISCOVER/OPEN/CLOSE) is silent.
on a healthy switched link.
- No auth/crypto. Anyone on the same L2 segment can `DISCOVER` and `OPEN`.
- Single server instance per interface.
- One outstanding `OPEN` at a time on the client (serialized via queue).
## Repository layout
```
gatuna/
├── README.md
├── PROTOCOL.md
├── LICENSE # CC0 1.0 Universal
├── .gitignore
├── gatunad/ # Rust server
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs # cmdline, rx dispatch, tx task
│ ├── link.rs # AF_PACKET, classic BPF, raw Ethernet I/O
│ ├── frame.rs # wire protocol encode/decode
│ ├── upstream.rs # cmdline parsing, hostname, upstream table
│ └── session.rs # session store, socket→tunnel pump
└── gatuna-win/ # .NET 8 WinForms client
├── gatuna.csproj
├── app.manifest # requireAdministrator
├── Program.cs # tray icon, entry point
├── MainForm.cs # UI: adapter picker, discover, test, upstream list
├── TunnelLink.cs # SharpPcap wrapper, raw Ethernet send/recv
├── Frame.cs # wire protocol encode/decode (mirrors Rust frame.rs)
├── SessionManager.cs # session lifecycle, listeners, OPEN serialization
└── PingTest.cs # PING/PONG network test with stats
```
## License
@@ -1,4 +1,4 @@
namespace gatuna_client;
namespace gatuna;
using System.Text;
@@ -1,6 +1,6 @@
using SharpPcap.LibPcap;
namespace gatuna_client;
namespace gatuna;
public partial class MainForm : Form
{
@@ -20,6 +20,8 @@ public partial class MainForm : Form
Text = "gatuna";
Width = 520;
Height = 420;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
StartPosition = FormStartPosition.CenterScreen;
Icon = SystemIcons.GetStockIcon(StockIconId.NetworkConnect, 32);
InitializeComponents();
@@ -1,6 +1,6 @@
using System.Collections.Concurrent;
namespace gatuna_client;
namespace gatuna;
sealed class PingTest
{
@@ -1,4 +1,4 @@
namespace gatuna_client;
namespace gatuna;
static class Program
{
@@ -3,7 +3,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading.Channels;
namespace gatuna_client;
namespace gatuna;
sealed class SessionManager : IDisposable
{
@@ -1,7 +1,7 @@
using SharpPcap;
using SharpPcap.LibPcap;
namespace gatuna_client;
namespace gatuna;
sealed class TunnelLink : IDisposable
{
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="0.1.0.0" name="gatuna-client" />
<assemblyIdentity version="0.1.0.0" name="gatuna" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
@@ -3,7 +3,8 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>gatuna_client</RootNamespace>
<AssemblyName>gatuna</AssemblyName>
<RootNamespace>gatuna</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>