Skip to content

A .dmsg / .skynet gateway for your LAN (OpenWRT / DD-WRT)

Skywire's deployment services — the reward system, transport discovery, route finder, address resolver — are reachable only over dmsg; there is no plain-HTTP endpoint. A .dmsg / .skynet name is not a normal hostname either: it encodes a public key, and the destination is a routing port on that key reachable only over the mesh.

This guide turns one board running a visor into a .dmsg gateway for a whole LAN, so any phone, laptop, or TV behind your OpenWRT / DD-WRT router can reach http://reward.dmsg/ (and any <pk>.dmsg) — with no skywire install on each device. You do not need a special skywire build or a port on the router itself; the router only forwards to a board that already runs skywire.

There are two ways to do it, and they are genuinely different:

Transparent (mesh gateway) SOCKS5 (resolving proxy)
Per-device setup None — any LAN device just works One SOCKS5 setting per device / browser
How a name resolves DNS → synthetic IP → transparent redirect The client speaks SOCKS5 and passes the name
Works for Every app (curl, apps, TVs, phones) SOCKS-aware apps + browsers
Board requirement Linux visor + root (netfilter) Any visor

If you want "any device on the LAN just works, with nothing to configure per device," use the transparent mesh gateway (Section 1). A SOCKS5 proxy cannot be transparent — SOCKS is a per-application protocol, the client has to speak it and hand over the hostname in-band, so there is always a per-device (or per-browser) setting. Nothing on the router changes that. The SOCKS5 method (Section 2) is the right choice when you only want one or two devices, or the board can't run the mesh gateway.


1. Transparent: the mesh gateway (zero per-device config)

The mesh gateway answers *.dmsg / *.skynet DNS with a synthetic IP from a private pool (100.64.0.0/16), remembers IP → public key, and transparently redirects TCP aimed at that pool into a mesh dial (reading the original destination via SO_ORIGINAL_DST). A LAN device never knows it happened — it just connects to a name. This is exactly what vpn-router --mesh-gateway provides for a board that is the router; --mesh-gateway-only provides the same thing for a board that sits behind your existing OpenWRT / DD-WRT router.

1a. On the board — run the standalone mesh gateway

The board needs a running visor (so it has mesh transports). Start the gateway as a visor app — it serves the mesh-name DNS and the transparent proxy, and does nothing else (no DHCP, no NAT, no tunnel):

skywire cli visor app start vpn-router --mesh-gateway-only --lan-ifc eth0

or, launched directly by the visor from its config (add vpn-router to the app list with args --mesh-gateway-only --lan-ifc eth0). Replace eth0 with the board's LAN interface. By default the DNS listens on 0.0.0.0:53 and the synthetic pool is 100.64.0.0/16; override with --mesh-bind, --mesh-dns-port, and --mesh-gateway-cidr if those collide with something on the board.

For HTTPS to .dmsg hosts, add --mesh-gateway-tls (the gateway TLS-terminates with a self-generated CA that LAN clients must trust) and, for friendly names, --mesh-alias reward=<pk>.

Confirm the DNS answers with a synthetic IP:

dig +short reward.dmsg @<board-ip>          # → a 100.64.x.x address

1b. On the router — forward DNS + route the pool to the board

Two settings, both supported on OpenWRT and DD-WRT. Devices keep the router as their DHCP server, DNS, and default gateway; only .dmsg / .skynet names and the synthetic pool detour to the board.

OpenWRT (<board-ip> = the board, e.g. 192.168.1.50):

  1. DNS forward — in /etc/config/dhcp under the dnsmasq section:

list server '/dmsg/<board-ip>'
list server '/skynet/<board-ip>'
/etc/init.d/dnsmasq restart

  1. Static route for the synthetic pool — in /etc/config/network:

config route
    option interface 'lan'
    option target '100.64.0.0/16'
    option gateway '<board-ip>'
/etc/init.d/network reload

DD-WRT:

  1. DNS forward — Services → Services, in Additional DNSMasq Options:
server=/dmsg/<board-ip>
server=/skynet/<board-ip>
  1. Static route — Setup → Advanced Routing → Static Routing: destination 100.64.0.0, mask 255.255.0.0, gateway <board-ip>, interface LAN. (Or, from Administration → Commands: ip route add 100.64.0.0/16 via <board-ip>.)

That's it. From any LAN device, with nothing configured on the device:

curl http://reward.dmsg/health

resolves through the router → the board's mesh-gateway DNS → a synthetic IP, whose traffic is routed to the board and dialed over the mesh.

Security: every device on the LAN can now reach dmsg/skynet through the board. Only do this on a trusted network. The gateway deliberately bypasses any VPN tunnel — mesh names are dialed directly over the visor's transports.

DNS port already taken? If something on the board already owns :53, start the gateway with --mesh-dns-port 5353 and point the router at it: server=/dmsg/<board-ip>#5353.


2. SOCKS5: the resolving proxy (per-device, but any visor)

