Skip to content

Commit dce2970

Browse files
committed
dnsx/cacher,dnsx/alg,x64/dns64: skip dnssec
1 parent 98fe1c0 commit dce2970

5 files changed

Lines changed: 61 additions & 22 deletions

File tree

intra/backend/dnsx_listener.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ type DNSSummary struct {
4343
// True if any among upstream transports (primary or secondary) returned blocked ans.
4444
// Only valid for A/AAAA queries. Unspecified IPs are considered as "blocked ans".
4545
UpstreamBlocks bool
46+
// True if DNSSEC OK bit is set.
47+
DO bool
48+
// True if DNSSEC validation was successful.
49+
AD bool
4650
// Diag message from Transport, if any. Typically, "no error"
4751
Msg string
4852
// Region of the Rethink DNS+ server (if used)

intra/dnsx/alg.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,9 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
835835
// the effort of setting up alg/ptr/nat caches which is wasteful in this case.
836836
dontalg := usepreset || skipcache || uidself
837837
synthAns := usepreset || usefixed
838+
hasdnssec := xdns.IsDNSSECRequested(q)
839+
840+
smm.DO = hasdnssec
838841

839842
if discarduid {
840843
uid = core.UNKNOWN_UID_STR
@@ -888,14 +891,17 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
888891
if !xdns.HasRcodeSuccess(ansin) {
889892
return ansin, err
890893
}
891-
log.D("alg: err but ans ok: %d; self? %t synth? %t; qerr %v",
892-
xdns.Len(ansin), uidself, synthAns, err)
894+
log.D("alg: for %s:%s err but ans ok: %d; do? %t, self? %t synth? %t; qerr %v",
895+
qname(q), qtype(q), xdns.Len(ansin), hasdnssec, uidself, synthAns, err)
893896
}
894897

895898
if ansin == nil { // may be nil on errors
896899
return nil, errNoAnswer // err is nil
897900
}
898901

902+
hasauth64 := false
903+
hasauth := xdns.IsDNSSECAnswerAuthenticated(ansin)
904+
899905
qname := qname(ansin)
900906
qtyp := qtype(ansin)
901907
smm.QName = qname
@@ -904,14 +910,19 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
904910
// if usefixed is true, then d64 is no-op, as preset fixed ip does have ipv6
905911
ans64 := t.dns64.D64(network, t1.ID().V(), uid, ansin) // ans64 may be nil if no D64 or error
906912
if ans64 != nil {
907-
log.D("alg: %s<>%s:%s[%s] %d dns64; s/ans(%d)/ans64(%d)",
908-
qname, smm.ID, idstr(t1), uid, qtyp, xdns.Len(ansin), xdns.Len(ans64))
913+
log.D("alg: %s<>%s:%s[%s] %d dns64; dnssec? %t; s/ans(%d)/ans64(%d)",
914+
qname, smm.ID, idstr(t1), uid, qtyp, hasdnssec, xdns.Len(ansin), xdns.Len(ans64))
909915
withDNS64Summary(ans64, smm)
910916
// todo: for uidself, skip dns64? see: ipmapper.go:undoAlgAndOrNat64
911917
// todo: skip for for undelegated domains like ipv4only.arpa?
912918
ansin = ans64
919+
// false if ans64 is synthesized or nil
920+
// or its value matches hasauth
921+
hasauth64 = xdns.IsDNSSECAnswerAuthenticated(ans64)
913922
} // else: no dns64, or error; continue with ansin
914923

924+
smm.AD = hasauth && hasauth64 // true if both ansin and ans64 are authenticated
925+
915926
hasq := xdns.HasAAAAQuestion(ansin) || xdns.HasAQuestion(ansin) ||
916927
xdns.HasSVCBQuestion(ansin) || xdns.HasHTTPQuestion(ansin)
917928
hasans := xdns.HasAnyAnswer(ansin)
@@ -927,8 +938,8 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
927938

928939
// todo: skip alg for undelegated domains like ipv4only.arpa?
929940
if !hasq || !hasans || !rgood || ans0000 || dontalg {
930-
log.D("alg: skip; query %s<>%s[%s]:%s:%d / a:%d, self(%t) dontalg(%t) hasq(%t) hasans(%t) rgood(%t), ans0000(%t)",
931-
smm.ID, idstr(t1), uid, qname, qtyp, xdns.Len(ansin), uidself, dontalg, hasq, hasans, rgood, ans0000)
941+
log.D("alg: skip; query %s<>%s[%s]:%s:%d / a:%d, dnssec(do? %t /ad? %t) self(%t) dontalg(%t) hasq(%t) hasans(%t) rgood(%t), ans0000(%t)",
942+
smm.ID, idstr(t1), uid, qname, qtyp, xdns.Len(ansin), smm.DO, smm.AD, uidself, dontalg, hasq, hasans, rgood, ans0000)
932943
return ansin, nil
933944
}
934945

@@ -1044,8 +1055,8 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
10441055
mustsubst = true
10451056
}
10461057

