Back to list
Skywire Development Update — October 6, 2026

Skywire Development Update — October 6, 2026

The sixth of October gave every deployment service a public status page. Transport discovery, dmsg discovery, the route finder, service discovery and the address resolver each began sampling their own counts into history and serving charts at their site root, with transport discovery adding a full network graph drawn on the GPU. Behind those pages, two bugs in transport discovery’s reconcile were found that had been hiding most of the dmsg servers’ live sudph transports and churning about one in twenty of the rest every minute. A long run of changes moved visors off repeated HTTP polling and onto the CXO feeds the services already publish, for dmsg servers, uptime, address bindings and per-key transport counts. On Android, SkyDNS made .dmsg and .skynet names open in every app on the phone, and then on Linux desktops too. And a heap profile of a replica visor led to removing several gigabytes an hour of allocations that produced nothing.

Skywire: SkyDNS on Android and Linux

5421 made .dmsg and .skynet names open in every app on an Android phone, not only browsers that use a proxy, replacing an earlier system-proxy approach that worked in Chrome but not in Firefox or in apps that ignore the proxy. The core answers DNS inside a tunnel, gives mesh names a synthetic address from a reserved range and carries TCP to those addresses over the mesh, and sends other names to a public resolver over DNS-over-TLS. With SkyVPN on, it runs inside the VPN tunnel behind a new switch that is on by default; with SkyVPN off, a new skydns app takes a tunnel of its own that routes only the synthetic range. A site has to be addressed by its key’s DNS label, since a 66-character hex label exceeds the 63-octet limit in RFC 1035, and the hex form had only ever worked through a proxy. It was tested on an emulator with Firefox and Chrome, not on a real phone. The same change fixed text users found too faint: the app’s variable fonts never had their weight applied, so all text drew at the files’ thin defaults.

5425 answered an F-Droid review request to name the third-party services the app uses by default and say they can be replaced, by first making two of them replaceable. The wallet’s node setting now shows for every coin, with Bitcoin taking an Esplora API and the Ethereum family a node and an indexer stored per coin. A new DNS server card in Settings is pinned into the VPN client’s and SkyDNS’s arguments on every start and applied to a running core at once; SkyDNS on its own now uses the phone’s own resolver instead of a fixed one, following it across network moves, and a resolver that drops the DNS-over-TLS port no longer holds each query for its whole five-second deadline before plain DNS gets a turn. The listing now names the coins, their default endpoints and the default resolver.

5422 fixed the Incoming calls card asking for permission after it was already allowed: the app counted only an explicit allow on the full-screen-intent app-op, while an op left at its default falls back to the install-time permission a sideloaded app holds, so the call screen worked while the app said it would not. It now asks the platform’s own canUseFullScreenIntent(), and the card gets its own list item so it no longer touches the Battery card. The PR notes it was tested on an emulator, not on the phone that found the problem. 5423 fixed the F-Droid lane for the 0.0.6 release, where removing the recipe’s Binaries key left its folded continuation line behind and YAML read the orphaned URL as part of the repository path; the key is now removed with its continuation lines. 5424 then got that lane past the binary scanner, which found prebuilt files that arrived with desktop features: the browse cookie jar’s public suffix tables, linked into the mobile build though browse is not part of it, now build only outside the mobile tag, and the recipe deletes all three files before building.

5426 brought SkyDNS to Linux desktops, where the skydns app takes a tunnel of its own and either hands only the dmsg and skynet zones to it through systemd-resolved or goes first in resolv.conf in a marked block that is removed on exit or by the next start after a crash. Tested end to end in an unprivileged namespace with getent, curl and Go’s resolver; the systemd-resolved path and running as a root visor app were not tested.

Skywire: Transport Discovery Was Missing Most of a Hub’s Transports

5428 found transport discovery missing between seventy and ninety percent of each dmsg server’s live sudph transports; on one hub it held about 69 against 389 locally. The reconcile throttle refreshes an unchanged transport with a Redis EXPIRE, which succeeds and returns false on a key that is already gone, and the code never read that result. An entry removed by TTL expiry, an HTTP delete or a CXO tombstone was therefore only ever touched again and stayed missing until the service restarted. Touching now returns the ids whose keys are gone, the reconcile re-registers them in the same pass, and the PR expects the transport count on hub edges to grow two to four times once deployed. The Redis case of its test was not run locally.

5429 fixed the other half. Both edges of a transport publish their lists at different times, and when one edge’s fresh list omitted a transport the reconcile deleted it, only for the other edge’s next list to register it again, so about five percent of sudph transports left and came back every minute and every feed built from the store carried that churn. An omission now deletes a transport only if the other edge has not listed it within the refresh gap. 5427 had the route finder return each hop’s type and throughput beside its latency, so a visor no longer fetches a transport discovery snapshot for each hop it does not hold locally. That snapshot had pinned the service’s routing feed, the whole transport graph republished every minute, for the life of any visor after its first multi-hop dial, and on production that feed is most of transport discovery’s outbound traffic.

Skywire: Status Pages for Every Service

