Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .machine_readable/6a2/STATE.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ open-failures = 0
# with ALPN "burble-bolt-v1" for sender authentication (ADR-0003, ADR-0004).
# New modules Burble.Bolt.{Listener,Sender,Quic}; scripts/gen-bolt-cert.sh
# for cert provisioning; docs/developer/wsl-mirrored-networking.adoc
# documents WSL mirrored-networking prereq for receiving LAN packets.
# documents WSL inbound-UDP options for the listener (superseded 2026-05-19).
# Other features in window: coturn STUN/TURN relay + time-limited ICE
# credentials (b10283b), Mumble bridge — permissions, positional audio,
# UDP fix (d557742), Assist API + LLM diagnostics + event stream (7fc1a45),
Expand All @@ -119,6 +119,14 @@ open-failures = 0
# automated backups (acbe88a), AffineScript Canary CI workflow (8eeed52),
# brand pack (ba23ec1). Android client + cross-repo Bluetooth plan landed
# as architecture doc (b1070af) — implementation deferred.
# 2026-05-19: WSL Bolt inbound networking reworked — mirrored mode caused
# recurring Hyper-V VmSwitch port-restore failures + intermittent
# Wsl/Service/E_UNEXPECTED on Win11 24H2/Insider. New
# scripts/wsl-bolt-udp-forward.ps1 host UDP forwarder (default NAT,
# bidirectional, udp/7373+9); wsl-mirrored-networking.adoc rewritten
# (NAT+forwarder default, bridged alt, mirrored last-resort); ADR-0005.
# proven-stun NAT traversal scoped + rejected (cold bolts target
# offline peers — hole-punching structurally impossible).

[crg]
grade = "C"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Safe `pollEvents` returning `Maybe NifEvent` instead of raw `Bits64`
- Bidirectional PositionUpdate (tag 8) decode with Vec3 + orientation relay via PubSub
- SpeakingStart/Stop (tags 6-7) diagnostic decode with server-only enforcement
- `scripts/wsl-bolt-udp-forward.ps1` — host UDP forwarder for the WSL2 Bolt listener (default NAT, no mirrored networking; ADR-0005)

