Back to list
Jul 22 2026

Development Update — July 22

Today Skywire cut v1.3.88, and the release’s headline is the VPN router: over the last stretch it graduated from an on-the-bench prototype into a fully configurable, documented feature that any supported board can run. The bigger leap that rode in with it is the mesh gateway — a LAN device behind the router can now reach a .dmsg or .skynet service by name, with no per-device setup and no SOCKS proxy, because the router resolves the name to a synthetic IP and transparently proxies the connection over the mesh. Underneath the features, a cluster of VPN reliability bugs got run down: a port collision that made vpn-server unstartable on every hypervisor-connected board, and two tunnel-exemption bugs that left the VPN silently dead on common transport types. The dmsg deployment also split two servers onto new hosts. (The VPN router gets its own dedicated feature article — this daily covers the day’s router PRs at a normal pace and points you there for the full story.)

Skywire: The Mesh Gateway

3553 feat(vpn): mesh gateway for .dmsg/.skynet — vpn-router + vpn-client turns the router into a full mesh gateway: any LAN device behind it can reach dmsg/skynet services by namecurl http://<pk>.dmsg, open http://<pk>.skynet in a browser — with no per-device configuration and no SOCKS proxy. A .dmsg/.skynet name isn’t an ordinary hostname; it encodes a public key, and the destination is a routing port reachable only over the mesh, often with no externally-listening TCP port and no IP to route to at all — so the gateway can’t just NAT it. It works in two halves. The router’s embedded resolver hands *.dmsg/*.skynet queries to a new meshgw component, which parses the name to a PK (reusing dmsgweb’s resolver grammar — raw hex, DNSLabel, or alias), leases a synthetic IP from a private pool (default 100.64.0.0/16), records the synthetic-IP → (scheme, pk) mapping, and answers with that IP. A nat/PREROUTING REDIRECT then sends any TCP aimed at the synthetic pool to a local proxy, which reads SO_ORIGINAL_DST — the synthetic IP maps back to the PK and the original destination port is the mesh routing port — dials <pk>:<port> over the mesh, and splices the streams. The mesh path deliberately bypasses the VPN tunnel: the REDIRECT fires in PREROUTING before any routing decision, so pool traffic never hits the LAN→tun policy route, and the proxy’s own dials originate locally on the main table. meshgw is decoupled from the router via an injected MeshDial, so the same component can later back the vpn-client for single-machine mesh-name resolution — it’s really a visor-level capability with the router and client as two exposure points. This shipped as the plaintext/by-PK foundation; TLS-MITM for HTTPS .dmsg hosts and alias→PK service-discovery resolution are follow-ups.

Skywire: VPN Reliability, Run Down

3544 fix(skyenv): move DmsgVisorRPCPort off 44 (collided with vpn-server) fixes a bug that made vpn-server unstartable on essentially every fleet board: skyenv.go hardcoded two services to port 44 — DmsgVisorRPCPort and VPNServerPort. The dmsg visor-RPC server is dual-listened, and its skynet mirror reserves port 44 on the appnet porter at visor init whenever the visor has hypervisor or dmsgpty-whitelist PKs — i.e. almost every board — so by the time vpn-server called Listen on skynet :44 the port was already taken, it errored port already bound, and the proc-manager cycled it (flooding polkit with pkexec prompts on non-root hosts along the way). Because VPNServerPort = 44 is baked into deployments and service discovery, the fix moves DmsgVisorRPCPort to 57, adjacent to its Dmsg* siblings. 3547 test(e2e): guard the port-44 vpn-server collision explains why CI never caught it — the native e2e split the two triggering conditions across different visors, so they never coincided on one visor the way they always do on a real board; the fix gives the vpn-server test visor a dmsgpty whitelist so its skynet mirror activates and the collision becomes a hard test failure. 3545 chore(wasm): re-embed wasm-visor after the skyenv port-44 fix re-embeds the committed wasm blob so it matches develop after the constant change compiled into pkg/skyenv.

