From d4081ae6b50b5a54c4a47741a22bf63d26a01873 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Tue, 11 Aug 2020 02:53:09 +0000 Subject: Generate DNS types --- .gitignore | 4 + Makefile | 7 +- dns/types.go | 271 ++------------------------------------------------ generate_dns_types.go | 192 +++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 268 deletions(-) create mode 100644 .gitignore create mode 100644 generate_dns_types.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75c9781 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +./dns-service + +# Generated files have a zzz_ prefix +**/zzz_*.go diff --git a/Makefile b/Makefile index c174da7..37c5199 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -GO_FILES := $(shell find . -name '*.go') - -dns-service: main.go $(GO_FILES) +.PHONY: dns-service +dns-service: + go generate ./... CGO_ENABLED=0 go build -o $@ $< .PHONY: clean clean: rm -f dns-service + 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 import ( "fmt" - "net" + _ "net" "code.crute.me/mcrute/go_ddns_manager/bind" "github.com/miekg/dns" ) +//go:generate go run ../generate_dns_types.go + func makeHeader(name string, zone *bind.Zone, t uint16, ttl int) dns.RR_Header { return dns.RR_Header{ Name: fmt.Sprintf("%s.%s", name, zone.Name), @@ -27,268 +29,7 @@ func toRRSet(z *bind.Zone, rr ...RR) []dns.RR { type RR interface { ToDNS(*bind.Zone) dns.RR -} - -type A struct { - Name string - Ttl int - A net.IP -} - -func (r *A) ToDNS(zone *bind.Zone) dns.RR { - return &dns.A{ - Hdr: makeHeader(r.Name, zone, dns.TypeA, r.Ttl), - A: r.A, - } -} - -type AAAA struct { - Name string - Ttl int - AAAA net.IP -} - -func (r *AAAA) ToDNS(zone *bind.Zone) dns.RR { - return &dns.AAAA{ - Hdr: makeHeader(r.Name, zone, dns.TypeAAAA, r.Ttl), - AAAA: r.AAAA, - } -} - -type CAA struct { - Name string - Ttl int - Flag uint8 - Tag string - Value string -} - -func (r *CAA) ToDNS(zone *bind.Zone) dns.RR { - return &dns.CAA{ - Hdr: makeHeader(r.Name, zone, dns.TypeCAA, r.Ttl), - Flag: r.Flag, - Tag: r.Tag, - Value: r.Value, - } -} - -type CERT struct { - Name string - Ttl int - Type uint16 - KeyTag uint16 - Algorithm uint8 - Certificate string -} - -func (r *CERT) ToDNS(zone *bind.Zone) dns.RR { - return &dns.CERT{ - Hdr: makeHeader(r.Name, zone, dns.TypeCERT, r.Ttl), - Type: r.Type, - KeyTag: r.KeyTag, - Algorithm: r.Algorithm, - Certificate: r.Certificate, - } -} - -type CNAME struct { - Name string - Ttl int - Target string -} - -func (r *CNAME) ToDNS(zone *bind.Zone) dns.RR { - return &dns.CNAME{ - Hdr: makeHeader(r.Name, zone, dns.TypeCNAME, r.Ttl), - Target: r.Target, - } -} - -type DNAME struct { - Name string - Ttl int - Target string -} - -func (r *DNAME) ToDNS(zone *bind.Zone) dns.RR { - return &dns.DNAME{ - Hdr: makeHeader(r.Name, zone, dns.TypeDNAME, r.Ttl), - Target: r.Target, - } -} - -type LOC struct { - Name string - Ttl int - Version uint8 - Size uint8 - HorizPre uint8 - VertPre uint8 - Latitude uint32 - Longitude uint32 - Altitude uint32 -} - -func (r *LOC) ToDNS(zone *bind.Zone) dns.RR { - return &dns.LOC{ - Hdr: makeHeader(r.Name, zone, dns.TypeLOC, r.Ttl), - Version: r.Version, - Size: r.Size, - HorizPre: r.HorizPre, - VertPre: r.VertPre, - Latitude: r.Latitude, - Longitude: r.Longitude, - Altitude: r.Altitude, - } -} - -type MX struct { - Name string - Ttl int - Preference uint16 - Mx string -} - -func (r *MX) ToDNS(zone *bind.Zone) dns.RR { - return &dns.MX{ - Hdr: makeHeader(r.Name, zone, dns.TypeMX, r.Ttl), - Preference: r.Preference, - Mx: r.Mx, - } -} - -type NAPTR struct { - Name string - Ttl int - Order uint16 - Preference uint16 - Flags string - Service string - Regexp string - Replacement string -} - -func (r *NAPTR) ToDNS(zone *bind.Zone) dns.RR { - return &dns.NAPTR{ - Hdr: makeHeader(r.Name, zone, dns.TypeNAPTR, r.Ttl), - Order: r.Order, - Preference: r.Preference, - Flags: r.Flags, - Service: r.Service, - Regexp: r.Regexp, - Replacement: r.Replacement, - } -} - -type NS struct { - Name string - Ttl int - Ns string -} - -func (r *NS) ToDNS(zone *bind.Zone) dns.RR { - return &dns.NS{ - Hdr: makeHeader(r.Name, zone, dns.TypeNS, r.Ttl), - Ns: r.Ns, - } -} - -type OPENPGPKEY struct { - Name string - Ttl int - PublicKey string -} - -func (r *OPENPGPKEY) ToDNS(zone *bind.Zone) dns.RR { - return &dns.OPENPGPKEY{ - Hdr: makeHeader(r.Name, zone, dns.TypeOPENPGPKEY, r.Ttl), - PublicKey: r.PublicKey, - } -} - -type PTR struct { - Name string - Ttl int - Ptr string -} - -func (r *PTR) ToDNS(zone *bind.Zone) dns.RR { - return &dns.PTR{ - Hdr: makeHeader(r.Name, zone, dns.TypePTR, r.Ttl), - Ptr: r.Ptr, - } -} - -type SOA struct { - Name string - Ttl int - Ns string - Mbox string - Serial uint32 - Refresh uint32 - Retry uint32 - Expire uint32 - Minttl uint32 -} - -func (r *SOA) ToDNS(zone *bind.Zone) dns.RR { - return &dns.SOA{ - Hdr: makeHeader(r.Name, zone, dns.TypeSOA, r.Ttl), - Ns: r.Ns, - Mbox: r.Mbox, - Serial: r.Serial, - Refresh: r.Refresh, - Retry: r.Retry, - Expire: r.Expire, - Minttl: r.Minttl, - } -} - -type SRV struct { - Name string - Ttl int - Priority uint16 - Weight uint16 - Port uint16 - Target string -} - -func (r *SRV) ToDNS(zone *bind.Zone) dns.RR { - return &dns.SRV{ - Hdr: makeHeader(r.Name, zone, dns.TypeSRV, r.Ttl), - Priority: r.Priority, - Weight: r.Weight, - Port: r.Port, - Target: r.Target, - } -} - -type SSHFP struct { - Name string - Ttl int - Algorithm uint8 - Type uint8 - FingerPrint string -} - -func (r *SSHFP) ToDNS(zone *bind.Zone) dns.RR { - return &dns.SSHFP{ - Hdr: makeHeader(r.Name, zone, dns.TypeSSHFP, r.Ttl), - Algorithm: r.Algorithm, - Type: r.Type, - FingerPrint: r.FingerPrint, - } -} - -type TXT struct { - Name string - Ttl int - Txt []string -} - -func (r *TXT) ToDNS(zone *bind.Zone) dns.RR { - return &dns.TXT{ - Hdr: makeHeader(r.Name, zone, dns.TypeTXT, r.Ttl), - Txt: r.Txt, - } + FromDNS(rr dns.RR) error + MarshalJSON() ([]byte, error) + UnmarshalJSON(data []byte) error } 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 @@ +//+build ignore + +package main + +import ( + "fmt" + "go/types" + "log" + "os" + "text/template" + + "golang.org/x/tools/go/packages" +) + +type Field struct { + Name string + Type string +} + +var tpl = template.Must(template.New("").Parse(`package dns + +// GENERATED FILE, DO NOT MODIFY +// See generate_dns_types.go in the repo root. + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/miekg/dns" + + "code.crute.me/mcrute/go_ddns_manager/bind" +) + +{{ range $name, $fields := . -}} +type {{ $name }} struct { + Name string + Ttl int + {{ range $fields -}} + {{ .Name }} {{ .Type }} + {{ end -}} +} + +func (r *{{ $name }}) ToDNS(zone *bind.Zone) dns.RR { + return &dns.{{ $name }}{ + Hdr: makeHeader(r.Name, zone, dns.Type{{ $name }}, r.Ttl), + {{ range $fields -}} + {{ .Name }}: r.{{ .Name }}, + {{ end }} + } +} + +func (r *{{ $name }}) FromDNS(rr dns.RR) error { + rt, ok := rr.(*dns.{{ $name }}) + if !ok { + return fmt.Errorf("Invalid type %T for '{{ $name }}'", rr) + } + + r.Name = rr.Header().Name + r.Ttl = int(rr.Header().Ttl) + {{ range $fields -}} + r.{{ .Name }} = rt.{{ .Name }} + {{ end }} + + return nil +} + +func (r *{{ $name }}) MarshalJSON() ([]byte, error) { + type Alias {{ $name }} + return json.Marshal(&struct { + Type string + *Alias + }{"{{ $name }}", (*Alias)(r)}) +} + +func (r *{{ $name }}) UnmarshalJSON(data []byte) error { + type Alias {{ $name }} + if err := json.Unmarshal(data, &struct{ + Type string + *Alias + }{Alias: (*Alias)(r)}); err != nil { + return err + } + return nil +} + +var _ RR = (*{{ $name }})(nil) + +{{ end }} + + +func FromDNS(rr dns.RR) interface{} { + switch v := rr.(type) { + {{ range $name, $fields := . -}} + case *dns.{{ $name }}: + rv := &{{ $name }}{} + rv.FromDNS(v) + return rv + {{ end }} + } + return nil +} +`)) + +var disallowedTypes = map[string]bool{ + "CDNSKEY": true, + "CDS": true, + "DLV": true, + "KEY": true, + "OPT": true, + "SIG": true, + "PrivateRR": true, + "RFC3597": true, + "ANY": true, +} + +var allowedPackages = map[string]bool{ + "net": true, +} + +func main() { + conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps} + pkgs, err := packages.Load(&conf, "github.com/miekg/dns") + if err != nil { + panic(err) + } + + scope := pkgs[0].Types.Scope() + localTypes := map[string][]Field{} + + for _, name := range scope.Names() { + o := scope.Lookup(name) + if o == nil || !o.Exported() { + continue + } + + // Only consider structs + st, ok := o.Type().Underlying().(*types.Struct) + if !ok { + continue + } + + name := o.Name() + + // Explicitly disallow some types that have complex embedded types + if _, skip := disallowedTypes[name]; skip { + continue + } + + // There must be a type constant for this + if scope.Lookup(fmt.Sprintf("Type%s", name)) == nil { + continue + } + + fields := []Field{} + for i := 0; i < st.NumFields(); i++ { + f := st.Field(i) + + // Exclude header field + if f.Name() == "Hdr" { + continue + } + + // Fail if there are complex types embedded + if tp, ok := f.Type().(*types.Named); ok { + if _, ok := allowedPackages[tp.Obj().Pkg().Path()]; !ok { + log.Fatalf("Invalid embedded complex type: %s", tp) + } + } + + // Also fail if there are complex types embedded in a slice + if tp, ok := f.Type().(*types.Slice); ok { + if ut, ok := tp.Elem().(*types.Named); ok { + if _, ok := allowedPackages[ut.Obj().Pkg().Path()]; !ok { + log.Fatalf("Invalid embedded complex type: %s", tp) + } + } + } + + fields = append(fields, Field{f.Name(), f.Type().String()}) + } + + localTypes[name] = fields + } + + fp, err := os.Create("zzz_types.go") + if err != nil { + panic(err) + } + defer fp.Close() + tpl.Execute(fp, localTypes) +} -- cgit v1.2.3