### Changed
- `prim__registerCallback` made module-private (unsafe boundary, awaits idris2#3182)
- `docs/developer/wsl-mirrored-networking.adoc` rewritten — NAT + host forwarder is the recommended WSL2 Bolt path; mirrored networking demoted to last-resort (Win11 24H2/Insider `Wsl/Service/E_UNEXPECTED` instability)

### Removed
- TODO.md (superseded by CLAUDE-WORK.md — 0 TODOs remain in codebase)
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ The Bolt listener binds udp/7373 (`Burble.Bolt.Listener`, also QUIC via
default NAT mode, inbound LAN UDP never reaches the listener. If you're
on Windows + WSL2 and the server side won't accept Bolt datagrams from
another host, see `docs/developer/wsl-mirrored-networking.adoc` —
`networkingMode=mirrored` in the host `.wslconfig` plus a Defender
firewall rule for udp/7373.
default NAT + the `scripts/wsl-bolt-udp-forward.ps1` host forwarder
(recommended) plus a Defender firewall rule for udp/7373.

## Do not

Expand Down
100 changes: 100 additions & 0 deletions docs/decisions/0005-wsl-bolt-inbound-udp-no-mirrored.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
= Architecture Decision Record: 0005-wsl-bolt-inbound-udp-no-mirrored
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> -->

# 5. WSL2 Bolt inbound UDP via a host forwarder, not mirrored networking

Date: 2026-05-19

## Status

Accepted

Relates to ADR-0004 (Bolt QUIC dual-bind — same udp/7373).

## Context

`Burble.Bolt.Listener` binds udp/7373 (and udp/9 WoL-compat), optionally
QUIC on the same port (ADR-0004). When the server runs inside WSL2 — the
default developer environment on Windows — its default NAT networking
drops inbound LAN UDP at the host: the distro sits behind an internal
vEthernet adapter and only outbound flows + replies are forwarded. Cold
bolts and cross-host QUIC bolts never reach the listener.

`docs/developer/wsl-mirrored-networking.adoc` previously instructed every
contributor to set `networkingMode=mirrored` in `%USERPROFILE%\.wslconfig`,
together with `dnsTunneling=true`, `autoProxy=true`, and experimental
`hostAddressLoopback=true`.

That guidance is actively harmful on current Windows. On Windows 11 24H2
and Insider builds, mirrored mode plus those companions causes recurring
Hyper-V VmSwitch `Failed to restore configuration for port … Object Name
not found` errors and intermittent `Wsl/Service/E_UNEXPECTED`
catastrophic failures — the WSL vNIC flaps and the utility VM bounces
every few hours. The development environment becomes, in the words of the
report that triggered this ADR, "incredibly unstable". Anyone following
the documented setup inherits the instability.

Constraints on the fix:

* `netsh interface portproxy` is TCP-only; it cannot forward UDP, so the
classic port-forward shortcut is unavailable.
* Bolt's primary path is stateless fire-and-forget raw UDP (cold poke),
but the QUIC path (ADR-0004) needs the reply datagram to return to the
original sender, so a one-directional forward is insufficient.
* The WSL2 NAT IP changes on every distro boot.

STUN/ICE NAT traversal (`proven-servers` `proven-stun`) was considered
and rejected: a cold bolt by design wakes a recipient who is *not* in any
session and may be offline, so hole-punching — which needs both peers
coordinating through a rendezvous simultaneously — cannot serve it. Bolt
is deliberately a server-reachability design (NAPTR/SRV + DDNS + router
port-forward), not symmetric P2P.

## Decision

Stop requiring mirrored networking. The recommended path is **default
WSL2 NAT plus a userspace UDP forwarder on the Windows host**:

* `scripts/wsl-bolt-udp-forward.ps1` — a bidirectional UDP relay binding
udp/7373 + udp/9 on the host, with a per-client ephemeral upstream
socket and idle expiry so QUIC handshakes and acks return to the
original sender. It re-resolves the WSL NAT IP every 15 s and rebuilds
upstream sockets when the distro reboots onto a new address. `-Install`
registers a logon scheduled task; `-Firewall` (elevated) adds the
Defender allow rules.
* Bridged networking (`networkingMode=bridged` + an external vSwitch) is
documented as a forwarder-free alternative for wired workstations.
* Mirrored networking is demoted to fallback-of-last-resort, retained
only for older stable Windows builds, with an explicit 24H2/Insider
instability warning, and pared to `networkingMode=mirrored` +
`firewall=true` (the crash-implicated companions removed).

`docs/developer/wsl-mirrored-networking.adoc` is rewritten accordingly;
its filename is deliberately kept (five inbound references, including
machine-readable state, made a rename pure breakage risk).

## Consequences

### Positive

* The default contributor dev environment is stable on every supported
Windows build — the instability class is removed at the source.
* The forwarder is portable: no Insider-specific config, works on Wi-Fi
and Ethernet, no admin needed for the relay itself.
* Bidirectional relay keeps the ADR-0004 QUIC return path working.

### Negative

* An extra Windows-host moving part (a scheduled task + a long-running
relay process) that contributors must install.
* A stateless localhost-adjacent hop is added to the cold-poke path
(negligible latency, but it exists).

### Neutral

* The on-machine `~/.wslconfig` revert from mirrored to plain NAT is an
operational step, tracked outside the repo; it is gated on a
reachability check (`scripts/check-bolt-reachability.sh`).
* `proven-frame` (verified framing of `Burble.Bolt.Packet`) remains a
separate, low-priority hardening opportunity, out of scope here.
35 changes: 34 additions & 1 deletion docs/decisions/README.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
= decisions Unit
// SPDX-License-Identifier: PMPL-1.0-or-later
= Architecture Decision Records

This is Burble's decision log. Each ADR captures one significant
architectural decision, its context, and its consequences. ADRs are
immutable once Accepted; a later ADR supersedes an earlier one rather
than editing it.

New ADRs copy `0000-template.adoc` and take the next number.

[cols="1,3,1",options="header"]
|===
| ADR | Title | Status

| link:0001-adopt-rsr-standard.adoc[0001]
| Adopt the RSR standard
| Accepted

| link:0002-indieweb-native-optional-integration.adoc[0002]
| IndieWeb-native optional integration
| Accepted

| link:0003-pake-sas-tiered-auth.adoc[0003]
| PAKE/SAS tiered authentication
| Accepted

| link:0004-bolt-quic-dual-bind.adoc[0004]
| Bolt QUIC dual-bind alongside raw UDP
| Accepted

| link:0005-wsl-bolt-inbound-udp-no-mirrored.adoc[0005]
| WSL2 Bolt inbound UDP via a host forwarder, not mirrored networking
| Accepted
|===
9 changes: 5 additions & 4 deletions docs/developer/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Developer-facing guides for working on Burble locally.

* link:ABI-FFI-README.adoc[ABI-FFI-README] — Zig FFI surface + Idris2
validations the Zig side mirrors.
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking] — host
`.wslconfig` setup for inbound UDP on the Bolt listener (udp/7373)
when running the server side under WSL2. Read this before debugging
"Bolt datagrams aren't arriving" on a Windows dev host.
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking] — getting
inbound UDP to the Bolt listener (udp/7373) when the server runs
under WSL2: default NAT + the host UDP forwarder (recommended),
bridged, or mirrored. Read this before debugging "Bolt datagrams
aren't arriving" on a Windows dev host.
4 changes: 2 additions & 2 deletions docs/developer/bolt-ddns.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,6 @@ same providers and writes an AAAA record.

* link:bolt-dns-records.adoc[bolt-dns-records.adoc] — NAPTR/SRV records
that depend on this hostname resolving to the current host
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking.adoc] — the
WSL2 networking mode that makes UDP/7373 reachable on the host LAN IP
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking.adoc] — how
UDP/7373 reaches the WSL listener on the host LAN IP (NAT forwarder)
* `scripts/cf-bolt-dns.sh` — the NAPTR/SRV provisioner
4 changes: 2 additions & 2 deletions docs/developer/router-port-forward.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,6 @@ task #13's end-to-end test from Joshua's machine.
record current so NAPTR resolves to the host even as the WAN IP drifts
* link:bolt-dns-records.adoc[bolt-dns-records.adoc] — the NAPTR/SRV
records that announce this host as Bolt-reachable
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking.adoc] — why
the LAN IP is the WSL listener's address (no second host→WSL forward)
* link:wsl-mirrored-networking.adoc[wsl-mirrored-networking.adoc] — how
the host LAN IP reaches the WSL listener (NAT + host UDP forwarder)
* `scripts/check-bolt-reachability.sh` — verification helper
Loading
Loading