1047-
log.D("alg: %s<>%s[%s]; %s:%d a6(a %d / h %d / s %t) : a4(a %d / h %d / s %t); ttl: %s",
1048-
smm.ID, idstr(t1), uid, qname, qtyp, len(a6), len(ip6hints), substok6, len(a4), len(ip4hints), substok4, ansttl)
1058+
log.D("alg: %s<>%s[%s]; %s:%d (do? %t / ad? %t) a6(a %d / h %d / s %t) : a4(a %d / h %d / s %t); ttl: %s",
1059+
smm.ID, idstr(t1), uid, qname, qtyp, smm.DO, smm.AD, len(a6), len(ip6hints), substok6, len(a4), len(ip4hints), substok4, ansttl)
10491060
if !substok4 && !substok6 {
10501061
if mustsubst {
10511062
err = errAlgCannotSubst
@@ -1084,13 +1095,13 @@ func (t *dnsgateway) q(t1, t2 Transport, preset []netip.Addr, network, uid strin
10841095
// the answer, whose ID != to t1 (cacher) itself. OTOH, dnsx.Resolver
10851096
// uses DNSSummary.ID when returning ans to the caller (ex: ipmapper)
10861097
tidToReg := smm.ID
1087-
log.D("alg: ok; for %s<>%s[%s]:%s:%d, domains %s real: %s / fix: %s => subst %s | %s; (mod? %t / fix? %t / synth? %t); sec %s; ttl %s",
1088-
tidToReg, idstr(t1), uid, qname, qtyp, targets, realip, fixedips, algip4, algip6, mod, usefixed, synthAns, secres.ips, ansttl)
1098+
log.D("alg: ok; for %s<>%s[%s]:%s:%d (do? %t / ad? %t), domains %s real: %s / fix: %s => subst %s | %s; (mod? %t / fix? %t / synth? %t); sec %s; ttl %s",
1099+
tidToReg, idstr(t1), uid, qname, qtyp, smm.DO, smm.AD, targets, realip, fixedips, algip4, algip6, mod, usefixed, synthAns, secres.ips, ansttl)
10891100

10901101
if t.registerLocked(qname, tidToReg, uid, algip4, algip6, realip, ansttl, targets, secres) {
10911102
// if mod is set, send modified answer
10921103
if mod {
1093-
withAlgSummaryIfNeeded(smm, algip4, algip6)
1104+
withAlgSummary(smm, algip4, algip6)
10941105
return ansmod, nil
10951106
} else {
10961107
return ansin, nil
@@ -1130,7 +1141,7 @@ func withDNS64Summary(ans64 *dns.Msg, s *x.DNSSummary) {
11301141
}
11311142
}
11321143

1133-
func withAlgSummaryIfNeeded(s *x.DNSSummary, algips ...netip.Addr) {
1144+
func withAlgSummary(s *x.DNSSummary, algips ...netip.Addr) {
11341145
if settings.Debug {
11351146
// convert algips to ipcsv; any algips may be invalid
11361147
ipcsv := Netip2Csv(algips)
@@ -1147,6 +1158,8 @@ func withAlgSummaryIfNeeded(s *x.DNSSummary, algips ...netip.Addr) {
11471158
s.Server = prefix + notransport
11481159
}
11491160
}
1161+
// if modified alg ips are being returned, then these are not authentic
1162+
s.AD = len(algips) > 0
11501163
}
11511164

11521165
func (t *dnsgateway) registerLocked(q, tid, uid string, algip4, algip6 netip.Addr, realips []netip.Addr, ttl time.Duration, targets []string, secres secans) bool {

intra/dnsx/cacher.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var (
5858
errBlocked = errors.New("dns: answer blocked")
5959
errHangover = errors.New("dns: no connectivity")
6060
errNilCacheResponse = errors.New("dns: nil cache response")
61+
errCannotUseStaleCache = errors.New("dns: cannot use stale cache response")
6162
errSkipInternalCache = errors.New("dns: skip internal cache")
6263
errCacheResponseMismatch = errors.New("dns: cache response mismatch")
6364
)
@@ -193,8 +194,12 @@ func mkcachekey(q *dns.Msg) (string, uint8, bool) {
193194
return "", 0, false
194195
}
195196
qtyp := strconv.Itoa(int(xdns.QType(q)))
197+
do := "0"
198+
if xdns.IsDNSSECRequested(q) {
199+
do = "1"
200+
}
196201

197-
return qname + cacheKeySep + qtyp, hash(qname), true
202+
return qname + cacheKeySep + qtyp + cacheKeySep + do, hash(qname), true
198203
}
199204

200205
// scrubCache deletes expired entries from the cache.
@@ -287,11 +292,19 @@ func (cb *cache) put(key string, cc *cres) (ok bool) {
287292
// 1. ansttl is 0 for synthesized "block" answers (see xdns.BlockTTL)
288293
// 2. for most empty ans (like qtype:65), ansttl is 0
289294
ansttl := time.Duration(xdns.RTtl(ans)) * time.Second
290-
if ansttl < cb.ttl {
291-
ansttl = cb.ttl
292-
} else { // bump up a bit longer than the ttl
293-
ansttl = ansttl + cb.halflife
295+
296+
if !xdns.IsDNSSECAnswerAuthenticated(ans) {
297+
if ansttl < cb.ttl {
298+
ansttl = cb.ttl
299+
} else { // bump up a bit longer than the ttl
300+
ansttl = ansttl + cb.halflife
301+
}
294302
}
303+
304+
if ansttl == 0 {
305+
return
306+
}
307+
295308
exp := time.Now().Add(ansttl)
296309
v := &cres{ // TODO: copy is not required?
297310
ans: cc.ans.Copy(),
@@ -319,6 +332,11 @@ func asResponse(q *dns.Msg, v *cres, fresh bool) (a *dns.Msg, s *x.DNSSummary, e
319332
err = errNilCacheResponse
320333
return
321334
}
335+
if !fresh && xdns.IsDNSSECAnswerAuthenticated(a) {
336+
// for stale responses, TTL is modified which violates DNSSEC?
337+
err = errCannotUseStaleCache
338+
return
339+
}
322340
aname := qname(a)
323341
qname := qname(q)
324342
if aname != qname {
@@ -327,7 +345,7 @@ func asResponse(q *dns.Msg, v *cres, fresh bool) (a *dns.Msg, s *x.DNSSummary, e
327345
return
328346
}
329347

330-
a.Id = q.Id
348+
a.Id = q.Id // OK to change even if dnssec?
331349
// dns 0x20 may mangle the question section, so preserve it
332350
// github.com/jedisct1/edgedns#correct-support-for-the-dns0x20-extension
333351
a.Question = q.Question
@@ -589,6 +607,8 @@ func fillSummary(s *x.DNSSummary, out *x.DNSSummary) {
589607

590608
if len(s.RData) != 0 {
591609
out.RData = s.RData
610+
out.AD = s.AD
611+
out.DO = s.DO
592612
}
593613

594614
out.Cached = s.Cached

intra/x64/dns64.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,9 @@ func (d *dns64) eval(network string, force64 bool, ansin *dns.Msg, r, uid string
165165
hasq6 := xdns.HasAAAAQuestion(ansin)
166166
hasans6 := xdns.HasAAAAAnswer(ansin)
167167
ans00006 := xdns.AQuadAUnspecified(ansin)
168+
hasauth := xdns.IsDNSSECAnswerAuthenticated(ansin)
168169
// treat as if v6 answer missing if enforcing 6to4
169-
if !hasq6 || (hasans6 && !force64) || ans00006 {
170+
if !hasq6 || ((hasauth || hasans6) && !force64) || ans00006 {
170171
// nb: has-aaaa-answer should cover for cases where
171172
// the response is blocked by dnsx.RDNS
172173
log.D("dns64: for(%s %s), no-op q(%s), q6(%t), ans6(%t), force64(%t), ans0000(%t)",
@@ -182,9 +183,9 @@ func (d *dns64) eval(network string, force64 bool, ansin *dns.Msg, r, uid string
182183
ip64 = d.ip64[dnsx.Local464Resolver]
183184
}
184185
}
185-
log.D("dns64: attempt underlay/local464 resolver [%s@%s] ip64 w len(%d)", r, uid, len(ip64))
186+
log.D("dns64: attempt underlay/local464 resolver [%s@%s] ip64 (ad? %t) w len(%d)", r, uid, hasauth, len(ip64))
186187
} else {
187-
log.V("dns64: for %s, no resolver id(%s[%s]) registered", uid, r, id)
188+
log.V("dns64: for %s, no resolver id(%s[%s]) registered (ad? %t)", uid, r, id, hasauth)
188189
}
189190

190191
ans4, err := d.query64(network, ansin, r, uid)

intra/xdns/common.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,9 @@ func Net2ProxyID(network string) (proto string, pids []string) {
137137
return
138138
}
139139

140+
// Bust cache as needed and if ans is not authenticated.
140141
func BustAndroidCacheIfNeeded(ans *dns.Msg) bool {
141-
if BustDnsproxydResNetCache {
142+
if BustDnsproxydResNetCache && !IsDNSSECAnswerAuthenticated(ans) {
142143
// TODO: skip negative records (SOA, NXDOMAIN, etc)
143144
return WithTtl(ans, ZeroTTL, dns.TypeA, dns.TypeAAAA)
144145
}

0 commit comments

Comments
 (0)