AdguardTeam / AdguardTeam/dnsproxy
Plain UDP upstream exchange fails on responses > 512 bytes (dns: buffer size too small / unpack overflows) — regression between v0.75.0 and v0.81.4
- Lingua principale
- Go
- Stelle
- 3.3k
- Fork
- 343
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Summary
Plain UDP upstream exchanges fail with `dns: buffer size too small` (and unpack-overflow variants) whenever the upstream returns a response larger than 512 bytes. This is a regression between v0.75.0 and v0.81.4, observed through AdGuard Home 0.107.76 (which vendors dnsproxy v0.81.4 + miekg/dns v1.1.72).
## Environment
- AdGuard Home 0.107.76 (dnsproxy v0.81.4, miekg/dns v1.1.72) on OpenWrt
- Upstream: plain UDP `127.0.0.1:9053` (local dnscrypt-proxy which relays large answers for Chinese CDN CNAME chains, e.g. `monitor.daliapp.cn` → 1005-byte NOERROR response)
## Symptom
Frequent log entries:
```
[error] dnsproxy: exchange failed upstream=127.0.0.1:9053 question=";logifier.doubao.com. IN A" duration=7.6ms err="exchanging with 127.0.0.1:9053 over udp: dns: buffer size too small"
[error] dnsproxy: exchange failed upstream=127.0.0.1:9053 question=";api.daliapp.cn. IN A" duration=115.5ms err="exchanging with 127.0.0.1:9053 over udp: dns: overflow unpacking uint32"
[error] dnsproxy: exchange failed upstream=127.0.0.1:9053 question=";logifier.doubao.com. IN A" duration=7.6ms err="exchanging with 127.0.0.1:9053 over udp: dns: overflow unpacking uint16"
```
Clients receive SERVFAIL. 322 occurrences within hours on a lightly-loaded router.
## Regression evidence (controlled A/B)
Identical workload (same set of ~1 KB-answer domains queried through AGH → dnsproxy → local forwarder) on two OpenWrt routers:
| AGH | dnsproxy | miekg/dns | plain UDP upstream result |
|---|---|---|---|
| 0.107.57 | v0.75.0 | v1.1.63 | all domains resolve, **0 errors** |
| 0.107.76 | v0.81.4 | v1.1.72 | SERVFAIL + the errors above, **322 in hours** |
Verified live on both; the old stack was re-tested with the same upstream and domain set and passes cleanly, so this is not workload-dependent.
The overflow-style errors (`overflow unpacking uint16/uint32`) suggest the response is being silently truncated at 512 bytes during the socket read and then fails to unpack, i.e. the read buffer ends up at 512 despite the request carrying an EDNS0 OPT advertising a larger size.
## Code pointers
- `upstream/plain.go` `dialExchange` → `setRequestForNetwork` sets `conn.UDPSize = dns.MinMsgSize` (512).
- miekg/dns `client.go` `ExchangeWithConnContext` contains `if opt != nil && opt.UDPSize() >= MinMsgSize { co.UDPSize = opt.UDPSize() }` — this override appears identical in v1.1.63 and v1.1.72, so the regression presumably hides in the read/unpack path changes between those versions; either way, sizing the read buffer from `dns.MinMsgSize` and relying on the OPT override looks fragile.
## Repro
```
# AGH upstream_dns: [ 127.0.0.1:9053 ] (plain UDP forwarder that relays ~1KB answers)
dig @127.0.0.1 -p 53 monitor.daliapp.cn # through AGH → SERVFAIL + error in log
dig @127.0.0.1 -p 9053 monitor.daliapp.cn # direct to the forwarder → full 1005-byte NOERROR
```
## Workaround
`tcp://127.0.0.1:9053` as upstream works fine (the forwarder listens on TCP as well), which is what we deployed.
Note: AdGuard Home's unmerged `udp_buf_size` exposure (AdguardTeam/AdGuardHome#8054) maps to the inbound socket buffer and does not address this upstream-exchange path.
## Suggested fix
Size the upstream UDP read buffer independently of the request (e.g. `dns.MaxMsgSize`) so any untruncated response can always be read, instead of starting from `dns.MinMsgSize` and depending on the OPT override.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.