If you only need one or two devices — or the board can't run the transparent gateway — use the embedded resolving proxy (skywire cli resolver). It's a SOCKS5 proxy that resolves *.dmsg / *.skynet and tunnels them over the visor's dmsg client. Because SOCKS5 carries the hostname itself, every .dmsg name works through it — short aliases (reward.dmsg, tpd.dmsg) and full <66-hex-pk>.dmsg addresses.

2a. On the board — bind the resolver to the LAN

The proxy binds 127.0.0.1 by default, so only the board can use it. Bind it to a LAN address instead. The simplest way, on a running visor, is at runtime — it persists, so it survives a restart:

skywire cli resolver up --bind 0.0.0.0

--bind 0.0.0.0 serves every interface (or give a specific LAN IP). This sets the bind (proxy_addr + enabled) in the running config and in skywire.conf (DMSGWEBADDR / SKYNETWEBADDR), so it survives autoconfig, and brings the .dmsg proxy up on port 4445 and .skynet on 4446.

Confirm it's listening on the LAN (replace 192.168.1.50 with the board's IP):

ss -ltn | grep 4445                                          # on the board: 0.0.0.0:4445
curl -x socks5h://192.168.1.50:4445 http://tpd.dmsg/health   # from another LAN host

On an skywire autoconfig-managed host (the usual Linux install), the visor saves a runtime --bind to skywire.conf too, so a later autoconfig keeps it. To set it there directly instead:

DMSGWEB=true
DMSGWEBADDR='0.0.0.0'
SKYNETWEB=true
SKYNETWEBADDR='0.0.0.0'
sudo skywire autoconfig

(Equivalently in one shot: sudo skywire autoconfig --dmsgweb --dmsgweb-addr 0.0.0.0 --skynetweb --skynetweb-addr 0.0.0.0.)

Security: a non-loopback bind exposes the proxy to everyone on the LAN. Only do this on a trusted network.

2a-bis. Keep the loopback proxy, add a separate one for the LAN

Re-binding DMSGWEBADDR moves the board's own resolver onto the LAN: there is then one proxy, serving both the board and every device on the network, under the visor's public key. RESOLVERS adds a second proxy instead, on its own port, so the loopback one stays exactly as it was:

DMSGWEB=true
SKYNETWEB=true
RESOLVERS=('dmsg:4447;name=lan;addr=0.0.0.0;sk=<64-hex-secret-key>;chain=false')
sudo skywire autoconfig

Three things that entry buys over re-binding 4445:

  • sk= gives the LAN proxy its own dmsg identity, attached in-process to this visor's relay (one session, no discovery entry — the visor pays for dmsg once). The LAN's requests are then attributable to a key you can rotate or revoke on its own, without touching the visor's.
  • chain=false leaves it unchained, so non-.dmsg CONNECTs are not forwarded to skynetweb → skysocks-client. Chained, the LAN proxy would be a working clearnet SOCKS5 proxy for every device on the network.
  • The board's own loopback 4445 keeps the full chain, unchanged.

Generate a key with skywire cli config gen -n --stdout | grep '"sk"', or reuse one you already hold. Each entry needs its own port: config gen refuses a set where two enabled resolvers claim the same one, naming both.

2b. On each device (or browser) — point at the proxy

Option A — per-device SOCKS5 (works on both OpenWRT and DD-WRT). Set the SOCKS5 proxy on the device or browser to the board:

  • Host: the board's LAN IP (e.g. 192.168.1.50) — Port: 4445
  • Proxy DNS when using SOCKS v5: ON (so .dmsg names resolve at the proxy)

Firefox: Settings → Network → Manual proxy → SOCKS v5, tick Proxy DNS when using SOCKS v5. Chrome/OS-level: --proxy-server="socks5://192.168.1.50:4445". curl -x socks5h://192.168.1.50:4445 http://reward.dmsg/. (.skynet is on 4446.)

Option B — browser auto-config via WPAD/PAC (OpenWRT, no per-device setup for browsers). Serve a PAC file from the router so browsers pick up the SOCKS proxy for *.dmsg automatically:

  1. /www/wpad.dat (symlink proxy.pac → it) on the router:

function FindProxyForURL(url, host) {
  if (dnsDomainIs(host, ".dmsg") || dnsDomainIs(host, ".skynet"))
    return "SOCKS5 192.168.1.50:4445";
  return "DIRECT";
}
ln -s /www/wpad.dat /www/proxy.pac

  1. Advertise it via DHCP option 252 in /etc/config/dhcp:

config dhcp 'lan'
    list dhcp_option '252,http://192.168.1.1/wpad.dat'
/etc/init.d/dnsmasq restart

Browsers set to Use system proxy settings / Auto-detect then route .dmsg and .skynet through the board. DD-WRT can serve a PAC file the same way from its /jffs web root (dhcp-option=252,http://<router>/proxy.pac).

Note this only covers browsers (and only ones set to auto-detect); a non-SOCKS-aware app still needs Option A. That per-app limit is the reason to prefer the transparent gateway (Section 1) when you want everything to work.


See also

  • resolving-proxy.md — the skywire cli resolver proxies Section 2 builds on
  • VPN router — turning a board into a full mesh router / gateway / WiFi AP (where --mesh-gateway is the built-in, board-is-the-router form of Section 1)