[Bug Report] must_rules bypass for DNS traffic broken since v2.0.0 (Must flag ignored in DNS fast path)
- Dominant language
- Go
- Stars
- 6.2k
- Forks
- 402
- Avg merge
- 1h 8m
- Merged PRs (30d)
- 1
Description
## Bug Report
### Summary
`must_rules` no longer bypasses the DNS controller for DNS traffic (port 53) in v2.0.0. This is a regression introduced by PR #970, which added DNS fast-path handlers in the userspace UDP/TCP/ingress layers and removed the v1.x `Must` guard, but did not re-apply the `Must` check in the new fast paths.
### Environment
- **Dae version**: v2.0.0 (regression confirmed; v1.1.0 unaffected)
- **OS**: Linux
- **Kernel**: 7.x
- **Others**: WAN interface bound (localhost proxy mode)
### Current Behavior
In v1.x, the userspace UDP handler had this check (`control/udp.go:150`):
```go
if routingResult.Must > 0 {
isDns = false // Regard as plain traffic.
}
```
This made `must_rules` work for DNS queries: when a process matched a `must_rules` rule (e.g. `pname(dig) -> must_rules`), its DNS queries (port 53) would bypass the DNS controller and be forwarded as plain traffic, respecting the `@server` argument of tools like `dig`.
PR #970 (`85a1fc3`, "Enhance control plane features and improve CI workflows") introduced "DNS fast path" handlers in `control/udp.go`, `control/tcp.go`, and `control/control_plane.go` for performance (avoid `UdpEndpoint` lookup per DNS query). These fast paths are gated only by `Port() == 53` and unconditionally call `DnsController.Handle_`, **without checking `routingResult.Must`**. The same PR removed the v1.x `if Must > 0 { isDns = false }` guard.
As a result, in v2.0.0, `must_rules` rules have no effect on DNS traffic — all port-53 traffic is hijacked into the DNS controller regardless of the `Must` flag set by eBPF routing.
### Steps to Reproduce
**1. Add a `must_rules` rule for a DNS tool in the routing section:**
```
routing {
pname(mosdns) -> must_rules # or pname(dig), pname(smartdns), etc.
# ... other rules ...
fallback: direct
}
```
**2. Query a CDN domain via two different DNS servers.** `download.microsoft.com` has geographic routing, so different DNS resolvers return IPs from entirely different network ranges:
```
dig @114.114.114.114 download.microsoft.com +short
dig @8.8.8.8 download.microsoft.com +short
```
**3. Compare the two answers.**
### Expected Behavior
| | v1.x (and patched v2.0.0) | v2.0.0 (broken) |
|---|---|---|
| `must_rules` for DNS | Works — `@server` is respected | **Broken** — `@server` is ignored |
| `dig @A` vs `dig @B` on a geo-routed CDN | Different IPs (each server's node) | **Both equal dae's own `dns.upstream` answer** |
| Root cause | `if Must > 0 { isDns = false }` skips DNS controller | Fast path `if Port()==53 { Handle_() }` ignores `Must` flag |
The bug signal: **two different `@server` values yield the identical answer** (which equals your dae `dns.upstream`'s answer), proving `@server` was silently ignored.
### Impact
Any `must_rules` configuration that should bypass the DNS controller for port-53 traffic is silently ineffective in v2.0.0. This affects:
- DNS loop prevention for local DNS proxies (`pname(mosdns, dnsmasq, smartdns, AdGuardHome) -> must_rules`)
- Diagnostic tools (`dig`, `nslookup`, `host`) with `pname(...) -> must_rules`
### Root Cause Analysis
The `Must` flag is set by the eBPF layer (`tproxy.c` → `ROUTE_STATE_MUST`) when a `must_rules` rule matches, and is correctly propagated to userspace via `routingResult.Must`. However, the three DNS fast-path entries introduced by #970 never read this flag:
- `control/udp.go` — `if realDst.Port() == 53 { ... Handle_() ... }`
- `control/tcp.go` — `if dst.Port() == 53 { handleTCPDnsFastPath(...) }`
- `control/control_plane.go` — DNS ingress fast path → `Handle_()`
The fix is to guard these entries with a `Must` check, restoring the v1.x semantics. A PR with the fix will follow.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing the DNS fast-path branches in control/udp.go, control/tcp.go, and control/control_plane.go, then reproduce the issue with the supplied dig commands and two @server values. Done means must_rules traffic bypasses the DNS controller as in v1.x, while ordinary port-53 traffic still uses the fast path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, linux
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100