Highlights
- XDNS finalmask transport hardened end to end: a pre-auth memory-exhaustion vector, data races, silent packet loss and a busy-spin failure mode are fixed. The transport survived a full loopback E2E battery: 8 MB transfers with sha256 match, six-way concurrency, a second client instance and three restart cycles.
Fork fixes
- xdns server: the per-client writeQueueMap is capped at 256 entries. Keys are attacker-chosen clientIDs decoded from query labels, so a unique-ID flood could previously pin unbounded memory (a 512-slot channel plus stash per entry) before the idle timeout ever fired. Over-limit clients are refused with an explicit error.
- xdns client/server: removed data races on resolverIdx (sendLoop mutated it unlocked while WriteTo read it under the mutex) and on closed (raw reads in both recvLoops). Poll encoding now reads one consistent resolver slot.
- xdns: packet loss is observable again. WriteTo reports errPayloadTooBig / errQueueFull (with a bounded 20 ms block so bursts keep mKCP pacing), encode errors propagate, and ReadFrom reports short consumer buffers instead of dropping silently. The dead len(p) >= 224 gate is replaced by a per-resolver limit measured from the real codec at construction.
- xdns: both recvLoops back off exponentially (10 ms base, 1 s cap) on transient UDP read errors instead of hot-spinning a core when an interface flaps.
- xdns server: responses routed to an idle-pruned queue no longer produce empty-answer artifacts; a live queue still emits the legitimate empty ack that dnstt-style ramp-up relies on; queue retirement is exactly-once (the idle pruner and the shutdown sweep can no longer double-close channels, which panicked under -race).
- xdns: hostile RDLENGTH values no longer allocate attacker-sized slices before validation (~66 KB to 432 B per crafted parse); the response batching window is armed once so stale timer ticks cannot collapse aggregation mid-batch; the grouping ladder is extracted into groupRecord with explicit emit/drop/stop outcomes.
- xdns: per-type encoded payload ceilings are computed lazily (sync.OnceValue) instead of panicking at package init, and are pinned by tests (TXT 934, A 118, AAAA 462 on a 1232-byte budget). The dnstt-derived DNS wire-format codec is now attributed (MIT, David Fifield).
- conf: xdns resolver and domain entries are validated at config-build time through shared runtime parsers (ValidateResolver / ValidateDomainSpec); the duplicated +udp:// substring check is gone, and non-IP resolver hosts fail early instead of at dial time.
- xdns config: the dead outermost-level guard (commented-out FakePacketConn/UdpHop checks) is replaced by a documented nesting policy with a runtime warning when the transport is wrapped at a non-outermost layer.
- tests: the vacuous len(encoded) < 0 assertion in TestEncodeRDataTXT now checks the exact [00] wire answer, debug fmt.Println output is gone, and new lifecycle stress cells cover concurrent WriteTo/ReadFrom/Close for both wrappers.
Upstream changes
- Merged upstream main at 65458e9 - "Config: Fix some issues (XTLS#6640)": config handling adjustments in infra/conf (xray/common), HTTP proxy client fixes and TCP hub housekeeping.
Compatibility notes
- xdns requires a small mKCP MTU (for example 120; the config floor is 21) because the DNS query payload budget is roughly 137 bytes for the smallest domain suffixes and shrinks with longer domains.
- xdns now returns typed errors on overflow/oversize instead of reporting success on silent drops; mKCP retransmission remains the recovery path, so well-formed deployments are unaffected, but loss is no longer invisible.
- Malformed xdns resolvers / domains entries are rejected at config-build time (previously they surfaced as dial/listen errors).
- mKCP header / seed remain removed in this fork; use finalmask/udp mkcp-original or mkcp-aes128gcm masking.
Validation
- go test ./transport/internet/finalmask/xdns/... ./infra/conf ./core ./transport/internet/tcp -count=1 - green.
- go test -race ./transport/internet/finalmask/xdns/ -count=1 - green, including new concurrent lifecycle stress cells.
- go test -gcflags=all=-d=checkptr=2 ./transport/internet/finalmask/xdns/ -count=1 - green; go vet clean.
- Loopback E2E over the real SOCKS -> Xray(xdns/mkcp) -> Xray -> HTTP origin path: hello smoke, 64 KB and 8 MB transfers with sha256 match, six-way concurrent downloads, second client instance, three client restart cycles; queue_full events observed in logs under load with no hangs.
- Linux/amd64 release-style build: CGO_ENABLED=0 GOAMD64=v1 -trimpath, xray version prints 26.8.27-1556; TestVersionFollowsYearMonthDayHHMM green.
- check-proto: regeneration with the locally available protoc 33.5 + protoc-gen-go-grpc 1.5.1 diverges from committed generated files (it would drop fork fields such as dokodemo PortMap); committed generated files are kept and their headers already pin protoc v6.33.5 / protoc-gen-go v1.36.11. Recorded as a known limitation of this environment.