Skip to content

Commit 1a22dac

Browse files
committed
WIP
1 parent 99305d9 commit 1a22dac

7 files changed

Lines changed: 389 additions & 16 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,9 @@ fabric.properties
6969

7070
/node_modules/
7171
/vendor/
72+
73+
# Project-specific
7274
/static/
7375
/build/
76+
/cmd/awl-tray/awl-tray
77+
/cmd/awl/awl

CLAUDE.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## What is AWL
6+
7+
Anywherelan (awl) is a peer-to-peer mesh VPN written in Go. It uses libp2p for P2P networking, WireGuard's TUN driver for the virtual network interface, and supports SOCKS5 proxy (exit nodes). There is also a fuller human-facing dev guide in `AGENTS.md` (general) and `AGENTS_test.md` (tests).
8+
9+
## Repository layout — Go modules
10+
11+
The repo contains **two** Go modules. They have independent dependency graphs and must be tidied separately:
12+
13+
- `go.mod` — root module `github.com/anywherelan/awl`: all of the core code (application, p2p, vpn, service, api, cli, config, …) plus `cmd/awl` and `cmd/gomobile-lib`.
14+
- `cmd/awl-tray/go.mod` — desktop tray app. Separate module so that the tray/GUI dependencies (systray, webview, zenity bindings) don't leak into the headless server binary. You **cannot** `go build ./cmd/awl-tray` from root; use `./build.sh awl-tray` or `cd cmd/awl-tray && go build`.
15+
16+
Always run `go mod tidy -compat=1.26` (not `-compat=1.x`) in whichever module you changed.
17+
18+
## Commands
19+
20+
### Build
21+
22+
```bash
23+
# Desktop binary (awl-tray) for current platform — this handles the separate awl-tray module
24+
./build.sh awl-tray
25+
26+
# Headless server binary (root module)
27+
go build github.com/anywherelan/awl/cmd/awl
28+
29+
# Web UI assets (requires Flutter + the sibling repo awl-flutter)
30+
./build.sh web
31+
32+
# Before first build on Windows (or cross-compiling to Windows), download wintun:
33+
./build.sh deps
34+
```
35+
36+
Version is injected at build time: `-ldflags "-X github.com/anywherelan/awl/config.Version=${VERSION}"`. The default at `config/version.go` is `DevVersion = "dev"`; `config.IsDevVersion()` / `Config.DevMode()` gate dev-only behavior.
37+
38+
The web UI is served from `//go:embed static` in `application.go`. Because Go's `embed` refuses to compile if the directory is missing, **tests and local builds both require `static/` to exist**, even if empty. If a fresh clone has no `static/`:
39+
40+
```bash
41+
mkdir -p static && touch static/index.html
42+
```
43+
44+
### Swagger / OpenAPI
45+
46+
API types are documented inline with `swag` annotations. After changing API request/response structs or handlers, regenerate:
47+
48+
```bash
49+
go generate ./...
50+
# equivalent: cd <root> && go run github.com/swaggo/swag/cmd/swag@latest init --parseDependency -g application.go
51+
```
52+
53+
The `go:generate` directive lives at the top of `application.go`.
54+
55+
### Test
56+
57+
```bash
58+
go test -count=1 ./...
59+
go test -race -count=1 ./...
60+
61+
# Single test
62+
go test -run TestName ./...
63+
64+
# Benchmarks
65+
go test -bench=. ./...
66+
```
67+
68+
Tests spin up real `Application` instances against real libp2p + DHT. See "Test Infrastructure" below.
69+
70+
#### Simulated network performance tests
71+
72+
Perf tests for both the VPN tunnel and SOCKS5 proxy live in `application_simnet_test.go`. They use `simlibp2p` + `simnet` to simulate network conditions with controlled latency and bandwidth. They take ~30s each and are skipped by default. Enable with `AWL_PERF_TESTS=1`:
73+
74+
```bash
75+
# Run all perf tests
76+
AWL_PERF_TESTS=1 go test -run TestSimulated -v .
77+
78+
# Run a specific scenario
79+
AWL_PERF_TESTS=1 go test -run TestSimulatedSOCKS5ProxyPerformance/LongDistCable_10Mbps_300ms -v .
80+
AWL_PERF_TESTS=1 go test -run TestSimulatedTunnelPerformance/LongDistCable_10Mbps_300ms -v .
81+
```
82+
83+
Tests output a table with throughput, utilization, and TTFB metrics.
84+
85+
### Lint
86+
87+
```bash
88+
golangci-lint run
89+
gofmt -d .
90+
go mod tidy -compat=1.26 # in whichever module(s) you touched
91+
```
92+
93+
Linter config: `.golangci.yml`. `go fmt` + `go vet` are enforced.
94+
95+
## Architecture
96+
97+
### Application lifecycle (`application.go`)
98+
99+
The correct init sequence — order matters, `Init` will panic if the logger/config aren't set up first, and it needs a platform-specific `tun.Device`:
100+
101+
```go
102+
app := awl.New() // zero-valued struct; does NOT set logger/config
103+
app.SetupLoggerAndConfig() // required before Init: loads config_awl.json
104+
// (falls back to config.NewConfig on error),
105+
// wires the ring-buffer log sink
106+
tunDevice, err := tun.CreateTUN("awl0", vpn.InterfaceMTU) // platform-specific
107+
if err != nil { /* ... */ }
108+
err = app.Init(ctx, tunDevice) // brings up P2P, VPN, DNS, SOCKS5, API, metrics
109+
// ... app.Ctx() is cancelled on app.Close()
110+
app.Close() // saves config, cancels ctx, shuts down services
111+
```
112+
113+
Entry points wrap this slightly differently:
114+
115+
- `cmd/awl/main.go` — headless server: builds its own TUN via `vpn.NewTunDevice`.
116+
- `cmd/awl-tray/main.go` — desktop tray: same core init, plus systray wiring and update checks.
117+
- `cmd/gomobile-lib/` — Android AAR built with `gomobile`; the Android app passes in an externally-managed `tun.Device` (VpnService file descriptor).
118+
119+
### Core packages
120+
121+
- **`p2p/`** — libp2p host. DHT routing, connection management, metrics. Transports: **QUIC (with native TLS 1.3) and TCP+TLS**, both enabled (`p2p/p2p.go:171-172`). Default listen port is probed at startup.
122+
- **`vpn/`** — WireGuard `tun.Device` wrapper with platform-specific constructors (`iface_linux.go`, `iface_darwin.go`, `iface_windows.go`, `iface_android.go`, `iface_other.go`). Packet batching; `InterfaceMTU = 3500`, `maxContentSize = InterfaceMTU + 100`.
123+
- **`service/`** — glue between P2P streams and app logic:
124+
- `tunnel.go` — routes IP packets between TUN and per-peer P2P streams. Non-blocking send (`select` with `default`) on the per-peer channel; drops on full.
125+
- `auth_status.go` — friend-request/accept flow, background retry, peer status exchange.
126+
- `socks5.go` — local SOCKS5 listener and remote exit-node proxying (thin wrapper; real server impl lives in `socks5/`).
127+
- **`socks5/`** — standalone SOCKS5 server/client implementation (`server.go`, `client.go`, `conn.go`). Owned by awl, not a third-party import.
128+
- **`api/`** — Echo HTTP REST API on port 8639. Handlers split across `api.go`, `peers.go`, `settings.go`, `debug.go`, `metrics.go`. `api/apiclient/` is the typed Go client used by the CLI and tests. pprof is mounted under `/api/v0/debug/pprof/`. Prometheus metrics at `/metrics`. Swagger served from `/swagger/`.
129+
- **`config/`** — JSON config (`config_awl.json`), `sync.RWMutex`-guarded, auto-saves on every mutation via `Config.save()`. Field-level docs are in `config/config.go`; defaults in `config/other.go::setDefaults`.
130+
- **`awldns/`****.awl domain resolver** built on `anywherelan/ts-dns` (Tailscale's DNS fork). The DNS *service* (lifecycle, OS DNS takeover, mapping refresh) lives in `application.go` as `DNSService` / `NewDNSService`, not here.
131+
- **`awlevent/`** — internal event bus on top of libp2p's eventbus. Events: `KnownPeerChanged`, `ReceivedAuthRequest`. Use `awlevent.WrapSubscriptionToCallback` to subscribe with a callback.
132+
- **`metrics/`** — Prometheus registry and custom AWL metrics; `StartBackgroundUpdater` is launched from `Application.Init`.
133+
- **`cli/`** — terminal CLI via `urfave/cli/v2` (`cli.go`, `me.go`, `peers.go`). Talks to a running daemon over the HTTP API via `api/apiclient`. Compiled into both `cmd/awl` and `cmd/awl-tray`.
134+
- **`protocol/`** — libp2p stream protocol IDs. Base path `/awl/0.3.0/`, methods: `auth/`, `status/`, `tunnel/`, `socks5/`, **`socks5-noauth/`**. Also the packet framing helpers (`AppendPacketToBuf`, `ReadUint64`).
135+
- **`update/`** — self-update client (via `GrigoryKrasnochub/updaterini`); drives `awl cli update` and the tray's "check for updates".
136+
- **`ringbuffer/`** — in-memory ring buffer used as a log sink so the API can return the last N log lines (`/api/v0/debug/log`).
137+
- **`entity/`** — shared DTOs between api/handlers and api/apiclient (keeps the two sides type-safe).
138+
139+
### Config file
140+
141+
`config_awl.json` lookup order (see `config/other.go::CalcAppDataDir`):
142+
143+
1. `$AWL_DATA_DIR` if set.
144+
2. Directory of the running executable, **only if** `config_awl.json` already exists there.
145+
3. OS user config dir: `~/.config/anywherelan/` (Linux), `%AppData%\anywherelan\` (Windows), `~/Library/Application Support/anywherelan/` (macOS).
146+
147+
The resolved path is printed at startup:
148+
149+
```
150+
INFO awl Initializing app in <path> directory
151+
```
152+
153+
`config.LoadConfig` falls back to `config.NewConfig` if the file is missing or unparseable, so the app always boots with a valid config. See the README for a populated example and field semantics.
154+
155+
### Test infrastructure
156+
157+
Tests spin up real `Application` instances with a mock TUN (`TestTUN`) that exposes `Outbound` / `Inbound` Go channels in place of a real interface:
158+
159+
```go
160+
// Integration tests (real libp2p, real DHT, mock TUN)
161+
ts := NewTestSuite(t)
162+
peer := ts.NewTestPeer(false)
163+
164+
// Perf tests (simulated network with controlled latency/bandwidth)
165+
ts := NewSimnetTestSuite(t)
166+
peer1, peer2 := ts.NewSimnetPeerPair(latency, bandwidthBps, socks5Conf1, socks5Conf2)
167+
168+
// Injecting and observing packets via TestTUN
169+
peer.tun.Outbound <- packet // inject outbound
170+
count := peer.tun.InboundCount() // received on this peer
171+
```
172+
173+
Key files:
174+
175+
- `test_suite_test.go` — test infrastructure, `NewTestSuite` / `NewTestPeer` / `TestTUN`.
176+
- `application_test.go` — integration tests (tunnel, auth flow, SOCKS5, DNS, API).
177+
- `application_simnet_test.go` — perf/simnet scenarios, gated on `AWL_PERF_TESTS=1`.
178+
- `api_handlers_test.go`, `cli_test.go` — HTTP API and CLI tests (both hit a real `Application`).
179+
180+
## Code conventions
181+
182+
- Config access: `sync.RWMutex` on `Config`, with `Lock`/`Unlock` / `RLock`/`RUnlock` always paired in the same function. Mutations call `Config.save()` while holding the lock.
183+
- Hot paths (tunnel packet forwarding, socks5) use non-blocking channel sends (`select` with `default`) to drop rather than back-pressure the caller. See `service/tunnel.go`.
184+
- Every package gets its own logger: `log.Logger("awl/<pkg>")` (ipfs/go-log/v2). Log levels for libp2p sub-loggers are tuned in `application.go::SetupLoggerAndConfig` (`swarm2`, `relay`, `connmgr`, `autonat` are pinned to warn).
185+
- Context plumbing: `Application.Init` stores a cancellable context on the app; long-running goroutines take `a.ctx` and exit on `Close()`.
186+
- Packet channels are sized per-peer, not global — see `service.Tunnel`.
187+
188+
## Monitoring
189+
190+
- Prometheus metrics: `http://localhost:8639/metrics`
191+
- pprof: `http://localhost:8639/api/v0/debug/pprof/`
192+
- Docker Compose stack with Grafana dashboards, Pyroscope, and Alloy in `monitoring/` — see `monitoring/README.md`.

CONTRIBUTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
- **CONTRIBUTING.md** — даже минимальный (как собрать, как запустить тесты, code style).
3+
4+
5+
- **Community чат** (Telegram или Discord) — ссылка на лендинг и README. Без community нет обратной связи, нет чувства живого проекта.
6+
7+
- **Секция Security**. Для VPN-продукта отсутствие информации о шифровании — красный флаг. Добавить: TLS 1.3 + Noise protocol через libp2p, Ed25519 ключи, отсутствие центральной точки компрометации.
8+
9+

0 commit comments

Comments
 (0)