diff -Nru golang-github-miekg-dns-1.1.26/acceptfunc_test.go golang-github-miekg-dns-1.1.35/acceptfunc_test.go --- golang-github-miekg-dns-1.1.26/acceptfunc_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/acceptfunc_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -6,7 +6,7 @@ func TestAcceptNotify(t *testing.T) { HandleFunc("example.org.", handleNotify) - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } diff -Nru golang-github-miekg-dns-1.1.26/client.go golang-github-miekg-dns-1.1.35/client.go --- golang-github-miekg-dns-1.1.26/client.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/client.go 2021-01-11 04:34:51.000000000 +0000 @@ -34,7 +34,7 @@ Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more // Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout, // WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and - // Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext) + // Client.Dialer) or context.Context.Deadline (see ExchangeContext) Timeout time.Duration DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero @@ -106,7 +106,7 @@ if err != nil { return nil, err } - + conn.UDPSize = c.UDPSize return conn, nil } @@ -124,15 +124,38 @@ // of 512 bytes // To specify a local address or a timeout, the caller has to set the `Client.Dialer` // attribute appropriately + func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) { + co, err := c.Dial(address) + + if err != nil { + return nil, 0, err + } + defer co.Close() + return c.ExchangeWithConn(m, co) +} + +// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection +// that will be used instead of creating a new one. +// Usage pattern with a *dns.Client: +// c := new(dns.Client) +// // connection management logic goes here +// +// conn := c.Dial(address) +// in, rtt, err := c.ExchangeWithConn(message, conn) +// +// This allows users of the library to implement their own connection management, +// as opposed to Exchange, which will always use new connections and incur the added overhead +// that entails when using "tcp" and especially "tcp-tls" clients. +func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) { if !c.SingleInflight { - return c.exchange(m, address) + return c.exchange(m, conn) } q := m.Question[0] key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass) r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) { - return c.exchange(m, address) + return c.exchange(m, conn) }) if r != nil && shared { r = r.Copy() @@ -141,15 +164,7 @@ return r, rtt, err } -func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { - var co *Conn - - co, err = c.Dial(a) - - if err != nil { - return nil, 0, err - } - defer co.Close() +func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) { opt := m.IsEdns0() // If EDNS0 is used use that for size. @@ -170,9 +185,20 @@ } co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout()))) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId + if _, ok := co.Conn.(net.PacketConn); ok { + for { + r, err = co.ReadMsg() + // Ignore replies with mismatched IDs because they might be + // responses to earlier queries that timed out. + if err != nil || r.Id == m.Id { + break + } + } + } else { + r, err = co.ReadMsg() + if err == nil && r.Id != m.Id { + err = ErrId + } } rtt = time.Since(t) return r, rtt, err diff -Nru golang-github-miekg-dns-1.1.26/client_test.go golang-github-miekg-dns-1.1.35/client_test.go --- golang-github-miekg-dns-1.1.26/client_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/client_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -3,6 +3,7 @@ import ( "context" "crypto/tls" + "errors" "fmt" "net" "strconv" @@ -15,7 +16,7 @@ HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -38,7 +39,7 @@ HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -72,7 +73,7 @@ HandleFunc("miek.nl.", HelloServerEchoAddrPort) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -116,7 +117,7 @@ Certificates: []tls.Certificate{cert}, } - s, addrstr, err := RunLocalTLSServer(":0", &config) + s, addrstr, _, err := RunLocalTLSServer(":0", &config) if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -162,11 +163,42 @@ } } +func isNetworkTimeout(err error) bool { + // TODO: when Go 1.14 support is dropped, do this: https://golang.org/doc/go1.15#net + var netError net.Error + return errors.As(err, &netError) && netError.Timeout() +} + func TestClientSyncBadID(t *testing.T) { HandleFunc("miek.nl.", HelloServerBadID) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := &Client{ + Timeout: 50 * time.Millisecond, + } + if _, _, err := c.Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) { + t.Errorf("query did not time out") + } + // And now with plain Exchange(). + if _, err = Exchange(m, addrstr); err == nil || !isNetworkTimeout(err) { + t.Errorf("query did not time out") + } +} + +func TestClientSyncBadThenGoodID(t *testing.T) { + HandleFunc("miek.nl.", HelloServerBadThenGoodID) + defer HandleRemove("miek.nl.") + + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -176,11 +208,40 @@ m.SetQuestion("miek.nl.", TypeSOA) c := new(Client) - if _, _, err := c.Exchange(m, addrstr); err != ErrId { - t.Errorf("did not find a bad Id") + r, _, err := c.Exchange(m, addrstr) + if err != nil { + t.Errorf("failed to exchange: %v", err) + } + if r.Id != m.Id { + t.Errorf("failed to get response with expected Id") } // And now with plain Exchange(). - if _, err := Exchange(m, addrstr); err != ErrId { + r, err = Exchange(m, addrstr) + if err != nil { + t.Errorf("failed to exchange: %v", err) + } + if r.Id != m.Id { + t.Errorf("failed to get response with expected Id") + } +} + +func TestClientSyncTCPBadID(t *testing.T) { + HandleFunc("miek.nl.", HelloServerBadID) + defer HandleRemove("miek.nl.") + + s, addrstr, _, err := RunLocalTCPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := &Client{ + Net: "tcp", + } + if _, _, err := c.Exchange(m, addrstr); err != ErrId { t.Errorf("did not find a bad Id") } } @@ -189,7 +250,7 @@ HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -236,7 +297,7 @@ HandleFunc("miek.nl.", handler) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %s", err) } @@ -286,7 +347,7 @@ defer HandleRemove("miek.nl.") // This uses TCP just to make it slightly different than TestClientSync - s, addrstr, err := RunLocalTCPServer(":0") + s, addrstr, _, err := RunLocalTCPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -533,7 +594,7 @@ HandleFunc("miek.nl.", handler) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %s", err) } @@ -565,3 +626,34 @@ } } } + +func TestExchangeWithConn(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + s, addrstr, _, err := RunLocalUDPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := new(Client) + conn, err := c.Dial(addrstr) + if err != nil { + t.Fatalf("failed to dial: %v", err) + } + + r, _, err := c.ExchangeWithConn(m, conn) + if err != nil { + t.Fatalf("failed to exchange: %v", err) + } + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { + t.Errorf("failed to get an valid answer\n%v", r) + } +} diff -Nru golang-github-miekg-dns-1.1.26/debian/changelog golang-github-miekg-dns-1.1.35/debian/changelog --- golang-github-miekg-dns-1.1.26/debian/changelog 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/changelog 2021-01-11 18:40:09.000000000 +0000 @@ -1,3 +1,18 @@ +golang-github-miekg-dns (1.1.35-1) unstable; urgency=medium + + [ Debian Janitor ] + * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, Repository-Browse. + + [ Eric Dorland ] + * New upstream version 1.1.35 + * Drop 01-Disable_network_tests.patch + * Drop obsolete generate.patch + * Drop upstreamed 03-fix-failing-tests-on-32-bit-platforms.patch + * Add build dep on golang-golang-x-tools-dev + * Add myself to uploaders + + -- Eric Dorland Mon, 11 Jan 2021 13:40:09 -0500 + golang-github-miekg-dns (1.1.26-2) unstable; urgency=medium * Team upload. diff -Nru golang-github-miekg-dns-1.1.26/debian/control golang-github-miekg-dns-1.1.35/debian/control --- golang-github-miekg-dns-1.1.26/debian/control 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/control 2021-01-11 18:40:09.000000000 +0000 @@ -4,6 +4,7 @@ Tim Potter , Martín Ferrari , Dmitry Smirnov , + Eric Dorland , Section: devel Testsuite: autopkgtest-pkg-go Priority: optional @@ -13,6 +14,7 @@ golang-golang-x-crypto-dev, golang-golang-x-net-dev (>= 1:0.0+git20180124), golang-golang-x-sync-dev, + golang-golang-x-tools-dev, Standards-Version: 4.5.0 Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-miekg-dns Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-miekg-dns.git diff -Nru golang-github-miekg-dns-1.1.26/debian/patches/01-Disable_network_tests.patch golang-github-miekg-dns-1.1.35/debian/patches/01-Disable_network_tests.patch --- golang-github-miekg-dns-1.1.26/debian/patches/01-Disable_network_tests.patch 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/patches/01-Disable_network_tests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -Description: Disable network tests - Network access is disabled for builds performed with pbduiler or - Debian buildd. Disable tests that access the network so the package - builds successfully in these environments. -Author: Tim Potter -Updated: 2017-05-19 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/issue_test.go -+++ b/issue_test.go -@@ -7,24 +7,6 @@ - "testing" - ) - --func TestTCPRtt(t *testing.T) { -- m := new(Msg) -- m.RecursionDesired = true -- m.SetQuestion("example.org.", TypeA) -- -- c := &Client{} -- for _, proto := range []string{"udp", "tcp"} { -- c.Net = proto -- _, rtt, err := c.Exchange(m, "8.8.4.4:53") -- if err != nil { -- t.Fatal(err) -- } -- if rtt == 0 { -- t.Fatalf("expecting non zero rtt %s, got zero", c.Net) -- } -- } --} -- - func TestNSEC3MissingSalt(t *testing.T) { - rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H") - m := new(Msg) ---- a/remote_test.go -+++ /dev/null -@@ -1,19 +0,0 @@ --package dns -- --import "testing" -- --const LinodeAddr = "176.58.119.54:53" -- --func TestClientRemote(t *testing.T) { -- m := new(Msg) -- m.SetQuestion("go.dns.miek.nl.", TypeTXT) -- -- c := new(Client) -- r, _, err := c.Exchange(m, LinodeAddr) -- if err != nil { -- t.Errorf("failed to exchange: %v", err) -- } -- if r != nil && r.Rcode != RcodeSuccess { -- t.Errorf("failed to get an valid answer\n%v", r) -- } --} diff -Nru golang-github-miekg-dns-1.1.26/debian/patches/03-fix-failing-tests-on-32-bit-platforms.patch golang-github-miekg-dns-1.1.35/debian/patches/03-fix-failing-tests-on-32-bit-platforms.patch --- golang-github-miekg-dns-1.1.26/debian/patches/03-fix-failing-tests-on-32-bit-platforms.patch 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/patches/03-fix-failing-tests-on-32-bit-platforms.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -From: Michael Hudson-Doyle -Date: Thu, 2 Jul 2020 19:54:07 +1200 -Subject: fix failing tests on 32 bit platforms (#1130) - -* fix check for bad offset on 32 bit systems - -* parse integers into int64 rather than platform dependent ints - -Co-authored-by: Michael Hudson-Doyle - -Origin: upstream, https://github.com/miekg/dns/commit/064ba4b78 -Last-Updated: 2020-07-03 - -Gpb-Pq: Name 03-fix-failing-tests-on-32-bit-platforms.patch ---- - generate.go | 26 +++++++++++++------------- - 1 file changed, 13 insertions(+), 13 deletions(-) - -diff --git a/generate.go b/generate.go -index f7e91a2..f713074 100644 ---- a/generate.go -+++ b/generate.go -@@ -20,13 +20,13 @@ import ( - // of $ after that are interpreted. - func (zp *ZoneParser) generate(l lex) (RR, bool) { - token := l.token -- step := 1 -+ step := int64(1) - if i := strings.IndexByte(token, '/'); i >= 0 { - if i+1 == len(token) { - return zp.setParseError("bad step in $GENERATE range", l) - } - -- s, err := strconv.Atoi(token[i+1:]) -+ s, err := strconv.ParseInt(token[i+1:], 10, 64) - if err != nil || s <= 0 { - return zp.setParseError("bad step in $GENERATE range", l) - } -@@ -40,12 +40,12 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) { - return zp.setParseError("bad start-stop in $GENERATE range", l) - } - -- start, err := strconv.Atoi(sx[0]) -+ start, err := strconv.ParseInt(sx[0], 10, 64) - if err != nil { - return zp.setParseError("bad start in $GENERATE range", l) - } - -- end, err := strconv.Atoi(sx[1]) -+ end, err := strconv.ParseInt(sx[1], 10, 64) - if err != nil { - return zp.setParseError("bad stop in $GENERATE range", l) - } -@@ -75,10 +75,10 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) { - r := &generateReader{ - s: s, - -- cur: start, -- start: start, -- end: end, -- step: step, -+ cur: int(start), -+ start: int(start), -+ end: int(end), -+ step: int(step), - - file: zp.file, - lex: &l, -@@ -188,7 +188,7 @@ func (r *generateReader) ReadByte() (byte, error) { - if errMsg != "" { - return 0, r.parseError(errMsg, si+3+sep) - } -- if r.start+offset < 0 || r.end+offset > 1<<31-1 { -+ if r.start+offset < 0 || int64(r.end) + int64(offset) > 1<<31-1 { - return 0, r.parseError("bad offset in $GENERATE", si+3+sep) - } - -@@ -229,19 +229,19 @@ func modToPrintf(s string) (string, int, string) { - return "", 0, "bad base in $GENERATE" - } - -- offset, err := strconv.Atoi(offStr) -+ offset, err := strconv.ParseInt(offStr, 10, 64) - if err != nil { - return "", 0, "bad offset in $GENERATE" - } - -- width, err := strconv.Atoi(widthStr) -+ width, err := strconv.ParseInt(widthStr, 10, 64) - if err != nil || width < 0 || width > 255 { - return "", 0, "bad width in $GENERATE" - } - - if width == 0 { -- return "%" + base, offset, "" -+ return "%" + base, int(offset), "" - } - -- return "%0" + widthStr + base, offset, "" -+ return "%0" + widthStr + base, int(offset), "" - } diff -Nru golang-github-miekg-dns-1.1.26/debian/patches/generate.patch golang-github-miekg-dns-1.1.35/debian/patches/generate.patch --- golang-github-miekg-dns-1.1.26/debian/patches/generate.patch 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/patches/generate.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -Last-Update: 2019-12-30 -Bug-Upstream: https://github.com/miekg/dns/issues/1066 -Author: Dmitry Smirnov -Description: fix FTBFS in "go generate" -~~~~ - src/github.com/miekg/dns/duplicate.go - 2019/12/30 01:23:37 can't find import: "github.com/miekg/dns" - exit status 1 - src/github.com/miekg/dns/duplicate.go:3: running "go": exit status 1 -~~~~ - ---- a/duplicate_generate.go -+++ b/duplicate_generate.go -@@ -40,9 +40,9 @@ - } - - func main() { - // Import and type-check the package -- pkg, err := importer.Default().Import("github.com/miekg/dns") -+ pkg, err := importer.For("source", nil).Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - // Collect actual types (*X) ---- a/msg_generate.go -+++ b/msg_generate.go -@@ -45,9 +45,9 @@ - } - - func main() { - // Import and type-check the package -- pkg, err := importer.Default().Import("github.com/miekg/dns") -+ pkg, err := importer.For("source", nil).Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - // Collect actual types (*X) ---- a/types_generate.go -+++ b/types_generate.go -@@ -82,9 +82,9 @@ - } - - func main() { - // Import and type-check the package -- pkg, err := importer.Default().Import("github.com/miekg/dns") -+ pkg, err := importer.For("source", nil).Import("github.com/miekg/dns") - fatalIfErr(err) - scope := pkg.Scope() - - // Collect constants like TypeX diff -Nru golang-github-miekg-dns-1.1.26/debian/patches/series golang-github-miekg-dns-1.1.35/debian/patches/series --- golang-github-miekg-dns-1.1.26/debian/patches/series 2020-07-03 13:58:38.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -01-Disable_network_tests.patch -generate.patch -03-fix-failing-tests-on-32-bit-platforms.patch diff -Nru golang-github-miekg-dns-1.1.26/debian/upstream/metadata golang-github-miekg-dns-1.1.35/debian/upstream/metadata --- golang-github-miekg-dns-1.1.26/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/debian/upstream/metadata 2021-01-11 18:40:09.000000000 +0000 @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/miekg/dns/issues +Bug-Submit: https://github.com/miekg/dns/issues/new +Repository: https://github.com/miekg/dns.git +Repository-Browse: https://github.com/miekg/dns diff -Nru golang-github-miekg-dns-1.1.26/defaults.go golang-github-miekg-dns-1.1.35/defaults.go --- golang-github-miekg-dns-1.1.26/defaults.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/defaults.go 2021-01-11 04:34:51.000000000 +0000 @@ -105,7 +105,7 @@ // SetTsig appends a TSIG RR to the message. // This is only a skeleton TSIG RR that is added as the last RR in the -// additional section. The Tsig is calculated when the message is being send. +// additional section. The TSIG is calculated when the message is being send. func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg { t := new(TSIG) t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} @@ -317,6 +317,12 @@ return s + "." } +// CanonicalName returns the domain name in canonical form. A name in canonical +// form is lowercase and fully qualified. See Section 6.2 in RFC 4034. +func CanonicalName(s string) string { + return strings.ToLower(Fqdn(s)) +} + // Copied from the official Go code. // ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP @@ -364,7 +370,7 @@ // String returns the string representation for the class c. func (c Class) String() string { if s, ok := ClassToString[uint16(c)]; ok { - // Only emit mnemonics when they are unambiguous, specically ANY is in both. + // Only emit mnemonics when they are unambiguous, specially ANY is in both. if _, ok := StringToType[s]; !ok { return s } diff -Nru golang-github-miekg-dns-1.1.26/dnssec.go golang-github-miekg-dns-1.1.35/dnssec.go --- golang-github-miekg-dns-1.1.26/dnssec.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/dnssec.go 2021-01-11 04:34:51.000000000 +0000 @@ -3,10 +3,8 @@ import ( "bytes" "crypto" - "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" - _ "crypto/md5" "crypto/rand" "crypto/rsa" _ "crypto/sha1" @@ -200,7 +198,7 @@ wire = wire[:n] owner := make([]byte, 255) - off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false) + off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false) if err1 != nil { return nil } @@ -285,7 +283,7 @@ sigwire.Inception = rr.Inception sigwire.KeyTag = rr.KeyTag // For signing, lowercase this name - sigwire.SignerName = strings.ToLower(rr.SignerName) + sigwire.SignerName = CanonicalName(rr.SignerName) // Create the desired binary blob signdata := make([]byte, DefaultMsgSize) @@ -318,6 +316,7 @@ } rr.Signature = toBase64(signature) + return nil case RSAMD5, DSA, DSANSEC3SHA1: // See RFC 6944. return ErrAlg @@ -332,9 +331,8 @@ } rr.Signature = toBase64(signature) + return nil } - - return nil } func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) { @@ -346,7 +344,6 @@ switch alg { case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: return signature, nil - case ECDSAP256SHA256, ECDSAP384SHA384: ecdsaSignature := &struct { R, S *big.Int @@ -366,20 +363,11 @@ signature := intToBytes(ecdsaSignature.R, intlen) signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...) return signature, nil - - // There is no defined interface for what a DSA backed crypto.Signer returns - case DSA, DSANSEC3SHA1: - // t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8) - // signature := []byte{byte(t)} - // signature = append(signature, intToBytes(r1, 20)...) - // signature = append(signature, intToBytes(s1, 20)...) - // rr.Signature = signature - case ED25519: return signature, nil + default: + return nil, ErrAlg } - - return nil, ErrAlg } // Verify validates an RRSet with the signature and key. This is only the @@ -423,7 +411,7 @@ sigwire.Expiration = rr.Expiration sigwire.Inception = rr.Inception sigwire.KeyTag = rr.KeyTag - sigwire.SignerName = strings.ToLower(rr.SignerName) + sigwire.SignerName = CanonicalName(rr.SignerName) // Create the desired binary blob signeddata := make([]byte, DefaultMsgSize) n, err := packSigWire(sigwire, signeddata) @@ -448,7 +436,7 @@ } switch rr.Algorithm { - case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5: + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? pubkey := k.publicKeyRSA() // Get the key if pubkey == nil { @@ -600,30 +588,6 @@ return pubkey } -func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey { - keybuf, err := fromBase64([]byte(k.PublicKey)) - if err != nil { - return nil - } - if len(keybuf) < 22 { - return nil - } - t, keybuf := int(keybuf[0]), keybuf[1:] - size := 64 + t*8 - q, keybuf := keybuf[:20], keybuf[20:] - if len(keybuf) != 3*size { - return nil - } - p, keybuf := keybuf[:size], keybuf[size:] - g, y := keybuf[:size], keybuf[size:] - pubkey := new(dsa.PublicKey) - pubkey.Parameters.Q = new(big.Int).SetBytes(q) - pubkey.Parameters.P = new(big.Int).SetBytes(p) - pubkey.Parameters.G = new(big.Int).SetBytes(g) - pubkey.Y = new(big.Int).SetBytes(y) - return pubkey -} - func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey { keybuf, err := fromBase64([]byte(k.PublicKey)) if err != nil { @@ -659,7 +623,7 @@ h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." } // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase - h.Name = strings.ToLower(h.Name) + h.Name = CanonicalName(h.Name) // 6.2. Canonical RR Form. (3) - domain rdata to lowercase. // NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, // HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, @@ -672,49 +636,49 @@ // conversion. switch x := r1.(type) { case *NS: - x.Ns = strings.ToLower(x.Ns) + x.Ns = CanonicalName(x.Ns) case *MD: - x.Md = strings.ToLower(x.Md) + x.Md = CanonicalName(x.Md) case *MF: - x.Mf = strings.ToLower(x.Mf) + x.Mf = CanonicalName(x.Mf) case *CNAME: - x.Target = strings.ToLower(x.Target) + x.Target = CanonicalName(x.Target) case *SOA: - x.Ns = strings.ToLower(x.Ns) - x.Mbox = strings.ToLower(x.Mbox) + x.Ns = CanonicalName(x.Ns) + x.Mbox = CanonicalName(x.Mbox) case *MB: - x.Mb = strings.ToLower(x.Mb) + x.Mb = CanonicalName(x.Mb) case *MG: - x.Mg = strings.ToLower(x.Mg) + x.Mg = CanonicalName(x.Mg) case *MR: - x.Mr = strings.ToLower(x.Mr) + x.Mr = CanonicalName(x.Mr) case *PTR: - x.Ptr = strings.ToLower(x.Ptr) + x.Ptr = CanonicalName(x.Ptr) case *MINFO: - x.Rmail = strings.ToLower(x.Rmail) - x.Email = strings.ToLower(x.Email) + x.Rmail = CanonicalName(x.Rmail) + x.Email = CanonicalName(x.Email) case *MX: - x.Mx = strings.ToLower(x.Mx) + x.Mx = CanonicalName(x.Mx) case *RP: - x.Mbox = strings.ToLower(x.Mbox) - x.Txt = strings.ToLower(x.Txt) + x.Mbox = CanonicalName(x.Mbox) + x.Txt = CanonicalName(x.Txt) case *AFSDB: - x.Hostname = strings.ToLower(x.Hostname) + x.Hostname = CanonicalName(x.Hostname) case *RT: - x.Host = strings.ToLower(x.Host) + x.Host = CanonicalName(x.Host) case *SIG: - x.SignerName = strings.ToLower(x.SignerName) + x.SignerName = CanonicalName(x.SignerName) case *PX: - x.Map822 = strings.ToLower(x.Map822) - x.Mapx400 = strings.ToLower(x.Mapx400) + x.Map822 = CanonicalName(x.Map822) + x.Mapx400 = CanonicalName(x.Mapx400) case *NAPTR: - x.Replacement = strings.ToLower(x.Replacement) + x.Replacement = CanonicalName(x.Replacement) case *KX: - x.Exchanger = strings.ToLower(x.Exchanger) + x.Exchanger = CanonicalName(x.Exchanger) case *SRV: - x.Target = strings.ToLower(x.Target) + x.Target = CanonicalName(x.Target) case *DNAME: - x.Target = strings.ToLower(x.Target) + x.Target = CanonicalName(x.Target) } // 6.2. Canonical RR Form. (5) - origTTL wire := make([]byte, Len(r1)+1) // +1 to be safe(r) diff -Nru golang-github-miekg-dns-1.1.26/dnssec_keygen.go golang-github-miekg-dns-1.1.35/dnssec_keygen.go --- golang-github-miekg-dns-1.1.26/dnssec_keygen.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/dnssec_keygen.go 2021-01-11 04:34:51.000000000 +0000 @@ -19,8 +19,6 @@ // bits should be set to the size of the algorithm. func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { switch k.Algorithm { - case RSAMD5, DSA, DSANSEC3SHA1: - return nil, ErrAlg case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: if bits < 512 || bits > 4096 { return nil, ErrKeySize @@ -41,6 +39,8 @@ if bits != 256 { return nil, ErrKeySize } + default: + return nil, ErrAlg } switch k.Algorithm { diff -Nru golang-github-miekg-dns-1.1.26/dnssec_keyscan.go golang-github-miekg-dns-1.1.35/dnssec_keyscan.go --- golang-github-miekg-dns-1.1.26/dnssec_keyscan.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/dnssec_keyscan.go 2021-01-11 04:34:51.000000000 +0000 @@ -43,15 +43,7 @@ return nil, ErrPrivKey } switch uint8(algo) { - case RSAMD5, DSA, DSANSEC3SHA1: - return nil, ErrAlg - case RSASHA1: - fallthrough - case RSASHA1NSEC3SHA1: - fallthrough - case RSASHA256: - fallthrough - case RSASHA512: + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: priv, err := readPrivateKeyRSA(m) if err != nil { return nil, err @@ -62,11 +54,7 @@ } priv.PublicKey = *pub return priv, nil - case ECCGOST: - return nil, ErrPrivKey - case ECDSAP256SHA256: - fallthrough - case ECDSAP384SHA384: + case ECDSAP256SHA256, ECDSAP384SHA384: priv, err := readPrivateKeyECDSA(m) if err != nil { return nil, err @@ -80,7 +68,7 @@ case ED25519: return readPrivateKeyED25519(m) default: - return nil, ErrPrivKey + return nil, ErrAlg } } diff -Nru golang-github-miekg-dns-1.1.26/dnssec_privkey.go golang-github-miekg-dns-1.1.35/dnssec_privkey.go --- golang-github-miekg-dns-1.1.26/dnssec_privkey.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/dnssec_privkey.go 2021-01-11 04:34:51.000000000 +0000 @@ -2,7 +2,6 @@ import ( "crypto" - "crypto/dsa" "crypto/ecdsa" "crypto/rsa" "math/big" @@ -17,8 +16,8 @@ // PrivateKeyString converts a PrivateKey to a string. This string has the same // format as the private-key-file of BIND9 (Private-key-format: v1.3). -// It needs some info from the key (the algorithm), so its a method of the DNSKEY -// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey +// It needs some info from the key (the algorithm), so its a method of the DNSKEY. +// It supports *rsa.PrivateKey, *ecdsa.PrivateKey and ed25519.PrivateKey. func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { algorithm := strconv.Itoa(int(r.Algorithm)) algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" @@ -67,21 +66,6 @@ "Algorithm: " + algorithm + "\n" + "PrivateKey: " + private + "\n" - case *dsa.PrivateKey: - T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) - prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) - subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) - base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) - priv := toBase64(intToBytes(p.X, 20)) - pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) - return format + - "Algorithm: " + algorithm + "\n" + - "Prime(p): " + prime + "\n" + - "Subprime(q): " + subprime + "\n" + - "Base(g): " + base + "\n" + - "Private_value(x): " + priv + "\n" + - "Public_value(y): " + pub + "\n" - case ed25519.PrivateKey: private := toBase64(p.Seed()) return format + diff -Nru golang-github-miekg-dns-1.1.26/dnssec_test.go golang-github-miekg-dns-1.1.35/dnssec_test.go --- golang-github-miekg-dns-1.1.26/dnssec_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/dnssec_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -168,7 +168,7 @@ key.Flags = 256 key.Protocol = 3 key.Algorithm = RSASHA256 - privkey, _ := key.Generate(1024) + privkey, _ := key.Generate(512) sig := new(RRSIG) sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} @@ -251,7 +251,7 @@ key.Flags = 256 key.Protocol = 3 key.Algorithm = RSASHA256 - priv, _ := key.Generate(2048) + priv, _ := key.Generate(512) soa := new(SOA) soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} diff -Nru golang-github-miekg-dns-1.1.26/doc.go golang-github-miekg-dns-1.1.35/doc.go --- golang-github-miekg-dns-1.1.26/doc.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/doc.go 2021-01-11 04:34:51.000000000 +0000 @@ -209,7 +209,7 @@ // *Msg r has an TSIG record and it was validated m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) } else { - // *Msg r has an TSIG records and it was not valided + // *Msg r has an TSIG records and it was not validated } } w.WriteMsg(m) @@ -260,7 +260,7 @@ on requests and responses, and protection of the overall integrity of a response. It works like TSIG, except that SIG(0) uses public key cryptography, instead of -the shared secret approach in TSIG. Supported algorithms: DSA, ECDSAP256SHA256, +the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512. Signing subsequent messages in multi-message sessions is not implemented. diff -Nru golang-github-miekg-dns-1.1.26/duplicate_generate.go golang-github-miekg-dns-1.1.35/duplicate_generate.go --- golang-github-miekg-dns-1.1.26/duplicate_generate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/duplicate_generate.go 2021-01-11 04:34:51.000000000 +0000 @@ -11,10 +11,11 @@ "bytes" "fmt" "go/format" - "go/importer" "go/types" "log" "os" + + "golang.org/x/tools/go/packages" ) var packageHdr = ` @@ -29,6 +30,9 @@ if !ok { return nil, false } + if st.NumFields() == 0 { + return nil, false + } if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { return st, false } @@ -39,9 +43,19 @@ return nil, false } +// loadModule retrieves package description for a given module. +func loadModule(name string) (*types.Package, error) { + conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo} + pkgs, err := packages.Load(&conf, name) + if err != nil { + return nil, err + } + return pkgs[0].Types, nil +} + func main() { // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") + pkg, err := loadModule("github.com/miekg/dns") fatalIfErr(err) scope := pkg.Scope() @@ -72,10 +86,7 @@ for _, name := range namedTypes { o := scope.Lookup(name) - st, isEmbedded := getTypeStruct(o.Type(), scope) - if isEmbedded { - continue - } + st, _ := getTypeStruct(o.Type(), scope) fmt.Fprintf(b, "func (r1 *%s) isDuplicate(_r2 RR) bool {\n", name) fmt.Fprintf(b, "r2, ok := _r2.(*%s)\n", name) fmt.Fprint(b, "if !ok { return false }\n") @@ -98,6 +109,24 @@ }`) continue + } + + if st.Tag(i) == `dns:"apl"` { + o3(`for i := 0; i < len(r1.%s); i++ { + if !r1.%s[i].equals(&r2.%s[i]) { + return false + } + }`) + + continue + } + + if st.Tag(i) == `dns:"pairs"` { + o2(`if !areSVCBPairArraysEqual(r1.%s, r2.%s) { + return false + }`) + + continue } o3(`for i := 0; i < len(r1.%s); i++ { diff -Nru golang-github-miekg-dns-1.1.26/duplicate.go golang-github-miekg-dns-1.1.35/duplicate.go --- golang-github-miekg-dns-1.1.26/duplicate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/duplicate.go 2021-01-11 04:34:51.000000000 +0000 @@ -3,9 +3,8 @@ //go:generate go run duplicate_generate.go // IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL. -// So this means the header data is equal *and* the RDATA is the same. Return true -// is so, otherwise false. -// It's a protocol violation to have identical RRs in a message. +// So this means the header data is equal *and* the RDATA is the same. Returns true +// if so, otherwise false. It's a protocol violation to have identical RRs in a message. func IsDuplicate(r1, r2 RR) bool { // Check whether the record header is identical. if !r1.Header().isDuplicate(r2.Header()) { diff -Nru golang-github-miekg-dns-1.1.26/duplicate_test.go golang-github-miekg-dns-1.1.35/duplicate_test.go --- golang-github-miekg-dns-1.1.26/duplicate_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/duplicate_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -34,6 +34,39 @@ } } +func TestDuplicateSVCB(t *testing.T) { + a1, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300=\254\032\030\000\ \043,\;`) + a2, _ := NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1:0::3:3:3:3 key65300="\254\ \030\000 +\,;"`) + + if !IsDuplicate(a1, a2) { + t.Errorf("expected %s/%s to be duplicates, but got false", a1.String(), a2.String()) + } + + a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3 key65300="\255\ \030\000 +\,;"`) + + if IsDuplicate(a1, a2) { + t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String()) + } + + a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv6hint=1::3:3:3:3`) + + if IsDuplicate(a1, a2) { + t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String()) + } + + a2, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1`) + + if IsDuplicate(a1, a2) { + t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String()) + } + + a1, _ = NewRR(`example.com. 3600 IN SVCB 1 . ipv4hint=1.1.1.1,1.0.2.1`) + + if IsDuplicate(a1, a2) { + t.Errorf("expected %s/%s not to be duplicates, but got true", a1.String(), a2.String()) + } +} + func TestDuplicateOwner(t *testing.T) { a1, _ := NewRR("www.example.org. IN A 127.0.0.1") a2, _ := NewRR("www.example.org. IN A 127.0.0.1") diff -Nru golang-github-miekg-dns-1.1.26/edns.go golang-github-miekg-dns-1.1.35/edns.go --- golang-github-miekg-dns-1.1.26/edns.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/edns.go 2021-01-11 04:34:51.000000000 +0000 @@ -543,6 +543,10 @@ } func (e *EDNS0_EXPIRE) unpack(b []byte) error { + if len(b) == 0 { + // zero-length EXPIRE query, see RFC 7314 Section 2 + return nil + } if len(b) < 4 { return ErrBuf } diff -Nru golang-github-miekg-dns-1.1.26/generate.go golang-github-miekg-dns-1.1.35/generate.go --- golang-github-miekg-dns-1.1.26/generate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/generate.go 2021-01-11 04:34:51.000000000 +0000 @@ -20,13 +20,13 @@ // of $ after that are interpreted. func (zp *ZoneParser) generate(l lex) (RR, bool) { token := l.token - step := 1 + step := int64(1) if i := strings.IndexByte(token, '/'); i >= 0 { if i+1 == len(token) { return zp.setParseError("bad step in $GENERATE range", l) } - s, err := strconv.Atoi(token[i+1:]) + s, err := strconv.ParseInt(token[i+1:], 10, 64) if err != nil || s <= 0 { return zp.setParseError("bad step in $GENERATE range", l) } @@ -40,12 +40,12 @@ return zp.setParseError("bad start-stop in $GENERATE range", l) } - start, err := strconv.Atoi(sx[0]) + start, err := strconv.ParseInt(sx[0], 10, 64) if err != nil { return zp.setParseError("bad start in $GENERATE range", l) } - end, err := strconv.Atoi(sx[1]) + end, err := strconv.ParseInt(sx[1], 10, 64) if err != nil { return zp.setParseError("bad stop in $GENERATE range", l) } @@ -75,10 +75,10 @@ r := &generateReader{ s: s, - cur: start, - start: start, - end: end, - step: step, + cur: int(start), + start: int(start), + end: int(end), + step: int(step), file: zp.file, lex: &l, @@ -188,7 +188,7 @@ if errMsg != "" { return 0, r.parseError(errMsg, si+3+sep) } - if r.start+offset < 0 || r.end+offset > 1<<31-1 { + if r.start+offset < 0 || int64(r.end) + int64(offset) > 1<<31-1 { return 0, r.parseError("bad offset in $GENERATE", si+3+sep) } @@ -229,19 +229,19 @@ return "", 0, "bad base in $GENERATE" } - offset, err := strconv.Atoi(offStr) + offset, err := strconv.ParseInt(offStr, 10, 64) if err != nil { return "", 0, "bad offset in $GENERATE" } - width, err := strconv.Atoi(widthStr) + width, err := strconv.ParseInt(widthStr, 10, 64) if err != nil || width < 0 || width > 255 { return "", 0, "bad width in $GENERATE" } if width == 0 { - return "%" + base, offset, "" + return "%" + base, int(offset), "" } - return "%0" + widthStr + base, offset, "" + return "%0" + widthStr + base, int(offset), "" } diff -Nru golang-github-miekg-dns-1.1.26/generate_test.go golang-github-miekg-dns-1.1.35/generate_test.go --- golang-github-miekg-dns-1.1.26/generate_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/generate_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -62,22 +62,23 @@ {`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) $GENERATE 0-1 $$INCLUDE ` + tmpdir + string(filepath.Separator) + `${0,4,d}.conf `, false}, -{`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) + {`@ IN SOA ns.test. hostmaster.test. ( 1 8h 2h 7d 1d ) $GENERATE 0-1 dhcp-${0,4,d} A 10.0.0.$ $GENERATE 0-2 dhcp-${0,4,d} A 10.1.0.$ `, false}, } -Outer: + for i := range tests { - for tok := range ParseZone(strings.NewReader(tests[i].zone), "test.", "test") { - if tok.Error != nil { - if !tests[i].fail { - t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, tok.Error) - } - continue Outer - } + z := NewZoneParser(strings.NewReader(tests[i].zone), "test.", "test") + z.SetIncludeAllowed(true) + + for _, ok := z.Next(); ok; _, ok = z.Next() { } - if tests[i].fail { + + err := z.Err() + if err != nil && !tests[i].fail { + t.Errorf("expected \n\n%s\nto be parsed, but got %v", tests[i].zone, err) + } else if err == nil && tests[i].fail { t.Errorf("expected \n\n%s\nto fail, but got no error", tests[i].zone) } } diff -Nru golang-github-miekg-dns-1.1.26/.github/workflows/cifuzz.yml golang-github-miekg-dns-1.1.35/.github/workflows/cifuzz.yml --- golang-github-miekg-dns-1.1.26/.github/workflows/cifuzz.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/.github/workflows/cifuzz.yml 2021-01-11 04:34:51.000000000 +0000 @@ -0,0 +1,23 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'go-dns' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'go-dns' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() + with: + name: artifacts + path: ./out/artifacts diff -Nru golang-github-miekg-dns-1.1.26/.github/workflows/codeql-analysis.yml golang-github-miekg-dns-1.1.35/.github/workflows/codeql-analysis.yml --- golang-github-miekg-dns-1.1.26/.github/workflows/codeql-analysis.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/.github/workflows/codeql-analysis.yml 2021-01-11 04:34:51.000000000 +0000 @@ -0,0 +1,54 @@ +name: "Code scanning - action" + +on: + push: + branches: [master, ] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 23 * * 5' + +jobs: + CodeQL-Build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff -Nru golang-github-miekg-dns-1.1.26/.github/workflows/go.yml golang-github-miekg-dns-1.1.35/.github/workflows/go.yml --- golang-github-miekg-dns-1.1.26/.github/workflows/go.yml 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/.github/workflows/go.yml 2021-01-11 04:34:51.000000000 +0000 @@ -0,0 +1,32 @@ +name: Go +on: [push, pull_request] +jobs: + + build: + name: Build and Test + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.14 + uses: actions/setup-go@v1 + with: + go-version: 1.14 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Get dependencies + run: go get -v -t -d ./... + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... + + - name: Fuzz + run: | + export GOPATH=$GOROOT + make -f Makefile.fuzz get + make -f Makefile.fuzz diff -Nru golang-github-miekg-dns-1.1.26/go.mod golang-github-miekg-dns-1.1.35/go.mod --- golang-github-miekg-dns-1.1.26/go.mod 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/go.mod 2021-01-11 04:34:51.000000000 +0000 @@ -3,10 +3,9 @@ go 1.12 require ( - golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/net v0.0.0-20190923162816-aa69164e4478 golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe - golang.org/x/text v0.3.2 // indirect - golang.org/x/tools v0.0.0-20190907020128-2ca718005c18 // indirect + golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect ) diff -Nru golang-github-miekg-dns-1.1.26/go.sum golang-github-miekg-dns-1.1.35/go.sum --- golang-github-miekg-dns-1.1.26/go.sum 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/go.sum 2021-01-11 04:34:51.000000000 +0000 @@ -5,6 +5,9 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -30,4 +33,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff -Nru golang-github-miekg-dns-1.1.26/issue_test.go golang-github-miekg-dns-1.1.35/issue_test.go --- golang-github-miekg-dns-1.1.26/issue_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/issue_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -7,24 +7,6 @@ "testing" ) -func TestTCPRtt(t *testing.T) { - m := new(Msg) - m.RecursionDesired = true - m.SetQuestion("example.org.", TypeA) - - c := &Client{} - for _, proto := range []string{"udp", "tcp"} { - c.Net = proto - _, rtt, err := c.Exchange(m, "8.8.4.4:53") - if err != nil { - t.Fatal(err) - } - if rtt == 0 { - t.Fatalf("expecting non zero rtt %s, got zero", c.Net) - } - } -} - func TestNSEC3MissingSalt(t *testing.T) { rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H") m := new(Msg) diff -Nru golang-github-miekg-dns-1.1.26/labels.go golang-github-miekg-dns-1.1.35/labels.go --- golang-github-miekg-dns-1.1.26/labels.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/labels.go 2021-01-11 04:34:51.000000000 +0000 @@ -83,7 +83,7 @@ return } -// CountLabel counts the the number of labels in the string s. +// CountLabel counts the number of labels in the string s. // s must be a syntactically valid domain name. func CountLabel(s string) (labels int) { if s == "." { diff -Nru golang-github-miekg-dns-1.1.26/labels_test.go golang-github-miekg-dns-1.1.35/labels_test.go --- golang-github-miekg-dns-1.1.26/labels_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/labels_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -231,6 +231,22 @@ } } +func TestCanonicalName(t *testing.T) { + for s, expect := range map[string]string{ + "": ".", + ".": ".", + "tld": "tld.", + "tld.": "tld.", + "example.test": "example.test.", + "Lower.CASE.test.": "lower.case.test.", + "*.Test": "*.test.", + } { + if got := CanonicalName(s); got != expect { + t.Errorf("CanonicalName(%q) = %q, expected %q", s, got, expect) + } + } +} + func BenchmarkSplitLabels(b *testing.B) { for i := 0; i < b.N; i++ { Split("www.example.com.") diff -Nru golang-github-miekg-dns-1.1.26/length_test.go golang-github-miekg-dns-1.1.35/length_test.go --- golang-github-miekg-dns-1.1.26/length_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/length_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -396,10 +396,19 @@ } func TestMsgCompressLengthLargeRecordsAllValues(t *testing.T) { + // we want to cross the 14 (16384) bit boundary here, so we build it up to just below and go slightly over. msg := new(Msg) msg.Compress = true msg.SetQuestion("redis.service.consul.", TypeSRV) - for i := 0; i < 900; i++ { + for i := 0; i < 170; i++ { + target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) + msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) + msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) + } + // msg.Len() == 15458 + // msg.Len() == 16470 at 180 + + for i := 170; i < 181; i++ { target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: ClassINET, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) diff -Nru golang-github-miekg-dns-1.1.26/msg_generate.go golang-github-miekg-dns-1.1.35/msg_generate.go --- golang-github-miekg-dns-1.1.26/msg_generate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg_generate.go 2021-01-11 04:34:51.000000000 +0000 @@ -10,11 +10,12 @@ "bytes" "fmt" "go/format" - "go/importer" "go/types" "log" "os" "strings" + + "golang.org/x/tools/go/packages" ) var packageHdr = ` @@ -34,6 +35,9 @@ if !ok { return nil, false } + if st.NumFields() == 0 { + return nil, false + } if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { return st, false } @@ -44,9 +48,19 @@ return nil, false } +// loadModule retrieves package description for a given module. +func loadModule(name string) (*types.Package, error) { + conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo} + pkgs, err := packages.Load(&conf, name) + if err != nil { + return nil, err + } + return pkgs[0].Types, nil +} + func main() { // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") + pkg, err := loadModule("github.com/miekg/dns") fatalIfErr(err) scope := pkg.Scope() @@ -99,8 +113,12 @@ o("off, err = packDataOpt(rr.%s, msg, off)\n") case `dns:"nsec"`: o("off, err = packDataNsec(rr.%s, msg, off)\n") + case `dns:"pairs"`: + o("off, err = packDataSVCB(rr.%s, msg, off)\n") case `dns:"domain-name"`: o("off, err = packDataDomainNames(rr.%s, msg, off, compression, false)\n") + case `dns:"apl"`: + o("off, err = packDataApl(rr.%s, msg, off)\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } @@ -223,8 +241,12 @@ o("rr.%s, off, err = unpackDataOpt(msg, off)\n") case `dns:"nsec"`: o("rr.%s, off, err = unpackDataNsec(msg, off)\n") + case `dns:"pairs"`: + o("rr.%s, off, err = unpackDataSVCB(msg, off)\n") case `dns:"domain-name"`: o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n") + case `dns:"apl"`: + o("rr.%s, off, err = unpackDataApl(msg, off)\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } diff -Nru golang-github-miekg-dns-1.1.26/msg.go golang-github-miekg-dns-1.1.35/msg.go --- golang-github-miekg-dns-1.1.26/msg.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg.go 2021-01-11 04:34:51.000000000 +0000 @@ -398,17 +398,12 @@ return "", lenmsg, ErrLongDomain } for _, b := range msg[off : off+c] { - switch b { - case '.', '(', ')', ';', ' ', '@': - fallthrough - case '"', '\\': + if isDomainNameLabelSpecial(b) { s = append(s, '\\', b) - default: - if b < ' ' || b > '~' { // unprintable, use \DDD - s = append(s, escapeByte(b)...) - } else { - s = append(s, b) - } + } else if b < ' ' || b > '~' { + s = append(s, escapeByte(b)...) + } else { + s = append(s, b) } } s = append(s, '.') @@ -661,7 +656,6 @@ } // If offset does not increase anymore, l is a lie if off1 == off { - l = i break } dst = append(dst, r) diff -Nru golang-github-miekg-dns-1.1.26/msg_helpers.go golang-github-miekg-dns-1.1.35/msg_helpers.go --- golang-github-miekg-dns-1.1.26/msg_helpers.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg_helpers.go 2021-01-11 04:34:51.000000000 +0000 @@ -6,6 +6,7 @@ "encoding/binary" "encoding/hex" "net" + "sort" "strings" ) @@ -423,92 +424,47 @@ if off+int(optlen) > len(msg) { return nil, len(msg), &Error{err: "overflow unpacking opt"} } + e := makeDataOpt(code) + if err := e.unpack(msg[off : off+int(optlen)]); err != nil { + return nil, len(msg), err + } + edns = append(edns, e) + off += int(optlen) + + if off < len(msg) { + goto Option + } + + return edns, off, nil +} + +func makeDataOpt(code uint16) EDNS0 { switch code { case EDNS0NSID: - e := new(EDNS0_NSID) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_NSID) case EDNS0SUBNET: - e := new(EDNS0_SUBNET) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_SUBNET) case EDNS0COOKIE: - e := new(EDNS0_COOKIE) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_COOKIE) case EDNS0EXPIRE: - e := new(EDNS0_EXPIRE) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_EXPIRE) case EDNS0UL: - e := new(EDNS0_UL) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_UL) case EDNS0LLQ: - e := new(EDNS0_LLQ) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_LLQ) case EDNS0DAU: - e := new(EDNS0_DAU) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_DAU) case EDNS0DHU: - e := new(EDNS0_DHU) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_DHU) case EDNS0N3U: - e := new(EDNS0_N3U) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_N3U) case EDNS0PADDING: - e := new(EDNS0_PADDING) - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return new(EDNS0_PADDING) default: e := new(EDNS0_LOCAL) e.Code = code - if err := e.unpack(msg[off : off+int(optlen)]); err != nil { - return nil, len(msg), err - } - edns = append(edns, e) - off += int(optlen) + return e } - - if off < len(msg) { - goto Option - } - - return edns, off, nil } func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) { @@ -521,9 +477,7 @@ binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length off += 4 if off+len(b) > len(msg) { - copy(msg[off:], b) - off = len(msg) - continue + return len(msg), &Error{err: "overflow packing opt"} } // Actual data copy(msg[off:off+len(b)], b) @@ -659,6 +613,65 @@ return off, nil } +func unpackDataSVCB(msg []byte, off int) ([]SVCBKeyValue, int, error) { + var xs []SVCBKeyValue + var code uint16 + var length uint16 + var err error + for off < len(msg) { + code, off, err = unpackUint16(msg, off) + if err != nil { + return nil, len(msg), &Error{err: "overflow unpacking SVCB"} + } + length, off, err = unpackUint16(msg, off) + if err != nil || off+int(length) > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking SVCB"} + } + e := makeSVCBKeyValue(SVCBKey(code)) + if e == nil { + return nil, len(msg), &Error{err: "bad SVCB key"} + } + if err := e.unpack(msg[off : off+int(length)]); err != nil { + return nil, len(msg), err + } + if len(xs) > 0 && e.Key() <= xs[len(xs)-1].Key() { + return nil, len(msg), &Error{err: "SVCB keys not in strictly increasing order"} + } + xs = append(xs, e) + off += int(length) + } + return xs, off, nil +} + +func packDataSVCB(pairs []SVCBKeyValue, msg []byte, off int) (int, error) { + pairs = append([]SVCBKeyValue(nil), pairs...) + sort.Slice(pairs, func(i, j int) bool { + return pairs[i].Key() < pairs[j].Key() + }) + prev := svcb_RESERVED + for _, el := range pairs { + if el.Key() == prev { + return len(msg), &Error{err: "repeated SVCB keys are not allowed"} + } + prev = el.Key() + packed, err := el.pack() + if err != nil { + return len(msg), err + } + off, err = packUint16(uint16(el.Key()), msg, off) + if err != nil { + return len(msg), &Error{err: "overflow packing SVCB"} + } + off, err = packUint16(uint16(len(packed)), msg, off) + if err != nil || off+len(packed) > len(msg) { + return len(msg), &Error{err: "overflow packing SVCB"} + } + copy(msg[off:off+len(packed)], packed) + off += len(packed) + } + return off, nil +} + func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) { var ( servers []string @@ -688,3 +701,133 @@ } return off, nil } + +func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) { + var err error + for i := range data { + off, err = packDataAplPrefix(&data[i], msg, off) + if err != nil { + return len(msg), err + } + } + return off, nil +} + +func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) { + if len(p.Network.IP) != len(p.Network.Mask) { + return len(msg), &Error{err: "address and mask lengths don't match"} + } + + var err error + prefix, _ := p.Network.Mask.Size() + addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8] + + switch len(p.Network.IP) { + case net.IPv4len: + off, err = packUint16(1, msg, off) + case net.IPv6len: + off, err = packUint16(2, msg, off) + default: + err = &Error{err: "unrecognized address family"} + } + if err != nil { + return len(msg), err + } + + off, err = packUint8(uint8(prefix), msg, off) + if err != nil { + return len(msg), err + } + + var n uint8 + if p.Negation { + n = 0x80 + } + + // trim trailing zero bytes as specified in RFC3123 Sections 4.1 and 4.2. + i := len(addr) - 1 + for ; i >= 0 && addr[i] == 0; i-- { + } + addr = addr[:i+1] + + adflen := uint8(len(addr)) & 0x7f + off, err = packUint8(n|adflen, msg, off) + if err != nil { + return len(msg), err + } + + if off+len(addr) > len(msg) { + return len(msg), &Error{err: "overflow packing APL prefix"} + } + off += copy(msg[off:], addr) + + return off, nil +} + +func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) { + var result []APLPrefix + for off < len(msg) { + prefix, end, err := unpackDataAplPrefix(msg, off) + if err != nil { + return nil, len(msg), err + } + off = end + result = append(result, prefix) + } + return result, off, nil +} + +func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) { + family, off, err := unpackUint16(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + prefix, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + nlen, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + + var ip []byte + switch family { + case 1: + ip = make([]byte, net.IPv4len) + case 2: + ip = make([]byte, net.IPv6len) + default: + return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"} + } + if int(prefix) > 8*len(ip) { + return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"} + } + afdlen := int(nlen & 0x7f) + if afdlen > len(ip) { + return APLPrefix{}, len(msg), &Error{err: "APL length too long"} + } + if off+afdlen > len(msg) { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"} + } + off += copy(ip, msg[off:off+afdlen]) + if afdlen > 0 { + last := ip[afdlen-1] + if last == 0 { + return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"} + } + } + ipnet := net.IPNet{ + IP: ip, + Mask: net.CIDRMask(int(prefix), 8*len(ip)), + } + network := ipnet.IP.Mask(ipnet.Mask) + if !network.Equal(ipnet.IP) { + return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"} + } + + return APLPrefix{ + Negation: (nlen & 0x80) != 0, + Network: ipnet, + }, off, nil +} diff -Nru golang-github-miekg-dns-1.1.26/msg_helpers_test.go golang-github-miekg-dns-1.1.35/msg_helpers_test.go --- golang-github-miekg-dns-1.1.26/msg_helpers_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg_helpers_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -1,6 +1,10 @@ package dns -import "testing" +import ( + "bytes" + "net" + "testing" +) // TestPacketDataNsec tests generated using fuzz.go and with a message pack // containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000" @@ -153,3 +157,368 @@ } }) } + +func TestPackDataAplPrefix(t *testing.T) { + tests := []struct { + name string + negation bool + ip net.IP + mask net.IPMask + expect []byte + }{ + { + "1:192.0.2.0/24", + false, + net.ParseIP("192.0.2.0").To4(), + net.CIDRMask(24, 32), + []byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2}, + }, + { + "2:2001:db8:cafe::0/48", + false, + net.ParseIP("2001:db8:cafe::"), + net.CIDRMask(48, 128), + []byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe}, + }, + { + "with trailing zero bytes 2:2001:db8:cafe::0/64", + false, + net.ParseIP("2001:db8:cafe::"), + net.CIDRMask(64, 128), + []byte{0x00, 0x02, 0x40, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe}, + }, + { + "no non-zero bytes 2::/16", + false, + net.ParseIP("::"), + net.CIDRMask(16, 128), + []byte{0x00, 0x02, 0x10, 0x00}, + }, + { + "!2:2001:db8::/32", + true, + net.ParseIP("2001:db8::"), + net.CIDRMask(32, 128), + []byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8}, + }, + { + "normalize 1:198.51.103.255/22", + false, + net.ParseIP("198.51.103.255").To4(), + net.CIDRMask(22, 32), + []byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22 + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ap := &APLPrefix{ + Negation: tt.negation, + Network: net.IPNet{IP: tt.ip, Mask: tt.mask}, + } + out := make([]byte, 16) + off, err := packDataAplPrefix(ap, out, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(tt.expect, out[:off]) { + t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off]) + } + // Make sure the packed bytes would be accepted by its own unpack + _, _, err = unpackDataAplPrefix(out, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + }) + } +} + +func TestPackDataAplPrefix_Failures(t *testing.T) { + tests := []struct { + name string + ip net.IP + mask net.IPMask + }{ + { + "family mismatch", + net.ParseIP("2001:db8::"), + net.CIDRMask(24, 32), + }, + { + "unrecognized family", + []byte{0x42}, + []byte{0xff}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}} + msg := make([]byte, 16) + off, err := packDataAplPrefix(ap, msg, 0) + if err == nil { + t.Fatal("expected error, got none") + } + if off != len(msg) { + t.Fatalf("expected %d, got %d", len(msg), off) + } + }) + } +} + +func TestPackDataAplPrefix_BufferBounds(t *testing.T) { + ap := &APLPrefix{ + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8::"), + Mask: net.CIDRMask(32, 128), + }, + } + wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8} + + t.Run("small", func(t *testing.T) { + msg := make([]byte, len(wire)) + _, err := packDataAplPrefix(ap, msg, 1) // offset + if err == nil { + t.Fatal("expected error, got none") + } + }) + + t.Run("exact fit", func(t *testing.T) { + msg := make([]byte, len(wire)) + off, err := packDataAplPrefix(ap, msg, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(wire, msg[:off]) { + t.Fatalf("expected %02x, got %02x", wire, msg[:off]) + } + }) +} + +func TestPackDataApl(t *testing.T) { + in := []APLPrefix{ + APLPrefix{ + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("198.51.0.0").To4(), + Mask: net.CIDRMask(16, 32), + }, + }, + APLPrefix{ + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8:beef::"), + Mask: net.CIDRMask(48, 128), + }, + }, + } + expect := []byte{ + // 1:192.51.0.0/16 + 0x00, 0x01, 0x10, 0x82, 0xc6, 0x33, + // 2:2001:db8:beef::0/48 + 0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef, + } + + msg := make([]byte, 32) + off, err := packDataApl(in, msg, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if !bytes.Equal(expect, msg[:off]) { + t.Fatalf("expected %02x, got %02x", expect, msg[:off]) + } +} + +func TestUnpackDataAplPrefix(t *testing.T) { + tests := []struct { + name string + wire []byte + negation bool + ip net.IP + mask net.IPMask + }{ + { + "1:192.0.2.0/24", + []byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2}, + false, + net.ParseIP("192.0.2.0").To4(), + net.CIDRMask(24, 32), + }, + { + "2:2001:db8::/32", + []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}, + false, + net.ParseIP("2001:db8::"), + net.CIDRMask(32, 128), + }, + { + "!2:2001:db8:8000::/33", + []byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80}, + true, + net.ParseIP("2001:db8:8000::"), + net.CIDRMask(33, 128), + }, + { + "1:0.0.0.0/0", + []byte{0x00, 0x01, 0x00, 0x00}, + false, + net.ParseIP("0.0.0.0").To4(), + net.CIDRMask(0, 32), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, off, err := unpackDataAplPrefix(tt.wire, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if off != len(tt.wire) { + t.Fatalf("expected offset %d, got %d", len(tt.wire), off) + } + if got.Negation != tt.negation { + t.Errorf("expected negation %v, got %v", tt.negation, got.Negation) + } + if !bytes.Equal(got.Network.IP, tt.ip) { + t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP) + } + if !bytes.Equal(got.Network.Mask, tt.mask) { + t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask) + } + }) + } +} + +func TestUnpackDataAplPrefix_Errors(t *testing.T) { + tests := []struct { + name string + wire []byte + }{ + { + "incomplete header", + []byte{0x00, 0x01, 0x18}, + }, + { + "unrecognized family", + []byte{0x00, 0x03, 0x00, 0x00}, + }, + { + "prefix length exceeded", + []byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0}, + }, + { + "address with extra byte", + []byte{0x00, 0x01, 0x10, 0x03, 192, 0, 2}, + }, + { + "incomplete buffer", + []byte{0x00, 0x01, 0x10, 0x02, 192}, + }, + { + "extra bits set", + []byte{0x00, 0x01, 22, 0x03, 192, 0, 2}, + }, + { + "afdlen invalid", + []byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := unpackDataAplPrefix(tt.wire, 0) + if err == nil { + t.Fatal("expected error, got none") + } + }) + } +} + +func TestUnpackDataApl(t *testing.T) { + wire := []byte{ + // 2:2001:db8:cafe:4200:0/56 + 0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42, + // 1:192.0.2.0/24 + 0x00, 0x01, 0x18, 0x03, 192, 0, 2, + // !1:192.0.2.128/25 + 0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128, + // 1:10.0.0.0/24 + 0x00, 0x01, 0x18, 0x01, 0x0a, + // !1:10.0.0.1/32 + 0x00, 0x01, 0x20, 0x84, 0x0a, 0, 0, 1, + // !1:0.0.0.0/0 + 0x00, 0x01, 0x00, 0x80, + // 2::0/0 + 0x00, 0x02, 0x00, 0x00, + } + expect := []APLPrefix{ + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("2001:db8:cafe:4200::"), + Mask: net.CIDRMask(56, 128), + }, + }, + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("192.0.2.0").To4(), + Mask: net.CIDRMask(24, 32), + }, + }, + { + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("192.0.2.128").To4(), + Mask: net.CIDRMask(25, 32), + }, + }, + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("10.0.0.0").To4(), + Mask: net.CIDRMask(24, 32), + }, + }, + { + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("10.0.0.1").To4(), + Mask: net.CIDRMask(32, 32), + }, + }, + { + Negation: true, + Network: net.IPNet{ + IP: net.ParseIP("0.0.0.0").To4(), + Mask: net.CIDRMask(0, 32), + }, + }, + { + Negation: false, + Network: net.IPNet{ + IP: net.ParseIP("::").To16(), + Mask: net.CIDRMask(0, 128), + }, + }, + } + + got, off, err := unpackDataApl(wire, 0) + if err != nil { + t.Fatalf("expected no error, got %q", err) + } + if off != len(wire) { + t.Fatalf("expected offset %d, got %d", len(wire), off) + } + if len(got) != len(expect) { + t.Fatalf("expected %d prefixes, got %d", len(expect), len(got)) + } + for i, exp := range expect { + if got[i].Negation != exp.Negation { + t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation) + } + if !bytes.Equal(got[i].Network.IP, exp.Network.IP) { + t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP) + } + if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) { + t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask) + } + } +} diff -Nru golang-github-miekg-dns-1.1.26/msg_test.go golang-github-miekg-dns-1.1.35/msg_test.go --- golang-github-miekg-dns-1.1.26/msg_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -133,19 +133,19 @@ ".", ""}, {"long label", - string(63) + maxPrintableLabel + "\x00", + "?" + maxPrintableLabel + "\x00", maxPrintableLabel + ".", ""}, {"unprintable label", - string(63) + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel, + "?" + regexp.MustCompile(`\\[0-9]+`).ReplaceAllStringFunc(maxUnprintableLabel, func(escape string) string { n, _ := strconv.ParseInt(escape[1:], 10, 8) - return string(n) + return string(rune(n)) }) + "\x00", maxUnprintableLabel + ".", ""}, {"long domain", - string(53) + strings.Replace(longDomain, ".", string(49), -1) + "\x00", + "5" + strings.Replace(longDomain, ".", "1", -1) + "\x00", longDomain + ".", ""}, {"compression pointer", @@ -154,7 +154,7 @@ "foo.\\003com\\000.example.com.", ""}, {"too long domain", - string(54) + "x" + strings.Replace(longDomain, ".", string(49), -1) + "\x00", + "6" + "x" + strings.Replace(longDomain, ".", "1", -1) + "\x00", "", ErrLongDomain.Error()}, {"too long by pointer", @@ -202,6 +202,7 @@ "\x03foo" + "\x03bar" + "\x07example" + "\xC0\x04", "", ErrLongDomain.Error()}, + {"forward compression pointer", "\x02\xC0\xFF\xC0\x01", "", ErrBuf.Error()}, {"reserved compression pointer 0b10", "\x07example\x80", "", "dns: bad rdata"}, {"reserved compression pointer 0b01", "\x07example\x40", "", "dns: bad rdata"}, } diff -Nru golang-github-miekg-dns-1.1.26/msg_truncate.go golang-github-miekg-dns-1.1.35/msg_truncate.go --- golang-github-miekg-dns-1.1.26/msg_truncate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/msg_truncate.go 2021-01-11 04:34:51.000000000 +0000 @@ -9,7 +9,8 @@ // requested buffer size. // // The TC bit will be set if any records were excluded from the message. -// This indicates to that the client should retry over TCP. +// If the TC bit is already set on the message it will be retained. +// TC indicates that the client should retry over TCP. // // According to RFC 2181, the TC bit should only be set if not all of the // "required" RRs can be included in the response. Unfortunately, we have @@ -28,11 +29,11 @@ } // RFC 6891 mandates that the payload size in an OPT record - // less than 512 bytes must be treated as equal to 512 bytes. + // less than 512 (MinMsgSize) bytes must be treated as equal to 512 bytes. // // For ease of use, we impose that restriction here. - if size < 512 { - size = 512 + if size < MinMsgSize { + size = MinMsgSize } l := msgLenWithCompressionMap(dns, nil) // uncompressed length @@ -73,11 +74,11 @@ var numExtra int if l < size { - l, numExtra = truncateLoop(dns.Extra, size, l, compression) + _, numExtra = truncateLoop(dns.Extra, size, l, compression) } // See the function documentation for when we set this. - dns.Truncated = len(dns.Answer) > numAnswer || + dns.Truncated = dns.Truncated || len(dns.Answer) > numAnswer || len(dns.Ns) > numNS || len(dns.Extra) > numExtra dns.Answer = dns.Answer[:numAnswer] diff -Nru golang-github-miekg-dns-1.1.26/nsecx.go golang-github-miekg-dns-1.1.35/nsecx.go --- golang-github-miekg-dns-1.1.26/nsecx.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/nsecx.go 2021-01-11 04:34:51.000000000 +0000 @@ -43,7 +43,7 @@ return toBase32(nsec3) } -// Cover returns true if a name is covered by the NSEC3 record +// Cover returns true if a name is covered by the NSEC3 record. func (rr *NSEC3) Cover(name string) bool { nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) owner := strings.ToUpper(rr.Hdr.Name) diff -Nru golang-github-miekg-dns-1.1.26/parse_test.go golang-github-miekg-dns-1.1.35/parse_test.go --- golang-github-miekg-dns-1.1.26/parse_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/parse_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -358,12 +358,27 @@ t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String()) } } + rr, err := NewRR("nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F") + if err != nil { + t.Fatal("failed to parse RR: ", err) + } + if nsec3param, ok := rr.(*NSEC3PARAM); ok { + if nsec3param.SaltLength != 8 { + t.Fatalf("nsec3param saltlen %d != 8", nsec3param.SaltLength) + } + } else { + t.Fatal("not nsec3 param: ", err) + } } func TestParseLOC(t *testing.T) { lt := map[string]string{ "SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m", "SW1A2AA.find.me.uk. LOC 51 0 0.0 N 00 07 39.611 W 0.00m 0.00m 0.00m 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 00 0.000 N 00 07 39.611 W 0m 0.00m 0.00m 0.00m", + "SW1A2AA.find.me.uk. LOC 51 30 12.748 N 00 07 39.611 W 0.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t51 30 12.748 N 00 07 39.611 W 0m 1m 10000m 10m", + // Exercise boundary cases + "SW1A2AA.find.me.uk. LOC 90 0 0.0 N 180 0 0.0 W 42849672.95 90000000.00m 90000000.00m 90000000.00m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t90 00 0.000 N 180 00 0.000 W 42849672.95m 90000000m 90000000m 90000000m", + "SW1A2AA.find.me.uk. LOC 89 59 59.999 N 179 59 59.999 W -100000 90000000.00m 90000000.00m 90000000m": "SW1A2AA.find.me.uk.\t3600\tIN\tLOC\t89 59 59.999 N 179 59 59.999 W -100000m 90000000m 90000000m 90000000m", } for i, o := range lt { rr, err := NewRR(i) @@ -375,6 +390,90 @@ t.Errorf("`%s' should be equal to\n`%s', but is `%s'", i, o, rr.String()) } } + + // Invalid cases (out of range values) + lt = map[string]string{ // Pair of (invalid) RDATA and the bad field name + // One of the subfields is out of range. + "91 0 0.0 N 00 07 39.611 W 0m": "Latitude", + "89 60 0.0 N 00 07 39.611 W 0m": "Latitude", + "89 00 60.0 N 00 07 39.611 W 0m": "Latitude", + "1 00 -1 N 00 07 39.611 W 0m": "Latitude", + "0 0 0.0 N 181 00 0.0 W 0m": "Longitude", + "0 0 0.0 N 179 60 0.0 W 0m": "Longitude", + "0 0 0.0 N 179 00 60.0 W 0m": "Longitude", + "0 0 0.0 N 1 00 -1 W 0m": "Longitude", + + // Each subfield is valid, but resulting latitude would be out of range. + "90 01 00.0 N 00 07 39.611 W 0m": "Latitude", + "0 0 0.0 N 180 01 0.0 W 0m": "Longitude", + } + for rdata, field := range lt { + _, err := NewRR(fmt.Sprintf("example.com. LOC %s", rdata)) + if err == nil || !strings.Contains(err.Error(), field) { + t.Errorf("expected error to contain %q, but got %v", field, err) + } + } +} + +// this tests a subroutine for the LOC RR parser. It's complicated enough to test separately. +func TestStringToCm(t *testing.T) { + tests := []struct { + // Test description: the input token and the expected return values from stringToCm. + token string + e uint8 + m uint8 + ok bool + }{ + {"100", 4, 1, true}, + {"0100", 4, 1, true}, // leading 0 (allowed) + {"100.99", 4, 1, true}, + {"90000000", 9, 9, true}, + {"90000000.00", 9, 9, true}, + {"0", 0, 0, true}, + {"0.00", 0, 0, true}, + {"0.01", 0, 1, true}, + {".01", 0, 1, true}, // empty 'meter' part (allowed) + {"0.1", 1, 1, true}, + + // out of range (too large) + {"90000001", 0, 0, false}, + {"90000000.01", 0, 0, false}, + + // more than 2 digits in 'cmeter' part + {"0.000", 0, 0, false}, + {"0.001", 0, 0, false}, + {"0.999", 0, 0, false}, + // with plus or minus sign (disallowed) + {"-100", 0, 0, false}, + {"+100", 0, 0, false}, + {"0.-10", 0, 0, false}, + {"0.+10", 0, 0, false}, + {"0a.00", 0, 0, false}, // invalid string for 'meter' part + {".1x", 0, 0, false}, // invalid string for 'cmeter' part + {".", 0, 0, false}, // empty 'cmeter' part (disallowed) + {"1.", 0, 0, false}, // ditto + {"m", 0, 0, false}, // only the "m" suffix + } + for _, tc := range tests { + tc := tc + t.Run(tc.token, func(t *testing.T) { + // In all cases the expected result is the same with or without the 'm' suffix. + // So we test both cases using the same test code. + for _, sfx := range []string{"", "m"} { + token := tc.token + sfx + e, m, ok := stringToCm(token) + if ok != tc.ok { + t.Fatal("unexpected validation result") + } + if m != tc.m { + t.Fatalf("Expected %d, got %d", tc.m, m) + } + if e != tc.e { + t.Fatalf("Expected %d, got %d", tc.e, e) + } + } + }) + } } func TestParseDS(t *testing.T) { @@ -534,25 +633,25 @@ example.com. DNAME 10 ; TTL=314 after second $TTL ` reCaseFromComment := regexp.MustCompile(`TTL=(\d+)\s+(.*)`) - records := ParseZone(strings.NewReader(zone), "", "") + z := NewZoneParser(strings.NewReader(zone), "", "") var i int - for record := range records { + + for rr, ok := z.Next(); ok; rr, ok = z.Next() { i++ - if record.Error != nil { - t.Error(record.Error) - continue - } - expected := reCaseFromComment.FindStringSubmatch(record.Comment) + expected := reCaseFromComment.FindStringSubmatch(z.Comment()) if len(expected) != 3 { t.Errorf("regexp didn't match for record %d", i) continue } expectedTTL, _ := strconv.ParseUint(expected[1], 10, 32) - ttl := record.RR.Header().Ttl + ttl := rr.Header().Ttl if ttl != uint32(expectedTTL) { t.Errorf("%s: expected TTL %d, got %d", expected[2], expectedTTL, ttl) } } + if err := z.Err(); err != nil { + t.Error(err) + } if i != 10 { t.Errorf("expected %d records, got %d", 5, i) } @@ -591,16 +690,12 @@ }, } for _, errorCase := range badZones { - entries := ParseZone(strings.NewReader(errorCase.zoneContents), "", "") - for entry := range entries { - if entry.Error == nil { - t.Errorf("%s: expected error, got nil", errorCase.label) - continue - } - err := entry.Error.err - if err != errorCase.expectedErr { - t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err) - } + z := NewZoneParser(strings.NewReader(errorCase.zoneContents), "", "") + z.Next() + if err := z.Err(); err == nil { + t.Errorf("%s: expected error, got nil", errorCase.label) + } else if !strings.Contains(err.Error(), errorCase.expectedErr) { + t.Errorf("%s: expected error `%s`, got `%s`", errorCase.label, errorCase.expectedErr, err) } } } @@ -707,9 +802,13 @@ } func TestEmpty(t *testing.T) { - for range ParseZone(strings.NewReader(""), "", "") { + z := NewZoneParser(strings.NewReader(""), "", "") + for _, ok := z.Next(); ok; _, ok = z.Next() { t.Errorf("should be empty") } + if err := z.Err(); err != nil { + t.Error("got an error when it shouldn't") + } } func TestLowercaseTokens(t *testing.T) { @@ -877,18 +976,20 @@ foo. IN NSEC miek.nl. TXT RRSIG NSEC; this is comment 7 foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 ` - for x := range ParseZone(strings.NewReader(zone), ".", "") { - if x.Error == nil { - if x.Comment != "" { - if _, ok := comments[x.Comment]; !ok { - t.Errorf("wrong comment %q", x.Comment) - } + z := NewZoneParser(strings.NewReader(zone), ".", "") + for _, ok := z.Next(); ok; _, ok = z.Next() { + if z.Comment() != "" { + if _, okC := comments[z.Comment()]; !okC { + t.Errorf("wrong comment %q", z.Comment()) } } } + if err := z.Err(); err != nil { + t.Error("got an error when it shouldn't") + } } -func TestParseZoneComments(t *testing.T) { +func TestZoneParserComments(t *testing.T) { for i, test := range []struct { zone string comments []string @@ -963,24 +1064,25 @@ r := strings.NewReader(test.zone) var j int - for r := range ParseZone(r, "", "") { - if r.Error != nil { - t.Fatal(r.Error) - } - + z := NewZoneParser(r, "", "") + for rr, ok := z.Next(); ok; rr, ok = z.Next() { if j >= len(test.comments) { t.Fatalf("too many records for zone %d at %d record, expected %d", i, j+1, len(test.comments)) } - if r.Comment != test.comments[j] { - t.Errorf("invalid comment for record %d:%d %v / %v", i, j, r.RR, r.Error) + if z.Comment() != test.comments[j] { + t.Errorf("invalid comment for record %d:%d %v", i, j, rr) t.Logf("expected %q", test.comments[j]) - t.Logf("got %q", r.Comment) + t.Logf("got %q", z.Comment()) } j++ } + if err := z.Err(); err != nil { + t.Fatal(err) + } + if j != len(test.comments) { t.Errorf("too few records for zone %d, got %d, expected %d", i, j, len(test.comments)) } @@ -1210,8 +1312,8 @@ algorithms := []algorithm{ {ECDSAP256SHA256, 256}, {ECDSAP384SHA384, 384}, - {RSASHA1, 1024}, - {RSASHA256, 2048}, + {RSASHA1, 512}, + {RSASHA256, 512}, {ED25519, 256}, } @@ -1531,6 +1633,70 @@ } } +func TestParseSVCB(t *testing.T) { + svcbs := map[string]string{ + `example.com. 3600 IN SVCB 0 cloudflare.com.`: `example.com. 3600 IN SVCB 0 cloudflare.com.`, + `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn=h2 ipv4hint=3.4.3.2`: `example.com. 3600 IN SVCB 65000 cloudflare.com. alpn="h2" ipv4hint="3.4.3.2"`, + `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000=4\ 3 key65001="\" " key65002 key65003= key65004="" key65005== key65006==\"\" key65007=\254 key65008=\032`: `example.com. 3600 IN SVCB 65000 cloudflare.com. key65000="4\ 3" key65001="\"\ " key65002="" key65003="" key65004="" key65005="=" key65006="=\"\"" key65007="\254" key65008="\ "`, + } + for s, o := range svcbs { + rr, err := NewRR(s) + if err != nil { + t.Error("failed to parse RR: ", err) + continue + } + if rr.String() != o { + t.Errorf("`%s' should be equal to\n`%s', but is `%s'", s, o, rr.String()) + } + } +} + +func TestParseBadSVCB(t *testing.T) { + header := `example.com. 3600 IN HTTPS ` + evils := []string{ + `0 . no-default-alpn`, // aliasform + `65536 . no-default-alpn`, // bad priority + `1 ..`, // bad domain + `1 . no-default-alpn=1`, // value illegal + `1 . key`, // invalid key + `1 . key=`, // invalid key + `1 . =`, // invalid key + `1 . ==`, // invalid key + `1 . =a`, // invalid key + `1 . ""`, // invalid key + `1 . ""=`, // invalid key + `1 . "a"`, // invalid key + `1 . "a"=`, // invalid key + `1 . key1=`, // we know that key + `1 . key65535`, // key reserved + `1 . key065534`, // key can't be padded + `1 . key65534="f`, // unterminated value + `1 . key65534="`, // unterminated value + `1 . key65534=\2`, // invalid numberic escape + `1 . key65534=\24`, // invalid numberic escape + `1 . key65534=\256`, // invalid numberic escape + `1 . key65534=\`, // invalid numberic escape + `1 . key65534=""alpn`, // zQuote ending needs whitespace + `1 . key65534="a"alpn`, // zQuote ending needs whitespace + `1 . ipv6hint=1.1.1.1`, // not ipv6 + `1 . ipv6hint=1:1:1:1`, // not ipv6 + `1 . ipv6hint=a`, // not ipv6 + `1 . ipv4hint=1.1.1.1.1`, // not ipv4 + `1 . ipv4hint=::fc`, // not ipv4 + `1 . ipv4hint=..11`, // not ipv4 + `1 . ipv4hint=a`, // not ipv4 + `1 . port=`, // empty port + `1 . echconfig=YUd`, // bad base64 + } + for _, o := range evils { + _, err := NewRR(header + o) + if err == nil { + t.Error("failed to reject invalid RR: ", header+o) + continue + } + } +} + func TestParseBadNAPTR(t *testing.T) { // Should look like: mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 "S" "SIP+D2U" "" _sip._udp.mplus.ims.vodafone.com. naptr := `mplus.ims.vodafone.com. 3600 IN NAPTR 10 100 S SIP+D2U _sip._udp.mplus.ims.vodafone.com.` @@ -1584,3 +1750,137 @@ t.Fatalf("Expected packet to contain NULL record") } } + +func TestParseAPL(t *testing.T) { + tests := []struct { + name string + in string + expect string + }{ + { + "v4", + ". APL 1:192.0.2.0/24", + ".\t3600\tIN\tAPL\t1:192.0.2.0/24", + }, + { + "v6", + ". APL 2:2001:db8::/32", + ".\t3600\tIN\tAPL\t2:2001:db8::/32", + }, + { + "null v6", + ". APL 2:::/0", + ".\t3600\tIN\tAPL\t2:::/0", + }, + { + "null v4", + ". APL 1:0.0.0.0/0", + ".\t3600\tIN\tAPL\t1:0.0.0.0/0", + }, + { + "full v6", + ". APL 2:::/0", + ".\t3600\tIN\tAPL\t2:::/0", + }, + { + "full v4", + ". APL 1:192.0.2.1/32", + ".\t3600\tIN\tAPL\t1:192.0.2.1/32", + }, + { + "full v6", + ". APL 2:2001:0db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128", + ".\t3600\tIN\tAPL\t2:2001:db8:d2b4:b6ba:50db:49cc:a8d1:5bb1/128", + }, + { + "v4in6", + ". APL 2:::ffff:192.0.2.0/120", + ".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120", + }, + { + "v4in6 v6 syntax", + ". APL 2:::ffff:c000:0200/120", + ".\t3600\tIN\tAPL\t2:::ffff:192.0.2.0/120", + }, + { + "negate", + ". APL !1:192.0.2.0/24", + ".\t3600\tIN\tAPL\t!1:192.0.2.0/24", + }, + { + "multiple", + ". APL 1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::0/48", + ".\t3600\tIN\tAPL\t1:192.0.2.0/24 !1:192.0.2.1/32 2:2001:db8::/32 !2:2001:db8:1::/48", + }, + { + "no address", + ". APL", + ".\t3600\tIN\tAPL\t", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + rr, err := NewRR(tc.in) + if err != nil { + t.Fatalf("failed to parse RR: %s", err) + } + + got := rr.String() + if got != tc.expect { + t.Errorf("expected %q, got %q", tc.expect, got) + } + }) + } +} + +func TestParseAPLErrors(t *testing.T) { + tests := []struct { + name string + in string + }{ + { + "unexpected", + `. APL ""`, + }, + { + "unrecognized family", + ". APL 3:0.0.0.0/0", + }, + { + "malformed family", + ". APL foo:0.0.0.0/0", + }, + { + "malformed address", + ". APL 1:192.0.2/16", + }, + { + "extra bits", + ". APL 2:2001:db8::/0", + }, + { + "address mismatch v2", + ". APL 1:2001:db8::/64", + }, + { + "address mismatch v6", + ". APL 2:192.0.2.1/32", + }, + { + "no prefix", + ". APL 1:192.0.2.1", + }, + { + "no family", + ". APL 0.0.0.0/0", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := NewRR(tc.in) + if err == nil { + t.Fatal("expected error, got none") + } + }) + } +} diff -Nru golang-github-miekg-dns-1.1.26/privaterr.go golang-github-miekg-dns-1.1.35/privaterr.go --- golang-github-miekg-dns-1.1.26/privaterr.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/privaterr.go 2021-01-11 04:34:51.000000000 +0000 @@ -13,7 +13,6 @@ // Pack is used when packing a private RR into a buffer. Pack([]byte) (int, error) // Unpack is used when unpacking a private RR from a buffer. - // TODO(miek): diff. signature than Pack, see edns0.go for instance. Unpack([]byte) (int, error) // Copy copies the Rdata into the PrivateRdata argument. Copy(PrivateRdata) error diff -Nru golang-github-miekg-dns-1.1.26/privaterr_test.go golang-github-miekg-dns-1.1.35/privaterr_test.go --- golang-github-miekg-dns-1.1.26/privaterr_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/privaterr_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -158,9 +158,11 @@ defer dns.PrivateHandleRemove(TypeVERSION) r := strings.NewReader(smallzone) - for x := range dns.ParseZone(r, ".", "") { - if err := x.Error; err != nil { - t.Fatal(err) - } + z := dns.NewZoneParser(r, ".", "") + + for _, ok := z.Next(); ok; _, ok = z.Next() { + } + if err := z.Err(); err != nil { + t.Fatal(err) } } diff -Nru golang-github-miekg-dns-1.1.26/README.md golang-github-miekg-dns-1.1.35/README.md --- golang-github-miekg-dns-1.1.26/README.md 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/README.md 2021-01-11 04:34:51.000000000 +0000 @@ -26,8 +26,8 @@ A not-so-up-to-date-list-that-may-be-actually-current: * https://github.com/coredns/coredns -* https://cloudflare.com * https://github.com/abh/geodns +* https://github.com/baidu/bfe * http://www.statdns.com/ * http://www.dnsinspect.com/ * https://github.com/chuangbo/jianbing-dictionary-dns @@ -41,11 +41,9 @@ * https://github.com/StalkR/dns-reverse-proxy * https://github.com/tianon/rawdns * https://mesosphere.github.io/mesos-dns/ -* https://pulse.turbobytes.com/ * https://github.com/fcambus/statzone * https://github.com/benschw/dns-clb-go * https://github.com/corny/dnscheck for -* https://namesmith.io * https://github.com/miekg/unbound * https://github.com/miekg/exdns * https://dnslookup.org @@ -54,22 +52,23 @@ * https://github.com/mehrdadrad/mylg * https://github.com/bamarni/dockness * https://github.com/fffaraz/microdns -* http://kelda.io * https://github.com/ipdcode/hades * https://github.com/StackExchange/dnscontrol/ * https://www.dnsperf.com/ * https://dnssectest.net/ -* https://dns.apebits.com * https://github.com/oif/apex * https://github.com/jedisct1/dnscrypt-proxy * https://github.com/jedisct1/rpdns * https://github.com/xor-gate/sshfp * https://github.com/rs/dnstrace * https://blitiri.com.ar/p/dnss ([github mirror](https://github.com/albertito/dnss)) -* https://github.com/semihalev/sdns * https://render.com * https://github.com/peterzen/goresolver * https://github.com/folbricht/routedns +* https://domainr.com/ +* https://zonedb.org/ +* https://router7.org/ +* https://github.com/fortio/dnsping Send pull request if you want to be listed here. @@ -127,6 +126,7 @@ * 2915 - NAPTR record * 2929 - DNS IANA Considerations * 3110 - RSASHA1 DNS keys +* 3123 - APL record * 3225 - DO bit (DNSSEC OK) * 340{1,2,3} - NAPTR record * 3445 - Limiting the scope of (DNS)KEY diff -Nru golang-github-miekg-dns-1.1.26/remote_test.go golang-github-miekg-dns-1.1.35/remote_test.go --- golang-github-miekg-dns-1.1.26/remote_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/remote_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -package dns - -import "testing" - -const LinodeAddr = "176.58.119.54:53" - -func TestClientRemote(t *testing.T) { - m := new(Msg) - m.SetQuestion("go.dns.miek.nl.", TypeTXT) - - c := new(Client) - r, _, err := c.Exchange(m, LinodeAddr) - if err != nil { - t.Errorf("failed to exchange: %v", err) - } - if r != nil && r.Rcode != RcodeSuccess { - t.Errorf("failed to get an valid answer\n%v", r) - } -} diff -Nru golang-github-miekg-dns-1.1.26/scan.go golang-github-miekg-dns-1.1.35/scan.go --- golang-github-miekg-dns-1.1.26/scan.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/scan.go 2021-01-11 04:34:51.000000000 +0000 @@ -87,31 +87,18 @@ column int // column in the file } -// Token holds the token that are returned when a zone file is parsed. -type Token struct { - // The scanned resource record when error is not nil. - RR - // When an error occurred, this has the error specifics. - Error *ParseError - // A potential comment positioned after the RR and on the same line. - Comment string -} - // ttlState describes the state necessary to fill in an omitted RR TTL type ttlState struct { ttl uint32 // ttl is the current default TTL isByDirective bool // isByDirective indicates whether ttl was set by a $TTL directive } -// NewRR reads the RR contained in the string s. Only the first RR is -// returned. If s contains no records, NewRR will return nil with no -// error. -// -// The class defaults to IN and TTL defaults to 3600. The full zone -// file syntax like $TTL, $ORIGIN, etc. is supported. +// NewRR reads the RR contained in the string s. Only the first RR is returned. +// If s contains no records, NewRR will return nil with no error. // -// All fields of the returned RR are set, except RR.Header().Rdlength -// which is set to 0. +// The class defaults to IN and TTL defaults to 3600. The full zone file syntax +// like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are +// set, except RR.Header().Rdlength which is set to 0. func NewRR(s string) (RR, error) { if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline return ReadRR(strings.NewReader(s+"\n"), "") @@ -133,70 +120,6 @@ return rr, zp.Err() } -// ParseZone reads a RFC 1035 style zonefile from r. It returns -// Tokens on the returned channel, each consisting of either a -// parsed RR and optional comment or a nil RR and an error. The -// channel is closed by ParseZone when the end of r is reached. -// -// The string file is used in error reporting and to resolve relative -// $INCLUDE directives. The string origin is used as the initial -// origin, as if the file would start with an $ORIGIN directive. -// -// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all -// supported. Note that $GENERATE's range support up to a maximum of -// of 65535 steps. -// -// Basic usage pattern when reading from a string (z) containing the -// zone data: -// -// for x := range dns.ParseZone(strings.NewReader(z), "", "") { -// if x.Error != nil { -// // log.Println(x.Error) -// } else { -// // Do something with x.RR -// } -// } -// -// Comments specified after an RR (and on the same line!) are -// returned too: -// -// foo. IN A 10.0.0.1 ; this is a comment -// -// The text "; this is comment" is returned in Token.Comment. -// Comments inside the RR are returned concatenated along with the -// RR. Comments on a line by themselves are discarded. -// -// To prevent memory leaks it is important to always fully drain the -// returned channel. If an error occurs, it will always be the last -// Token sent on the channel. -// -// Deprecated: New users should prefer the ZoneParser API. -func ParseZone(r io.Reader, origin, file string) chan *Token { - t := make(chan *Token, 10000) - go parseZone(r, origin, file, t) - return t -} - -func parseZone(r io.Reader, origin, file string, t chan *Token) { - defer close(t) - - zp := NewZoneParser(r, origin, file) - zp.SetIncludeAllowed(true) - - for rr, ok := zp.Next(); ok; rr, ok = zp.Next() { - t <- &Token{RR: rr, Comment: zp.Comment()} - } - - if err := zp.Err(); err != nil { - pe, ok := err.(*ParseError) - if !ok { - pe = &ParseError{file: file, err: err.Error()} - } - - t <- &Token{Error: pe} - } -} - // ZoneParser is a parser for an RFC 1035 style zonefile. // // Each parsed RR in the zone is returned sequentially from Next. An @@ -247,7 +170,7 @@ includeDepth uint8 - includeAllowed bool + includeAllowed bool generateDisallowed bool } @@ -1287,11 +1210,29 @@ if cmeters, err = strconv.Atoi(s[1]); err != nil { return } + // There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12'). + // So we simply reject it. + // We also make sure the first character is a digit to reject '+-' signs. + if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' { + return + } + if len(s[1]) == 1 { + // 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm. + cmeters *= 10 + } + if len(s[0]) == 0 { + // This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm). + break + } fallthrough case 1: if meters, err = strconv.Atoi(s[0]); err != nil { return } + // RFC1876 states the max value is 90000000.00. The latter two conditions enforce it. + if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) { + return + } case 0: // huh? return 0, 0, false @@ -1304,13 +1245,10 @@ e = 0 val = cmeters } - for val > 10 { + for val >= 10 { e++ val /= 10 } - if e > 9 { - ok = false - } m = uint8(val) return } @@ -1352,6 +1290,9 @@ // LOC record helper function func locCheckNorth(token string, latitude uint32) (uint32, bool) { + if latitude > 90 * 1000 * 60 * 60 { + return latitude, false + } switch token { case "n", "N": return LOC_EQUATOR + latitude, true @@ -1363,6 +1304,9 @@ // LOC record helper function func locCheckEast(token string, longitude uint32) (uint32, bool) { + if longitude > 180 * 1000 * 60 * 60 { + return longitude, false + } switch token { case "e", "E": return LOC_EQUATOR + longitude, true diff -Nru golang-github-miekg-dns-1.1.26/scan_rr.go golang-github-miekg-dns-1.1.35/scan_rr.go --- golang-github-miekg-dns-1.1.26/scan_rr.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/scan_rr.go 2021-01-11 04:34:51.000000000 +0000 @@ -1,6 +1,7 @@ package dns import ( + "bytes" "encoding/base64" "net" "strconv" @@ -10,15 +11,15 @@ // A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces) // or an error func endingToString(c *zlexer, errstr string) (string, *ParseError) { - var s string + var buffer bytes.Buffer l, _ := c.Next() // zString for l.value != zNewline && l.value != zEOF { if l.err { - return s, &ParseError{"", errstr, l} + return buffer.String(), &ParseError{"", errstr, l} } switch l.value { case zString: - s += l.token + buffer.WriteString(l.token) case zBlank: // Ok default: return "", &ParseError{"", errstr, l} @@ -26,7 +27,7 @@ l, _ = c.Next() } - return s, nil + return buffer.String(), nil } // A remainder of the rdata with embedded spaces, split on unquoted whitespace @@ -403,7 +404,7 @@ if l.err { return &ParseError{"", "bad SOA zone parameter", l} } - if j, e := strconv.ParseUint(l.token, 10, 32); e != nil { + if j, err := strconv.ParseUint(l.token, 10, 32); err != nil { if i == 0 { // Serial must be a number return &ParseError{"", "bad SOA zone parameter", l} @@ -446,16 +447,16 @@ c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { return &ParseError{"", "bad SRV Weight", l} } rr.Weight = uint16(i) c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { return &ParseError{"", "bad SRV Port", l} } rr.Port = uint16(i) @@ -482,8 +483,8 @@ c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { return &ParseError{"", "bad NAPTR Preference", l} } rr.Preference = uint16(i) @@ -581,15 +582,15 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError { // Non zero defaults for LOC record, see RFC 1876, Section 3. - rr.HorizPre = 165 // 10000 - rr.VertPre = 162 // 10 - rr.Size = 18 // 1 + rr.Size = 0x12 // 1e2 cm (1m) + rr.HorizPre = 0x16 // 1e6 cm (10000m) + rr.VertPre = 0x13 // 1e3 cm (10m) ok := false // North l, _ := c.Next() i, e := strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { + if e != nil || l.err || i > 90 { return &ParseError{"", "bad LOC Latitude", l} } rr.Latitude = 1000 * 60 * 60 * uint32(i) @@ -600,15 +601,15 @@ if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { goto East } - i, e = strconv.ParseUint(l.token, 10, 32) - if e != nil || l.err { + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 { return &ParseError{"", "bad LOC Latitude minutes", l} + } else { + rr.Latitude += 1000 * 60 * uint32(i) } - rr.Latitude += 1000 * 60 * uint32(i) c.Next() // zBlank l, _ = c.Next() - if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err { + if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 { return &ParseError{"", "bad LOC Latitude seconds", l} } else { rr.Latitude += uint32(1000 * i) @@ -626,7 +627,7 @@ // East c.Next() // zBlank l, _ = c.Next() - if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err { + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 180 { return &ParseError{"", "bad LOC Longitude", l} } else { rr.Longitude = 1000 * 60 * 60 * uint32(i) @@ -637,14 +638,14 @@ if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { goto Altitude } - if i, e := strconv.ParseUint(l.token, 10, 32); e != nil || l.err { + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 { return &ParseError{"", "bad LOC Longitude minutes", l} } else { rr.Longitude += 1000 * 60 * uint32(i) } c.Next() // zBlank l, _ = c.Next() - if i, e := strconv.ParseFloat(l.token, 32); e != nil || l.err { + if i, err := strconv.ParseFloat(l.token, 32); err != nil || l.err || i < 0 || i >= 60 { return &ParseError{"", "bad LOC Longitude seconds", l} } else { rr.Longitude += uint32(1000 * i) @@ -667,7 +668,7 @@ if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' { l.token = l.token[0 : len(l.token)-1] } - if i, e := strconv.ParseFloat(l.token, 32); e != nil { + if i, err := strconv.ParseFloat(l.token, 64); err != nil { return &ParseError{"", "bad LOC Altitude", l} } else { rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5) @@ -681,23 +682,23 @@ case zString: switch count { case 0: // Size - e, m, ok := stringToCm(l.token) + exp, m, ok := stringToCm(l.token) if !ok { return &ParseError{"", "bad LOC Size", l} } - rr.Size = e&0x0f | m<<4&0xf0 + rr.Size = exp&0x0f | m<<4&0xf0 case 1: // HorizPre - e, m, ok := stringToCm(l.token) + exp, m, ok := stringToCm(l.token) if !ok { return &ParseError{"", "bad LOC HorizPre", l} } - rr.HorizPre = e&0x0f | m<<4&0xf0 + rr.HorizPre = exp&0x0f | m<<4&0xf0 case 2: // VertPre - e, m, ok := stringToCm(l.token) + exp, m, ok := stringToCm(l.token) if !ok { return &ParseError{"", "bad LOC VertPre", l} } - rr.VertPre = e&0x0f | m<<4&0xf0 + rr.VertPre = exp&0x0f | m<<4&0xf0 } count++ case zBlank: @@ -762,7 +763,7 @@ l, _ := c.Next() if v, ok := StringToCertType[l.token]; ok { rr.Type = v - } else if i, e := strconv.ParseUint(l.token, 10, 16); e != nil { + } else if i, err := strconv.ParseUint(l.token, 10, 16); err != nil { return &ParseError{"", "bad CERT Type", l} } else { rr.Type = uint16(i) @@ -778,7 +779,7 @@ l, _ = c.Next() // zString if v, ok := StringToAlgorithm[l.token]; ok { rr.Algorithm = v - } else if i, e := strconv.ParseUint(l.token, 10, 8); e != nil { + } else if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { return &ParseError{"", "bad CERT Algorithm", l} } else { rr.Algorithm = uint8(i) @@ -812,8 +813,8 @@ c.Next() // zBlank l, _ = c.Next() - j, e = strconv.ParseUint(l.token, 10, 16) - if e != nil { + j, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil { // Serial must be a number return &ParseError{"", "bad CSYNC flags", l} } @@ -845,9 +846,7 @@ return nil } -func (rr *SIG) parse(c *zlexer, o string) *ParseError { - return rr.RRSIG.parse(c, o) -} +func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) } func (rr *RRSIG) parse(c *zlexer, o string) *ParseError { l, _ := c.Next() @@ -868,24 +867,24 @@ c.Next() // zBlank l, _ = c.Next() - i, err := strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { return &ParseError{"", "bad RRSIG Algorithm", l} } rr.Algorithm = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, err = strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad RRSIG Labels", l} } rr.Labels = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, err = strconv.ParseUint(l.token, 10, 32) - if err != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 32) + if e2 != nil || l.err { return &ParseError{"", "bad RRSIG OrigTtl", l} } rr.OrigTtl = uint32(i) @@ -894,8 +893,7 @@ l, _ = c.Next() if i, err := StringToTime(l.token); err != nil { // Try to see if all numeric and use it as epoch - if i, err := strconv.ParseInt(l.token, 10, 64); err == nil { - // TODO(miek): error out on > MAX_UINT32, same below + if i, err := strconv.ParseUint(l.token, 10, 32); err == nil { rr.Expiration = uint32(i) } else { return &ParseError{"", "bad RRSIG Expiration", l} @@ -907,7 +905,7 @@ c.Next() // zBlank l, _ = c.Next() if i, err := StringToTime(l.token); err != nil { - if i, err := strconv.ParseInt(l.token, 10, 64); err == nil { + if i, err := strconv.ParseUint(l.token, 10, 32); err == nil { rr.Inception = uint32(i) } else { return &ParseError{"", "bad RRSIG Inception", l} @@ -918,8 +916,8 @@ c.Next() // zBlank l, _ = c.Next() - i, err = strconv.ParseUint(l.token, 10, 16) - if err != nil || l.err { + i, e3 := strconv.ParseUint(l.token, 10, 16) + if e3 != nil || l.err { return &ParseError{"", "bad RRSIG KeyTag", l} } rr.KeyTag = uint16(i) @@ -933,9 +931,9 @@ } rr.SignerName = name - s, e := endingToString(c, "bad RRSIG Signature") - if e != nil { - return e + s, e4 := endingToString(c, "bad RRSIG Signature") + if e4 != nil { + return e4 } rr.Signature = s @@ -985,15 +983,15 @@ rr.Hash = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad NSEC3 Flags", l} } rr.Flags = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { return &ParseError{"", "bad NSEC3 Iterations", l} } rr.Iterations = uint16(i) @@ -1050,22 +1048,22 @@ rr.Hash = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad NSEC3PARAM Flags", l} } rr.Flags = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { return &ParseError{"", "bad NSEC3PARAM Iterations", l} } rr.Iterations = uint16(i) c.Next() l, _ = c.Next() if l.token != "-" { - rr.SaltLength = uint8(len(l.token)) + rr.SaltLength = uint8(len(l.token) / 2) rr.Salt = l.token } return slurpRemainder(c) @@ -1132,15 +1130,15 @@ rr.Algorithm = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad SSHFP Type", l} } rr.Type = uint8(i) c.Next() // zBlank - s, e1 := endingToString(c, "bad SSHFP Fingerprint") - if e1 != nil { - return e1 + s, e2 := endingToString(c, "bad SSHFP Fingerprint") + if e2 != nil { + return e2 } rr.FingerPrint = s return nil @@ -1155,37 +1153,32 @@ rr.Flags = uint16(i) c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad " + typ + " Protocol", l} } rr.Protocol = uint8(i) c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { return &ParseError{"", "bad " + typ + " Algorithm", l} } rr.Algorithm = uint8(i) - s, e1 := endingToString(c, "bad "+typ+" PublicKey") - if e1 != nil { - return e1 + s, e3 := endingToString(c, "bad "+typ+" PublicKey") + if e3 != nil { + return e3 } rr.PublicKey = s return nil } -func (rr *DNSKEY) parse(c *zlexer, o string) *ParseError { - return rr.parseDNSKEY(c, o, "DNSKEY") -} - -func (rr *KEY) parse(c *zlexer, o string) *ParseError { - return rr.parseDNSKEY(c, o, "KEY") -} - -func (rr *CDNSKEY) parse(c *zlexer, o string) *ParseError { - return rr.parseDNSKEY(c, o, "CDNSKEY") -} +func (rr *DNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "DNSKEY") } +func (rr *KEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "KEY") } +func (rr *CDNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "CDNSKEY") } +func (rr *DS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DS") } +func (rr *DLV) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DLV") } +func (rr *CDS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "CDS") } func (rr *RKEY) parse(c *zlexer, o string) *ParseError { l, _ := c.Next() @@ -1196,21 +1189,21 @@ rr.Flags = uint16(i) c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad RKEY Protocol", l} } rr.Protocol = uint8(i) c.Next() // zBlank l, _ = c.Next() // zString - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { return &ParseError{"", "bad RKEY Algorithm", l} } rr.Algorithm = uint8(i) - s, e1 := endingToString(c, "bad RKEY PublicKey") - if e1 != nil { - return e1 + s, e3 := endingToString(c, "bad RKEY PublicKey") + if e3 != nil { + return e3 } rr.PublicKey = s return nil @@ -1243,15 +1236,15 @@ rr.Longitude = l.token c.Next() // zBlank l, _ = c.Next() - _, e = strconv.ParseFloat(l.token, 64) - if e != nil || l.err { + _, e1 := strconv.ParseFloat(l.token, 64) + if e1 != nil || l.err { return &ParseError{"", "bad GPOS Latitude", l} } rr.Latitude = l.token c.Next() // zBlank l, _ = c.Next() - _, e = strconv.ParseFloat(l.token, 64) - if e != nil || l.err { + _, e2 := strconv.ParseFloat(l.token, 64) + if e2 != nil || l.err { return &ParseError{"", "bad GPOS Altitude", l} } rr.Altitude = l.token @@ -1267,7 +1260,7 @@ rr.KeyTag = uint16(i) c.Next() // zBlank l, _ = c.Next() - if i, e = strconv.ParseUint(l.token, 10, 8); e != nil { + if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { tokenUpper := strings.ToUpper(l.token) i, ok := StringToAlgorithm[tokenUpper] if !ok || l.err { @@ -1279,31 +1272,19 @@ } c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad " + typ + " DigestType", l} } rr.DigestType = uint8(i) - s, e1 := endingToString(c, "bad "+typ+" Digest") - if e1 != nil { - return e1 + s, e2 := endingToString(c, "bad "+typ+" Digest") + if e2 != nil { + return e2 } rr.Digest = s return nil } -func (rr *DS) parse(c *zlexer, o string) *ParseError { - return rr.parseDS(c, o, "DS") -} - -func (rr *DLV) parse(c *zlexer, o string) *ParseError { - return rr.parseDS(c, o, "DLV") -} - -func (rr *CDS) parse(c *zlexer, o string) *ParseError { - return rr.parseDS(c, o, "CDS") -} - func (rr *TA) parse(c *zlexer, o string) *ParseError { l, _ := c.Next() i, e := strconv.ParseUint(l.token, 10, 16) @@ -1313,7 +1294,7 @@ rr.KeyTag = uint16(i) c.Next() // zBlank l, _ = c.Next() - if i, e := strconv.ParseUint(l.token, 10, 8); e != nil { + if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { tokenUpper := strings.ToUpper(l.token) i, ok := StringToAlgorithm[tokenUpper] if !ok || l.err { @@ -1325,14 +1306,14 @@ } c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad TA DigestType", l} } rr.DigestType = uint8(i) - s, err := endingToString(c, "bad TA Digest") - if err != nil { - return err + s, e2 := endingToString(c, "bad TA Digest") + if e2 != nil { + return e2 } rr.Digest = s return nil @@ -1347,22 +1328,22 @@ rr.Usage = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad TLSA Selector", l} } rr.Selector = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { return &ParseError{"", "bad TLSA MatchingType", l} } rr.MatchingType = uint8(i) // So this needs be e2 (i.e. different than e), because...??t - s, e2 := endingToString(c, "bad TLSA Certificate") - if e2 != nil { - return e2 + s, e3 := endingToString(c, "bad TLSA Certificate") + if e3 != nil { + return e3 } rr.Certificate = s return nil @@ -1377,22 +1358,22 @@ rr.Usage = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad SMIMEA Selector", l} } rr.Selector = uint8(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 8) - if e != nil || l.err { + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { return &ParseError{"", "bad SMIMEA MatchingType", l} } rr.MatchingType = uint8(i) // So this needs be e2 (i.e. different than e), because...??t - s, e2 := endingToString(c, "bad SMIMEA Certificate") - if e2 != nil { - return e2 + s, e3 := endingToString(c, "bad SMIMEA Certificate") + if e3 != nil { + return e3 } rr.Certificate = s return nil @@ -1469,16 +1450,16 @@ rr.Priority = uint16(i) c.Next() // zBlank l, _ = c.Next() - i, e = strconv.ParseUint(l.token, 10, 16) - if e != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { return &ParseError{"", "bad URI Weight", l} } rr.Weight = uint16(i) c.Next() // zBlank - s, err := endingToTxtSlice(c, "bad URI Target") - if err != nil { - return err + s, e2 := endingToTxtSlice(c, "bad URI Target") + if e2 != nil { + return e2 } if len(s) != 1 { return &ParseError{"", "bad URI Target", l} @@ -1506,9 +1487,9 @@ rr.Preference = uint16(i) c.Next() // zBlank l, _ = c.Next() // zString - u, err := stringToNodeID(l) - if err != nil || l.err { - return err + u, e1 := stringToNodeID(l) + if e1 != nil || l.err { + return e1 } rr.NodeID = u return slurpRemainder(c) @@ -1546,7 +1527,6 @@ return &ParseError{"", "bad LP Fqdn", l} } rr.Fqdn = name - return slurpRemainder(c) } @@ -1559,9 +1539,9 @@ rr.Preference = uint16(i) c.Next() // zBlank l, _ = c.Next() // zString - u, err := stringToNodeID(l) - if err != nil || l.err { - return err + u, e1 := stringToNodeID(l) + if e1 != nil || l.err { + return e1 } rr.Locator64 = u return slurpRemainder(c) @@ -1624,14 +1604,13 @@ return &ParseError{"", "bad PX Mapx400", l} } rr.Mapx400 = mapx400 - return slurpRemainder(c) } func (rr *CAA) parse(c *zlexer, o string) *ParseError { l, _ := c.Next() - i, err := strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { return &ParseError{"", "bad CAA Flag", l} } rr.Flag = uint8(i) @@ -1644,9 +1623,9 @@ rr.Tag = l.token c.Next() // zBlank - s, e := endingToTxtSlice(c, "bad CAA Value") - if e != nil { - return e + s, e1 := endingToTxtSlice(c, "bad CAA Value") + if e1 != nil { + return e1 } if len(s) != 1 { return &ParseError{"", "bad CAA Value", l} @@ -1667,8 +1646,8 @@ // Get the key length and key values l, _ = c.Next() - i, err := strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { return &ParseError{"", "bad TKEY key length", l} } rr.KeySize = uint16(i) @@ -1682,8 +1661,8 @@ // Get the otherdata length and string data l, _ = c.Next() - i, err = strconv.ParseUint(l.token, 10, 8) - if err != nil || l.err { + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { return &ParseError{"", "bad TKEY otherdata length", l} } rr.OtherLen = uint16(i) @@ -1693,6 +1672,71 @@ return &ParseError{"", "bad TKEY otherday", l} } rr.OtherData = l.token + return nil +} + +func (rr *APL) parse(c *zlexer, o string) *ParseError { + var prefixes []APLPrefix + + for { + l, _ := c.Next() + if l.value == zNewline || l.value == zEOF { + break + } + if l.value == zBlank && prefixes != nil { + continue + } + if l.value != zString { + return &ParseError{"", "unexpected APL field", l} + } + + // Expected format: [!]afi:address/prefix + + colon := strings.IndexByte(l.token, ':') + if colon == -1 { + return &ParseError{"", "missing colon in APL field", l} + } + + family, cidr := l.token[:colon], l.token[colon+1:] + + var negation bool + if family != "" && family[0] == '!' { + negation = true + family = family[1:] + } + + afi, e := strconv.ParseUint(family, 10, 16) + if e != nil { + return &ParseError{"", "failed to parse APL family: " + e.Error(), l} + } + var addrLen int + switch afi { + case 1: + addrLen = net.IPv4len + case 2: + addrLen = net.IPv6len + default: + return &ParseError{"", "unrecognized APL family", l} + } + + ip, subnet, e1 := net.ParseCIDR(cidr) + if e1 != nil { + return &ParseError{"", "failed to parse APL address: " + e1.Error(), l} + } + if !ip.Equal(subnet.IP) { + return &ParseError{"", "extra bits in APL address", l} + } + + if len(subnet.IP) != addrLen { + return &ParseError{"", "address mismatch with the APL family", l} + } + + prefixes = append(prefixes, APLPrefix{ + Negation: negation, + Network: *subnet, + }) + } + rr.Prefixes = prefixes return nil } diff -Nru golang-github-miekg-dns-1.1.26/scan_test.go golang-github-miekg-dns-1.1.35/scan_test.go --- golang-github-miekg-dns-1.1.26/scan_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/scan_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -9,7 +9,7 @@ "testing" ) -func TestParseZoneGenerate(t *testing.T) { +func TestZoneParserGenerate(t *testing.T) { zone := "$ORIGIN example.org.\n$GENERATE 10-12 foo${2,3,d} IN A 127.0.0.$" wantRRs := []RR{ @@ -17,22 +17,21 @@ &A{Hdr: RR_Header{Name: "foo013.example.org."}, A: net.ParseIP("127.0.0.11")}, &A{Hdr: RR_Header{Name: "foo014.example.org."}, A: net.ParseIP("127.0.0.12")}, } + wantIdx := 0 - tok := ParseZone(strings.NewReader(zone), "", "") - for x := range tok { + z := NewZoneParser(strings.NewReader(zone), "", "") + + for rr, ok := z.Next(); ok; rr, ok = z.Next() { if wantIdx >= len(wantRRs) { t.Fatalf("expected %d RRs, but got more", len(wantRRs)) } - if x.Error != nil { - t.Fatalf("expected no error, but got %s", x.Error) - } - if got, want := x.RR.Header().Name, wantRRs[wantIdx].Header().Name; got != want { + if got, want := rr.Header().Name, wantRRs[wantIdx].Header().Name; got != want { t.Fatalf("expected name %s, but got %s", want, got) } - a, ok := x.RR.(*A) - if !ok { - t.Fatalf("expected *A RR, but got %T", x.RR) + a, okA := rr.(*A) + if !okA { + t.Fatalf("expected *A RR, but got %T", rr) } if got, want := a.A, wantRRs[wantIdx].(*A).A; !got.Equal(want) { t.Fatalf("expected A with IP %v, but got %v", got, want) @@ -40,12 +39,16 @@ wantIdx++ } + if err := z.Err(); err != nil { + t.Fatalf("expected no error, but got %s", err) + } + if wantIdx != len(wantRRs) { t.Errorf("too few records, expected %d, got %d", len(wantRRs), wantIdx) } } -func TestParseZoneInclude(t *testing.T) { +func TestZoneParserInclude(t *testing.T) { tmpfile, err := ioutil.TempFile("", "dns") if err != nil { @@ -63,18 +66,19 @@ zone := "$ORIGIN example.org.\n$INCLUDE " + tmpfile.Name() + "\nbar\tIN\tA\t127.0.0.2" var got int - tok := ParseZone(strings.NewReader(zone), "", "") - for x := range tok { - if x.Error != nil { - t.Fatalf("expected no error, but got %s", x.Error) - } - switch x.RR.Header().Name { + z := NewZoneParser(strings.NewReader(zone), "", "") + z.SetIncludeAllowed(true) + for rr, ok := z.Next(); ok; _, ok = z.Next() { + switch rr.Header().Name { case "foo.example.org.", "bar.example.org.": default: - t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", x.RR.Header().Name) + t.Fatalf("expected foo.example.org. or bar.example.org., but got %s", rr.Header().Name) } got++ } + if err := z.Err(); err != nil { + t.Fatalf("expected no error, but got %s", err) + } if expected := 2; got != expected { t.Errorf("failed to parse zone after include, expected %d records, got %d", expected, got) @@ -82,17 +86,15 @@ os.Remove(tmpfile.Name()) - tok = ParseZone(strings.NewReader(zone), "", "") - for x := range tok { - if x.Error == nil { - t.Fatalf("expected first token to contain an error but it didn't") - } - if !strings.Contains(x.Error.Error(), "failed to open") || - !strings.Contains(x.Error.Error(), tmpfile.Name()) || - !strings.Contains(x.Error.Error(), "no such file or directory") { - t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, - tmpfile.Name(), x.Error) - } + z = NewZoneParser(strings.NewReader(zone), "", "") + z.SetIncludeAllowed(true) + z.Next() + if err := z.Err(); err == nil || + !strings.Contains(err.Error(), "failed to open") || + !strings.Contains(err.Error(), tmpfile.Name()) || + !strings.Contains(err.Error(), "no such file or directory") { + t.Fatalf(`expected error to contain: "failed to open", %q and "no such file or directory" but got: %s`, + tmpfile.Name(), err) } } @@ -274,16 +276,6 @@ foo. IN TXT "THIS IS TEXT MAN"; this is comment 8 ` -func BenchmarkParseZone(b *testing.B) { - for n := 0; n < b.N; n++ { - for tok := range ParseZone(strings.NewReader(benchZone), "example.org.", "") { - if tok.Error != nil { - b.Fatal(tok.Error) - } - } - } -} - func BenchmarkZoneParser(b *testing.B) { for n := 0; n < b.N; n++ { zp := NewZoneParser(strings.NewReader(benchZone), "example.org.", "") diff -Nru golang-github-miekg-dns-1.1.26/serve_mux.go golang-github-miekg-dns-1.1.35/serve_mux.go --- golang-github-miekg-dns-1.1.26/serve_mux.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/serve_mux.go 2021-01-11 04:34:51.000000000 +0000 @@ -1,7 +1,6 @@ package dns import ( - "strings" "sync" ) @@ -36,7 +35,7 @@ return nil } - q = strings.ToLower(q) + q = CanonicalName(q) var handler Handler for off, end := 0, false; !end; off, end = NextLabel(q, off) { @@ -66,7 +65,7 @@ if mux.z == nil { mux.z = make(map[string]Handler) } - mux.z[Fqdn(pattern)] = handler + mux.z[CanonicalName(pattern)] = handler mux.m.Unlock() } @@ -81,7 +80,7 @@ panic("dns: invalid pattern " + pattern) } mux.m.Lock() - delete(mux.z, Fqdn(pattern)) + delete(mux.z, CanonicalName(pattern)) mux.m.Unlock() } @@ -92,7 +91,7 @@ // are redirected to the parent zone (if that is also registered), // otherwise the child gets the query. // -// If no handler is found, or there is no question, a standard SERVFAIL +// If no handler is found, or there is no question, a standard REFUSED // message is returned func (mux *ServeMux) ServeDNS(w ResponseWriter, req *Msg) { var h Handler @@ -103,7 +102,7 @@ if h != nil { h.ServeDNS(w, req) } else { - HandleFailed(w, req) + handleRefused(w, req) } } diff -Nru golang-github-miekg-dns-1.1.26/server.go golang-github-miekg-dns-1.1.35/server.go --- golang-github-miekg-dns-1.1.26/server.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/server.go 2021-01-11 04:34:51.000000000 +0000 @@ -72,13 +72,22 @@ tsigStatus error tsigRequestMAC string tsigSecret map[string]string // the tsig secrets - udp *net.UDPConn // i/o connection if UDP was used + udp net.PacketConn // i/o connection if UDP was used tcp net.Conn // i/o connection if TCP was used udpSession *SessionUDP // oob data to get egress interface right + pcSession net.Addr // address to use when writing to a generic net.PacketConn writer Writer // writer to output the raw DNS bits } +// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets. +func handleRefused(w ResponseWriter, r *Msg) { + m := new(Msg) + m.SetRcode(r, RcodeRefused) + w.WriteMsg(m) +} + // HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets. +// Deprecated: This function is going away. func HandleFailed(w ResponseWriter, r *Msg) { m := new(Msg) m.SetRcode(r, RcodeServerFailure) @@ -139,12 +148,24 @@ ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) } -// defaultReader is an adapter for the Server struct that implements the Reader interface -// using the readTCP and readUDP func of the embedded Server. +// PacketConnReader is an optional interface that Readers can implement to support using generic net.PacketConns. +type PacketConnReader interface { + Reader + + // ReadPacketConn reads a raw message from a generic net.PacketConn UDP connection. Implementations may + // alter connection properties, for example the read-deadline. + ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) +} + +// defaultReader is an adapter for the Server struct that implements the Reader and +// PacketConnReader interfaces using the readTCP, readUDP and readPacketConn funcs +// of the embedded Server. type defaultReader struct { *Server } +var _ PacketConnReader = defaultReader{} + func (dr defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { return dr.readTCP(conn, timeout) } @@ -153,8 +174,14 @@ return dr.readUDP(conn, timeout) } +func (dr defaultReader) ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) { + return dr.readPacketConn(conn, timeout) +} + // DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader. // Implementations should never return a nil Reader. +// Readers should also implement the optional PacketConnReader interface. +// PacketConnReader is required to use a generic net.PacketConn. type DecorateReader func(Reader) Reader // DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer. @@ -317,24 +344,22 @@ srv.init() - pConn := srv.PacketConn - l := srv.Listener - if pConn != nil { + if srv.PacketConn != nil { // Check PacketConn interface's type is valid and value // is not nil - if t, ok := pConn.(*net.UDPConn); ok && t != nil { + if t, ok := srv.PacketConn.(*net.UDPConn); ok && t != nil { if e := setUDPSocketOptions(t); e != nil { return e } - srv.started = true - unlock() - return srv.serveUDP(t) } + srv.started = true + unlock() + return srv.serveUDP(srv.PacketConn) } - if l != nil { + if srv.Listener != nil { srv.started = true unlock() - return srv.serveTCP(l) + return srv.serveTCP(srv.Listener) } return &Error{err: "bad listeners"} } @@ -438,18 +463,24 @@ } // serveUDP starts a UDP listener for the server. -func (srv *Server) serveUDP(l *net.UDPConn) error { +func (srv *Server) serveUDP(l net.PacketConn) error { defer l.Close() - if srv.NotifyStartedFunc != nil { - srv.NotifyStartedFunc() - } - reader := Reader(defaultReader{srv}) if srv.DecorateReader != nil { reader = srv.DecorateReader(reader) } + lUDP, isUDP := l.(*net.UDPConn) + readerPC, canPacketConn := reader.(PacketConnReader) + if !isUDP && !canPacketConn { + return &Error{err: "PacketConnReader was not implemented on Reader returned from DecorateReader but is required for net.PacketConn"} + } + + if srv.NotifyStartedFunc != nil { + srv.NotifyStartedFunc() + } + var wg sync.WaitGroup defer func() { wg.Wait() @@ -459,7 +490,17 @@ rtimeout := srv.getReadTimeout() // deadline is not used here for srv.isStarted() { - m, s, err := reader.ReadUDP(l, rtimeout) + var ( + m []byte + sPC net.Addr + sUDP *SessionUDP + err error + ) + if isUDP { + m, sUDP, err = reader.ReadUDP(lUDP, rtimeout) + } else { + m, sPC, err = readerPC.ReadPacketConn(l, rtimeout) + } if err != nil { if !srv.isStarted() { return nil @@ -476,7 +517,7 @@ continue } wg.Add(1) - go srv.serveUDPPacket(&wg, m, l, s) + go srv.serveUDPPacket(&wg, m, l, sUDP, sPC) } return nil @@ -538,8 +579,8 @@ } // Serve a new UDP request. -func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u *net.UDPConn, s *SessionUDP) { - w := &response{tsigSecret: srv.TsigSecret, udp: u, udpSession: s} +func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u net.PacketConn, udpSession *SessionUDP, pcSession net.Addr) { + w := &response{tsigSecret: srv.TsigSecret, udp: u, udpSession: udpSession, pcSession: pcSession} if srv.DecorateWriter != nil { w.writer = srv.DecorateWriter(w) } else { @@ -651,6 +692,24 @@ return m, s, nil } +func (srv *Server) readPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) { + srv.lock.RLock() + if srv.started { + // See the comment in readTCP above. + conn.SetReadDeadline(time.Now().Add(timeout)) + } + srv.lock.RUnlock() + + m := srv.udpPool.Get().([]byte) + n, addr, err := conn.ReadFrom(m) + if err != nil { + srv.udpPool.Put(m) + return nil, nil, err + } + m = m[:n] + return m, addr, nil +} + // WriteMsg implements the ResponseWriter.WriteMsg method. func (w *response) WriteMsg(m *Msg) (err error) { if w.closed { @@ -684,7 +743,10 @@ switch { case w.udp != nil: - return WriteToSessionUDP(w.udp, m, w.udpSession) + if u, ok := w.udp.(*net.UDPConn); ok { + return WriteToSessionUDP(u, m, w.udpSession) + } + return w.udp.WriteTo(m, w.pcSession) case w.tcp != nil: if len(m) > MaxMsgSize { return 0, &Error{err: "message too large"} @@ -717,10 +779,12 @@ switch { case w.udpSession != nil: return w.udpSession.RemoteAddr() + case w.pcSession != nil: + return w.pcSession case w.tcp != nil: return w.tcp.RemoteAddr() default: - panic("dns: internal error: udpSession and tcp both nil") + panic("dns: internal error: udpSession, pcSession and tcp are all nil") } } diff -Nru golang-github-miekg-dns-1.1.26/server_test.go golang-github-miekg-dns-1.1.35/server_test.go --- golang-github-miekg-dns-1.1.26/server_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/server_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -35,6 +35,19 @@ w.WriteMsg(m) } +func HelloServerBadThenGoodID(w ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + m.Id++ + + m.Extra = make([]RR, 1) + m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} + w.WriteMsg(m) + + m.Id-- + w.WriteMsg(m) +} + func HelloServerEchoAddrPort(w ResponseWriter, req *Msg) { m := new(Msg) m.SetReply(req) @@ -54,13 +67,7 @@ w.WriteMsg(m) } -func RunLocalUDPServer(laddr string) (*Server, string, error) { - server, l, _, err := RunLocalUDPServerWithFinChan(laddr) - - return server, l, err -} - -func RunLocalUDPServerWithFinChan(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) { +func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) { pc, err := net.ListenPacket("udp", laddr) if err != nil { return nil, "", nil, err @@ -71,15 +78,15 @@ waitLock.Lock() server.NotifyStartedFunc = waitLock.Unlock - // fin must be buffered so the goroutine below won't block - // forever if fin is never read from. This always happens - // in RunLocalUDPServer and can happen in TestShutdownUDP. - fin := make(chan error, 1) - for _, opt := range opts { opt(server) } + // fin must be buffered so the goroutine below won't block + // forever if fin is never read from. This always happens + // if the channel is discarded and can happen in TestShutdownUDP. + fin := make(chan error, 1) + go func() { fin <- server.ActivateAndServe() pc.Close() @@ -89,13 +96,14 @@ return server, pc.LocalAddr().String(), fin, nil } -func RunLocalTCPServer(laddr string) (*Server, string, error) { - server, l, _, err := RunLocalTCPServerWithFinChan(laddr) - - return server, l, err +func RunLocalPacketConnServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) { + return RunLocalUDPServer(laddr, append(opts, func(srv *Server) { + // Make srv.PacketConn opaque to trigger the generic code paths. + srv.PacketConn = struct{ net.PacketConn }{srv.PacketConn} + })...) } -func RunLocalTCPServerWithFinChan(laddr string) (*Server, string, chan error, error) { +func RunLocalTCPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) { l, err := net.Listen("tcp", laddr) if err != nil { return nil, "", nil, err @@ -107,8 +115,11 @@ waitLock.Lock() server.NotifyStartedFunc = waitLock.Unlock - // See the comment in RunLocalUDPServerWithFinChan as to - // why fin must be buffered. + for _, opt := range opts { + opt(server) + } + + // See the comment in RunLocalUDPServer as to why fin must be buffered. fin := make(chan error, 1) go func() { @@ -120,70 +131,69 @@ return server, l.Addr().String(), fin, nil } -func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, error) { - l, err := tls.Listen("tcp", laddr, config) - if err != nil { - return nil, "", err - } - - server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour} - - waitLock := sync.Mutex{} - waitLock.Lock() - server.NotifyStartedFunc = waitLock.Unlock - - go func() { - server.ActivateAndServe() - l.Close() - }() - - waitLock.Lock() - return server, l.Addr().String(), nil +func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan error, error) { + return RunLocalTCPServer(laddr, func(srv *Server) { + srv.Listener = tls.NewListener(srv.Listener, config) + }) } func TestServing(t *testing.T) { - HandleFunc("miek.nl.", HelloServer) - HandleFunc("example.com.", AnotherHelloServer) - defer HandleRemove("miek.nl.") - defer HandleRemove("example.com.") - - s, addrstr, err := RunLocalUDPServer(":0") - if err != nil { - t.Fatalf("unable to run test server: %v", err) - } - defer s.Shutdown() + for _, tc := range []struct { + name string + network string + runServer func(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) + }{ + {"udp", "udp", RunLocalUDPServer}, + {"tcp", "tcp", RunLocalTCPServer}, + {"PacketConn", "udp", RunLocalPacketConnServer}, + } { + t.Run(tc.name, func(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + HandleFunc("example.com.", AnotherHelloServer) + defer HandleRemove("miek.nl.") + defer HandleRemove("example.com.") + + s, addrstr, _, err := tc.runServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() - c := new(Client) - m := new(Msg) - m.SetQuestion("miek.nl.", TypeTXT) - r, _, err := c.Exchange(m, addrstr) - if err != nil || len(r.Extra) == 0 { - t.Fatal("failed to exchange miek.nl", err) - } - txt := r.Extra[0].(*TXT).Txt[0] - if txt != "Hello world" { - t.Error("unexpected result for miek.nl", txt, "!= Hello world") - } + c := &Client{ + Net: tc.network, + } + m := new(Msg) + m.SetQuestion("miek.nl.", TypeTXT) + r, _, err := c.Exchange(m, addrstr) + if err != nil || len(r.Extra) == 0 { + t.Fatal("failed to exchange miek.nl", err) + } + txt := r.Extra[0].(*TXT).Txt[0] + if txt != "Hello world" { + t.Error("unexpected result for miek.nl", txt, "!= Hello world") + } - m.SetQuestion("example.com.", TypeTXT) - r, _, err = c.Exchange(m, addrstr) - if err != nil { - t.Fatal("failed to exchange example.com", err) - } - txt = r.Extra[0].(*TXT).Txt[0] - if txt != "Hello example" { - t.Error("unexpected result for example.com", txt, "!= Hello example") - } + m.SetQuestion("example.com.", TypeTXT) + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Fatal("failed to exchange example.com", err) + } + txt = r.Extra[0].(*TXT).Txt[0] + if txt != "Hello example" { + t.Error("unexpected result for example.com", txt, "!= Hello example") + } - // Test Mixes cased as noticed by Ask. - m.SetQuestion("eXaMplE.cOm.", TypeTXT) - r, _, err = c.Exchange(m, addrstr) - if err != nil { - t.Error("failed to exchange eXaMplE.cOm", err) - } - txt = r.Extra[0].(*TXT).Txt[0] - if txt != "Hello example" { - t.Error("unexpected result for example.com", txt, "!= Hello example") + // Test Mixes cased as noticed by Ask. + m.SetQuestion("eXaMplE.cOm.", TypeTXT) + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Error("failed to exchange eXaMplE.cOm", err) + } + txt = r.Extra[0].(*TXT).Txt[0] + if txt != "Hello example" { + t.Error("unexpected result for example.com", txt, "!= Hello example") + } + }) } } @@ -191,7 +201,7 @@ func TestServeIgnoresZFlag(t *testing.T) { HandleFunc("example.com.", AnotherHelloServer) - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -220,7 +230,7 @@ HandleFunc("example.com.", AnotherHelloServer) opcode := 15 - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -259,7 +269,7 @@ Certificates: []tls.Certificate{cert}, } - s, addrstr, err := RunLocalTLSServer(":0", &config) + s, addrstr, _, err := RunLocalTLSServer(":0", &config) if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -345,7 +355,7 @@ Certificates: []tls.Certificate{cert}, } - s, addrstr, err := RunLocalTLSServer(":0", &config) + s, addrstr, _, err := RunLocalTLSServer(":0", &config) if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -368,7 +378,7 @@ // UDP DNS Server HandleFunc(".", tlsHandlerTLS(false)) defer HandleRemove(".") - s, addrstr, err = RunLocalUDPServer(":0") + s, addrstr, _, err = RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -382,7 +392,7 @@ } // TCP DNS Server - s, addrstr, err = RunLocalTCPServer(":0") + s, addrstr, _, err = RunLocalTCPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -466,7 +476,7 @@ defer HandleRemove("miek.nl.") a := runtime.GOMAXPROCS(4) - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { b.Fatalf("unable to run test server: %v", err) } @@ -491,7 +501,7 @@ HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") a := runtime.GOMAXPROCS(4) - s, addrstr, err := RunLocalUDPServer("[::1]:0") + s, addrstr, _, err := RunLocalUDPServer("[::1]:0") if err != nil { if strings.Contains(err.Error(), "bind: cannot assign requested address") { b.Skip("missing IPv6 support") @@ -528,7 +538,7 @@ HandleFunc("miek.nl.", HelloServerCompress) defer HandleRemove("miek.nl.") a := runtime.GOMAXPROCS(4) - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { b.Fatalf("unable to run test server: %v", err) } @@ -581,7 +591,7 @@ HandleFunc("example.", HelloServerLargeResponse) defer HandleRemove("example.") - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -621,7 +631,7 @@ t.Skip("skipping test in short mode.") } HandleFunc("miek.nl.", HelloServer) - s, addrstr, err := RunLocalUDPServer(":0") + s, addrstr, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -635,7 +645,8 @@ if err != nil { t.Fatal("failed to exchange", err) } - m.Response = true + m.Response = true // this holds up the reply, set short read time out to avoid waiting too long + c.ReadTimeout = 100 * time.Millisecond _, _, err = c.Exchange(m, addrstr) if err == nil { t.Fatal("exchanged response message") @@ -643,7 +654,7 @@ } func TestShutdownTCP(t *testing.T) { - s, _, fin, err := RunLocalTCPServerWithFinChan(":0") + s, _, fin, err := RunLocalTCPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -668,7 +679,7 @@ } func checkInProgressQueriesAtShutdownServer(t *testing.T, srv *Server, addr string, client *Client) { - const requests = 100 + const requests = 15 // enough to make this interesting? TODO: find a proper value var errOnce sync.Once // t.Fail will panic if it's called after the test function has @@ -698,7 +709,7 @@ }) defer HandleRemove("example.com.") - client.Timeout = 10 * time.Second + client.Timeout = 1 * time.Second conns := make([]*Conn, requests) eg := new(errgroup.Group) @@ -774,7 +785,7 @@ } func TestInProgressQueriesAtShutdownTCP(t *testing.T) { - s, addr, _, err := RunLocalTCPServerWithFinChan(":0") + s, addr, _, err := RunLocalTCPServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -793,7 +804,7 @@ Certificates: []tls.Certificate{cert}, } - s, _, err := RunLocalTLSServer(":0", &config) + s, _, _, err := RunLocalTLSServer(":0", &config) if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -813,7 +824,7 @@ Certificates: []tls.Certificate{cert}, } - s, addr, err := RunLocalTLSServer(":0", &config) + s, addr, _, err := RunLocalTLSServer(":0", &config) if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -828,7 +839,6 @@ } func TestHandlerCloseTCP(t *testing.T) { - ln, err := net.Listen("tcp", ":0") if err != nil { panic(err) @@ -873,7 +883,26 @@ } func TestShutdownUDP(t *testing.T) { - s, _, fin, err := RunLocalUDPServerWithFinChan(":0") + s, _, fin, err := RunLocalUDPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + err = s.Shutdown() + if err != nil { + t.Errorf("could not shutdown test UDP server, %v", err) + } + select { + case err := <-fin: + if err != nil { + t.Errorf("error returned from ActivateAndServe, %v", err) + } + case <-time.After(2 * time.Second): + t.Error("could not shutdown test UDP server. Gave up waiting") + } +} + +func TestShutdownPacketConn(t *testing.T) { + s, _, fin, err := RunLocalPacketConnServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -892,7 +921,17 @@ } func TestInProgressQueriesAtShutdownUDP(t *testing.T) { - s, addr, _, err := RunLocalUDPServerWithFinChan(":0") + s, addr, _, err := RunLocalUDPServer(":0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + + c := &Client{Net: "udp"} + checkInProgressQueriesAtShutdownServer(t, s, addr, c) +} + +func TestInProgressQueriesAtShutdownPacketConn(t *testing.T) { + s, addr, _, err := RunLocalPacketConnServer(":0") if err != nil { t.Fatalf("unable to run test server: %v", err) } @@ -905,7 +944,7 @@ var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) - s, _, _, err := RunLocalUDPServerWithFinChan(":0") + s, _, _, err := RunLocalUDPServer(":0") if err != nil { t.Fatalf("could not start server: %s", err) } @@ -968,22 +1007,29 @@ func TestServerRoundtripTsig(t *testing.T) { secret := map[string]string{"test.": "so6ZGir4GPAqINNh9U5c3A=="} - s, addrstr, _, err := RunLocalUDPServerWithFinChan(":0", func(srv *Server) { + s, addrstr, _, err := RunLocalUDPServer(":0", func(srv *Server) { srv.TsigSecret = secret + srv.MsgAcceptFunc = func(dh Header) MsgAcceptAction { + // defaultMsgAcceptFunc does reject UPDATE queries + return MsgAccept + } }) if err != nil { t.Fatalf("unable to run test server: %v", err) } defer s.Shutdown() + handlerFired := make(chan struct{}) HandleFunc("example.com.", func(w ResponseWriter, r *Msg) { + close(handlerFired) + m := new(Msg) m.SetReply(r) if r.IsTsig() != nil { status := w.TsigStatus() if status == nil { // *Msg r has an TSIG record and it was validated - m.SetTsig("test.", HmacMD5, 300, time.Now().Unix()) + m.SetTsig("test.", HmacSHA256, 300, time.Now().Unix()) } else { // *Msg r has an TSIG records and it was not valided t.Errorf("invalid TSIG: %v", status) @@ -991,7 +1037,9 @@ } else { t.Error("missing TSIG") } - w.WriteMsg(m) + if err := w.WriteMsg(m); err != nil { + t.Error("writemsg failed", err) + } }) c := new(Client) @@ -1008,11 +1056,17 @@ Target: "bar.example.com.", }} c.TsigSecret = secret - m.SetTsig("test.", HmacMD5, 300, time.Now().Unix()) + m.SetTsig("test.", HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m, addrstr) if err != nil { t.Fatal("failed to exchange", err) } + select { + case <-handlerFired: + // ok, handler was actually called + default: + t.Error("handler was not called") + } } func TestResponseAfterClose(t *testing.T) { diff -Nru golang-github-miekg-dns-1.1.26/sig0.go golang-github-miekg-dns-1.1.35/sig0.go --- golang-github-miekg-dns-1.1.26/sig0.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/sig0.go 2021-01-11 04:34:51.000000000 +0000 @@ -2,7 +2,6 @@ import ( "crypto" - "crypto/dsa" "crypto/ecdsa" "crypto/rsa" "encoding/binary" @@ -85,7 +84,7 @@ var hash crypto.Hash switch rr.Algorithm { - case DSA, RSASHA1: + case RSASHA1: hash = crypto.SHA1 case RSASHA256, ECDSAP256SHA256: hash = crypto.SHA256 @@ -178,17 +177,6 @@ hashed := hasher.Sum(nil) sig := buf[sigend:] switch k.Algorithm { - case DSA: - pk := k.publicKeyDSA() - sig = sig[1:] - r := new(big.Int).SetBytes(sig[:len(sig)/2]) - s := new(big.Int).SetBytes(sig[len(sig)/2:]) - if pk != nil { - if dsa.Verify(pk, hashed, r, s) { - return nil - } - return ErrSig - } case RSASHA1, RSASHA256, RSASHA512: pk := k.publicKeyRSA() if pk != nil { diff -Nru golang-github-miekg-dns-1.1.26/sig0_test.go golang-github-miekg-dns-1.1.35/sig0_test.go --- golang-github-miekg-dns-1.1.26/sig0_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/sig0_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -19,12 +19,14 @@ keyrr.Hdr.Rrtype = TypeKEY keyrr.Hdr.Class = ClassINET keyrr.Algorithm = alg - keysize := 1024 + keysize := 512 switch alg { case ECDSAP256SHA256: keysize = 256 case ECDSAP384SHA384: keysize = 384 + case RSASHA512: + keysize = 1024 } pk, err := keyrr.Generate(keysize) if err != nil { diff -Nru golang-github-miekg-dns-1.1.26/svcb.go golang-github-miekg-dns-1.1.35/svcb.go --- golang-github-miekg-dns-1.1.26/svcb.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/svcb.go 2021-01-11 04:34:51.000000000 +0000 @@ -0,0 +1,744 @@ +package dns + +import ( + "bytes" + "encoding/binary" + "errors" + "net" + "sort" + "strconv" + "strings" +) + +type SVCBKey uint16 + +// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2. +const ( + SVCB_MANDATORY SVCBKey = 0 + SVCB_ALPN SVCBKey = 1 + SVCB_NO_DEFAULT_ALPN SVCBKey = 2 + SVCB_PORT SVCBKey = 3 + SVCB_IPV4HINT SVCBKey = 4 + SVCB_ECHCONFIG SVCBKey = 5 + SVCB_IPV6HINT SVCBKey = 6 + svcb_RESERVED SVCBKey = 65535 +) + +var svcbKeyToStringMap = map[SVCBKey]string{ + SVCB_MANDATORY: "mandatory", + SVCB_ALPN: "alpn", + SVCB_NO_DEFAULT_ALPN: "no-default-alpn", + SVCB_PORT: "port", + SVCB_IPV4HINT: "ipv4hint", + SVCB_ECHCONFIG: "echconfig", + SVCB_IPV6HINT: "ipv6hint", +} + +var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap) + +func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey { + n := make(map[string]SVCBKey, len(m)) + for u, s := range m { + n[s] = u + } + return n +} + +// String takes the numerical code of an SVCB key and returns its name. +// Returns an empty string for reserved keys. +// Accepts unassigned keys as well as experimental/private keys. +func (key SVCBKey) String() string { + if x := svcbKeyToStringMap[key]; x != "" { + return x + } + if key == svcb_RESERVED { + return "" + } + return "key" + strconv.FormatUint(uint64(key), 10) +} + +// svcbStringToKey returns the numerical code of an SVCB key. +// Returns svcb_RESERVED for reserved/invalid keys. +// Accepts unassigned keys as well as experimental/private keys. +func svcbStringToKey(s string) SVCBKey { + if strings.HasPrefix(s, "key") { + a, err := strconv.ParseUint(s[3:], 10, 16) + // no leading zeros + // key shouldn't be registered + if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" { + return svcb_RESERVED + } + return SVCBKey(a) + } + if key, ok := svcbStringToKeyMap[s]; ok { + return key + } + return svcb_RESERVED +} + +func (rr *SVCB) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{l.token, "bad SVCB priority", l} + } + rr.Priority = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Target = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{l.token, "bad SVCB Target", l} + } + rr.Target = name + + // Values (if any) + l, _ = c.Next() + var xs []SVCBKeyValue + // Helps require whitespace between pairs. + // Prevents key1000="a"key1001=... + canHaveNextKey := true + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zString: + if !canHaveNextKey { + // The key we can now read was probably meant to be + // a part of the last value. + return &ParseError{l.token, "bad SVCB value quotation", l} + } + + // In key=value pairs, value does not have to be quoted unless value + // contains whitespace. And keys don't need to have values. + // Similarly, keys with an equality signs after them don't need values. + // l.token includes at least up to the first equality sign. + idx := strings.IndexByte(l.token, '=') + var key, value string + if idx < 0 { + // Key with no value and no equality sign + key = l.token + } else if idx == 0 { + return &ParseError{l.token, "bad SVCB key", l} + } else { + key, value = l.token[:idx], l.token[idx+1:] + + if value == "" { + // We have a key and an equality sign. Maybe we have nothing + // after "=" or we have a double quote. + l, _ = c.Next() + if l.value == zQuote { + // Only needed when value ends with double quotes. + // Any value starting with zQuote ends with it. + canHaveNextKey = false + + l, _ = c.Next() + switch l.value { + case zString: + // We have a value in double quotes. + value = l.token + l, _ = c.Next() + if l.value != zQuote { + return &ParseError{l.token, "SVCB unterminated value", l} + } + case zQuote: + // There's nothing in double quotes. + default: + return &ParseError{l.token, "bad SVCB value", l} + } + } + } + } + kv := makeSVCBKeyValue(svcbStringToKey(key)) + if kv == nil { + return &ParseError{l.token, "bad SVCB key", l} + } + if err := kv.parse(value); err != nil { + return &ParseError{l.token, err.Error(), l} + } + xs = append(xs, kv) + case zQuote: + return &ParseError{l.token, "SVCB key can't contain double quotes", l} + case zBlank: + canHaveNextKey = true + default: + return &ParseError{l.token, "bad SVCB values", l} + } + l, _ = c.Next() + } + rr.Value = xs + if rr.Priority == 0 && len(xs) > 0 { + return &ParseError{l.token, "SVCB aliasform can't have values", l} + } + return nil +} + +// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys. +func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue { + switch key { + case SVCB_MANDATORY: + return new(SVCBMandatory) + case SVCB_ALPN: + return new(SVCBAlpn) + case SVCB_NO_DEFAULT_ALPN: + return new(SVCBNoDefaultAlpn) + case SVCB_PORT: + return new(SVCBPort) + case SVCB_IPV4HINT: + return new(SVCBIPv4Hint) + case SVCB_ECHCONFIG: + return new(SVCBECHConfig) + case SVCB_IPV6HINT: + return new(SVCBIPv6Hint) + case svcb_RESERVED: + return nil + default: + e := new(SVCBLocal) + e.KeyCode = key + return e + } +} + +// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01). +type SVCB struct { + Hdr RR_Header + Priority uint16 + Target string `dns:"domain-name"` + Value []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is non-zero. +} + +// HTTPS RR. Everything valid for SVCB applies to HTTPS as well. +// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols. +type HTTPS struct { + SVCB +} + +func (rr *HTTPS) String() string { + return rr.SVCB.String() +} + +func (rr *HTTPS) parse(c *zlexer, o string) *ParseError { + return rr.SVCB.parse(c, o) +} + +// SVCBKeyValue defines a key=value pair for the SVCB RR type. +// An SVCB RR can have multiple SVCBKeyValues appended to it. +type SVCBKeyValue interface { + Key() SVCBKey // Key returns the numerical key code. + pack() ([]byte, error) // pack returns the encoded value. + unpack([]byte) error // unpack sets the value. + String() string // String returns the string representation of the value. + parse(string) error // parse sets the value to the given string representation of the value. + copy() SVCBKeyValue // copy returns a deep-copy of the pair. + len() int // len returns the length of value in the wire format. +} + +// SVCBMandatory pair adds to required keys that must be interpreted for the RR +// to be functional. +// Basic use pattern for creating a mandatory option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBMandatory) +// e.Code = []uint16{65403} +// s.Value = append(s.Value, e) +type SVCBMandatory struct { + Code []SVCBKey // Must not include mandatory +} + +func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY } + +func (s *SVCBMandatory) String() string { + str := make([]string, len(s.Code)) + for i, e := range s.Code { + str[i] = e.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBMandatory) pack() ([]byte, error) { + codes := append([]SVCBKey(nil), s.Code...) + sort.Slice(codes, func(i, j int) bool { + return codes[i] < codes[j] + }) + b := make([]byte, 2*len(codes)) + for i, e := range codes { + binary.BigEndian.PutUint16(b[2*i:], uint16(e)) + } + return b, nil +} + +func (s *SVCBMandatory) unpack(b []byte) error { + if len(b)%2 != 0 { + return errors.New("dns: svcbmandatory: value length is not a multiple of 2") + } + codes := make([]SVCBKey, 0, len(b)/2) + for i := 0; i < len(b); i += 2 { + // We assume strictly increasing order. + codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:]))) + } + s.Code = codes + return nil +} + +func (s *SVCBMandatory) parse(b string) error { + str := strings.Split(b, ",") + codes := make([]SVCBKey, 0, len(str)) + for _, e := range str { + codes = append(codes, svcbStringToKey(e)) + } + s.Code = codes + return nil +} + +func (s *SVCBMandatory) len() int { + return 2 * len(s.Code) +} + +func (s *SVCBMandatory) copy() SVCBKeyValue { + return &SVCBMandatory{ + append([]SVCBKey(nil), s.Code...), + } +} + +// SVCBAlpn pair is used to list supported connection protocols. +// Protocol ids can be found at: +// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids +// Basic use pattern for creating an alpn option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBAlpn) +// e.Alpn = []string{"h2", "http/1.1"} +// h.Value = append(o.Value, e) +type SVCBAlpn struct { + Alpn []string +} + +func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN } +func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") } + +func (s *SVCBAlpn) pack() ([]byte, error) { + // Liberally estimate the size of an alpn as 10 octets + b := make([]byte, 0, 10*len(s.Alpn)) + for _, e := range s.Alpn { + if len(e) == 0 { + return nil, errors.New("dns: svcbalpn: empty alpn-id") + } + if len(e) > 255 { + return nil, errors.New("dns: svcbalpn: alpn-id too long") + } + b = append(b, byte(len(e))) + b = append(b, e...) + } + return b, nil +} + +func (s *SVCBAlpn) unpack(b []byte) error { + // Estimate the size of the smallest alpn as 4 bytes + alpn := make([]string, 0, len(b)/4) + for i := 0; i < len(b); { + length := int(b[i]) + i++ + if i+length > len(b) { + return errors.New("dns: svcbalpn: alpn array overflowing") + } + alpn = append(alpn, string(b[i:i+length])) + i += length + } + s.Alpn = alpn + return nil +} + +func (s *SVCBAlpn) parse(b string) error { + s.Alpn = strings.Split(b, ",") + return nil +} + +func (s *SVCBAlpn) len() int { + var l int + for _, e := range s.Alpn { + l += 1 + len(e) + } + return l +} + +func (s *SVCBAlpn) copy() SVCBKeyValue { + return &SVCBAlpn{ + append([]string(nil), s.Alpn...), + } +} + +// SVCBNoDefaultAlpn pair signifies no support for default connection protocols. +// Basic use pattern for creating a no-default-alpn option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBNoDefaultAlpn) +// s.Value = append(s.Value, e) +type SVCBNoDefaultAlpn struct{} + +func (*SVCBNoDefaultAlpn) Key() SVCBKey { return SVCB_NO_DEFAULT_ALPN } +func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue { return &SVCBNoDefaultAlpn{} } +func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil } +func (*SVCBNoDefaultAlpn) String() string { return "" } +func (*SVCBNoDefaultAlpn) len() int { return 0 } + +func (*SVCBNoDefaultAlpn) unpack(b []byte) error { + if len(b) != 0 { + return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") + } + return nil +} + +func (*SVCBNoDefaultAlpn) parse(b string) error { + if len(b) != 0 { + return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") + } + return nil +} + +// SVCBPort pair defines the port for connection. +// Basic use pattern for creating a port option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBPort) +// e.Port = 80 +// s.Value = append(s.Value, e) +type SVCBPort struct { + Port uint16 +} + +func (*SVCBPort) Key() SVCBKey { return SVCB_PORT } +func (*SVCBPort) len() int { return 2 } +func (s *SVCBPort) String() string { return strconv.FormatUint(uint64(s.Port), 10) } +func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} } + +func (s *SVCBPort) unpack(b []byte) error { + if len(b) != 2 { + return errors.New("dns: svcbport: port length is not exactly 2 octets") + } + s.Port = binary.BigEndian.Uint16(b) + return nil +} + +func (s *SVCBPort) pack() ([]byte, error) { + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, s.Port) + return b, nil +} + +func (s *SVCBPort) parse(b string) error { + port, err := strconv.ParseUint(b, 10, 16) + if err != nil { + return errors.New("dns: svcbport: port out of range") + } + s.Port = uint16(port) + return nil +} + +// SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections +// if A and AAAA record responses for SVCB's Target domain haven't been received. +// In that case, optionally, A and AAAA requests can be made, after which the connection +// to the hinted IP address may be terminated and a new connection may be opened. +// Basic use pattern for creating an ipv4hint option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBIPv4Hint) +// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()} +// +// Or +// +// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()} +// h.Value = append(h.Value, e) +type SVCBIPv4Hint struct { + Hint []net.IP +} + +func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT } +func (s *SVCBIPv4Hint) len() int { return 4 * len(s.Hint) } + +func (s *SVCBIPv4Hint) pack() ([]byte, error) { + b := make([]byte, 0, 4*len(s.Hint)) + for _, e := range s.Hint { + x := e.To4() + if x == nil { + return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6") + } + b = append(b, x...) + } + return b, nil +} + +func (s *SVCBIPv4Hint) unpack(b []byte) error { + if len(b) == 0 || len(b)%4 != 0 { + return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4") + } + x := make([]net.IP, 0, len(b)/4) + for i := 0; i < len(b); i += 4 { + x = append(x, net.IP(b[i:i+4])) + } + s.Hint = x + return nil +} + +func (s *SVCBIPv4Hint) String() string { + str := make([]string, len(s.Hint)) + for i, e := range s.Hint { + x := e.To4() + if x == nil { + return "" + } + str[i] = x.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBIPv4Hint) parse(b string) error { + if strings.Contains(b, ":") { + return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6") + } + str := strings.Split(b, ",") + dst := make([]net.IP, len(str)) + for i, e := range str { + ip := net.ParseIP(e).To4() + if ip == nil { + return errors.New("dns: svcbipv4hint: bad ip") + } + dst[i] = ip + } + s.Hint = dst + return nil +} + +func (s *SVCBIPv4Hint) copy() SVCBKeyValue { + return &SVCBIPv4Hint{ + append([]net.IP(nil), s.Hint...), + } +} + +// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx]. +// Basic use pattern for creating an echconfig option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBECHConfig) +// e.ECH = []byte{0xfe, 0x08, ...} +// h.Value = append(h.Value, e) +type SVCBECHConfig struct { + ECH []byte +} + +func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG } +func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) } +func (s *SVCBECHConfig) len() int { return len(s.ECH) } + +func (s *SVCBECHConfig) pack() ([]byte, error) { + return append([]byte(nil), s.ECH...), nil +} + +func (s *SVCBECHConfig) copy() SVCBKeyValue { + return &SVCBECHConfig{ + append([]byte(nil), s.ECH...), + } +} + +func (s *SVCBECHConfig) unpack(b []byte) error { + s.ECH = append([]byte(nil), b...) + return nil +} +func (s *SVCBECHConfig) parse(b string) error { + x, err := fromBase64([]byte(b)) + if err != nil { + return errors.New("dns: svcbechconfig: bad base64 echconfig") + } + s.ECH = x + return nil +} + +// SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections +// if A and AAAA record responses for SVCB's Target domain haven't been received. +// In that case, optionally, A and AAAA requests can be made, after which the +// connection to the hinted IP address may be terminated and a new connection may be opened. +// Basic use pattern for creating an ipv6hint option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBIPv6Hint) +// e.Hint = []net.IP{net.ParseIP("2001:db8::1")} +// h.Value = append(h.Value, e) +type SVCBIPv6Hint struct { + Hint []net.IP +} + +func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT } +func (s *SVCBIPv6Hint) len() int { return 16 * len(s.Hint) } + +func (s *SVCBIPv6Hint) pack() ([]byte, error) { + b := make([]byte, 0, 16*len(s.Hint)) + for _, e := range s.Hint { + if len(e) != net.IPv6len || e.To4() != nil { + return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4") + } + b = append(b, e...) + } + return b, nil +} + +func (s *SVCBIPv6Hint) unpack(b []byte) error { + if len(b) == 0 || len(b)%16 != 0 { + return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16") + } + x := make([]net.IP, 0, len(b)/16) + for i := 0; i < len(b); i += 16 { + ip := net.IP(b[i : i+16]) + if ip.To4() != nil { + return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4") + } + x = append(x, ip) + } + s.Hint = x + return nil +} + +func (s *SVCBIPv6Hint) String() string { + str := make([]string, len(s.Hint)) + for i, e := range s.Hint { + if x := e.To4(); x != nil { + return "" + } + str[i] = e.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBIPv6Hint) parse(b string) error { + if strings.Contains(b, ".") { + return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4") + } + str := strings.Split(b, ",") + dst := make([]net.IP, len(str)) + for i, e := range str { + ip := net.ParseIP(e) + if ip == nil { + return errors.New("dns: svcbipv6hint: bad ip") + } + dst[i] = ip + } + s.Hint = dst + return nil +} + +func (s *SVCBIPv6Hint) copy() SVCBKeyValue { + return &SVCBIPv6Hint{ + append([]net.IP(nil), s.Hint...), + } +} + +// SVCBLocal pair is intended for experimental/private use. The key is recommended +// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER]. +// Basic use pattern for creating a keyNNNNN option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBLocal) +// e.KeyCode = 65400 +// e.Data = []byte("abc") +// h.Value = append(h.Value, e) +type SVCBLocal struct { + KeyCode SVCBKey // Never 65535 or any assigned keys. + Data []byte // All byte sequences are allowed. +} + +func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode } +func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil } +func (s *SVCBLocal) len() int { return len(s.Data) } + +func (s *SVCBLocal) unpack(b []byte) error { + s.Data = append([]byte(nil), b...) + return nil +} + +func (s *SVCBLocal) String() string { + var str strings.Builder + str.Grow(4 * len(s.Data)) + for _, e := range s.Data { + if ' ' <= e && e <= '~' { + switch e { + case '"', ';', ' ', '\\': + str.WriteByte('\\') + str.WriteByte(e) + default: + str.WriteByte(e) + } + } else { + str.WriteString(escapeByte(e)) + } + } + return str.String() +} + +func (s *SVCBLocal) parse(b string) error { + data := make([]byte, 0, len(b)) + for i := 0; i < len(b); { + if b[i] != '\\' { + data = append(data, b[i]) + i++ + continue + } + if i+1 == len(b) { + return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated") + } + if isDigit(b[i+1]) { + if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) { + a, err := strconv.ParseUint(b[i+1:i+4], 10, 8) + if err == nil { + i += 4 + data = append(data, byte(a)) + continue + } + } + return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet") + } else { + data = append(data, b[i+1]) + i += 2 + } + } + s.Data = data + return nil +} + +func (s *SVCBLocal) copy() SVCBKeyValue { + return &SVCBLocal{s.KeyCode, + append([]byte(nil), s.Data...), + } +} + +func (rr *SVCB) String() string { + s := rr.Hdr.String() + + strconv.Itoa(int(rr.Priority)) + " " + + sprintName(rr.Target) + for _, e := range rr.Value { + s += " " + e.Key().String() + "=\"" + e.String() + "\"" + } + return s +} + +// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their +// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function. +func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool { + a = append([]SVCBKeyValue(nil), a...) + b = append([]SVCBKeyValue(nil), b...) + sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() }) + sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() }) + for i, e := range a { + if e.Key() != b[i].Key() { + return false + } + b1, err1 := e.pack() + b2, err2 := b[i].pack() + if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) { + return false + } + } + return true +} diff -Nru golang-github-miekg-dns-1.1.26/svcb_test.go golang-github-miekg-dns-1.1.35/svcb_test.go --- golang-github-miekg-dns-1.1.26/svcb_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/svcb_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -0,0 +1,120 @@ +package dns + +import ( + "testing" +) + +// This tests everything valid about SVCB but parsing. +// Parsing tests belong to parse_test.go. +func TestSVCB(t *testing.T) { + svcbs := []struct { + key string + data string + }{ + {`mandatory`, `alpn,key65000`}, + {`alpn`, `h2,h2c`}, + {`port`, `499`}, + {`ipv4hint`, `3.4.3.2,1.1.1.1`}, + {`no-default-alpn`, ``}, + {`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`}, + {`echconfig`, `YUdWc2JHOD0=`}, + {`key65000`, `4\ 3`}, + {`key65001`, `\"\ `}, + {`key65002`, ``}, + {`key65003`, `=\"\"`}, + {`key65004`, `\254\ \ \030\000`}, + } + + for _, o := range svcbs { + keyCode := svcbStringToKey(o.key) + kv := makeSVCBKeyValue(keyCode) + if kv == nil { + t.Error("failed to parse svc key: ", o.key) + continue + } + if kv.Key() != keyCode { + t.Error("key constant is not in sync: ", keyCode) + continue + } + err := kv.parse(o.data) + if err != nil { + t.Error("failed to parse svc pair: ", o.key) + continue + } + b, err := kv.pack() + if err != nil { + t.Error("failed to pack value of svc pair: ", o.key, err) + continue + } + if len(b) != int(kv.len()) { + t.Errorf("expected packed svc value %s to be of length %d but got %d", o.key, int(kv.len()), len(b)) + } + err = kv.unpack(b) + if err != nil { + t.Error("failed to unpack value of svc pair: ", o.key, err) + continue + } + if str := kv.String(); str != o.data { + t.Errorf("`%s' should be equal to\n`%s', but is `%s'", o.key, o.data, str) + } + } +} + +func TestDecodeBadSVCB(t *testing.T) { + svcbs := []struct { + key SVCBKey + data []byte + }{ + { + key: SVCB_ALPN, + data: []byte{3, 0, 0}, // There aren't three octets after 3 + }, + { + key: SVCB_NO_DEFAULT_ALPN, + data: []byte{0}, + }, + { + key: SVCB_PORT, + data: []byte{}, + }, + { + key: SVCB_IPV4HINT, + data: []byte{0, 0, 0}, + }, + { + key: SVCB_IPV6HINT, + data: []byte{0, 0, 0}, + }, + } + for _, o := range svcbs { + err := makeSVCBKeyValue(SVCBKey(o.key)).unpack(o.data) + if err == nil { + t.Error("accepted invalid svc value with key ", SVCBKey(o.key).String()) + } + } +} + +func TestCompareSVCB(t *testing.T) { + val1 := []SVCBKeyValue{ + &SVCBPort{ + Port: 117, + }, + &SVCBAlpn{ + Alpn: []string{"h2", "h3"}, + }, + } + val2 := []SVCBKeyValue{ + &SVCBAlpn{ + Alpn: []string{"h2", "h3"}, + }, + &SVCBPort{ + Port: 117, + }, + } + if !areSVCBPairArraysEqual(val1, val2) { + t.Error("svcb pairs were compared without sorting") + } + if val1[0].Key() != SVCB_PORT || val2[0].Key() != SVCB_ALPN { + t.Error("original svcb pairs were reordered during comparison") + } +} diff -Nru golang-github-miekg-dns-1.1.26/.travis.yml golang-github-miekg-dns-1.1.35/.travis.yml --- golang-github-miekg-dns-1.1.26/.travis.yml 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/.travis.yml 2021-01-11 04:34:51.000000000 +0000 @@ -2,14 +2,15 @@ sudo: false go: - - "1.12.x" - - "1.13.x" + - 1.14.x + - 1.15.x - tip env: - GO111MODULE=on script: + - go generate ./... && test `git ls-files --modified | wc -l` = 0 - go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... after_success: diff -Nru golang-github-miekg-dns-1.1.26/tsig.go golang-github-miekg-dns-1.1.35/tsig.go --- golang-github-miekg-dns-1.1.26/tsig.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/tsig.go 2021-01-11 04:34:51.000000000 +0000 @@ -2,7 +2,6 @@ import ( "crypto/hmac" - "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" @@ -16,10 +15,13 @@ // HMAC hashing codes. These are transmitted as domain names. const ( - HmacMD5 = "hmac-md5.sig-alg.reg.int." HmacSHA1 = "hmac-sha1." + HmacSHA224 = "hmac-sha224." HmacSHA256 = "hmac-sha256." + HmacSHA384 = "hmac-sha384." HmacSHA512 = "hmac-sha512." + + HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported. ) // TSIG is the RR the holds the transaction signature of a message. @@ -111,32 +113,33 @@ if err != nil { return nil, "", err } - buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) + buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly) + if err != nil { + return nil, "", err + } t := new(TSIG) var h hash.Hash - switch strings.ToLower(rr.Algorithm) { - case HmacMD5: - h = hmac.New(md5.New, rawsecret) + switch CanonicalName(rr.Algorithm) { case HmacSHA1: h = hmac.New(sha1.New, rawsecret) + case HmacSHA224: + h = hmac.New(sha256.New224, rawsecret) case HmacSHA256: h = hmac.New(sha256.New, rawsecret) + case HmacSHA384: + h = hmac.New(sha512.New384, rawsecret) case HmacSHA512: h = hmac.New(sha512.New, rawsecret) default: return nil, "", ErrKeyAlg } h.Write(buf) + // Copy all TSIG fields except MAC and its size, which are filled using the computed digest. + *t = *rr t.MAC = hex.EncodeToString(h.Sum(nil)) t.MACSize = uint16(len(t.MAC) / 2) // Size is half! - t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} - t.Fudge = rr.Fudge - t.TimeSigned = rr.TimeSigned - t.Algorithm = rr.Algorithm - t.OrigId = m.Id - tbuf := make([]byte, Len(t)) off, err := PackRR(t, tbuf, 0, nil, false) if err != nil { @@ -153,6 +156,11 @@ // If the signature does not validate err contains the // error, otherwise it is nil. func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { + return tsigVerify(msg, secret, requestMAC, timersOnly, uint64(time.Now().Unix())) +} + +// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests. +func tsigVerify(msg []byte, secret, requestMAC string, timersOnly bool, now uint64) error { rawsecret, err := fromBase64([]byte(secret)) if err != nil { return err @@ -168,27 +176,21 @@ return err } - buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) - - // Fudge factor works both ways. A message can arrive before it was signed because - // of clock skew. - now := uint64(time.Now().Unix()) - ti := now - tsig.TimeSigned - if now < tsig.TimeSigned { - ti = tsig.TimeSigned - now - } - if uint64(tsig.Fudge) < ti { - return ErrTime + buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly) + if err != nil { + return err } var h hash.Hash - switch strings.ToLower(tsig.Algorithm) { - case HmacMD5: - h = hmac.New(md5.New, rawsecret) + switch CanonicalName(tsig.Algorithm) { case HmacSHA1: h = hmac.New(sha1.New, rawsecret) + case HmacSHA224: + h = hmac.New(sha256.New224, rawsecret) case HmacSHA256: h = hmac.New(sha256.New, rawsecret) + case HmacSHA384: + h = hmac.New(sha512.New384, rawsecret) case HmacSHA512: h = hmac.New(sha512.New, rawsecret) default: @@ -198,11 +200,24 @@ if !hmac.Equal(h.Sum(nil), msgMAC) { return ErrSig } + + // Fudge factor works both ways. A message can arrive before it was signed because + // of clock skew. + // We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis + // instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143. + ti := now - tsig.TimeSigned + if now < tsig.TimeSigned { + ti = tsig.TimeSigned - now + } + if uint64(tsig.Fudge) < ti { + return ErrTime + } + return nil } // Create a wiredata buffer for the MAC calculation. -func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { +func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) ([]byte, error) { var buf []byte if rr.TimeSigned == 0 { rr.TimeSigned = uint64(time.Now().Unix()) @@ -219,7 +234,10 @@ m.MACSize = uint16(len(requestMAC) / 2) m.MAC = requestMAC buf = make([]byte, len(requestMAC)) // long enough - n, _ := packMacWire(m, buf) + n, err := packMacWire(m, buf) + if err != nil { + return nil, err + } buf = buf[:n] } @@ -228,20 +246,26 @@ tsig := new(timerWireFmt) tsig.TimeSigned = rr.TimeSigned tsig.Fudge = rr.Fudge - n, _ := packTimerWire(tsig, tsigvar) + n, err := packTimerWire(tsig, tsigvar) + if err != nil { + return nil, err + } tsigvar = tsigvar[:n] } else { tsig := new(tsigWireFmt) - tsig.Name = strings.ToLower(rr.Hdr.Name) + tsig.Name = CanonicalName(rr.Hdr.Name) tsig.Class = ClassANY tsig.Ttl = rr.Hdr.Ttl - tsig.Algorithm = strings.ToLower(rr.Algorithm) + tsig.Algorithm = CanonicalName(rr.Algorithm) tsig.TimeSigned = rr.TimeSigned tsig.Fudge = rr.Fudge tsig.Error = rr.Error tsig.OtherLen = rr.OtherLen tsig.OtherData = rr.OtherData - n, _ := packTsigWire(tsig, tsigvar) + n, err := packTsigWire(tsig, tsigvar) + if err != nil { + return nil, err + } tsigvar = tsigvar[:n] } @@ -251,7 +275,7 @@ } else { buf = append(msgbuf, tsigvar...) } - return buf + return buf, nil } // Strip the TSIG from the raw message. diff -Nru golang-github-miekg-dns-1.1.26/tsig_test.go golang-github-miekg-dns-1.1.35/tsig_test.go --- golang-github-miekg-dns-1.1.26/tsig_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/tsig_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -2,6 +2,9 @@ import ( "encoding/binary" + "encoding/hex" + "fmt" + "strings" "testing" "time" ) @@ -14,7 +17,7 @@ } func TestTsig(t *testing.T) { - m := newTsig(HmacMD5) + m := newTsig(HmacSHA256) buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) if err != nil { t.Fatal(err) @@ -26,7 +29,7 @@ // TSIG accounts for ID substitution. This means if the message ID is // changed by a forwarder, we should still be able to verify the TSIG. - m = newTsig(HmacMD5) + m = newTsig(HmacSHA256) buf, _, err = TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) if err != nil { t.Fatal(err) @@ -40,7 +43,7 @@ } func TestTsigCase(t *testing.T) { - m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5 + m := newTsig(strings.ToUpper(HmacSHA256)) buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) if err != nil { t.Fatal(err) @@ -50,3 +53,187 @@ t.Fatal(err) } } + +const ( + // A template wire-format DNS message (in hex form) containing a TSIG RR. + // Its time signed field will be filled by tests. + wireMsg = "c60028000001000000010001076578616d706c6503636f6d00000600010161c00c0001000100000e100004c0000201077465" + + "73746b65790000fa00ff00000000003d0b686d61632d73686132353600" + + "%012x" + // placeholder for the "time signed" field + "012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000" + // A secret (in base64 format) with which the TSIG in wireMsg will be validated + testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk=" + // the 'time signed' field value that would make the TSIG RR valid with testSecret + timeSigned uint64 = 1594855491 +) + +func TestTsigErrors(t *testing.T) { + // Helper shortcut to build wire-format test message. + // TsigVerify can modify the slice, so we need to create a new one for each test case below. + buildMsgData := func(tm uint64) []byte { + msgData, err := hex.DecodeString(fmt.Sprintf(wireMsg, tm)) + if err != nil { + t.Fatal(err) + } + return msgData + } + + // the signature is valid but 'time signed' is too far from the "current time". + if err := tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned+301); err != ErrTime { + t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err) + } + if err := tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned-301); err != ErrTime { + t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err) + } + + // the signature is invalid and 'time signed' is too far. + // the signature should be checked first, so we should see ErrSig. + if err := tsigVerify(buildMsgData(timeSigned+301), testSecret, "", false, timeSigned); err != ErrSig { + t.Fatalf("expected an error '%v' but got '%v'", ErrSig, err) + } + + // tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error. + msgData := buildMsgData(timeSigned) + copy(msgData[67:], "bogus") + if err := tsigVerify(msgData, testSecret, "", false, timeSigned); err != ErrKeyAlg { + t.Fatalf("expected an error '%v' but got '%v'", ErrKeyAlg, err) + } + + // call TsigVerify with a message that doesn't contain a TSIG + msgData, tsig, err := stripTsig(buildMsgData(timeSigned)) + if err != nil { + t.Fatal(err) + } + if err := tsigVerify(msgData, testSecret, "", false, timeSigned); err != ErrNoSig { + t.Fatalf("expected an error '%v' but got '%v'", ErrNoSig, err) + } + + // replace the test TSIG with a bogus one with large "other data", which would cause overflow in TsigVerify. + // The overflow should be caught without disruption. + tsig.OtherData = strings.Repeat("00", 4096) + tsig.OtherLen = uint16(len(tsig.OtherData) / 2) + msg := new(Msg) + if err = msg.Unpack(msgData); err != nil { + t.Fatal(err) + } + msg.Extra = append(msg.Extra, tsig) + if msgData, err = msg.Pack(); err != nil { + t.Fatal(err) + } + err = tsigVerify(msgData, testSecret, "", false, timeSigned) + if err == nil || !strings.Contains(err.Error(), "overflow") { + t.Errorf("expected error to contain %q, but got %v", "overflow", err) + } +} + +// This test exercises some more corner cases for TsigGenerate. +func TestTsigGenerate(t *testing.T) { + // This is a template TSIG to be used for signing. + tsig := TSIG{ + Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, + Algorithm: HmacSHA256, + TimeSigned: timeSigned, + Fudge: 300, + OrigId: 42, + Error: RcodeBadTime, // use a non-0 value to make sure it's indeed used + } + + tests := []struct { + desc string // test description + requestMAC string // request MAC to be passed to TsigGenerate (arbitrary choice) + otherData string // other data specified in the TSIG (arbitrary choice) + expectedMAC string // pre-computed expected (correct) MAC in hex form + }{ + {"with request MAC", "3684c225", "", + "c110e3f62694755c10761dc8717462431ee34340b7c9d1eee09449150757c5b1"}, + {"no request MAC", "", "", + "385449a425c6d52b9bf2c65c0726eefa0ad8084cdaf488f24547e686605b9610"}, + {"with other data", "3684c225", "666f6f", + "15b91571ca80b3b410a77e2b44f8cc4f35ace22b26020138439dd94803e23b5d"}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + // Build TSIG for signing from the template + testTSIG := tsig + testTSIG.OtherLen = uint16(len(tc.otherData) / 2) + testTSIG.OtherData = tc.otherData + req := &Msg{ + MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, + Question: []Question{Question{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, + Extra: []RR{&testTSIG}, + } + + // Call generate, and check the returned MAC against the expected value + msgData, mac, err := TsigGenerate(req, testSecret, tc.requestMAC, false) + if err != nil { + t.Error(err) + } + if mac != tc.expectedMAC { + t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, mac) + } + + // Retrieve the TSIG to be sent out, confirm the MAC in it + _, outTSIG, err := stripTsig(msgData) + if err != nil { + t.Error(err) + } + if outTSIG.MAC != tc.expectedMAC { + t.Fatalf("MAC doesn't match: expected '%s', but got '%s'", tc.expectedMAC, outTSIG.MAC) + } + // Confirm other fields of MAC. + // RDLENGTH should be valid as stripTsig succeeded, so we exclude it from comparison + outTSIG.MACSize = 0 + outTSIG.MAC = "" + testTSIG.Hdr.Rdlength = outTSIG.Hdr.Rdlength + if *outTSIG != testTSIG { + t.Fatalf("TSIG RR doesn't match: expected '%v', but got '%v'", *outTSIG, testTSIG) + } + }) + } +} + +func TestTSIGHMAC224And384(t *testing.T) { + tests := []struct { + algorithm string // TSIG algorithm, also used as test description + secret string // (arbitrarily chosen) secret suitable for the algorithm in base64 format + expectedMAC string // pre-computed expected (correct) MAC in hex form + }{ + {HmacSHA224, "hVEkQuAqnTmBuRrT9KF1Udr91gOMGWPw9LaTtw==", + "d6daf9ea189e48bc38f9aed63d6cc4140cdfa38a7a333ee2eefdbd31", + }, + {HmacSHA384, "Qjer2TL2lAdpq9w6Gjs98/ClCQx/L3vtgVHCmrZ8l/oKEPjqUUMFO18gMCRwd5H4", + "89a48936d29187870c325cbdba5ad71609bd038d0459d6010c844d659c570e881d3650e4fe7310be53ebe5178d0d1001", + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.algorithm, func(t *testing.T) { + // Build a DNS message with TSIG for the test scenario + tsig := TSIG{ + Hdr: RR_Header{Name: "testkey.", Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}, + Algorithm: tc.algorithm, + TimeSigned: timeSigned, + Fudge: 300, + OrigId: 42, + } + req := &Msg{ + MsgHdr: MsgHdr{Opcode: OpcodeUpdate}, + Question: []Question{Question{Name: "example.com.", Qtype: TypeSOA, Qclass: ClassINET}}, + Extra: []RR{&tsig}, + } + + // Confirm both Generate and Verify recognize the algorithm and handle it correctly + msgData, mac, err := TsigGenerate(req, tc.secret, "", false) + if err != nil { + t.Error(err) + } + if mac != tc.expectedMAC { + t.Fatalf("MAC doesn't match: expected '%s' but got '%s'", tc.expectedMAC, mac) + } + if err = tsigVerify(msgData, tc.secret, "", false, timeSigned); err != nil { + t.Error(err) + } + }) + } +} diff -Nru golang-github-miekg-dns-1.1.26/types_generate.go golang-github-miekg-dns-1.1.35/types_generate.go --- golang-github-miekg-dns-1.1.26/types_generate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/types_generate.go 2021-01-11 04:34:51.000000000 +0000 @@ -11,12 +11,13 @@ "bytes" "fmt" "go/format" - "go/importer" "go/types" "log" "os" "strings" "text/template" + + "golang.org/x/tools/go/packages" ) var skipLen = map[string]struct{}{ @@ -71,6 +72,9 @@ if !ok { return nil, false } + if st.NumFields() == 0 { + return nil, false + } if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { return st, false } @@ -81,9 +85,19 @@ return nil, false } +// loadModule retrieves package description for a given module. +func loadModule(name string) (*types.Package, error) { + conf := packages.Config{Mode: packages.NeedTypes | packages.NeedTypesInfo} + pkgs, err := packages.Load(&conf, name) + if err != nil { + return nil, err + } + return pkgs[0].Types, nil +} + func main() { // Import and type-check the package - pkg, err := importer.Default().Import("github.com/miekg/dns") + pkg, err := loadModule("github.com/miekg/dns") fatalIfErr(err) scope := pkg.Scope() @@ -168,6 +182,10 @@ o("for _, x := range rr.%s { l += domainNameLen(x, off+l, compression, false) }\n") case `dns:"txt"`: o("for _, x := range rr.%s { l += len(x) + 1 }\n") + case `dns:"apl"`: + o("for _, x := range rr.%s { l += x.len() }\n") + case `dns:"pairs"`: + o("for _, x := range rr.%s { l += 4 + int(x.len()) }\n") default: log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) } @@ -228,11 +246,15 @@ for _, name := range namedTypes { o := scope.Lookup(name) st, isEmbedded := getTypeStruct(o.Type(), scope) + fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) + fields := make([]string, 0, st.NumFields()) if isEmbedded { - continue + a, _ := o.Type().Underlying().(*types.Struct) + parent := a.Field(0).Name() + fields = append(fields, "*rr."+parent+".copy().(*"+parent+")") + goto WriteCopy } - fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) - fields := []string{"rr.Hdr"} + fields = append(fields, "rr.Hdr") for i := 1; i < st.NumFields(); i++ { f := st.Field(i).Name() if sl, ok := st.Field(i).Type().(*types.Slice); ok { @@ -249,6 +271,18 @@ fields = append(fields, f) continue } + if t == "APLPrefix" { + fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n", + f, t, f, f, f) + fields = append(fields, f) + continue + } + if t == "SVCBKeyValue" { + fmt.Fprintf(b, "%s := make([]%s, len(rr.%s));\nfor i,e := range rr.%s {\n %s[i] = e.copy()\n}\n", + f, t, f, f, f) + fields = append(fields, f) + continue + } fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", f, t, f, f, f) fields = append(fields, f) @@ -260,6 +294,7 @@ } fields = append(fields, "rr."+f) } + WriteCopy: fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ",")) fmt.Fprintf(b, "}\n") } diff -Nru golang-github-miekg-dns-1.1.26/types.go golang-github-miekg-dns-1.1.35/types.go --- golang-github-miekg-dns-1.1.26/types.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/types.go 2021-01-11 04:34:51.000000000 +0000 @@ -1,6 +1,7 @@ package dns import ( + "bytes" "fmt" "net" "strconv" @@ -61,6 +62,7 @@ TypeCERT uint16 = 37 TypeDNAME uint16 = 39 TypeOPT uint16 = 41 // EDNS + TypeAPL uint16 = 42 TypeDS uint16 = 43 TypeSSHFP uint16 = 44 TypeRRSIG uint16 = 46 @@ -79,6 +81,8 @@ TypeCDNSKEY uint16 = 60 TypeOPENPGPKEY uint16 = 61 TypeCSYNC uint16 = 62 + TypeSVCB uint16 = 64 + TypeHTTPS uint16 = 65 TypeSPF uint16 = 99 TypeUINFO uint16 = 100 TypeUID uint16 = 101 @@ -163,11 +167,11 @@ _RD = 1 << 8 // recursion desired _RA = 1 << 7 // recursion available _Z = 1 << 6 // Z - _AD = 1 << 5 // authticated data + _AD = 1 << 5 // authenticated data _CD = 1 << 4 // checking disabled ) -// Various constants used in the LOC RR, See RFC 1887. +// Various constants used in the LOC RR. See RFC 1887. const ( LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2. LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2. @@ -207,8 +211,11 @@ //go:generate go run types_generate.go -// Question holds a DNS question. There can be multiple questions in the -// question section of a message. Usually there is just one. +// Question holds a DNS question. Usually there is just one. While the +// original DNS RFCs allow multiple questions in the question section of a +// message, in practice it never works. Because most DNS servers see multiple +// questions as an error, it is recommended to only have one question per +// message. type Question struct { Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed) Qtype uint16 @@ -229,7 +236,7 @@ return s } -// ANY is a wildcard record. See RFC 1035, Section 3.2.3. ANY +// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY // is named "*" there. type ANY struct { Hdr RR_Header @@ -440,45 +447,38 @@ var dst strings.Builder for i := 0; i < len(s); { - if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' { + if s[i] == '.' { if dst.Len() != 0 { - dst.WriteString(s[i : i+2]) + dst.WriteByte('.') } - i += 2 + i++ continue } b, n := nextByte(s, i) if n == 0 { - i++ - continue - } - if b == '.' { - if dst.Len() != 0 { - dst.WriteByte('.') + // Drop "dangling" incomplete escapes. + if dst.Len() == 0 { + return s[:i] } - i += n - continue + break } - switch b { - case ' ', '\'', '@', ';', '(', ')', '"', '\\': // additional chars to escape + if isDomainNameLabelSpecial(b) { if dst.Len() == 0 { dst.Grow(len(s) * 2) dst.WriteString(s[:i]) } dst.WriteByte('\\') dst.WriteByte(b) - default: - if ' ' <= b && b <= '~' { - if dst.Len() != 0 { - dst.WriteByte(b) - } - } else { - if dst.Len() == 0 { - dst.Grow(len(s) * 2) - dst.WriteString(s[:i]) - } - dst.WriteString(escapeByte(b)) + } else if b < ' ' || b > '~' { // unprintable, use \DDD + if dst.Len() == 0 { + dst.Grow(len(s) * 2) + dst.WriteString(s[:i]) + } + dst.WriteString(escapeByte(b)) + } else { + if dst.Len() != 0 { + dst.WriteByte(b) } } i += n @@ -501,15 +501,10 @@ } b, n := nextByte(s, i) - switch { - case n == 0: + if n == 0 { i++ // dangling back slash - case b == '.': - dst.WriteByte('.') - case b < ' ' || b > '~': - dst.WriteString(escapeByte(b)) - default: - dst.WriteByte(b) + } else { + writeTXTStringByte(&dst, b) } i += n } @@ -585,6 +580,17 @@ return escapedByteLarge[int(b)*4 : int(b)*4+4] } +// isDomainNameLabelSpecial returns true if +// a domain name label byte should be prefixed +// with an escaping backslash. +func isDomainNameLabelSpecial(b byte) bool { + switch b { + case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\': + return true + } + return false +} + func nextByte(s string, offset int) (byte, int) { if offset >= len(s) { return 0, 0 @@ -757,8 +763,8 @@ Altitude uint32 } -// cmToM takes a cm value expressed in RFC1876 SIZE mantissa/exponent -// format and returns a string in m (two decimals for the cm) +// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent +// format and returns a string in m (two decimals for the cm). func cmToM(m, e uint8) string { if e < 2 { if e == 1 { @@ -1116,6 +1122,7 @@ Target string `dns:"octet"` } +// rr.Target to be parsed as a sequence of character encoded octets according to RFC 3986 func (rr *URI) String() string { return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) + " " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target) @@ -1277,6 +1284,7 @@ Value string `dns:"octet"` } +// rr.Value Is the character-string encoding of the value field as specified in RFC 1035, Section 5.1. func (rr *CAA) String() string { return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value) } @@ -1353,6 +1361,88 @@ return l } +// APL RR. See RFC 3123. +type APL struct { + Hdr RR_Header + Prefixes []APLPrefix `dns:"apl"` +} + +// APLPrefix is an address prefix hold by an APL record. +type APLPrefix struct { + Negation bool + Network net.IPNet +} + +// String returns presentation form of the APL record. +func (rr *APL) String() string { + var sb strings.Builder + sb.WriteString(rr.Hdr.String()) + for i, p := range rr.Prefixes { + if i > 0 { + sb.WriteByte(' ') + } + sb.WriteString(p.str()) + } + return sb.String() +} + +// str returns presentation form of the APL prefix. +func (p *APLPrefix) str() string { + var sb strings.Builder + if p.Negation { + sb.WriteByte('!') + } + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteByte('1') + case net.IPv6len: + sb.WriteByte('2') + } + + sb.WriteByte(':') + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteString(p.Network.IP.String()) + case net.IPv6len: + // add prefix for IPv4-mapped IPv6 + if v4 := p.Network.IP.To4(); v4 != nil { + sb.WriteString("::ffff:") + } + sb.WriteString(p.Network.IP.String()) + } + + sb.WriteByte('/') + + prefix, _ := p.Network.Mask.Size() + sb.WriteString(strconv.Itoa(prefix)) + + return sb.String() +} + +// equals reports whether two APL prefixes are identical. +func (a *APLPrefix) equals(b *APLPrefix) bool { + return a.Negation == b.Negation && + bytes.Equal(a.Network.IP, b.Network.IP) && + bytes.Equal(a.Network.Mask, b.Network.Mask) +} + +// copy returns a copy of the APL prefix. +func (p *APLPrefix) copy() APLPrefix { + return APLPrefix{ + Negation: p.Negation, + Network: copyNet(p.Network), + } +} + +// len returns size of the prefix in wire format. +func (p *APLPrefix) len() int { + // 4-byte header and the network address prefix (see Section 4 of RFC 3123) + prefix, _ := p.Network.Mask.Size() + return 4 + (prefix+7)/8 +} + // TimeToString translates the RRSIG's incep. and expir. times to the // string representation used when printing the record. // It takes serial arithmetic (RFC 1982) into account. @@ -1409,6 +1499,17 @@ return p } +// copyNet returns a copy of a subnet. +func copyNet(n net.IPNet) net.IPNet { + m := make(net.IPMask, len(n.Mask)) + copy(m, n.Mask) + + return net.IPNet{ + IP: copyIP(n.IP), + Mask: m, + } +} + // SplitN splits a string into N sized string chunks. // This might become an exported function once. func splitN(s string, n int) []string { diff -Nru golang-github-miekg-dns-1.1.26/types_test.go golang-github-miekg-dns-1.1.35/types_test.go --- golang-github-miekg-dns-1.1.26/types_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/types_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -74,18 +74,41 @@ } func TestSprintName(t *testing.T) { - got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\") - - if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want { - t.Errorf("expected %q, got %q", got, want) + tests := map[string]string{ + // Non-numeric escaping of special printable characters. + " '@;()\"\\..example": `\ \'\@\;\(\)\"\..example`, + "\\032\\039\\064\\059\\040\\041\\034\\046\\092.example": `\ \'\@\;\(\)\"\.\\.example`, + + // Numeric escaping of nonprintable characters. + "\x00\x07\x09\x0a\x1f.\x7f\x80\xad\xef\xff": `\000\007\009\010\031.\127\128\173\239\255`, + "\\000\\007\\009\\010\\031.\\127\\128\\173\\239\\255": `\000\007\009\010\031.\127\128\173\239\255`, + + // No escaping of other printable characters, at least after a prior escape. + ";[a-zA-Z0-9_]+/*.~": `\;[a-zA-Z0-9_]+/*.~`, + ";\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `\;[a-zA-Z0-9_]+/*.~`, + // "\\091\\097\\045\\122\\065\\045\\090\\048\\045\\057\\095\\093\\043\\047\\042.\\126": `[a-zA-Z0-9_]+/*.~`, + + // Incomplete "dangling" escapes are dropped regardless of prior escaping. + "a\\": `a`, + ";\\": `\;`, + + // Escaped dots stay escaped regardless of prior escaping. + "a\\.\\046.\\.\\046": `a\.\..\.\.`, + "a\\046\\..\\046\\.": `a\.\..\.\.`, + } + for input, want := range tests { + got := sprintName(input) + if got != want { + t.Errorf("input %q: expected %q, got %q", input, want, got) + } } } func TestSprintTxtOctet(t *testing.T) { got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\") - if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want { - t.Errorf("expected %q, got %q", got, want) + if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want { + t.Errorf("expected %q, got %q", want, got) } } @@ -96,7 +119,7 @@ }) if want := "\"abc.def\\007\\\"W@\\173\\005\\239\" \"example.com\""; got != want { - t.Errorf("expected %q, got %q", got, want) + t.Errorf("expected %q, got %q", want, got) } } @@ -128,7 +151,7 @@ got := sprintName("abc\\.def\007\"\127@\255\x05\xef\\") if want := "abc\\.def\\007\\\"W\\@\\173\\005\\239"; got != want { - b.Fatalf("expected %q, got %q", got, want) + b.Fatalf("expected %q, got %q", want, got) } } } @@ -138,7 +161,7 @@ got := sprintName("large.example.com") if want := "large.example.com"; got != want { - b.Fatalf("expected %q, got %q", got, want) + b.Fatalf("expected %q, got %q", want, got) } } } @@ -147,8 +170,8 @@ for n := 0; n < b.N; n++ { got := sprintTxtOctet("abc\\.def\007\"\127@\255\x05\xef\\") - if want := "\"abc\\.def\\007\"W@\\173\\005\\239\""; got != want { - b.Fatalf("expected %q, got %q", got, want) + if want := "\"abc\\.def\\007\\\"W@\\173\\005\\239\""; got != want { + b.Fatalf("expected %q, got %q", want, got) } } } diff -Nru golang-github-miekg-dns-1.1.26/version.go golang-github-miekg-dns-1.1.35/version.go --- golang-github-miekg-dns-1.1.26/version.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/version.go 2021-01-11 04:34:51.000000000 +0000 @@ -3,13 +3,13 @@ import "fmt" // Version is current version of this library. -var Version = V{1, 1, 26} +var Version = v{1, 1, 35} -// V holds the version of this library. -type V struct { +// v holds the version of this library. +type v struct { Major, Minor, Patch int } -func (v V) String() string { +func (v v) String() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) } diff -Nru golang-github-miekg-dns-1.1.26/version_test.go golang-github-miekg-dns-1.1.35/version_test.go --- golang-github-miekg-dns-1.1.26/version_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/version_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -3,7 +3,7 @@ import "testing" func TestVersion(t *testing.T) { - v := V{1, 0, 0} + v := v{1, 0, 0} if x := v.String(); x != "1.0.0" { t.Fatalf("Failed to convert version %v, got: %s", v, x) } diff -Nru golang-github-miekg-dns-1.1.26/xfr_test.go golang-github-miekg-dns-1.1.35/xfr_test.go --- golang-github-miekg-dns-1.1.26/xfr_test.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/xfr_test.go 2021-01-11 04:34:51.000000000 +0000 @@ -1,11 +1,6 @@ package dns -import ( - "net" - "sync" - "testing" - "time" -) +import "testing" var ( tsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} @@ -52,7 +47,7 @@ HandleFunc("miek.nl.", InvalidXfrServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalTCPServer(":0") + s, addrstr, _, err := RunLocalTCPServer(":0") if err != nil { t.Fatalf("unable to run test server: %s", err) } @@ -78,86 +73,57 @@ HandleFunc("miek.nl.", SingleEnvelopeXfrServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalTCPServerWithTsig(":0", tsigSecret) + s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) { + srv.TsigSecret = tsigSecret + }) if err != nil { t.Fatalf("unable to run test server: %s", err) } defer s.Shutdown() - axfrTestingSuite(addrstr) + axfrTestingSuite(t, addrstr) } func TestMultiEnvelopeXfr(t *testing.T) { HandleFunc("miek.nl.", MultipleEnvelopeXfrServer) defer HandleRemove("miek.nl.") - s, addrstr, err := RunLocalTCPServerWithTsig(":0", tsigSecret) + s, addrstr, _, err := RunLocalTCPServer(":0", func(srv *Server) { + srv.TsigSecret = tsigSecret + }) if err != nil { t.Fatalf("unable to run test server: %s", err) } defer s.Shutdown() - axfrTestingSuite(addrstr) + axfrTestingSuite(t, addrstr) } -func RunLocalTCPServerWithTsig(laddr string, tsig map[string]string) (*Server, string, error) { - server, l, _, err := RunLocalTCPServerWithFinChanWithTsig(laddr, tsig) - - return server, l, err -} +func axfrTestingSuite(t *testing.T, addrstr string) { + tr := new(Transfer) + m := new(Msg) + m.SetAxfr("miek.nl.") -func RunLocalTCPServerWithFinChanWithTsig(laddr string, tsig map[string]string) (*Server, string, chan error, error) { - l, err := net.Listen("tcp", laddr) + c, err := tr.In(m, addrstr) if err != nil { - return nil, "", nil, err + t.Fatal("failed to zone transfer in", err) } - server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour, TsigSecret: tsig} - - waitLock := sync.Mutex{} - waitLock.Lock() - server.NotifyStartedFunc = waitLock.Unlock - - // See the comment in RunLocalUDPServerWithFinChan as to - // why fin must be buffered. - fin := make(chan error, 1) - - go func() { - fin <- server.ActivateAndServe() - l.Close() - }() - - waitLock.Lock() - return server, l.Addr().String(), fin, nil -} - -func axfrTestingSuite(addrstr string) func(*testing.T) { - return func(t *testing.T) { - tr := new(Transfer) - m := new(Msg) - m.SetAxfr("miek.nl.") - - c, err := tr.In(m, addrstr) - if err != nil { - t.Fatal("failed to zone transfer in", err) + var records []RR + for msg := range c { + if msg.Error != nil { + t.Fatal(msg.Error) } + records = append(records, msg.RR...) + } - var records []RR - for msg := range c { - if msg.Error != nil { - t.Fatal(msg.Error) - } - records = append(records, msg.RR...) - } + if len(records) != len(xfrTestData) { + t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData) + } - if len(records) != len(xfrTestData) { + for i, rr := range records { + if !IsDuplicate(rr, xfrTestData[i]) { t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData) } - - for i := range records { - if !IsDuplicate(records[i], xfrTestData[i]) { - t.Fatalf("bad axfr: expected %v, got %v", records, xfrTestData) - } - } } } diff -Nru golang-github-miekg-dns-1.1.26/zduplicate.go golang-github-miekg-dns-1.1.35/zduplicate.go --- golang-github-miekg-dns-1.1.26/zduplicate.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/zduplicate.go 2021-01-11 04:34:51.000000000 +0000 @@ -52,6 +52,23 @@ return true } +func (r1 *APL) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*APL) + if !ok { + return false + } + _ = r2 + if len(r1.Prefixes) != len(r2.Prefixes) { + return false + } + for i := 0; i < len(r1.Prefixes); i++ { + if !r1.Prefixes[i].equals(&r2.Prefixes[i]) { + return false + } + } + return true +} + func (r1 *AVC) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*AVC) if !ok { @@ -87,6 +104,48 @@ return true } +func (r1 *CDNSKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CDNSKEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *CDS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CDS) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + func (r1 *CERT) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*CERT) if !ok { @@ -155,6 +214,27 @@ return true } +func (r1 *DLV) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DLV) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + func (r1 *DNAME) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*DNAME) if !ok { @@ -322,6 +402,48 @@ return true } +func (r1 *HTTPS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*HTTPS) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + if len(r1.Value) != len(r2.Value) { + return false + } + if !areSVCBPairArraysEqual(r1.Value, r2.Value) { + return false + } + return true +} + +func (r1 *KEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*KEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + func (r1 *KX) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*KX) if !ok { @@ -832,6 +954,42 @@ return true } +func (r1 *SIG) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SIG) + if !ok { + return false + } + _ = r2 + if r1.TypeCovered != r2.TypeCovered { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.Labels != r2.Labels { + return false + } + if r1.OrigTtl != r2.OrigTtl { + return false + } + if r1.Expiration != r2.Expiration { + return false + } + if r1.Inception != r2.Inception { + return false + } + if r1.KeyTag != r2.KeyTag { + return false + } + if !isDuplicateName(r1.SignerName, r2.SignerName) { + return false + } + if r1.Signature != r2.Signature { + return false + } + return true +} + func (r1 *SMIMEA) isDuplicate(_r2 RR) bool { r2, ok := _r2.(*SMIMEA) if !ok { @@ -937,6 +1095,27 @@ return false } return true +} + +func (r1 *SVCB) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SVCB) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + if len(r1.Value) != len(r2.Value) { + return false + } + if !areSVCBPairArraysEqual(r1.Value, r2.Value) { + return false + } + return true } func (r1 *TA) isDuplicate(_r2 RR) bool { diff -Nru golang-github-miekg-dns-1.1.26/zmsg.go golang-github-miekg-dns-1.1.35/zmsg.go --- golang-github-miekg-dns-1.1.26/zmsg.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/zmsg.go 2021-01-11 04:34:51.000000000 +0000 @@ -36,6 +36,14 @@ return off, nil } +func (rr *APL) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataApl(rr.Prefixes, msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *AVC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { off, err = packStringTxt(rr.Txt, msg, off) if err != nil { @@ -308,6 +316,22 @@ return off, nil } +func (rr *HTTPS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDataSVCB(rr.Value, msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { off, err = packUint16(rr.Flags, msg, off) if err != nil { @@ -898,6 +922,22 @@ return off, nil } +func (rr *SVCB) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDataSVCB(rr.Value, msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *TA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { off, err = packUint16(rr.KeyTag, msg, off) if err != nil { @@ -1127,6 +1167,17 @@ return off, nil } +func (rr *APL) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Prefixes, off, err = unpackDataApl(msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *AVC) unpack(msg []byte, off int) (off1 int, err error) { rdStart := off _ = rdStart @@ -1540,6 +1591,31 @@ return off, nil } +func (rr *HTTPS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Value, off, err = unpackDataSVCB(msg, off) + if err != nil { + return off, err + } + return off, nil +} + func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) { rdStart := off _ = rdStart @@ -2439,6 +2515,31 @@ if err != nil { return off, err } + return off, nil +} + +func (rr *SVCB) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Value, off, err = unpackDataSVCB(msg, off) + if err != nil { + return off, err + } return off, nil } diff -Nru golang-github-miekg-dns-1.1.26/ztypes.go golang-github-miekg-dns-1.1.35/ztypes.go --- golang-github-miekg-dns-1.1.26/ztypes.go 2019-12-30 00:59:07.000000000 +0000 +++ golang-github-miekg-dns-1.1.35/ztypes.go 2021-01-11 04:34:51.000000000 +0000 @@ -13,6 +13,7 @@ TypeAAAA: func() RR { return new(AAAA) }, TypeAFSDB: func() RR { return new(AFSDB) }, TypeANY: func() RR { return new(ANY) }, + TypeAPL: func() RR { return new(APL) }, TypeAVC: func() RR { return new(AVC) }, TypeCAA: func() RR { return new(CAA) }, TypeCDNSKEY: func() RR { return new(CDNSKEY) }, @@ -32,6 +33,7 @@ TypeGPOS: func() RR { return new(GPOS) }, TypeHINFO: func() RR { return new(HINFO) }, TypeHIP: func() RR { return new(HIP) }, + TypeHTTPS: func() RR { return new(HTTPS) }, TypeKEY: func() RR { return new(KEY) }, TypeKX: func() RR { return new(KX) }, TypeL32: func() RR { return new(L32) }, @@ -69,6 +71,7 @@ TypeSPF: func() RR { return new(SPF) }, TypeSRV: func() RR { return new(SRV) }, TypeSSHFP: func() RR { return new(SSHFP) }, + TypeSVCB: func() RR { return new(SVCB) }, TypeTA: func() RR { return new(TA) }, TypeTALINK: func() RR { return new(TALINK) }, TypeTKEY: func() RR { return new(TKEY) }, @@ -87,6 +90,7 @@ TypeAAAA: "AAAA", TypeAFSDB: "AFSDB", TypeANY: "ANY", + TypeAPL: "APL", TypeATMA: "ATMA", TypeAVC: "AVC", TypeAXFR: "AXFR", @@ -108,6 +112,7 @@ TypeGPOS: "GPOS", TypeHINFO: "HINFO", TypeHIP: "HIP", + TypeHTTPS: "HTTPS", TypeISDN: "ISDN", TypeIXFR: "IXFR", TypeKEY: "KEY", @@ -151,6 +156,7 @@ TypeSPF: "SPF", TypeSRV: "SRV", TypeSSHFP: "SSHFP", + TypeSVCB: "SVCB", TypeTA: "TA", TypeTALINK: "TALINK", TypeTKEY: "TKEY", @@ -169,6 +175,7 @@ func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } func (rr *ANY) Header() *RR_Header { return &rr.Hdr } +func (rr *APL) Header() *RR_Header { return &rr.Hdr } func (rr *AVC) Header() *RR_Header { return &rr.Hdr } func (rr *CAA) Header() *RR_Header { return &rr.Hdr } func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr } @@ -188,6 +195,7 @@ func (rr *GPOS) Header() *RR_Header { return &rr.Hdr } func (rr *HINFO) Header() *RR_Header { return &rr.Hdr } func (rr *HIP) Header() *RR_Header { return &rr.Hdr } +func (rr *HTTPS) Header() *RR_Header { return &rr.Hdr } func (rr *KEY) Header() *RR_Header { return &rr.Hdr } func (rr *KX) Header() *RR_Header { return &rr.Hdr } func (rr *L32) Header() *RR_Header { return &rr.Hdr } @@ -226,6 +234,7 @@ func (rr *SPF) Header() *RR_Header { return &rr.Hdr } func (rr *SRV) Header() *RR_Header { return &rr.Hdr } func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr } +func (rr *SVCB) Header() *RR_Header { return &rr.Hdr } func (rr *TA) Header() *RR_Header { return &rr.Hdr } func (rr *TALINK) Header() *RR_Header { return &rr.Hdr } func (rr *TKEY) Header() *RR_Header { return &rr.Hdr } @@ -262,6 +271,13 @@ l := rr.Hdr.len(off, compression) return l } +func (rr *APL) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Prefixes { + l += x.len() + } + return l +} func (rr *AVC) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) for _, x := range rr.Txt { @@ -582,6 +598,15 @@ l += len(rr.FingerPrint) / 2 return l } +func (rr *SVCB) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Priority + l += domainNameLen(rr.Target, off+l, compression, false) + for _, x := range rr.Value { + l += 4 + int(x.len()) + } + return l +} func (rr *TA) len(off int, compression map[string]struct{}) int { l := rr.Hdr.len(off, compression) l += 2 // KeyTag @@ -673,6 +698,13 @@ func (rr *ANY) copy() RR { return &ANY{rr.Hdr} } +func (rr *APL) copy() RR { + Prefixes := make([]APLPrefix, len(rr.Prefixes)) + for i, e := range rr.Prefixes { + Prefixes[i] = e.copy() + } + return &APL{rr.Hdr, Prefixes} +} func (rr *AVC) copy() RR { Txt := make([]string, len(rr.Txt)) copy(Txt, rr.Txt) @@ -681,6 +713,12 @@ func (rr *CAA) copy() RR { return &CAA{rr.Hdr, rr.Flag, rr.Tag, rr.Value} } +func (rr *CDNSKEY) copy() RR { + return &CDNSKEY{*rr.DNSKEY.copy().(*DNSKEY)} +} +func (rr *CDS) copy() RR { + return &CDS{*rr.DS.copy().(*DS)} +} func (rr *CERT) copy() RR { return &CERT{rr.Hdr, rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate} } @@ -695,6 +733,9 @@ func (rr *DHCID) copy() RR { return &DHCID{rr.Hdr, rr.Digest} } +func (rr *DLV) copy() RR { + return &DLV{*rr.DS.copy().(*DS)} +} func (rr *DNAME) copy() RR { return &DNAME{rr.Hdr, rr.Target} } @@ -727,6 +768,12 @@ copy(RendezvousServers, rr.RendezvousServers) return &HIP{rr.Hdr, rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers} } +func (rr *HTTPS) copy() RR { + return &HTTPS{*rr.SVCB.copy().(*SVCB)} +} +func (rr *KEY) copy() RR { + return &KEY{*rr.DNSKEY.copy().(*DNSKEY)} +} func (rr *KX) copy() RR { return &KX{rr.Hdr, rr.Preference, rr.Exchanger} } @@ -830,6 +877,9 @@ func (rr *RT) copy() RR { return &RT{rr.Hdr, rr.Preference, rr.Host} } +func (rr *SIG) copy() RR { + return &SIG{*rr.RRSIG.copy().(*RRSIG)} +} func (rr *SMIMEA) copy() RR { return &SMIMEA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} } @@ -847,6 +897,13 @@ func (rr *SSHFP) copy() RR { return &SSHFP{rr.Hdr, rr.Algorithm, rr.Type, rr.FingerPrint} } +func (rr *SVCB) copy() RR { + Value := make([]SVCBKeyValue, len(rr.Value)) + for i, e := range rr.Value { + Value[i] = e.copy() + } + return &SVCB{rr.Hdr, rr.Priority, rr.Target, Value} +} func (rr *TA) copy() RR { return &TA{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} }