3546 fix(vpn): exempt ALL transport peer IPs from the tunnel, not just stcpr and its regression follow-up 3548 fix(vpn): only exempt transport peers with a routable IP close a class of silent VPN failure. The vpn-client installs direct routes so traffic to skywire infrastructure and to the visor’s existing transport peers bypasses the tunnel — otherwise the tunnel routes the very connections it rides on into the TUN and severs them. But the transport-peer half of that list came from a helper that filtered for STCPR only, so a visor whose path to the vpn-server ran over sudph (a normal transport type, and what you get whenever stcpr can’t be established) had that peer’s IP tunneled: the client’s own connection to the server broke, it never got the ServerHello, and the tunnel came up NO-CARRIER. The fix renames the helper to TransportRemoteAddrs() and returns remote IPs for every non-DMSG transport (DMSG stays excluded — it relays through servers with no single routable peer IP, and those servers are exempted separately). That first cut was too broad: some transports have a non-IP remote address — notably webrtc, whose address is webrtc-datachannel(<pk>) — and the client resolves every exemption entry, so a webrtc entry made the whole client fail to start with a DNS lookup error. The follow-up filters to transports whose host is a literal IP, so stcpr/sudph/stcp/quic keep their IPs and webrtc (or any hostname-only address) is dropped before the lookup.

3541 fix(dmsgpty): auto-establish a direct transport before the skynet pty dial fixes cli pty exec hanging under dmsg-server load. The dmsgpty MultiDialer tries skynet first, then falls back to a raw dmsg client connection relayed through a dmsg server — but the skynet strategy only used an existing route, it never created one, so with no transport to the peer it failed and dropped every pty/scp call onto the fragile, relay-bound dmsg path. The fix gives the dialer an ensureTransport hook wired to the visor’s existing ensureFastTransport (stcpr→sudph), so before the skynet dial it auto-establishes a direct transport when none exists — a direct stcpr to a co-located peer means zero dmsg-server relay. A peer that isn’t directly reachable just falls through to the dmsg strategy as before, so nothing regresses.

Skywire: The VPN Router, Configured and Documented

The router’s day was a run of hardware-validation fixes and the config/doc work that turned it into a real feature — see the dedicated VPN-router article for the full walkthrough. 3542 feat(vpnrouter): standalone run mode + LAN-through-tunnel policy routing lets skywire app vpn-router run as a plain daemon (previously it log.Fatal’d without PROC_CONFIG), and installs a dedicated routing table (142) with an ip rule matching only the LAN subnet, so forwarded client packets egress via the VPN while everything the router itself originates — dmsg and skywire included — keeps the main table and the tunnel’s own carrier survives. 3551 fix(vpnrouter): policy-route by input interface, not source subnet corrects that rule: matching from <lan-subnet> also caught the gateway’s own replies (its address is in that subnet), black-holing DNS answers and ping replies to clients, so it now matches by input interface (iif <lan>) — locally-generated router traffic has no iif and stays on the main table. 3549 fix(vpnrouter): clamp forwarded TCP MSS to the path MTU adds a TCPMSS --clamp-mss-to-pmtu rule on FORWARD out the tun, fixing full-size TCP flows that black-holed (ping worked, HTTPS stalled) because the mesh tunnel’s usable MTU is below the LAN’s 1500 and PMTU discovery is unreliable across the NAT. 3543 fix(vpnrouter): disable Wi-Fi power-save before hostapd runs iw dev <ifc> set power_save off before starting hostapd, the single most effective stability fix for the rtl8723bs SDIO radios on original skyminer boards, whose firmware power-save bug flaps AP mode. 3550 feat(vpnrouter): configure all router variants exposes every topology through config gen/autoconfig/skywire.conf — subnet, WiFi-out, SSID, passphrase, band, channel, country, open — via a vpnRouterArgs() helper, where before only the ethernet-out variant was reachable without hand-editing app args. 3552 docs(vpn): complete VPN-router guide lands the full mkdocs guide covering the three uplink×downstream deployment modes and the real-world measures learned validating on Orange Pi rtl8723bs boards.

Skywire: Deployment and Release

3554 separate two dmsg-server 2 and 3 to new servers splits dmsg servers 2 and 3 onto their own hosts, spreading the deployment’s relay load. 3555 chore(release): v1.3.88 cuts the release — changelog (16 PRs on top of v1.3.87), the new “Mesh gateway” docs section and its three VPNROUTERMESHGW* config knobs, and a regenerated dependency graph and lines-of-code table now that pkg/vpnrouter/meshgw is in the tree. The v1.3.88 tag follows.