├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTORS ├── COPYRIGHT ├── LICENSE ├── README.md ├── client.go ├── client_test.go ├── clientconfig.go ├── clientconfig_test.go ├── compress_generate.go ├── dane.go ├── defaults.go ├── dns.go ├── dns_bench_test.go ├── dns_test.go ├── dnssec.go ├── dnssec_keygen.go ├── dnssec_keyscan.go ├── dnssec_privkey.go ├── dnssec_test.go ├── dnsutil ├── util.go └── util_test.go ├── doc.go ├── dyn_test.go ├── edns.go ├── edns_test.go ├── example_test.go ├── format.go ├── fuzz_test.go ├── generate.go ├── idn ├── code_points.go ├── example_test.go ├── punycode.go └── punycode_test.go ├── issue_test.go ├── labels.go ├── labels_test.go ├── msg.go ├── msg_generate.go ├── msg_helpers.go ├── msg_test.go ├── nsecx.go ├── nsecx_test.go ├── parse_test.go ├── privaterr.go ├── privaterr_test.go ├── rawmsg.go ├── remote_test.go ├── reverse.go ├── sanitize.go ├── sanitize_test.go ├── scan.go ├── scan_rr.go ├── scan_test.go ├── scanner.go ├── server.go ├── server_test.go ├── sig0.go ├── sig0_test.go ├── singleinflight.go ├── smimea.go ├── tlsa.go ├── tsig.go ├── tsig_test.go ├── types.go ├── types_generate.go ├── types_test.go ├── udp.go ├── udp_linux.go ├── udp_other.go ├── udp_windows.go ├── update.go ├── update_test.go ├── xfr.go ├── xfr_test.go ├── zcompress.go ├── zmsg.go └── ztypes.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | tags 3 | test.out 4 | a.out 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.7.x 5 | - 1.8.x 6 | - tip 7 | 8 | before_install: 9 | # don't use the miekg/dns when testing forks 10 | - mkdir -p $GOPATH/src/github.com/miekg 11 | - ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true 12 | 13 | script: 14 | - go test -race -v -bench=. 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Miek Gieben 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alex A. Skinner 2 | Andrew Tunnell-Jones 3 | Ask Bjørn Hansen 4 | Dave Cheney 5 | Dusty Wilson 6 | Marek Majkowski 7 | Peter van Dijk 8 | Omri Bahumi 9 | Alex Sergeyev 10 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. All rights reserved. Use of this source code 2 | is governed by a BSD-style license that can be found in the LICENSE file. 3 | Extensions of the original work are copyright (c) 2011 Miek Gieben 4 | 5 | Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is 6 | governed by a BSD-style license that can be found in the LICENSE file. 7 | 8 | Copyright 2014 CloudFlare. All rights reserved. Use of this source code is 9 | governed by a BSD-style license that can be found in the LICENSE file. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Extensions of the original work are copyright (c) 2011 Miek Gieben 2 | 3 | As this is fork of the official Go code the same license applies: 4 | 5 | Copyright (c) 2009 The Go Authors. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following disclaimer 15 | in the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Google Inc. nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) 2 | [![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns) 3 | 4 | # Alternative (more granular) approach to a DNS library 5 | 6 | > Less is more. 7 | 8 | Complete and usable DNS library. All widely used Resource Records are 9 | supported, including the DNSSEC types. It follows a lean and mean philosophy. 10 | If there is stuff you should know as a DNS programmer there isn't a convenience 11 | function for it. Server side and client side programming is supported, i.e. you 12 | can build servers and resolvers with it. 13 | 14 | We try to keep the "master" branch as sane as possible and at the bleeding edge 15 | of standards, avoiding breaking changes wherever reasonable. We support the last 16 | two versions of Go, currently: 1.7 and 1.8. 17 | 18 | # Goals 19 | 20 | * KISS; 21 | * Fast; 22 | * Small API. If it's easy to code in Go, don't make a function for it. 23 | 24 | # Users 25 | 26 | A not-so-up-to-date-list-that-may-be-actually-current: 27 | 28 | * https://github.com/coredns/coredns 29 | * https://cloudflare.com 30 | * https://github.com/abh/geodns 31 | * http://www.statdns.com/ 32 | * http://www.dnsinspect.com/ 33 | * https://github.com/chuangbo/jianbing-dictionary-dns 34 | * http://www.dns-lg.com/ 35 | * https://github.com/fcambus/rrda 36 | * https://github.com/kenshinx/godns 37 | * https://github.com/skynetservices/skydns 38 | * https://github.com/hashicorp/consul 39 | * https://github.com/DevelopersPL/godnsagent 40 | * https://github.com/duedil-ltd/discodns 41 | * https://github.com/StalkR/dns-reverse-proxy 42 | * https://github.com/tianon/rawdns 43 | * https://mesosphere.github.io/mesos-dns/ 44 | * https://pulse.turbobytes.com/ 45 | * https://play.google.com/store/apps/details?id=com.turbobytes.dig 46 | * https://github.com/fcambus/statzone 47 | * https://github.com/benschw/dns-clb-go 48 | * https://github.com/corny/dnscheck for http://public-dns.info/ 49 | * https://namesmith.io 50 | * https://github.com/miekg/unbound 51 | * https://github.com/miekg/exdns 52 | * https://dnslookup.org 53 | * https://github.com/looterz/grimd 54 | * https://github.com/phamhongviet/serf-dns 55 | * https://github.com/mehrdadrad/mylg 56 | * https://github.com/bamarni/dockness 57 | * https://github.com/fffaraz/microdns 58 | * http://quilt.io 59 | * https://github.com/ipdcode/hades (JD.COM) 60 | * https://github.com/StackExchange/dnscontrol/ 61 | * https://www.dnsperf.com/ 62 | * https://dnssectest.net/ 63 | 64 | Send pull request if you want to be listed here. 65 | 66 | # Features 67 | 68 | * UDP/TCP queries, IPv4 and IPv6; 69 | * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported; 70 | * Fast: 71 | * Reply speed around ~ 80K qps (faster hardware results in more qps); 72 | * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds; 73 | * Server side programming (mimicking the net/http package); 74 | * Client side programming; 75 | * DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA; 76 | * EDNS0, NSID, Cookies; 77 | * AXFR/IXFR; 78 | * TSIG, SIG(0); 79 | * DNS over TLS: optional encrypted connection between client and server; 80 | * DNS name compression; 81 | * Depends only on the standard library. 82 | 83 | Have fun! 84 | 85 | Miek Gieben - 2010-2012 - 86 | 87 | # Building 88 | 89 | Building is done with the `go` tool. If you have setup your GOPATH 90 | correctly, the following should work: 91 | 92 | go get github.com/miekg/dns 93 | go build github.com/miekg/dns 94 | 95 | ## Examples 96 | 97 | A short "how to use the API" is at the beginning of doc.go (this also will show 98 | when you call `godoc github.com/miekg/dns`). 99 | 100 | Example programs can be found in the `github.com/miekg/exdns` repository. 101 | 102 | ## Supported RFCs 103 | 104 | *all of them* 105 | 106 | * 103{4,5} - DNS standard 107 | * 1348 - NSAP record (removed the record) 108 | * 1982 - Serial Arithmetic 109 | * 1876 - LOC record 110 | * 1995 - IXFR 111 | * 1996 - DNS notify 112 | * 2136 - DNS Update (dynamic updates) 113 | * 2181 - RRset definition - there is no RRset type though, just []RR 114 | * 2537 - RSAMD5 DNS keys 115 | * 2065 - DNSSEC (updated in later RFCs) 116 | * 2671 - EDNS record 117 | * 2782 - SRV record 118 | * 2845 - TSIG record 119 | * 2915 - NAPTR record 120 | * 2929 - DNS IANA Considerations 121 | * 3110 - RSASHA1 DNS keys 122 | * 3225 - DO bit (DNSSEC OK) 123 | * 340{1,2,3} - NAPTR record 124 | * 3445 - Limiting the scope of (DNS)KEY 125 | * 3597 - Unknown RRs 126 | * 403{3,4,5} - DNSSEC + validation functions 127 | * 4255 - SSHFP record 128 | * 4343 - Case insensitivity 129 | * 4408 - SPF record 130 | * 4509 - SHA256 Hash in DS 131 | * 4592 - Wildcards in the DNS 132 | * 4635 - HMAC SHA TSIG 133 | * 4701 - DHCID 134 | * 4892 - id.server 135 | * 5001 - NSID 136 | * 5155 - NSEC3 record 137 | * 5205 - HIP record 138 | * 5702 - SHA2 in the DNS 139 | * 5936 - AXFR 140 | * 5966 - TCP implementation recommendations 141 | * 6605 - ECDSA 142 | * 6725 - IANA Registry Update 143 | * 6742 - ILNP DNS 144 | * 6840 - Clarifications and Implementation Notes for DNS Security 145 | * 6844 - CAA record 146 | * 6891 - EDNS0 update 147 | * 6895 - DNS IANA considerations 148 | * 6975 - Algorithm Understanding in DNSSEC 149 | * 7043 - EUI48/EUI64 records 150 | * 7314 - DNS (EDNS) EXPIRE Option 151 | * 7828 - edns-tcp-keepalive EDNS0 Option 152 | * 7553 - URI record 153 | * 7858 - DNS over TLS: Initiation and Performance Considerations (draft) 154 | * 7873 - Domain Name System (DNS) Cookies (draft-ietf-dnsop-cookies) 155 | * xxxx - EDNS0 DNS Update Lease (draft) 156 | 157 | ## Loosely based upon 158 | 159 | * `ldns` 160 | * `NSD` 161 | * `Net::DNS` 162 | * `GRONG` 163 | -------------------------------------------------------------------------------- /clientconfig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // ClientConfig wraps the contents of the /etc/resolv.conf file. 11 | type ClientConfig struct { 12 | Servers []string // servers to use 13 | Search []string // suffixes to append to local name 14 | Port string // what port to use 15 | Ndots int // number of dots in name to trigger absolute lookup 16 | Timeout int // seconds before giving up on packet 17 | Attempts int // lost packets before giving up on server, not used in the package dns 18 | } 19 | 20 | // ClientConfigFromFile parses a resolv.conf(5) like file and returns 21 | // a *ClientConfig. 22 | func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { 23 | file, err := os.Open(resolvconf) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer file.Close() 28 | c := new(ClientConfig) 29 | scanner := bufio.NewScanner(file) 30 | c.Servers = make([]string, 0) 31 | c.Search = make([]string, 0) 32 | c.Port = "53" 33 | c.Ndots = 1 34 | c.Timeout = 5 35 | c.Attempts = 2 36 | 37 | for scanner.Scan() { 38 | if err := scanner.Err(); err != nil { 39 | return nil, err 40 | } 41 | line := scanner.Text() 42 | f := strings.Fields(line) 43 | if len(f) < 1 { 44 | continue 45 | } 46 | switch f[0] { 47 | case "nameserver": // add one name server 48 | if len(f) > 1 { 49 | // One more check: make sure server name is 50 | // just an IP address. Otherwise we need DNS 51 | // to look it up. 52 | name := f[1] 53 | c.Servers = append(c.Servers, name) 54 | } 55 | 56 | case "domain": // set search path to just this domain 57 | if len(f) > 1 { 58 | c.Search = make([]string, 1) 59 | c.Search[0] = f[1] 60 | } else { 61 | c.Search = make([]string, 0) 62 | } 63 | 64 | case "search": // set search path to given servers 65 | c.Search = make([]string, len(f)-1) 66 | for i := 0; i < len(c.Search); i++ { 67 | c.Search[i] = f[i+1] 68 | } 69 | 70 | case "options": // magic options 71 | for i := 1; i < len(f); i++ { 72 | s := f[i] 73 | switch { 74 | case len(s) >= 6 && s[:6] == "ndots:": 75 | n, _ := strconv.Atoi(s[6:]) 76 | if n < 1 { 77 | n = 1 78 | } 79 | c.Ndots = n 80 | case len(s) >= 8 && s[:8] == "timeout:": 81 | n, _ := strconv.Atoi(s[8:]) 82 | if n < 1 { 83 | n = 1 84 | } 85 | c.Timeout = n 86 | case len(s) >= 8 && s[:9] == "attempts:": 87 | n, _ := strconv.Atoi(s[9:]) 88 | if n < 1 { 89 | n = 1 90 | } 91 | c.Attempts = n 92 | case s == "rotate": 93 | /* not imp */ 94 | } 95 | } 96 | } 97 | } 98 | return c, nil 99 | } 100 | 101 | // NameList returns all of the names that should be queried based on the 102 | // config. It is based off of go's net/dns name building, but it does not 103 | // check the length of the resulting names. 104 | func (c *ClientConfig) NameList(name string) []string { 105 | // if this domain is already fully qualified, no append needed. 106 | if IsFqdn(name) { 107 | return []string{name} 108 | } 109 | 110 | // Check to see if the name has more labels than Ndots. Do this before making 111 | // the domain fully qualified. 112 | hasNdots := CountLabel(name) > c.Ndots 113 | // Make the domain fully qualified. 114 | name = Fqdn(name) 115 | 116 | // Make a list of names based off search. 117 | names := []string{} 118 | 119 | // If name has enough dots, try that first. 120 | if hasNdots { 121 | names = append(names, name) 122 | } 123 | for _, s := range c.Search { 124 | names = append(names, Fqdn(name+s)) 125 | } 126 | // If we didn't have enough dots, try after suffixes. 127 | if !hasNdots { 128 | names = append(names, name) 129 | } 130 | return names 131 | } 132 | -------------------------------------------------------------------------------- /clientconfig_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | const normal string = ` 11 | # Comment 12 | domain somedomain.com 13 | nameserver 10.28.10.2 14 | nameserver 11.28.10.1 15 | ` 16 | 17 | const missingNewline string = ` 18 | domain somedomain.com 19 | nameserver 10.28.10.2 20 | nameserver 11.28.10.1` // <- NOTE: NO newline. 21 | 22 | func testConfig(t *testing.T, data string) { 23 | tempDir, err := ioutil.TempDir("", "") 24 | if err != nil { 25 | t.Fatalf("tempDir: %v", err) 26 | } 27 | defer os.RemoveAll(tempDir) 28 | 29 | path := filepath.Join(tempDir, "resolv.conf") 30 | if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { 31 | t.Fatalf("writeFile: %v", err) 32 | } 33 | cc, err := ClientConfigFromFile(path) 34 | if err != nil { 35 | t.Errorf("error parsing resolv.conf: %v", err) 36 | } 37 | if l := len(cc.Servers); l != 2 { 38 | t.Errorf("incorrect number of nameservers detected: %d", l) 39 | } 40 | if l := len(cc.Search); l != 1 { 41 | t.Errorf("domain directive not parsed correctly: %v", cc.Search) 42 | } else { 43 | if cc.Search[0] != "somedomain.com" { 44 | t.Errorf("domain is unexpected: %v", cc.Search[0]) 45 | } 46 | } 47 | } 48 | 49 | func TestNameserver(t *testing.T) { testConfig(t, normal) } 50 | func TestMissingFinalNewLine(t *testing.T) { testConfig(t, missingNewline) } 51 | 52 | func TestNameList(t *testing.T) { 53 | cfg := ClientConfig{ 54 | Ndots: 1, 55 | } 56 | // fqdn should be only result returned 57 | names := cfg.NameList("miek.nl.") 58 | if len(names) != 1 { 59 | t.Errorf("NameList returned != 1 names: %v", names) 60 | } else if names[0] != "miek.nl." { 61 | t.Errorf("NameList didn't return sent fqdn domain: %v", names[0]) 62 | } 63 | 64 | cfg.Search = []string{ 65 | "test", 66 | } 67 | // Sent domain has NDots and search 68 | names = cfg.NameList("miek.nl") 69 | if len(names) != 2 { 70 | t.Errorf("NameList returned != 2 names: %v", names) 71 | } else if names[0] != "miek.nl." { 72 | t.Errorf("NameList didn't return sent domain first: %v", names[0]) 73 | } else if names[1] != "miek.nl.test." { 74 | t.Errorf("NameList didn't return search last: %v", names[1]) 75 | } 76 | 77 | cfg.Ndots = 2 78 | // Sent domain has less than NDots and search 79 | names = cfg.NameList("miek.nl") 80 | if len(names) != 2 { 81 | t.Errorf("NameList returned != 2 names: %v", names) 82 | } else if names[0] != "miek.nl.test." { 83 | t.Errorf("NameList didn't return search first: %v", names[0]) 84 | } else if names[1] != "miek.nl." { 85 | t.Errorf("NameList didn't return sent domain last: %v", names[1]) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /compress_generate.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | // compression_generate.go is meant to run with go generate. It will use 4 | // go/{importer,types} to track down all the RR struct types. Then for each type 5 | // it will look to see if there are (compressible) names, if so it will add that 6 | // type to compressionLenHelperType and comressionLenSearchType which "fake" the 7 | // compression so that Len() is fast. 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "go/format" 14 | "go/importer" 15 | "go/types" 16 | "log" 17 | "os" 18 | ) 19 | 20 | var packageHdr = ` 21 | // *** DO NOT MODIFY *** 22 | // AUTOGENERATED BY go generate from compress_generate.go 23 | 24 | package dns 25 | 26 | ` 27 | 28 | // getTypeStruct will take a type and the package scope, and return the 29 | // (innermost) struct if the type is considered a RR type (currently defined as 30 | // those structs beginning with a RR_Header, could be redefined as implementing 31 | // the RR interface). The bool return value indicates if embedded structs were 32 | // resolved. 33 | func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { 34 | st, ok := t.Underlying().(*types.Struct) 35 | if !ok { 36 | return nil, false 37 | } 38 | if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { 39 | return st, false 40 | } 41 | if st.Field(0).Anonymous() { 42 | st, _ := getTypeStruct(st.Field(0).Type(), scope) 43 | return st, true 44 | } 45 | return nil, false 46 | } 47 | 48 | func main() { 49 | // Import and type-check the package 50 | pkg, err := importer.Default().Import("github.com/miekg/dns") 51 | fatalIfErr(err) 52 | scope := pkg.Scope() 53 | 54 | domainTypes := map[string]bool{} // Types that have a domain name in them (either comressible or not). 55 | cdomainTypes := map[string]bool{} // Types that have a compressible domain name in them (subset of domainType) 56 | for _, name := range scope.Names() { 57 | o := scope.Lookup(name) 58 | if o == nil || !o.Exported() { 59 | continue 60 | } 61 | st, _ := getTypeStruct(o.Type(), scope) 62 | if st == nil { 63 | continue 64 | } 65 | if name == "PrivateRR" { 66 | continue 67 | } 68 | 69 | if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { 70 | log.Fatalf("Constant Type%s does not exist.", o.Name()) 71 | } 72 | 73 | for i := 1; i < st.NumFields(); i++ { 74 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 75 | if st.Tag(i) == `dns:"domain-name"` { 76 | domainTypes[o.Name()] = true 77 | } 78 | if st.Tag(i) == `dns:"cdomain-name"` { 79 | cdomainTypes[o.Name()] = true 80 | domainTypes[o.Name()] = true 81 | } 82 | continue 83 | } 84 | 85 | switch { 86 | case st.Tag(i) == `dns:"domain-name"`: 87 | domainTypes[o.Name()] = true 88 | case st.Tag(i) == `dns:"cdomain-name"`: 89 | cdomainTypes[o.Name()] = true 90 | domainTypes[o.Name()] = true 91 | } 92 | } 93 | } 94 | 95 | b := &bytes.Buffer{} 96 | b.WriteString(packageHdr) 97 | 98 | // compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names 99 | 100 | fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n") 101 | fmt.Fprint(b, "switch x := r.(type) {\n") 102 | for name, _ := range domainTypes { 103 | o := scope.Lookup(name) 104 | st, _ := getTypeStruct(o.Type(), scope) 105 | 106 | fmt.Fprintf(b, "case *%s:\n", name) 107 | for i := 1; i < st.NumFields(); i++ { 108 | out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) } 109 | 110 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 111 | switch st.Tag(i) { 112 | case `dns:"domain-name"`: 113 | fallthrough 114 | case `dns:"cdomain-name"`: 115 | // For HIP we need to slice over the elements in this slice. 116 | fmt.Fprintf(b, `for i := range x.%s { 117 | compressionLenHelper(c, x.%s[i]) 118 | } 119 | `, st.Field(i).Name(), st.Field(i).Name()) 120 | } 121 | continue 122 | } 123 | 124 | switch { 125 | case st.Tag(i) == `dns:"cdomain-name"`: 126 | fallthrough 127 | case st.Tag(i) == `dns:"domain-name"`: 128 | out(st.Field(i).Name()) 129 | } 130 | } 131 | } 132 | fmt.Fprintln(b, "}\n}\n\n") 133 | 134 | // compressionLenSearchType - search cdomain-tags types for compressible names. 135 | 136 | fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n") 137 | fmt.Fprint(b, "switch x := r.(type) {\n") 138 | for name, _ := range cdomainTypes { 139 | o := scope.Lookup(name) 140 | st, _ := getTypeStruct(o.Type(), scope) 141 | 142 | fmt.Fprintf(b, "case *%s:\n", name) 143 | j := 1 144 | for i := 1; i < st.NumFields(); i++ { 145 | out := func(s string, j int) { 146 | fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name()) 147 | } 148 | 149 | // There are no slice types with names that can be compressed. 150 | 151 | switch { 152 | case st.Tag(i) == `dns:"cdomain-name"`: 153 | out(st.Field(i).Name(), j) 154 | j++ 155 | } 156 | } 157 | k := "k1" 158 | ok := "ok1" 159 | for i := 2; i < j; i++ { 160 | k += fmt.Sprintf(" + k%d", i) 161 | ok += fmt.Sprintf(" && ok%d", i) 162 | } 163 | fmt.Fprintf(b, "return %s, %s\n", k, ok) 164 | } 165 | fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n") 166 | 167 | // gofmt 168 | res, err := format.Source(b.Bytes()) 169 | if err != nil { 170 | b.WriteTo(os.Stderr) 171 | log.Fatal(err) 172 | } 173 | 174 | f, err := os.Create("zcompress.go") 175 | fatalIfErr(err) 176 | defer f.Close() 177 | f.Write(res) 178 | } 179 | 180 | func fatalIfErr(err error) { 181 | if err != nil { 182 | log.Fatal(err) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /dane.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/sha512" 6 | "crypto/x509" 7 | "encoding/hex" 8 | "errors" 9 | ) 10 | 11 | // CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records. 12 | func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { 13 | switch matchingType { 14 | case 0: 15 | switch selector { 16 | case 0: 17 | return hex.EncodeToString(cert.Raw), nil 18 | case 1: 19 | return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil 20 | } 21 | case 1: 22 | h := sha256.New() 23 | switch selector { 24 | case 0: 25 | h.Write(cert.Raw) 26 | return hex.EncodeToString(h.Sum(nil)), nil 27 | case 1: 28 | h.Write(cert.RawSubjectPublicKeyInfo) 29 | return hex.EncodeToString(h.Sum(nil)), nil 30 | } 31 | case 2: 32 | h := sha512.New() 33 | switch selector { 34 | case 0: 35 | h.Write(cert.Raw) 36 | return hex.EncodeToString(h.Sum(nil)), nil 37 | case 1: 38 | h.Write(cert.RawSubjectPublicKeyInfo) 39 | return hex.EncodeToString(h.Sum(nil)), nil 40 | } 41 | } 42 | return "", errors.New("dns: bad MatchingType or Selector") 43 | } 44 | -------------------------------------------------------------------------------- /defaults.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | const hexDigit = "0123456789abcdef" 10 | 11 | // Everything is assumed in ClassINET. 12 | 13 | // SetReply creates a reply message from a request message. 14 | func (dns *Msg) SetReply(request *Msg) *Msg { 15 | dns.Id = request.Id 16 | dns.Response = true 17 | dns.Opcode = request.Opcode 18 | if dns.Opcode == OpcodeQuery { 19 | dns.RecursionDesired = request.RecursionDesired // Copy rd bit 20 | dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit 21 | } 22 | dns.Rcode = RcodeSuccess 23 | if len(request.Question) > 0 { 24 | dns.Question = make([]Question, 1) 25 | dns.Question[0] = request.Question[0] 26 | } 27 | return dns 28 | } 29 | 30 | // SetQuestion creates a question message, it sets the Question 31 | // section, generates an Id and sets the RecursionDesired (RD) 32 | // bit to true. 33 | func (dns *Msg) SetQuestion(z string, t uint16) *Msg { 34 | dns.Id = Id() 35 | dns.RecursionDesired = true 36 | dns.Question = make([]Question, 1) 37 | dns.Question[0] = Question{z, t, ClassINET} 38 | return dns 39 | } 40 | 41 | // SetNotify creates a notify message, it sets the Question 42 | // section, generates an Id and sets the Authoritative (AA) 43 | // bit to true. 44 | func (dns *Msg) SetNotify(z string) *Msg { 45 | dns.Opcode = OpcodeNotify 46 | dns.Authoritative = true 47 | dns.Id = Id() 48 | dns.Question = make([]Question, 1) 49 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 50 | return dns 51 | } 52 | 53 | // SetRcode creates an error message suitable for the request. 54 | func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { 55 | dns.SetReply(request) 56 | dns.Rcode = rcode 57 | return dns 58 | } 59 | 60 | // SetRcodeFormatError creates a message with FormError set. 61 | func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { 62 | dns.Rcode = RcodeFormatError 63 | dns.Opcode = OpcodeQuery 64 | dns.Response = true 65 | dns.Authoritative = false 66 | dns.Id = request.Id 67 | return dns 68 | } 69 | 70 | // SetUpdate makes the message a dynamic update message. It 71 | // sets the ZONE section to: z, TypeSOA, ClassINET. 72 | func (dns *Msg) SetUpdate(z string) *Msg { 73 | dns.Id = Id() 74 | dns.Response = false 75 | dns.Opcode = OpcodeUpdate 76 | dns.Compress = false // BIND9 cannot handle compression 77 | dns.Question = make([]Question, 1) 78 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 79 | return dns 80 | } 81 | 82 | // SetIxfr creates message for requesting an IXFR. 83 | func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { 84 | dns.Id = Id() 85 | dns.Question = make([]Question, 1) 86 | dns.Ns = make([]RR, 1) 87 | s := new(SOA) 88 | s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} 89 | s.Serial = serial 90 | s.Ns = ns 91 | s.Mbox = mbox 92 | dns.Question[0] = Question{z, TypeIXFR, ClassINET} 93 | dns.Ns[0] = s 94 | return dns 95 | } 96 | 97 | // SetAxfr creates message for requesting an AXFR. 98 | func (dns *Msg) SetAxfr(z string) *Msg { 99 | dns.Id = Id() 100 | dns.Question = make([]Question, 1) 101 | dns.Question[0] = Question{z, TypeAXFR, ClassINET} 102 | return dns 103 | } 104 | 105 | // SetTsig appends a TSIG RR to the message. 106 | // This is only a skeleton TSIG RR that is added as the last RR in the 107 | // additional section. The Tsig is calculated when the message is being send. 108 | func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg { 109 | t := new(TSIG) 110 | t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} 111 | t.Algorithm = algo 112 | t.Fudge = fudge 113 | t.TimeSigned = uint64(timesigned) 114 | t.OrigId = dns.Id 115 | dns.Extra = append(dns.Extra, t) 116 | return dns 117 | } 118 | 119 | // SetEdns0 appends a EDNS0 OPT RR to the message. 120 | // TSIG should always the last RR in a message. 121 | func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { 122 | e := new(OPT) 123 | e.Hdr.Name = "." 124 | e.Hdr.Rrtype = TypeOPT 125 | e.SetUDPSize(udpsize) 126 | if do { 127 | e.SetDo() 128 | } 129 | dns.Extra = append(dns.Extra, e) 130 | return dns 131 | } 132 | 133 | // IsTsig checks if the message has a TSIG record as the last record 134 | // in the additional section. It returns the TSIG record found or nil. 135 | func (dns *Msg) IsTsig() *TSIG { 136 | if len(dns.Extra) > 0 { 137 | if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { 138 | return dns.Extra[len(dns.Extra)-1].(*TSIG) 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | // IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 145 | // record in the additional section will do. It returns the OPT record 146 | // found or nil. 147 | func (dns *Msg) IsEdns0() *OPT { 148 | // EDNS0 is at the end of the additional section, start there. 149 | // We might want to change this to *only* look at the last two 150 | // records. So we see TSIG and/or OPT - this a slightly bigger 151 | // change though. 152 | for i := len(dns.Extra) - 1; i >= 0; i-- { 153 | if dns.Extra[i].Header().Rrtype == TypeOPT { 154 | return dns.Extra[i].(*OPT) 155 | } 156 | } 157 | return nil 158 | } 159 | 160 | // IsDomainName checks if s is a valid domain name, it returns the number of 161 | // labels and true, when a domain name is valid. Note that non fully qualified 162 | // domain name is considered valid, in this case the last label is counted in 163 | // the number of labels. When false is returned the number of labels is not 164 | // defined. Also note that this function is extremely liberal; almost any 165 | // string is a valid domain name as the DNS is 8 bit protocol. It checks if each 166 | // label fits in 63 characters, but there is no length check for the entire 167 | // string s. I.e. a domain name longer than 255 characters is considered valid. 168 | func IsDomainName(s string) (labels int, ok bool) { 169 | _, labels, err := packDomainName(s, nil, 0, nil, false) 170 | return labels, err == nil 171 | } 172 | 173 | // IsSubDomain checks if child is indeed a child of the parent. If child and parent 174 | // are the same domain true is returned as well. 175 | func IsSubDomain(parent, child string) bool { 176 | // Entire child is contained in parent 177 | return CompareDomainName(parent, child) == CountLabel(parent) 178 | } 179 | 180 | // IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. 181 | // The checking is performed on the binary payload. 182 | func IsMsg(buf []byte) error { 183 | // Header 184 | if len(buf) < 12 { 185 | return errors.New("dns: bad message header") 186 | } 187 | // Header: Opcode 188 | // TODO(miek): more checks here, e.g. check all header bits. 189 | return nil 190 | } 191 | 192 | // IsFqdn checks if a domain name is fully qualified. 193 | func IsFqdn(s string) bool { 194 | l := len(s) 195 | if l == 0 { 196 | return false 197 | } 198 | return s[l-1] == '.' 199 | } 200 | 201 | // IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. 202 | // This means the RRs need to have the same type, name, and class. Returns true 203 | // if the RR set is valid, otherwise false. 204 | func IsRRset(rrset []RR) bool { 205 | if len(rrset) == 0 { 206 | return false 207 | } 208 | if len(rrset) == 1 { 209 | return true 210 | } 211 | rrHeader := rrset[0].Header() 212 | rrType := rrHeader.Rrtype 213 | rrClass := rrHeader.Class 214 | rrName := rrHeader.Name 215 | 216 | for _, rr := range rrset[1:] { 217 | curRRHeader := rr.Header() 218 | if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { 219 | // Mismatch between the records, so this is not a valid rrset for 220 | //signing/verifying 221 | return false 222 | } 223 | } 224 | 225 | return true 226 | } 227 | 228 | // Fqdn return the fully qualified domain name from s. 229 | // If s is already fully qualified, it behaves as the identity function. 230 | func Fqdn(s string) string { 231 | if IsFqdn(s) { 232 | return s 233 | } 234 | return s + "." 235 | } 236 | 237 | // Copied from the official Go code. 238 | 239 | // ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP 240 | // address suitable for reverse DNS (PTR) record lookups or an error if it fails 241 | // to parse the IP address. 242 | func ReverseAddr(addr string) (arpa string, err error) { 243 | ip := net.ParseIP(addr) 244 | if ip == nil { 245 | return "", &Error{err: "unrecognized address: " + addr} 246 | } 247 | if ip.To4() != nil { 248 | return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + 249 | strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil 250 | } 251 | // Must be IPv6 252 | buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) 253 | // Add it, in reverse, to the buffer 254 | for i := len(ip) - 1; i >= 0; i-- { 255 | v := ip[i] 256 | buf = append(buf, hexDigit[v&0xF]) 257 | buf = append(buf, '.') 258 | buf = append(buf, hexDigit[v>>4]) 259 | buf = append(buf, '.') 260 | } 261 | // Append "ip6.arpa." and return (buf already has the final .) 262 | buf = append(buf, "ip6.arpa."...) 263 | return string(buf), nil 264 | } 265 | 266 | // String returns the string representation for the type t. 267 | func (t Type) String() string { 268 | if t1, ok := TypeToString[uint16(t)]; ok { 269 | return t1 270 | } 271 | return "TYPE" + strconv.Itoa(int(t)) 272 | } 273 | 274 | // String returns the string representation for the class c. 275 | func (c Class) String() string { 276 | if c1, ok := ClassToString[uint16(c)]; ok { 277 | return c1 278 | } 279 | return "CLASS" + strconv.Itoa(int(c)) 280 | } 281 | 282 | // String returns the string representation for the name n. 283 | func (n Name) String() string { 284 | return sprintName(string(n)) 285 | } 286 | -------------------------------------------------------------------------------- /dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "strconv" 4 | 5 | const ( 6 | year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. 7 | defaultTtl = 3600 // Default internal TTL. 8 | 9 | DefaultMsgSize = 4096 // DefaultMsgSize is the standard default for messages larger than 512 bytes. 10 | MinMsgSize = 512 // MinMsgSize is the minimal size of a DNS packet. 11 | MaxMsgSize = 65535 // MaxMsgSize is the largest possible DNS packet. 12 | ) 13 | 14 | // Error represents a DNS error. 15 | type Error struct{ err string } 16 | 17 | func (e *Error) Error() string { 18 | if e == nil { 19 | return "dns: " 20 | } 21 | return "dns: " + e.err 22 | } 23 | 24 | // An RR represents a resource record. 25 | type RR interface { 26 | // Header returns the header of an resource record. The header contains 27 | // everything up to the rdata. 28 | Header() *RR_Header 29 | // String returns the text representation of the resource record. 30 | String() string 31 | 32 | // copy returns a copy of the RR 33 | copy() RR 34 | // len returns the length (in octets) of the uncompressed RR in wire format. 35 | len() int 36 | // pack packs an RR into wire format. 37 | pack([]byte, int, map[string]int, bool) (int, error) 38 | } 39 | 40 | // RR_Header is the header all DNS resource records share. 41 | type RR_Header struct { 42 | Name string `dns:"cdomain-name"` 43 | Rrtype uint16 44 | Class uint16 45 | Ttl uint32 46 | Rdlength uint16 // Length of data after header. 47 | } 48 | 49 | // Header returns itself. This is here to make RR_Header implements the RR interface. 50 | func (h *RR_Header) Header() *RR_Header { return h } 51 | 52 | // Just to implement the RR interface. 53 | func (h *RR_Header) copy() RR { return nil } 54 | 55 | func (h *RR_Header) copyHeader() *RR_Header { 56 | r := new(RR_Header) 57 | r.Name = h.Name 58 | r.Rrtype = h.Rrtype 59 | r.Class = h.Class 60 | r.Ttl = h.Ttl 61 | r.Rdlength = h.Rdlength 62 | return r 63 | } 64 | 65 | func (h *RR_Header) String() string { 66 | var s string 67 | 68 | if h.Rrtype == TypeOPT { 69 | s = ";" 70 | // and maybe other things 71 | } 72 | 73 | s += sprintName(h.Name) + "\t" 74 | s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" 75 | s += Class(h.Class).String() + "\t" 76 | s += Type(h.Rrtype).String() + "\t" 77 | return s 78 | } 79 | 80 | func (h *RR_Header) len() int { 81 | l := len(h.Name) + 1 82 | l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) 83 | return l 84 | } 85 | 86 | // ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597. 87 | func (rr *RFC3597) ToRFC3597(r RR) error { 88 | buf := make([]byte, r.len()*2) 89 | off, err := PackRR(r, buf, 0, nil, false) 90 | if err != nil { 91 | return err 92 | } 93 | buf = buf[:off] 94 | if int(r.Header().Rdlength) > off { 95 | return ErrBuf 96 | } 97 | 98 | rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength)) 99 | if err != nil { 100 | return err 101 | } 102 | *rr = *rfc3597.(*RFC3597) 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /dns_bench_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkMsgLength(b *testing.B) { 9 | b.StopTimer() 10 | makeMsg := func(question string, ans, ns, e []RR) *Msg { 11 | msg := new(Msg) 12 | msg.SetQuestion(Fqdn(question), TypeANY) 13 | msg.Answer = append(msg.Answer, ans...) 14 | msg.Ns = append(msg.Ns, ns...) 15 | msg.Extra = append(msg.Extra, e...) 16 | msg.Compress = true 17 | return msg 18 | } 19 | name1 := "12345678901234567890123456789012345.12345678.123." 20 | rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) 21 | msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) 22 | b.StartTimer() 23 | for i := 0; i < b.N; i++ { 24 | msg.Len() 25 | } 26 | } 27 | 28 | func BenchmarkMsgLengthPack(b *testing.B) { 29 | makeMsg := func(question string, ans, ns, e []RR) *Msg { 30 | msg := new(Msg) 31 | msg.SetQuestion(Fqdn(question), TypeANY) 32 | msg.Answer = append(msg.Answer, ans...) 33 | msg.Ns = append(msg.Ns, ns...) 34 | msg.Extra = append(msg.Extra, e...) 35 | msg.Compress = true 36 | return msg 37 | } 38 | name1 := "12345678901234567890123456789012345.12345678.123." 39 | rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) 40 | msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) 41 | b.ResetTimer() 42 | for i := 0; i < b.N; i++ { 43 | _, _ = msg.Pack() 44 | } 45 | } 46 | 47 | func BenchmarkPackDomainName(b *testing.B) { 48 | name1 := "12345678901234567890123456789012345.12345678.123." 49 | buf := make([]byte, len(name1)+1) 50 | b.ResetTimer() 51 | for i := 0; i < b.N; i++ { 52 | _, _ = PackDomainName(name1, buf, 0, nil, false) 53 | } 54 | } 55 | 56 | func BenchmarkUnpackDomainName(b *testing.B) { 57 | name1 := "12345678901234567890123456789012345.12345678.123." 58 | buf := make([]byte, len(name1)+1) 59 | _, _ = PackDomainName(name1, buf, 0, nil, false) 60 | b.ResetTimer() 61 | for i := 0; i < b.N; i++ { 62 | _, _, _ = UnpackDomainName(buf, 0) 63 | } 64 | } 65 | 66 | func BenchmarkUnpackDomainNameUnprintable(b *testing.B) { 67 | name1 := "\x02\x02\x02\x025\x02\x02\x02\x02.12345678.123." 68 | buf := make([]byte, len(name1)+1) 69 | _, _ = PackDomainName(name1, buf, 0, nil, false) 70 | b.ResetTimer() 71 | for i := 0; i < b.N; i++ { 72 | _, _, _ = UnpackDomainName(buf, 0) 73 | } 74 | } 75 | 76 | func BenchmarkCopy(b *testing.B) { 77 | b.ReportAllocs() 78 | m := new(Msg) 79 | m.SetQuestion("miek.nl.", TypeA) 80 | rr, _ := NewRR("miek.nl. 2311 IN A 127.0.0.1") 81 | m.Answer = []RR{rr} 82 | rr, _ = NewRR("miek.nl. 2311 IN NS 127.0.0.1") 83 | m.Ns = []RR{rr} 84 | rr, _ = NewRR("miek.nl. 2311 IN A 127.0.0.1") 85 | m.Extra = []RR{rr} 86 | 87 | b.ResetTimer() 88 | for i := 0; i < b.N; i++ { 89 | m.Copy() 90 | } 91 | } 92 | 93 | func BenchmarkPackA(b *testing.B) { 94 | a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)} 95 | 96 | buf := make([]byte, a.len()) 97 | b.ReportAllocs() 98 | b.ResetTimer() 99 | for i := 0; i < b.N; i++ { 100 | _, _ = PackRR(a, buf, 0, nil, false) 101 | } 102 | } 103 | 104 | func BenchmarkUnpackA(b *testing.B) { 105 | a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)} 106 | 107 | buf := make([]byte, a.len()) 108 | PackRR(a, buf, 0, nil, false) 109 | a = nil 110 | b.ReportAllocs() 111 | b.ResetTimer() 112 | for i := 0; i < b.N; i++ { 113 | _, _, _ = UnpackRR(buf, 0) 114 | } 115 | } 116 | 117 | func BenchmarkPackMX(b *testing.B) { 118 | m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."} 119 | 120 | buf := make([]byte, m.len()) 121 | b.ReportAllocs() 122 | b.ResetTimer() 123 | for i := 0; i < b.N; i++ { 124 | _, _ = PackRR(m, buf, 0, nil, false) 125 | } 126 | } 127 | 128 | func BenchmarkUnpackMX(b *testing.B) { 129 | m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."} 130 | 131 | buf := make([]byte, m.len()) 132 | PackRR(m, buf, 0, nil, false) 133 | m = nil 134 | b.ReportAllocs() 135 | b.ResetTimer() 136 | for i := 0; i < b.N; i++ { 137 | _, _, _ = UnpackRR(buf, 0) 138 | } 139 | } 140 | 141 | func BenchmarkPackAAAAA(b *testing.B) { 142 | aaaa, _ := NewRR(". IN A ::1") 143 | 144 | buf := make([]byte, aaaa.len()) 145 | b.ReportAllocs() 146 | b.ResetTimer() 147 | for i := 0; i < b.N; i++ { 148 | _, _ = PackRR(aaaa, buf, 0, nil, false) 149 | } 150 | } 151 | 152 | func BenchmarkUnpackAAAA(b *testing.B) { 153 | aaaa, _ := NewRR(". IN A ::1") 154 | 155 | buf := make([]byte, aaaa.len()) 156 | PackRR(aaaa, buf, 0, nil, false) 157 | aaaa = nil 158 | b.ReportAllocs() 159 | b.ResetTimer() 160 | for i := 0; i < b.N; i++ { 161 | _, _, _ = UnpackRR(buf, 0) 162 | } 163 | } 164 | 165 | func BenchmarkPackMsg(b *testing.B) { 166 | makeMsg := func(question string, ans, ns, e []RR) *Msg { 167 | msg := new(Msg) 168 | msg.SetQuestion(Fqdn(question), TypeANY) 169 | msg.Answer = append(msg.Answer, ans...) 170 | msg.Ns = append(msg.Ns, ns...) 171 | msg.Extra = append(msg.Extra, e...) 172 | msg.Compress = true 173 | return msg 174 | } 175 | name1 := "12345678901234567890123456789012345.12345678.123." 176 | rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) 177 | msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) 178 | buf := make([]byte, 512) 179 | b.ReportAllocs() 180 | b.ResetTimer() 181 | for i := 0; i < b.N; i++ { 182 | _, _ = msg.PackBuffer(buf) 183 | } 184 | } 185 | 186 | func BenchmarkUnpackMsg(b *testing.B) { 187 | makeMsg := func(question string, ans, ns, e []RR) *Msg { 188 | msg := new(Msg) 189 | msg.SetQuestion(Fqdn(question), TypeANY) 190 | msg.Answer = append(msg.Answer, ans...) 191 | msg.Ns = append(msg.Ns, ns...) 192 | msg.Extra = append(msg.Extra, e...) 193 | msg.Compress = true 194 | return msg 195 | } 196 | name1 := "12345678901234567890123456789012345.12345678.123." 197 | rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) 198 | msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) 199 | msgBuf, _ := msg.Pack() 200 | b.ReportAllocs() 201 | b.ResetTimer() 202 | for i := 0; i < b.N; i++ { 203 | _ = msg.Unpack(msgBuf) 204 | } 205 | } 206 | 207 | func BenchmarkIdGeneration(b *testing.B) { 208 | for i := 0; i < b.N; i++ { 209 | _ = id() 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /dnssec_keygen.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/elliptic" 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "math/big" 11 | ) 12 | 13 | // Generate generates a DNSKEY of the given bit size. 14 | // The public part is put inside the DNSKEY record. 15 | // The Algorithm in the key must be set as this will define 16 | // what kind of DNSKEY will be generated. 17 | // The ECDSA algorithms imply a fixed keysize, in that case 18 | // bits should be set to the size of the algorithm. 19 | func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { 20 | switch k.Algorithm { 21 | case DSA, DSANSEC3SHA1: 22 | if bits != 1024 { 23 | return nil, ErrKeySize 24 | } 25 | case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: 26 | if bits < 512 || bits > 4096 { 27 | return nil, ErrKeySize 28 | } 29 | case RSASHA512: 30 | if bits < 1024 || bits > 4096 { 31 | return nil, ErrKeySize 32 | } 33 | case ECDSAP256SHA256: 34 | if bits != 256 { 35 | return nil, ErrKeySize 36 | } 37 | case ECDSAP384SHA384: 38 | if bits != 384 { 39 | return nil, ErrKeySize 40 | } 41 | } 42 | 43 | switch k.Algorithm { 44 | case DSA, DSANSEC3SHA1: 45 | params := new(dsa.Parameters) 46 | if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { 47 | return nil, err 48 | } 49 | priv := new(dsa.PrivateKey) 50 | priv.PublicKey.Parameters = *params 51 | err := dsa.GenerateKey(priv, rand.Reader) 52 | if err != nil { 53 | return nil, err 54 | } 55 | k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) 56 | return priv, nil 57 | case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: 58 | priv, err := rsa.GenerateKey(rand.Reader, bits) 59 | if err != nil { 60 | return nil, err 61 | } 62 | k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) 63 | return priv, nil 64 | case ECDSAP256SHA256, ECDSAP384SHA384: 65 | var c elliptic.Curve 66 | switch k.Algorithm { 67 | case ECDSAP256SHA256: 68 | c = elliptic.P256() 69 | case ECDSAP384SHA384: 70 | c = elliptic.P384() 71 | } 72 | priv, err := ecdsa.GenerateKey(c, rand.Reader) 73 | if err != nil { 74 | return nil, err 75 | } 76 | k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) 77 | return priv, nil 78 | default: 79 | return nil, ErrAlg 80 | } 81 | } 82 | 83 | // Set the public key (the value E and N) 84 | func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { 85 | if _E == 0 || _N == nil { 86 | return false 87 | } 88 | buf := exponentToBuf(_E) 89 | buf = append(buf, _N.Bytes()...) 90 | k.PublicKey = toBase64(buf) 91 | return true 92 | } 93 | 94 | // Set the public key for Elliptic Curves 95 | func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { 96 | if _X == nil || _Y == nil { 97 | return false 98 | } 99 | var intlen int 100 | switch k.Algorithm { 101 | case ECDSAP256SHA256: 102 | intlen = 32 103 | case ECDSAP384SHA384: 104 | intlen = 48 105 | } 106 | k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) 107 | return true 108 | } 109 | 110 | // Set the public key for DSA 111 | func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { 112 | if _Q == nil || _P == nil || _G == nil || _Y == nil { 113 | return false 114 | } 115 | buf := dsaToBuf(_Q, _P, _G, _Y) 116 | k.PublicKey = toBase64(buf) 117 | return true 118 | } 119 | 120 | // Set the public key (the values E and N) for RSA 121 | // RFC 3110: Section 2. RSA Public KEY Resource Records 122 | func exponentToBuf(_E int) []byte { 123 | var buf []byte 124 | i := big.NewInt(int64(_E)).Bytes() 125 | if len(i) < 256 { 126 | buf = make([]byte, 1, 1+len(i)) 127 | buf[0] = uint8(len(i)) 128 | } else { 129 | buf = make([]byte, 3, 3+len(i)) 130 | buf[0] = 0 131 | buf[1] = uint8(len(i) >> 8) 132 | buf[2] = uint8(len(i)) 133 | } 134 | buf = append(buf, i...) 135 | return buf 136 | } 137 | 138 | // Set the public key for X and Y for Curve. The two 139 | // values are just concatenated. 140 | func curveToBuf(_X, _Y *big.Int, intlen int) []byte { 141 | buf := intToBytes(_X, intlen) 142 | buf = append(buf, intToBytes(_Y, intlen)...) 143 | return buf 144 | } 145 | 146 | // Set the public key for X and Y for Curve. The two 147 | // values are just concatenated. 148 | func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { 149 | t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) 150 | buf := []byte{byte(t)} 151 | buf = append(buf, intToBytes(_Q, 20)...) 152 | buf = append(buf, intToBytes(_P, 64+t*8)...) 153 | buf = append(buf, intToBytes(_G, 64+t*8)...) 154 | buf = append(buf, intToBytes(_Y, 64+t*8)...) 155 | return buf 156 | } 157 | -------------------------------------------------------------------------------- /dnssec_keyscan.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "io" 9 | "math/big" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | // NewPrivateKey returns a PrivateKey by parsing the string s. 15 | // s should be in the same form of the BIND private key files. 16 | func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { 17 | if s == "" || s[len(s)-1] != '\n' { // We need a closing newline 18 | return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") 19 | } 20 | return k.ReadPrivateKey(strings.NewReader(s), "") 21 | } 22 | 23 | // ReadPrivateKey reads a private key from the io.Reader q. The string file is 24 | // only used in error reporting. 25 | // The public key must be known, because some cryptographic algorithms embed 26 | // the public inside the privatekey. 27 | func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { 28 | m, err := parseKey(q, file) 29 | if m == nil { 30 | return nil, err 31 | } 32 | if _, ok := m["private-key-format"]; !ok { 33 | return nil, ErrPrivKey 34 | } 35 | if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { 36 | return nil, ErrPrivKey 37 | } 38 | // TODO(mg): check if the pubkey matches the private key 39 | algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8) 40 | if err != nil { 41 | return nil, ErrPrivKey 42 | } 43 | switch uint8(algo) { 44 | case DSA: 45 | priv, err := readPrivateKeyDSA(m) 46 | if err != nil { 47 | return nil, err 48 | } 49 | pub := k.publicKeyDSA() 50 | if pub == nil { 51 | return nil, ErrKey 52 | } 53 | priv.PublicKey = *pub 54 | return priv, nil 55 | case RSAMD5: 56 | fallthrough 57 | case RSASHA1: 58 | fallthrough 59 | case RSASHA1NSEC3SHA1: 60 | fallthrough 61 | case RSASHA256: 62 | fallthrough 63 | case RSASHA512: 64 | priv, err := readPrivateKeyRSA(m) 65 | if err != nil { 66 | return nil, err 67 | } 68 | pub := k.publicKeyRSA() 69 | if pub == nil { 70 | return nil, ErrKey 71 | } 72 | priv.PublicKey = *pub 73 | return priv, nil 74 | case ECCGOST: 75 | return nil, ErrPrivKey 76 | case ECDSAP256SHA256: 77 | fallthrough 78 | case ECDSAP384SHA384: 79 | priv, err := readPrivateKeyECDSA(m) 80 | if err != nil { 81 | return nil, err 82 | } 83 | pub := k.publicKeyECDSA() 84 | if pub == nil { 85 | return nil, ErrKey 86 | } 87 | priv.PublicKey = *pub 88 | return priv, nil 89 | default: 90 | return nil, ErrPrivKey 91 | } 92 | } 93 | 94 | // Read a private key (file) string and create a public key. Return the private key. 95 | func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { 96 | p := new(rsa.PrivateKey) 97 | p.Primes = []*big.Int{nil, nil} 98 | for k, v := range m { 99 | switch k { 100 | case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": 101 | v1, err := fromBase64([]byte(v)) 102 | if err != nil { 103 | return nil, err 104 | } 105 | switch k { 106 | case "modulus": 107 | p.PublicKey.N = big.NewInt(0) 108 | p.PublicKey.N.SetBytes(v1) 109 | case "publicexponent": 110 | i := big.NewInt(0) 111 | i.SetBytes(v1) 112 | p.PublicKey.E = int(i.Int64()) // int64 should be large enough 113 | case "privateexponent": 114 | p.D = big.NewInt(0) 115 | p.D.SetBytes(v1) 116 | case "prime1": 117 | p.Primes[0] = big.NewInt(0) 118 | p.Primes[0].SetBytes(v1) 119 | case "prime2": 120 | p.Primes[1] = big.NewInt(0) 121 | p.Primes[1].SetBytes(v1) 122 | } 123 | case "exponent1", "exponent2", "coefficient": 124 | // not used in Go (yet) 125 | case "created", "publish", "activate": 126 | // not used in Go (yet) 127 | } 128 | } 129 | return p, nil 130 | } 131 | 132 | func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) { 133 | p := new(dsa.PrivateKey) 134 | p.X = big.NewInt(0) 135 | for k, v := range m { 136 | switch k { 137 | case "private_value(x)": 138 | v1, err := fromBase64([]byte(v)) 139 | if err != nil { 140 | return nil, err 141 | } 142 | p.X.SetBytes(v1) 143 | case "created", "publish", "activate": 144 | /* not used in Go (yet) */ 145 | } 146 | } 147 | return p, nil 148 | } 149 | 150 | func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { 151 | p := new(ecdsa.PrivateKey) 152 | p.D = big.NewInt(0) 153 | // TODO: validate that the required flags are present 154 | for k, v := range m { 155 | switch k { 156 | case "privatekey": 157 | v1, err := fromBase64([]byte(v)) 158 | if err != nil { 159 | return nil, err 160 | } 161 | p.D.SetBytes(v1) 162 | case "created", "publish", "activate": 163 | /* not used in Go (yet) */ 164 | } 165 | } 166 | return p, nil 167 | } 168 | 169 | // parseKey reads a private key from r. It returns a map[string]string, 170 | // with the key-value pairs, or an error when the file is not correct. 171 | func parseKey(r io.Reader, file string) (map[string]string, error) { 172 | s := scanInit(r) 173 | m := make(map[string]string) 174 | c := make(chan lex) 175 | k := "" 176 | // Start the lexer 177 | go klexer(s, c) 178 | for l := range c { 179 | // It should alternate 180 | switch l.value { 181 | case zKey: 182 | k = l.token 183 | case zValue: 184 | if k == "" { 185 | return nil, &ParseError{file, "no private key seen", l} 186 | } 187 | //println("Setting", strings.ToLower(k), "to", l.token, "b") 188 | m[strings.ToLower(k)] = l.token 189 | k = "" 190 | } 191 | } 192 | return m, nil 193 | } 194 | 195 | // klexer scans the sourcefile and returns tokens on the channel c. 196 | func klexer(s *scan, c chan lex) { 197 | var l lex 198 | str := "" // Hold the current read text 199 | commt := false 200 | key := true 201 | x, err := s.tokenText() 202 | defer close(c) 203 | for err == nil { 204 | l.column = s.position.Column 205 | l.line = s.position.Line 206 | switch x { 207 | case ':': 208 | if commt { 209 | break 210 | } 211 | l.token = str 212 | if key { 213 | l.value = zKey 214 | c <- l 215 | // Next token is a space, eat it 216 | s.tokenText() 217 | key = false 218 | str = "" 219 | } else { 220 | l.value = zValue 221 | } 222 | case ';': 223 | commt = true 224 | case '\n': 225 | if commt { 226 | // Reset a comment 227 | commt = false 228 | } 229 | l.value = zValue 230 | l.token = str 231 | c <- l 232 | str = "" 233 | commt = false 234 | key = true 235 | default: 236 | if commt { 237 | break 238 | } 239 | str += string(x) 240 | } 241 | x, err = s.tokenText() 242 | } 243 | if len(str) > 0 { 244 | // Send remainder 245 | l.token = str 246 | l.value = zValue 247 | c <- l 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /dnssec_privkey.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "math/big" 9 | "strconv" 10 | ) 11 | 12 | const format = "Private-key-format: v1.3\n" 13 | 14 | // PrivateKeyString converts a PrivateKey to a string. This string has the same 15 | // format as the private-key-file of BIND9 (Private-key-format: v1.3). 16 | // It needs some info from the key (the algorithm), so its a method of the DNSKEY 17 | // It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey 18 | func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { 19 | algorithm := strconv.Itoa(int(r.Algorithm)) 20 | algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" 21 | 22 | switch p := p.(type) { 23 | case *rsa.PrivateKey: 24 | modulus := toBase64(p.PublicKey.N.Bytes()) 25 | e := big.NewInt(int64(p.PublicKey.E)) 26 | publicExponent := toBase64(e.Bytes()) 27 | privateExponent := toBase64(p.D.Bytes()) 28 | prime1 := toBase64(p.Primes[0].Bytes()) 29 | prime2 := toBase64(p.Primes[1].Bytes()) 30 | // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm 31 | // and from: http://code.google.com/p/go/issues/detail?id=987 32 | one := big.NewInt(1) 33 | p1 := big.NewInt(0).Sub(p.Primes[0], one) 34 | q1 := big.NewInt(0).Sub(p.Primes[1], one) 35 | exp1 := big.NewInt(0).Mod(p.D, p1) 36 | exp2 := big.NewInt(0).Mod(p.D, q1) 37 | coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0]) 38 | 39 | exponent1 := toBase64(exp1.Bytes()) 40 | exponent2 := toBase64(exp2.Bytes()) 41 | coefficient := toBase64(coeff.Bytes()) 42 | 43 | return format + 44 | "Algorithm: " + algorithm + "\n" + 45 | "Modulus: " + modulus + "\n" + 46 | "PublicExponent: " + publicExponent + "\n" + 47 | "PrivateExponent: " + privateExponent + "\n" + 48 | "Prime1: " + prime1 + "\n" + 49 | "Prime2: " + prime2 + "\n" + 50 | "Exponent1: " + exponent1 + "\n" + 51 | "Exponent2: " + exponent2 + "\n" + 52 | "Coefficient: " + coefficient + "\n" 53 | 54 | case *ecdsa.PrivateKey: 55 | var intlen int 56 | switch r.Algorithm { 57 | case ECDSAP256SHA256: 58 | intlen = 32 59 | case ECDSAP384SHA384: 60 | intlen = 48 61 | } 62 | private := toBase64(intToBytes(p.D, intlen)) 63 | return format + 64 | "Algorithm: " + algorithm + "\n" + 65 | "PrivateKey: " + private + "\n" 66 | 67 | case *dsa.PrivateKey: 68 | T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) 69 | prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) 70 | subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) 71 | base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) 72 | priv := toBase64(intToBytes(p.X, 20)) 73 | pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) 74 | return format + 75 | "Algorithm: " + algorithm + "\n" + 76 | "Prime(p): " + prime + "\n" + 77 | "Subprime(q): " + subprime + "\n" + 78 | "Base(g): " + base + "\n" + 79 | "Private_value(x): " + priv + "\n" + 80 | "Public_value(y): " + pub + "\n" 81 | 82 | default: 83 | return "" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /dnsutil/util.go: -------------------------------------------------------------------------------- 1 | // Package dnsutil contains higher-level methods useful with the dns 2 | // package. While package dns implements the DNS protocols itself, 3 | // these functions are related but not directly required for protocol 4 | // processing. They are often useful in preparing input/output of the 5 | // functions in package dns. 6 | package dnsutil 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | // AddDomain adds origin to s if s is not already a FQDN. 15 | // Note that the result may not be a FQDN. If origin does not end 16 | // with a ".", the result won't either. 17 | // This implements the zonefile convention (specified in RFC 1035, 18 | // Section "5.1. Format") that "@" represents the 19 | // apex (bare) domain. i.e. AddOrigin("@", "foo.com.") returns "foo.com.". 20 | func AddOrigin(s, origin string) string { 21 | // ("foo.", "origin.") -> "foo." (already a FQDN) 22 | // ("foo", "origin.") -> "foo.origin." 23 | // ("foo"), "origin" -> "foo.origin" 24 | // ("@", "origin.") -> "origin." (@ represents the apex (bare) domain) 25 | // ("", "origin.") -> "origin." (not obvious) 26 | // ("foo", "") -> "foo" (not obvious) 27 | 28 | if dns.IsFqdn(s) { 29 | return s // s is already a FQDN, no need to mess with it. 30 | } 31 | if len(origin) == 0 { 32 | return s // Nothing to append. 33 | } 34 | if s == "@" || len(s) == 0 { 35 | return origin // Expand apex. 36 | } 37 | 38 | if origin == "." { 39 | return s + origin // AddOrigin(s, ".") is an expensive way to add a ".". 40 | } 41 | 42 | return s + "." + origin // The simple case. 43 | } 44 | 45 | // TrimDomainName trims origin from s if s is a subdomain. 46 | // This function will never return "", but returns "@" instead (@ represents the apex (bare) domain). 47 | func TrimDomainName(s, origin string) string { 48 | // An apex (bare) domain is always returned as "@". 49 | // If the return value ends in a ".", the domain was not the suffix. 50 | // origin can end in "." or not. Either way the results should be the same. 51 | 52 | if len(s) == 0 { 53 | return "@" // Return the apex (@) rather than "". 54 | } 55 | // Someone is using TrimDomainName(s, ".") to remove a dot if it exists. 56 | if origin == "." { 57 | return strings.TrimSuffix(s, origin) 58 | } 59 | 60 | // Dude, you aren't even if the right subdomain! 61 | if !dns.IsSubDomain(origin, s) { 62 | return s 63 | } 64 | 65 | slabels := dns.Split(s) 66 | olabels := dns.Split(origin) 67 | m := dns.CompareDomainName(s, origin) 68 | if len(olabels) == m { 69 | if len(olabels) == len(slabels) { 70 | return "@" // origin == s 71 | } 72 | if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) { 73 | return "@" // TrimDomainName(".foo.", "foo.") 74 | } 75 | } 76 | 77 | // Return the first (len-m) labels: 78 | return s[:slabels[len(slabels)-m]-1] 79 | } 80 | -------------------------------------------------------------------------------- /dnsutil/util_test.go: -------------------------------------------------------------------------------- 1 | package dnsutil 2 | 3 | import "testing" 4 | 5 | func TestAddOrigin(t *testing.T) { 6 | var tests = []struct{ e1, e2, expected string }{ 7 | {"@", "example.com", "example.com"}, 8 | {"foo", "example.com", "foo.example.com"}, 9 | {"foo.", "example.com", "foo."}, 10 | {"@", "example.com.", "example.com."}, 11 | {"foo", "example.com.", "foo.example.com."}, 12 | {"foo.", "example.com.", "foo."}, 13 | // Oddball tests: 14 | // In general origin should not be "" or "." but at least 15 | // these tests verify we don't crash and will keep results 16 | // from changing unexpectedly. 17 | {"*.", "", "*."}, 18 | {"@", "", "@"}, 19 | {"foobar", "", "foobar"}, 20 | {"foobar.", "", "foobar."}, 21 | {"*.", ".", "*."}, 22 | {"@", ".", "."}, 23 | {"foobar", ".", "foobar."}, 24 | {"foobar.", ".", "foobar."}, 25 | } 26 | for _, test := range tests { 27 | actual := AddOrigin(test.e1, test.e2) 28 | if test.expected != actual { 29 | t.Errorf("AddOrigin(%#v, %#v) expected %#v, go %#v\n", test.e1, test.e2, test.expected, actual) 30 | } 31 | } 32 | } 33 | 34 | func TestTrimDomainName(t *testing.T) { 35 | 36 | // Basic tests. 37 | // Try trimming "example.com" and "example.com." from typical use cases. 38 | var tests_examplecom = []struct{ experiment, expected string }{ 39 | {"foo.example.com", "foo"}, 40 | {"foo.example.com.", "foo"}, 41 | {".foo.example.com", ".foo"}, 42 | {".foo.example.com.", ".foo"}, 43 | {"*.example.com", "*"}, 44 | {"example.com", "@"}, 45 | {"example.com.", "@"}, 46 | {"com.", "com."}, 47 | {"foo.", "foo."}, 48 | {"serverfault.com.", "serverfault.com."}, 49 | {"serverfault.com", "serverfault.com"}, 50 | {".foo.ronco.com", ".foo.ronco.com"}, 51 | {".foo.ronco.com.", ".foo.ronco.com."}, 52 | } 53 | for _, dom := range []string{"example.com", "example.com."} { 54 | for i, test := range tests_examplecom { 55 | actual := TrimDomainName(test.experiment, dom) 56 | if test.expected != actual { 57 | t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.experiment, dom, test.expected, actual) 58 | } 59 | } 60 | } 61 | 62 | // Paranoid tests. 63 | // These test shouldn't be needed but I was weary of off-by-one errors. 64 | // In theory, these can't happen because there are no single-letter TLDs, 65 | // but it is good to exercize the code this way. 66 | var tests = []struct{ experiment, expected string }{ 67 | {"", "@"}, 68 | {".", "."}, 69 | {"a.b.c.d.e.f.", "a.b.c.d.e"}, 70 | {"b.c.d.e.f.", "b.c.d.e"}, 71 | {"c.d.e.f.", "c.d.e"}, 72 | {"d.e.f.", "d.e"}, 73 | {"e.f.", "e"}, 74 | {"f.", "@"}, 75 | {".a.b.c.d.e.f.", ".a.b.c.d.e"}, 76 | {".b.c.d.e.f.", ".b.c.d.e"}, 77 | {".c.d.e.f.", ".c.d.e"}, 78 | {".d.e.f.", ".d.e"}, 79 | {".e.f.", ".e"}, 80 | {".f.", "@"}, 81 | {"a.b.c.d.e.f", "a.b.c.d.e"}, 82 | {"a.b.c.d.e.", "a.b.c.d.e."}, 83 | {"a.b.c.d.e", "a.b.c.d.e"}, 84 | {"a.b.c.d.", "a.b.c.d."}, 85 | {"a.b.c.d", "a.b.c.d"}, 86 | {"a.b.c.", "a.b.c."}, 87 | {"a.b.c", "a.b.c"}, 88 | {"a.b.", "a.b."}, 89 | {"a.b", "a.b"}, 90 | {"a.", "a."}, 91 | {"a", "a"}, 92 | {".a.b.c.d.e.f", ".a.b.c.d.e"}, 93 | {".a.b.c.d.e.", ".a.b.c.d.e."}, 94 | {".a.b.c.d.e", ".a.b.c.d.e"}, 95 | {".a.b.c.d.", ".a.b.c.d."}, 96 | {".a.b.c.d", ".a.b.c.d"}, 97 | {".a.b.c.", ".a.b.c."}, 98 | {".a.b.c", ".a.b.c"}, 99 | {".a.b.", ".a.b."}, 100 | {".a.b", ".a.b"}, 101 | {".a.", ".a."}, 102 | {".a", ".a"}, 103 | } 104 | for _, dom := range []string{"f", "f."} { 105 | for i, test := range tests { 106 | actual := TrimDomainName(test.experiment, dom) 107 | if test.expected != actual { 108 | t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.experiment, dom, test.expected, actual) 109 | } 110 | } 111 | } 112 | 113 | // Test cases for bugs found in the wild. 114 | // These test cases provide both origin, s, and the expected result. 115 | // If you find a bug in the while, this is probably the easiest place 116 | // to add it as a test case. 117 | var tests_wild = []struct{ e1, e2, expected string }{ 118 | {"mathoverflow.net.", ".", "mathoverflow.net"}, 119 | {"mathoverflow.net", ".", "mathoverflow.net"}, 120 | {"", ".", "@"}, 121 | {"@", ".", "@"}, 122 | } 123 | for i, test := range tests_wild { 124 | actual := TrimDomainName(test.e1, test.e2) 125 | if test.expected != actual { 126 | t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.e1, test.e2, test.expected, actual) 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package dns implements a full featured interface to the Domain Name System. 3 | Server- and client-side programming is supported. 4 | The package allows complete control over what is send out to the DNS. The package 5 | API follows the less-is-more principle, by presenting a small, clean interface. 6 | 7 | The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, 8 | TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. 9 | Note that domain names MUST be fully qualified, before sending them, unqualified 10 | names in a message will result in a packing failure. 11 | 12 | Resource records are native types. They are not stored in wire format. 13 | Basic usage pattern for creating a new resource record: 14 | 15 | r := new(dns.MX) 16 | r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, 17 | Class: dns.ClassINET, Ttl: 3600} 18 | r.Preference = 10 19 | r.Mx = "mx.miek.nl." 20 | 21 | Or directly from a string: 22 | 23 | mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") 24 | 25 | Or when the default TTL (3600) and class (IN) suit you: 26 | 27 | mx, err := dns.NewRR("miek.nl. MX 10 mx.miek.nl.") 28 | 29 | Or even: 30 | 31 | mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") 32 | 33 | In the DNS messages are exchanged, these messages contain resource 34 | records (sets). Use pattern for creating a message: 35 | 36 | m := new(dns.Msg) 37 | m.SetQuestion("miek.nl.", dns.TypeMX) 38 | 39 | Or when not certain if the domain name is fully qualified: 40 | 41 | m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) 42 | 43 | The message m is now a message with the question section set to ask 44 | the MX records for the miek.nl. zone. 45 | 46 | The following is slightly more verbose, but more flexible: 47 | 48 | m1 := new(dns.Msg) 49 | m1.Id = dns.Id() 50 | m1.RecursionDesired = true 51 | m1.Question = make([]dns.Question, 1) 52 | m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} 53 | 54 | After creating a message it can be send. 55 | Basic use pattern for synchronous querying the DNS at a 56 | server configured on 127.0.0.1 and port 53: 57 | 58 | c := new(dns.Client) 59 | in, rtt, err := c.Exchange(m1, "127.0.0.1:53") 60 | 61 | Suppressing multiple outstanding queries (with the same question, type and 62 | class) is as easy as setting: 63 | 64 | c.SingleInflight = true 65 | 66 | If these "advanced" features are not needed, a simple UDP query can be send, 67 | with: 68 | 69 | in, err := dns.Exchange(m1, "127.0.0.1:53") 70 | 71 | When this functions returns you will get dns message. A dns message consists 72 | out of four sections. 73 | The question section: in.Question, the answer section: in.Answer, 74 | the authority section: in.Ns and the additional section: in.Extra. 75 | 76 | Each of these sections (except the Question section) contain a []RR. Basic 77 | use pattern for accessing the rdata of a TXT RR as the first RR in 78 | the Answer section: 79 | 80 | if t, ok := in.Answer[0].(*dns.TXT); ok { 81 | // do something with t.Txt 82 | } 83 | 84 | Domain Name and TXT Character String Representations 85 | 86 | Both domain names and TXT character strings are converted to presentation 87 | form both when unpacked and when converted to strings. 88 | 89 | For TXT character strings, tabs, carriage returns and line feeds will be 90 | converted to \t, \r and \n respectively. Back slashes and quotations marks 91 | will be escaped. Bytes below 32 and above 127 will be converted to \DDD 92 | form. 93 | 94 | For domain names, in addition to the above rules brackets, periods, 95 | spaces, semicolons and the at symbol are escaped. 96 | 97 | DNSSEC 98 | 99 | DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It 100 | uses public key cryptography to sign resource records. The 101 | public keys are stored in DNSKEY records and the signatures in RRSIG records. 102 | 103 | Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit 104 | to a request. 105 | 106 | m := new(dns.Msg) 107 | m.SetEdns0(4096, true) 108 | 109 | Signature generation, signature verification and key generation are all supported. 110 | 111 | DYNAMIC UPDATES 112 | 113 | Dynamic updates reuses the DNS message format, but renames three of 114 | the sections. Question is Zone, Answer is Prerequisite, Authority is 115 | Update, only the Additional is not renamed. See RFC 2136 for the gory details. 116 | 117 | You can set a rather complex set of rules for the existence of absence of 118 | certain resource records or names in a zone to specify if resource records 119 | should be added or removed. The table from RFC 2136 supplemented with the Go 120 | DNS function shows which functions exist to specify the prerequisites. 121 | 122 | 3.2.4 - Table Of Metavalues Used In Prerequisite Section 123 | 124 | CLASS TYPE RDATA Meaning Function 125 | -------------------------------------------------------------- 126 | ANY ANY empty Name is in use dns.NameUsed 127 | ANY rrset empty RRset exists (value indep) dns.RRsetUsed 128 | NONE ANY empty Name is not in use dns.NameNotUsed 129 | NONE rrset empty RRset does not exist dns.RRsetNotUsed 130 | zone rrset rr RRset exists (value dep) dns.Used 131 | 132 | The prerequisite section can also be left empty. 133 | If you have decided on the prerequisites you can tell what RRs should 134 | be added or deleted. The next table shows the options you have and 135 | what functions to call. 136 | 137 | 3.4.2.6 - Table Of Metavalues Used In Update Section 138 | 139 | CLASS TYPE RDATA Meaning Function 140 | --------------------------------------------------------------- 141 | ANY ANY empty Delete all RRsets from name dns.RemoveName 142 | ANY rrset empty Delete an RRset dns.RemoveRRset 143 | NONE rrset rr Delete an RR from RRset dns.Remove 144 | zone rrset rr Add to an RRset dns.Insert 145 | 146 | TRANSACTION SIGNATURE 147 | 148 | An TSIG or transaction signature adds a HMAC TSIG record to each message sent. 149 | The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512. 150 | 151 | Basic use pattern when querying with a TSIG name "axfr." (note that these key names 152 | must be fully qualified - as they are domain names) and the base64 secret 153 | "so6ZGir4GPAqINNh9U5c3A==": 154 | 155 | c := new(dns.Client) 156 | c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 157 | m := new(dns.Msg) 158 | m.SetQuestion("miek.nl.", dns.TypeMX) 159 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 160 | ... 161 | // When sending the TSIG RR is calculated and filled in before sending 162 | 163 | When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with 164 | TSIG, this is the basic use pattern. In this example we request an AXFR for 165 | miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A==" 166 | and using the server 176.58.119.54: 167 | 168 | t := new(dns.Transfer) 169 | m := new(dns.Msg) 170 | t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 171 | m.SetAxfr("miek.nl.") 172 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 173 | c, err := t.In(m, "176.58.119.54:53") 174 | for r := range c { ... } 175 | 176 | You can now read the records from the transfer as they come in. Each envelope is checked with TSIG. 177 | If something is not correct an error is returned. 178 | 179 | Basic use pattern validating and replying to a message that has TSIG set. 180 | 181 | server := &dns.Server{Addr: ":53", Net: "udp"} 182 | server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 183 | go server.ListenAndServe() 184 | dns.HandleFunc(".", handleRequest) 185 | 186 | func handleRequest(w dns.ResponseWriter, r *dns.Msg) { 187 | m := new(dns.Msg) 188 | m.SetReply(r) 189 | if r.IsTsig() != nil { 190 | if w.TsigStatus() == nil { 191 | // *Msg r has an TSIG record and it was validated 192 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 193 | } else { 194 | // *Msg r has an TSIG records and it was not valided 195 | } 196 | } 197 | w.WriteMsg(m) 198 | } 199 | 200 | PRIVATE RRS 201 | 202 | RFC 6895 sets aside a range of type codes for private use. This range 203 | is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these 204 | can be used, before requesting an official type code from IANA. 205 | 206 | see http://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more 207 | information. 208 | 209 | EDNS0 210 | 211 | EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated 212 | by RFC 6891. It defines an new RR type, the OPT RR, which is then completely 213 | abused. 214 | Basic use pattern for creating an (empty) OPT RR: 215 | 216 | o := new(dns.OPT) 217 | o.Hdr.Name = "." // MUST be the root zone, per definition. 218 | o.Hdr.Rrtype = dns.TypeOPT 219 | 220 | The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) 221 | interfaces. Currently only a few have been standardized: EDNS0_NSID 222 | (RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note 223 | that these options may be combined in an OPT RR. 224 | Basic use pattern for a server to check if (and which) options are set: 225 | 226 | // o is a dns.OPT 227 | for _, s := range o.Option { 228 | switch e := s.(type) { 229 | case *dns.EDNS0_NSID: 230 | // do stuff with e.Nsid 231 | case *dns.EDNS0_SUBNET: 232 | // access e.Family, e.Address, etc. 233 | } 234 | } 235 | 236 | SIG(0) 237 | 238 | From RFC 2931: 239 | 240 | SIG(0) provides protection for DNS transactions and requests .... 241 | ... protection for glue records, DNS requests, protection for message headers 242 | on requests and responses, and protection of the overall integrity of a response. 243 | 244 | It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared 245 | secret approach in TSIG. 246 | Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and 247 | RSASHA512. 248 | 249 | Signing subsequent messages in multi-message sessions is not implemented. 250 | */ 251 | package dns 252 | -------------------------------------------------------------------------------- /dyn_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Find better solution 4 | -------------------------------------------------------------------------------- /edns_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestOPTTtl(t *testing.T) { 6 | e := &OPT{} 7 | e.Hdr.Name = "." 8 | e.Hdr.Rrtype = TypeOPT 9 | 10 | // verify the default setting of DO=0 11 | if e.Do() { 12 | t.Errorf("DO bit should be zero") 13 | } 14 | 15 | // There are 6 possible invocations of SetDo(): 16 | // 17 | // 1. Starting with DO=0, using SetDo() 18 | // 2. Starting with DO=0, using SetDo(true) 19 | // 3. Starting with DO=0, using SetDo(false) 20 | // 4. Starting with DO=1, using SetDo() 21 | // 5. Starting with DO=1, using SetDo(true) 22 | // 6. Starting with DO=1, using SetDo(false) 23 | 24 | // verify that invoking SetDo() sets DO=1 (TEST #1) 25 | e.SetDo() 26 | if !e.Do() { 27 | t.Errorf("DO bit should be non-zero") 28 | } 29 | // verify that using SetDo(true) works when DO=1 (TEST #5) 30 | e.SetDo(true) 31 | if !e.Do() { 32 | t.Errorf("DO bit should still be non-zero") 33 | } 34 | // verify that we can use SetDo(false) to set DO=0 (TEST #6) 35 | e.SetDo(false) 36 | if e.Do() { 37 | t.Errorf("DO bit should be zero") 38 | } 39 | // verify that if we call SetDo(false) when DO=0 that it is unchanged (TEST #3) 40 | e.SetDo(false) 41 | if e.Do() { 42 | t.Errorf("DO bit should still be zero") 43 | } 44 | // verify that using SetDo(true) works for DO=0 (TEST #2) 45 | e.SetDo(true) 46 | if !e.Do() { 47 | t.Errorf("DO bit should be non-zero") 48 | } 49 | // verify that using SetDo() works for DO=1 (TEST #4) 50 | e.SetDo() 51 | if !e.Do() { 52 | t.Errorf("DO bit should be non-zero") 53 | } 54 | 55 | if e.Version() != 0 { 56 | t.Errorf("version should be non-zero") 57 | } 58 | 59 | e.SetVersion(42) 60 | if e.Version() != 42 { 61 | t.Errorf("set 42, expected %d, got %d", 42, e.Version()) 62 | } 63 | 64 | e.SetExtendedRcode(42) 65 | if e.ExtendedRcode() != 42 { 66 | t.Errorf("set 42, expected %d, got %d", 42-15, e.ExtendedRcode()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package dns_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | // Retrieve the MX records for miek.nl. 13 | func ExampleMX() { 14 | config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") 15 | c := new(dns.Client) 16 | m := new(dns.Msg) 17 | m.SetQuestion("miek.nl.", dns.TypeMX) 18 | m.RecursionDesired = true 19 | r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) 20 | if err != nil { 21 | return 22 | } 23 | if r.Rcode != dns.RcodeSuccess { 24 | return 25 | } 26 | for _, a := range r.Answer { 27 | if mx, ok := a.(*dns.MX); ok { 28 | fmt.Printf("%s\n", mx.String()) 29 | } 30 | } 31 | } 32 | 33 | // Retrieve the DNSKEY records of a zone and convert them 34 | // to DS records for SHA1, SHA256 and SHA384. 35 | func ExampleDS() { 36 | config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") 37 | c := new(dns.Client) 38 | m := new(dns.Msg) 39 | zone := "miek.nl" 40 | m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY) 41 | m.SetEdns0(4096, true) 42 | r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) 43 | if err != nil { 44 | return 45 | } 46 | if r.Rcode != dns.RcodeSuccess { 47 | return 48 | } 49 | for _, k := range r.Answer { 50 | if key, ok := k.(*dns.DNSKEY); ok { 51 | for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} { 52 | fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags) 53 | } 54 | } 55 | } 56 | } 57 | 58 | const TypeAPAIR = 0x0F99 59 | 60 | type APAIR struct { 61 | addr [2]net.IP 62 | } 63 | 64 | func NewAPAIR() dns.PrivateRdata { return new(APAIR) } 65 | 66 | func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() } 67 | func (rd *APAIR) Parse(txt []string) error { 68 | if len(txt) != 2 { 69 | return errors.New("two addresses required for APAIR") 70 | } 71 | for i, s := range txt { 72 | ip := net.ParseIP(s) 73 | if ip == nil { 74 | return errors.New("invalid IP in APAIR text representation") 75 | } 76 | rd.addr[i] = ip 77 | } 78 | return nil 79 | } 80 | 81 | func (rd *APAIR) Pack(buf []byte) (int, error) { 82 | b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...) 83 | n := copy(buf, b) 84 | if n != len(b) { 85 | return n, dns.ErrBuf 86 | } 87 | return n, nil 88 | } 89 | 90 | func (rd *APAIR) Unpack(buf []byte) (int, error) { 91 | ln := net.IPv4len * 2 92 | if len(buf) != ln { 93 | return 0, errors.New("invalid length of APAIR rdata") 94 | } 95 | cp := make([]byte, ln) 96 | copy(cp, buf) // clone bytes to use them in IPs 97 | 98 | rd.addr[0] = net.IP(cp[:3]) 99 | rd.addr[1] = net.IP(cp[4:]) 100 | 101 | return len(buf), nil 102 | } 103 | 104 | func (rd *APAIR) Copy(dest dns.PrivateRdata) error { 105 | cp := make([]byte, rd.Len()) 106 | _, err := rd.Pack(cp) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | d := dest.(*APAIR) 112 | d.addr[0] = net.IP(cp[:3]) 113 | d.addr[1] = net.IP(cp[4:]) 114 | return nil 115 | } 116 | 117 | func (rd *APAIR) Len() int { 118 | return net.IPv4len * 2 119 | } 120 | 121 | func ExamplePrivateHandle() { 122 | dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR) 123 | defer dns.PrivateHandleRemove(TypeAPAIR) 124 | 125 | rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)") 126 | if err != nil { 127 | log.Fatal("could not parse APAIR record: ", err) 128 | } 129 | fmt.Println(rr) 130 | // Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 131 | 132 | m := new(dns.Msg) 133 | m.Id = 12345 134 | m.SetQuestion("miek.nl.", TypeAPAIR) 135 | m.Answer = append(m.Answer, rr) 136 | 137 | fmt.Println(m) 138 | // ;; opcode: QUERY, status: NOERROR, id: 12345 139 | // ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 140 | // 141 | // ;; QUESTION SECTION: 142 | // ;miek.nl. IN APAIR 143 | // 144 | // ;; ANSWER SECTION: 145 | // miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 146 | } 147 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "strconv" 7 | ) 8 | 9 | // NumField returns the number of rdata fields r has. 10 | func NumField(r RR) int { 11 | return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header 12 | } 13 | 14 | // Field returns the rdata field i as a string. Fields are indexed starting from 1. 15 | // RR types that holds slice data, for instance the NSEC type bitmap will return a single 16 | // string where the types are concatenated using a space. 17 | // Accessing non existing fields will cause a panic. 18 | func Field(r RR, i int) string { 19 | if i == 0 { 20 | return "" 21 | } 22 | d := reflect.ValueOf(r).Elem().Field(i) 23 | switch k := d.Kind(); k { 24 | case reflect.String: 25 | return d.String() 26 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 27 | return strconv.FormatInt(d.Int(), 10) 28 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 29 | return strconv.FormatUint(d.Uint(), 10) 30 | case reflect.Slice: 31 | switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { 32 | case `dns:"a"`: 33 | // TODO(miek): Hmm store this as 16 bytes 34 | if d.Len() < net.IPv6len { 35 | return net.IPv4(byte(d.Index(0).Uint()), 36 | byte(d.Index(1).Uint()), 37 | byte(d.Index(2).Uint()), 38 | byte(d.Index(3).Uint())).String() 39 | } 40 | return net.IPv4(byte(d.Index(12).Uint()), 41 | byte(d.Index(13).Uint()), 42 | byte(d.Index(14).Uint()), 43 | byte(d.Index(15).Uint())).String() 44 | case `dns:"aaaa"`: 45 | return net.IP{ 46 | byte(d.Index(0).Uint()), 47 | byte(d.Index(1).Uint()), 48 | byte(d.Index(2).Uint()), 49 | byte(d.Index(3).Uint()), 50 | byte(d.Index(4).Uint()), 51 | byte(d.Index(5).Uint()), 52 | byte(d.Index(6).Uint()), 53 | byte(d.Index(7).Uint()), 54 | byte(d.Index(8).Uint()), 55 | byte(d.Index(9).Uint()), 56 | byte(d.Index(10).Uint()), 57 | byte(d.Index(11).Uint()), 58 | byte(d.Index(12).Uint()), 59 | byte(d.Index(13).Uint()), 60 | byte(d.Index(14).Uint()), 61 | byte(d.Index(15).Uint()), 62 | }.String() 63 | case `dns:"nsec"`: 64 | if d.Len() == 0 { 65 | return "" 66 | } 67 | s := Type(d.Index(0).Uint()).String() 68 | for i := 1; i < d.Len(); i++ { 69 | s += " " + Type(d.Index(i).Uint()).String() 70 | } 71 | return s 72 | default: 73 | // if it does not have a tag its a string slice 74 | fallthrough 75 | case `dns:"txt"`: 76 | if d.Len() == 0 { 77 | return "" 78 | } 79 | s := d.Index(0).String() 80 | for i := 1; i < d.Len(); i++ { 81 | s += " " + d.Index(i).String() 82 | } 83 | return s 84 | } 85 | } 86 | return "" 87 | } 88 | -------------------------------------------------------------------------------- /fuzz_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestFuzzString(t *testing.T) { 6 | testcases := []string{"", " MINFO ", " RP ", " NSEC 0 0", " \" NSEC 0 0\"", " \" MINFO \"", 7 | ";a ", ";a����������", 8 | " NSAP O ", " NSAP N ", 9 | " TYPE4 TYPE6a789a3bc0045c8a5fb42c7d1bd998f5444 IN 9579b47d46817afbd17273e6", 10 | " TYPE45 3 3 4147994 TYPE\\(\\)\\)\\(\\)\\(\\(\\)\\(\\)\\)\\)\\(\\)\\(\\)\\(\\(\\R 948\"\")\\(\\)\\)\\)\\(\\ ", 11 | "$GENERATE 0-3 ${441189,5039418474430,o}", 12 | "$INCLUDE 00 TYPE00000000000n ", 13 | "$INCLUDE PE4 TYPE061463623/727071511 \\(\\)\\$GENERATE 6-462/0", 14 | } 15 | for i, tc := range testcases { 16 | rr, err := NewRR(tc) 17 | if err == nil { 18 | // rr can be nil because we can (for instance) just parse a comment 19 | if rr == nil { 20 | continue 21 | } 22 | t.Fatalf("parsed mailformed RR %d: %s", i, rr.String()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // Parse the $GENERATE statement as used in BIND9 zones. 12 | // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. 13 | // We are called after '$GENERATE '. After which we expect: 14 | // * the range (12-24/2) 15 | // * lhs (ownername) 16 | // * [[ttl][class]] 17 | // * type 18 | // * rhs (rdata) 19 | // But we are lazy here, only the range is parsed *all* occurrences 20 | // of $ after that are interpreted. 21 | // Any error are returned as a string value, the empty string signals 22 | // "no error". 23 | func generate(l lex, c chan lex, t chan *Token, o string) string { 24 | step := 1 25 | if i := strings.IndexAny(l.token, "/"); i != -1 { 26 | if i+1 == len(l.token) { 27 | return "bad step in $GENERATE range" 28 | } 29 | if s, err := strconv.Atoi(l.token[i+1:]); err == nil { 30 | if s < 0 { 31 | return "bad step in $GENERATE range" 32 | } 33 | step = s 34 | } else { 35 | return "bad step in $GENERATE range" 36 | } 37 | l.token = l.token[:i] 38 | } 39 | sx := strings.SplitN(l.token, "-", 2) 40 | if len(sx) != 2 { 41 | return "bad start-stop in $GENERATE range" 42 | } 43 | start, err := strconv.Atoi(sx[0]) 44 | if err != nil { 45 | return "bad start in $GENERATE range" 46 | } 47 | end, err := strconv.Atoi(sx[1]) 48 | if err != nil { 49 | return "bad stop in $GENERATE range" 50 | } 51 | if end < 0 || start < 0 || end < start { 52 | return "bad range in $GENERATE range" 53 | } 54 | 55 | <-c // _BLANK 56 | // Create a complete new string, which we then parse again. 57 | s := "" 58 | BuildRR: 59 | l = <-c 60 | if l.value != zNewline && l.value != zEOF { 61 | s += l.token 62 | goto BuildRR 63 | } 64 | for i := start; i <= end; i += step { 65 | var ( 66 | escape bool 67 | dom bytes.Buffer 68 | mod string 69 | err error 70 | offset int 71 | ) 72 | 73 | for j := 0; j < len(s); j++ { // No 'range' because we need to jump around 74 | switch s[j] { 75 | case '\\': 76 | if escape { 77 | dom.WriteByte('\\') 78 | escape = false 79 | continue 80 | } 81 | escape = true 82 | case '$': 83 | mod = "%d" 84 | offset = 0 85 | if escape { 86 | dom.WriteByte('$') 87 | escape = false 88 | continue 89 | } 90 | escape = false 91 | if j+1 >= len(s) { // End of the string 92 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 93 | continue 94 | } else { 95 | if s[j+1] == '$' { 96 | dom.WriteByte('$') 97 | j++ 98 | continue 99 | } 100 | } 101 | // Search for { and } 102 | if s[j+1] == '{' { // Modifier block 103 | sep := strings.Index(s[j+2:], "}") 104 | if sep == -1 { 105 | return "bad modifier in $GENERATE" 106 | } 107 | mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) 108 | if err != nil { 109 | return err.Error() 110 | } 111 | j += 2 + sep // Jump to it 112 | } 113 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 114 | default: 115 | if escape { // Pretty useless here 116 | escape = false 117 | continue 118 | } 119 | dom.WriteByte(s[j]) 120 | } 121 | } 122 | // Re-parse the RR and send it on the current channel t 123 | rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String()) 124 | if err != nil { 125 | return err.Error() 126 | } 127 | t <- &Token{RR: rx} 128 | // Its more efficient to first built the rrlist and then parse it in 129 | // one go! But is this a problem? 130 | } 131 | return "" 132 | } 133 | 134 | // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. 135 | func modToPrintf(s string) (string, int, error) { 136 | xs := strings.SplitN(s, ",", 3) 137 | if len(xs) != 3 { 138 | return "", 0, errors.New("bad modifier in $GENERATE") 139 | } 140 | // xs[0] is offset, xs[1] is width, xs[2] is base 141 | if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { 142 | return "", 0, errors.New("bad base in $GENERATE") 143 | } 144 | offset, err := strconv.Atoi(xs[0]) 145 | if err != nil || offset > 255 { 146 | return "", 0, errors.New("bad offset in $GENERATE") 147 | } 148 | width, err := strconv.Atoi(xs[1]) 149 | if err != nil || width > 255 { 150 | return "", offset, errors.New("bad width in $GENERATE") 151 | } 152 | switch { 153 | case width < 0: 154 | return "", offset, errors.New("bad width in $GENERATE") 155 | case width == 0: 156 | return "%" + xs[1] + xs[2], offset, nil 157 | } 158 | return "%0" + xs[1] + xs[2], offset, nil 159 | } 160 | -------------------------------------------------------------------------------- /idn/example_test.go: -------------------------------------------------------------------------------- 1 | package idn_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/miekg/dns/idn" 6 | ) 7 | 8 | func ExampleToPunycode() { 9 | name := "インターネット.テスト" 10 | fmt.Printf("%s -> %s", name, idn.ToPunycode(name)) 11 | // Output: インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah 12 | } 13 | 14 | func ExampleFromPunycode() { 15 | name := "xn--mgbaja8a1hpac.xn--mgbachtv" 16 | fmt.Printf("%s -> %s", name, idn.FromPunycode(name)) 17 | // Output: xn--mgbaja8a1hpac.xn--mgbachtv -> الانترنت.اختبار 18 | } 19 | -------------------------------------------------------------------------------- /idn/punycode.go: -------------------------------------------------------------------------------- 1 | // Package idn implements encoding from and to punycode as speficied by RFC 3492. 2 | package idn 3 | 4 | import ( 5 | "bytes" 6 | "strings" 7 | "unicode" 8 | "unicode/utf8" 9 | 10 | "github.com/miekg/dns" 11 | ) 12 | 13 | // Implementation idea from RFC itself and from from IDNA::Punycode created by 14 | // Tatsuhiko Miyagawa and released under Perl Artistic 15 | // License in 2002. 16 | 17 | const ( 18 | _MIN rune = 1 19 | _MAX rune = 26 20 | _SKEW rune = 38 21 | _BASE rune = 36 22 | _BIAS rune = 72 23 | _N rune = 128 24 | _DAMP rune = 700 25 | 26 | _DELIMITER = '-' 27 | _PREFIX = "xn--" 28 | ) 29 | 30 | // ToPunycode converts unicode domain names to DNS-appropriate punycode names. 31 | // This function will return an empty string result for domain names with 32 | // invalid unicode strings. This function expects domain names in lowercase. 33 | func ToPunycode(s string) string { 34 | // Early check to see if encoding is needed. 35 | // This will prevent making heap allocations when not needed. 36 | if !needToPunycode(s) { 37 | return s 38 | } 39 | 40 | tokens := dns.SplitDomainName(s) 41 | switch { 42 | case s == "": 43 | return "" 44 | case tokens == nil: // s == . 45 | return "." 46 | case s[len(s)-1] == '.': 47 | tokens = append(tokens, "") 48 | } 49 | 50 | for i := range tokens { 51 | t := encode([]byte(tokens[i])) 52 | if t == nil { 53 | return "" 54 | } 55 | tokens[i] = string(t) 56 | } 57 | return strings.Join(tokens, ".") 58 | } 59 | 60 | // FromPunycode returns unicode domain name from provided punycode string. 61 | // This function expects punycode strings in lowercase. 62 | func FromPunycode(s string) string { 63 | // Early check to see if decoding is needed. 64 | // This will prevent making heap allocations when not needed. 65 | if !needFromPunycode(s) { 66 | return s 67 | } 68 | 69 | tokens := dns.SplitDomainName(s) 70 | switch { 71 | case s == "": 72 | return "" 73 | case tokens == nil: // s == . 74 | return "." 75 | case s[len(s)-1] == '.': 76 | tokens = append(tokens, "") 77 | } 78 | for i := range tokens { 79 | tokens[i] = string(decode([]byte(tokens[i]))) 80 | } 81 | return strings.Join(tokens, ".") 82 | } 83 | 84 | // digitval converts single byte into meaningful value that's used to calculate decoded unicode character. 85 | const errdigit = 0xffff 86 | 87 | func digitval(code rune) rune { 88 | switch { 89 | case code >= 'A' && code <= 'Z': 90 | return code - 'A' 91 | case code >= 'a' && code <= 'z': 92 | return code - 'a' 93 | case code >= '0' && code <= '9': 94 | return code - '0' + 26 95 | } 96 | return errdigit 97 | } 98 | 99 | // lettercode finds BASE36 byte (a-z0-9) based on calculated number. 100 | func lettercode(digit rune) rune { 101 | switch { 102 | case digit >= 0 && digit <= 25: 103 | return digit + 'a' 104 | case digit >= 26 && digit <= 36: 105 | return digit - 26 + '0' 106 | } 107 | panic("dns: not reached") 108 | } 109 | 110 | // adapt calculates next bias to be used for next iteration delta. 111 | func adapt(delta rune, numpoints int, firsttime bool) rune { 112 | if firsttime { 113 | delta /= _DAMP 114 | } else { 115 | delta /= 2 116 | } 117 | 118 | var k rune 119 | for delta = delta + delta/rune(numpoints); delta > (_BASE-_MIN)*_MAX/2; k += _BASE { 120 | delta /= _BASE - _MIN 121 | } 122 | 123 | return k + ((_BASE-_MIN+1)*delta)/(delta+_SKEW) 124 | } 125 | 126 | // next finds minimal rune (one with lowest codepoint value) that should be equal or above boundary. 127 | func next(b []rune, boundary rune) rune { 128 | if len(b) == 0 { 129 | panic("dns: invalid set of runes to determine next one") 130 | } 131 | m := b[0] 132 | for _, x := range b[1:] { 133 | if x >= boundary && (m < boundary || x < m) { 134 | m = x 135 | } 136 | } 137 | return m 138 | } 139 | 140 | // preprune converts unicode rune to lower case. At this time it's not 141 | // supporting all things described in RFCs. 142 | func preprune(r rune) rune { 143 | if unicode.IsUpper(r) { 144 | r = unicode.ToLower(r) 145 | } 146 | return r 147 | } 148 | 149 | // tfunc is a function that helps calculate each character weight. 150 | func tfunc(k, bias rune) rune { 151 | switch { 152 | case k <= bias: 153 | return _MIN 154 | case k >= bias+_MAX: 155 | return _MAX 156 | } 157 | return k - bias 158 | } 159 | 160 | // needToPunycode returns true for strings that require punycode encoding 161 | // (contain unicode characters). 162 | func needToPunycode(s string) bool { 163 | // This function is very similar to bytes.Runes. We don't use bytes.Runes 164 | // because it makes a heap allocation that's not needed here. 165 | for i := 0; len(s) > 0; i++ { 166 | r, l := utf8.DecodeRuneInString(s) 167 | if r > 0x7f { 168 | return true 169 | } 170 | s = s[l:] 171 | } 172 | return false 173 | } 174 | 175 | // needFromPunycode returns true for strings that require punycode decoding. 176 | func needFromPunycode(s string) bool { 177 | if s == "." { 178 | return false 179 | } 180 | 181 | off := 0 182 | end := false 183 | pl := len(_PREFIX) 184 | sl := len(s) 185 | 186 | // If s starts with _PREFIX. 187 | if sl > pl && s[off:off+pl] == _PREFIX { 188 | return true 189 | } 190 | 191 | for { 192 | // Find the part after the next ".". 193 | off, end = dns.NextLabel(s, off) 194 | if end { 195 | return false 196 | } 197 | // If this parts starts with _PREFIX. 198 | if sl-off > pl && s[off:off+pl] == _PREFIX { 199 | return true 200 | } 201 | } 202 | } 203 | 204 | // encode transforms Unicode input bytes (that represent DNS label) into 205 | // punycode bytestream. This function would return nil if there's an invalid 206 | // character in the label. 207 | func encode(input []byte) []byte { 208 | n, bias := _N, _BIAS 209 | 210 | b := bytes.Runes(input) 211 | for i := range b { 212 | if !isValidRune(b[i]) { 213 | return nil 214 | } 215 | 216 | b[i] = preprune(b[i]) 217 | } 218 | 219 | basic := make([]byte, 0, len(b)) 220 | for _, ltr := range b { 221 | if ltr <= 0x7f { 222 | basic = append(basic, byte(ltr)) 223 | } 224 | } 225 | basiclen := len(basic) 226 | fulllen := len(b) 227 | if basiclen == fulllen { 228 | return basic 229 | } 230 | 231 | var out bytes.Buffer 232 | 233 | out.WriteString(_PREFIX) 234 | if basiclen > 0 { 235 | out.Write(basic) 236 | out.WriteByte(_DELIMITER) 237 | } 238 | 239 | var ( 240 | ltr, nextltr rune 241 | delta, q rune // delta calculation (see rfc) 242 | t, k, cp rune // weight and codepoint calculation 243 | ) 244 | 245 | for h := basiclen; h < fulllen; n, delta = n+1, delta+1 { 246 | nextltr = next(b, n) 247 | delta, n = delta+(nextltr-n)*rune(h+1), nextltr 248 | 249 | for _, ltr = range b { 250 | if ltr < n { 251 | delta++ 252 | } 253 | if ltr == n { 254 | q = delta 255 | for k = _BASE; ; k += _BASE { 256 | t = tfunc(k, bias) 257 | if q < t { 258 | break 259 | } 260 | cp = t + ((q - t) % (_BASE - t)) 261 | out.WriteRune(lettercode(cp)) 262 | q = (q - t) / (_BASE - t) 263 | } 264 | 265 | out.WriteRune(lettercode(q)) 266 | 267 | bias = adapt(delta, h+1, h == basiclen) 268 | h, delta = h+1, 0 269 | } 270 | } 271 | } 272 | return out.Bytes() 273 | } 274 | 275 | // decode transforms punycode input bytes (that represent DNS label) into Unicode bytestream. 276 | func decode(b []byte) []byte { 277 | src := b // b would move and we need to keep it 278 | 279 | n, bias := _N, _BIAS 280 | if !bytes.HasPrefix(b, []byte(_PREFIX)) { 281 | return b 282 | } 283 | out := make([]rune, 0, len(b)) 284 | b = b[len(_PREFIX):] 285 | for pos := len(b) - 1; pos >= 0; pos-- { 286 | // only last delimiter is our interest 287 | if b[pos] == _DELIMITER { 288 | out = append(out, bytes.Runes(b[:pos])...) 289 | b = b[pos+1:] // trim source string 290 | break 291 | } 292 | } 293 | if len(b) == 0 { 294 | return src 295 | } 296 | var ( 297 | i, oldi, w rune 298 | ch byte 299 | t, digit rune 300 | ln int 301 | ) 302 | 303 | for i = 0; len(b) > 0; i++ { 304 | oldi, w = i, 1 305 | for k := _BASE; len(b) > 0; k += _BASE { 306 | ch, b = b[0], b[1:] 307 | digit = digitval(rune(ch)) 308 | if digit == errdigit { 309 | return src 310 | } 311 | i += digit * w 312 | if i < 0 { 313 | // safety check for rune overflow 314 | return src 315 | } 316 | 317 | t = tfunc(k, bias) 318 | if digit < t { 319 | break 320 | } 321 | 322 | w *= _BASE - t 323 | } 324 | ln = len(out) + 1 325 | bias = adapt(i-oldi, ln, oldi == 0) 326 | n += i / rune(ln) 327 | i = i % rune(ln) 328 | // insert 329 | out = append(out, 0) 330 | copy(out[i+1:], out[i:]) 331 | out[i] = n 332 | } 333 | 334 | var ret bytes.Buffer 335 | for _, r := range out { 336 | ret.WriteRune(r) 337 | } 338 | return ret.Bytes() 339 | } 340 | 341 | // isValidRune checks if the character is valid. We will look for the 342 | // character property in the code points list. For now we aren't checking special 343 | // rules in case of contextual property 344 | func isValidRune(r rune) bool { 345 | return findProperty(r) == propertyPVALID 346 | } 347 | 348 | // findProperty will try to check the code point property of the given 349 | // character. It will use a binary search algorithm as we have a slice of 350 | // ordered ranges (average case performance O(log n)) 351 | func findProperty(r rune) property { 352 | imin, imax := 0, len(codePoints) 353 | 354 | for imax >= imin { 355 | imid := (imin + imax) / 2 356 | 357 | codePoint := codePoints[imid] 358 | if (codePoint.start == r && codePoint.end == 0) || (codePoint.start <= r && codePoint.end >= r) { 359 | return codePoint.state 360 | } 361 | 362 | if (codePoint.end > 0 && codePoint.end < r) || (codePoint.end == 0 && codePoint.start < r) { 363 | imin = imid + 1 364 | } else { 365 | imax = imid - 1 366 | } 367 | } 368 | 369 | return propertyUnknown 370 | } 371 | -------------------------------------------------------------------------------- /idn/punycode_test.go: -------------------------------------------------------------------------------- 1 | package idn 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var testcases = [][2]string{ 9 | {"", ""}, 10 | {"a", "a"}, 11 | {"a-b", "a-b"}, 12 | {"a-b-c", "a-b-c"}, 13 | {"abc", "abc"}, 14 | {"я", "xn--41a"}, 15 | {"zя", "xn--z-0ub"}, 16 | {"яZ", "xn--z-zub"}, 17 | {"а-я", "xn----7sb8g"}, 18 | {"إختبار", "xn--kgbechtv"}, 19 | {"آزمایشی", "xn--hgbk6aj7f53bba"}, 20 | {"测试", "xn--0zwm56d"}, 21 | {"測試", "xn--g6w251d"}, 22 | {"испытание", "xn--80akhbyknj4f"}, 23 | {"परीक्षा", "xn--11b5bs3a9aj6g"}, 24 | {"δοκιμή", "xn--jxalpdlp"}, 25 | {"테스트", "xn--9t4b11yi5a"}, 26 | {"טעסט", "xn--deba0ad"}, 27 | {"テスト", "xn--zckzah"}, 28 | {"பரிட்சை", "xn--hlcj6aya9esc7a"}, 29 | {"mamão-com-açúcar", "xn--mamo-com-acar-yeb1e6q"}, 30 | {"σ", "xn--4xa"}, 31 | } 32 | 33 | func TestEncodeDecodePunycode(t *testing.T) { 34 | for _, tst := range testcases { 35 | enc := encode([]byte(tst[0])) 36 | if string(enc) != tst[1] { 37 | t.Errorf("%s encodeded as %s but should be %s", tst[0], enc, tst[1]) 38 | } 39 | dec := decode([]byte(tst[1])) 40 | if string(dec) != strings.ToLower(tst[0]) { 41 | t.Errorf("%s decoded as %s but should be %s", tst[1], dec, strings.ToLower(tst[0])) 42 | } 43 | } 44 | } 45 | 46 | func TestToFromPunycode(t *testing.T) { 47 | for _, tst := range testcases { 48 | // assert unicode.com == punycode.com 49 | full := ToPunycode(tst[0] + ".com") 50 | if full != tst[1]+".com" { 51 | t.Errorf("invalid result from string conversion to punycode, %s and should be %s.com", full, tst[1]) 52 | } 53 | // assert punycode.punycode == unicode.unicode 54 | decoded := FromPunycode(tst[1] + "." + tst[1]) 55 | if decoded != strings.ToLower(tst[0]+"."+tst[0]) { 56 | t.Errorf("invalid result from string conversion to punycode, %s and should be %s.%s", decoded, tst[0], tst[0]) 57 | } 58 | } 59 | } 60 | 61 | func TestEncodeDecodeFinalPeriod(t *testing.T) { 62 | for _, tst := range testcases { 63 | // assert unicode.com. == punycode.com. 64 | full := ToPunycode(tst[0] + ".") 65 | if full != tst[1]+"." { 66 | t.Errorf("invalid result from string conversion to punycode when period added at the end, %#v and should be %#v", full, tst[1]+".") 67 | } 68 | // assert punycode.com. == unicode.com. 69 | decoded := FromPunycode(tst[1] + ".") 70 | if decoded != strings.ToLower(tst[0]+".") { 71 | t.Errorf("invalid result from string conversion to punycode when period added, %#v and should be %#v", decoded, tst[0]+".") 72 | } 73 | full = ToPunycode(tst[0]) 74 | if full != tst[1] { 75 | t.Errorf("invalid result from string conversion to punycode when no period added at the end, %#v and should be %#v", full, tst[1]+".") 76 | } 77 | // assert punycode.com. == unicode.com. 78 | decoded = FromPunycode(tst[1]) 79 | if decoded != strings.ToLower(tst[0]) { 80 | t.Errorf("invalid result from string conversion to punycode when no period added, %#v and should be %#v", decoded, tst[0]+".") 81 | } 82 | } 83 | } 84 | 85 | var invalidACEs = []string{ 86 | "xn--*", 87 | "xn--", 88 | "xn---", 89 | "xn--a000000000", 90 | } 91 | 92 | func TestInvalidPunycode(t *testing.T) { 93 | for _, d := range invalidACEs { 94 | s := FromPunycode(d) 95 | if s != d { 96 | t.Errorf("Changed invalid name %s to %#v", d, s) 97 | } 98 | } 99 | } 100 | 101 | // You can verify the labels that are valid or not comparing to the Verisign 102 | // website: http://mct.verisign-grs.com/ 103 | var invalidUnicodes = []string{ 104 | "Σ", 105 | "ЯZ", 106 | "Испытание", 107 | } 108 | 109 | func TestInvalidUnicodes(t *testing.T) { 110 | for _, d := range invalidUnicodes { 111 | s := ToPunycode(d) 112 | if s != "" { 113 | t.Errorf("Changed invalid name %s to %#v", d, s) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /issue_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Tests that solve that an specific issue. 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestTCPRtt(t *testing.T) { 11 | m := new(Msg) 12 | m.RecursionDesired = true 13 | m.SetQuestion("example.org.", TypeA) 14 | 15 | c := &Client{} 16 | for _, proto := range []string{"udp", "tcp"} { 17 | c.Net = proto 18 | _, rtt, err := c.Exchange(m, "8.8.4.4:53") 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | if rtt == 0 { 23 | t.Fatalf("expecting non zero rtt %s, got zero", c.Net) 24 | } 25 | } 26 | } 27 | 28 | func TestNSEC3MissingSalt(t *testing.T) { 29 | rr, err := NewRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H") 30 | if err != nil { 31 | t.Fatalf("failed to parse example rr: %s", err) 32 | } 33 | m := new(Msg) 34 | m.Answer = []RR{rr} 35 | mb, err := m.Pack() 36 | if err != nil { 37 | t.Fatalf("expected to pack message. err: %s", err) 38 | } 39 | if err := m.Unpack(mb); err != nil { 40 | t.Fatalf("expected to unpack message. missing salt? err: %s", err) 41 | } 42 | in := rr.(*NSEC3).Salt 43 | out := m.Answer[0].(*NSEC3).Salt 44 | if in != out { 45 | t.Fatalf("expected salts to match. packed: `%s`. returned: `%s`", in, out) 46 | } 47 | } 48 | 49 | func TestNSEC3MixedNextDomain(t *testing.T) { 50 | rr, err := NewRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 - k8udemvp1j2f7eg6jebps17vp3n8i58h") 51 | if err != nil { 52 | t.Fatalf("failed to parse example rr: %s", err) 53 | } 54 | m := new(Msg) 55 | m.Answer = []RR{rr} 56 | mb, err := m.Pack() 57 | if err != nil { 58 | t.Fatalf("expected to pack message. err: %s", err) 59 | } 60 | if err := m.Unpack(mb); err != nil { 61 | t.Fatalf("expected to unpack message. err: %s", err) 62 | } 63 | in := strings.ToUpper(rr.(*NSEC3).NextDomain) 64 | out := m.Answer[0].(*NSEC3).NextDomain 65 | if in != out { 66 | t.Fatalf("expected round trip to produce NextDomain `%s`, instead `%s`", in, out) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /labels.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "strings" 4 | 5 | // Holds a bunch of helper functions for dealing with labels. 6 | 7 | // SplitDomainName splits a name string into it's labels. 8 | // www.miek.nl. returns []string{"www", "miek", "nl"} 9 | // .www.miek.nl. returns []string{"", "www", "miek", "nl"}, 10 | // The root label (.) returns nil. Note that using 11 | // strings.Split(s) will work in most cases, but does not handle 12 | // escaped dots (\.) for instance. 13 | // s must be a syntactically valid domain name, see IsDomainName. 14 | func SplitDomainName(s string) (labels []string) { 15 | if len(s) == 0 { 16 | return nil 17 | } 18 | fqdnEnd := 0 // offset of the final '.' or the length of the name 19 | idx := Split(s) 20 | begin := 0 21 | if s[len(s)-1] == '.' { 22 | fqdnEnd = len(s) - 1 23 | } else { 24 | fqdnEnd = len(s) 25 | } 26 | 27 | switch len(idx) { 28 | case 0: 29 | return nil 30 | case 1: 31 | // no-op 32 | default: 33 | end := 0 34 | for i := 1; i < len(idx); i++ { 35 | end = idx[i] 36 | labels = append(labels, s[begin:end-1]) 37 | begin = end 38 | } 39 | } 40 | 41 | labels = append(labels, s[begin:fqdnEnd]) 42 | return labels 43 | } 44 | 45 | // CompareDomainName compares the names s1 and s2 and 46 | // returns how many labels they have in common starting from the *right*. 47 | // The comparison stops at the first inequality. The names are not downcased 48 | // before the comparison. 49 | // 50 | // www.miek.nl. and miek.nl. have two labels in common: miek and nl 51 | // www.miek.nl. and www.bla.nl. have one label in common: nl 52 | // 53 | // s1 and s2 must be syntactically valid domain names. 54 | func CompareDomainName(s1, s2 string) (n int) { 55 | s1, s2 = strings.ToLower(s1), strings.ToLower(s2) 56 | s1 = Fqdn(s1) 57 | s2 = Fqdn(s2) 58 | l1 := Split(s1) 59 | l2 := Split(s2) 60 | 61 | // the first check: root label 62 | if l1 == nil || l2 == nil { 63 | return 64 | } 65 | 66 | j1 := len(l1) - 1 // end 67 | i1 := len(l1) - 2 // start 68 | j2 := len(l2) - 1 69 | i2 := len(l2) - 2 70 | // the second check can be done here: last/only label 71 | // before we fall through into the for-loop below 72 | if s1[l1[j1]:] == s2[l2[j2]:] { 73 | n++ 74 | } else { 75 | return 76 | } 77 | for { 78 | if i1 < 0 || i2 < 0 { 79 | break 80 | } 81 | if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { 82 | n++ 83 | } else { 84 | break 85 | } 86 | j1-- 87 | i1-- 88 | j2-- 89 | i2-- 90 | } 91 | return 92 | } 93 | 94 | // CountLabel counts the the number of labels in the string s. 95 | // s must be a syntactically valid domain name. 96 | func CountLabel(s string) (labels int) { 97 | if s == "." { 98 | return 99 | } 100 | off := 0 101 | end := false 102 | for { 103 | off, end = NextLabel(s, off) 104 | labels++ 105 | if end { 106 | return 107 | } 108 | } 109 | } 110 | 111 | // Split splits a name s into its label indexes. 112 | // www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. 113 | // The root name (.) returns nil. Also see SplitDomainName. 114 | // s must be a syntactically valid domain name. 115 | func Split(s string) []int { 116 | if s == "." { 117 | return nil 118 | } 119 | idx := make([]int, 1, 3) 120 | off := 0 121 | end := false 122 | 123 | for { 124 | off, end = NextLabel(s, off) 125 | if end { 126 | return idx 127 | } 128 | idx = append(idx, off) 129 | } 130 | } 131 | 132 | // NextLabel returns the index of the start of the next label in the 133 | // string s starting at offset. 134 | // The bool end is true when the end of the string has been reached. 135 | // Also see PrevLabel. 136 | func NextLabel(s string, offset int) (i int, end bool) { 137 | quote := false 138 | for i = offset; i < len(s)-1; i++ { 139 | switch s[i] { 140 | case '\\': 141 | quote = !quote 142 | default: 143 | quote = false 144 | case '.': 145 | if quote { 146 | quote = !quote 147 | continue 148 | } 149 | return i + 1, false 150 | } 151 | } 152 | return i + 1, true 153 | } 154 | 155 | // PrevLabel returns the index of the label when starting from the right and 156 | // jumping n labels to the left. 157 | // The bool start is true when the start of the string has been overshot. 158 | // Also see NextLabel. 159 | func PrevLabel(s string, n int) (i int, start bool) { 160 | if n == 0 { 161 | return len(s), false 162 | } 163 | lab := Split(s) 164 | if lab == nil { 165 | return 0, true 166 | } 167 | if n > len(lab) { 168 | return 0, true 169 | } 170 | return lab[len(lab)-n], false 171 | } 172 | -------------------------------------------------------------------------------- /labels_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestCompareDomainName(t *testing.T) { 6 | s1 := "www.miek.nl." 7 | s2 := "miek.nl." 8 | s3 := "www.bla.nl." 9 | s4 := "nl.www.bla." 10 | s5 := "nl" 11 | s6 := "miek.nl" 12 | 13 | if CompareDomainName(s1, s2) != 2 { 14 | t.Errorf("%s with %s should be %d", s1, s2, 2) 15 | } 16 | if CompareDomainName(s1, s3) != 1 { 17 | t.Errorf("%s with %s should be %d", s1, s3, 1) 18 | } 19 | if CompareDomainName(s3, s4) != 0 { 20 | t.Errorf("%s with %s should be %d", s3, s4, 0) 21 | } 22 | // Non qualified tests 23 | if CompareDomainName(s1, s5) != 1 { 24 | t.Errorf("%s with %s should be %d", s1, s5, 1) 25 | } 26 | if CompareDomainName(s1, s6) != 2 { 27 | t.Errorf("%s with %s should be %d", s1, s5, 2) 28 | } 29 | 30 | if CompareDomainName(s1, ".") != 0 { 31 | t.Errorf("%s with %s should be %d", s1, s5, 0) 32 | } 33 | if CompareDomainName(".", ".") != 0 { 34 | t.Errorf("%s with %s should be %d", ".", ".", 0) 35 | } 36 | if CompareDomainName("test.com.", "TEST.COM.") != 2 { 37 | t.Errorf("test.com. and TEST.COM. should be an exact match") 38 | } 39 | } 40 | 41 | func TestSplit(t *testing.T) { 42 | splitter := map[string]int{ 43 | "www.miek.nl.": 3, 44 | "www.miek.nl": 3, 45 | "www..miek.nl": 4, 46 | `www\.miek.nl.`: 2, 47 | `www\\.miek.nl.`: 3, 48 | ".": 0, 49 | "nl.": 1, 50 | "nl": 1, 51 | "com.": 1, 52 | ".com.": 2, 53 | } 54 | for s, i := range splitter { 55 | if x := len(Split(s)); x != i { 56 | t.Errorf("labels should be %d, got %d: %s %v", i, x, s, Split(s)) 57 | } else { 58 | t.Logf("%s %v", s, Split(s)) 59 | } 60 | } 61 | } 62 | 63 | func TestSplit2(t *testing.T) { 64 | splitter := map[string][]int{ 65 | "www.miek.nl.": {0, 4, 9}, 66 | "www.miek.nl": {0, 4, 9}, 67 | "nl": {0}, 68 | } 69 | for s, i := range splitter { 70 | x := Split(s) 71 | switch len(i) { 72 | case 1: 73 | if x[0] != i[0] { 74 | t.Errorf("labels should be %v, got %v: %s", i, x, s) 75 | } 76 | default: 77 | if x[0] != i[0] || x[1] != i[1] || x[2] != i[2] { 78 | t.Errorf("labels should be %v, got %v: %s", i, x, s) 79 | } 80 | } 81 | } 82 | } 83 | 84 | func TestPrevLabel(t *testing.T) { 85 | type prev struct { 86 | string 87 | int 88 | } 89 | prever := map[prev]int{ 90 | prev{"www.miek.nl.", 0}: 12, 91 | prev{"www.miek.nl.", 1}: 9, 92 | prev{"www.miek.nl.", 2}: 4, 93 | 94 | prev{"www.miek.nl", 0}: 11, 95 | prev{"www.miek.nl", 1}: 9, 96 | prev{"www.miek.nl", 2}: 4, 97 | 98 | prev{"www.miek.nl.", 5}: 0, 99 | prev{"www.miek.nl", 5}: 0, 100 | 101 | prev{"www.miek.nl.", 3}: 0, 102 | prev{"www.miek.nl", 3}: 0, 103 | } 104 | for s, i := range prever { 105 | x, ok := PrevLabel(s.string, s.int) 106 | if i != x { 107 | t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string) 108 | } 109 | } 110 | } 111 | 112 | func TestCountLabel(t *testing.T) { 113 | splitter := map[string]int{ 114 | "www.miek.nl.": 3, 115 | "www.miek.nl": 3, 116 | "nl": 1, 117 | ".": 0, 118 | } 119 | for s, i := range splitter { 120 | x := CountLabel(s) 121 | if x != i { 122 | t.Errorf("CountLabel should have %d, got %d", i, x) 123 | } 124 | } 125 | } 126 | 127 | func TestSplitDomainName(t *testing.T) { 128 | labels := map[string][]string{ 129 | "miek.nl": {"miek", "nl"}, 130 | ".": nil, 131 | "www.miek.nl.": {"www", "miek", "nl"}, 132 | "www.miek.nl": {"www", "miek", "nl"}, 133 | "www..miek.nl": {"www", "", "miek", "nl"}, 134 | `www\.miek.nl`: {`www\.miek`, "nl"}, 135 | `www\\.miek.nl`: {`www\\`, "miek", "nl"}, 136 | ".www.miek.nl.": {"", "www", "miek", "nl"}, 137 | } 138 | domainLoop: 139 | for domain, splits := range labels { 140 | parts := SplitDomainName(domain) 141 | if len(parts) != len(splits) { 142 | t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) 143 | continue domainLoop 144 | } 145 | for i := range parts { 146 | if parts[i] != splits[i] { 147 | t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) 148 | continue domainLoop 149 | } 150 | } 151 | } 152 | } 153 | 154 | func TestIsDomainName(t *testing.T) { 155 | type ret struct { 156 | ok bool 157 | lab int 158 | } 159 | names := map[string]*ret{ 160 | "..": {false, 1}, 161 | "@.": {true, 1}, 162 | "www.example.com": {true, 3}, 163 | "www.e%ample.com": {true, 3}, 164 | "www.example.com.": {true, 3}, 165 | "mi\\k.nl.": {true, 2}, 166 | "mi\\k.nl": {true, 2}, 167 | } 168 | for d, ok := range names { 169 | l, k := IsDomainName(d) 170 | if ok.ok != k || ok.lab != l { 171 | t.Errorf(" got %v %d for %s ", k, l, d) 172 | t.Errorf("have %v %d for %s ", ok.ok, ok.lab, d) 173 | } 174 | } 175 | } 176 | 177 | func BenchmarkSplitLabels(b *testing.B) { 178 | for i := 0; i < b.N; i++ { 179 | Split("www.example.com") 180 | } 181 | } 182 | 183 | func BenchmarkLenLabels(b *testing.B) { 184 | for i := 0; i < b.N; i++ { 185 | CountLabel("www.example.com") 186 | } 187 | } 188 | 189 | func BenchmarkCompareLabels(b *testing.B) { 190 | b.ReportAllocs() 191 | for i := 0; i < b.N; i++ { 192 | CompareDomainName("www.example.com", "aa.example.com") 193 | } 194 | } 195 | 196 | func BenchmarkIsSubDomain(b *testing.B) { 197 | b.ReportAllocs() 198 | for i := 0; i < b.N; i++ { 199 | IsSubDomain("www.example.com", "aa.example.com") 200 | IsSubDomain("example.com", "aa.example.com") 201 | IsSubDomain("miek.nl", "aa.example.com") 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /msg_generate.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | // msg_generate.go is meant to run with go generate. It will use 4 | // go/{importer,types} to track down all the RR struct types. Then for each type 5 | // it will generate pack/unpack methods based on the struct tags. The generated source is 6 | // written to zmsg.go, and is meant to be checked into git. 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "go/format" 13 | "go/importer" 14 | "go/types" 15 | "log" 16 | "os" 17 | "strings" 18 | ) 19 | 20 | var packageHdr = ` 21 | // *** DO NOT MODIFY *** 22 | // AUTOGENERATED BY go generate from msg_generate.go 23 | 24 | package dns 25 | 26 | ` 27 | 28 | // getTypeStruct will take a type and the package scope, and return the 29 | // (innermost) struct if the type is considered a RR type (currently defined as 30 | // those structs beginning with a RR_Header, could be redefined as implementing 31 | // the RR interface). The bool return value indicates if embedded structs were 32 | // resolved. 33 | func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { 34 | st, ok := t.Underlying().(*types.Struct) 35 | if !ok { 36 | return nil, false 37 | } 38 | if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { 39 | return st, false 40 | } 41 | if st.Field(0).Anonymous() { 42 | st, _ := getTypeStruct(st.Field(0).Type(), scope) 43 | return st, true 44 | } 45 | return nil, false 46 | } 47 | 48 | func main() { 49 | // Import and type-check the package 50 | pkg, err := importer.Default().Import("github.com/miekg/dns") 51 | fatalIfErr(err) 52 | scope := pkg.Scope() 53 | 54 | // Collect actual types (*X) 55 | var namedTypes []string 56 | for _, name := range scope.Names() { 57 | o := scope.Lookup(name) 58 | if o == nil || !o.Exported() { 59 | continue 60 | } 61 | if st, _ := getTypeStruct(o.Type(), scope); st == nil { 62 | continue 63 | } 64 | if name == "PrivateRR" { 65 | continue 66 | } 67 | 68 | // Check if corresponding TypeX exists 69 | if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { 70 | log.Fatalf("Constant Type%s does not exist.", o.Name()) 71 | } 72 | 73 | namedTypes = append(namedTypes, o.Name()) 74 | } 75 | 76 | b := &bytes.Buffer{} 77 | b.WriteString(packageHdr) 78 | 79 | fmt.Fprint(b, "// pack*() functions\n\n") 80 | for _, name := range namedTypes { 81 | o := scope.Lookup(name) 82 | st, _ := getTypeStruct(o.Type(), scope) 83 | 84 | fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name) 85 | fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress) 86 | if err != nil { 87 | return off, err 88 | } 89 | headerEnd := off 90 | `) 91 | for i := 1; i < st.NumFields(); i++ { 92 | o := func(s string) { 93 | fmt.Fprintf(b, s, st.Field(i).Name()) 94 | fmt.Fprint(b, `if err != nil { 95 | return off, err 96 | } 97 | `) 98 | } 99 | 100 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 101 | switch st.Tag(i) { 102 | case `dns:"-"`: // ignored 103 | case `dns:"txt"`: 104 | o("off, err = packStringTxt(rr.%s, msg, off)\n") 105 | case `dns:"opt"`: 106 | o("off, err = packDataOpt(rr.%s, msg, off)\n") 107 | case `dns:"nsec"`: 108 | o("off, err = packDataNsec(rr.%s, msg, off)\n") 109 | case `dns:"domain-name"`: 110 | o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n") 111 | default: 112 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 113 | } 114 | continue 115 | } 116 | 117 | switch { 118 | case st.Tag(i) == `dns:"-"`: // ignored 119 | case st.Tag(i) == `dns:"cdomain-name"`: 120 | o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n") 121 | case st.Tag(i) == `dns:"domain-name"`: 122 | o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n") 123 | case st.Tag(i) == `dns:"a"`: 124 | o("off, err = packDataA(rr.%s, msg, off)\n") 125 | case st.Tag(i) == `dns:"aaaa"`: 126 | o("off, err = packDataAAAA(rr.%s, msg, off)\n") 127 | case st.Tag(i) == `dns:"uint48"`: 128 | o("off, err = packUint48(rr.%s, msg, off)\n") 129 | case st.Tag(i) == `dns:"txt"`: 130 | o("off, err = packString(rr.%s, msg, off)\n") 131 | 132 | case strings.HasPrefix(st.Tag(i), `dns:"size-base32`): // size-base32 can be packed just like base32 133 | fallthrough 134 | case st.Tag(i) == `dns:"base32"`: 135 | o("off, err = packStringBase32(rr.%s, msg, off)\n") 136 | 137 | case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): // size-base64 can be packed just like base64 138 | fallthrough 139 | case st.Tag(i) == `dns:"base64"`: 140 | o("off, err = packStringBase64(rr.%s, msg, off)\n") 141 | 142 | case strings.HasPrefix(st.Tag(i), `dns:"size-hex:SaltLength`): 143 | // directly write instead of using o() so we get the error check in the correct place 144 | field := st.Field(i).Name() 145 | fmt.Fprintf(b, `// Only pack salt if value is not "-", i.e. empty 146 | if rr.%s != "-" { 147 | off, err = packStringHex(rr.%s, msg, off) 148 | if err != nil { 149 | return off, err 150 | } 151 | } 152 | `, field, field) 153 | continue 154 | case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): // size-hex can be packed just like hex 155 | fallthrough 156 | case st.Tag(i) == `dns:"hex"`: 157 | o("off, err = packStringHex(rr.%s, msg, off)\n") 158 | 159 | case st.Tag(i) == `dns:"octet"`: 160 | o("off, err = packStringOctet(rr.%s, msg, off)\n") 161 | case st.Tag(i) == "": 162 | switch st.Field(i).Type().(*types.Basic).Kind() { 163 | case types.Uint8: 164 | o("off, err = packUint8(rr.%s, msg, off)\n") 165 | case types.Uint16: 166 | o("off, err = packUint16(rr.%s, msg, off)\n") 167 | case types.Uint32: 168 | o("off, err = packUint32(rr.%s, msg, off)\n") 169 | case types.Uint64: 170 | o("off, err = packUint64(rr.%s, msg, off)\n") 171 | case types.String: 172 | o("off, err = packString(rr.%s, msg, off)\n") 173 | default: 174 | log.Fatalln(name, st.Field(i).Name()) 175 | } 176 | default: 177 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 178 | } 179 | } 180 | // We have packed everything, only now we know the rdlength of this RR 181 | fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)") 182 | fmt.Fprintln(b, "return off, nil }\n") 183 | } 184 | 185 | fmt.Fprint(b, "// unpack*() functions\n\n") 186 | for _, name := range namedTypes { 187 | o := scope.Lookup(name) 188 | st, _ := getTypeStruct(o.Type(), scope) 189 | 190 | fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name) 191 | fmt.Fprintf(b, "rr := new(%s)\n", name) 192 | fmt.Fprint(b, "rr.Hdr = h\n") 193 | fmt.Fprint(b, `if noRdata(h) { 194 | return rr, off, nil 195 | } 196 | var err error 197 | rdStart := off 198 | _ = rdStart 199 | 200 | `) 201 | for i := 1; i < st.NumFields(); i++ { 202 | o := func(s string) { 203 | fmt.Fprintf(b, s, st.Field(i).Name()) 204 | fmt.Fprint(b, `if err != nil { 205 | return rr, off, err 206 | } 207 | `) 208 | } 209 | 210 | // size-* are special, because they reference a struct member we should use for the length. 211 | if strings.HasPrefix(st.Tag(i), `dns:"size-`) { 212 | structMember := structMember(st.Tag(i)) 213 | structTag := structTag(st.Tag(i)) 214 | switch structTag { 215 | case "hex": 216 | fmt.Fprintf(b, "rr.%s, off, err = unpackStringHex(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) 217 | case "base32": 218 | fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase32(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) 219 | case "base64": 220 | fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase64(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember) 221 | default: 222 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 223 | } 224 | fmt.Fprint(b, `if err != nil { 225 | return rr, off, err 226 | } 227 | `) 228 | continue 229 | } 230 | 231 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 232 | switch st.Tag(i) { 233 | case `dns:"-"`: // ignored 234 | case `dns:"txt"`: 235 | o("rr.%s, off, err = unpackStringTxt(msg, off)\n") 236 | case `dns:"opt"`: 237 | o("rr.%s, off, err = unpackDataOpt(msg, off)\n") 238 | case `dns:"nsec"`: 239 | o("rr.%s, off, err = unpackDataNsec(msg, off)\n") 240 | case `dns:"domain-name"`: 241 | o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") 242 | default: 243 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 244 | } 245 | continue 246 | } 247 | 248 | switch st.Tag(i) { 249 | case `dns:"-"`: // ignored 250 | case `dns:"cdomain-name"`: 251 | fallthrough 252 | case `dns:"domain-name"`: 253 | o("rr.%s, off, err = UnpackDomainName(msg, off)\n") 254 | case `dns:"a"`: 255 | o("rr.%s, off, err = unpackDataA(msg, off)\n") 256 | case `dns:"aaaa"`: 257 | o("rr.%s, off, err = unpackDataAAAA(msg, off)\n") 258 | case `dns:"uint48"`: 259 | o("rr.%s, off, err = unpackUint48(msg, off)\n") 260 | case `dns:"txt"`: 261 | o("rr.%s, off, err = unpackString(msg, off)\n") 262 | case `dns:"base32"`: 263 | o("rr.%s, off, err = unpackStringBase32(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") 264 | case `dns:"base64"`: 265 | o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") 266 | case `dns:"hex"`: 267 | o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") 268 | case `dns:"octet"`: 269 | o("rr.%s, off, err = unpackStringOctet(msg, off)\n") 270 | case "": 271 | switch st.Field(i).Type().(*types.Basic).Kind() { 272 | case types.Uint8: 273 | o("rr.%s, off, err = unpackUint8(msg, off)\n") 274 | case types.Uint16: 275 | o("rr.%s, off, err = unpackUint16(msg, off)\n") 276 | case types.Uint32: 277 | o("rr.%s, off, err = unpackUint32(msg, off)\n") 278 | case types.Uint64: 279 | o("rr.%s, off, err = unpackUint64(msg, off)\n") 280 | case types.String: 281 | o("rr.%s, off, err = unpackString(msg, off)\n") 282 | default: 283 | log.Fatalln(name, st.Field(i).Name()) 284 | } 285 | default: 286 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 287 | } 288 | // If we've hit len(msg) we return without error. 289 | if i < st.NumFields()-1 { 290 | fmt.Fprintf(b, `if off == len(msg) { 291 | return rr, off, nil 292 | } 293 | `) 294 | } 295 | } 296 | fmt.Fprintf(b, "return rr, off, err }\n\n") 297 | } 298 | // Generate typeToUnpack map 299 | fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){") 300 | for _, name := range namedTypes { 301 | if name == "RFC3597" { 302 | continue 303 | } 304 | fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name) 305 | } 306 | fmt.Fprintln(b, "}\n") 307 | 308 | // gofmt 309 | res, err := format.Source(b.Bytes()) 310 | if err != nil { 311 | b.WriteTo(os.Stderr) 312 | log.Fatal(err) 313 | } 314 | 315 | // write result 316 | f, err := os.Create("zmsg.go") 317 | fatalIfErr(err) 318 | defer f.Close() 319 | f.Write(res) 320 | } 321 | 322 | // structMember will take a tag like dns:"size-base32:SaltLength" and return the last part of this string. 323 | func structMember(s string) string { 324 | fields := strings.Split(s, ":") 325 | if len(fields) == 0 { 326 | return "" 327 | } 328 | f := fields[len(fields)-1] 329 | // f should have a closing " 330 | if len(f) > 1 { 331 | return f[:len(f)-1] 332 | } 333 | return f 334 | } 335 | 336 | // structTag will take a tag like dns:"size-base32:SaltLength" and return base32. 337 | func structTag(s string) string { 338 | fields := strings.Split(s, ":") 339 | if len(fields) < 2 { 340 | return "" 341 | } 342 | return fields[1][len("\"size-"):] 343 | } 344 | 345 | func fatalIfErr(err error) { 346 | if err != nil { 347 | log.Fatal(err) 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /msg_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | const ( 12 | maxPrintableLabel = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789x" 13 | tooLongLabel = maxPrintableLabel + "x" 14 | ) 15 | 16 | var ( 17 | longDomain = maxPrintableLabel[:53] + strings.TrimSuffix( 18 | strings.Join([]string{".", ".", ".", ".", "."}, maxPrintableLabel[:49]), ".") 19 | reChar = regexp.MustCompile(`.`) 20 | i = -1 21 | maxUnprintableLabel = reChar.ReplaceAllStringFunc(maxPrintableLabel, func(ch string) string { 22 | if i++; i >= 32 { 23 | i = 0 24 | } 25 | return fmt.Sprintf("\\%03d", i) 26 | }) 27 | ) 28 | 29 | func TestUnpackDomainName(t *testing.T) { 30 | var cases = []struct { 31 | label string 32 | input string 33 | expectedOutput string 34 | expectedError string 35 | }{ 36 | {"empty domain", 37 | "\x00", 38 | ".", 39 | ""}, 40 | {"long label", 41 | string(63) + maxPrintableLabel + "\x00", 42 | maxPrintableLabel + ".", 43 | ""}, 44 | {"unprintable label", 45 | string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel, 46 | func(escape string) string { 47 | n, _ := strconv.ParseInt(escape[1:], 10, 8) 48 | return string(n) 49 | }) + "\x00", 50 | maxUnprintableLabel + ".", 51 | ""}, 52 | {"long domain", 53 | string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00", 54 | longDomain + ".", 55 | ""}, 56 | {"compression pointer", 57 | // an unrealistic but functional test referencing an offset _inside_ a label 58 | "\x03foo" + "\x05\x03com\x00" + "\x07example" + "\xC0\x05", 59 | "foo.\\003com\\000.example.com.", 60 | ""}, 61 | 62 | {"too long domain", 63 | string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00", 64 | "x" + longDomain + ".", 65 | ErrLongDomain.Error()}, 66 | {"too long by pointer", 67 | // a matryoshka doll name to get over 255 octets after expansion via internal pointers 68 | string([]byte{ 69 | // 11 length values, first to last 70 | 40, 37, 34, 31, 28, 25, 22, 19, 16, 13, 0, 71 | // 12 filler values 72 | 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 73 | // 10 pointers, last to first 74 | 192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1, 75 | }), 76 | "", 77 | ErrLongDomain.Error()}, 78 | {"long by pointer", 79 | // a matryoshka doll name _not_ exceeding 255 octets after expansion 80 | string([]byte{ 81 | // 11 length values, first to last 82 | 37, 34, 31, 28, 25, 22, 19, 16, 13, 10, 0, 83 | // 9 filler values 84 | 120, 120, 120, 120, 120, 120, 120, 120, 120, 85 | // 10 pointers, last to first 86 | 192, 10, 192, 9, 192, 8, 192, 7, 192, 6, 192, 5, 192, 4, 192, 3, 192, 2, 192, 1, 87 | }), 88 | "" + 89 | (`\"\031\028\025\022\019\016\013\010\000xxxxxxxxx` + 90 | `\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003\192\002.`) + 91 | (`\031\028\025\022\019\016\013\010\000xxxxxxxxx` + 92 | `\192\010\192\009\192\008\192\007\192\006\192\005\192\004\192\003.`) + 93 | (`\028\025\022\019\016\013\010\000xxxxxxxxx` + 94 | `\192\010\192\009\192\008\192\007\192\006\192\005\192\004.`) + 95 | (`\025\022\019\016\013\010\000xxxxxxxxx` + 96 | `\192\010\192\009\192\008\192\007\192\006\192\005.`) + 97 | `\022\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007\192\006.` + 98 | `\019\016\013\010\000xxxxxxxxx\192\010\192\009\192\008\192\007.` + 99 | `\016\013\010\000xxxxxxxxx\192\010\192\009\192\008.` + 100 | `\013\010\000xxxxxxxxx\192\010\192\009.` + 101 | `\010\000xxxxxxxxx\192\010.` + 102 | `\000xxxxxxxxx.`, 103 | ""}, 104 | {"truncated name", "\x07example\x03", "", "dns: buffer size too small"}, 105 | {"non-absolute name", "\x07example\x03com", "", "dns: buffer size too small"}, 106 | {"compression pointer cycle", 107 | "\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04", 108 | "", 109 | "dns: too many compression pointers"}, 110 | {"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"}, 111 | {"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"}, 112 | } 113 | for _, test := range cases { 114 | output, idx, err := UnpackDomainName([]byte(test.input), 0) 115 | if test.expectedOutput != "" && output != test.expectedOutput { 116 | t.Errorf("%s: expected %s, got %s", test.label, test.expectedOutput, output) 117 | } 118 | if test.expectedError == "" && err != nil { 119 | t.Errorf("%s: expected no error, got %d %v", test.label, idx, err) 120 | } else if test.expectedError != "" && (err == nil || err.Error() != test.expectedError) { 121 | t.Errorf("%s: expected error %s, got %d %v", test.label, test.expectedError, idx, err) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /nsecx.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha1" 5 | "hash" 6 | "strings" 7 | ) 8 | 9 | type saltWireFmt struct { 10 | Salt string `dns:"size-hex"` 11 | } 12 | 13 | // HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase. 14 | func HashName(label string, ha uint8, iter uint16, salt string) string { 15 | saltwire := new(saltWireFmt) 16 | saltwire.Salt = salt 17 | wire := make([]byte, DefaultMsgSize) 18 | n, err := packSaltWire(saltwire, wire) 19 | if err != nil { 20 | return "" 21 | } 22 | wire = wire[:n] 23 | name := make([]byte, 255) 24 | off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) 25 | if err != nil { 26 | return "" 27 | } 28 | name = name[:off] 29 | var s hash.Hash 30 | switch ha { 31 | case SHA1: 32 | s = sha1.New() 33 | default: 34 | return "" 35 | } 36 | 37 | // k = 0 38 | s.Write(name) 39 | s.Write(wire) 40 | nsec3 := s.Sum(nil) 41 | // k > 0 42 | for k := uint16(0); k < iter; k++ { 43 | s.Reset() 44 | s.Write(nsec3) 45 | s.Write(wire) 46 | nsec3 = s.Sum(nsec3[:0]) 47 | } 48 | return toBase32(nsec3) 49 | } 50 | 51 | // Cover returns true if a name is covered by the NSEC3 record 52 | func (rr *NSEC3) Cover(name string) bool { 53 | nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 54 | owner := strings.ToUpper(rr.Hdr.Name) 55 | labelIndices := Split(owner) 56 | if len(labelIndices) < 2 { 57 | return false 58 | } 59 | ownerHash := owner[:labelIndices[1]-1] 60 | ownerZone := owner[labelIndices[1]:] 61 | if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone 62 | return false 63 | } 64 | 65 | nextHash := rr.NextDomain 66 | if ownerHash == nextHash { // empty interval 67 | return false 68 | } 69 | if ownerHash > nextHash { // end of zone 70 | if nameHash > ownerHash { // covered since there is nothing after ownerHash 71 | return true 72 | } 73 | return nameHash < nextHash // if nameHash is before beginning of zone it is covered 74 | } 75 | if nameHash < ownerHash { // nameHash is before ownerHash, not covered 76 | return false 77 | } 78 | return nameHash < nextHash // if nameHash is before nextHash is it covered (between ownerHash and nextHash) 79 | } 80 | 81 | // Match returns true if a name matches the NSEC3 record 82 | func (rr *NSEC3) Match(name string) bool { 83 | nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 84 | owner := strings.ToUpper(rr.Hdr.Name) 85 | labelIndices := Split(owner) 86 | if len(labelIndices) < 2 { 87 | return false 88 | } 89 | ownerHash := owner[:labelIndices[1]-1] 90 | ownerZone := owner[labelIndices[1]:] 91 | if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone 92 | return false 93 | } 94 | if ownerHash == nameHash { 95 | return true 96 | } 97 | return false 98 | } 99 | 100 | func packSaltWire(sw *saltWireFmt, msg []byte) (int, error) { 101 | off, err := packStringHex(sw.Salt, msg, 0) 102 | if err != nil { 103 | return off, err 104 | } 105 | return off, nil 106 | } 107 | -------------------------------------------------------------------------------- /nsecx_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestPackNsec3(t *testing.T) { 6 | nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD") 7 | if nsec3 != "ROCCJAE8BJJU7HN6T7NG3TNM8ACRS87J" { 8 | t.Error(nsec3) 9 | } 10 | 11 | nsec3 = HashName("a.b.c.example.org.", SHA1, 2, "DEAD") 12 | if nsec3 != "6LQ07OAHBTOOEU2R9ANI2AT70K5O0RCG" { 13 | t.Error(nsec3) 14 | } 15 | } 16 | 17 | func TestNsec3(t *testing.T) { 18 | nsec3, _ := NewRR("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 SK4F38CQ0ATIEI8MH3RGD0P5I4II6QAN NS SOA TXT RRSIG DNSKEY NSEC3PARAM") 19 | if !nsec3.(*NSEC3).Match("nl.") { // name hash = sk4e8fj94u78smusb40o1n0oltbblu2r 20 | t.Fatal("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. should match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") 21 | } 22 | if !nsec3.(*NSEC3).Match("NL.") { // name hash = sk4e8fj94u78smusb40o1n0oltbblu2r 23 | t.Fatal("sk4e8fj94u78smusb40o1n0oltbblu2r.NL. should match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") 24 | } 25 | if nsec3.(*NSEC3).Match("com.") { // 26 | t.Fatal("com. is not in the zone nl.") 27 | } 28 | if nsec3.(*NSEC3).Match("test.nl.") { // name hash = gd0ptr5bnfpimpu2d3v6gd4n0bai7s0q 29 | t.Fatal("gd0ptr5bnfpimpu2d3v6gd4n0bai7s0q.nl. should not match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") 30 | } 31 | nsec3, _ = NewRR("nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 SK4F38CQ0ATIEI8MH3RGD0P5I4II6QAN NS SOA TXT RRSIG DNSKEY NSEC3PARAM") 32 | if nsec3.(*NSEC3).Match("nl.") { 33 | t.Fatal("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. should not match a record without a owner hash") 34 | } 35 | 36 | for _, tc := range []struct { 37 | rr *NSEC3 38 | name string 39 | covers bool 40 | }{ 41 | // positive tests 42 | { // name hash between owner hash and next hash 43 | rr: &NSEC3{ 44 | Hdr: RR_Header{Name: "2N1TB3VAIRUOBL6RKDVII42N9TFMIALP.com."}, 45 | Hash: 1, 46 | Flags: 1, 47 | Iterations: 5, 48 | Salt: "F10E9F7EA83FC8F3", 49 | NextDomain: "PT3RON8N7PM3A0OE989IB84OOSADP7O8", 50 | }, 51 | name: "bsd.com.", 52 | covers: true, 53 | }, 54 | { // end of zone, name hash is after owner hash 55 | rr: &NSEC3{ 56 | Hdr: RR_Header{Name: "3v62ulr0nre83v0rja2vjgtlif9v6rab.com."}, 57 | Hash: 1, 58 | Flags: 1, 59 | Iterations: 5, 60 | Salt: "F10E9F7EA83FC8F3", 61 | NextDomain: "2N1TB3VAIRUOBL6RKDVII42N9TFMIALP", 62 | }, 63 | name: "csd.com.", 64 | covers: true, 65 | }, 66 | { // end of zone, name hash is before beginning of zone 67 | rr: &NSEC3{ 68 | Hdr: RR_Header{Name: "PT3RON8N7PM3A0OE989IB84OOSADP7O8.com."}, 69 | Hash: 1, 70 | Flags: 1, 71 | Iterations: 5, 72 | Salt: "F10E9F7EA83FC8F3", 73 | NextDomain: "3V62ULR0NRE83V0RJA2VJGTLIF9V6RAB", 74 | }, 75 | name: "asd.com.", 76 | covers: true, 77 | }, 78 | // negative tests 79 | { // too short owner name 80 | rr: &NSEC3{ 81 | Hdr: RR_Header{Name: "nl."}, 82 | Hash: 1, 83 | Flags: 1, 84 | Iterations: 5, 85 | Salt: "F10E9F7EA83FC8F3", 86 | NextDomain: "39P99DCGG0MDLARTCRMCF6OFLLUL7PR6", 87 | }, 88 | name: "asd.com.", 89 | covers: false, 90 | }, 91 | { // outside of zone 92 | rr: &NSEC3{ 93 | Hdr: RR_Header{Name: "39p91242oslggest5e6a7cci4iaeqvnk.nl."}, 94 | Hash: 1, 95 | Flags: 1, 96 | Iterations: 5, 97 | Salt: "F10E9F7EA83FC8F3", 98 | NextDomain: "39P99DCGG0MDLARTCRMCF6OFLLUL7PR6", 99 | }, 100 | name: "asd.com.", 101 | covers: false, 102 | }, 103 | { // empty interval 104 | rr: &NSEC3{ 105 | Hdr: RR_Header{Name: "2n1tb3vairuobl6rkdvii42n9tfmialp.com."}, 106 | Hash: 1, 107 | Flags: 1, 108 | Iterations: 5, 109 | Salt: "F10E9F7EA83FC8F3", 110 | NextDomain: "2N1TB3VAIRUOBL6RKDVII42N9TFMIALP", 111 | }, 112 | name: "asd.com.", 113 | covers: false, 114 | }, 115 | { // name hash is before owner hash, not covered 116 | rr: &NSEC3{ 117 | Hdr: RR_Header{Name: "3V62ULR0NRE83V0RJA2VJGTLIF9V6RAB.com."}, 118 | Hash: 1, 119 | Flags: 1, 120 | Iterations: 5, 121 | Salt: "F10E9F7EA83FC8F3", 122 | NextDomain: "PT3RON8N7PM3A0OE989IB84OOSADP7O8", 123 | }, 124 | name: "asd.com.", 125 | covers: false, 126 | }, 127 | } { 128 | covers := tc.rr.Cover(tc.name) 129 | if tc.covers != covers { 130 | t.Fatalf("Cover failed for %s: expected %t, got %t [record: %s]", tc.name, tc.covers, covers, tc.rr) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /privaterr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // PrivateRdata is an interface used for implementing "Private Use" RR types, see 9 | // RFC 6895. This allows one to experiment with new RR types, without requesting an 10 | // official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. 11 | type PrivateRdata interface { 12 | // String returns the text presentaton of the Rdata of the Private RR. 13 | String() string 14 | // Parse parses the Rdata of the private RR. 15 | Parse([]string) error 16 | // Pack is used when packing a private RR into a buffer. 17 | Pack([]byte) (int, error) 18 | // Unpack is used when unpacking a private RR from a buffer. 19 | // TODO(miek): diff. signature than Pack, see edns0.go for instance. 20 | Unpack([]byte) (int, error) 21 | // Copy copies the Rdata. 22 | Copy(PrivateRdata) error 23 | // Len returns the length in octets of the Rdata. 24 | Len() int 25 | } 26 | 27 | // PrivateRR represents an RR that uses a PrivateRdata user-defined type. 28 | // It mocks normal RRs and implements dns.RR interface. 29 | type PrivateRR struct { 30 | Hdr RR_Header 31 | Data PrivateRdata 32 | } 33 | 34 | func mkPrivateRR(rrtype uint16) *PrivateRR { 35 | // Panics if RR is not an instance of PrivateRR. 36 | rrfunc, ok := TypeToRR[rrtype] 37 | if !ok { 38 | panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) 39 | } 40 | 41 | anyrr := rrfunc() 42 | switch rr := anyrr.(type) { 43 | case *PrivateRR: 44 | return rr 45 | } 46 | panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr)) 47 | } 48 | 49 | // Header return the RR header of r. 50 | func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } 51 | 52 | func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } 53 | 54 | // Private len and copy parts to satisfy RR interface. 55 | func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } 56 | func (r *PrivateRR) copy() RR { 57 | // make new RR like this: 58 | rr := mkPrivateRR(r.Hdr.Rrtype) 59 | newh := r.Hdr.copyHeader() 60 | rr.Hdr = *newh 61 | 62 | err := r.Data.Copy(rr.Data) 63 | if err != nil { 64 | panic("dns: got value that could not be used to copy Private rdata") 65 | } 66 | return rr 67 | } 68 | func (r *PrivateRR) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) { 69 | off, err := r.Hdr.pack(msg, off, compression, compress) 70 | if err != nil { 71 | return off, err 72 | } 73 | headerEnd := off 74 | n, err := r.Data.Pack(msg[off:]) 75 | if err != nil { 76 | return len(msg), err 77 | } 78 | off += n 79 | r.Header().Rdlength = uint16(off - headerEnd) 80 | return off, nil 81 | } 82 | 83 | // PrivateHandle registers a private resource record type. It requires 84 | // string and numeric representation of private RR type and generator function as argument. 85 | func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { 86 | rtypestr = strings.ToUpper(rtypestr) 87 | 88 | TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } 89 | TypeToString[rtype] = rtypestr 90 | StringToType[rtypestr] = rtype 91 | 92 | typeToUnpack[rtype] = func(h RR_Header, msg []byte, off int) (RR, int, error) { 93 | if noRdata(h) { 94 | return &h, off, nil 95 | } 96 | var err error 97 | 98 | rr := mkPrivateRR(h.Rrtype) 99 | rr.Hdr = h 100 | 101 | off1, err := rr.Data.Unpack(msg[off:]) 102 | off += off1 103 | if err != nil { 104 | return rr, off, err 105 | } 106 | return rr, off, err 107 | } 108 | 109 | setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { 110 | rr := mkPrivateRR(h.Rrtype) 111 | rr.Hdr = h 112 | 113 | var l lex 114 | text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 115 | Fetch: 116 | for { 117 | // TODO(miek): we could also be returning _QUOTE, this might or might not 118 | // be an issue (basically parsing TXT becomes hard) 119 | switch l = <-c; l.value { 120 | case zNewline, zEOF: 121 | break Fetch 122 | case zString: 123 | text = append(text, l.token) 124 | } 125 | } 126 | 127 | err := rr.Data.Parse(text) 128 | if err != nil { 129 | return nil, &ParseError{f, err.Error(), l}, "" 130 | } 131 | 132 | return rr, nil, "" 133 | } 134 | 135 | typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} 136 | } 137 | 138 | // PrivateHandleRemove removes defenitions required to support private RR type. 139 | func PrivateHandleRemove(rtype uint16) { 140 | rtypestr, ok := TypeToString[rtype] 141 | if ok { 142 | delete(TypeToRR, rtype) 143 | delete(TypeToString, rtype) 144 | delete(typeToparserFunc, rtype) 145 | delete(StringToType, rtypestr) 146 | delete(typeToUnpack, rtype) 147 | } 148 | return 149 | } 150 | -------------------------------------------------------------------------------- /privaterr_test.go: -------------------------------------------------------------------------------- 1 | package dns_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | const TypeISBN uint16 = 0xFF00 11 | 12 | // A crazy new RR type :) 13 | type ISBN struct { 14 | x string // rdata with 10 or 13 numbers, dashes or spaces allowed 15 | } 16 | 17 | func NewISBN() dns.PrivateRdata { return &ISBN{""} } 18 | 19 | func (rd *ISBN) Len() int { return len([]byte(rd.x)) } 20 | func (rd *ISBN) String() string { return rd.x } 21 | 22 | func (rd *ISBN) Parse(txt []string) error { 23 | rd.x = strings.TrimSpace(strings.Join(txt, " ")) 24 | return nil 25 | } 26 | 27 | func (rd *ISBN) Pack(buf []byte) (int, error) { 28 | b := []byte(rd.x) 29 | n := copy(buf, b) 30 | if n != len(b) { 31 | return n, dns.ErrBuf 32 | } 33 | return n, nil 34 | } 35 | 36 | func (rd *ISBN) Unpack(buf []byte) (int, error) { 37 | rd.x = string(buf) 38 | return len(buf), nil 39 | } 40 | 41 | func (rd *ISBN) Copy(dest dns.PrivateRdata) error { 42 | isbn, ok := dest.(*ISBN) 43 | if !ok { 44 | return dns.ErrRdata 45 | } 46 | isbn.x = rd.x 47 | return nil 48 | } 49 | 50 | var testrecord = strings.Join([]string{"example.org.", "3600", "IN", "ISBN", "12-3 456789-0-123"}, "\t") 51 | 52 | func TestPrivateText(t *testing.T) { 53 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 54 | defer dns.PrivateHandleRemove(TypeISBN) 55 | 56 | rr, err := dns.NewRR(testrecord) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if rr.String() != testrecord { 61 | t.Errorf("record string representation did not match original %#v != %#v", rr.String(), testrecord) 62 | } else { 63 | t.Log(rr.String()) 64 | } 65 | } 66 | 67 | func TestPrivateByteSlice(t *testing.T) { 68 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 69 | defer dns.PrivateHandleRemove(TypeISBN) 70 | 71 | rr, err := dns.NewRR(testrecord) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | buf := make([]byte, 100) 77 | off, err := dns.PackRR(rr, buf, 0, nil, false) 78 | if err != nil { 79 | t.Errorf("got error packing ISBN: %v", err) 80 | } 81 | 82 | custrr := rr.(*dns.PrivateRR) 83 | if ln := custrr.Data.Len() + len(custrr.Header().Name) + 11; ln != off { 84 | t.Errorf("offset is not matching to length of Private RR: %d!=%d", off, ln) 85 | } 86 | 87 | rr1, off1, err := dns.UnpackRR(buf[:off], 0) 88 | if err != nil { 89 | t.Errorf("got error unpacking ISBN: %v", err) 90 | return 91 | } 92 | 93 | if off1 != off { 94 | t.Errorf("offset after unpacking differs: %d != %d", off1, off) 95 | } 96 | 97 | if rr1.String() != testrecord { 98 | t.Errorf("record string representation did not match original %#v != %#v", rr1.String(), testrecord) 99 | } else { 100 | t.Log(rr1.String()) 101 | } 102 | } 103 | 104 | const TypeVERSION uint16 = 0xFF01 105 | 106 | type VERSION struct { 107 | x string 108 | } 109 | 110 | func NewVersion() dns.PrivateRdata { return &VERSION{""} } 111 | 112 | func (rd *VERSION) String() string { return rd.x } 113 | func (rd *VERSION) Parse(txt []string) error { 114 | rd.x = strings.TrimSpace(strings.Join(txt, " ")) 115 | return nil 116 | } 117 | 118 | func (rd *VERSION) Pack(buf []byte) (int, error) { 119 | b := []byte(rd.x) 120 | n := copy(buf, b) 121 | if n != len(b) { 122 | return n, dns.ErrBuf 123 | } 124 | return n, nil 125 | } 126 | 127 | func (rd *VERSION) Unpack(buf []byte) (int, error) { 128 | rd.x = string(buf) 129 | return len(buf), nil 130 | } 131 | 132 | func (rd *VERSION) Copy(dest dns.PrivateRdata) error { 133 | isbn, ok := dest.(*VERSION) 134 | if !ok { 135 | return dns.ErrRdata 136 | } 137 | isbn.x = rd.x 138 | return nil 139 | } 140 | 141 | func (rd *VERSION) Len() int { 142 | return len([]byte(rd.x)) 143 | } 144 | 145 | var smallzone = `$ORIGIN example.org. 146 | @ SOA sns.dns.icann.org. noc.dns.icann.org. ( 147 | 2014091518 7200 3600 1209600 3600 148 | ) 149 | A 1.2.3.4 150 | ok ISBN 1231-92110-12 151 | go VERSION ( 152 | 1.3.1 ; comment 153 | ) 154 | www ISBN 1231-92110-16 155 | * CNAME @ 156 | ` 157 | 158 | func TestPrivateZoneParser(t *testing.T) { 159 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 160 | dns.PrivateHandle("VERSION", TypeVERSION, NewVersion) 161 | defer dns.PrivateHandleRemove(TypeISBN) 162 | defer dns.PrivateHandleRemove(TypeVERSION) 163 | 164 | r := strings.NewReader(smallzone) 165 | for x := range dns.ParseZone(r, ".", "") { 166 | if err := x.Error; err != nil { 167 | t.Fatal(err) 168 | } 169 | t.Log(x.RR) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /rawmsg.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "encoding/binary" 4 | 5 | // rawSetRdlength sets the rdlength in the header of 6 | // the RR. The offset 'off' must be positioned at the 7 | // start of the header of the RR, 'end' must be the 8 | // end of the RR. 9 | func rawSetRdlength(msg []byte, off, end int) bool { 10 | l := len(msg) 11 | Loop: 12 | for { 13 | if off+1 > l { 14 | return false 15 | } 16 | c := int(msg[off]) 17 | off++ 18 | switch c & 0xC0 { 19 | case 0x00: 20 | if c == 0x00 { 21 | // End of the domainname 22 | break Loop 23 | } 24 | if off+c > l { 25 | return false 26 | } 27 | off += c 28 | 29 | case 0xC0: 30 | // pointer, next byte included, ends domainname 31 | off++ 32 | break Loop 33 | } 34 | } 35 | // The domainname has been seen, we at the start of the fixed part in the header. 36 | // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. 37 | off += 2 + 2 + 4 38 | if off+2 > l { 39 | return false 40 | } 41 | //off+1 is the end of the header, 'end' is the end of the rr 42 | //so 'end' - 'off+2' is the length of the rdata 43 | rdatalen := end - (off + 2) 44 | if rdatalen > 0xFFFF { 45 | return false 46 | } 47 | binary.BigEndian.PutUint16(msg[off:], uint16(rdatalen)) 48 | return true 49 | } 50 | -------------------------------------------------------------------------------- /remote_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | const LinodeAddr = "176.58.119.54:53" 6 | 7 | func TestClientRemote(t *testing.T) { 8 | m := new(Msg) 9 | m.SetQuestion("go.dns.miek.nl.", TypeTXT) 10 | 11 | c := new(Client) 12 | r, _, err := c.Exchange(m, LinodeAddr) 13 | if err != nil { 14 | t.Errorf("failed to exchange: %v", err) 15 | } 16 | if r != nil && r.Rcode != RcodeSuccess { 17 | t.Errorf("failed to get an valid answer\n%v", r) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /reverse.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // StringToType is the reverse of TypeToString, needed for string parsing. 4 | var StringToType = reverseInt16(TypeToString) 5 | 6 | // StringToClass is the reverse of ClassToString, needed for string parsing. 7 | var StringToClass = reverseInt16(ClassToString) 8 | 9 | // StringToOpcode is a map of opcodes to strings. 10 | var StringToOpcode = reverseInt(OpcodeToString) 11 | 12 | // StringToRcode is a map of rcodes to strings. 13 | var StringToRcode = reverseInt(RcodeToString) 14 | 15 | // Reverse a map 16 | func reverseInt8(m map[uint8]string) map[string]uint8 { 17 | n := make(map[string]uint8, len(m)) 18 | for u, s := range m { 19 | n[s] = u 20 | } 21 | return n 22 | } 23 | 24 | func reverseInt16(m map[uint16]string) map[string]uint16 { 25 | n := make(map[string]uint16, len(m)) 26 | for u, s := range m { 27 | n[s] = u 28 | } 29 | return n 30 | } 31 | 32 | func reverseInt(m map[int]string) map[string]int { 33 | n := make(map[string]int, len(m)) 34 | for u, s := range m { 35 | n[s] = u 36 | } 37 | return n 38 | } 39 | -------------------------------------------------------------------------------- /sanitize.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Dedup removes identical RRs from rrs. It preserves the original ordering. 4 | // The lowest TTL of any duplicates is used in the remaining one. Dedup modifies 5 | // rrs. 6 | // m is used to store the RRs temporay. If it is nil a new map will be allocated. 7 | func Dedup(rrs []RR, m map[string]RR) []RR { 8 | if m == nil { 9 | m = make(map[string]RR) 10 | } 11 | // Save the keys, so we don't have to call normalizedString twice. 12 | keys := make([]*string, 0, len(rrs)) 13 | 14 | for _, r := range rrs { 15 | key := normalizedString(r) 16 | keys = append(keys, &key) 17 | if _, ok := m[key]; ok { 18 | // Shortest TTL wins. 19 | if m[key].Header().Ttl > r.Header().Ttl { 20 | m[key].Header().Ttl = r.Header().Ttl 21 | } 22 | continue 23 | } 24 | 25 | m[key] = r 26 | } 27 | // If the length of the result map equals the amount of RRs we got, 28 | // it means they were all different. We can then just return the original rrset. 29 | if len(m) == len(rrs) { 30 | return rrs 31 | } 32 | 33 | j := 0 34 | for i, r := range rrs { 35 | // If keys[i] lives in the map, we should copy and remove it. 36 | if _, ok := m[*keys[i]]; ok { 37 | delete(m, *keys[i]) 38 | rrs[j] = r 39 | j++ 40 | } 41 | 42 | if len(m) == 0 { 43 | break 44 | } 45 | } 46 | 47 | return rrs[:j] 48 | } 49 | 50 | // normalizedString returns a normalized string from r. The TTL 51 | // is removed and the domain name is lowercased. We go from this: 52 | // DomainNameTTLCLASSTYPERDATA to: 53 | // lowercasenameCLASSTYPE... 54 | func normalizedString(r RR) string { 55 | // A string Go DNS makes has: domainnameTTL... 56 | b := []byte(r.String()) 57 | 58 | // find the first non-escaped tab, then another, so we capture where the TTL lives. 59 | esc := false 60 | ttlStart, ttlEnd := 0, 0 61 | for i := 0; i < len(b) && ttlEnd == 0; i++ { 62 | switch { 63 | case b[i] == '\\': 64 | esc = !esc 65 | case b[i] == '\t' && !esc: 66 | if ttlStart == 0 { 67 | ttlStart = i 68 | continue 69 | } 70 | if ttlEnd == 0 { 71 | ttlEnd = i 72 | } 73 | case b[i] >= 'A' && b[i] <= 'Z' && !esc: 74 | b[i] += 32 75 | default: 76 | esc = false 77 | } 78 | } 79 | 80 | // remove TTL. 81 | copy(b[ttlStart:], b[ttlEnd:]) 82 | cut := ttlEnd - ttlStart 83 | return string(b[:len(b)-cut]) 84 | } 85 | -------------------------------------------------------------------------------- /sanitize_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestDedup(t *testing.T) { 6 | // make it []string 7 | testcases := map[[3]RR][]string{ 8 | [...]RR{ 9 | newRR(t, "mIek.nl. IN A 127.0.0.1"), 10 | newRR(t, "mieK.nl. IN A 127.0.0.1"), 11 | newRR(t, "miek.Nl. IN A 127.0.0.1"), 12 | }: {"mIek.nl.\t3600\tIN\tA\t127.0.0.1"}, 13 | [...]RR{ 14 | newRR(t, "miEk.nl. 2000 IN A 127.0.0.1"), 15 | newRR(t, "mieK.Nl. 1000 IN A 127.0.0.1"), 16 | newRR(t, "Miek.nL. 500 IN A 127.0.0.1"), 17 | }: {"miEk.nl.\t500\tIN\tA\t127.0.0.1"}, 18 | [...]RR{ 19 | newRR(t, "miek.nl. IN A 127.0.0.1"), 20 | newRR(t, "miek.nl. CH A 127.0.0.1"), 21 | newRR(t, "miek.nl. IN A 127.0.0.1"), 22 | }: {"miek.nl.\t3600\tIN\tA\t127.0.0.1", 23 | "miek.nl.\t3600\tCH\tA\t127.0.0.1", 24 | }, 25 | [...]RR{ 26 | newRR(t, "miek.nl. CH A 127.0.0.1"), 27 | newRR(t, "miek.nl. IN A 127.0.0.1"), 28 | newRR(t, "miek.de. IN A 127.0.0.1"), 29 | }: {"miek.nl.\t3600\tCH\tA\t127.0.0.1", 30 | "miek.nl.\t3600\tIN\tA\t127.0.0.1", 31 | "miek.de.\t3600\tIN\tA\t127.0.0.1", 32 | }, 33 | [...]RR{ 34 | newRR(t, "miek.de. IN A 127.0.0.1"), 35 | newRR(t, "miek.nl. 200 IN A 127.0.0.1"), 36 | newRR(t, "miek.nl. 300 IN A 127.0.0.1"), 37 | }: {"miek.de.\t3600\tIN\tA\t127.0.0.1", 38 | "miek.nl.\t200\tIN\tA\t127.0.0.1", 39 | }, 40 | } 41 | 42 | for rr, expected := range testcases { 43 | out := Dedup([]RR{rr[0], rr[1], rr[2]}, nil) 44 | for i, o := range out { 45 | if o.String() != expected[i] { 46 | t.Fatalf("expected %v, got %v", expected[i], o.String()) 47 | } 48 | } 49 | } 50 | } 51 | 52 | func BenchmarkDedup(b *testing.B) { 53 | rrs := []RR{ 54 | newRR(nil, "miEk.nl. 2000 IN A 127.0.0.1"), 55 | newRR(nil, "mieK.Nl. 1000 IN A 127.0.0.1"), 56 | newRR(nil, "Miek.nL. 500 IN A 127.0.0.1"), 57 | } 58 | m := make(map[string]RR) 59 | for i := 0; i < b.N; i++ { 60 | Dedup(rrs, m) 61 | } 62 | } 63 | 64 | func TestNormalizedString(t *testing.T) { 65 | tests := map[RR]string{ 66 | newRR(t, "mIEk.Nl. 3600 IN A 127.0.0.1"): "miek.nl.\tIN\tA\t127.0.0.1", 67 | newRR(t, "m\\ iek.nL. 3600 IN A 127.0.0.1"): "m\\ iek.nl.\tIN\tA\t127.0.0.1", 68 | newRR(t, "m\\\tIeK.nl. 3600 in A 127.0.0.1"): "m\\009iek.nl.\tIN\tA\t127.0.0.1", 69 | } 70 | for tc, expected := range tests { 71 | n := normalizedString(tc) 72 | if n != expected { 73 | t.Errorf("expected %s, got %s", expected, n) 74 | } 75 | } 76 | } 77 | 78 | func newRR(t *testing.T, s string) RR { 79 | r, err := NewRR(s) 80 | if err != nil { 81 | t.Logf("newRR: %v", err) 82 | } 83 | return r 84 | } 85 | -------------------------------------------------------------------------------- /scan_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestParseZoneInclude(t *testing.T) { 11 | 12 | tmpfile, err := ioutil.TempFile("", "dns") 13 | if err != nil { 14 | t.Fatalf("could not create tmpfile for test: %s", err) 15 | } 16 | 17 | if _, err := tmpfile.WriteString("foo\tIN\tA\t127.0.0.1"); err != nil { 18 | t.Fatalf("unable to write content to tmpfile %q: %s", tmpfile.Name(), err) 19 | } 20 | if err := tmpfile.Close(); err != nil { 21 | t.Fatalf("could not close tmpfile %q: %s", tmpfile.Name(), err) 22 | } 23 | 24 | zone := "$INCLUDE " + tmpfile.Name() 25 | 26 | tok := ParseZone(strings.NewReader(zone), "", "") 27 | for x := range tok { 28 | if x.Error != nil { 29 | t.Fatalf("expected no error, but got %s", x.Error) 30 | } 31 | } 32 | 33 | os.Remove(tmpfile.Name()) 34 | 35 | tok = ParseZone(strings.NewReader(zone), "", "") 36 | for x := range tok { 37 | if x.Error == nil { 38 | t.Fatalf("expected first token to contain an error but it didn't") 39 | } 40 | if !strings.Contains(x.Error.Error(), "failed to open") || 41 | !strings.Contains(x.Error.Error(), tmpfile.Name()) { 42 | t.Fatalf(`expected error to contain: "failed to open" and %q but got: %s`, tmpfile.Name(), x.Error) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Implement a simple scanner, return a byte stream from an io reader. 4 | 5 | import ( 6 | "bufio" 7 | "io" 8 | "text/scanner" 9 | ) 10 | 11 | type scan struct { 12 | src *bufio.Reader 13 | position scanner.Position 14 | eof bool // Have we just seen a eof 15 | } 16 | 17 | func scanInit(r io.Reader) *scan { 18 | s := new(scan) 19 | s.src = bufio.NewReader(r) 20 | s.position.Line = 1 21 | return s 22 | } 23 | 24 | // tokenText returns the next byte from the input 25 | func (s *scan) tokenText() (byte, error) { 26 | c, err := s.src.ReadByte() 27 | if err != nil { 28 | return c, err 29 | } 30 | // delay the newline handling until the next token is delivered, 31 | // fixes off-by-one errors when reporting a parse error. 32 | if s.eof == true { 33 | s.position.Line++ 34 | s.position.Column = 0 35 | s.eof = false 36 | } 37 | if c == '\n' { 38 | s.eof = true 39 | return c, nil 40 | } 41 | s.position.Column++ 42 | return c, nil 43 | } 44 | -------------------------------------------------------------------------------- /sig0.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "encoding/binary" 9 | "math/big" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // Sign signs a dns.Msg. It fills the signature with the appropriate data. 15 | // The SIG record should have the SignerName, KeyTag, Algorithm, Inception 16 | // and Expiration set. 17 | func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { 18 | if k == nil { 19 | return nil, ErrPrivKey 20 | } 21 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 22 | return nil, ErrKey 23 | } 24 | rr.Header().Rrtype = TypeSIG 25 | rr.Header().Class = ClassANY 26 | rr.Header().Ttl = 0 27 | rr.Header().Name = "." 28 | rr.OrigTtl = 0 29 | rr.TypeCovered = 0 30 | rr.Labels = 0 31 | 32 | buf := make([]byte, m.Len()+rr.len()) 33 | mbuf, err := m.PackBuffer(buf) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if &buf[0] != &mbuf[0] { 38 | return nil, ErrBuf 39 | } 40 | off, err := PackRR(rr, buf, len(mbuf), nil, false) 41 | if err != nil { 42 | return nil, err 43 | } 44 | buf = buf[:off:cap(buf)] 45 | 46 | hash, ok := AlgorithmToHash[rr.Algorithm] 47 | if !ok { 48 | return nil, ErrAlg 49 | } 50 | 51 | hasher := hash.New() 52 | // Write SIG rdata 53 | hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) 54 | // Write message 55 | hasher.Write(buf[:len(mbuf)]) 56 | 57 | signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | rr.Signature = toBase64(signature) 63 | 64 | buf = append(buf, signature...) 65 | if len(buf) > int(^uint16(0)) { 66 | return nil, ErrBuf 67 | } 68 | // Adjust sig data length 69 | rdoff := len(mbuf) + 1 + 2 + 2 + 4 70 | rdlen := binary.BigEndian.Uint16(buf[rdoff:]) 71 | rdlen += uint16(len(signature)) 72 | binary.BigEndian.PutUint16(buf[rdoff:], rdlen) 73 | // Adjust additional count 74 | adc := binary.BigEndian.Uint16(buf[10:]) 75 | adc++ 76 | binary.BigEndian.PutUint16(buf[10:], adc) 77 | return buf, nil 78 | } 79 | 80 | // Verify validates the message buf using the key k. 81 | // It's assumed that buf is a valid message from which rr was unpacked. 82 | func (rr *SIG) Verify(k *KEY, buf []byte) error { 83 | if k == nil { 84 | return ErrKey 85 | } 86 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 87 | return ErrKey 88 | } 89 | 90 | var hash crypto.Hash 91 | switch rr.Algorithm { 92 | case DSA, RSASHA1: 93 | hash = crypto.SHA1 94 | case RSASHA256, ECDSAP256SHA256: 95 | hash = crypto.SHA256 96 | case ECDSAP384SHA384: 97 | hash = crypto.SHA384 98 | case RSASHA512: 99 | hash = crypto.SHA512 100 | default: 101 | return ErrAlg 102 | } 103 | hasher := hash.New() 104 | 105 | buflen := len(buf) 106 | qdc := binary.BigEndian.Uint16(buf[4:]) 107 | anc := binary.BigEndian.Uint16(buf[6:]) 108 | auc := binary.BigEndian.Uint16(buf[8:]) 109 | adc := binary.BigEndian.Uint16(buf[10:]) 110 | offset := 12 111 | var err error 112 | for i := uint16(0); i < qdc && offset < buflen; i++ { 113 | _, offset, err = UnpackDomainName(buf, offset) 114 | if err != nil { 115 | return err 116 | } 117 | // Skip past Type and Class 118 | offset += 2 + 2 119 | } 120 | for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { 121 | _, offset, err = UnpackDomainName(buf, offset) 122 | if err != nil { 123 | return err 124 | } 125 | // Skip past Type, Class and TTL 126 | offset += 2 + 2 + 4 127 | if offset+1 >= buflen { 128 | continue 129 | } 130 | var rdlen uint16 131 | rdlen = binary.BigEndian.Uint16(buf[offset:]) 132 | offset += 2 133 | offset += int(rdlen) 134 | } 135 | if offset >= buflen { 136 | return &Error{err: "overflowing unpacking signed message"} 137 | } 138 | 139 | // offset should be just prior to SIG 140 | bodyend := offset 141 | // owner name SHOULD be root 142 | _, offset, err = UnpackDomainName(buf, offset) 143 | if err != nil { 144 | return err 145 | } 146 | // Skip Type, Class, TTL, RDLen 147 | offset += 2 + 2 + 4 + 2 148 | sigstart := offset 149 | // Skip Type Covered, Algorithm, Labels, Original TTL 150 | offset += 2 + 1 + 1 + 4 151 | if offset+4+4 >= buflen { 152 | return &Error{err: "overflow unpacking signed message"} 153 | } 154 | expire := binary.BigEndian.Uint32(buf[offset:]) 155 | offset += 4 156 | incept := binary.BigEndian.Uint32(buf[offset:]) 157 | offset += 4 158 | now := uint32(time.Now().Unix()) 159 | if now < incept || now > expire { 160 | return ErrTime 161 | } 162 | // Skip key tag 163 | offset += 2 164 | var signername string 165 | signername, offset, err = UnpackDomainName(buf, offset) 166 | if err != nil { 167 | return err 168 | } 169 | // If key has come from the DNS name compression might 170 | // have mangled the case of the name 171 | if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { 172 | return &Error{err: "signer name doesn't match key name"} 173 | } 174 | sigend := offset 175 | hasher.Write(buf[sigstart:sigend]) 176 | hasher.Write(buf[:10]) 177 | hasher.Write([]byte{ 178 | byte((adc - 1) << 8), 179 | byte(adc - 1), 180 | }) 181 | hasher.Write(buf[12:bodyend]) 182 | 183 | hashed := hasher.Sum(nil) 184 | sig := buf[sigend:] 185 | switch k.Algorithm { 186 | case DSA: 187 | pk := k.publicKeyDSA() 188 | sig = sig[1:] 189 | r := big.NewInt(0) 190 | r.SetBytes(sig[:len(sig)/2]) 191 | s := big.NewInt(0) 192 | s.SetBytes(sig[len(sig)/2:]) 193 | if pk != nil { 194 | if dsa.Verify(pk, hashed, r, s) { 195 | return nil 196 | } 197 | return ErrSig 198 | } 199 | case RSASHA1, RSASHA256, RSASHA512: 200 | pk := k.publicKeyRSA() 201 | if pk != nil { 202 | return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) 203 | } 204 | case ECDSAP256SHA256, ECDSAP384SHA384: 205 | pk := k.publicKeyECDSA() 206 | r := big.NewInt(0) 207 | r.SetBytes(sig[:len(sig)/2]) 208 | s := big.NewInt(0) 209 | s.SetBytes(sig[len(sig)/2:]) 210 | if pk != nil { 211 | if ecdsa.Verify(pk, hashed, r, s) { 212 | return nil 213 | } 214 | return ErrSig 215 | } 216 | } 217 | return ErrKeyAlg 218 | } 219 | -------------------------------------------------------------------------------- /sig0_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestSIG0(t *testing.T) { 10 | if testing.Short() { 11 | t.Skip("skipping test in short mode.") 12 | } 13 | m := new(Msg) 14 | m.SetQuestion("example.org.", TypeSOA) 15 | for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} { 16 | algstr := AlgorithmToString[alg] 17 | keyrr := new(KEY) 18 | keyrr.Hdr.Name = algstr + "." 19 | keyrr.Hdr.Rrtype = TypeKEY 20 | keyrr.Hdr.Class = ClassINET 21 | keyrr.Algorithm = alg 22 | keysize := 1024 23 | switch alg { 24 | case ECDSAP256SHA256: 25 | keysize = 256 26 | case ECDSAP384SHA384: 27 | keysize = 384 28 | } 29 | pk, err := keyrr.Generate(keysize) 30 | if err != nil { 31 | t.Errorf("failed to generate key for “%s”: %v", algstr, err) 32 | continue 33 | } 34 | now := uint32(time.Now().Unix()) 35 | sigrr := new(SIG) 36 | sigrr.Hdr.Name = "." 37 | sigrr.Hdr.Rrtype = TypeSIG 38 | sigrr.Hdr.Class = ClassANY 39 | sigrr.Algorithm = alg 40 | sigrr.Expiration = now + 300 41 | sigrr.Inception = now - 300 42 | sigrr.KeyTag = keyrr.KeyTag() 43 | sigrr.SignerName = keyrr.Hdr.Name 44 | mb, err := sigrr.Sign(pk.(crypto.Signer), m) 45 | if err != nil { 46 | t.Errorf("failed to sign message using “%s”: %v", algstr, err) 47 | continue 48 | } 49 | m := new(Msg) 50 | if err := m.Unpack(mb); err != nil { 51 | t.Errorf("failed to unpack message signed using “%s”: %v", algstr, err) 52 | continue 53 | } 54 | if len(m.Extra) != 1 { 55 | t.Errorf("missing SIG for message signed using “%s”", algstr) 56 | continue 57 | } 58 | var sigrrwire *SIG 59 | switch rr := m.Extra[0].(type) { 60 | case *SIG: 61 | sigrrwire = rr 62 | default: 63 | t.Errorf("expected SIG RR, instead: %v", rr) 64 | continue 65 | } 66 | for _, rr := range []*SIG{sigrr, sigrrwire} { 67 | id := "sigrr" 68 | if rr == sigrrwire { 69 | id = "sigrrwire" 70 | } 71 | if err := rr.Verify(keyrr, mb); err != nil { 72 | t.Errorf("failed to verify “%s” signed SIG(%s): %v", algstr, id, err) 73 | continue 74 | } 75 | } 76 | mb[13]++ 77 | if err := sigrr.Verify(keyrr, mb); err == nil { 78 | t.Errorf("verify succeeded on an altered message using “%s”", algstr) 79 | continue 80 | } 81 | sigrr.Expiration = 2 82 | sigrr.Inception = 1 83 | mb, _ = sigrr.Sign(pk.(crypto.Signer), m) 84 | if err := sigrr.Verify(keyrr, mb); err == nil { 85 | t.Errorf("verify succeeded on an expired message using “%s”", algstr) 86 | continue 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /singleinflight.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Adapted for dns package usage by Miek Gieben. 6 | 7 | package dns 8 | 9 | import "sync" 10 | import "time" 11 | 12 | // call is an in-flight or completed singleflight.Do call 13 | type call struct { 14 | wg sync.WaitGroup 15 | val *Msg 16 | rtt time.Duration 17 | err error 18 | dups int 19 | } 20 | 21 | // singleflight represents a class of work and forms a namespace in 22 | // which units of work can be executed with duplicate suppression. 23 | type singleflight struct { 24 | sync.Mutex // protects m 25 | m map[string]*call // lazily initialized 26 | } 27 | 28 | // Do executes and returns the results of the given function, making 29 | // sure that only one execution is in-flight for a given key at a 30 | // time. If a duplicate comes in, the duplicate caller waits for the 31 | // original to complete and receives the same results. 32 | // The return value shared indicates whether v was given to multiple callers. 33 | func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { 34 | g.Lock() 35 | if g.m == nil { 36 | g.m = make(map[string]*call) 37 | } 38 | if c, ok := g.m[key]; ok { 39 | c.dups++ 40 | g.Unlock() 41 | c.wg.Wait() 42 | return c.val, c.rtt, c.err, true 43 | } 44 | c := new(call) 45 | c.wg.Add(1) 46 | g.m[key] = c 47 | g.Unlock() 48 | 49 | c.val, c.rtt, c.err = fn() 50 | c.wg.Done() 51 | 52 | g.Lock() 53 | delete(g.m, key) 54 | g.Unlock() 55 | 56 | return c.val, c.rtt, c.err, c.dups > 0 57 | } 58 | -------------------------------------------------------------------------------- /smimea.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/x509" 6 | "encoding/hex" 7 | ) 8 | 9 | // Sign creates a SMIMEA record from an SSL certificate. 10 | func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { 11 | r.Hdr.Rrtype = TypeSMIMEA 12 | r.Usage = uint8(usage) 13 | r.Selector = uint8(selector) 14 | r.MatchingType = uint8(matchingType) 15 | 16 | r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) 17 | if err != nil { 18 | return err 19 | } 20 | return nil 21 | } 22 | 23 | // Verify verifies a SMIMEA record against an SSL certificate. If it is OK 24 | // a nil error is returned. 25 | func (r *SMIMEA) Verify(cert *x509.Certificate) error { 26 | c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) 27 | if err != nil { 28 | return err // Not also ErrSig? 29 | } 30 | if r.Certificate == c { 31 | return nil 32 | } 33 | return ErrSig // ErrSig, really? 34 | } 35 | 36 | // SMIMEAName returns the ownername of a SMIMEA resource record as per the 37 | // format specified in RFC 'draft-ietf-dane-smime-12' Section 2 and 3 38 | func SMIMEAName(email, domain string) (string, error) { 39 | hasher := sha256.New() 40 | hasher.Write([]byte(email)) 41 | 42 | // RFC Section 3: "The local-part is hashed using the SHA2-256 43 | // algorithm with the hash truncated to 28 octets and 44 | // represented in its hexadecimal representation to become the 45 | // left-most label in the prepared domain name" 46 | return hex.EncodeToString(hasher.Sum(nil)[:28]) + "." + "_smimecert." + domain, nil 47 | } 48 | -------------------------------------------------------------------------------- /tlsa.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/x509" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | // Sign creates a TLSA record from an SSL certificate. 10 | func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { 11 | r.Hdr.Rrtype = TypeTLSA 12 | r.Usage = uint8(usage) 13 | r.Selector = uint8(selector) 14 | r.MatchingType = uint8(matchingType) 15 | 16 | r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) 17 | if err != nil { 18 | return err 19 | } 20 | return nil 21 | } 22 | 23 | // Verify verifies a TLSA record against an SSL certificate. If it is OK 24 | // a nil error is returned. 25 | func (r *TLSA) Verify(cert *x509.Certificate) error { 26 | c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) 27 | if err != nil { 28 | return err // Not also ErrSig? 29 | } 30 | if r.Certificate == c { 31 | return nil 32 | } 33 | return ErrSig // ErrSig, really? 34 | } 35 | 36 | // TLSAName returns the ownername of a TLSA resource record as per the 37 | // rules specified in RFC 6698, Section 3. 38 | func TLSAName(name, service, network string) (string, error) { 39 | if !IsFqdn(name) { 40 | return "", ErrFqdn 41 | } 42 | p, err := net.LookupPort(network, service) 43 | if err != nil { 44 | return "", err 45 | } 46 | return "_" + strconv.Itoa(p) + "._" + network + "." + name, nil 47 | } 48 | -------------------------------------------------------------------------------- /tsig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "hash" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // HMAC hashing codes. These are transmitted as domain names. 18 | const ( 19 | HmacMD5 = "hmac-md5.sig-alg.reg.int." 20 | HmacSHA1 = "hmac-sha1." 21 | HmacSHA256 = "hmac-sha256." 22 | HmacSHA512 = "hmac-sha512." 23 | ) 24 | 25 | // TSIG is the RR the holds the transaction signature of a message. 26 | // See RFC 2845 and RFC 4635. 27 | type TSIG struct { 28 | Hdr RR_Header 29 | Algorithm string `dns:"domain-name"` 30 | TimeSigned uint64 `dns:"uint48"` 31 | Fudge uint16 32 | MACSize uint16 33 | MAC string `dns:"size-hex:MACSize"` 34 | OrigId uint16 35 | Error uint16 36 | OtherLen uint16 37 | OtherData string `dns:"size-hex:OtherLen"` 38 | } 39 | 40 | // TSIG has no official presentation format, but this will suffice. 41 | 42 | func (rr *TSIG) String() string { 43 | s := "\n;; TSIG PSEUDOSECTION:\n" 44 | s += rr.Hdr.String() + 45 | " " + rr.Algorithm + 46 | " " + tsigTimeToString(rr.TimeSigned) + 47 | " " + strconv.Itoa(int(rr.Fudge)) + 48 | " " + strconv.Itoa(int(rr.MACSize)) + 49 | " " + strings.ToUpper(rr.MAC) + 50 | " " + strconv.Itoa(int(rr.OrigId)) + 51 | " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR 52 | " " + strconv.Itoa(int(rr.OtherLen)) + 53 | " " + rr.OtherData 54 | return s 55 | } 56 | 57 | // The following values must be put in wireformat, so that the MAC can be calculated. 58 | // RFC 2845, section 3.4.2. TSIG Variables. 59 | type tsigWireFmt struct { 60 | // From RR_Header 61 | Name string `dns:"domain-name"` 62 | Class uint16 63 | Ttl uint32 64 | // Rdata of the TSIG 65 | Algorithm string `dns:"domain-name"` 66 | TimeSigned uint64 `dns:"uint48"` 67 | Fudge uint16 68 | // MACSize, MAC and OrigId excluded 69 | Error uint16 70 | OtherLen uint16 71 | OtherData string `dns:"size-hex:OtherLen"` 72 | } 73 | 74 | // If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC 75 | type macWireFmt struct { 76 | MACSize uint16 77 | MAC string `dns:"size-hex:MACSize"` 78 | } 79 | 80 | // 3.3. Time values used in TSIG calculations 81 | type timerWireFmt struct { 82 | TimeSigned uint64 `dns:"uint48"` 83 | Fudge uint16 84 | } 85 | 86 | // TsigGenerate fills out the TSIG record attached to the message. 87 | // The message should contain 88 | // a "stub" TSIG RR with the algorithm, key name (owner name of the RR), 89 | // time fudge (defaults to 300 seconds) and the current time 90 | // The TSIG MAC is saved in that Tsig RR. 91 | // When TsigGenerate is called for the first time requestMAC is set to the empty string and 92 | // timersOnly is false. 93 | // If something goes wrong an error is returned, otherwise it is nil. 94 | func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { 95 | if m.IsTsig() == nil { 96 | panic("dns: TSIG not last RR in additional") 97 | } 98 | // If we barf here, the caller is to blame 99 | rawsecret, err := fromBase64([]byte(secret)) 100 | if err != nil { 101 | return nil, "", err 102 | } 103 | 104 | rr := m.Extra[len(m.Extra)-1].(*TSIG) 105 | m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg 106 | mbuf, err := m.Pack() 107 | if err != nil { 108 | return nil, "", err 109 | } 110 | buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) 111 | 112 | t := new(TSIG) 113 | var h hash.Hash 114 | switch strings.ToLower(rr.Algorithm) { 115 | case HmacMD5: 116 | h = hmac.New(md5.New, []byte(rawsecret)) 117 | case HmacSHA1: 118 | h = hmac.New(sha1.New, []byte(rawsecret)) 119 | case HmacSHA256: 120 | h = hmac.New(sha256.New, []byte(rawsecret)) 121 | case HmacSHA512: 122 | h = hmac.New(sha512.New, []byte(rawsecret)) 123 | default: 124 | return nil, "", ErrKeyAlg 125 | } 126 | h.Write(buf) 127 | t.MAC = hex.EncodeToString(h.Sum(nil)) 128 | t.MACSize = uint16(len(t.MAC) / 2) // Size is half! 129 | 130 | t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} 131 | t.Fudge = rr.Fudge 132 | t.TimeSigned = rr.TimeSigned 133 | t.Algorithm = rr.Algorithm 134 | t.OrigId = m.Id 135 | 136 | tbuf := make([]byte, t.len()) 137 | if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { 138 | tbuf = tbuf[:off] // reset to actual size used 139 | } else { 140 | return nil, "", err 141 | } 142 | mbuf = append(mbuf, tbuf...) 143 | // Update the ArCount directly in the buffer. 144 | binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1)) 145 | 146 | return mbuf, t.MAC, nil 147 | } 148 | 149 | // TsigVerify verifies the TSIG on a message. 150 | // If the signature does not validate err contains the 151 | // error, otherwise it is nil. 152 | func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { 153 | rawsecret, err := fromBase64([]byte(secret)) 154 | if err != nil { 155 | return err 156 | } 157 | // Strip the TSIG from the incoming msg 158 | stripped, tsig, err := stripTsig(msg) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | msgMAC, err := hex.DecodeString(tsig.MAC) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) 169 | 170 | // Fudge factor works both ways. A message can arrive before it was signed because 171 | // of clock skew. 172 | now := uint64(time.Now().Unix()) 173 | ti := now - tsig.TimeSigned 174 | if now < tsig.TimeSigned { 175 | ti = tsig.TimeSigned - now 176 | } 177 | if uint64(tsig.Fudge) < ti { 178 | return ErrTime 179 | } 180 | 181 | var h hash.Hash 182 | switch strings.ToLower(tsig.Algorithm) { 183 | case HmacMD5: 184 | h = hmac.New(md5.New, rawsecret) 185 | case HmacSHA1: 186 | h = hmac.New(sha1.New, rawsecret) 187 | case HmacSHA256: 188 | h = hmac.New(sha256.New, rawsecret) 189 | case HmacSHA512: 190 | h = hmac.New(sha512.New, rawsecret) 191 | default: 192 | return ErrKeyAlg 193 | } 194 | h.Write(buf) 195 | if !hmac.Equal(h.Sum(nil), msgMAC) { 196 | return ErrSig 197 | } 198 | return nil 199 | } 200 | 201 | // Create a wiredata buffer for the MAC calculation. 202 | func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { 203 | var buf []byte 204 | if rr.TimeSigned == 0 { 205 | rr.TimeSigned = uint64(time.Now().Unix()) 206 | } 207 | if rr.Fudge == 0 { 208 | rr.Fudge = 300 // Standard (RFC) default. 209 | } 210 | 211 | // Replace message ID in header with original ID from TSIG 212 | binary.BigEndian.PutUint16(msgbuf[0:2], rr.OrigId) 213 | 214 | if requestMAC != "" { 215 | m := new(macWireFmt) 216 | m.MACSize = uint16(len(requestMAC) / 2) 217 | m.MAC = requestMAC 218 | buf = make([]byte, len(requestMAC)) // long enough 219 | n, _ := packMacWire(m, buf) 220 | buf = buf[:n] 221 | } 222 | 223 | tsigvar := make([]byte, DefaultMsgSize) 224 | if timersOnly { 225 | tsig := new(timerWireFmt) 226 | tsig.TimeSigned = rr.TimeSigned 227 | tsig.Fudge = rr.Fudge 228 | n, _ := packTimerWire(tsig, tsigvar) 229 | tsigvar = tsigvar[:n] 230 | } else { 231 | tsig := new(tsigWireFmt) 232 | tsig.Name = strings.ToLower(rr.Hdr.Name) 233 | tsig.Class = ClassANY 234 | tsig.Ttl = rr.Hdr.Ttl 235 | tsig.Algorithm = strings.ToLower(rr.Algorithm) 236 | tsig.TimeSigned = rr.TimeSigned 237 | tsig.Fudge = rr.Fudge 238 | tsig.Error = rr.Error 239 | tsig.OtherLen = rr.OtherLen 240 | tsig.OtherData = rr.OtherData 241 | n, _ := packTsigWire(tsig, tsigvar) 242 | tsigvar = tsigvar[:n] 243 | } 244 | 245 | if requestMAC != "" { 246 | x := append(buf, msgbuf...) 247 | buf = append(x, tsigvar...) 248 | } else { 249 | buf = append(msgbuf, tsigvar...) 250 | } 251 | return buf 252 | } 253 | 254 | // Strip the TSIG from the raw message. 255 | func stripTsig(msg []byte) ([]byte, *TSIG, error) { 256 | // Copied from msg.go's Unpack() Header, but modified. 257 | var ( 258 | dh Header 259 | err error 260 | ) 261 | off, tsigoff := 0, 0 262 | 263 | if dh, off, err = unpackMsgHdr(msg, off); err != nil { 264 | return nil, nil, err 265 | } 266 | if dh.Arcount == 0 { 267 | return nil, nil, ErrNoSig 268 | } 269 | 270 | // Rcode, see msg.go Unpack() 271 | if int(dh.Bits&0xF) == RcodeNotAuth { 272 | return nil, nil, ErrAuth 273 | } 274 | 275 | for i := 0; i < int(dh.Qdcount); i++ { 276 | _, off, err = unpackQuestion(msg, off) 277 | if err != nil { 278 | return nil, nil, err 279 | } 280 | } 281 | 282 | _, off, err = unpackRRslice(int(dh.Ancount), msg, off) 283 | if err != nil { 284 | return nil, nil, err 285 | } 286 | _, off, err = unpackRRslice(int(dh.Nscount), msg, off) 287 | if err != nil { 288 | return nil, nil, err 289 | } 290 | 291 | rr := new(TSIG) 292 | var extra RR 293 | for i := 0; i < int(dh.Arcount); i++ { 294 | tsigoff = off 295 | extra, off, err = UnpackRR(msg, off) 296 | if err != nil { 297 | return nil, nil, err 298 | } 299 | if extra.Header().Rrtype == TypeTSIG { 300 | rr = extra.(*TSIG) 301 | // Adjust Arcount. 302 | arcount := binary.BigEndian.Uint16(msg[10:]) 303 | binary.BigEndian.PutUint16(msg[10:], arcount-1) 304 | break 305 | } 306 | } 307 | if rr == nil { 308 | return nil, nil, ErrNoSig 309 | } 310 | return msg[:tsigoff], rr, nil 311 | } 312 | 313 | // Translate the TSIG time signed into a date. There is no 314 | // need for RFC1982 calculations as this date is 48 bits. 315 | func tsigTimeToString(t uint64) string { 316 | ti := time.Unix(int64(t), 0).UTC() 317 | return ti.Format("20060102150405") 318 | } 319 | 320 | func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) { 321 | // copied from zmsg.go TSIG packing 322 | // RR_Header 323 | off, err := PackDomainName(tw.Name, msg, 0, nil, false) 324 | if err != nil { 325 | return off, err 326 | } 327 | off, err = packUint16(tw.Class, msg, off) 328 | if err != nil { 329 | return off, err 330 | } 331 | off, err = packUint32(tw.Ttl, msg, off) 332 | if err != nil { 333 | return off, err 334 | } 335 | 336 | off, err = PackDomainName(tw.Algorithm, msg, off, nil, false) 337 | if err != nil { 338 | return off, err 339 | } 340 | off, err = packUint48(tw.TimeSigned, msg, off) 341 | if err != nil { 342 | return off, err 343 | } 344 | off, err = packUint16(tw.Fudge, msg, off) 345 | if err != nil { 346 | return off, err 347 | } 348 | 349 | off, err = packUint16(tw.Error, msg, off) 350 | if err != nil { 351 | return off, err 352 | } 353 | off, err = packUint16(tw.OtherLen, msg, off) 354 | if err != nil { 355 | return off, err 356 | } 357 | off, err = packStringHex(tw.OtherData, msg, off) 358 | if err != nil { 359 | return off, err 360 | } 361 | return off, nil 362 | } 363 | 364 | func packMacWire(mw *macWireFmt, msg []byte) (int, error) { 365 | off, err := packUint16(mw.MACSize, msg, 0) 366 | if err != nil { 367 | return off, err 368 | } 369 | off, err = packStringHex(mw.MAC, msg, off) 370 | if err != nil { 371 | return off, err 372 | } 373 | return off, nil 374 | } 375 | 376 | func packTimerWire(tw *timerWireFmt, msg []byte) (int, error) { 377 | off, err := packUint48(tw.TimeSigned, msg, 0) 378 | if err != nil { 379 | return off, err 380 | } 381 | off, err = packUint16(tw.Fudge, msg, off) 382 | if err != nil { 383 | return off, err 384 | } 385 | return off, nil 386 | } 387 | -------------------------------------------------------------------------------- /tsig_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func newTsig(algo string) *Msg { 10 | m := new(Msg) 11 | m.SetQuestion("example.org.", TypeA) 12 | m.SetTsig("example.", algo, 300, time.Now().Unix()) 13 | return m 14 | } 15 | 16 | func TestTsig(t *testing.T) { 17 | m := newTsig(HmacMD5) 18 | buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | // TSIG accounts for ID substitution. This means if the message ID is 28 | // changed by a forwarder, we should still be able to verify the TSIG. 29 | m = newTsig(HmacMD5) 30 | buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | binary.BigEndian.PutUint16(buf[0:2], uint16(42)) 36 | err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | } 41 | 42 | func TestTsigCase(t *testing.T) { 43 | m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5 44 | buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /types_generate.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | // types_generate.go is meant to run with go generate. It will use 4 | // go/{importer,types} to track down all the RR struct types. Then for each type 5 | // it will generate conversion tables (TypeToRR and TypeToString) and banal 6 | // methods (len, Header, copy) based on the struct tags. The generated source is 7 | // written to ztypes.go, and is meant to be checked into git. 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "go/format" 14 | "go/importer" 15 | "go/types" 16 | "log" 17 | "os" 18 | "strings" 19 | "text/template" 20 | ) 21 | 22 | var skipLen = map[string]struct{}{ 23 | "NSEC": {}, 24 | "NSEC3": {}, 25 | "OPT": {}, 26 | } 27 | 28 | var packageHdr = ` 29 | // *** DO NOT MODIFY *** 30 | // AUTOGENERATED BY go generate from type_generate.go 31 | 32 | package dns 33 | 34 | import ( 35 | "encoding/base64" 36 | "net" 37 | ) 38 | 39 | ` 40 | 41 | var TypeToRR = template.Must(template.New("TypeToRR").Parse(` 42 | // TypeToRR is a map of constructors for each RR type. 43 | var TypeToRR = map[uint16]func() RR{ 44 | {{range .}}{{if ne . "RFC3597"}} Type{{.}}: func() RR { return new({{.}}) }, 45 | {{end}}{{end}} } 46 | 47 | `)) 48 | 49 | var typeToString = template.Must(template.New("typeToString").Parse(` 50 | // TypeToString is a map of strings for each RR type. 51 | var TypeToString = map[uint16]string{ 52 | {{range .}}{{if ne . "NSAPPTR"}} Type{{.}}: "{{.}}", 53 | {{end}}{{end}} TypeNSAPPTR: "NSAP-PTR", 54 | } 55 | 56 | `)) 57 | 58 | var headerFunc = template.Must(template.New("headerFunc").Parse(` 59 | // Header() functions 60 | {{range .}} func (rr *{{.}}) Header() *RR_Header { return &rr.Hdr } 61 | {{end}} 62 | 63 | `)) 64 | 65 | // getTypeStruct will take a type and the package scope, and return the 66 | // (innermost) struct if the type is considered a RR type (currently defined as 67 | // those structs beginning with a RR_Header, could be redefined as implementing 68 | // the RR interface). The bool return value indicates if embedded structs were 69 | // resolved. 70 | func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { 71 | st, ok := t.Underlying().(*types.Struct) 72 | if !ok { 73 | return nil, false 74 | } 75 | if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { 76 | return st, false 77 | } 78 | if st.Field(0).Anonymous() { 79 | st, _ := getTypeStruct(st.Field(0).Type(), scope) 80 | return st, true 81 | } 82 | return nil, false 83 | } 84 | 85 | func main() { 86 | // Import and type-check the package 87 | pkg, err := importer.Default().Import("github.com/miekg/dns") 88 | fatalIfErr(err) 89 | scope := pkg.Scope() 90 | 91 | // Collect constants like TypeX 92 | var numberedTypes []string 93 | for _, name := range scope.Names() { 94 | o := scope.Lookup(name) 95 | if o == nil || !o.Exported() { 96 | continue 97 | } 98 | b, ok := o.Type().(*types.Basic) 99 | if !ok || b.Kind() != types.Uint16 { 100 | continue 101 | } 102 | if !strings.HasPrefix(o.Name(), "Type") { 103 | continue 104 | } 105 | name := strings.TrimPrefix(o.Name(), "Type") 106 | if name == "PrivateRR" { 107 | continue 108 | } 109 | numberedTypes = append(numberedTypes, name) 110 | } 111 | 112 | // Collect actual types (*X) 113 | var namedTypes []string 114 | for _, name := range scope.Names() { 115 | o := scope.Lookup(name) 116 | if o == nil || !o.Exported() { 117 | continue 118 | } 119 | if st, _ := getTypeStruct(o.Type(), scope); st == nil { 120 | continue 121 | } 122 | if name == "PrivateRR" { 123 | continue 124 | } 125 | 126 | // Check if corresponding TypeX exists 127 | if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { 128 | log.Fatalf("Constant Type%s does not exist.", o.Name()) 129 | } 130 | 131 | namedTypes = append(namedTypes, o.Name()) 132 | } 133 | 134 | b := &bytes.Buffer{} 135 | b.WriteString(packageHdr) 136 | 137 | // Generate TypeToRR 138 | fatalIfErr(TypeToRR.Execute(b, namedTypes)) 139 | 140 | // Generate typeToString 141 | fatalIfErr(typeToString.Execute(b, numberedTypes)) 142 | 143 | // Generate headerFunc 144 | fatalIfErr(headerFunc.Execute(b, namedTypes)) 145 | 146 | // Generate len() 147 | fmt.Fprint(b, "// len() functions\n") 148 | for _, name := range namedTypes { 149 | if _, ok := skipLen[name]; ok { 150 | continue 151 | } 152 | o := scope.Lookup(name) 153 | st, isEmbedded := getTypeStruct(o.Type(), scope) 154 | if isEmbedded { 155 | continue 156 | } 157 | fmt.Fprintf(b, "func (rr *%s) len() int {\n", name) 158 | fmt.Fprintf(b, "l := rr.Hdr.len()\n") 159 | for i := 1; i < st.NumFields(); i++ { 160 | o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) } 161 | 162 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 163 | switch st.Tag(i) { 164 | case `dns:"-"`: 165 | // ignored 166 | case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`: 167 | o("for _, x := range rr.%s { l += len(x) + 1 }\n") 168 | default: 169 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 170 | } 171 | continue 172 | } 173 | 174 | switch { 175 | case st.Tag(i) == `dns:"-"`: 176 | // ignored 177 | case st.Tag(i) == `dns:"cdomain-name"`, st.Tag(i) == `dns:"domain-name"`: 178 | o("l += len(rr.%s) + 1\n") 179 | case st.Tag(i) == `dns:"octet"`: 180 | o("l += len(rr.%s)\n") 181 | case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): 182 | fallthrough 183 | case st.Tag(i) == `dns:"base64"`: 184 | o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n") 185 | case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): 186 | fallthrough 187 | case st.Tag(i) == `dns:"hex"`: 188 | o("l += len(rr.%s)/2 + 1\n") 189 | case st.Tag(i) == `dns:"a"`: 190 | o("l += net.IPv4len // %s\n") 191 | case st.Tag(i) == `dns:"aaaa"`: 192 | o("l += net.IPv6len // %s\n") 193 | case st.Tag(i) == `dns:"txt"`: 194 | o("for _, t := range rr.%s { l += len(t) + 1 }\n") 195 | case st.Tag(i) == `dns:"uint48"`: 196 | o("l += 6 // %s\n") 197 | case st.Tag(i) == "": 198 | switch st.Field(i).Type().(*types.Basic).Kind() { 199 | case types.Uint8: 200 | o("l++ // %s\n") 201 | case types.Uint16: 202 | o("l += 2 // %s\n") 203 | case types.Uint32: 204 | o("l += 4 // %s\n") 205 | case types.Uint64: 206 | o("l += 8 // %s\n") 207 | case types.String: 208 | o("l += len(rr.%s) + 1\n") 209 | default: 210 | log.Fatalln(name, st.Field(i).Name()) 211 | } 212 | default: 213 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 214 | } 215 | } 216 | fmt.Fprintf(b, "return l }\n") 217 | } 218 | 219 | // Generate copy() 220 | fmt.Fprint(b, "// copy() functions\n") 221 | for _, name := range namedTypes { 222 | o := scope.Lookup(name) 223 | st, isEmbedded := getTypeStruct(o.Type(), scope) 224 | if isEmbedded { 225 | continue 226 | } 227 | fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) 228 | fields := []string{"*rr.Hdr.copyHeader()"} 229 | for i := 1; i < st.NumFields(); i++ { 230 | f := st.Field(i).Name() 231 | if sl, ok := st.Field(i).Type().(*types.Slice); ok { 232 | t := sl.Underlying().String() 233 | t = strings.TrimPrefix(t, "[]") 234 | if strings.Contains(t, ".") { 235 | splits := strings.Split(t, ".") 236 | t = splits[len(splits)-1] 237 | } 238 | fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", 239 | f, t, f, f, f) 240 | fields = append(fields, f) 241 | continue 242 | } 243 | if st.Field(i).Type().String() == "net.IP" { 244 | fields = append(fields, "copyIP(rr."+f+")") 245 | continue 246 | } 247 | fields = append(fields, "rr."+f) 248 | } 249 | fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ",")) 250 | fmt.Fprintf(b, "}\n") 251 | } 252 | 253 | // gofmt 254 | res, err := format.Source(b.Bytes()) 255 | if err != nil { 256 | b.WriteTo(os.Stderr) 257 | log.Fatal(err) 258 | } 259 | 260 | // write result 261 | f, err := os.Create("ztypes.go") 262 | fatalIfErr(err) 263 | defer f.Close() 264 | f.Write(res) 265 | } 266 | 267 | func fatalIfErr(err error) { 268 | if err != nil { 269 | log.Fatal(err) 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCmToM(t *testing.T) { 8 | s := cmToM(0, 0) 9 | if s != "0.00" { 10 | t.Error("0, 0") 11 | } 12 | 13 | s = cmToM(1, 0) 14 | if s != "0.01" { 15 | t.Error("1, 0") 16 | } 17 | 18 | s = cmToM(3, 1) 19 | if s != "0.30" { 20 | t.Error("3, 1") 21 | } 22 | 23 | s = cmToM(4, 2) 24 | if s != "4" { 25 | t.Error("4, 2") 26 | } 27 | 28 | s = cmToM(5, 3) 29 | if s != "50" { 30 | t.Error("5, 3") 31 | } 32 | 33 | s = cmToM(7, 5) 34 | if s != "7000" { 35 | t.Error("7, 5") 36 | } 37 | 38 | s = cmToM(9, 9) 39 | if s != "90000000" { 40 | t.Error("9, 9") 41 | } 42 | } 43 | 44 | func TestSplitN(t *testing.T) { 45 | xs := splitN("abc", 5) 46 | if len(xs) != 1 && xs[0] != "abc" { 47 | t.Errorf("Failure to split abc") 48 | } 49 | 50 | s := "" 51 | for i := 0; i < 255; i++ { 52 | s += "a" 53 | } 54 | 55 | xs = splitN(s, 255) 56 | if len(xs) != 1 && xs[0] != s { 57 | t.Errorf("failure to split 255 char long string") 58 | } 59 | 60 | s += "b" 61 | xs = splitN(s, 255) 62 | if len(xs) != 2 || xs[1] != "b" { 63 | t.Errorf("failure to split 256 char long string: %d", len(xs)) 64 | } 65 | 66 | // Make s longer 67 | for i := 0; i < 255; i++ { 68 | s += "a" 69 | } 70 | xs = splitN(s, 255) 71 | if len(xs) != 3 || xs[2] != "a" { 72 | t.Errorf("failure to split 510 char long string: %d", len(xs)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /udp.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | // SessionUDP holds the remote address and the associated 10 | // out-of-band data. 11 | type SessionUDP struct { 12 | raddr *net.UDPAddr 13 | context []byte 14 | } 15 | 16 | // RemoteAddr returns the remote network address. 17 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 18 | 19 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 20 | // net.UDPAddr. 21 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 22 | oob := make([]byte, 40) 23 | n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) 24 | if err != nil { 25 | return n, nil, err 26 | } 27 | return n, &SessionUDP{raddr, oob[:oobn]}, err 28 | } 29 | 30 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 31 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 32 | n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) 33 | return n, err 34 | } 35 | -------------------------------------------------------------------------------- /udp_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux,!appengine 2 | 3 | package dns 4 | 5 | // See: 6 | // * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and 7 | // * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ 8 | // 9 | // Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing 10 | // interface, this might not always be the correct one. This code will make sure the egress 11 | // packet's interface matched the ingress' one. 12 | 13 | import ( 14 | "net" 15 | "syscall" 16 | ) 17 | 18 | // setUDPSocketOptions sets the UDP socket options. 19 | // This function is implemented on a per platform basis. See udp_*.go for more details 20 | func setUDPSocketOptions(conn *net.UDPConn) error { 21 | sa, err := getUDPSocketName(conn) 22 | if err != nil { 23 | return err 24 | } 25 | switch sa.(type) { 26 | case *syscall.SockaddrInet6: 27 | v6only, err := getUDPSocketOptions6Only(conn) 28 | if err != nil { 29 | return err 30 | } 31 | setUDPSocketOptions6(conn) 32 | if !v6only { 33 | setUDPSocketOptions4(conn) 34 | } 35 | case *syscall.SockaddrInet4: 36 | setUDPSocketOptions4(conn) 37 | } 38 | return nil 39 | } 40 | 41 | // setUDPSocketOptions4 prepares the v4 socket for sessions. 42 | func setUDPSocketOptions4(conn *net.UDPConn) error { 43 | file, err := conn.File() 44 | if err != nil { 45 | return err 46 | } 47 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { 48 | file.Close() 49 | return err 50 | } 51 | // Calling File() above results in the connection becoming blocking, we must fix that. 52 | // See https://github.com/miekg/dns/issues/279 53 | err = syscall.SetNonblock(int(file.Fd()), true) 54 | if err != nil { 55 | file.Close() 56 | return err 57 | } 58 | file.Close() 59 | return nil 60 | } 61 | 62 | // setUDPSocketOptions6 prepares the v6 socket for sessions. 63 | func setUDPSocketOptions6(conn *net.UDPConn) error { 64 | file, err := conn.File() 65 | if err != nil { 66 | return err 67 | } 68 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { 69 | file.Close() 70 | return err 71 | } 72 | err = syscall.SetNonblock(int(file.Fd()), true) 73 | if err != nil { 74 | file.Close() 75 | return err 76 | } 77 | file.Close() 78 | return nil 79 | } 80 | 81 | // getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined 82 | // (dualstack). 83 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { 84 | file, err := conn.File() 85 | if err != nil { 86 | return false, err 87 | } 88 | // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections 89 | v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) 90 | if err != nil { 91 | file.Close() 92 | return false, err 93 | } 94 | file.Close() 95 | return v6only == 1, nil 96 | } 97 | 98 | func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { 99 | file, err := conn.File() 100 | if err != nil { 101 | return nil, err 102 | } 103 | defer file.Close() 104 | return syscall.Getsockname(int(file.Fd())) 105 | } 106 | -------------------------------------------------------------------------------- /udp_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux appengine 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | // These do nothing. See udp_linux.go for an example of how to implement this. 10 | 11 | // We tried to adhire to some kind of naming scheme. 12 | func setUDPSocketOptions(conn *net.UDPConn) error { return nil } 13 | func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } 14 | func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } 15 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } 16 | -------------------------------------------------------------------------------- /udp_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package dns 4 | 5 | import "net" 6 | 7 | type SessionUDP struct { 8 | raddr *net.UDPAddr 9 | } 10 | 11 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 12 | 13 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 14 | // net.UDPAddr. 15 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 16 | n, raddr, err := conn.ReadFrom(b) 17 | if err != nil { 18 | return n, nil, err 19 | } 20 | session := &SessionUDP{raddr.(*net.UDPAddr)} 21 | return n, session, err 22 | } 23 | 24 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 25 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 26 | n, err := conn.WriteTo(b, session.raddr) 27 | return n, err 28 | } 29 | 30 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // NameUsed sets the RRs in the prereq section to 4 | // "Name is in use" RRs. RFC 2136 section 2.4.4. 5 | func (u *Msg) NameUsed(rr []RR) { 6 | if u.Answer == nil { 7 | u.Answer = make([]RR, 0, len(rr)) 8 | } 9 | for _, r := range rr { 10 | u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) 11 | } 12 | } 13 | 14 | // NameNotUsed sets the RRs in the prereq section to 15 | // "Name is in not use" RRs. RFC 2136 section 2.4.5. 16 | func (u *Msg) NameNotUsed(rr []RR) { 17 | if u.Answer == nil { 18 | u.Answer = make([]RR, 0, len(rr)) 19 | } 20 | for _, r := range rr { 21 | u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}}) 22 | } 23 | } 24 | 25 | // Used sets the RRs in the prereq section to 26 | // "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. 27 | func (u *Msg) Used(rr []RR) { 28 | if len(u.Question) == 0 { 29 | panic("dns: empty question section") 30 | } 31 | if u.Answer == nil { 32 | u.Answer = make([]RR, 0, len(rr)) 33 | } 34 | for _, r := range rr { 35 | r.Header().Class = u.Question[0].Qclass 36 | u.Answer = append(u.Answer, r) 37 | } 38 | } 39 | 40 | // RRsetUsed sets the RRs in the prereq section to 41 | // "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. 42 | func (u *Msg) RRsetUsed(rr []RR) { 43 | if u.Answer == nil { 44 | u.Answer = make([]RR, 0, len(rr)) 45 | } 46 | for _, r := range rr { 47 | u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) 48 | } 49 | } 50 | 51 | // RRsetNotUsed sets the RRs in the prereq section to 52 | // "RRset does not exist" RRs. RFC 2136 section 2.4.3. 53 | func (u *Msg) RRsetNotUsed(rr []RR) { 54 | if u.Answer == nil { 55 | u.Answer = make([]RR, 0, len(rr)) 56 | } 57 | for _, r := range rr { 58 | u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassNONE}}) 59 | } 60 | } 61 | 62 | // Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. 63 | func (u *Msg) Insert(rr []RR) { 64 | if len(u.Question) == 0 { 65 | panic("dns: empty question section") 66 | } 67 | if u.Ns == nil { 68 | u.Ns = make([]RR, 0, len(rr)) 69 | } 70 | for _, r := range rr { 71 | r.Header().Class = u.Question[0].Qclass 72 | u.Ns = append(u.Ns, r) 73 | } 74 | } 75 | 76 | // RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. 77 | func (u *Msg) RemoveRRset(rr []RR) { 78 | if u.Ns == nil { 79 | u.Ns = make([]RR, 0, len(rr)) 80 | } 81 | for _, r := range rr { 82 | u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) 83 | } 84 | } 85 | 86 | // RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 87 | func (u *Msg) RemoveName(rr []RR) { 88 | if u.Ns == nil { 89 | u.Ns = make([]RR, 0, len(rr)) 90 | } 91 | for _, r := range rr { 92 | u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) 93 | } 94 | } 95 | 96 | // Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4 97 | func (u *Msg) Remove(rr []RR) { 98 | if u.Ns == nil { 99 | u.Ns = make([]RR, 0, len(rr)) 100 | } 101 | for _, r := range rr { 102 | r.Header().Class = ClassNONE 103 | r.Header().Ttl = 0 104 | u.Ns = append(u.Ns, r) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /update_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestDynamicUpdateParsing(t *testing.T) { 9 | prefix := "example.com. IN " 10 | for _, typ := range TypeToString { 11 | if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" || 12 | typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" || 13 | typ == "Reserved" || typ == "None" || typ == "NXT" || typ == "MAILB" || typ == "MAILA" { 14 | continue 15 | } 16 | r, err := NewRR(prefix + typ) 17 | if err != nil { 18 | t.Errorf("failure to parse: %s %s: %v", prefix, typ, err) 19 | } else { 20 | t.Logf("parsed: %s", r.String()) 21 | } 22 | } 23 | } 24 | 25 | func TestDynamicUpdateUnpack(t *testing.T) { 26 | // From https://github.com/miekg/dns/issues/150#issuecomment-62296803 27 | // It should be an update message for the zone "example.", 28 | // deleting the A RRset "example." and then adding an A record at "example.". 29 | // class ANY, TYPE A 30 | buf := []byte{171, 68, 40, 0, 0, 1, 0, 0, 0, 2, 0, 0, 7, 101, 120, 97, 109, 112, 108, 101, 0, 0, 6, 0, 1, 192, 12, 0, 1, 0, 255, 0, 0, 0, 0, 0, 0, 192, 12, 0, 1, 0, 1, 0, 0, 0, 0, 0, 4, 127, 0, 0, 1} 31 | msg := new(Msg) 32 | err := msg.Unpack(buf) 33 | if err != nil { 34 | t.Errorf("failed to unpack: %v\n%s", err, msg.String()) 35 | } 36 | } 37 | 38 | func TestDynamicUpdateZeroRdataUnpack(t *testing.T) { 39 | m := new(Msg) 40 | rr := &RR_Header{Name: ".", Rrtype: 0, Class: 1, Ttl: ^uint32(0), Rdlength: 0} 41 | m.Answer = []RR{rr, rr, rr, rr, rr} 42 | m.Ns = m.Answer 43 | for n, s := range TypeToString { 44 | rr.Rrtype = n 45 | bytes, err := m.Pack() 46 | if err != nil { 47 | t.Errorf("failed to pack %s: %v", s, err) 48 | continue 49 | } 50 | if err := new(Msg).Unpack(bytes); err != nil { 51 | t.Errorf("failed to unpack %s: %v", s, err) 52 | } 53 | } 54 | } 55 | 56 | func TestRemoveRRset(t *testing.T) { 57 | // Should add a zero data RR in Class ANY with a TTL of 0 58 | // for each set mentioned in the RRs provided to it. 59 | rr, err := NewRR(". 100 IN A 127.0.0.1") 60 | if err != nil { 61 | t.Fatalf("error constructing RR: %v", err) 62 | } 63 | m := new(Msg) 64 | m.Ns = []RR{&RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY, Ttl: 0, Rdlength: 0}} 65 | expectstr := m.String() 66 | expect, err := m.Pack() 67 | if err != nil { 68 | t.Fatalf("error packing expected msg: %v", err) 69 | } 70 | 71 | m.Ns = nil 72 | m.RemoveRRset([]RR{rr}) 73 | actual, err := m.Pack() 74 | if err != nil { 75 | t.Fatalf("error packing actual msg: %v", err) 76 | } 77 | if !bytes.Equal(actual, expect) { 78 | tmp := new(Msg) 79 | if err := tmp.Unpack(actual); err != nil { 80 | t.Fatalf("error unpacking actual msg: %v\nexpected: %v\ngot: %v\n", err, expect, actual) 81 | } 82 | t.Errorf("expected msg:\n%s", expectstr) 83 | t.Errorf("actual msg:\n%v", tmp) 84 | } 85 | } 86 | 87 | func TestPreReqAndRemovals(t *testing.T) { 88 | // Build a list of multiple prereqs and then somes removes followed by an insert. 89 | // We should be able to add multiple prereqs and updates. 90 | m := new(Msg) 91 | m.SetUpdate("example.org.") 92 | m.Id = 1234 93 | 94 | // Use a full set of RRs each time, so we are sure the rdata is stripped. 95 | rrName1, _ := NewRR("name_used. 3600 IN A 127.0.0.1") 96 | rrName2, _ := NewRR("name_not_used. 3600 IN A 127.0.0.1") 97 | rrRemove1, _ := NewRR("remove1. 3600 IN A 127.0.0.1") 98 | rrRemove2, _ := NewRR("remove2. 3600 IN A 127.0.0.1") 99 | rrRemove3, _ := NewRR("remove3. 3600 IN A 127.0.0.1") 100 | rrInsert, _ := NewRR("insert. 3600 IN A 127.0.0.1") 101 | rrRrset1, _ := NewRR("rrset_used1. 3600 IN A 127.0.0.1") 102 | rrRrset2, _ := NewRR("rrset_used2. 3600 IN A 127.0.0.1") 103 | rrRrset3, _ := NewRR("rrset_not_used. 3600 IN A 127.0.0.1") 104 | 105 | // Handle the prereqs. 106 | m.NameUsed([]RR{rrName1}) 107 | m.NameNotUsed([]RR{rrName2}) 108 | m.RRsetUsed([]RR{rrRrset1}) 109 | m.Used([]RR{rrRrset2}) 110 | m.RRsetNotUsed([]RR{rrRrset3}) 111 | 112 | // and now the updates. 113 | m.RemoveName([]RR{rrRemove1}) 114 | m.RemoveRRset([]RR{rrRemove2}) 115 | m.Remove([]RR{rrRemove3}) 116 | m.Insert([]RR{rrInsert}) 117 | 118 | // This test function isn't a Example function because we print these RR with tabs at the 119 | // end and the Example function trim these, thus they never match. 120 | // TODO(miek): don't print these tabs and make this into an Example function. 121 | expect := `;; opcode: UPDATE, status: NOERROR, id: 1234 122 | ;; flags:; QUERY: 1, ANSWER: 5, AUTHORITY: 4, ADDITIONAL: 0 123 | 124 | ;; QUESTION SECTION: 125 | ;example.org. IN SOA 126 | 127 | ;; ANSWER SECTION: 128 | name_used. 0 ANY ANY 129 | name_not_used. 0 NONE ANY 130 | rrset_used1. 0 ANY A 131 | rrset_used2. 3600 IN A 127.0.0.1 132 | rrset_not_used. 0 NONE A 133 | 134 | ;; AUTHORITY SECTION: 135 | remove1. 0 ANY ANY 136 | remove2. 0 ANY A 137 | remove3. 0 NONE A 127.0.0.1 138 | insert. 3600 IN A 127.0.0.1 139 | ` 140 | 141 | if m.String() != expect { 142 | t.Errorf("expected msg:\n%s", expect) 143 | t.Errorf("actual msg:\n%v", m.String()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /xfr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Envelope is used when doing a zone transfer with a remote server. 9 | type Envelope struct { 10 | RR []RR // The set of RRs in the answer section of the xfr reply message. 11 | Error error // If something went wrong, this contains the error. 12 | } 13 | 14 | // A Transfer defines parameters that are used during a zone transfer. 15 | type Transfer struct { 16 | *Conn 17 | DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds 18 | ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds 19 | WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds 20 | TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be fully qualified 21 | tsigTimersOnly bool 22 | } 23 | 24 | // Think we need to away to stop the transfer 25 | 26 | // In performs an incoming transfer with the server in a. 27 | // If you would like to set the source IP, or some other attribute 28 | // of a Dialer for a Transfer, you can do so by specifying the attributes 29 | // in the Transfer.Conn: 30 | // 31 | // d := net.Dialer{LocalAddr: transfer_source} 32 | // con, err := d.Dial("tcp", master) 33 | // dnscon := &dns.Conn{Conn:con} 34 | // transfer = &dns.Transfer{Conn: dnscon} 35 | // channel, err := transfer.In(message, master) 36 | // 37 | func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { 38 | timeout := dnsTimeout 39 | if t.DialTimeout != 0 { 40 | timeout = t.DialTimeout 41 | } 42 | if t.Conn == nil { 43 | t.Conn, err = DialTimeout("tcp", a, timeout) 44 | if err != nil { 45 | return nil, err 46 | } 47 | } 48 | if err := t.WriteMsg(q); err != nil { 49 | return nil, err 50 | } 51 | env = make(chan *Envelope) 52 | go func() { 53 | if q.Question[0].Qtype == TypeAXFR { 54 | go t.inAxfr(q.Id, env) 55 | return 56 | } 57 | if q.Question[0].Qtype == TypeIXFR { 58 | go t.inIxfr(q.Id, env) 59 | return 60 | } 61 | }() 62 | return env, nil 63 | } 64 | 65 | func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { 66 | first := true 67 | defer t.Close() 68 | defer close(c) 69 | timeout := dnsTimeout 70 | if t.ReadTimeout != 0 { 71 | timeout = t.ReadTimeout 72 | } 73 | for { 74 | t.Conn.SetReadDeadline(time.Now().Add(timeout)) 75 | in, err := t.ReadMsg() 76 | if err != nil { 77 | c <- &Envelope{nil, err} 78 | return 79 | } 80 | if id != in.Id { 81 | c <- &Envelope{in.Answer, ErrId} 82 | return 83 | } 84 | if first { 85 | if in.Rcode != RcodeSuccess { 86 | c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} 87 | return 88 | } 89 | if !isSOAFirst(in) { 90 | c <- &Envelope{in.Answer, ErrSoa} 91 | return 92 | } 93 | first = !first 94 | // only one answer that is SOA, receive more 95 | if len(in.Answer) == 1 { 96 | t.tsigTimersOnly = true 97 | c <- &Envelope{in.Answer, nil} 98 | continue 99 | } 100 | } 101 | 102 | if !first { 103 | t.tsigTimersOnly = true // Subsequent envelopes use this. 104 | if isSOALast(in) { 105 | c <- &Envelope{in.Answer, nil} 106 | return 107 | } 108 | c <- &Envelope{in.Answer, nil} 109 | } 110 | } 111 | } 112 | 113 | func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { 114 | serial := uint32(0) // The first serial seen is the current server serial 115 | first := true 116 | defer t.Close() 117 | defer close(c) 118 | timeout := dnsTimeout 119 | if t.ReadTimeout != 0 { 120 | timeout = t.ReadTimeout 121 | } 122 | for { 123 | t.SetReadDeadline(time.Now().Add(timeout)) 124 | in, err := t.ReadMsg() 125 | if err != nil { 126 | c <- &Envelope{nil, err} 127 | return 128 | } 129 | if id != in.Id { 130 | c <- &Envelope{in.Answer, ErrId} 131 | return 132 | } 133 | if first { 134 | if in.Rcode != RcodeSuccess { 135 | c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} 136 | return 137 | } 138 | // A single SOA RR signals "no changes" 139 | if len(in.Answer) == 1 && isSOAFirst(in) { 140 | c <- &Envelope{in.Answer, nil} 141 | return 142 | } 143 | 144 | // Check if the returned answer is ok 145 | if !isSOAFirst(in) { 146 | c <- &Envelope{in.Answer, ErrSoa} 147 | return 148 | } 149 | // This serial is important 150 | serial = in.Answer[0].(*SOA).Serial 151 | first = !first 152 | } 153 | 154 | // Now we need to check each message for SOA records, to see what we need to do 155 | if !first { 156 | t.tsigTimersOnly = true 157 | // If the last record in the IXFR contains the servers' SOA, we should quit 158 | if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { 159 | if v.Serial == serial { 160 | c <- &Envelope{in.Answer, nil} 161 | return 162 | } 163 | } 164 | c <- &Envelope{in.Answer, nil} 165 | } 166 | } 167 | } 168 | 169 | // Out performs an outgoing transfer with the client connecting in w. 170 | // Basic use pattern: 171 | // 172 | // ch := make(chan *dns.Envelope) 173 | // tr := new(dns.Transfer) 174 | // go tr.Out(w, r, ch) 175 | // ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} 176 | // close(ch) 177 | // w.Hijack() 178 | // // w.Close() // Client closes connection 179 | // 180 | // The server is responsible for sending the correct sequence of RRs through the 181 | // channel ch. 182 | func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { 183 | for x := range ch { 184 | r := new(Msg) 185 | // Compress? 186 | r.SetReply(q) 187 | r.Authoritative = true 188 | // assume it fits TODO(miek): fix 189 | r.Answer = append(r.Answer, x.RR...) 190 | if err := w.WriteMsg(r); err != nil { 191 | return err 192 | } 193 | } 194 | w.TsigTimersOnly(true) 195 | return nil 196 | } 197 | 198 | // ReadMsg reads a message from the transfer connection t. 199 | func (t *Transfer) ReadMsg() (*Msg, error) { 200 | m := new(Msg) 201 | p := make([]byte, MaxMsgSize) 202 | n, err := t.Read(p) 203 | if err != nil && n == 0 { 204 | return nil, err 205 | } 206 | p = p[:n] 207 | if err := m.Unpack(p); err != nil { 208 | return nil, err 209 | } 210 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 211 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 212 | return m, ErrSecret 213 | } 214 | // Need to work on the original message p, as that was used to calculate the tsig. 215 | err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 216 | t.tsigRequestMAC = ts.MAC 217 | } 218 | return m, err 219 | } 220 | 221 | // WriteMsg writes a message through the transfer connection t. 222 | func (t *Transfer) WriteMsg(m *Msg) (err error) { 223 | var out []byte 224 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 225 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 226 | return ErrSecret 227 | } 228 | out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 229 | } else { 230 | out, err = m.Pack() 231 | } 232 | if err != nil { 233 | return err 234 | } 235 | if _, err = t.Write(out); err != nil { 236 | return err 237 | } 238 | return nil 239 | } 240 | 241 | func isSOAFirst(in *Msg) bool { 242 | if len(in.Answer) > 0 { 243 | return in.Answer[0].Header().Rrtype == TypeSOA 244 | } 245 | return false 246 | } 247 | 248 | func isSOALast(in *Msg) bool { 249 | if len(in.Answer) > 0 { 250 | return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA 251 | } 252 | return false 253 | } 254 | 255 | const errXFR = "bad xfr rcode: %d" 256 | -------------------------------------------------------------------------------- /xfr_test.go: -------------------------------------------------------------------------------- 1 | // +build net 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | "strings" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func getIP(s string) string { 13 | a, err := net.LookupAddr(s) 14 | if err != nil { 15 | return "" 16 | } 17 | return a[0] 18 | } 19 | 20 | // flaky, need to setup local server and test from that. 21 | func TestAXFR_Miek(t *testing.T) { 22 | // This test runs against a server maintained by Miek 23 | if testing.Short() { 24 | return 25 | } 26 | m := new(Msg) 27 | m.SetAxfr("miek.nl.") 28 | 29 | server := getIP("linode.atoom.net") 30 | 31 | tr := new(Transfer) 32 | 33 | if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { 34 | t.Fatal("failed to setup axfr: ", err) 35 | } else { 36 | for ex := range a { 37 | if ex.Error != nil { 38 | t.Errorf("error %v", ex.Error) 39 | break 40 | } 41 | for _, rr := range ex.RR { 42 | t.Log(rr.String()) 43 | } 44 | } 45 | } 46 | } 47 | 48 | // fails. 49 | func TestAXFR_NLNL_MultipleEnvelopes(t *testing.T) { 50 | // This test runs against a server maintained by NLnet Labs 51 | if testing.Short() { 52 | return 53 | } 54 | m := new(Msg) 55 | m.SetAxfr("nlnetlabs.nl.") 56 | 57 | server := getIP("open.nlnetlabs.nl.") 58 | 59 | tr := new(Transfer) 60 | if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { 61 | t.Fatalf("failed to setup axfr %v for server: %v", err, server) 62 | } else { 63 | for ex := range a { 64 | if ex.Error != nil { 65 | t.Errorf("error %v", ex.Error) 66 | break 67 | } 68 | } 69 | } 70 | } 71 | 72 | func TestAXFR_Miek_Tsig(t *testing.T) { 73 | // This test runs against a server maintained by Miek 74 | if testing.Short() { 75 | return 76 | } 77 | m := new(Msg) 78 | m.SetAxfr("example.nl.") 79 | m.SetTsig("axfr.", HmacMD5, 300, time.Now().Unix()) 80 | 81 | tr := new(Transfer) 82 | tr.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 83 | 84 | if a, err := tr.In(m, "176.58.119.54:53"); err != nil { 85 | t.Fatal("failed to setup axfr: ", err) 86 | } else { 87 | for ex := range a { 88 | if ex.Error != nil { 89 | t.Errorf("error %v", ex.Error) 90 | break 91 | } 92 | for _, rr := range ex.RR { 93 | t.Log(rr.String()) 94 | } 95 | } 96 | } 97 | } 98 | 99 | func TestAXFR_SIDN_NSD3_NONE(t *testing.T) { testAXFRSIDN(t, "nsd", "") } 100 | func TestAXFR_SIDN_NSD3_MD5(t *testing.T) { testAXFRSIDN(t, "nsd", HmacMD5) } 101 | func TestAXFR_SIDN_NSD3_SHA1(t *testing.T) { testAXFRSIDN(t, "nsd", HmacSHA1) } 102 | func TestAXFR_SIDN_NSD3_SHA256(t *testing.T) { testAXFRSIDN(t, "nsd", HmacSHA256) } 103 | 104 | func TestAXFR_SIDN_NSD4_NONE(t *testing.T) { testAXFRSIDN(t, "nsd4", "") } 105 | func TestAXFR_SIDN_NSD4_MD5(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacMD5) } 106 | func TestAXFR_SIDN_NSD4_SHA1(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacSHA1) } 107 | func TestAXFR_SIDN_NSD4_SHA256(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacSHA256) } 108 | 109 | func TestAXFR_SIDN_BIND9_NONE(t *testing.T) { testAXFRSIDN(t, "bind9", "") } 110 | func TestAXFR_SIDN_BIND9_MD5(t *testing.T) { testAXFRSIDN(t, "bind9", HmacMD5) } 111 | func TestAXFR_SIDN_BIND9_SHA1(t *testing.T) { testAXFRSIDN(t, "bind9", HmacSHA1) } 112 | func TestAXFR_SIDN_BIND9_SHA256(t *testing.T) { testAXFRSIDN(t, "bind9", HmacSHA256) } 113 | 114 | func TestAXFR_SIDN_KNOT_NONE(t *testing.T) { testAXFRSIDN(t, "knot", "") } 115 | func TestAXFR_SIDN_KNOT_MD5(t *testing.T) { testAXFRSIDN(t, "knot", HmacMD5) } 116 | func TestAXFR_SIDN_KNOT_SHA1(t *testing.T) { testAXFRSIDN(t, "knot", HmacSHA1) } 117 | func TestAXFR_SIDN_KNOT_SHA256(t *testing.T) { testAXFRSIDN(t, "knot", HmacSHA256) } 118 | 119 | func TestAXFR_SIDN_POWERDNS_NONE(t *testing.T) { testAXFRSIDN(t, "powerdns", "") } 120 | func TestAXFR_SIDN_POWERDNS_MD5(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacMD5) } 121 | func TestAXFR_SIDN_POWERDNS_SHA1(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacSHA1) } 122 | func TestAXFR_SIDN_POWERDNS_SHA256(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacSHA256) } 123 | 124 | func TestAXFR_SIDN_YADIFA_NONE(t *testing.T) { testAXFRSIDN(t, "yadifa", "") } 125 | func TestAXFR_SIDN_YADIFA_MD5(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacMD5) } 126 | func TestAXFR_SIDN_YADIFA_SHA1(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacSHA1) } 127 | func TestAXFR_SIDN_YADIFA_SHA256(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacSHA256) } 128 | 129 | func testAXFRSIDN(t *testing.T, host, alg string) { 130 | // This tests run against a server maintained by SIDN labs, see: 131 | // https://workbench.sidnlabs.nl/ 132 | if testing.Short() { 133 | return 134 | } 135 | x := new(Transfer) 136 | x.TsigSecret = map[string]string{ 137 | "wb_md5.": "Wu/utSasZUkoeCNku152Zw==", 138 | "wb_sha1_longkey.": "uhMpEhPq/RAD9Bt4mqhfmi+7ZdKmjLQb/lcrqYPXR4s/nnbsqw==", 139 | "wb_sha256.": "npfrIJjt/MJOjGJoBNZtsjftKMhkSpIYMv2RzRZt1f8=", 140 | } 141 | keyname := map[string]string{ 142 | HmacMD5: "wb_md5.", 143 | HmacSHA1: "wb_sha1_longkey.", 144 | HmacSHA256: "wb_sha256.", 145 | }[alg] 146 | 147 | m := new(Msg) 148 | m.SetAxfr("types.wb.sidnlabs.nl.") 149 | if keyname != "" { 150 | m.SetTsig(keyname, alg, 300, time.Now().Unix()) 151 | } 152 | c, err := x.In(m, host+".sidnlabs.nl:53") 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | for e := range c { 157 | if e.Error != nil { 158 | t.Fatal(e.Error) 159 | } 160 | } 161 | } 162 | 163 | func TestAXFRFailNotAuth(t *testing.T) { 164 | // This tests run against a server maintained by SIDN labs, see: 165 | // https://workbench.sidnlabs.nl/ 166 | if testing.Short() { 167 | return 168 | } 169 | x := new(Transfer) 170 | 171 | m := new(Msg) 172 | m.SetAxfr("sidnlabs.nl.") 173 | c, err := x.In(m, "yadifa.sidnlabs.nl:53") 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | for e := range c { 178 | if e.Error != nil { 179 | if !strings.HasPrefix(e.Error.Error(), "dns: bad xfr rcode:") { 180 | t.Fatal(e.Error) 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /zcompress.go: -------------------------------------------------------------------------------- 1 | // *** DO NOT MODIFY *** 2 | // AUTOGENERATED BY go generate from compress_generate.go 3 | 4 | package dns 5 | 6 | func compressionLenHelperType(c map[string]int, r RR) { 7 | switch x := r.(type) { 8 | case *PTR: 9 | compressionLenHelper(c, x.Ptr) 10 | case *SOA: 11 | compressionLenHelper(c, x.Ns) 12 | compressionLenHelper(c, x.Mbox) 13 | case *AFSDB: 14 | compressionLenHelper(c, x.Hostname) 15 | case *HIP: 16 | for i := range x.RendezvousServers { 17 | compressionLenHelper(c, x.RendezvousServers[i]) 18 | } 19 | case *LP: 20 | compressionLenHelper(c, x.Fqdn) 21 | case *CNAME: 22 | compressionLenHelper(c, x.Target) 23 | case *MB: 24 | compressionLenHelper(c, x.Mb) 25 | case *RP: 26 | compressionLenHelper(c, x.Mbox) 27 | compressionLenHelper(c, x.Txt) 28 | case *RRSIG: 29 | compressionLenHelper(c, x.SignerName) 30 | case *MF: 31 | compressionLenHelper(c, x.Mf) 32 | case *MINFO: 33 | compressionLenHelper(c, x.Rmail) 34 | compressionLenHelper(c, x.Email) 35 | case *SIG: 36 | compressionLenHelper(c, x.SignerName) 37 | case *SRV: 38 | compressionLenHelper(c, x.Target) 39 | case *TSIG: 40 | compressionLenHelper(c, x.Algorithm) 41 | case *KX: 42 | compressionLenHelper(c, x.Exchanger) 43 | case *MG: 44 | compressionLenHelper(c, x.Mg) 45 | case *NSAPPTR: 46 | compressionLenHelper(c, x.Ptr) 47 | case *PX: 48 | compressionLenHelper(c, x.Map822) 49 | compressionLenHelper(c, x.Mapx400) 50 | case *DNAME: 51 | compressionLenHelper(c, x.Target) 52 | case *MR: 53 | compressionLenHelper(c, x.Mr) 54 | case *MX: 55 | compressionLenHelper(c, x.Mx) 56 | case *TKEY: 57 | compressionLenHelper(c, x.Algorithm) 58 | case *NSEC: 59 | compressionLenHelper(c, x.NextDomain) 60 | case *TALINK: 61 | compressionLenHelper(c, x.PreviousName) 62 | compressionLenHelper(c, x.NextName) 63 | case *MD: 64 | compressionLenHelper(c, x.Md) 65 | case *NAPTR: 66 | compressionLenHelper(c, x.Replacement) 67 | case *NS: 68 | compressionLenHelper(c, x.Ns) 69 | case *RT: 70 | compressionLenHelper(c, x.Host) 71 | } 72 | } 73 | 74 | func compressionLenSearchType(c map[string]int, r RR) (int, bool) { 75 | switch x := r.(type) { 76 | case *MG: 77 | k1, ok1 := compressionLenSearch(c, x.Mg) 78 | return k1, ok1 79 | case *PTR: 80 | k1, ok1 := compressionLenSearch(c, x.Ptr) 81 | return k1, ok1 82 | case *AFSDB: 83 | k1, ok1 := compressionLenSearch(c, x.Hostname) 84 | return k1, ok1 85 | case *MB: 86 | k1, ok1 := compressionLenSearch(c, x.Mb) 87 | return k1, ok1 88 | case *MD: 89 | k1, ok1 := compressionLenSearch(c, x.Md) 90 | return k1, ok1 91 | case *MF: 92 | k1, ok1 := compressionLenSearch(c, x.Mf) 93 | return k1, ok1 94 | case *NS: 95 | k1, ok1 := compressionLenSearch(c, x.Ns) 96 | return k1, ok1 97 | case *RT: 98 | k1, ok1 := compressionLenSearch(c, x.Host) 99 | return k1, ok1 100 | case *SOA: 101 | k1, ok1 := compressionLenSearch(c, x.Ns) 102 | k2, ok2 := compressionLenSearch(c, x.Mbox) 103 | return k1 + k2, ok1 && ok2 104 | case *CNAME: 105 | k1, ok1 := compressionLenSearch(c, x.Target) 106 | return k1, ok1 107 | case *MINFO: 108 | k1, ok1 := compressionLenSearch(c, x.Rmail) 109 | k2, ok2 := compressionLenSearch(c, x.Email) 110 | return k1 + k2, ok1 && ok2 111 | case *MR: 112 | k1, ok1 := compressionLenSearch(c, x.Mr) 113 | return k1, ok1 114 | case *MX: 115 | k1, ok1 := compressionLenSearch(c, x.Mx) 116 | return k1, ok1 117 | } 118 | return 0, false 119 | } 120 | --------------------------------------------------------------------------------