Build1 distinct publisher3 min readPublished
A Tailscale App Connector fronting a sing-box TUN turns plain TCP into SOCKS5 for any tailnet device. The steering is longest-prefix match, and it fails without complaining.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Longest-prefix match is the whole steering language in this design, and it does more work than the config files suggest. A /32 in the connector's route list beats the same address learned from another subnet router advertising 10.20.0.0/16, so one host gets carved out of an existing network without renumbering anything [6]. The author is straight about the other side of that: delete the /32 and traffic reverts to the old path with no error anywhere [7]. Connections still complete. They just stop going through the corporate proxy, and no client sees a difference.
The destination list also exists twice. Once in the tailnet policy file, where 10.10.0.0/24, 10.20.0.50/32 and 192.0.2.7/32 are nominated for the connector [5], and once in /usr/local/etc/singbox-routes.conf on the node, carrying the same three entries under a comment telling you to keep them in sync [11]. One copy decides what arrives at the connector, the other decides what the connector does with it, and neither validates the other [17].
The rule that does the steering matches on input interface tailscale0 [12], which means packets the connector node originates itself never match it [19]. That is load-bearing in two directions. The SOCKS5 session sing-box opens to 10.0.0.10:1080 is pinned to tailscale0 by explicit bind_interface [9] and cannot be recaptured by the node's own rules, and in any case 10.0.0.10 falls outside all three steered prefixes [18]. It also means a curl from a shell on the connector exercises none of the path you just built.
The ordering bug is the part of the writeup worth keeping. sing-box's unit is Type=simple, so systemd fires ExecStartPost the moment the process forks, before singtun0 exists, and every route add fails with Cannot find device [14]. The ip rule entries install regardless, because rules are not device-bound, so the node ends up with rules present and table 100 empty [15]. Half the configuration lands, which is worse than none of it landing, and the shape of that failure comes directly from Type=simple giving systemd nothing to wait for. The mitigation is a poll loop in the script, checking for the interface in 100ms steps for up to ten seconds and logging how late it showed up [16].
What this buys is a proxy for devices that have no place to configure one, since iOS offers no per-app proxy setting and most apps ignore the system proxy anyway [1]. What it costs is that the translation now lives in a routing table on one Linux box, with auto_route deliberately switched off so sing-box does not manage that table either [10]. Every question a client might ask about whether its traffic went through the proxy has to be answered somewhere else.
Ranked by verification strength, evidence, and original report placement.
The sing-box inbound is a TUN tagged tun-in with interface_name singtun0, address 172.19.0.1/30, mtu 1500, auto_route false and strict_route false.
The sing-box outbound is type socks, version 5, server 10.0.0.10 port 1080, with bind_interface tailscale0; the route block has empty rules and final set to that outbound, so anything routed into the TUN is forwarded without needing a rule of its own.
sing-box's unit is Type=simple, so systemd runs ExecStartPost the moment the process is forked, before sing-box has created singtun0, and every `ip route add ... dev singtun0` fails with Cannot find device.
The ip rule entries still install, because rules are not device-bound, leaving rules present and table 100 empty.
iOS has no per-app proxy configuration and most apps ignore the system proxy; on a laptop the same problem is handled with a PAC file.
Some internal services are only reachable through a corporate SOCKS5 proxy.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed, self-consistent configuration evidence from one source
Nearly every claim is backed by verbatim artifacts in the source: the tailnet policy stanza, complete sing-box JSON, the full routing script, and the systemd drop-in, plus named code paths (route/rule/rule_action.go, route/route.go) and specific sing-box versions. That is well above assertion-only evidence and the derived observations follow mechanically from the printed configuration. It is nevertheless one publisher, one author, with no independent reproduction, no logs or packet captures, and key failure claims (silent reversion when a /32 is dropped) asserted rather than demonstrated.
One self-reported deployment
The only adoption signal is the author's own working connector node, disclosed with first-hand operational detail (the observed 100ms race window). No other operators, organizations, download or usage figures, vendor deployment counts, or third-party reproductions appear in the supplied material, so adoption is real but minimal and unverified.
Slightly understated relative to its own evidence
The headline promise (transparent SOCKS5 with no PAC file, no proxy settings, no per-app configuration) is delivered with runnable configuration, and the article volunteers its weaknesses in the same breath — the longest-prefix mechanism is labelled the fragility, the systemd wiring is shown failing silently, and a sing-box limitation is traced into source code across four versions. Framing is therefore at or slightly below what the evidence supports. The small negative is tempered by the fact that adoption is a single self-report while the design is presented as generally applicable to 'any tailnet device'.
Practitioner write-up, no disclosed commercial interest
The source is an individual technical post on a developer community platform, with anonymized addresses and no stated affiliation with, sponsorship by, or product being sold on behalf of Tailscale or sing-box. The residual incentive is ordinary reputational and audience-building benefit from publishing a working recipe, plus a mild bias toward presenting one's own chosen stack favourably; no pricing, referral, or vendor-promotional mechanism is visible in the supplied material.
Moderate-low: rich artifacts, single unverified author
Confidence is held down by structural thinness — one publisher, one source, one author, no independent verification of either the code-path finding or the operational race window, and adoption limited to the author's own node. It is not lower because the technical claims are internally consistent, largely self-verifying from printed configuration, and the derived observations require no assumptions beyond the material shown.
build
canvas.toBlob hands you a PNG and calls it WebP: check blob.type, not the user agent1 distinct publisher
build
A redaction placeholder became a primary key, and it closed other people's promises1 distinct publisher
build
Your .ai viewer is a pdf.js problem, and its worst bugs never throw1 distinct publisher
build
MCP's empty-string cursor: the null check that hides most of a tool catalog1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 25, 2026