Fix STUN test when server address is an IP literal - #4350
Open
swedenborg33 wants to merge 247 commits into
Open
Conversation
`SecTrustEvaluateWithError` is serial
This reverts commit 62cb06c.
Since b0c6762, every rule inside a referenced rule-set was evaluated as if merged into the outer rule, which required tracking per-branch group states and let outer rules and rule-set rules satisfy each other's grouped conditions in both directions. Restrict merging to the only designed case: a rule-set containing exactly one non-inverted default rule is merged into the outer rule as before. Any other rule-set now matches as an ordinary condition of the outer rule: it matches when any of its rules matches on its own, and its rules no longer exchange grouped match state with the outer rule in either direction. Multiple referenced rule-sets keep OR semantics. Flat address rule-sets such as generated geosite/geoip sets contain a single default rule, so their behavior is unchanged. The group-state set machinery is replaced by a single required/satisfied mask pair. Also update the route and DNS rule docs.
DHCP option 119/15 and resolved link domains are not rooted, so appending them to the query name produced a non-FQDN name, failing to pack with "domain must be fully qualified".
stun.Run assigned M.Socksaddr to serverAddr in the dialer branch, which net.UDPConn.WriteTo rejects with EINVAL and gonet.UDPConn.WriteTo panics on.
nekohasekai
force-pushed
the
testing
branch
14 times, most recently
from
July 29, 2026 13:44
067b00f to
4f7f894
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
stun.Runassigns two different types toserverAddrdepending on which branchit takes (
common/stun/stun.go):serverAddris then passed toconn.WriteTo(request, addr)inroundTrip.When the server is given as a domain,
ListenPacketWithDestinationresolves itand returns a conn wrapped by
N.ListenSerial, which acceptsM.Socksaddr. When theserver is given as an IP literal, the raw packet conn is returned and the type
mismatch surfaces:
net.UDPConn.WriteTorejects the address:write udp4 ...: invalid argument.The STUN test simply does not work.
gonet.UDPConn.WriteTo(gVisor, used by WireGuard and Tailscale endpoints)type-asserts to
*net.UDPAddrand panics, taking down the whole instance:The default server (
stun.voipgate.com:3478) is a hostname, which is why thedomain-only path has been exercised and the defect stayed hidden.
Originally observed on the iOS graphical client (1.14.0-beta.2) via
Tools -> STUN Test with a WireGuard endpoint selected and an IP address typed into
the Server field; the extension crashed with SIGABRT.
Fix
Convert to
*net.UDPAddrwhen the server is already an IP. The domain case is leftuntouched, since that path goes through the wrapper and works today.
Alternatives considered: wrapping the raw packet conn returned by
Endpoint.ListenPacketWithDestinationfor the IP branch, so that it acceptsM.Socksaddrthe way the domain branch already does viaN.ListenSerial; or makinggonet.UDPConn.WriteToreturn an error instead of panicking. Both are broaderchanges affecting other callers, so this keeps the fix at the site that produces the
wrong type.
Reproduction
createDialerincmd/sing-box/cmd_tools.gofalls back toinstance.Outbound().Default()when-ois omitted, sooptions.Dialeris nevernilon the command line. The shortest reproduction therefore needs no specialconfiguration at all — any config, any outbound:
For the panic, an endpoint is required (no TUN, no remote server, no graphical
client; the peer never has to answer, because the failure happens on the first
write):
{ "endpoints": [ { "type": "wireguard", "tag": "wg-ep", "system": false, "address": ["10.0.0.2/32"], "private_key": "<generated>", "peers": [ { "address": "127.0.0.1", "port": 51820, "public_key": "<generated>", "allowed_ips": ["0.0.0.0/0"] } ] } ], "outbounds": [{ "type": "direct", "tag": "direct" }] }Note: reaching the endpoint case from the command line requires one local change —
createPreStartedClientincmd/sing-box/cmd_tools.gocallsinstance.PreStart(),while
Endpoint.startedis only set atStartStatePostStart, so everytoolscommand against a WireGuard or Tailscale endpoint currently fails with
WireGuard is not ready yet. ReplacingPreStart()withStart()locally makes thepanic reproducible on the CLI. That is a separate concern and is not part of this
change.
Verification
Built from v1.14.0-beta.2 with
-tags with_gvisor,with_wireguard, go1.26.directinvalid argumentdirectwg-epi/o timeout, peer is a stub)wg-epi/o timeouti/o timeoutThe two
directrows return the same NAT classification, so the IP path now behavesidentically to the hostname path.
Not covered by the above: IPv6 server literals, and outbounds other than
direct(VLESS, Shadowsocks, Hysteria2 and so on) whose packet conns are wrapped
differently. The
wg-eprow after the fix shows a clean failure rather than asuccessful test, because the peer in the minimal config is a stub.