5430 made transport discovery and dmsg discovery sample their counts every five minutes into their own Redis, eight days raw and four hundred days of hourly means, and serve a self-contained page of SVG charts at /, available over dmsg and, through a new charts_addr setting, over plain HTTP serving only that page. Transport discovery shows transports by type, visors, versions and daily bandwidth; dmsg discovery shows clients, servers and clients per server. 5433 separated the registered dmsg servers from the many servers hypervisors run unregistered for their own LAN, which had buried the registered ones, and 5443 had transport discovery mark the visors that run dmsg servers, tinting them in its tables and drawing them larger in its graph. 5434 added visors by transport count, daily latency per transport type with missing days drawn as gaps, and the twenty-five visors that sent the most on the last settled day, counted as the rewards pool that pays for bandwidth counts them.

5435 gave the route finder a charts page counting route requests, pairs with no route, errors, best-route hop count and answer time, plus each route setup node’s public statistics, and 5452 added how many routes each query returns and every returned route’s length. 5437 gave service discovery a page of services by type, VPN and proxy servers by country, and versions. 5438 gave the address resolver a page of bindings per transport type, dial-back reachability, lookups and binds, showing only counts and never which keys were looked up, and 5456 separated its real request load from the refreshes it makes itself from visors’ feeds.

5436 added a donut beside each stacked chart showing every series’ share at the hovered point, and made an open page refresh itself once a minute unless the cursor is on a chart. 5444 made every status page show times in the viewer’s own time zone, except daily charts, which stay UTC days and say so. 5439 added a transport graph page with each pair of visors joined by a transport drawn as one edge coloured by type, laid out in Go from the previous positions so the picture stays put; on the live set of 1,122 visors and 98,953 pairs a cold layout took about two seconds. 5440 then moved its drawing from a 2D canvas, which redrew every line on each hover and pan step and froze the tab, to the WebGL engine the network view already uses, served from the binary’s own embedded wasm module; two seconds of pointer movement and zooming kept main-thread lag at four milliseconds or less. 5432 ran the Redis store test packages one at a time in CI, since two packages flushing the same database in parallel had broken each other.

Skywire: Feeds Instead of Polls

5446 had dmsg discovery publish each registered server on its existing clients-by-server feed, rewritten only when something a client dials by changes, so visors refresh their server cache from a snapshot they already hold instead of fetching the full list over HTTP every five minutes, which across the network was about four requests a second. 5451 moved dmsg servers’ own peer discovery and transport discovery’s server marking onto the same feed, with its format in one shared package. 5448 let a visor skip its five-minute HTTP uptime heartbeat to transport discovery while that service is subscribed to the visor’s transport-list feed, after fixing the reconcile so it records presence first every time; a visor with no transports had returned early before presence was ever recorded.

5449 had visors subscribe to the address resolver’s bindings feed and answer stcpr, squicr and swtr lookups from it, falling through to HTTP on any miss, where each lookup had cost about 140 milliseconds; sudph still goes to the resolver, which also asks the peer to dial back. 5454 stopped route setup repeating address resolver calls for a peer it has no transport to, taking advertised types from that feed, backing off a failed type for two minutes doubling up to thirty, and remembering a not-found for two minutes. 5457 sent sudph lookups, six in ten of all lookups, over the UDP control connection the visor already holds to the resolver instead of a fresh HTTP request over dmsg, falling back to HTTP if an older resolver never answers. 5455 had transport discovery publish its per-key transport counts as a small feed of their own, about 38 KB a minute, so the network view no longer downloads the full transport list, about 6 MB gzipped, only to count it. Several of these note they had not yet been checked against the live feed.

5450 stopped a dmsg server looking up each client’s discovery entry, with no timeout and no cache, before serving its streams, which made browser and direct clients without an entry wait one to two and a half seconds per session and forever if the lookup hung; a client only takes smux when its server advertises it, which this server never does, so every session is now served as yamux. 5445 fixed CXO schema kinds going on the wire as raw numbers, which TinyGo numbers differently from Go, so a TinyGo subscriber in a browser tab never filled its first root; the wire now uses Go’s numbering through a fixed table, which is the identity under Go.

Skywire: Log Levels and a Log Server That Was Too Open

5441 gave survey-whitelisted keys a way to raise one visor’s log level for a while, since visors are moving to info by default: a /debug/loglevel endpoint sets a level that reverts on its own within at most an hour, is never saved to the config, and is driven by skywire cli log level. It also stopped the --loglvl flag being written into the config file by any later flush. 5447 made that command honor --json. 5442 found the localhost log server, created with no whitelist, registered as an allowed port on skynet, so any skynet peer that found the port could read the visor’s log, node info, stats and pprof without being whitelisted; the log server already reaches skynet on port 80 behind the whitelist, so the registration was dropped.

Skywire: Allocations That Produced Nothing

A heap profile of a replica visor showed 17 GB allocated in half an hour. 5453 removed about 3.5 GB of it: trace fields built for every packet with tracing off, packets copied three times on read, keys hex-encoded inside a sort comparator, a geoip lookup per transport on every summary poll, and vstream frames copied twice. 5458 turned the flight recorder off by default, since it made about a third of that garbage, behind a live flight_recorder setting. 5459 moved kcp-go to the 0magnet fork on the v5.6 line, whose timed scheduler no longer boxes every task or makes a new closure per reschedule, which had been 2.4 GB of that half hour, and 5460 sized each kcp session’s transmit queue to its flush batch instead of 2,048 messages, freeing about 55 MB on a few hundred sessions.