Batch small GeoSite and GeoIP reads, reuse prefetched bytes when skipping
records, and reconstruct the matched prefix without seeking backward.
Report EOF after a matched prefix as a truncated record.
Build the succinct domain index one level at a time and alternate two reusable queues instead of retaining every processed node until construction completes.
Keep explicit node IDs to preserve terminal ordering, Foreach output, and binary serialization. Query logic and the serialized layout remain unchanged.
This reduces peak build memory and improves construction time for DomainMap. In isolated measurements, the mixed 100k-rule workload used about 53% less peak memory and completed about 11% faster, while deep 100k-rule input used about 72% less peak memory and completed about 24% faster.
The +.domain syntax includes both the domain itself and its subdomains. Using it for a suffix-only terminal adds an unintended exact match when the output is reinserted and can overwrite a separately stored exact value.
Emit .domain for suffix-only entries in DomainTrie and DomainSet Foreach, keeping exact entries separate. Preserve the internal '+' marker when converting Trie to Set, and combine MRS entries into +.domain only when both exact and suffix entries exist.
Add regression coverage for matching behavior and distinct values after reinsertion, as well as MRS deduplication and compact output. Query logic and binary layouts remain unchanged.
Once the key is exhausted, a terminal node completes the match; otherwise only saved wildcard branches can still succeed. No input remains to match the current node's child edges, so their starting position is unnecessary.
Defer the child-edge select in Has until more input needs matching, both after matching a byte and when restoring a wildcard. Keep the node-terminator select needed to resume saved wildcard branches.
Earlier candidate benchmarks show median query times ~3%/~1% lower for short/long queries and ~11%/~8% lower for exact/mixed-rule workloads, with no additional allocations or changes to the index layout.
Child edges are 0 bits and each node ends with a 1 bit in labelBitmap. An edge of nodeId has exactly nodeId preceding 1 bits, so its label index is bmIdx - nodeId. Child nodes follow the same order as labels, starting at 1 because node 0 is the root. Therefore, the child node ID is bmIdx - nodeId + 1.
Use this identity in Has and keys instead of countZeros. Keep rank for wildcard restoration, where the cursor lacks the parent node ID.
Existing short/long query benchmarks take ~12%/~11% less time, with no additional allocations or changes to the index layout.
D.Msg.PackBuffer sizes its scratch space by the uncompressed message length
and silently allocates a new slice when that exceeds the given buffer, even
though the compressed result would fit. The udp hijack path in
listener/sing_tun/dns.go always sends the original buffer, producing a
correct-length datagram full of zeros or stale bytes whenever a reply's
uncompressed size exceeds SafeDnsPacketSize (e.g. 100+ A records for one
name). Copy the packed result back into the caller's buffer when it fits.
The tcp path in RelayDnsConn already guards against this; the udp path did
not.