-
Notifications
You must be signed in to change notification settings - Fork 6
/
amazondns.go
186 lines (156 loc) · 3.57 KB
/
amazondns.go
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package amazondns
import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// AmazonDNS represents a plugin instance that can proxy requests to AmazonDNS.
type AmazonDNS struct {
Next plugin.Handler
client *dns.Client
zones []string
zoneMap map[string]*Zone
}
type Zone struct {
zone string
dns string
soa dns.RR
ns []dns.RR
nsa []dns.RR
}
// ServeDNS implements plugin.Handler.
func (ad AmazonDNS) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
name := state.Name()
key := plugin.Zones(ad.zones).Matches(name)
if key == "" {
return plugin.NextOrFailure(ad.Name(), ad.Next, ctx, w, r)
}
zone := ad.zoneMap[key]
qtype := state.QType()
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
L:
switch qtype {
case dns.TypeA:
for _, nsa := range zone.nsa {
if name == nsa.Header().Name {
m.Answer = []dns.RR{nsa}
m.Ns = zone.ns
m.Rcode = dns.RcodeSuccess
break L
}
}
fallthrough
case dns.TypeAAAA:
fallthrough
case dns.TypeCNAME:
// Need recursive mode for getting record from AmazonDNS
r.MsgHdr.RecursionDesired = true
resp, _, err := ad.client.Exchange(r, zone.dns)
if err != nil {
return dns.RcodeServerFailure, err
}
m.Answer = resp.Answer
m.Rcode = resp.Rcode
// It's useful resolving CNAME for DNS of ELB, RDS and so on
resolveCNAME(name, m)
// Overwrite authority and additional section
if len(m.Answer) > 0 {
m.Ns = zone.ns
m.Extra = zone.nsa
} else {
handleNotFound(zone, name, m)
}
case dns.TypeNS:
if name == zone.soa.Header().Name {
m.Answer = zone.ns
m.Extra = zone.nsa
m.Rcode = dns.RcodeSuccess
} else {
handleNotFound(zone, name, m)
}
case dns.TypeSOA:
if name == zone.soa.Header().Name {
m.Answer = []dns.RR{zone.soa}
m.Ns = zone.ns
} else {
handleNotFound(zone, name, m)
}
default:
handleNotFound(zone, name, m)
}
state.SizeAndDo(m)
m, _ = state.Scrub(m)
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
func handleNotFound(zone *Zone, name string, m *dns.Msg) {
// Set authority
m.Ns = []dns.RR{zone.soa}
if m.Rcode == dns.RcodeNameError {
if name == zone.soa.Header().Name {
m.Rcode = dns.RcodeSuccess
return
}
for _, ns := range zone.ns {
if name == ns.Header().Name {
m.Rcode = dns.RcodeSuccess
return
}
}
for _, nsa := range zone.nsa {
if name == nsa.Header().Name {
m.Rcode = dns.RcodeSuccess
return
}
}
}
}
func resolveCNAME(reqName string, res *dns.Msg) {
ignore := map[string]struct{}{}
for {
var name string
var cname string
var ttl uint32
cnameIndex := -1
// Find CNAME record
for i, rr := range res.Answer {
if rr.Header().Rrtype == dns.TypeCNAME {
name = rr.Header().Name
if name != reqName {
continue
}
if _, ok := ignore[name]; ok {
continue
}
cname = rr.(*dns.CNAME).Target
ttl = rr.Header().Ttl
cnameIndex = i
break
}
}
if cnameIndex == -1 {
break
}
var replaced bool
// Replace records belong to CNAME record
for _, rr := range res.Answer {
if rr.Header().Name == cname {
rr.Header().Name = name
rr.Header().Ttl = ttl
replaced = true
}
}
// Remove CNAME record
if replaced {
res.Answer = append(res.Answer[:cnameIndex], res.Answer[cnameIndex+1:]...)
} else {
ignore[name] = struct{}{}
}
}
}
// Name implements the Handler interface.
func (ad AmazonDNS) Name() string { return "amazondns" }