diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | dns/types.go | 271 | ||||
-rw-r--r-- | generate_dns_types.go | 192 |
4 files changed, 206 insertions, 268 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75c9781 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1,4 @@ | |||
1 | ./dns-service | ||
2 | |||
3 | # Generated files have a zzz_ prefix | ||
4 | **/zzz_*.go | ||
@@ -1,8 +1,9 @@ | |||
1 | GO_FILES := $(shell find . -name '*.go') | 1 | .PHONY: dns-service |
2 | 2 | dns-service: | |
3 | dns-service: main.go $(GO_FILES) | 3 | go generate ./... |
4 | CGO_ENABLED=0 go build -o $@ $< | 4 | CGO_ENABLED=0 go build -o $@ $< |
5 | 5 | ||
6 | .PHONY: clean | 6 | .PHONY: clean |
7 | clean: | 7 | clean: |
8 | rm -f dns-service | 8 | rm -f dns-service |
9 | find . -name 'zzz_*.go' -delete | ||
diff --git a/dns/types.go b/dns/types.go index 8f840be..aa5112b 100644 --- a/dns/types.go +++ b/dns/types.go | |||
@@ -2,12 +2,14 @@ package dns | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | "net" | 5 | _ "net" |
6 | 6 | ||
7 | "code.crute.me/mcrute/go_ddns_manager/bind" | 7 | "code.crute.me/mcrute/go_ddns_manager/bind" |
8 | "github.com/miekg/dns" | 8 | "github.com/miekg/dns" |
9 | ) | 9 | ) |
10 | 10 | ||
11 | //go:generate go run ../generate_dns_types.go | ||
12 | |||
11 | func makeHeader(name string, zone *bind.Zone, t uint16, ttl int) dns.RR_Header { | 13 | func makeHeader(name string, zone *bind.Zone, t uint16, ttl int) dns.RR_Header { |
12 | return dns.RR_Header{ | 14 | return dns.RR_Header{ |
13 | Name: fmt.Sprintf("%s.%s", name, zone.Name), | 15 | Name: fmt.Sprintf("%s.%s", name, zone.Name), |
@@ -27,268 +29,7 @@ func toRRSet(z *bind.Zone, rr ...RR) []dns.RR { | |||
27 | 29 | ||
28 | type RR interface { | 30 | type RR interface { |
29 | ToDNS(*bind.Zone) dns.RR | 31 | ToDNS(*bind.Zone) dns.RR |
30 | } | 32 | FromDNS(rr dns.RR) error |
31 | 33 | MarshalJSON() ([]byte, error) | |
32 | type A struct { | 34 | UnmarshalJSON(data []byte) error |
33 | Name string | ||
34 | Ttl int | ||
35 | A net.IP | ||
36 | } | ||
37 | |||
38 | func (r *A) ToDNS(zone *bind.Zone) dns.RR { | ||
39 | return &dns.A{ | ||
40 | Hdr: makeHeader(r.Name, zone, dns.TypeA, r.Ttl), | ||
41 | A: r.A, | ||
42 | } | ||
43 | } | ||
44 | |||
45 | type AAAA struct { | ||
46 | Name string | ||
47 | Ttl int | ||
48 | AAAA net.IP | ||
49 | } | ||
50 | |||
51 | func (r *AAAA) ToDNS(zone *bind.Zone) dns.RR { | ||
52 | return &dns.AAAA{ | ||
53 | Hdr: makeHeader(r.Name, zone, dns.TypeAAAA, r.Ttl), | ||
54 | AAAA: r.AAAA, | ||
55 | } | ||
56 | } | ||
57 | |||
58 | type CAA struct { | ||
59 | Name string | ||
60 | Ttl int | ||
61 | Flag uint8 | ||
62 | Tag string | ||
63 | Value string | ||
64 | } | ||
65 | |||
66 | func (r *CAA) ToDNS(zone *bind.Zone) dns.RR { | ||
67 | return &dns.CAA{ | ||
68 | Hdr: makeHeader(r.Name, zone, dns.TypeCAA, r.Ttl), | ||
69 | Flag: r.Flag, | ||
70 | Tag: r.Tag, | ||
71 | Value: r.Value, | ||
72 | } | ||
73 | } | ||
74 | |||
75 | type CERT struct { | ||
76 | Name string | ||
77 | Ttl int | ||
78 | Type uint16 | ||
79 | KeyTag uint16 | ||
80 | Algorithm uint8 | ||
81 | Certificate string | ||
82 | } | ||
83 | |||
84 | func (r *CERT) ToDNS(zone *bind.Zone) dns.RR { | ||
85 | return &dns.CERT{ | ||
86 | Hdr: makeHeader(r.Name, zone, dns.TypeCERT, r.Ttl), | ||
87 | Type: r.Type, | ||
88 | KeyTag: r.KeyTag, | ||
89 | Algorithm: r.Algorithm, | ||
90 | Certificate: r.Certificate, | ||
91 | } | ||
92 | } | ||
93 | |||
94 | type CNAME struct { | ||
95 | Name string | ||
96 | Ttl int | ||
97 | Target string | ||
98 | } | ||
99 | |||
100 | func (r *CNAME) ToDNS(zone *bind.Zone) dns.RR { | ||
101 | return &dns.CNAME{ | ||
102 | Hdr: makeHeader(r.Name, zone, dns.TypeCNAME, r.Ttl), | ||
103 | Target: r.Target, | ||
104 | } | ||
105 | } | ||
106 | |||
107 | type DNAME struct { | ||
108 | Name string | ||
109 | Ttl int | ||
110 | Target string | ||
111 | } | ||
112 | |||
113 | func (r *DNAME) ToDNS(zone *bind.Zone) dns.RR { | ||
114 | return &dns.DNAME{ | ||
115 | Hdr: makeHeader(r.Name, zone, dns.TypeDNAME, r.Ttl), | ||
116 | Target: r.Target, | ||
117 | } | ||
118 | } | ||
119 | |||
120 | type LOC struct { | ||
121 | Name string | ||
122 | Ttl int | ||
123 | Version uint8 | ||
124 | Size uint8 | ||
125 | HorizPre uint8 | ||
126 | VertPre uint8 | ||
127 | Latitude uint32 | ||
128 | Longitude uint32 | ||
129 | Altitude uint32 | ||
130 | } | ||
131 | |||
132 | func (r *LOC) ToDNS(zone *bind.Zone) dns.RR { | ||
133 | return &dns.LOC{ | ||
134 | Hdr: makeHeader(r.Name, zone, dns.TypeLOC, r.Ttl), | ||
135 | Version: r.Version, | ||
136 | Size: r.Size, | ||
137 | HorizPre: r.HorizPre, | ||
138 | VertPre: r.VertPre, | ||
139 | Latitude: r.Latitude, | ||
140 | Longitude: r.Longitude, | ||
141 | Altitude: r.Altitude, | ||
142 | } | ||
143 | } | ||
144 | |||
145 | type MX struct { | ||
146 | Name string | ||
147 | Ttl int | ||
148 | Preference uint16 | ||
149 | Mx string | ||
150 | } | ||
151 | |||
152 | func (r *MX) ToDNS(zone *bind.Zone) dns.RR { | ||
153 | return &dns.MX{ | ||
154 | Hdr: makeHeader(r.Name, zone, dns.TypeMX, r.Ttl), | ||
155 | Preference: r.Preference, | ||
156 | Mx: r.Mx, | ||
157 | } | ||
158 | } | ||
159 | |||
160 | type NAPTR struct { | ||
161 | Name string | ||
162 | Ttl int | ||
163 | Order uint16 | ||
164 | Preference uint16 | ||
165 | Flags string | ||
166 | Service string | ||
167 | Regexp string | ||
168 | Replacement string | ||
169 | } | ||
170 | |||
171 | func (r *NAPTR) ToDNS(zone *bind.Zone) dns.RR { | ||
172 | return &dns.NAPTR{ | ||
173 | Hdr: makeHeader(r.Name, zone, dns.TypeNAPTR, r.Ttl), | ||
174 | Order: r.Order, | ||
175 | Preference: r.Preference, | ||
176 | Flags: r.Flags, | ||
177 | Service: r.Service, | ||
178 | Regexp: r.Regexp, | ||
179 | Replacement: r.Replacement, | ||
180 | } | ||
181 | } | ||
182 | |||
183 | type NS struct { | ||
184 | Name string | ||
185 | Ttl int | ||
186 | Ns string | ||
187 | } | ||
188 | |||
189 | func (r *NS) ToDNS(zone *bind.Zone) dns.RR { | ||
190 | return &dns.NS{ | ||
191 | Hdr: makeHeader(r.Name, zone, dns.TypeNS, r.Ttl), | ||
192 | Ns: r.Ns, | ||
193 | } | ||
194 | } | ||
195 | |||
196 | type OPENPGPKEY struct { | ||
197 | Name string | ||
198 | Ttl int | ||
199 | PublicKey string | ||
200 | } | ||
201 | |||
202 | func (r *OPENPGPKEY) ToDNS(zone *bind.Zone) dns.RR { | ||
203 | return &dns.OPENPGPKEY{ | ||
204 | Hdr: makeHeader(r.Name, zone, dns.TypeOPENPGPKEY, r.Ttl), | ||
205 | PublicKey: r.PublicKey, | ||
206 | } | ||
207 | } | ||
208 | |||
209 | type PTR struct { | ||
210 | Name string | ||
211 | Ttl int | ||
212 | Ptr string | ||
213 | } | ||
214 | |||
215 | func (r *PTR) ToDNS(zone *bind.Zone) dns.RR { | ||
216 | return &dns.PTR{ | ||
217 | Hdr: makeHeader(r.Name, zone, dns.TypePTR, r.Ttl), | ||
218 | Ptr: r.Ptr, | ||
219 | } | ||
220 | } | ||
221 | |||
222 | type SOA struct { | ||
223 | Name string | ||
224 | Ttl int | ||
225 | Ns string | ||
226 | Mbox string | ||
227 | Serial uint32 | ||
228 | Refresh uint32 | ||
229 | Retry uint32 | ||
230 | Expire uint32 | ||
231 | Minttl uint32 | ||
232 | } | ||
233 | |||
234 | func (r *SOA) ToDNS(zone *bind.Zone) dns.RR { | ||
235 | return &dns.SOA{ | ||
236 | Hdr: makeHeader(r.Name, zone, dns.TypeSOA, r.Ttl), | ||
237 | Ns: r.Ns, | ||
238 | Mbox: r.Mbox, | ||
239 | Serial: r.Serial, | ||
240 | Refresh: r.Refresh, | ||
241 | Retry: r.Retry, | ||
242 | Expire: r.Expire, | ||
243 | Minttl: r.Minttl, | ||
244 | } | ||
245 | } | ||
246 | |||
247 | type SRV struct { | ||
248 | Name string | ||
249 | Ttl int | ||
250 | Priority uint16 | ||
251 | Weight uint16 | ||
252 | Port uint16 | ||
253 | Target string | ||
254 | } | ||
255 | |||
256 | func (r *SRV) ToDNS(zone *bind.Zone) dns.RR { | ||
257 | return &dns.SRV{ | ||
258 | Hdr: makeHeader(r.Name, zone, dns.TypeSRV, r.Ttl), | ||
259 | Priority: r.Priority, | ||
260 | Weight: r.Weight, | ||
261 | Port: r.Port, | ||
262 | Target: r.Target, | ||
263 | } | ||
264 | } | ||
265 | |||
266 | type SSHFP struct { | ||
267 | Name string | ||
268 | Ttl int | ||
269 | Algorithm uint8 | ||
270 | Type uint8 | ||
271 | FingerPrint string | ||
272 | } | ||
273 | |||
274 | func (r *SSHFP) ToDNS(zone *bind.Zone) dns.RR { | ||
275 | return &dns.SSHFP{ | ||
276 | Hdr: makeHeader(r.Name, zone, dns.TypeSSHFP, r.Ttl), | ||
277 | Algorithm: r.Algorithm, | ||
278 | Type: r.Type, | ||
279 | FingerPrint: r.FingerPrint, | ||
280 | } | ||
281 | } | ||
282 | |||
283 | type TXT struct { | ||
284 | Name string | ||
285 | Ttl int | ||
286 | Txt []string | ||
287 | } | ||
288 | |||
289 | func (r *TXT) ToDNS(zone *bind.Zone) dns.RR { | ||
290 | return &dns.TXT{ | ||
291 | Hdr: makeHeader(r.Name, zone, dns.TypeTXT, r.Ttl), | ||
292 | Txt: r.Txt, | ||
293 | } | ||
294 | } | 35 | } |
diff --git a/generate_dns_types.go b/generate_dns_types.go new file mode 100644 index 0000000..282c630 --- /dev/null +++ b/generate_dns_types.go | |||
@@ -0,0 +1,192 @@ | |||
1 | //+build ignore | ||
2 | |||
3 | package main | ||
4 | |||
5 | import ( | ||
6 | "fmt" | ||
7 | "go/types" | ||
8 | "log" | ||
9 | "os" | ||
10 | "text/template" | ||
11 | |||
12 | "golang.org/x/tools/go/packages" | ||
13 | ) | ||
14 | |||
15 | type Field struct { | ||
16 | Name string | ||
17 | Type string | ||
18 | } | ||
19 | |||
20 | var tpl = template.Must(template.New("").Parse(`package dns | ||
21 | |||
22 | // GENERATED FILE, DO NOT MODIFY | ||
23 | // See generate_dns_types.go in the repo root. | ||
24 | |||
25 | import ( | ||
26 | "encoding/json" | ||
27 | "fmt" | ||
28 | "net" | ||
29 | |||
30 | "github.com/miekg/dns" | ||
31 | |||
32 | "code.crute.me/mcrute/go_ddns_manager/bind" | ||
33 | ) | ||
34 | |||
35 | {{ range $name, $fields := . -}} | ||
36 | type {{ $name }} struct { | ||
37 | Name string | ||
38 | Ttl int | ||
39 | {{ range $fields -}} | ||
40 | {{ .Name }} {{ .Type }} | ||
41 | {{ end -}} | ||
42 | } | ||
43 | |||
44 | func (r *{{ $name }}) ToDNS(zone *bind.Zone) dns.RR { | ||
45 | return &dns.{{ $name }}{ | ||
46 | Hdr: makeHeader(r.Name, zone, dns.Type{{ $name }}, r.Ttl), | ||
47 | {{ range $fields -}} | ||
48 | {{ .Name }}: r.{{ .Name }}, | ||
49 | {{ end }} | ||
50 | } | ||
51 | } | ||
52 | |||
53 | func (r *{{ $name }}) FromDNS(rr dns.RR) error { | ||
54 | rt, ok := rr.(*dns.{{ $name }}) | ||
55 | if !ok { | ||
56 | return fmt.Errorf("Invalid type %T for '{{ $name }}'", rr) | ||
57 | } | ||
58 | |||
59 | r.Name = rr.Header().Name | ||
60 | r.Ttl = int(rr.Header().Ttl) | ||
61 | {{ range $fields -}} | ||
62 | r.{{ .Name }} = rt.{{ .Name }} | ||
63 | {{ end }} | ||
64 | |||
65 | return nil | ||
66 | } | ||
67 | |||
68 | func (r *{{ $name }}) MarshalJSON() ([]byte, error) { | ||
69 | type Alias {{ $name }} | ||
70 | return json.Marshal(&struct { | ||
71 | Type string | ||
72 | *Alias | ||
73 | }{"{{ $name }}", (*Alias)(r)}) | ||
74 | } | ||
75 | |||
76 | func (r *{{ $name }}) UnmarshalJSON(data []byte) error { | ||
77 | type Alias {{ $name }} | ||
78 | if err := json.Unmarshal(data, &struct{ | ||
79 | Type string | ||
80 | *Alias | ||
81 | }{Alias: (*Alias)(r)}); err != nil { | ||
82 | return err | ||
83 | } | ||
84 | return nil | ||
85 | } | ||
86 | |||
87 | var _ RR = (*{{ $name }})(nil) | ||
88 | |||
89 | {{ end }} | ||
90 | |||
91 | |||
92 | func FromDNS(rr dns.RR) interface{} { | ||
93 | switch v := rr.(type) { | ||
94 | {{ range $name, $fields := . -}} | ||
95 | case *dns.{{ $name }}: | ||
96 | rv := &{{ $name }}{} | ||
97 | rv.FromDNS(v) | ||
98 | return rv | ||
99 | {{ end }} | ||
100 | } | ||
101 | return nil | ||
102 | } | ||
103 | `)) | ||
104 | |||
105 | var disallowedTypes = map[string]bool{ | ||
106 | "CDNSKEY": true, | ||
107 | "CDS": true, | ||
108 | "DLV": true, | ||
109 | "KEY": true, | ||
110 | "OPT": true, | ||
111 | "SIG": true, | ||
112 | "PrivateRR": true, | ||
113 | "RFC3597": true, | ||
114 | "ANY": true, | ||
115 | } | ||
116 | |||
117 | var allowedPackages = map[string]bool{ | ||
118 | "net": true, | ||
119 | } | ||
120 | |||
121 | func main() { | ||
122 | conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps} | ||
123 | pkgs, err := packages.Load(&conf, "github.com/miekg/dns") | ||
124 | if err != nil { | ||
125 | panic(err) | ||
126 | } | ||
127 | |||
128 | scope := pkgs[0].Types.Scope() | ||
129 | localTypes := map[string][]Field{} | ||
130 | |||
131 | for _, name := range scope.Names() { | ||
132 | o := scope.Lookup(name) | ||
133 | if o == nil || !o.Exported() { | ||
134 | continue | ||
135 | } | ||
136 | |||
137 | // Only consider structs | ||
138 | st, ok := o.Type().Underlying().(*types.Struct) | ||
139 | if !ok { | ||
140 | continue | ||
141 | } | ||
142 | |||
143 | name := o.Name() | ||
144 | |||
145 | // Explicitly disallow some types that have complex embedded types | ||
146 | if _, skip := disallowedTypes[name]; skip { | ||
147 | continue | ||
148 | } | ||
149 | |||
150 | // There must be a type constant for this | ||
151 | if scope.Lookup(fmt.Sprintf("Type%s", name)) == nil { | ||
152 | continue | ||
153 | } | ||
154 | |||
155 | fields := []Field{} | ||
156 | for i := 0; i < st.NumFields(); i++ { | ||
157 | f := st.Field(i) | ||
158 | |||
159 | // Exclude header field | ||
160 | if f.Name() == "Hdr" { | ||
161 | continue | ||
162 | } | ||
163 | |||
164 | // Fail if there are complex types embedded | ||
165 | if tp, ok := f.Type().(*types.Named); ok { | ||
166 | if _, ok := allowedPackages[tp.Obj().Pkg().Path()]; !ok { | ||
167 | log.Fatalf("Invalid embedded complex type: %s", tp) | ||
168 | } | ||
169 | } | ||
170 | |||
171 | // Also fail if there are complex types embedded in a slice | ||
172 | if tp, ok := f.Type().(*types.Slice); ok { | ||
173 | if ut, ok := tp.Elem().(*types.Named); ok { | ||
174 | if _, ok := allowedPackages[ut.Obj().Pkg().Path()]; !ok { | ||
175 | log.Fatalf("Invalid embedded complex type: %s", tp) | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | |||
180 | fields = append(fields, Field{f.Name(), f.Type().String()}) | ||
181 | } | ||
182 | |||
183 | localTypes[name] = fields | ||
184 | } | ||
185 | |||
186 | fp, err := os.Create("zzz_types.go") | ||
187 | if err != nil { | ||
188 | panic(err) | ||
189 | } | ||
190 | defer fp.Close() | ||
191 | tpl.Execute(fp, localTypes) | ||
192 | } | ||