Summary
parse_dns_response does two things to a DNS reply: it blanks SVCB/HTTPS answers, and it synthesises an empty response for domains that is_domain_blocked rejects — dns.c:181. Neither happens while remote VPN (WireGuard) egress is on.
Root cause
parse_dns_response is reached from exactly two places, both on the userspace NAT path:
udp.c:134, on the reply of an established UDP session
tcp.c:608, on the reply of an established TCP session
A DNS query with WireGuard on never creates either session: handle_ip sends it to the tunnel and returns (ip.c:487, wg_dest is unconditionally true for is_dns). Replies come back through gotatun and are handled by the Rust inspector in wgbridge-rs/src/dns.rs, which is explicitly passive — its module doc says packets "are never modified or blocked here". It records answers for tracker attribution and forwards them untouched.
Consequences, in order of severity
SVCB/HTTPS blanking is lost outright. Nothing else suppresses those records, so ECH and HTTP/3 upgrade hints reach the app whenever the tunnel is on. This is the divergence that motivated unifying the two DNS parsers in the first place.
DNS-level domain blocking degrades rather than disappears. is_address_allowed still runs in handle_ip before the hijack, so a blocked domain's connections are still blocked by IP. What is lost is the NXDOMAIN-style answer, so an app sees a resolvable name whose connections then fail, instead of a clean resolution failure. Different failure shape, same end state.
Scope
Any WireGuard-enabled configuration. Present on master; not introduced by #730.
Direction
The two DNS parsers (jni/netguard/dns.c and wgbridge-rs/src/dns.rs) converge on ServiceSinkhole.dnsResolved but have drifted in what they do with a response. Either give the Rust inspector the same rewriting authority as dns.c — it already parses the answer section, so it is a matter of writing back — or retire dns.c in favour of a single shared implementation, which is the direction the routing policy has just taken (wgbridge-rs/src/policy.rs).
Root cause is shared with the two sibling issues linked below: handle_ip hands the packet to WireGuard before the DNS and session machinery runs, so everything that machinery provides disappears whenever the tunnel is on.
Summary
parse_dns_responsedoes two things to a DNS reply: it blanks SVCB/HTTPS answers, and it synthesises an empty response for domains thatis_domain_blockedrejects —dns.c:181. Neither happens while remote VPN (WireGuard) egress is on.Root cause
parse_dns_responseis reached from exactly two places, both on the userspace NAT path:udp.c:134, on the reply of an established UDP sessiontcp.c:608, on the reply of an established TCP sessionA DNS query with WireGuard on never creates either session:
handle_ipsends it to the tunnel and returns (ip.c:487,wg_destis unconditionally true foris_dns). Replies come back through gotatun and are handled by the Rust inspector inwgbridge-rs/src/dns.rs, which is explicitly passive — its module doc says packets "are never modified or blocked here". It records answers for tracker attribution and forwards them untouched.Consequences, in order of severity
SVCB/HTTPS blanking is lost outright. Nothing else suppresses those records, so ECH and HTTP/3 upgrade hints reach the app whenever the tunnel is on. This is the divergence that motivated unifying the two DNS parsers in the first place.
DNS-level domain blocking degrades rather than disappears.
is_address_allowedstill runs inhandle_ipbefore the hijack, so a blocked domain's connections are still blocked by IP. What is lost is the NXDOMAIN-style answer, so an app sees a resolvable name whose connections then fail, instead of a clean resolution failure. Different failure shape, same end state.Scope
Any WireGuard-enabled configuration. Present on
master; not introduced by #730.Direction
The two DNS parsers (
jni/netguard/dns.candwgbridge-rs/src/dns.rs) converge onServiceSinkhole.dnsResolvedbut have drifted in what they do with a response. Either give the Rust inspector the same rewriting authority asdns.c— it already parses the answer section, so it is a matter of writing back — or retiredns.cin favour of a single shared implementation, which is the direction the routing policy has just taken (wgbridge-rs/src/policy.rs).Root cause is shared with the two sibling issues linked below:
handle_iphands the packet to WireGuard before the DNS and session machinery runs, so everything that machinery provides disappears whenever the tunnel is on.