forked from cbuijs/sdproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransform.go
More file actions
120 lines (108 loc) · 3.46 KB
/
transform.go
File metadata and controls
120 lines (108 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
File: transform.go
Version: 1.0.0
Updated: 2026-03-23 16:00 CET
Description:
Outbound response mutation functions for sdproxy. Applied in ProcessDNS
(process.go) after a response arrives from the cache or upstream, before
it is written to the client.
transformResponse — dispatcher; calls the active transforms in order.
flattenCNAME — collapses a CNAME chain into a single A/AAAA record
under the original query name (flatten_cname: true).
responseContainsNullIP — detects 0.0.0.0 / :: sentinel IPs used by
upstream blocklists; used to annotate cache-hit logs.
All functions operate on an existing *dns.Msg — no upstream calls, no I/O.
Changes:
1.0.0 - Split from process.go v1.52.0.
... Older commit-information removed for brevity.
*/
package main
import "github.com/miekg/dns"
// transformResponse applies the configured response transforms in order:
// 1. flattenCNAME — when flatten_cname: true and qtype is A or AAAA.
// 2. minimizeAnswer — strips Ns and Extra sections when minimize_answer: true.
//
// inPlace controls whether msg is mutated directly (true) or a copy is made
// first (false). Callers that own the message (e.g. upstream response) pass
// true; callers sharing a pooled pointer pass false.
//
// Returns msg unchanged when neither transform is active — zero overhead on
// the common path.
func transformResponse(msg *dns.Msg, qtype uint16, inPlace bool) *dns.Msg {
if !cfg.Server.FlattenCNAME && !cfg.Server.MinimizeAnswer {
return msg
}
out := msg
if !inPlace {
out = msg.Copy()
}
if cfg.Server.FlattenCNAME && (qtype == dns.TypeA || qtype == dns.TypeAAAA) {
flattenCNAME(out)
}
if cfg.Server.MinimizeAnswer {
out.Ns = nil
out.Extra = nil
}
return out
}
// flattenCNAME collapses a CNAME chain in out.Answer into a single A or AAAA
// record under the original query name. TTL is set to the chain minimum so
// the result expires no later than the shortest-lived link in the chain.
//
// No-ops when the answer has fewer than two records or does not start with a
// CNAME — avoids touching direct A/AAAA responses.
func flattenCNAME(out *dns.Msg) {
if len(out.Answer) < 2 {
return
}
first, ok := out.Answer[0].(*dns.CNAME)
if !ok {
return
}
queryName := first.Hdr.Name
minTTL := first.Hdr.Ttl
finals := make([]dns.RR, 0, len(out.Answer)-1)
for _, rr := range out.Answer[1:] {
if rr.Header().Ttl < minTTL {
minTTL = rr.Header().Ttl
}
switch r := rr.(type) {
case *dns.A:
cp := *r
cp.Hdr.Name = queryName
cp.Hdr.Ttl = minTTL
finals = append(finals, &cp)
case *dns.AAAA:
cp := *r
cp.Hdr.Name = queryName
cp.Hdr.Ttl = minTTL
finals = append(finals, &cp)
}
}
if len(finals) == 0 {
return // only CNAMEs with no terminal address — leave chain intact
}
// Apply the chain-minimum TTL to every final record.
for _, rr := range finals {
rr.Header().Ttl = minTTL
}
out.Answer = finals
}
// responseContainsNullIP reports whether any A or AAAA answer carries an
// unspecified address (0.0.0.0 or ::). Upstream blocklists commonly use this
// as a sentinel instead of returning NXDOMAIN. Used to annotate log lines.
func responseContainsNullIP(msg *dns.Msg) bool {
for _, rr := range msg.Answer {
switch r := rr.(type) {
case *dns.A:
if r.A.IsUnspecified() {
return true
}
case *dns.AAAA:
if r.AAAA.IsUnspecified() {
return true
}
}
}
return false
}