From 939cc26592f51bf5f480c0e515320a2c276d2412 Mon Sep 17 00:00:00 2001 From: Rohit Jnagal Date: Wed, 7 Jan 2015 18:25:45 +0000 Subject: [PATCH] Add skydns to deps. godep build fails without this. Also pulls in github.com/miekg/dns. rackspace/gophercloud doesn't seem to need kr/text and golang-pretty anymore. --- Godeps/Godeps.json | 14 +- .../_workspace/src/github.com/kr/text/License | 19 - .../_workspace/src/github.com/kr/text/Readme | 3 - .../src/github.com/kr/text/colwriter/Readme | 5 - .../github.com/kr/text/colwriter/column.go | 147 -- .../kr/text/colwriter/column_test.go | 90 - .../_workspace/src/github.com/kr/text/doc.go | 3 - .../src/github.com/kr/text/indent.go | 74 - .../src/github.com/kr/text/indent_test.go | 119 - .../src/github.com/kr/text/mc/Readme | 9 - .../src/github.com/kr/text/mc/mc.go | 62 - .../_workspace/src/github.com/kr/text/wrap.go | 86 - .../src/github.com/kr/text/wrap_test.go | 44 - .../src/github.com/miekg/dns/.gitignore | 4 + .../src/github.com/miekg/dns/.travis.yml | 21 + .../src/github.com/miekg/dns/AUTHORS | 1 + .../src/github.com/miekg/dns/CONTRIBUTORS | 9 + .../src/github.com/miekg/dns/COPYRIGHT | 9 + .../src/github.com/miekg/dns/LICENSE | 32 + .../src/github.com/miekg/dns/README.md | 140 ++ .../src/github.com/miekg/dns/client.go | 319 +++ .../src/github.com/miekg/dns/client_test.go | 195 ++ .../src/github.com/miekg/dns/clientconfig.go | 94 + .../src/github.com/miekg/dns/defaults.go | 242 ++ .../src/github.com/miekg/dns/dns.go | 193 ++ .../src/github.com/miekg/dns/dns_test.go | 511 ++++ .../src/github.com/miekg/dns/dnssec.go | 756 ++++++ .../src/github.com/miekg/dns/dnssec_test.go | 672 +++++ .../src/github.com/miekg/dns/dyn_test.go | 3 + .../src/github.com/miekg/dns/edns.go | 501 ++++ .../src/github.com/miekg/dns/edns_test.go | 48 + .../src/github.com/miekg/dns/example_test.go | 147 ++ .../github.com/miekg/dns/idn/example_test.go | 18 + .../src/github.com/miekg/dns/idn/punycode.go | 268 ++ .../github.com/miekg/dns/idn/punycode_test.go | 94 + .../src/github.com/miekg/dns/keygen.go | 157 ++ .../src/github.com/miekg/dns/kscan.go | 244 ++ .../src/github.com/miekg/dns/labels.go | 162 ++ .../src/github.com/miekg/dns/labels_test.go | 214 ++ .../src/github.com/miekg/dns/msg.go | 1899 +++++++++++++++ .../src/github.com/miekg/dns/nsecx.go | 110 + .../src/github.com/miekg/dns/nsecx_test.go | 33 + .../src/github.com/miekg/dns/parse_test.go | 1276 ++++++++++ .../src/github.com/miekg/dns/privaterr.go | 122 + .../github.com/miekg/dns/privaterr_test.go | 169 ++ .../src/github.com/miekg/dns/rawmsg.go | 95 + .../src/github.com/miekg/dns/scanner.go | 43 + .../src/github.com/miekg/dns/server.go | 626 +++++ .../src/github.com/miekg/dns/server_test.go | 401 +++ .../src/github.com/miekg/dns/sig0.go | 262 ++ .../src/github.com/miekg/dns/sig0_test.go | 96 + .../github.com/miekg/dns/singleinflight.go | 57 + .../src/github.com/miekg/dns/tlsa.go | 84 + .../src/github.com/miekg/dns/tsig.go | 378 +++ .../src/github.com/miekg/dns/types.go | 1697 +++++++++++++ .../src/github.com/miekg/dns/types_test.go | 42 + .../src/github.com/miekg/dns/udp.go | 55 + .../src/github.com/miekg/dns/udp_linux.go | 63 + .../src/github.com/miekg/dns/udp_other.go | 17 + .../src/github.com/miekg/dns/udp_windows.go | 34 + .../src/github.com/miekg/dns/update.go | 138 ++ .../src/github.com/miekg/dns/update_test.go | 105 + .../src/github.com/miekg/dns/xfr.go | 236 ++ .../src/github.com/miekg/dns/zgenerate.go | 157 ++ .../src/github.com/miekg/dns/zscan.go | 956 ++++++++ .../src/github.com/miekg/dns/zscan_rr.go | 2155 +++++++++++++++++ .../skynetservices/skydns/msg/service.go | 95 + .../tonnerre/golang-pretty/.gitignore | 4 - .../github.com/tonnerre/golang-pretty/License | 19 - .../github.com/tonnerre/golang-pretty/Readme | 9 - .../tonnerre/golang-pretty/debian/changelog | 5 - .../tonnerre/golang-pretty/debian/compat | 1 - .../tonnerre/golang-pretty/debian/control | 22 - .../tonnerre/golang-pretty/debian/copyright | 30 - .../tonnerre/golang-pretty/debian/docs | 1 - .../tonnerre/golang-pretty/debian/rules | 11 - .../golang-pretty/debian/source/format | 1 - .../github.com/tonnerre/golang-pretty/diff.go | 148 -- .../tonnerre/golang-pretty/diff_test.go | 73 - .../tonnerre/golang-pretty/example_test.go | 20 - .../tonnerre/golang-pretty/formatter.go | 300 --- .../tonnerre/golang-pretty/formatter_test.go | 146 -- .../tonnerre/golang-pretty/pretty.go | 98 - .../github.com/tonnerre/golang-pretty/zero.go | 41 - 84 files changed, 16462 insertions(+), 1597 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/kr/text/License delete mode 100644 Godeps/_workspace/src/github.com/kr/text/Readme delete mode 100644 Godeps/_workspace/src/github.com/kr/text/colwriter/Readme delete mode 100644 Godeps/_workspace/src/github.com/kr/text/colwriter/column.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/doc.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/indent.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/indent_test.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/mc/Readme delete mode 100644 Godeps/_workspace/src/github.com/kr/text/mc/mc.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/wrap.go delete mode 100644 Godeps/_workspace/src/github.com/kr/text/wrap_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/.gitignore create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/AUTHORS create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/CONTRIBUTORS create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/COPYRIGHT create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/LICENSE create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/README.md create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/client.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/client_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/clientconfig.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/defaults.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/dns.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/dns_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/dnssec.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/dnssec_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/dyn_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/edns.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/edns_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/example_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/idn/example_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/idn/punycode.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/idn/punycode_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/keygen.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/kscan.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/labels.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/labels_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/msg.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/nsecx.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/nsecx_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/parse_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/privaterr.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/privaterr_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/rawmsg.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/scanner.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/server.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/server_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/sig0.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/sig0_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/singleinflight.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/tlsa.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/tsig.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/types.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/types_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/udp.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/udp_linux.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/udp_other.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/udp_windows.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/update.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/update_test.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/xfr.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/zgenerate.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/zscan.go create mode 100644 Godeps/_workspace/src/github.com/miekg/dns/zscan_rr.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/.gitignore delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/License delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/Readme delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/pretty.go delete mode 100644 Godeps/_workspace/src/github.com/tonnerre/golang-pretty/zero.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3f7010cf158..05d47f3b76d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -130,8 +130,8 @@ "Rev": "aef70dacbc78771e35beb261bb3a72986adf7906" }, { - "ImportPath": "github.com/kr/text", - "Rev": "6807e777504f54ad073ecef66747de158294b639" + "ImportPath": "github.com/miekg/dns", + "Rev": "3f504e8dabd5d562e997d19ce0200aa41973e1b2" }, { "ImportPath": "github.com/mitchellh/goamz/aws", @@ -159,6 +159,11 @@ "ImportPath": "github.com/skratchdot/open-golang/open", "Rev": "ba570a111973b539baf23c918213059543b5bb6e" }, + { + "ImportPath": "github.com/skynetservices/skydns/msg", + "Comment": "2.0.1d-2-g245a121", + "Rev": "245a1216be2a7f5377ea56e957fdfa0de6ecd067" + }, { "ImportPath": "github.com/spf13/cobra", "Rev": "e1e66f7b4e667751cf530ddb6e72b79d6eeb0235" @@ -179,11 +184,6 @@ "ImportPath": "github.com/stretchr/testify/mock", "Rev": "37614ac27794505bf7867ca93aac883cadb6a5f7" }, - { - "ImportPath": "github.com/tonnerre/golang-pretty", - "Comment": "debian/0.0_git20130613-1-1-ge7fccc0", - "Rev": "e7fccc03e91bad289b96c21aa3312a220689bdd7" - }, { "ImportPath": "github.com/vaughan0/go-ini", "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" diff --git a/Godeps/_workspace/src/github.com/kr/text/License b/Godeps/_workspace/src/github.com/kr/text/License deleted file mode 100644 index 480a3280599..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/License +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2012 Keith Rarick - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/kr/text/Readme b/Godeps/_workspace/src/github.com/kr/text/Readme deleted file mode 100644 index 7e6e7c0687b..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/Readme +++ /dev/null @@ -1,3 +0,0 @@ -This is a Go package for manipulating paragraphs of text. - -See http://go.pkgdoc.org/github.com/kr/text for full documentation. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme b/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme deleted file mode 100644 index 1c1f4e68393..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme +++ /dev/null @@ -1,5 +0,0 @@ -Package colwriter provides a write filter that formats -input lines in multiple columns. - -The package is a straightforward translation from -/src/cmd/draw/mc.c in Plan 9 from User Space. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go deleted file mode 100644 index 7302ce9f7a8..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go +++ /dev/null @@ -1,147 +0,0 @@ -// Package colwriter provides a write filter that formats -// input lines in multiple columns. -// -// The package is a straightforward translation from -// /src/cmd/draw/mc.c in Plan 9 from User Space. -package colwriter - -import ( - "bytes" - "io" - "unicode/utf8" -) - -const ( - tab = 4 -) - -const ( - // Print each input line ending in a colon ':' separately. - BreakOnColon uint = 1 << iota -) - -// A Writer is a filter that arranges input lines in as many columns as will -// fit in its width. Tab '\t' chars in the input are translated to sequences -// of spaces ending at multiples of 4 positions. -// -// If BreakOnColon is set, each input line ending in a colon ':' is written -// separately. -// -// The Writer assumes that all Unicode code points have the same width; this -// may not be true in some fonts. -type Writer struct { - w io.Writer - buf []byte - width int - flag uint -} - -// NewWriter allocates and initializes a new Writer writing to w. -// Parameter width controls the total number of characters on each line -// across all columns. -func NewWriter(w io.Writer, width int, flag uint) *Writer { - return &Writer{ - w: w, - width: width, - flag: flag, - } -} - -// Write writes p to the writer w. The only errors returned are ones -// encountered while writing to the underlying output stream. -func (w *Writer) Write(p []byte) (n int, err error) { - var linelen int - var lastWasColon bool - for i, c := range p { - w.buf = append(w.buf, c) - linelen++ - if c == '\t' { - w.buf[len(w.buf)-1] = ' ' - for linelen%tab != 0 { - w.buf = append(w.buf, ' ') - linelen++ - } - } - if w.flag&BreakOnColon != 0 && c == ':' { - lastWasColon = true - } else if lastWasColon { - if c == '\n' { - pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'}) - if pos < 0 { - pos = 0 - } - line := w.buf[pos:] - w.buf = w.buf[:pos] - if err = w.columnate(); err != nil { - if len(line) < i { - return i - len(line), err - } - return 0, err - } - if n, err := w.w.Write(line); err != nil { - if r := len(line) - n; r < i { - return i - r, err - } - return 0, err - } - } - lastWasColon = false - } - if c == '\n' { - linelen = 0 - } - } - return len(p), nil -} - -// Flush should be called after the last call to Write to ensure that any data -// buffered in the Writer is written to output. -func (w *Writer) Flush() error { - return w.columnate() -} - -func (w *Writer) columnate() error { - words := bytes.Split(w.buf, []byte{'\n'}) - w.buf = nil - if len(words[len(words)-1]) == 0 { - words = words[:len(words)-1] - } - maxwidth := 0 - for _, wd := range words { - if n := utf8.RuneCount(wd); n > maxwidth { - maxwidth = n - } - } - maxwidth++ // space char - wordsPerLine := w.width / maxwidth - if wordsPerLine <= 0 { - wordsPerLine = 1 - } - nlines := (len(words) + wordsPerLine - 1) / wordsPerLine - for i := 0; i < nlines; i++ { - col := 0 - endcol := 0 - for j := i; j < len(words); j += nlines { - endcol += maxwidth - _, err := w.w.Write(words[j]) - if err != nil { - return err - } - col += utf8.RuneCount(words[j]) - if j+nlines < len(words) { - for col < endcol { - _, err := w.w.Write([]byte{' '}) - if err != nil { - return err - } - col++ - } - } - } - _, err := w.w.Write([]byte{'\n'}) - if err != nil { - return err - } - } - return nil -} diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go deleted file mode 100644 index 8d0bf8f3be4..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package colwriter - -import ( - "bytes" - "testing" -) - -var src = ` -.git -.gitignore -.godir -Procfile: -README.md -api.go -apps.go -auth.go -darwin.go -data.go -dyno.go: -env.go -git.go -help.go -hkdist -linux.go -ls.go -main.go -plugin.go -run.go -scale.go -ssh.go -tail.go -term -unix.go -update.go -version.go -windows.go -`[1:] - -var tests = []struct{ - wid int - flag uint - src string - want string -}{ - {80, 0, "", ""}, - {80, 0, src, ` -.git README.md darwin.go git.go ls.go scale.go unix.go -.gitignore api.go data.go help.go main.go ssh.go update.go -.godir apps.go dyno.go: hkdist plugin.go tail.go version.go -Procfile: auth.go env.go linux.go run.go term windows.go -`[1:]}, - {80, BreakOnColon, src, ` -.git .gitignore .godir - -Procfile: -README.md api.go apps.go auth.go darwin.go data.go - -dyno.go: -env.go hkdist main.go scale.go term version.go -git.go linux.go plugin.go ssh.go unix.go windows.go -help.go ls.go run.go tail.go update.go -`[1:]}, - {20, 0, ` -Hello -Γειά σου -안녕 -今日は -`[1:], ` -Hello 안녕 -Γειά σου 今日は -`[1:]}, -} - -func TestWriter(t *testing.T) { - for _, test := range tests { - b := new(bytes.Buffer) - w := NewWriter(b, test.wid, test.flag) - if _, err := w.Write([]byte(test.src)); err != nil { - t.Error(err) - } - if err := w.Flush(); err != nil { - t.Error(err) - } - if g := b.String(); test.want != g { - t.Log("\n" + test.want) - t.Log("\n" + g) - t.Errorf("%q != %q", test.want, g) - } - } -} diff --git a/Godeps/_workspace/src/github.com/kr/text/doc.go b/Godeps/_workspace/src/github.com/kr/text/doc.go deleted file mode 100644 index cf4c198f955..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package text provides rudimentary functions for manipulating text in -// paragraphs. -package text diff --git a/Godeps/_workspace/src/github.com/kr/text/indent.go b/Godeps/_workspace/src/github.com/kr/text/indent.go deleted file mode 100644 index 4ebac45c092..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/indent.go +++ /dev/null @@ -1,74 +0,0 @@ -package text - -import ( - "io" -) - -// Indent inserts prefix at the beginning of each non-empty line of s. The -// end-of-line marker is NL. -func Indent(s, prefix string) string { - return string(IndentBytes([]byte(s), []byte(prefix))) -} - -// IndentBytes inserts prefix at the beginning of each non-empty line of b. -// The end-of-line marker is NL. -func IndentBytes(b, prefix []byte) []byte { - var res []byte - bol := true - for _, c := range b { - if bol && c != '\n' { - res = append(res, prefix...) - } - res = append(res, c) - bol = c == '\n' - } - return res -} - -// Writer indents each line of its input. -type indentWriter struct { - w io.Writer - bol bool - pre [][]byte - sel int - off int -} - -// NewIndentWriter makes a new write filter that indents the input -// lines. Each line is prefixed in order with the corresponding -// element of pre. If there are more lines than elements, the last -// element of pre is repeated for each subsequent line. -func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer { - return &indentWriter{ - w: w, - pre: pre, - bol: true, - } -} - -// The only errors returned are from the underlying indentWriter. -func (w *indentWriter) Write(p []byte) (n int, err error) { - for _, c := range p { - if w.bol { - var i int - i, err = w.w.Write(w.pre[w.sel][w.off:]) - w.off += i - if err != nil { - return n, err - } - } - _, err = w.w.Write([]byte{c}) - if err != nil { - return n, err - } - n++ - w.bol = c == '\n' - if w.bol { - w.off = 0 - if w.sel < len(w.pre)-1 { - w.sel++ - } - } - } - return n, nil -} diff --git a/Godeps/_workspace/src/github.com/kr/text/indent_test.go b/Godeps/_workspace/src/github.com/kr/text/indent_test.go deleted file mode 100644 index 5c723eee855..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/indent_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package text - -import ( - "bytes" - "testing" -) - -type T struct { - inp, exp, pre string -} - -var tests = []T{ - { - "The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n", - "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n", - "xxx", - }, - { - "The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.", - "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.", - "xxx", - }, -} - -func TestIndent(t *testing.T) { - for _, test := range tests { - got := Indent(test.inp, test.pre) - if got != test.exp { - t.Errorf("mismatch %q != %q", got, test.exp) - } - } -} - -type IndentWriterTest struct { - inp, exp string - pre []string -} - -var ts = []IndentWriterTest{ - { - ` -The quick brown fox -jumps over the lazy -dog. -But not quickly. -`[1:], - ` -xxxThe quick brown fox -xxxjumps over the lazy -xxxdog. -xxxBut not quickly. -`[1:], - []string{"xxx"}, - }, - { - ` -The quick brown fox -jumps over the lazy -dog. -But not quickly. -`[1:], - ` -xxaThe quick brown fox -xxxjumps over the lazy -xxxdog. -xxxBut not quickly. -`[1:], - []string{"xxa", "xxx"}, - }, - { - ` -The quick brown fox -jumps over the lazy -dog. -But not quickly. -`[1:], - ` -xxaThe quick brown fox -xxbjumps over the lazy -xxcdog. -xxxBut not quickly. -`[1:], - []string{"xxa", "xxb", "xxc", "xxx"}, - }, - { - ` -The quick brown fox -jumps over the lazy -dog. - -But not quickly.`[1:], - ` -xxaThe quick brown fox -xxxjumps over the lazy -xxxdog. -xxx -xxxBut not quickly.`[1:], - []string{"xxa", "xxx"}, - }, -} - -func TestIndentWriter(t *testing.T) { - for _, test := range ts { - b := new(bytes.Buffer) - pre := make([][]byte, len(test.pre)) - for i := range test.pre { - pre[i] = []byte(test.pre[i]) - } - w := NewIndentWriter(b, pre...) - if _, err := w.Write([]byte(test.inp)); err != nil { - t.Error(err) - } - if got := b.String(); got != test.exp { - t.Errorf("mismatch %q != %q", got, test.exp) - t.Log(got) - t.Log(test.exp) - } - } -} diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/Readme b/Godeps/_workspace/src/github.com/kr/text/mc/Readme deleted file mode 100644 index 519ddc00a13..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/mc/Readme +++ /dev/null @@ -1,9 +0,0 @@ -Command mc prints in multiple columns. - - Usage: mc [-] [-N] [file...] - -Mc splits the input into as many columns as will fit in N -print positions. If the output is a tty, the default N is -the number of characters in a terminal line; otherwise the -default N is 80. Under option - each input line ending in -a colon ':' is printed separately. diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/mc.go b/Godeps/_workspace/src/github.com/kr/text/mc/mc.go deleted file mode 100644 index 00169a30f16..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/mc/mc.go +++ /dev/null @@ -1,62 +0,0 @@ -// Command mc prints in multiple columns. -// -// Usage: mc [-] [-N] [file...] -// -// Mc splits the input into as many columns as will fit in N -// print positions. If the output is a tty, the default N is -// the number of characters in a terminal line; otherwise the -// default N is 80. Under option - each input line ending in -// a colon ':' is printed separately. -package main - -import ( - "github.com/kr/pty" - "github.com/kr/text/colwriter" - "io" - "log" - "os" - "strconv" -) - -func main() { - var width int - var flag uint - args := os.Args[1:] - for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' { - if len(args[0]) > 1 { - width, _ = strconv.Atoi(args[0][1:]) - } else { - flag |= colwriter.BreakOnColon - } - args = args[1:] - } - if width < 1 { - _, width, _ = pty.Getsize(os.Stdout) - } - if width < 1 { - width = 80 - } - - w := colwriter.NewWriter(os.Stdout, width, flag) - if len(args) > 0 { - for _, s := range args { - if f, err := os.Open(s); err == nil { - copyin(w, f) - f.Close() - } else { - log.Println(err) - } - } - } else { - copyin(w, os.Stdin) - } -} - -func copyin(w *colwriter.Writer, r io.Reader) { - if _, err := io.Copy(w, r); err != nil { - log.Println(err) - } - if err := w.Flush(); err != nil { - log.Println(err) - } -} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap.go b/Godeps/_workspace/src/github.com/kr/text/wrap.go deleted file mode 100644 index ca8856515c3..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/wrap.go +++ /dev/null @@ -1,86 +0,0 @@ -package text - -import ( - "bytes" - "math" -) - -var ( - nl = []byte{'\n'} - sp = []byte{' '} -) - -const defaultPenalty = 1e5 - -// Wrap wraps s into a paragraph of lines of length lim, with minimal -// raggedness. -func Wrap(s string, lim int) string { - return string(WrapBytes([]byte(s), lim)) -} - -// WrapBytes wraps b into a paragraph of lines of length lim, with minimal -// raggedness. -func WrapBytes(b []byte, lim int) []byte { - words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp) - var lines [][]byte - for _, line := range WrapWords(words, 1, lim, defaultPenalty) { - lines = append(lines, bytes.Join(line, sp)) - } - return bytes.Join(lines, nl) -} - -// WrapWords is the low-level line-breaking algorithm, useful if you need more -// control over the details of the text wrapping process. For most uses, either -// Wrap or WrapBytes will be sufficient and more convenient. -// -// WrapWords splits a list of words into lines with minimal "raggedness", -// treating each byte as one unit, accounting for spc units between adjacent -// words on each line, and attempting to limit lines to lim units. Raggedness -// is the total error over all lines, where error is the square of the -// difference of the length of the line and lim. Too-long lines (which only -// happen when a single word is longer than lim units) have pen penalty units -// added to the error. -func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte { - n := len(words) - - length := make([][]int, n) - for i := 0; i < n; i++ { - length[i] = make([]int, n) - length[i][i] = len(words[i]) - for j := i + 1; j < n; j++ { - length[i][j] = length[i][j-1] + spc + len(words[j]) - } - } - - nbrk := make([]int, n) - cost := make([]int, n) - for i := range cost { - cost[i] = math.MaxInt32 - } - for i := n - 1; i >= 0; i-- { - if length[i][n-1] <= lim { - cost[i] = 0 - nbrk[i] = n - } else { - for j := i + 1; j < n; j++ { - d := lim - length[i][j-1] - c := d*d + cost[j] - if length[i][j-1] > lim { - c += pen // too-long lines get a worse penalty - } - if c < cost[i] { - cost[i] = c - nbrk[i] = j - } - } - } - } - - var lines [][][]byte - i := 0 - for i < n { - lines = append(lines, words[i:nbrk[i]]) - i = nbrk[i] - } - return lines -} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap_test.go b/Godeps/_workspace/src/github.com/kr/text/wrap_test.go deleted file mode 100644 index 90f065cd1ae..00000000000 --- a/Godeps/_workspace/src/github.com/kr/text/wrap_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package text - -import ( - "bytes" - "testing" -) - -var text = "The quick brown fox jumps over the lazy dog." - -func TestWrap(t *testing.T) { - exp := [][]string{ - {"The", "quick", "brown", "fox"}, - {"jumps", "over", "the", "lazy", "dog."}, - } - words := bytes.Split([]byte(text), sp) - got := WrapWords(words, 1, 24, defaultPenalty) - if len(exp) != len(got) { - t.Fail() - } - for i := range exp { - if len(exp[i]) != len(got[i]) { - t.Fail() - } - for j := range exp[i] { - if exp[i][j] != string(got[i][j]) { - t.Fatal(i, exp[i][j], got[i][j]) - } - } - } -} - -func TestWrapNarrow(t *testing.T) { - exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog." - if Wrap(text, 5) != exp { - t.Fail() - } -} - -func TestWrapOneLine(t *testing.T) { - exp := "The quick brown fox jumps over the lazy dog." - if Wrap(text, 500) != exp { - t.Fail() - } -} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/.gitignore b/Godeps/_workspace/src/github.com/miekg/dns/.gitignore new file mode 100644 index 00000000000..776cd950c25 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/.gitignore @@ -0,0 +1,4 @@ +*.6 +tags +test.out +a.out diff --git a/Godeps/_workspace/src/github.com/miekg/dns/.travis.yml b/Godeps/_workspace/src/github.com/miekg/dns/.travis.yml new file mode 100644 index 00000000000..4485679769d --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/.travis.yml @@ -0,0 +1,21 @@ +language: go +go: + - 1.2 + - 1.3 +env: + # "gvm update" resets GOOS and GOARCH environment variable, workaround it by setting + # BUILD_GOOS and BUILD_GOARCH and overriding GOARCH and GOOS in the build script + global: + - BUILD_GOARCH=amd64 + matrix: + - BUILD_GOOS=linux + - BUILD_GOOS=darwin + - BUILD_GOOS=windows +script: + - gvm cross $BUILD_GOOS $BUILD_GOARCH + - GOARCH=$BUILD_GOARCH GOOS=$BUILD_GOOS go build + + # only test on linux + # also specify -short; the crypto tests fail in weird ways *sometimes* + # See issue #151 + - if [ $BUILD_GOOS == "linux" ]; then GOARCH=$BUILD_GOARCH GOOS=$BUILD_GOOS go test -short -bench=.; fi diff --git a/Godeps/_workspace/src/github.com/miekg/dns/AUTHORS b/Godeps/_workspace/src/github.com/miekg/dns/AUTHORS new file mode 100644 index 00000000000..1965683525a --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/AUTHORS @@ -0,0 +1 @@ +Miek Gieben diff --git a/Godeps/_workspace/src/github.com/miekg/dns/CONTRIBUTORS b/Godeps/_workspace/src/github.com/miekg/dns/CONTRIBUTORS new file mode 100644 index 00000000000..f77e8a895f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/CONTRIBUTORS @@ -0,0 +1,9 @@ +Alex A. Skinner +Andrew Tunnell-Jones +Ask Bjørn Hansen +Dave Cheney +Dusty Wilson +Marek Majkowski +Peter van Dijk +Omri Bahumi +Alex Sergeyev diff --git a/Godeps/_workspace/src/github.com/miekg/dns/COPYRIGHT b/Godeps/_workspace/src/github.com/miekg/dns/COPYRIGHT new file mode 100644 index 00000000000..35702b10e87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/COPYRIGHT @@ -0,0 +1,9 @@ +Copyright 2009 The Go Authors. All rights reserved. Use of this source code +is governed by a BSD-style license that can be found in the LICENSE file. +Extensions of the original work are copyright (c) 2011 Miek Gieben + +Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. + +Copyright 2014 CloudFlare. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. diff --git a/Godeps/_workspace/src/github.com/miekg/dns/LICENSE b/Godeps/_workspace/src/github.com/miekg/dns/LICENSE new file mode 100644 index 00000000000..5763fa7fe5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/LICENSE @@ -0,0 +1,32 @@ +Extensions of the original work are copyright (c) 2011 Miek Gieben + +As this is fork of the official Go code the same license applies: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Godeps/_workspace/src/github.com/miekg/dns/README.md b/Godeps/_workspace/src/github.com/miekg/dns/README.md new file mode 100644 index 00000000000..3cb850a0b07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/README.md @@ -0,0 +1,140 @@ +[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) + +# Alternative (more granular) approach to a DNS library + +> Less is more. + +Complete and usable DNS library. All widely used Resource Records are +supported, including the DNSSEC types. It follows a lean and mean philosophy. +If there is stuff you should know as a DNS programmer there isn't a convenience +function for it. Server side and client side programming is supported, i.e. you +can build servers and resolvers with it. + +If you like this, you may also be interested in: + +* https://github.com/miekg/unbound -- Go wrapper for the Unbound resolver. + +# Goals + +* KISS; +* Fast; +* Small API, if its easy to code in Go, don't make a function for it. + +# Users + +A not-so-up-to-date-list-that-may-be-actually-current: + +* https://github.com/abh/geodns +* http://www.statdns.com/ +* http://www.dnsinspect.com/ +* https://github.com/chuangbo/jianbing-dictionary-dns +* http://www.dns-lg.com/ +* https://github.com/fcambus/rrda +* https://github.com/kenshinx/godns +* https://github.com/skynetservices/skydns +* https://github.com/DevelopersPL/godnsagent +* https://github.com/duedil-ltd/discodns + +Send pull request if you want to be listed here. + +# Features + +* UDP/TCP queries, IPv4 and IPv6; +* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported; +* Fast: + * Reply speed around ~ 80K qps (faster hardware results in more qps); + * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds; +* Server side programming (mimicking the net/http package); +* Client side programming; +* DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA; +* EDNS0, NSID; +* AXFR/IXFR; +* TSIG, SIG(0); +* DNS name compression; +* Depends only on the standard library. + +Have fun! + +Miek Gieben - 2010-2012 - + +# Building + +Building is done with the `go` tool. If you have setup your GOPATH +correctly, the following should work: + + go get github.com/miekg/dns + go build github.com/miekg/dns + +## Examples + +A short "how to use the API" is at the beginning of dns.go (this also will show +when you call `godoc github.com/miekg/dns`). + +Example programs can be found in the `github.com/miekg/exdns` repository. + +## Supported RFCs + +*all of them* + +* 103{4,5} - DNS standard +* 1348 - NSAP record +* 1982 - Serial Arithmetic +* 1876 - LOC record +* 1995 - IXFR +* 1996 - DNS notify +* 2136 - DNS Update (dynamic updates) +* 2181 - RRset definition - there is no RRset type though, just []RR +* 2537 - RSAMD5 DNS keys +* 2065 - DNSSEC (updated in later RFCs) +* 2671 - EDNS record +* 2782 - SRV record +* 2845 - TSIG record +* 2915 - NAPTR record +* 2929 - DNS IANA Considerations +* 3110 - RSASHA1 DNS keys +* 3225 - DO bit (DNSSEC OK) +* 340{1,2,3} - NAPTR record +* 3445 - Limiting the scope of (DNS)KEY +* 3597 - Unkown RRs +* 403{3,4,5} - DNSSEC + validation functions +* 4255 - SSHFP record +* 4343 - Case insensitivity +* 4408 - SPF record +* 4509 - SHA256 Hash in DS +* 4592 - Wildcards in the DNS +* 4635 - HMAC SHA TSIG +* 4701 - DHCID +* 4892 - id.server +* 5001 - NSID +* 5155 - NSEC3 record +* 5205 - HIP record +* 5702 - SHA2 in the DNS +* 5936 - AXFR +* 5966 - TCP implementation recommendations +* 6605 - ECDSA +* 6725 - IANA Registry Update +* 6742 - ILNP DNS +* 6891 - EDNS0 update +* 6895 - DNS IANA considerations +* 6975 - Algorithm Understanding in DNSSEC +* 7043 - EUI48/EUI64 records +* 7314 - DNS (EDNS) EXPIRE Option +* xxxx - URI record (draft) +* xxxx - EDNS0 DNS Update Lease (draft) + +## Loosely based upon + +* `ldns` +* `NSD` +* `Net::DNS` +* `GRONG` + +## TODO + +* privatekey.Precompute() when signing? +* Last remaining RRs: APL, ATMA, A6 and NXT; +* Missing in parsing: ISDN, UNSPEC, ATMA; +* CAA parsing is broken; +* NSEC(3) cover/match/closest enclose; +* Replies with TC bit are not parsed to the end; +* Create IsMsg to validate a message before fully parsing it. diff --git a/Godeps/_workspace/src/github.com/miekg/dns/client.go b/Godeps/_workspace/src/github.com/miekg/dns/client.go new file mode 100644 index 00000000000..ee8e22333b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/client.go @@ -0,0 +1,319 @@ +package dns + +// A client implementation. + +import ( + "bytes" + "io" + "net" + "time" +) + +const dnsTimeout time.Duration = 2 * 1e9 +const tcpIdleTimeout time.Duration = 8 * time.Second + +// A Conn represents a connection to a DNS server. +type Conn struct { + net.Conn // a net.Conn holding the connection + UDPSize uint16 // minimum receive buffer for UDP messages + TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified + rtt time.Duration + t time.Time + tsigRequestMAC string +} + +// A Client defines parameters for a DNS client. +type Client struct { + Net string // if "tcp" a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) + UDPSize uint16 // minimum receive buffer for UDP messages + DialTimeout time.Duration // net.DialTimeout (ns), defaults to 2 * 1e9 + ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections (ns), defaults to 2 * 1e9 + WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections (ns), defaults to 2 * 1e9 + TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified + SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass + group singleflight +} + +// Exchange performs a synchronous UDP query. It sends the message m to the address +// contained in a and waits for an reply. Exchange does not retry a failed query, nor +// will it fall back to TCP in case of truncation. +// If you need to send a DNS message on an already existing connection, you can use the +// following: +// +// co := &dns.Conn{Conn: c} // c is your net.Conn +// co.WriteMsg(m) +// in, err := co.ReadMsg() +// co.Close() +// +func Exchange(m *Msg, a string) (r *Msg, err error) { + var co *Conn + co, err = DialTimeout("udp", a, dnsTimeout) + if err != nil { + return nil, err + } + + defer co.Close() + co.SetReadDeadline(time.Now().Add(dnsTimeout)) + co.SetWriteDeadline(time.Now().Add(dnsTimeout)) + if err = co.WriteMsg(m); err != nil { + return nil, err + } + r, err = co.ReadMsg() + return r, err +} + +// ExchangeConn performs a synchronous query. It sends the message m via the connection +// c and waits for a reply. The connection c is not closed by ExchangeConn. +// This function is going away, but can easily be mimicked: +// +// co := &dns.Conn{Conn: c} // c is your net.Conn +// co.WriteMsg(m) +// in, _ := co.ReadMsg() +// co.Close() +// +func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { + println("dns: this function is deprecated") + co := new(Conn) + co.Conn = c + if err = co.WriteMsg(m); err != nil { + return nil, err + } + r, err = co.ReadMsg() + return r, err +} + +// Exchange performs an synchronous query. It sends the message m to the address +// contained in a and waits for an reply. Basic use pattern with a *dns.Client: +// +// c := new(dns.Client) +// in, rtt, err := c.Exchange(message, "127.0.0.1:53") +// +// Exchange does not retry a failed query, nor will it fall back to TCP in +// case of truncation. +func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { + if !c.SingleInflight { + return c.exchange(m, a) + } + // This adds a bunch of garbage, TODO(miek). + t := "nop" + if t1, ok := TypeToString[m.Question[0].Qtype]; ok { + t = t1 + } + cl := "nop" + if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { + cl = cl1 + } + r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { + return c.exchange(m, a) + }) + if err != nil { + return r, rtt, err + } + if shared { + return r.Copy(), rtt, nil + } + return r, rtt, nil +} + +func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { + timeout := dnsTimeout + var co *Conn + if c.DialTimeout != 0 { + timeout = c.DialTimeout + } + if c.Net == "" { + co, err = DialTimeout("udp", a, timeout) + } else { + co, err = DialTimeout(c.Net, a, timeout) + } + if err != nil { + return nil, 0, err + } + timeout = dnsTimeout + if c.ReadTimeout != 0 { + timeout = c.ReadTimeout + } + co.SetReadDeadline(time.Now().Add(timeout)) + timeout = dnsTimeout + if c.WriteTimeout != 0 { + timeout = c.WriteTimeout + } + co.SetWriteDeadline(time.Now().Add(timeout)) + defer co.Close() + opt := m.IsEdns0() + // If EDNS0 is used use that for size. + if opt != nil && opt.UDPSize() >= MinMsgSize { + co.UDPSize = opt.UDPSize() + } + // Otherwise use the client's configured UDP size. + if opt == nil && c.UDPSize >= MinMsgSize { + co.UDPSize = c.UDPSize + } + co.TsigSecret = c.TsigSecret + if err = co.WriteMsg(m); err != nil { + return nil, 0, err + } + r, err = co.ReadMsg() + return r, co.rtt, err +} + +// ReadMsg reads a message from the connection co. +// If the received message contains a TSIG record the transaction +// signature is verified. +func (co *Conn) ReadMsg() (*Msg, error) { + var p []byte + m := new(Msg) + if _, ok := co.Conn.(*net.TCPConn); ok { + p = make([]byte, MaxMsgSize) + } else { + if co.UDPSize >= 512 { + p = make([]byte, co.UDPSize) + } else { + p = make([]byte, MinMsgSize) + } + } + n, err := co.Read(p) + if err != nil && n == 0 { + return nil, err + } + p = p[:n] + if err := m.Unpack(p); err != nil { + return nil, err + } + co.rtt = time.Since(co.t) + if t := m.IsTsig(); t != nil { + if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { + return m, ErrSecret + } + // Need to work on the original message p, as that was used to calculate the tsig. + err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) + } + return m, err +} + +// Read implements the net.Conn read method. +func (co *Conn) Read(p []byte) (n int, err error) { + if co.Conn == nil { + return 0, ErrConnEmpty + } + if len(p) < 2 { + return 0, io.ErrShortBuffer + } + if t, ok := co.Conn.(*net.TCPConn); ok { + n, err = t.Read(p[0:2]) + if err != nil || n != 2 { + return n, err + } + l, _ := unpackUint16(p[0:2], 0) + if l == 0 { + return 0, ErrShortRead + } + if int(l) > len(p) { + return int(l), io.ErrShortBuffer + } + n, err = t.Read(p[:l]) + if err != nil { + return n, err + } + i := n + for i < int(l) { + j, err := t.Read(p[i:int(l)]) + if err != nil { + return i, err + } + i += j + } + n = i + return n, err + } + // UDP connection + n, err = co.Conn.Read(p) + if err != nil { + return n, err + } + return n, err +} + +// WriteMsg sends a message throught the connection co. +// If the message m contains a TSIG record the transaction +// signature is calculated. +func (co *Conn) WriteMsg(m *Msg) (err error) { + var out []byte + if t := m.IsTsig(); t != nil { + mac := "" + if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { + return ErrSecret + } + out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) + // Set for the next read, allthough only used in zone transfers + co.tsigRequestMAC = mac + } else { + out, err = m.Pack() + } + if err != nil { + return err + } + co.t = time.Now() + if _, err = co.Write(out); err != nil { + return err + } + return nil +} + +// Write implements the net.Conn Write method. +func (co *Conn) Write(p []byte) (n int, err error) { + if t, ok := co.Conn.(*net.TCPConn); ok { + lp := len(p) + if lp < 2 { + return 0, io.ErrShortBuffer + } + if lp > MaxMsgSize { + return 0, &Error{err: "message too large"} + } + l := make([]byte, 2, lp+2) + l[0], l[1] = packUint16(uint16(lp)) + p = append(l, p...) + n, err := io.Copy(t, bytes.NewReader(p)) + return int(n), err + } + n, err = co.Conn.(*net.UDPConn).Write(p) + return n, err +} + +// Dial connects to the address on the named network. +func Dial(network, address string) (conn *Conn, err error) { + conn = new(Conn) + conn.Conn, err = net.Dial(network, address) + if err != nil { + return nil, err + } + return conn, nil +} + +// Dialtimeout acts like Dial but takes a timeout. +func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { + conn = new(Conn) + conn.Conn, err = net.DialTimeout(network, address, timeout) + if err != nil { + return nil, err + } + return conn, nil +} + +// Close implements the net.Conn Close method. +func (co *Conn) Close() error { return co.Conn.Close() } + +// LocalAddr implements the net.Conn LocalAddr method. +func (co *Conn) LocalAddr() net.Addr { return co.Conn.LocalAddr() } + +// RemoteAddr implements the net.Conn RemoteAddr method. +func (co *Conn) RemoteAddr() net.Addr { return co.Conn.RemoteAddr() } + +// SetDeadline implements the net.Conn SetDeadline method. +func (co *Conn) SetDeadline(t time.Time) error { return co.Conn.SetDeadline(t) } + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (co *Conn) SetReadDeadline(t time.Time) error { return co.Conn.SetReadDeadline(t) } + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (co *Conn) SetWriteDeadline(t time.Time) error { return co.Conn.SetWriteDeadline(t) } diff --git a/Godeps/_workspace/src/github.com/miekg/dns/client_test.go b/Godeps/_workspace/src/github.com/miekg/dns/client_test.go new file mode 100644 index 00000000000..2113f3b0e5b --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/client_test.go @@ -0,0 +1,195 @@ +package dns + +import ( + "testing" + "time" +) + +func TestClientSync(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := new(Client) + r, _, e := c.Exchange(m, addrstr) + if e != nil { + t.Logf("failed to exchange: %s", e.Error()) + t.Fail() + } + if r != nil && r.Rcode != RcodeSuccess { + t.Log("failed to get an valid answer") + t.Fail() + t.Logf("%v\n", r) + } + // And now with plain Exchange(). + r, e = Exchange(m, addrstr) + if e != nil { + t.Logf("failed to exchange: %s", e.Error()) + t.Fail() + } + if r != nil && r.Rcode != RcodeSuccess { + t.Log("failed to get an valid answer") + t.Fail() + t.Logf("%v\n", r) + } +} + +func TestClientEDNS0(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeDNSKEY) + + m.SetEdns0(2048, true) + + c := new(Client) + r, _, e := c.Exchange(m, addrstr) + if e != nil { + t.Logf("failed to exchange: %s", e.Error()) + t.Fail() + } + + if r != nil && r.Rcode != RcodeSuccess { + t.Log("failed to get an valid answer") + t.Fail() + t.Logf("%v\n", r) + } +} + +func TestSingleSingleInflight(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeDNSKEY) + + c := new(Client) + c.SingleInflight = true + nr := 10 + ch := make(chan time.Duration) + for i := 0; i < nr; i++ { + go func() { + _, rtt, _ := c.Exchange(m, addrstr) + ch <- rtt + }() + } + i := 0 + var first time.Duration + // With inflight *all* rtt are identical, and by doing actual lookups + // the changes that this is a coincidence is small. +Loop: + for { + select { + case rtt := <-ch: + if i == 0 { + first = rtt + } else { + if first != rtt { + t.Log("all rtts should be equal") + t.Fail() + } + } + i++ + if i == 10 { + break Loop + } + } + } +} + +/* +func TestClientTsigAXFR(t *testing.T) { + m := new(Msg) + m.SetAxfr("example.nl.") + m.SetTsig("axfr.", HmacMD5, 300, time.Now().Unix()) + + tr := new(Transfer) + tr.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} + + if a, err := tr.In(m, "176.58.119.54:53"); err != nil { + t.Log("failed to setup axfr: " + err.Error()) + t.Fatal() + } else { + for ex := range a { + if ex.Error != nil { + t.Logf("error %s\n", ex.Error.Error()) + t.Fail() + break + } + for _, rr := range ex.RR { + t.Logf("%s\n", rr.String()) + } + } + } +} + +func TestClientAXFRMultipleEnvelopes(t *testing.T) { + m := new(Msg) + m.SetAxfr("nlnetlabs.nl.") + + tr := new(Transfer) + if a, err := tr.In(m, "213.154.224.1:53"); err != nil { + t.Log("Failed to setup axfr" + err.Error()) + t.Fail() + return + } else { + for ex := range a { + if ex.Error != nil { + t.Logf("Error %s\n", ex.Error.Error()) + t.Fail() + break + } + } + } +} +*/ + +// ExampleUpdateLeaseTSIG shows how to update a lease signed with TSIG. +func ExampleUpdateLeaseTSIG(t *testing.T) { + m := new(Msg) + m.SetUpdate("t.local.ip6.io.") + rr, _ := NewRR("t.local.ip6.io. 30 A 127.0.0.1") + rrs := make([]RR, 1) + rrs[0] = rr + m.Insert(rrs) + + lease_rr := new(OPT) + lease_rr.Hdr.Name = "." + lease_rr.Hdr.Rrtype = TypeOPT + e := new(EDNS0_UL) + e.Code = EDNS0UL + e.Lease = 120 + lease_rr.Option = append(lease_rr.Option, e) + m.Extra = append(m.Extra, lease_rr) + + c := new(Client) + m.SetTsig("polvi.", HmacMD5, 300, time.Now().Unix()) + c.TsigSecret = map[string]string{"polvi.": "pRZgBrBvI4NAHZYhxmhs/Q=="} + + _, _, err := c.Exchange(m, "127.0.0.1:53") + if err != nil { + t.Log(err.Error()) + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/clientconfig.go b/Godeps/_workspace/src/github.com/miekg/dns/clientconfig.go new file mode 100644 index 00000000000..87cf8961862 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/clientconfig.go @@ -0,0 +1,94 @@ +package dns + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +// Wraps the contents of the /etc/resolv.conf. +type ClientConfig struct { + Servers []string // servers to use + Search []string // suffixes to append to local name + Port string // what port to use + Ndots int // number of dots in name to trigger absolute lookup + Timeout int // seconds before giving up on packet + Attempts int // lost packets before giving up on server, not used in the package dns +} + +// ClientConfigFromFile parses a resolv.conf(5) like file and returns +// a *ClientConfig. +func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { + file, err := os.Open(resolvconf) + if err != nil { + return nil, err + } + defer file.Close() + c := new(ClientConfig) + b := bufio.NewReader(file) + c.Servers = make([]string, 0) + c.Search = make([]string, 0) + c.Port = "53" + c.Ndots = 1 + c.Timeout = 5 + c.Attempts = 2 + for line, ok := b.ReadString('\n'); ok == nil; line, ok = b.ReadString('\n') { + f := strings.Fields(line) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": // add one name server + if len(f) > 1 { + // One more check: make sure server name is + // just an IP address. Otherwise we need DNS + // to look it up. + name := f[1] + c.Servers = append(c.Servers, name) + } + + case "domain": // set search path to just this domain + if len(f) > 1 { + c.Search = make([]string, 1) + c.Search[0] = f[1] + } else { + c.Search = make([]string, 0) + } + + case "search": // set search path to given servers + c.Search = make([]string, len(f)-1) + for i := 0; i < len(c.Search); i++ { + c.Search[i] = f[i+1] + } + + case "options": // magic options + for i := 1; i < len(f); i++ { + s := f[i] + switch { + case len(s) >= 6 && s[:6] == "ndots:": + n, _ := strconv.Atoi(s[6:]) + if n < 1 { + n = 1 + } + c.Ndots = n + case len(s) >= 8 && s[:8] == "timeout:": + n, _ := strconv.Atoi(s[8:]) + if n < 1 { + n = 1 + } + c.Timeout = n + case len(s) >= 8 && s[:9] == "attempts:": + n, _ := strconv.Atoi(s[9:]) + if n < 1 { + n = 1 + } + c.Attempts = n + case s == "rotate": + /* not imp */ + } + } + } + } + return c, nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/defaults.go b/Godeps/_workspace/src/github.com/miekg/dns/defaults.go new file mode 100644 index 00000000000..0c8fa9c84b6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/defaults.go @@ -0,0 +1,242 @@ +package dns + +import ( + "errors" + "net" + "strconv" +) + +const hexDigit = "0123456789abcdef" + +// Everything is assumed in ClassINET. + +// SetReply creates a reply message from a request message. +func (dns *Msg) SetReply(request *Msg) *Msg { + dns.Id = request.Id + dns.RecursionDesired = request.RecursionDesired // Copy rd bit + dns.Response = true + dns.Opcode = OpcodeQuery + dns.Rcode = RcodeSuccess + if len(request.Question) > 0 { + dns.Question = make([]Question, 1) + dns.Question[0] = request.Question[0] + } + return dns +} + +// SetQuestion creates a question message. +func (dns *Msg) SetQuestion(z string, t uint16) *Msg { + dns.Id = Id() + dns.RecursionDesired = true + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, t, ClassINET} + return dns +} + +// SetNotify creates a notify message. +func (dns *Msg) SetNotify(z string) *Msg { + dns.Opcode = OpcodeNotify + dns.Authoritative = true + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeSOA, ClassINET} + return dns +} + +// SetRcode creates an error message suitable for the request. +func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { + dns.SetReply(request) + dns.Rcode = rcode + return dns +} + +// SetRcodeFormatError creates a message with FormError set. +func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { + dns.Rcode = RcodeFormatError + dns.Opcode = OpcodeQuery + dns.Response = true + dns.Authoritative = false + dns.Id = request.Id + return dns +} + +// SetUpdate makes the message a dynamic update message. It +// sets the ZONE section to: z, TypeSOA, ClassINET. +func (dns *Msg) SetUpdate(z string) *Msg { + dns.Id = Id() + dns.Response = false + dns.Opcode = OpcodeUpdate + dns.Compress = false // BIND9 cannot handle compression + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeSOA, ClassINET} + return dns +} + +// SetIxfr creates message for requesting an IXFR. +func (dns *Msg) SetIxfr(z string, serial uint32) *Msg { + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Ns = make([]RR, 1) + s := new(SOA) + s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} + s.Serial = serial + dns.Question[0] = Question{z, TypeIXFR, ClassINET} + dns.Ns[0] = s + return dns +} + +// SetAxfr creates message for requesting an AXFR. +func (dns *Msg) SetAxfr(z string) *Msg { + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeAXFR, ClassINET} + return dns +} + +// 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. +func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { + t := new(TSIG) + t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} + t.Algorithm = algo + t.Fudge = 300 + t.TimeSigned = uint64(timesigned) + t.OrigId = dns.Id + dns.Extra = append(dns.Extra, t) + return dns +} + +// SetEdns0 appends a EDNS0 OPT RR to the message. +// TSIG should always the last RR in a message. +func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { + e := new(OPT) + e.Hdr.Name = "." + e.Hdr.Rrtype = TypeOPT + e.SetUDPSize(udpsize) + if do { + e.SetDo() + } + dns.Extra = append(dns.Extra, e) + return dns +} + +// IsTsig checks if the message has a TSIG record as the last record +// in the additional section. It returns the TSIG record found or nil. +func (dns *Msg) IsTsig() *TSIG { + if len(dns.Extra) > 0 { + if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { + return dns.Extra[len(dns.Extra)-1].(*TSIG) + } + } + return nil +} + +// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 +// record in the additional section will do. It returns the OPT record +// found or nil. +func (dns *Msg) IsEdns0() *OPT { + for _, r := range dns.Extra { + if r.Header().Rrtype == TypeOPT { + return r.(*OPT) + } + } + return nil +} + +// IsDomainName checks if s is a valid domainname, it returns +// the number of labels and true, when a domain name is valid. +// Note that non fully qualified domain name is considered valid, in this case the +// last label is counted in the number of labels. +// When false is returned the number of labels is not defined. +func IsDomainName(s string) (labels int, ok bool) { + _, labels, err := packDomainName(s, nil, 0, nil, false) + return labels, err == nil +} + +// IsSubDomain checks if child is indeed a child of the parent. Both child and +// parent are *not* downcased before doing the comparison. +func IsSubDomain(parent, child string) bool { + // Entire child is contained in parent + return CompareDomainName(parent, child) == CountLabel(parent) +} + +// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. +// The checking is performed on the binary payload. +func IsMsg(buf []byte) error { + // Header + if len(buf) < 12 { + return errors.New("dns: bad message header") + } + // Header: Opcode + // TODO(miek): more checks here, e.g. check all header bits. + return nil +} + +// IsFqdn checks if a domain name is fully qualified. +func IsFqdn(s string) bool { + l := len(s) + if l == 0 { + return false + } + return s[l-1] == '.' +} + +// Fqdns return the fully qualified domain name from s. +// If s is already fully qualified, it behaves as the identity function. +func Fqdn(s string) string { + if IsFqdn(s) { + return s + } + return s + "." +} + +// Copied from the official Go code. + +// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address suitable for reverse DNS (PTR) record lookups or an error if it fails +// to parse the IP address. +func ReverseAddr(addr string) (arpa string, err error) { + ip := net.ParseIP(addr) + if ip == nil { + return "", &Error{err: "unrecognized address: " + addr} + } + if ip.To4() != nil { + return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + + strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil + } + // Must be IPv6 + buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) + // Add it, in reverse, to the buffer + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF]) + buf = append(buf, '.') + buf = append(buf, hexDigit[v>>4]) + buf = append(buf, '.') + } + // Append "ip6.arpa." and return (buf already has the final .) + buf = append(buf, "ip6.arpa."...) + return string(buf), nil +} + +// String returns the string representation for the type t. +func (t Type) String() string { + if t1, ok := TypeToString[uint16(t)]; ok { + return t1 + } + return "TYPE" + strconv.Itoa(int(t)) +} + +// String returns the string representation for the class c. +func (c Class) String() string { + if c1, ok := ClassToString[uint16(c)]; ok { + return c1 + } + return "CLASS" + strconv.Itoa(int(c)) +} + +// String returns the string representation for the name n. +func (n Name) String() string { + return sprintName(string(n)) +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/dns.go b/Godeps/_workspace/src/github.com/miekg/dns/dns.go new file mode 100644 index 00000000000..7540c0d5bca --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/dns.go @@ -0,0 +1,193 @@ +// Package dns implements a full featured interface to the Domain Name System. +// Server- and client-side programming is supported. +// The package allows complete control over what is send out to the DNS. The package +// API follows the less-is-more principle, by presenting a small, clean interface. +// +// The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, +// TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. +// Note that domain names MUST be fully qualified, before sending them, unqualified +// names in a message will result in a packing failure. +// +// Resource records are native types. They are not stored in wire format. +// Basic usage pattern for creating a new resource record: +// +// r := new(dns.MX) +// r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600} +// r.Preference = 10 +// r.Mx = "mx.miek.nl." +// +// Or directly from a string: +// +// mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") +// +// Or when the default TTL (3600) and class (IN) suit you: +// +// mx, err := dns.NewRR("miek.nl. MX 10 mx.miek.nl.") +// +// Or even: +// +// mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") +// +// In the DNS messages are exchanged, these messages contain resource +// records (sets). Use pattern for creating a message: +// +// m := new(dns.Msg) +// m.SetQuestion("miek.nl.", dns.TypeMX) +// +// Or when not certain if the domain name is fully qualified: +// +// m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) +// +// The message m is now a message with the question section set to ask +// the MX records for the miek.nl. zone. +// +// The following is slightly more verbose, but more flexible: +// +// m1 := new(dns.Msg) +// m1.Id = dns.Id() +// m1.RecursionDesired = true +// m1.Question = make([]dns.Question, 1) +// m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} +// +// After creating a message it can be send. +// Basic use pattern for synchronous querying the DNS at a +// server configured on 127.0.0.1 and port 53: +// +// c := new(dns.Client) +// in, rtt, err := c.Exchange(m1, "127.0.0.1:53") +// +// Suppressing +// multiple outstanding queries (with the same question, type and class) is as easy as setting: +// +// c.SingleInflight = true +// +// If these "advanced" features are not needed, a simple UDP query can be send, +// with: +// +// in, err := dns.Exchange(m1, "127.0.0.1:53") +// +// When this functions returns you will get dns message. A dns message consists +// out of four sections. +// The question section: in.Question, the answer section: in.Answer, +// the authority section: in.Ns and the additional section: in.Extra. +// +// Each of these sections (except the Question section) contain a []RR. Basic +// use pattern for accessing the rdata of a TXT RR as the first RR in +// the Answer section: +// +// if t, ok := in.Answer[0].(*dns.TXT); ok { +// // do something with t.Txt +// } +// +// Domain Name and TXT Character String Representations +// +// Both domain names and TXT character strings are converted to presentation +// form both when unpacked and when converted to strings. +// +// For TXT character strings, tabs, carriage returns and line feeds will be +// converted to \t, \r and \n respectively. Back slashes and quotations marks +// will be escaped. Bytes below 32 and above 127 will be converted to \DDD +// form. +// +// For domain names, in addition to the above rules brackets, periods, +// spaces, semicolons and the at symbol are escaped. +package dns + +import ( + "strconv" +) + +const ( + year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. + DefaultMsgSize = 4096 // Standard default for larger than 512 bytes. + MinMsgSize = 512 // Minimal size of a DNS packet. + MaxMsgSize = 65536 // Largest possible DNS packet. + defaultTtl = 3600 // Default TTL. +) + +// Error represents a DNS error +type Error struct{ err string } + +func (e *Error) Error() string { + if e == nil { + return "dns: " + } + return "dns: " + e.err +} + +// An RR represents a resource record. +type RR interface { + // Header returns the header of an resource record. The header contains + // everything up to the rdata. + Header() *RR_Header + // String returns the text representation of the resource record. + String() string + // copy returns a copy of the RR + copy() RR + // len returns the length (in octects) of the uncompressed RR in wire format. + len() int +} + +// DNS resource records. +// There are many types of RRs, +// but they all share the same header. +type RR_Header struct { + Name string `dns:"cdomain-name"` + Rrtype uint16 + Class uint16 + Ttl uint32 + Rdlength uint16 // length of data after header +} + +func (h *RR_Header) Header() *RR_Header { return h } + +// Just to imlement the RR interface +func (h *RR_Header) copy() RR { return nil } + +func (h *RR_Header) copyHeader() *RR_Header { + r := new(RR_Header) + r.Name = h.Name + r.Rrtype = h.Rrtype + r.Class = h.Class + r.Ttl = h.Ttl + r.Rdlength = h.Rdlength + return r +} + +func (h *RR_Header) String() string { + var s string + + if h.Rrtype == TypeOPT { + s = ";" + // and maybe other things + } + + s += sprintName(h.Name) + "\t" + s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" + s += Class(h.Class).String() + "\t" + s += Type(h.Rrtype).String() + "\t" + return s +} + +func (h *RR_Header) len() int { + l := len(h.Name) + 1 + l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) + return l +} + +// ToRFC3597 converts a known RR to the unknown RR representation +// from RFC 3597. +func (rr *RFC3597) ToRFC3597(r RR) error { + buf := make([]byte, r.len()*2) + off, err := PackStruct(r, buf, 0) + if err != nil { + return err + } + buf = buf[:off] + rawSetRdlength(buf, 0, off) + _, err = UnpackStruct(rr, buf, 0) + if err != nil { + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/dns_test.go b/Godeps/_workspace/src/github.com/miekg/dns/dns_test.go new file mode 100644 index 00000000000..16c86f474f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/dns_test.go @@ -0,0 +1,511 @@ +package dns + +import ( + "encoding/hex" + "net" + "testing" +) + +func TestPackUnpack(t *testing.T) { + out := new(Msg) + out.Answer = make([]RR, 1) + key := new(DNSKEY) + key = &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1} + key.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 3600} + key.PublicKey = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" + + out.Answer[0] = key + msg, err := out.Pack() + if err != nil { + t.Log("failed to pack msg with DNSKEY") + t.Fail() + } + in := new(Msg) + if in.Unpack(msg) != nil { + t.Log("failed to unpack msg with DNSKEY") + t.Fail() + } + + sig := new(RRSIG) + sig = &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2, + OrigTtl: 3600, Expiration: 4000, Inception: 4000, KeyTag: 34641, SignerName: "miek.nl.", + Signature: "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"} + sig.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeRRSIG, Class: ClassINET, Ttl: 3600} + + out.Answer[0] = sig + msg, err = out.Pack() + if err != nil { + t.Log("failed to pack msg with RRSIG") + t.Fail() + } + + if in.Unpack(msg) != nil { + t.Log("failed to unpack msg with RRSIG") + t.Fail() + } +} + +func TestPackUnpack2(t *testing.T) { + m := new(Msg) + m.Extra = make([]RR, 1) + m.Answer = make([]RR, 1) + dom := "miek.nl." + rr := new(A) + rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0} + rr.A = net.IPv4(127, 0, 0, 1) + + x := new(TXT) + x.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} + x.Txt = []string{"heelalaollo"} + + m.Extra[0] = x + m.Answer[0] = rr + _, err := m.Pack() + if err != nil { + t.Log("Packing failed: " + err.Error()) + t.Fail() + return + } +} + +func TestPackUnpack3(t *testing.T) { + m := new(Msg) + m.Extra = make([]RR, 2) + m.Answer = make([]RR, 1) + dom := "miek.nl." + rr := new(A) + rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0} + rr.A = net.IPv4(127, 0, 0, 1) + + x1 := new(TXT) + x1.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} + x1.Txt = []string{} + + x2 := new(TXT) + x2.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} + x2.Txt = []string{"heelalaollo"} + + m.Extra[0] = x1 + m.Extra[1] = x2 + m.Answer[0] = rr + b, err := m.Pack() + if err != nil { + t.Log("packing failed: " + err.Error()) + t.Fail() + return + } + + var unpackMsg Msg + err = unpackMsg.Unpack(b) + if err != nil { + t.Log("unpacking failed") + t.Fail() + return + } +} + +func TestBailiwick(t *testing.T) { + yes := map[string]string{ + "miek.nl": "ns.miek.nl", + ".": "miek.nl", + } + for parent, child := range yes { + if !IsSubDomain(parent, child) { + t.Logf("%s should be child of %s\n", child, parent) + t.Logf("comparelabels %d", CompareDomainName(parent, child)) + t.Logf("lenlabels %d %d", CountLabel(parent), CountLabel(child)) + t.Fail() + } + } + no := map[string]string{ + "www.miek.nl": "ns.miek.nl", + "m\\.iek.nl": "ns.miek.nl", + "w\\.iek.nl": "w.iek.nl", + "p\\\\.iek.nl": "ns.p.iek.nl", // p\\.iek.nl , literal \ in domain name + "miek.nl": ".", + } + for parent, child := range no { + if IsSubDomain(parent, child) { + t.Logf("%s should not be child of %s\n", child, parent) + t.Logf("comparelabels %d", CompareDomainName(parent, child)) + t.Logf("lenlabels %d %d", CountLabel(parent), CountLabel(child)) + t.Fail() + } + } +} + +func TestPack(t *testing.T) { + rr := []string{"US. 86400 IN NSEC 0-.us. NS SOA RRSIG NSEC DNSKEY TYPE65534"} + m := new(Msg) + var err error + m.Answer = make([]RR, 1) + for _, r := range rr { + m.Answer[0], err = NewRR(r) + if err != nil { + t.Logf("failed to create RR: %s\n", err.Error()) + t.Fail() + continue + } + if _, err := m.Pack(); err != nil { + t.Logf("packing failed: %s\n", err.Error()) + t.Fail() + } + } + x := new(Msg) + ns, _ := NewRR("pool.ntp.org. 390 IN NS a.ntpns.org") + ns.(*NS).Ns = "a.ntpns.org" + x.Ns = append(m.Ns, ns) + x.Ns = append(m.Ns, ns) + x.Ns = append(m.Ns, ns) + // This crashes due to the fact the a.ntpns.org isn't a FQDN + // How to recover() from a remove panic()? + if _, err := x.Pack(); err == nil { + t.Log("packing should fail") + t.Fail() + } + x.Answer = make([]RR, 1) + x.Answer[0], err = NewRR(rr[0]) + if _, err := x.Pack(); err == nil { + t.Log("packing should fail") + t.Fail() + } + x.Question = make([]Question, 1) + x.Question[0] = Question{";sd#edddds鍛↙赏‘℅∥↙xzztsestxssweewwsssstx@s@Z嵌e@cn.pool.ntp.org.", TypeA, ClassINET} + if _, err := x.Pack(); err == nil { + t.Log("packing should fail") + t.Fail() + } +} + +func TestPackNAPTR(t *testing.T) { + for _, n := range []string{ + `apple.com. IN NAPTR 100 50 "se" "SIP+D2U" "" _sip._udp.apple.com.`, + `apple.com. IN NAPTR 90 50 "se" "SIP+D2T" "" _sip._tcp.apple.com.`, + `apple.com. IN NAPTR 50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.`, + } { + rr, _ := NewRR(n) + msg := make([]byte, rr.len()) + if off, err := PackRR(rr, msg, 0, nil, false); err != nil { + t.Logf("packing failed: %s", err.Error()) + t.Logf("length %d, need more than %d\n", rr.len(), off) + t.Fail() + } else { + t.Logf("buf size needed: %d\n", off) + } + } +} + +func TestCompressLength(t *testing.T) { + m := new(Msg) + m.SetQuestion("miek.nl", TypeMX) + ul := m.Len() + m.Compress = true + if ul != m.Len() { + t.Fatalf("should be equal") + } +} + +// Does the predicted length match final packed length? +func TestMsgCompressLength(t *testing.T) { + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + msg.Compress = true + return msg + } + + name1 := "12345678901234567890123456789012345.12345678.123." + rrA, _ := NewRR(name1 + " 3600 IN A 192.0.2.1") + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + tests := []*Msg{ + makeMsg(name1, []RR{rrA}, nil, nil), + makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)} + + for _, msg := range tests { + predicted := msg.Len() + buf, err := msg.Pack() + if err != nil { + t.Error(err) + t.Fail() + } + if predicted < len(buf) { + t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d\n", + msg.Question[0].Name, len(msg.Answer), predicted, len(buf)) + t.Fail() + } + } +} + +func TestMsgLength(t *testing.T) { + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + return msg + } + + name1 := "12345678901234567890123456789012345.12345678.123." + rrA, _ := NewRR(name1 + " 3600 IN A 192.0.2.1") + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + tests := []*Msg{ + makeMsg(name1, []RR{rrA}, nil, nil), + makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)} + + for _, msg := range tests { + predicted := msg.Len() + buf, err := msg.Pack() + if err != nil { + t.Error(err) + t.Fail() + } + if predicted < len(buf) { + t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d\n", + msg.Question[0].Name, predicted, len(buf)) + t.Fail() + } + } +} + +func TestMsgLength2(t *testing.T) { + // Serialized replies + var testMessages = []string{ + // google.com. IN A? + "064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000", + // amazon.com. IN A? (reply has no EDNS0 record) + // TODO(miek): this one is off-by-one, need to find out why + //"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001", + // yahoo.com. IN A? + "fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000", + // microsoft.com. IN A? + "f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000000010001c07700010001000000050004d5c7b435c077001c00010000000500102a010111202000000000000000010001c08900010001000000050004cf2e4bfec089001c00010000000500102404f800200300000000000000010001c09b000100010000000500044137e28cc09b001c00010000000500102a010111200f000100000000000100010000290500000000050000", + // google.com. IN MX? + "724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c01000000000000001bc04200010001000000050004adc2461bc05700010001000000050004adc2451bc06c000100010000000500044a7d8f1bc081000100010000000500044a7d191bc0ca00010001000000050004d8ef200ac09400010001000000050004d8ef220ac0a600010001000000050004d8ef240ac0b800010001000000050004d8ef260a0000290500000000050000", + // reddit.com. IN A? + "12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690001000100000005000460073240c07c000100010000000500046007fb81c090000100010000000500047c283484c090001c00010000000500102a0226f0006700000000000000000064c0a400010001000000050004c16c5b01c0a4001c000100000005001026001401000200000000000000000001c0b800010001000000050004c16c5bc3c0b8001c0001000000050010260014010002000000000000000000c30000290500000000050000", + } + + for i, hexData := range testMessages { + // we won't fail the decoding of the hex + input, _ := hex.DecodeString(hexData) + m := new(Msg) + m.Unpack(input) + //println(m.String()) + m.Compress = true + lenComp := m.Len() + b, _ := m.Pack() + pacComp := len(b) + m.Compress = false + lenUnComp := m.Len() + b, _ = m.Pack() + pacUnComp := len(b) + if pacComp+1 != lenComp { + t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i) + } + if pacUnComp+1 != lenUnComp { + t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i) + } + } +} + +func TestMsgLengthCompressionMalformed(t *testing.T) { + // SOA with empty hostmaster, which is illegal + soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345}, + Ns: ".", + Mbox: "", + Serial: 0, + Refresh: 28800, + Retry: 7200, + Expire: 604800, + Minttl: 60} + m := new(Msg) + m.Compress = true + m.Ns = []RR{soa} + m.Len() // Should not crash. +} + +func BenchmarkMsgLength(b *testing.B) { + b.StopTimer() + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + msg.Compress = true + return msg + } + name1 := "12345678901234567890123456789012345.12345678.123." + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) + b.StartTimer() + for i := 0; i < b.N; i++ { + msg.Len() + } +} + +func BenchmarkMsgLengthPack(b *testing.B) { + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + msg.Compress = true + return msg + } + name1 := "12345678901234567890123456789012345.12345678.123." + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = msg.Pack() + } +} + +func BenchmarkMsgPackBuffer(b *testing.B) { + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + msg.Compress = true + return msg + } + name1 := "12345678901234567890123456789012345.12345678.123." + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) + buf := make([]byte, 512) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = msg.PackBuffer(buf) + } +} + +func BenchmarkMsgUnpack(b *testing.B) { + makeMsg := func(question string, ans, ns, e []RR) *Msg { + msg := new(Msg) + msg.SetQuestion(Fqdn(question), TypeANY) + msg.Answer = append(msg.Answer, ans...) + msg.Ns = append(msg.Ns, ns...) + msg.Extra = append(msg.Extra, e...) + msg.Compress = true + return msg + } + name1 := "12345678901234567890123456789012345.12345678.123." + rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) + msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) + msg_buf, _ := msg.Pack() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = msg.Unpack(msg_buf) + } +} + +func BenchmarkPackDomainName(b *testing.B) { + name1 := "12345678901234567890123456789012345.12345678.123." + buf := make([]byte, len(name1)+1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = PackDomainName(name1, buf, 0, nil, false) + } +} + +func BenchmarkUnpackDomainName(b *testing.B) { + name1 := "12345678901234567890123456789012345.12345678.123." + buf := make([]byte, len(name1)+1) + _, _ = PackDomainName(name1, buf, 0, nil, false) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, _ = UnpackDomainName(buf, 0) + } +} + +func BenchmarkUnpackDomainNameUnprintable(b *testing.B) { + name1 := "\x02\x02\x02\x025\x02\x02\x02\x02.12345678.123." + buf := make([]byte, len(name1)+1) + _, _ = PackDomainName(name1, buf, 0, nil, false) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, _ = UnpackDomainName(buf, 0) + } +} + +func TestToRFC3597(t *testing.T) { + a, _ := NewRR("miek.nl. IN A 10.0.1.1") + x := new(RFC3597) + x.ToRFC3597(a) + if x.String() != `miek.nl. 3600 CLASS1 TYPE1 \# 4 0a000101` { + t.Fail() + } +} + +func TestNoRdataPack(t *testing.T) { + data := make([]byte, 1024) + for typ, fn := range typeToRR { + if typ == TypeCAA { + continue // TODO(miek): known omission + } + r := fn() + *r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 3600} + _, e := PackRR(r, data, 0, nil, false) + if e != nil { + t.Logf("failed to pack RR with zero rdata: %s: %s\n", TypeToString[typ], e.Error()) + t.Fail() + } + } +} + +// TODO(miek): fix dns buffer too small errors this throws +func TestNoRdataUnpack(t *testing.T) { + data := make([]byte, 1024) + for typ, fn := range typeToRR { + if typ == TypeSOA || typ == TypeTSIG || typ == TypeWKS { + // SOA, TSIG will not be seen (like this) in dyn. updates? + // WKS is an bug, but...deprecated record. + continue + } + r := fn() + *r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 3600} + off, e := PackRR(r, data, 0, nil, false) + if e != nil { + // Should always works, TestNoDataPack should have catched this + continue + } + rr, _, e := UnpackRR(data[:off], 0) + if e != nil { + t.Logf("failed to unpack RR with zero rdata: %s: %s\n", TypeToString[typ], e.Error()) + t.Fail() + } + t.Logf("%s\n", rr) + } +} + +func TestRdataOverflow(t *testing.T) { + rr := new(RFC3597) + rr.Hdr.Name = "." + rr.Hdr.Class = ClassINET + rr.Hdr.Rrtype = 65280 + rr.Rdata = hex.EncodeToString(make([]byte, 0xFFFF)) + buf := make([]byte, 0xFFFF*2) + if _, err := PackRR(rr, buf, 0, nil, false); err != nil { + t.Fatalf("maximum size rrdata pack failed: %v", err) + } + rr.Rdata += "00" + if _, err := PackRR(rr, buf, 0, nil, false); err != ErrRdata { + t.Fatalf("oversize rrdata pack didn't return ErrRdata - instead: %v", err) + } +} + +func TestCopy(t *testing.T) { + rr, _ := NewRR("miek.nl. 2311 IN A 127.0.0.1") // Weird TTL to avoid catching TTL + rr1 := Copy(rr) + if rr.String() != rr1.String() { + t.Fatalf("Copy() failed %s != %s", rr.String(), rr1.String()) + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/dnssec.go b/Godeps/_workspace/src/github.com/miekg/dns/dnssec.go new file mode 100644 index 00000000000..d1c2ae62582 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/dnssec.go @@ -0,0 +1,756 @@ +// DNSSEC +// +// DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It +// uses public key cryptography to sign resource records. The +// public keys are stored in DNSKEY records and the signatures in RRSIG records. +// +// Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit +// to an request. +// +// m := new(dns.Msg) +// m.SetEdns0(4096, true) +// +// Signature generation, signature verification and key generation are all supported. +package dns + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/md5" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "hash" + "io" + "math/big" + "sort" + "strings" + "time" +) + +// DNSSEC encryption algorithm codes. +const ( + _ uint8 = iota + RSAMD5 + DH + DSA + _ // Skip 4, RFC 6725, section 2.1 + RSASHA1 + DSANSEC3SHA1 + RSASHA1NSEC3SHA1 + RSASHA256 + _ // Skip 9, RFC 6725, section 2.1 + RSASHA512 + _ // Skip 11, RFC 6725, section 2.1 + ECCGOST + ECDSAP256SHA256 + ECDSAP384SHA384 + INDIRECT uint8 = 252 + PRIVATEDNS uint8 = 253 // Private (experimental keys) + PRIVATEOID uint8 = 254 +) + +// DNSSEC hashing algorithm codes. +const ( + _ uint8 = iota + SHA1 // RFC 4034 + SHA256 // RFC 4509 + GOST94 // RFC 5933 + SHA384 // Experimental + SHA512 // Experimental +) + +// DNSKEY flag values. +const ( + SEP = 1 + REVOKE = 1 << 7 + ZONE = 1 << 8 +) + +// The RRSIG needs to be converted to wireformat with some of +// the rdata (the signature) missing. Use this struct to easy +// the conversion (and re-use the pack/unpack functions). +type rrsigWireFmt struct { + TypeCovered uint16 + Algorithm uint8 + Labels uint8 + OrigTtl uint32 + Expiration uint32 + Inception uint32 + KeyTag uint16 + SignerName string `dns:"domain-name"` + /* No Signature */ +} + +// Used for converting DNSKEY's rdata to wirefmt. +type dnskeyWireFmt struct { + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` + /* Nothing is left out */ +} + +func divRoundUp(a, b int) int { + return (a + b - 1) / b +} + +// KeyTag calculates the keytag (or key-id) of the DNSKEY. +func (k *DNSKEY) KeyTag() uint16 { + if k == nil { + return 0 + } + var keytag int + switch k.Algorithm { + case RSAMD5: + // Look at the bottom two bytes of the modules, which the last + // item in the pubkey. We could do this faster by looking directly + // at the base64 values. But I'm lazy. + modulus, _ := fromBase64([]byte(k.PublicKey)) + if len(modulus) > 1 { + x, _ := unpackUint16(modulus, len(modulus)-2) + keytag = int(x) + } + default: + keywire := new(dnskeyWireFmt) + keywire.Flags = k.Flags + keywire.Protocol = k.Protocol + keywire.Algorithm = k.Algorithm + keywire.PublicKey = k.PublicKey + wire := make([]byte, DefaultMsgSize) + n, err := PackStruct(keywire, wire, 0) + if err != nil { + return 0 + } + wire = wire[:n] + for i, v := range wire { + if i&1 != 0 { + keytag += int(v) // must be larger than uint32 + } else { + keytag += int(v) << 8 + } + } + keytag += (keytag >> 16) & 0xFFFF + keytag &= 0xFFFF + } + return uint16(keytag) +} + +// ToDS converts a DNSKEY record to a DS record. +func (k *DNSKEY) ToDS(h uint8) *DS { + if k == nil { + return nil + } + ds := new(DS) + ds.Hdr.Name = k.Hdr.Name + ds.Hdr.Class = k.Hdr.Class + ds.Hdr.Rrtype = TypeDS + ds.Hdr.Ttl = k.Hdr.Ttl + ds.Algorithm = k.Algorithm + ds.DigestType = h + ds.KeyTag = k.KeyTag() + + keywire := new(dnskeyWireFmt) + keywire.Flags = k.Flags + keywire.Protocol = k.Protocol + keywire.Algorithm = k.Algorithm + keywire.PublicKey = k.PublicKey + wire := make([]byte, DefaultMsgSize) + n, err := PackStruct(keywire, wire, 0) + if err != nil { + return nil + } + wire = wire[:n] + + owner := make([]byte, 255) + off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false) + if err1 != nil { + return nil + } + owner = owner[:off] + // RFC4034: + // digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); + // "|" denotes concatenation + // DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. + + // digest buffer + digest := append(owner, wire...) // another copy + + switch h { + case SHA1: + s := sha1.New() + io.WriteString(s, string(digest)) + ds.Digest = hex.EncodeToString(s.Sum(nil)) + case SHA256: + s := sha256.New() + io.WriteString(s, string(digest)) + ds.Digest = hex.EncodeToString(s.Sum(nil)) + case SHA384: + s := sha512.New384() + io.WriteString(s, string(digest)) + ds.Digest = hex.EncodeToString(s.Sum(nil)) + case GOST94: + /* I have no clue */ + default: + return nil + } + return ds +} + +// Sign signs an RRSet. The signature needs to be filled in with +// the values: Inception, Expiration, KeyTag, SignerName and Algorithm. +// The rest is copied from the RRset. Sign returns true when the signing went OK, +// otherwise false. +// There is no check if RRSet is a proper (RFC 2181) RRSet. +// If OrigTTL is non zero, it is used as-is, otherwise the TTL of the RRset +// is used as the OrigTTL. +func (rr *RRSIG) Sign(k PrivateKey, rrset []RR) error { + if k == nil { + return ErrPrivKey + } + // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set + if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + return ErrKey + } + + rr.Hdr.Rrtype = TypeRRSIG + rr.Hdr.Name = rrset[0].Header().Name + rr.Hdr.Class = rrset[0].Header().Class + if rr.OrigTtl == 0 { // If set don't override + rr.OrigTtl = rrset[0].Header().Ttl + } + rr.TypeCovered = rrset[0].Header().Rrtype + rr.Labels = uint8(CountLabel(rrset[0].Header().Name)) + + if strings.HasPrefix(rrset[0].Header().Name, "*") { + rr.Labels-- // wildcard, remove from label count + } + + sigwire := new(rrsigWireFmt) + sigwire.TypeCovered = rr.TypeCovered + sigwire.Algorithm = rr.Algorithm + sigwire.Labels = rr.Labels + sigwire.OrigTtl = rr.OrigTtl + sigwire.Expiration = rr.Expiration + sigwire.Inception = rr.Inception + sigwire.KeyTag = rr.KeyTag + // For signing, lowercase this name + sigwire.SignerName = strings.ToLower(rr.SignerName) + + // Create the desired binary blob + signdata := make([]byte, DefaultMsgSize) + n, err := PackStruct(sigwire, signdata, 0) + if err != nil { + return err + } + signdata = signdata[:n] + wire, err := rawSignatureData(rrset, rr) + if err != nil { + return err + } + signdata = append(signdata, wire...) + + var sighash []byte + var h hash.Hash + var ch crypto.Hash // Only need for RSA + var intlen int + switch rr.Algorithm { + case DSA, DSANSEC3SHA1: + // Implicit in the ParameterSizes + case RSASHA1, RSASHA1NSEC3SHA1: + h = sha1.New() + ch = crypto.SHA1 + case RSASHA256, ECDSAP256SHA256: + h = sha256.New() + ch = crypto.SHA256 + intlen = 32 + case ECDSAP384SHA384: + h = sha512.New384() + intlen = 48 + case RSASHA512: + h = sha512.New() + ch = crypto.SHA512 + case RSAMD5: + fallthrough // Deprecated in RFC 6725 + default: + return ErrAlg + } + io.WriteString(h, string(signdata)) + sighash = h.Sum(nil) + + switch p := k.(type) { + case *dsa.PrivateKey: + r1, s1, err := dsa.Sign(rand.Reader, p, sighash) + if err != nil { + return err + } + signature := []byte{0x4D} // T value, here the ASCII M for Miek (not used in DNSSEC) + signature = append(signature, intToBytes(r1, 20)...) + signature = append(signature, intToBytes(s1, 20)...) + rr.Signature = toBase64(signature) + case *rsa.PrivateKey: + // We can use nil as rand.Reader here (says AGL) + signature, err := rsa.SignPKCS1v15(nil, p, ch, sighash) + if err != nil { + return err + } + rr.Signature = toBase64(signature) + case *ecdsa.PrivateKey: + r1, s1, err := ecdsa.Sign(rand.Reader, p, sighash) + if err != nil { + return err + } + signature := intToBytes(r1, intlen) + signature = append(signature, intToBytes(s1, intlen)...) + rr.Signature = toBase64(signature) + default: + // Not given the correct key + return ErrKeyAlg + } + return nil +} + +// Verify validates an RRSet with the signature and key. This is only the +// cryptographic test, the signature validity period must be checked separately. +// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. +func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { + // First the easy checks + if len(rrset) == 0 { + return ErrRRset + } + if rr.KeyTag != k.KeyTag() { + return ErrKey + } + if rr.Hdr.Class != k.Hdr.Class { + return ErrKey + } + if rr.Algorithm != k.Algorithm { + return ErrKey + } + if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) { + return ErrKey + } + if k.Protocol != 3 { + return ErrKey + } + for _, r := range rrset { + if r.Header().Class != rr.Hdr.Class { + return ErrRRset + } + if r.Header().Rrtype != rr.TypeCovered { + return ErrRRset + } + } + // RFC 4035 5.3.2. Reconstructing the Signed Data + // Copy the sig, except the rrsig data + sigwire := new(rrsigWireFmt) + sigwire.TypeCovered = rr.TypeCovered + sigwire.Algorithm = rr.Algorithm + sigwire.Labels = rr.Labels + sigwire.OrigTtl = rr.OrigTtl + sigwire.Expiration = rr.Expiration + sigwire.Inception = rr.Inception + sigwire.KeyTag = rr.KeyTag + sigwire.SignerName = strings.ToLower(rr.SignerName) + // Create the desired binary blob + signeddata := make([]byte, DefaultMsgSize) + n, err := PackStruct(sigwire, signeddata, 0) + if err != nil { + return err + } + signeddata = signeddata[:n] + wire, err := rawSignatureData(rrset, rr) + if err != nil { + return err + } + signeddata = append(signeddata, wire...) + + sigbuf := rr.sigBuf() // Get the binary signature data + if rr.Algorithm == PRIVATEDNS { // PRIVATEOID + // TODO(mg) + // remove the domain name and assume its our + } + + switch rr.Algorithm { + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5: + // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? + pubkey := k.publicKeyRSA() // Get the key + if pubkey == nil { + return ErrKey + } + // Setup the hash as defined for this alg. + var h hash.Hash + var ch crypto.Hash + switch rr.Algorithm { + case RSAMD5: + h = md5.New() + ch = crypto.MD5 + case RSASHA1, RSASHA1NSEC3SHA1: + h = sha1.New() + ch = crypto.SHA1 + case RSASHA256: + h = sha256.New() + ch = crypto.SHA256 + case RSASHA512: + h = sha512.New() + ch = crypto.SHA512 + } + io.WriteString(h, string(signeddata)) + sighash := h.Sum(nil) + return rsa.VerifyPKCS1v15(pubkey, ch, sighash, sigbuf) + case ECDSAP256SHA256, ECDSAP384SHA384: + pubkey := k.publicKeyCurve() + if pubkey == nil { + return ErrKey + } + var h hash.Hash + switch rr.Algorithm { + case ECDSAP256SHA256: + h = sha256.New() + case ECDSAP384SHA384: + h = sha512.New384() + } + io.WriteString(h, string(signeddata)) + sighash := h.Sum(nil) + // Split sigbuf into the r and s coordinates + r := big.NewInt(0) + r.SetBytes(sigbuf[:len(sigbuf)/2]) + s := big.NewInt(0) + s.SetBytes(sigbuf[len(sigbuf)/2:]) + if ecdsa.Verify(pubkey, sighash, r, s) { + return nil + } + return ErrSig + } + // Unknown alg + return ErrAlg +} + +// ValidityPeriod uses RFC1982 serial arithmetic to calculate +// if a signature period is valid. If t is the zero time, the +// current time is taken other t is. +func (rr *RRSIG) ValidityPeriod(t time.Time) bool { + var utc int64 + if t.IsZero() { + utc = time.Now().UTC().Unix() + } else { + utc = t.UTC().Unix() + } + modi := (int64(rr.Inception) - utc) / year68 + mode := (int64(rr.Expiration) - utc) / year68 + ti := int64(rr.Inception) + (modi * year68) + te := int64(rr.Expiration) + (mode * year68) + return ti <= utc && utc <= te +} + +// Return the signatures base64 encodedig sigdata as a byte slice. +func (s *RRSIG) sigBuf() []byte { + sigbuf, err := fromBase64([]byte(s.Signature)) + if err != nil { + return nil + } + return sigbuf +} + +// setPublicKeyInPrivate sets the public key in the private key. +func (k *DNSKEY) setPublicKeyInPrivate(p PrivateKey) bool { + switch t := p.(type) { + case *dsa.PrivateKey: + x := k.publicKeyDSA() + if x == nil { + return false + } + t.PublicKey = *x + case *rsa.PrivateKey: + x := k.publicKeyRSA() + if x == nil { + return false + } + t.PublicKey = *x + case *ecdsa.PrivateKey: + x := k.publicKeyCurve() + if x == nil { + return false + } + t.PublicKey = *x + } + return true +} + +// publicKeyRSA returns the RSA public key from a DNSKEY record. +func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey { + keybuf, err := fromBase64([]byte(k.PublicKey)) + if err != nil { + return nil + } + + // RFC 2537/3110, section 2. RSA Public KEY Resource Records + // Length is in the 0th byte, unless its zero, then it + // it in bytes 1 and 2 and its a 16 bit number + explen := uint16(keybuf[0]) + keyoff := 1 + if explen == 0 { + explen = uint16(keybuf[1])<<8 | uint16(keybuf[2]) + keyoff = 3 + } + pubkey := new(rsa.PublicKey) + + pubkey.N = big.NewInt(0) + shift := uint64((explen - 1) * 8) + expo := uint64(0) + for i := int(explen - 1); i > 0; i-- { + expo += uint64(keybuf[keyoff+i]) << shift + shift -= 8 + } + // Remainder + expo += uint64(keybuf[keyoff]) + if expo > 2<<31 { + // Larger expo than supported. + // println("dns: F5 primes (or larger) are not supported") + return nil + } + pubkey.E = int(expo) + + pubkey.N.SetBytes(keybuf[keyoff+int(explen):]) + return pubkey +} + +// publicKeyCurve returns the Curve public key from the DNSKEY record. +func (k *DNSKEY) publicKeyCurve() *ecdsa.PublicKey { + keybuf, err := fromBase64([]byte(k.PublicKey)) + if err != nil { + return nil + } + pubkey := new(ecdsa.PublicKey) + switch k.Algorithm { + case ECDSAP256SHA256: + pubkey.Curve = elliptic.P256() + if len(keybuf) != 64 { + // wrongly encoded key + return nil + } + case ECDSAP384SHA384: + pubkey.Curve = elliptic.P384() + if len(keybuf) != 96 { + // Wrongly encoded key + return nil + } + } + pubkey.X = big.NewInt(0) + pubkey.X.SetBytes(keybuf[:len(keybuf)/2]) + pubkey.Y = big.NewInt(0) + pubkey.Y.SetBytes(keybuf[len(keybuf)/2:]) + 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 = big.NewInt(0).SetBytes(q) + pubkey.Parameters.P = big.NewInt(0).SetBytes(p) + pubkey.Parameters.G = big.NewInt(0).SetBytes(g) + pubkey.Y = big.NewInt(0).SetBytes(y) + return pubkey +} + +// Set the public key (the value E and N) +func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { + if _E == 0 || _N == nil { + return false + } + buf := exponentToBuf(_E) + buf = append(buf, _N.Bytes()...) + k.PublicKey = toBase64(buf) + return true +} + +// Set the public key for Elliptic Curves +func (k *DNSKEY) setPublicKeyCurve(_X, _Y *big.Int) bool { + if _X == nil || _Y == nil { + return false + } + var intlen int + switch k.Algorithm { + case ECDSAP256SHA256: + intlen = 32 + case ECDSAP384SHA384: + intlen = 48 + } + k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) + return true +} + +// Set the public key for DSA +func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { + if _Q == nil || _P == nil || _G == nil || _Y == nil { + return false + } + buf := dsaToBuf(_Q, _P, _G, _Y) + k.PublicKey = toBase64(buf) + return true +} + +// Set the public key (the values E and N) for RSA +// RFC 3110: Section 2. RSA Public KEY Resource Records +func exponentToBuf(_E int) []byte { + var buf []byte + i := big.NewInt(int64(_E)) + if len(i.Bytes()) < 256 { + buf = make([]byte, 1) + buf[0] = uint8(len(i.Bytes())) + } else { + buf = make([]byte, 3) + buf[0] = 0 + buf[1] = uint8(len(i.Bytes()) >> 8) + buf[2] = uint8(len(i.Bytes())) + } + buf = append(buf, i.Bytes()...) + return buf +} + +// Set the public key for X and Y for Curve. The two +// values are just concatenated. +func curveToBuf(_X, _Y *big.Int, intlen int) []byte { + buf := intToBytes(_X, intlen) + buf = append(buf, intToBytes(_Y, intlen)...) + return buf +} + +// Set the public key for X and Y for Curve. The two +// values are just concatenated. +func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { + t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) + buf := []byte{byte(t)} + buf = append(buf, intToBytes(_Q, 20)...) + buf = append(buf, intToBytes(_P, 64+t*8)...) + buf = append(buf, intToBytes(_G, 64+t*8)...) + buf = append(buf, intToBytes(_Y, 64+t*8)...) + return buf +} + +type wireSlice [][]byte + +func (p wireSlice) Len() int { return len(p) } +func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p wireSlice) Less(i, j int) bool { + _, ioff, _ := UnpackDomainName(p[i], 0) + _, joff, _ := UnpackDomainName(p[j], 0) + return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0 +} + +// Return the raw signature data. +func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) { + wires := make(wireSlice, len(rrset)) + for i, r := range rrset { + r1 := r.copy() + r1.Header().Ttl = s.OrigTtl + labels := SplitDomainName(r1.Header().Name) + // 6.2. Canonical RR Form. (4) - wildcards + if len(labels) > int(s.Labels) { + // Wildcard + r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." + } + // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase + r1.Header().Name = strings.ToLower(r1.Header().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, + // SRV, DNAME, A6 + switch x := r1.(type) { + case *NS: + x.Ns = strings.ToLower(x.Ns) + case *CNAME: + x.Target = strings.ToLower(x.Target) + case *SOA: + x.Ns = strings.ToLower(x.Ns) + x.Mbox = strings.ToLower(x.Mbox) + case *MB: + x.Mb = strings.ToLower(x.Mb) + case *MG: + x.Mg = strings.ToLower(x.Mg) + case *MR: + x.Mr = strings.ToLower(x.Mr) + case *PTR: + x.Ptr = strings.ToLower(x.Ptr) + case *MINFO: + x.Rmail = strings.ToLower(x.Rmail) + x.Email = strings.ToLower(x.Email) + case *MX: + x.Mx = strings.ToLower(x.Mx) + case *NAPTR: + x.Replacement = strings.ToLower(x.Replacement) + case *KX: + x.Exchanger = strings.ToLower(x.Exchanger) + case *SRV: + x.Target = strings.ToLower(x.Target) + case *DNAME: + x.Target = strings.ToLower(x.Target) + } + // 6.2. Canonical RR Form. (5) - origTTL + wire := make([]byte, r1.len()+1) // +1 to be safe(r) + off, err1 := PackRR(r1, wire, 0, nil, false) + if err1 != nil { + return nil, err1 + } + wire = wire[:off] + wires[i] = wire + } + sort.Sort(wires) + for _, wire := range wires { + buf = append(buf, wire...) + } + return buf, nil +} + +// Map for algorithm names. +var AlgorithmToString = map[uint8]string{ + RSAMD5: "RSAMD5", + DH: "DH", + DSA: "DSA", + RSASHA1: "RSASHA1", + DSANSEC3SHA1: "DSA-NSEC3-SHA1", + RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1", + RSASHA256: "RSASHA256", + RSASHA512: "RSASHA512", + ECCGOST: "ECC-GOST", + ECDSAP256SHA256: "ECDSAP256SHA256", + ECDSAP384SHA384: "ECDSAP384SHA384", + INDIRECT: "INDIRECT", + PRIVATEDNS: "PRIVATEDNS", + PRIVATEOID: "PRIVATEOID", +} + +// Map of algorithm strings. +var StringToAlgorithm = reverseInt8(AlgorithmToString) + +// Map for hash names. +var HashToString = map[uint8]string{ + SHA1: "SHA1", + SHA256: "SHA256", + GOST94: "GOST94", + SHA384: "SHA384", + SHA512: "SHA512", +} + +// Map of hash strings. +var StringToHash = reverseInt8(HashToString) diff --git a/Godeps/_workspace/src/github.com/miekg/dns/dnssec_test.go b/Godeps/_workspace/src/github.com/miekg/dns/dnssec_test.go new file mode 100644 index 00000000000..f6263d505f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/dnssec_test.go @@ -0,0 +1,672 @@ +package dns + +import ( + "crypto/rsa" + "reflect" + "strings" + "testing" + "time" +) + +func getKey() *DNSKEY { + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" + return key +} + +func getSoa() *SOA { + soa := new(SOA) + soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} + soa.Ns = "open.nlnetlabs.nl." + soa.Mbox = "miekg.atoom.net." + soa.Serial = 1293945905 + soa.Refresh = 14400 + soa.Retry = 3600 + soa.Expire = 604800 + soa.Minttl = 86400 + return soa +} + +func TestGenerateEC(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = ECDSAP256SHA256 + privkey, _ := key.Generate(256) + t.Logf("%s\n", key.String()) + t.Logf("%s\n", key.PrivateKeyString(privkey)) +} + +func TestGenerateDSA(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = DSA + privkey, _ := key.Generate(1024) + t.Logf("%s\n", key.String()) + t.Logf("%s\n", key.PrivateKeyString(privkey)) +} + +func TestGenerateRSA(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + privkey, _ := key.Generate(1024) + t.Logf("%s\n", key.String()) + t.Logf("%s\n", key.PrivateKeyString(privkey)) +} + +func TestSecure(t *testing.T) { + soa := getSoa() + + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = TypeSOA + sig.Algorithm = RSASHA256 + sig.Labels = 2 + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.OrigTtl = 14400 + sig.KeyTag = 12051 + sig.SignerName = "miek.nl." + sig.Signature = "oMCbslaAVIp/8kVtLSms3tDABpcPRUgHLrOR48OOplkYo+8TeEGWwkSwaz/MRo2fB4FxW0qj/hTlIjUGuACSd+b1wKdH5GvzRJc2pFmxtCbm55ygAh4EUL0F6U5cKtGJGSXxxg6UFCQ0doJCmiGFa78LolaUOXImJrk6AFrGa0M=" + + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" + + // It should validate. Period is checked seperately, so this will keep on working + if sig.Verify(key, []RR{soa}) != nil { + t.Log("failure to validate") + t.Fail() + } +} + +func TestSignature(t *testing.T) { + sig := new(RRSIG) + sig.Hdr.Name = "miek.nl." + sig.Hdr.Class = ClassINET + sig.Hdr.Ttl = 3600 + sig.TypeCovered = TypeDNSKEY + sig.Algorithm = RSASHA1 + sig.Labels = 2 + sig.OrigTtl = 4000 + sig.Expiration = 1000 //Thu Jan 1 02:06:40 CET 1970 + sig.Inception = 800 //Thu Jan 1 01:13:20 CET 1970 + sig.KeyTag = 34641 + sig.SignerName = "miek.nl." + sig.Signature = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" + + // Should not be valid + if sig.ValidityPeriod(time.Now()) { + t.Log("should not be valid") + t.Fail() + } + + sig.Inception = 315565800 //Tue Jan 1 10:10:00 CET 1980 + sig.Expiration = 4102477800 //Fri Jan 1 10:10:00 CET 2100 + if !sig.ValidityPeriod(time.Now()) { + t.Log("should be valid") + t.Fail() + } +} + +func TestSignVerify(t *testing.T) { + // The record we want to sign + soa := new(SOA) + soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} + soa.Ns = "open.nlnetlabs.nl." + soa.Mbox = "miekg.atoom.net." + soa.Serial = 1293945905 + soa.Refresh = 14400 + soa.Retry = 3600 + soa.Expire = 604800 + soa.Minttl = 86400 + + soa1 := new(SOA) + soa1.Hdr = RR_Header{"*.miek.nl.", TypeSOA, ClassINET, 14400, 0} + soa1.Ns = "open.nlnetlabs.nl." + soa1.Mbox = "miekg.atoom.net." + soa1.Serial = 1293945905 + soa1.Refresh = 14400 + soa1.Retry = 3600 + soa1.Expire = 604800 + soa1.Minttl = 86400 + + srv := new(SRV) + srv.Hdr = RR_Header{"srv.miek.nl.", TypeSRV, ClassINET, 14400, 0} + srv.Port = 1000 + srv.Weight = 800 + srv.Target = "web1.miek.nl." + + // With this key + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + privkey, _ := key.Generate(512) + + // Fill in the values of the Sig, before signing + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = soa.Hdr.Rrtype + sig.Labels = uint8(CountLabel(soa.Hdr.Name)) // works for all 3 + sig.OrigTtl = soa.Hdr.Ttl + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.KeyTag = key.KeyTag() // Get the keyfrom the Key + sig.SignerName = key.Hdr.Name + sig.Algorithm = RSASHA256 + + for _, r := range []RR{soa, soa1, srv} { + if sig.Sign(privkey, []RR{r}) != nil { + t.Log("failure to sign the record") + t.Fail() + continue + } + if sig.Verify(key, []RR{r}) != nil { + t.Log("failure to validate") + t.Fail() + continue + } + t.Logf("validated: %s\n", r.Header().Name) + } +} + +func Test65534(t *testing.T) { + t6 := new(RFC3597) + t6.Hdr = RR_Header{"miek.nl.", 65534, ClassINET, 14400, 0} + t6.Rdata = "505D870001" + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + privkey, _ := key.Generate(1024) + + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = t6.Hdr.Rrtype + sig.Labels = uint8(CountLabel(t6.Hdr.Name)) + sig.OrigTtl = t6.Hdr.Ttl + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.KeyTag = key.KeyTag() + sig.SignerName = key.Hdr.Name + sig.Algorithm = RSASHA256 + if err := sig.Sign(privkey, []RR{t6}); err != nil { + t.Log(err) + t.Log("failure to sign the TYPE65534 record") + t.Fail() + } + if err := sig.Verify(key, []RR{t6}); err != nil { + t.Log(err) + t.Log("failure to validate") + t.Fail() + } else { + t.Logf("validated: %s\n", t6.Header().Name) + } +} + +func TestDnskey(t *testing.T) { + // f, _ := os.Open("t/Kmiek.nl.+010+05240.key") + pubkey, _ := ReadRR(strings.NewReader(` +miek.nl. IN DNSKEY 256 3 10 AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL ;{id = 5240 (zsk), size = 1024b} +`), "Kmiek.nl.+010+05240.key") + privkey, _ := pubkey.(*DNSKEY).ReadPrivateKey(strings.NewReader(` +Private-key-format: v1.2 +Algorithm: 10 (RSASHA512) +Modulus: m4wK7YV26AeROtdiCXmqLG9wPDVoMOW8vjr/EkpscEAdjXp81RvZvrlzCSjYmz9onFRgltmTl3AINnFh+t9tlW0M9C5zejxBoKFXELv8ljPYAdz2oe+pDWPhWsfvVFYg2VCjpViPM38EakyE5mhk4TDOnUd+w4TeU1hyhZTWyYs= +PublicExponent: AQAB +PrivateExponent: UfCoIQ/Z38l8vB6SSqOI/feGjHEl/fxIPX4euKf0D/32k30fHbSaNFrFOuIFmWMB3LimWVEs6u3dpbB9CQeCVg7hwU5puG7OtuiZJgDAhNeOnxvo5btp4XzPZrJSxR4WNQnwIiYWbl0aFlL1VGgHC/3By89ENZyWaZcMLW4KGWE= +Prime1: yxwC6ogAu8aVcDx2wg1V0b5M5P6jP8qkRFVMxWNTw60Vkn+ECvw6YAZZBHZPaMyRYZLzPgUlyYRd0cjupy4+fQ== +Prime2: xA1bF8M0RTIQ6+A11AoVG6GIR/aPGg5sogRkIZ7ID/sF6g9HMVU/CM2TqVEBJLRPp73cv6ZeC3bcqOCqZhz+pw== +Exponent1: xzkblyZ96bGYxTVZm2/vHMOXswod4KWIyMoOepK6B/ZPcZoIT6omLCgtypWtwHLfqyCz3MK51Nc0G2EGzg8rFQ== +Exponent2: Pu5+mCEb7T5F+kFNZhQadHUklt0JUHbi3hsEvVoHpEGSw3BGDQrtIflDde0/rbWHgDPM4WQY+hscd8UuTXrvLw== +Coefficient: UuRoNqe7YHnKmQzE6iDWKTMIWTuoqqrFAmXPmKQnC+Y+BQzOVEHUo9bXdDnoI9hzXP1gf8zENMYwYLeWpuYlFQ== +`), "Kmiek.nl.+010+05240.private") + if pubkey.(*DNSKEY).PublicKey != "AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL" { + t.Log("pubkey is not what we've read") + t.Fail() + } + // Coefficient looks fishy... + t.Logf("%s", pubkey.(*DNSKEY).PrivateKeyString(privkey)) +} + +func TestTag(t *testing.T) { + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 3600 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" + + tag := key.KeyTag() + if tag != 12051 { + t.Logf("wrong key tag: %d for key %v\n", tag, key) + t.Fail() + } +} + +func TestKeyRSA(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 3600 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + priv, _ := key.Generate(2048) + + soa := new(SOA) + soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} + soa.Ns = "open.nlnetlabs.nl." + soa.Mbox = "miekg.atoom.net." + soa.Serial = 1293945905 + soa.Refresh = 14400 + soa.Retry = 3600 + soa.Expire = 604800 + soa.Minttl = 86400 + + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = TypeSOA + sig.Algorithm = RSASHA256 + sig.Labels = 2 + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.OrigTtl = soa.Hdr.Ttl + sig.KeyTag = key.KeyTag() + sig.SignerName = key.Hdr.Name + + if err := sig.Sign(priv, []RR{soa}); err != nil { + t.Logf("failed to sign") + t.Fail() + return + } + if err := sig.Verify(key, []RR{soa}); err != nil { + t.Logf("failed to verify") + t.Fail() + } +} + +func TestKeyToDS(t *testing.T) { + key := new(DNSKEY) + key.Hdr.Name = "miek.nl." + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 3600 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = RSASHA256 + key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" + + ds := key.ToDS(SHA1) + if strings.ToUpper(ds.Digest) != "B5121BDB5B8D86D0CC5FFAFBAAABE26C3E20BAC1" { + t.Logf("wrong DS digest for SHA1\n%v\n", ds) + t.Fail() + } +} + +func TestSignRSA(t *testing.T) { + pub := "miek.nl. IN DNSKEY 256 3 5 AwEAAb+8lGNCxJgLS8rYVer6EnHVuIkQDghdjdtewDzU3G5R7PbMbKVRvH2Ma7pQyYceoaqWZQirSj72euPWfPxQnMy9ucCylA+FuH9cSjIcPf4PqJfdupHk9X6EBYjxrCLY4p1/yBwgyBIRJtZtAqM3ceAH2WovEJD6rTtOuHo5AluJ" + + priv := `Private-key-format: v1.3 +Algorithm: 5 (RSASHA1) +Modulus: v7yUY0LEmAtLythV6voScdW4iRAOCF2N217APNTcblHs9sxspVG8fYxrulDJhx6hqpZlCKtKPvZ649Z8/FCczL25wLKUD4W4f1xKMhw9/g+ol926keT1foQFiPGsItjinX/IHCDIEhEm1m0Cozdx4AfZai8QkPqtO064ejkCW4k= +PublicExponent: AQAB +PrivateExponent: YPwEmwjk5HuiROKU4xzHQ6l1hG8Iiha4cKRG3P5W2b66/EN/GUh07ZSf0UiYB67o257jUDVEgwCuPJz776zfApcCB4oGV+YDyEu7Hp/rL8KcSN0la0k2r9scKwxTp4BTJT23zyBFXsV/1wRDK1A5NxsHPDMYi2SoK63Enm/1ptk= +Prime1: /wjOG+fD0ybNoSRn7nQ79udGeR1b0YhUA5mNjDx/x2fxtIXzygYk0Rhx9QFfDy6LOBvz92gbNQlzCLz3DJt5hw== +Prime2: wHZsJ8OGhkp5p3mrJFZXMDc2mbYusDVTA+t+iRPdS797Tj0pjvU2HN4vTnTj8KBQp6hmnY7dLp9Y1qserySGbw== +Exponent1: N0A7FsSRIg+IAN8YPQqlawoTtG1t1OkJ+nWrurPootScApX6iMvn8fyvw3p2k51rv84efnzpWAYiC8SUaQDNxQ== +Exponent2: SvuYRaGyvo0zemE3oS+WRm2scxR8eiA8WJGeOc+obwOKCcBgeZblXzfdHGcEC1KaOcetOwNW/vwMA46lpLzJNw== +Coefficient: 8+7ZN/JgByqv0NfULiFKTjtyegUcijRuyij7yNxYbCBneDvZGxJwKNi4YYXWx743pcAj4Oi4Oh86gcmxLs+hGw== +Created: 20110302104537 +Publish: 20110302104537 +Activate: 20110302104537` + + xk, _ := NewRR(pub) + k := xk.(*DNSKEY) + p, err := k.NewPrivateKey(priv) + if err != nil { + t.Logf("%v\n", err) + t.Fail() + } + switch priv := p.(type) { + case *rsa.PrivateKey: + if 65537 != priv.PublicKey.E { + t.Log("exponenent should be 65537") + t.Fail() + } + default: + t.Logf("we should have read an RSA key: %v", priv) + t.Fail() + } + if k.KeyTag() != 37350 { + t.Logf("%d %v\n", k.KeyTag(), k) + t.Log("keytag should be 37350") + t.Fail() + } + + soa := new(SOA) + soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} + soa.Ns = "open.nlnetlabs.nl." + soa.Mbox = "miekg.atoom.net." + soa.Serial = 1293945905 + soa.Refresh = 14400 + soa.Retry = 3600 + soa.Expire = 604800 + soa.Minttl = 86400 + + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.KeyTag = k.KeyTag() + sig.SignerName = k.Hdr.Name + sig.Algorithm = k.Algorithm + + sig.Sign(p, []RR{soa}) + if sig.Signature != "D5zsobpQcmMmYsUMLxCVEtgAdCvTu8V/IEeP4EyLBjqPJmjt96bwM9kqihsccofA5LIJ7DN91qkCORjWSTwNhzCv7bMyr2o5vBZElrlpnRzlvsFIoAZCD9xg6ZY7ZyzUJmU6IcTwG4v3xEYajcpbJJiyaw/RqR90MuRdKPiBzSo=" { + t.Log("signature is not correct") + t.Logf("%v\n", sig) + t.Fail() + } +} + +func TestSignVerifyECDSA(t *testing.T) { + pub := `example.net. 3600 IN DNSKEY 257 3 14 ( + xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1 + w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8 + /uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )` + priv := `Private-key-format: v1.2 +Algorithm: 14 (ECDSAP384SHA384) +PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR` + + eckey, err := NewRR(pub) + if err != nil { + t.Fatal(err.Error()) + } + privkey, err := eckey.(*DNSKEY).NewPrivateKey(priv) + if err != nil { + t.Fatal(err.Error()) + } + // TODO: Create seperate test for this + ds := eckey.(*DNSKEY).ToDS(SHA384) + if ds.KeyTag != 10771 { + t.Fatal("wrong keytag on DS") + } + if ds.Digest != "72d7b62976ce06438e9c0bf319013cf801f09ecc84b8d7e9495f27e305c6a9b0563a9b5f4d288405c3008a946df983d6" { + t.Fatal("wrong DS Digest") + } + a, _ := NewRR("www.example.net. 3600 IN A 192.0.2.1") + sig := new(RRSIG) + sig.Hdr = RR_Header{"example.net.", TypeRRSIG, ClassINET, 14400, 0} + sig.Expiration, _ = StringToTime("20100909102025") + sig.Inception, _ = StringToTime("20100812102025") + sig.KeyTag = eckey.(*DNSKEY).KeyTag() + sig.SignerName = eckey.(*DNSKEY).Hdr.Name + sig.Algorithm = eckey.(*DNSKEY).Algorithm + + if sig.Sign(privkey, []RR{a}) != nil { + t.Fatal("failure to sign the record") + } + + if e := sig.Verify(eckey.(*DNSKEY), []RR{a}); e != nil { + t.Logf("\n%s\n%s\n%s\n\n%s\n\n", + eckey.(*DNSKEY).String(), + a.String(), + sig.String(), + eckey.(*DNSKEY).PrivateKeyString(privkey), + ) + + t.Fatalf("failure to validate: %s", e.Error()) + } +} + +func TestSignVerifyECDSA2(t *testing.T) { + srv1, err := NewRR("srv.miek.nl. IN SRV 1000 800 0 web1.miek.nl.") + if err != nil { + t.Fatalf(err.Error()) + } + srv := srv1.(*SRV) + + // With this key + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = ECDSAP256SHA256 + privkey, err := key.Generate(256) + if err != nil { + t.Fatal("failure to generate key") + } + + // Fill in the values of the Sig, before signing + sig := new(RRSIG) + sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} + sig.TypeCovered = srv.Hdr.Rrtype + sig.Labels = uint8(CountLabel(srv.Hdr.Name)) // works for all 3 + sig.OrigTtl = srv.Hdr.Ttl + sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" + sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" + sig.KeyTag = key.KeyTag() // Get the keyfrom the Key + sig.SignerName = key.Hdr.Name + sig.Algorithm = ECDSAP256SHA256 + + if sig.Sign(privkey, []RR{srv}) != nil { + t.Fatal("failure to sign the record") + } + + err = sig.Verify(key, []RR{srv}) + if err != nil { + t.Logf("\n%s\n%s\n%s\n\n%s\n\n", + key.String(), + srv.String(), + sig.String(), + key.PrivateKeyString(privkey), + ) + + t.Fatal("Failure to validate:", err) + } +} + +// Here the test vectors from the relevant RFCs are checked. +// rfc6605 6.1 +func TestRFC6605P256(t *testing.T) { + exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 13 ( + GojIhhXUN/u4v54ZQqGSnyhWJwaubCvTmeexv7bR6edb + krSqQpF64cYbcB7wNcP+e+MAnLr+Wi9xMWyQLc8NAA== )` + exPriv := `Private-key-format: v1.2 +Algorithm: 13 (ECDSAP256SHA256) +PrivateKey: GU6SnQ/Ou+xC5RumuIUIuJZteXT2z0O/ok1s38Et6mQ=` + rrDNSKEY, err := NewRR(exDNSKEY) + if err != nil { + t.Fatal(err.Error()) + } + priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv) + if err != nil { + t.Fatal(err.Error()) + } + + exDS := `example.net. 3600 IN DS 55648 13 2 ( + b4c8c1fe2e7477127b27115656ad6256f424625bf5c1 + e2770ce6d6e37df61d17 )` + rrDS, err := NewRR(exDS) + if err != nil { + t.Fatal(err.Error()) + } + ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA256) + if !reflect.DeepEqual(ourDS, rrDS.(*DS)) { + t.Errorf("DS record differs:\n%v\n%v\n", ourDS, rrDS.(*DS)) + } + + exA := `www.example.net. 3600 IN A 192.0.2.1` + exRRSIG := `www.example.net. 3600 IN RRSIG A 13 3 3600 ( + 20100909100439 20100812100439 55648 example.net. + qx6wLYqmh+l9oCKTN6qIc+bw6ya+KJ8oMz0YP107epXA + yGmt+3SNruPFKG7tZoLBLlUzGGus7ZwmwWep666VCw== )` + rrA, err := NewRR(exA) + if err != nil { + t.Fatal(err.Error()) + } + rrRRSIG, err := NewRR(exRRSIG) + if err != nil { + t.Fatal(err.Error()) + } + if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { + t.Errorf("Failure to validate the spec RRSIG: %v", err) + } + + ourRRSIG := &RRSIG{ + Hdr: RR_Header{ + Ttl: rrA.Header().Ttl, + }, + KeyTag: rrDNSKEY.(*DNSKEY).KeyTag(), + SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name, + Algorithm: rrDNSKEY.(*DNSKEY).Algorithm, + } + ourRRSIG.Expiration, _ = StringToTime("20100909100439") + ourRRSIG.Inception, _ = StringToTime("20100812100439") + err = ourRRSIG.Sign(priv, []RR{rrA}) + if err != nil { + t.Fatal(err.Error()) + } + + if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { + t.Errorf("Failure to validate our RRSIG: %v", err) + } + + // Signatures are randomized + rrRRSIG.(*RRSIG).Signature = "" + ourRRSIG.Signature = "" + if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) { + t.Fatalf("RRSIG record differs:\n%v\n%v\n", ourRRSIG, rrRRSIG.(*RRSIG)) + } +} + +// rfc6605 6.2 +func TestRFC6605P384(t *testing.T) { + exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 14 ( + xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1 + w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8 + /uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )` + exPriv := `Private-key-format: v1.2 +Algorithm: 14 (ECDSAP384SHA384) +PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR` + rrDNSKEY, err := NewRR(exDNSKEY) + if err != nil { + t.Fatal(err.Error()) + } + priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv) + if err != nil { + t.Fatal(err.Error()) + } + + exDS := `example.net. 3600 IN DS 10771 14 4 ( + 72d7b62976ce06438e9c0bf319013cf801f09ecc84b8 + d7e9495f27e305c6a9b0563a9b5f4d288405c3008a94 + 6df983d6 )` + rrDS, err := NewRR(exDS) + if err != nil { + t.Fatal(err.Error()) + } + ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA384) + if !reflect.DeepEqual(ourDS, rrDS.(*DS)) { + t.Fatalf("DS record differs:\n%v\n%v\n", ourDS, rrDS.(*DS)) + } + + exA := `www.example.net. 3600 IN A 192.0.2.1` + exRRSIG := `www.example.net. 3600 IN RRSIG A 14 3 3600 ( + 20100909102025 20100812102025 10771 example.net. + /L5hDKIvGDyI1fcARX3z65qrmPsVz73QD1Mr5CEqOiLP + 95hxQouuroGCeZOvzFaxsT8Glr74hbavRKayJNuydCuz + WTSSPdz7wnqXL5bdcJzusdnI0RSMROxxwGipWcJm )` + rrA, err := NewRR(exA) + if err != nil { + t.Fatal(err.Error()) + } + rrRRSIG, err := NewRR(exRRSIG) + if err != nil { + t.Fatal(err.Error()) + } + if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { + t.Errorf("Failure to validate the spec RRSIG: %v", err) + } + + ourRRSIG := &RRSIG{ + Hdr: RR_Header{ + Ttl: rrA.Header().Ttl, + }, + KeyTag: rrDNSKEY.(*DNSKEY).KeyTag(), + SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name, + Algorithm: rrDNSKEY.(*DNSKEY).Algorithm, + } + ourRRSIG.Expiration, _ = StringToTime("20100909102025") + ourRRSIG.Inception, _ = StringToTime("20100812102025") + err = ourRRSIG.Sign(priv, []RR{rrA}) + if err != nil { + t.Fatal(err.Error()) + } + + if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { + t.Errorf("Failure to validate our RRSIG: %v", err) + } + + // Signatures are randomized + rrRRSIG.(*RRSIG).Signature = "" + ourRRSIG.Signature = "" + if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) { + t.Fatalf("RRSIG record differs:\n%v\n%v\n", ourRRSIG, rrRRSIG.(*RRSIG)) + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/dyn_test.go b/Godeps/_workspace/src/github.com/miekg/dns/dyn_test.go new file mode 100644 index 00000000000..09986a5e4ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/dyn_test.go @@ -0,0 +1,3 @@ +package dns + +// Find better solution diff --git a/Godeps/_workspace/src/github.com/miekg/dns/edns.go b/Godeps/_workspace/src/github.com/miekg/dns/edns.go new file mode 100644 index 00000000000..8b676e61241 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/edns.go @@ -0,0 +1,501 @@ +// EDNS0 +// +// EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated +// by RFC 6891. It defines an new RR type, the OPT RR, which is then completely +// abused. +// Basic use pattern for creating an (empty) OPT RR: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." // MUST be the root zone, per definition. +// o.Hdr.Rrtype = dns.TypeOPT +// +// The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) +// interfaces. Currently only a few have been standardized: EDNS0_NSID +// (RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note +// that these options may be combined in an OPT RR. +// Basic use pattern for a server to check if (and which) options are set: +// +// // o is a dns.OPT +// for _, s := range o.Option { +// switch e := s.(type) { +// case *dns.EDNS0_NSID: +// // do stuff with e.Nsid +// case *dns.EDNS0_SUBNET: +// // access e.Family, e.Address, etc. +// } +// } +package dns + +import ( + "encoding/hex" + "errors" + "net" + "strconv" +) + +// EDNS0 Option codes. +const ( + EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 + EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt + EDNS0NSID = 0x3 // nsid (RFC5001) + EDNS0DAU = 0x5 // DNSSEC Algorithm Understood + EDNS0DHU = 0x6 // DS Hash Understood + EDNS0N3U = 0x7 // NSEC3 Hash Understood + EDNS0SUBNET = 0x8 // client-subnet (RFC6891) + EDNS0EXPIRE = 0x9 // EDNS0 expire + EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET + _DO = 1 << 15 // dnssec ok +) + +type OPT struct { + Hdr RR_Header + Option []EDNS0 `dns:"opt"` +} + +func (rr *OPT) Header() *RR_Header { + return &rr.Hdr +} + +func (rr *OPT) String() string { + s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; " + if rr.Do() { + s += "flags: do; " + } else { + s += "flags: ; " + } + s += "udp: " + strconv.Itoa(int(rr.UDPSize())) + + for _, o := range rr.Option { + switch o.(type) { + case *EDNS0_NSID: + s += "\n; NSID: " + o.String() + h, e := o.pack() + var r string + if e == nil { + for _, c := range h { + r += "(" + string(c) + ")" + } + s += " " + r + } + case *EDNS0_SUBNET: + s += "\n; SUBNET: " + o.String() + if o.(*EDNS0_SUBNET).DraftOption { + s += " (draft)" + } + case *EDNS0_UL: + s += "\n; UPDATE LEASE: " + o.String() + case *EDNS0_LLQ: + s += "\n; LONG LIVED QUERIES: " + o.String() + case *EDNS0_DAU: + s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String() + case *EDNS0_DHU: + s += "\n; DS HASH UNDERSTOOD: " + o.String() + case *EDNS0_N3U: + s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String() + } + } + return s +} + +func (rr *OPT) len() int { + l := rr.Hdr.len() + for i := 0; i < len(rr.Option); i++ { + lo, _ := rr.Option[i].pack() + l += 2 + len(lo) + } + return l +} + +func (rr *OPT) copy() RR { + return &OPT{*rr.Hdr.copyHeader(), rr.Option} +} + +// return the old value -> delete SetVersion? + +// Version returns the EDNS version used. Only zero is defined. +func (rr *OPT) Version() uint8 { + return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16) +} + +// SetVersion sets the version of EDNS. This is usually zero. +func (rr *OPT) SetVersion(v uint8) { + rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16) +} + +// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL). +func (rr *OPT) ExtendedRcode() uint8 { + return uint8((rr.Hdr.Ttl & 0xFF000000) >> 24) +} + +// SetExtendedRcode sets the EDNS extended RCODE field. +func (rr *OPT) SetExtendedRcode(v uint8) { + rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24) +} + +// UDPSize returns the UDP buffer size. +func (rr *OPT) UDPSize() uint16 { + return rr.Hdr.Class +} + +// SetUDPSize sets the UDP buffer size. +func (rr *OPT) SetUDPSize(size uint16) { + rr.Hdr.Class = size +} + +// Do returns the value of the DO (DNSSEC OK) bit. +func (rr *OPT) Do() bool { + return rr.Hdr.Ttl&_DO == _DO +} + +// SetDo sets the DO (DNSSEC OK) bit. +func (rr *OPT) SetDo() { + rr.Hdr.Ttl |= _DO +} + +// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to +// it. +type EDNS0 interface { + // Option returns the option code for the option. + Option() uint16 + // pack returns the bytes of the option data. + pack() ([]byte, error) + // unpack sets the data as found in the buffer. Is also sets + // the length of the slice as the length of the option data. + unpack([]byte) error + // String returns the string representation of the option. + String() string +} + +// The nsid EDNS0 option is used to retrieve a nameserver +// identifier. When sending a request Nsid must be set to the empty string +// The identifier is an opaque string encoded as hex. +// Basic use pattern for creating an nsid option: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_NSID) +// e.Code = dns.EDNS0NSID +// e.Nsid = "AA" +// o.Option = append(o.Option, e) +type EDNS0_NSID struct { + Code uint16 // Always EDNS0NSID + Nsid string // This string needs to be hex encoded +} + +func (e *EDNS0_NSID) pack() ([]byte, error) { + h, err := hex.DecodeString(e.Nsid) + if err != nil { + return nil, err + } + return h, nil +} + +func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } +func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil } +func (e *EDNS0_NSID) String() string { return string(e.Nsid) } + +// The subnet EDNS0 option is used to give the remote nameserver +// an idea of where the client lives. It can then give back a different +// answer depending on the location or network topology. +// Basic use pattern for creating an subnet option: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_SUBNET) +// e.Code = dns.EDNS0SUBNET +// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6 +// e.NetMask = 32 // 32 for IPV4, 128 for IPv6 +// e.SourceScope = 0 +// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4 +// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6 +// o.Option = append(o.Option, e) +type EDNS0_SUBNET struct { + Code uint16 // Always EDNS0SUBNET + Family uint16 // 1 for IP, 2 for IP6 + SourceNetmask uint8 + SourceScope uint8 + Address net.IP + DraftOption bool // Set to true if using the old (0x50fa) option code +} + +func (e *EDNS0_SUBNET) Option() uint16 { + if e.DraftOption { + return EDNS0SUBNETDRAFT + } + return EDNS0SUBNET +} + +func (e *EDNS0_SUBNET) pack() ([]byte, error) { + b := make([]byte, 4) + b[0], b[1] = packUint16(e.Family) + b[2] = e.SourceNetmask + b[3] = e.SourceScope + switch e.Family { + case 1: + if e.SourceNetmask > net.IPv4len*8 { + return nil, errors.New("dns: bad netmask") + } + ip := make([]byte, net.IPv4len) + a := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8)) + for i := 0; i < net.IPv4len; i++ { + if i+1 > len(e.Address) { + break + } + ip[i] = a[i] + } + needLength := e.SourceNetmask / 8 + if e.SourceNetmask%8 > 0 { + needLength++ + } + ip = ip[:needLength] + b = append(b, ip...) + case 2: + if e.SourceNetmask > net.IPv6len*8 { + return nil, errors.New("dns: bad netmask") + } + ip := make([]byte, net.IPv6len) + a := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8)) + for i := 0; i < net.IPv6len; i++ { + if i+1 > len(e.Address) { + break + } + ip[i] = a[i] + } + needLength := e.SourceNetmask / 8 + if e.SourceNetmask%8 > 0 { + needLength++ + } + ip = ip[:needLength] + b = append(b, ip...) + default: + return nil, errors.New("dns: bad address family") + } + return b, nil +} + +func (e *EDNS0_SUBNET) unpack(b []byte) error { + lb := len(b) + if lb < 4 { + return ErrBuf + } + e.Family, _ = unpackUint16(b, 0) + e.SourceNetmask = b[2] + e.SourceScope = b[3] + switch e.Family { + case 1: + addr := make([]byte, 4) + for i := 0; i < int(e.SourceNetmask/8); i++ { + if i >= len(addr) || 4+i >= len(b) { + return ErrBuf + } + addr[i] = b[4+i] + } + e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3]) + case 2: + addr := make([]byte, 16) + for i := 0; i < int(e.SourceNetmask/8); i++ { + if i >= len(addr) || 4+i >= len(b) { + return ErrBuf + } + addr[i] = b[4+i] + } + e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4], + addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], + addr[11], addr[12], addr[13], addr[14], addr[15]} + } + return nil +} + +func (e *EDNS0_SUBNET) String() (s string) { + if e.Address == nil { + s = "" + } else if e.Address.To4() != nil { + s = e.Address.String() + } else { + s = "[" + e.Address.String() + "]" + } + s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope)) + return +} + +// The UL (Update Lease) EDNS0 (draft RFC) option is used to tell the server to set +// an expiration on an update RR. This is helpful for clients that cannot clean +// up after themselves. This is a draft RFC and more information can be found at +// http://files.dns-sd.org/draft-sekar-dns-ul.txt +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_UL) +// e.Code = dns.EDNS0UL +// e.Lease = 120 // in seconds +// o.Option = append(o.Option, e) +type EDNS0_UL struct { + Code uint16 // Always EDNS0UL + Lease uint32 +} + +func (e *EDNS0_UL) Option() uint16 { return EDNS0UL } +func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) } + +// Copied: http://golang.org/src/pkg/net/dnsmsg.go +func (e *EDNS0_UL) pack() ([]byte, error) { + b := make([]byte, 4) + b[0] = byte(e.Lease >> 24) + b[1] = byte(e.Lease >> 16) + b[2] = byte(e.Lease >> 8) + b[3] = byte(e.Lease) + return b, nil +} + +func (e *EDNS0_UL) unpack(b []byte) error { + if len(b) < 4 { + return ErrBuf + } + e.Lease = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) + return nil +} + +// Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 +// Implemented for completeness, as the EDNS0 type code is assigned. +type EDNS0_LLQ struct { + Code uint16 // Always EDNS0LLQ + Version uint16 + Opcode uint16 + Error uint16 + Id uint64 + LeaseLife uint32 +} + +func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ } + +func (e *EDNS0_LLQ) pack() ([]byte, error) { + b := make([]byte, 18) + b[0], b[1] = packUint16(e.Version) + b[2], b[3] = packUint16(e.Opcode) + b[4], b[5] = packUint16(e.Error) + b[6] = byte(e.Id >> 56) + b[7] = byte(e.Id >> 48) + b[8] = byte(e.Id >> 40) + b[9] = byte(e.Id >> 32) + b[10] = byte(e.Id >> 24) + b[11] = byte(e.Id >> 16) + b[12] = byte(e.Id >> 8) + b[13] = byte(e.Id) + b[14] = byte(e.LeaseLife >> 24) + b[15] = byte(e.LeaseLife >> 16) + b[16] = byte(e.LeaseLife >> 8) + b[17] = byte(e.LeaseLife) + return b, nil +} + +func (e *EDNS0_LLQ) unpack(b []byte) error { + if len(b) < 18 { + return ErrBuf + } + e.Version, _ = unpackUint16(b, 0) + e.Opcode, _ = unpackUint16(b, 2) + e.Error, _ = unpackUint16(b, 4) + e.Id = uint64(b[6])<<56 | uint64(b[6+1])<<48 | uint64(b[6+2])<<40 | + uint64(b[6+3])<<32 | uint64(b[6+4])<<24 | uint64(b[6+5])<<16 | uint64(b[6+6])<<8 | uint64(b[6+7]) + e.LeaseLife = uint32(b[14])<<24 | uint32(b[14+1])<<16 | uint32(b[14+2])<<8 | uint32(b[14+3]) + return nil +} + +func (e *EDNS0_LLQ) String() string { + s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) + + " " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) + + " " + strconv.FormatUint(uint64(e.LeaseLife), 10) + return s +} + +type EDNS0_DAU struct { + Code uint16 // Always EDNS0DAU + AlgCode []uint8 +} + +func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU } +func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_DAU) String() string { + s := "" + for i := 0; i < len(e.AlgCode); i++ { + if a, ok := AlgorithmToString[e.AlgCode[i]]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(e.AlgCode[i])) + } + } + return s +} + +type EDNS0_DHU struct { + Code uint16 // Always EDNS0DHU + AlgCode []uint8 +} + +func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU } +func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_DHU) String() string { + s := "" + for i := 0; i < len(e.AlgCode); i++ { + if a, ok := HashToString[e.AlgCode[i]]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(e.AlgCode[i])) + } + } + return s +} + +type EDNS0_N3U struct { + Code uint16 // Always EDNS0N3U + AlgCode []uint8 +} + +func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U } +func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_N3U) String() string { + // Re-use the hash map + s := "" + for i := 0; i < len(e.AlgCode); i++ { + if a, ok := HashToString[e.AlgCode[i]]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(e.AlgCode[i])) + } + } + return s +} + +type EDNS0_EXPIRE struct { + Code uint16 // Always EDNS0EXPIRE + Expire uint32 +} + +func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } +func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } + +func (e *EDNS0_EXPIRE) pack() ([]byte, error) { + b := make([]byte, 4) + b[0] = byte(e.Expire >> 24) + b[1] = byte(e.Expire >> 16) + b[2] = byte(e.Expire >> 8) + b[3] = byte(e.Expire) + return b, nil +} + +func (e *EDNS0_EXPIRE) unpack(b []byte) error { + if len(b) < 4 { + return ErrBuf + } + e.Expire = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) + return nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/edns_test.go b/Godeps/_workspace/src/github.com/miekg/dns/edns_test.go new file mode 100644 index 00000000000..8ee82ab4264 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/edns_test.go @@ -0,0 +1,48 @@ +package dns + +import "testing" + +func TestOPTTtl(t *testing.T) { + e := &OPT{} + e.Hdr.Name = "." + e.Hdr.Rrtype = TypeOPT + + if e.Do() { + t.Fail() + } + + e.SetDo() + if !e.Do() { + t.Fail() + } + + oldTtl := e.Hdr.Ttl + + if e.Version() != 0 { + t.Fail() + } + + e.SetVersion(42) + if e.Version() != 42 { + t.Fail() + } + + e.SetVersion(0) + if e.Hdr.Ttl != oldTtl { + t.Fail() + } + + if e.ExtendedRcode() != 0 { + t.Fail() + } + + e.SetExtendedRcode(42) + if e.ExtendedRcode() != 42 { + t.Fail() + } + + e.SetExtendedRcode(0) + if e.Hdr.Ttl != oldTtl { + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/example_test.go b/Godeps/_workspace/src/github.com/miekg/dns/example_test.go new file mode 100644 index 00000000000..1578a4d0539 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/example_test.go @@ -0,0 +1,147 @@ +package dns_test + +import ( + "errors" + "fmt" + "github.com/miekg/dns" + "log" + "net" +) + +// Retrieve the MX records for miek.nl. +func ExampleMX() { + config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion("miek.nl.", dns.TypeMX) + m.RecursionDesired = true + r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) + if err != nil { + return + } + if r.Rcode != dns.RcodeSuccess { + return + } + for _, a := range r.Answer { + if mx, ok := a.(*dns.MX); ok { + fmt.Printf("%s\n", mx.String()) + } + } +} + +// Retrieve the DNSKEY records of a zone and convert them +// to DS records for SHA1, SHA256 and SHA384. +func ExampleDS(zone string) { + config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") + c := new(dns.Client) + m := new(dns.Msg) + if zone == "" { + zone = "miek.nl" + } + m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY) + m.SetEdns0(4096, true) + r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) + if err != nil { + return + } + if r.Rcode != dns.RcodeSuccess { + return + } + for _, k := range r.Answer { + if key, ok := k.(*dns.DNSKEY); ok { + for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} { + fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags) + } + } + } +} + +const TypeAPAIR = 0x0F99 + +type APAIR struct { + addr [2]net.IP +} + +func NewAPAIR() dns.PrivateRdata { return new(APAIR) } + +func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() } +func (rd *APAIR) Parse(txt []string) error { + if len(txt) != 2 { + return errors.New("two addresses required for APAIR") + } + for i, s := range txt { + ip := net.ParseIP(s) + if ip == nil { + return errors.New("invalid IP in APAIR text representation") + } + rd.addr[i] = ip + } + return nil +} + +func (rd *APAIR) Pack(buf []byte) (int, error) { + b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...) + n := copy(buf, b) + if n != len(b) { + return n, dns.ErrBuf + } + return n, nil +} + +func (rd *APAIR) Unpack(buf []byte) (int, error) { + ln := net.IPv4len * 2 + if len(buf) != ln { + return 0, errors.New("invalid length of APAIR rdata") + } + cp := make([]byte, ln) + copy(cp, buf) // clone bytes to use them in IPs + + rd.addr[0] = net.IP(cp[:3]) + rd.addr[1] = net.IP(cp[4:]) + + return len(buf), nil +} + +func (rd *APAIR) Copy(dest dns.PrivateRdata) error { + cp := make([]byte, rd.Len()) + _, err := rd.Pack(cp) + if err != nil { + return err + } + + d := dest.(*APAIR) + d.addr[0] = net.IP(cp[:3]) + d.addr[1] = net.IP(cp[4:]) + return nil +} + +func (rd *APAIR) Len() int { + return net.IPv4len * 2 +} + +func ExamplePrivateHandle() { + dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR) + defer dns.PrivateHandleRemove(TypeAPAIR) + + rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)") + if err != nil { + log.Fatal("could not parse APAIR record: ", err) + } + fmt.Println(rr) + // Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 + + m := new(dns.Msg) + m.Id = 12345 + m.SetQuestion("miek.nl.", TypeAPAIR) + m.Answer = append(m.Answer, rr) + + fmt.Println(m) + // ;; opcode: QUERY, status: NOERROR, id: 12345 + // ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + // + // ;; QUESTION SECTION: + // ;miek.nl. IN APAIR + // + // ;; ANSWER SECTION: + // miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/idn/example_test.go b/Godeps/_workspace/src/github.com/miekg/dns/idn/example_test.go new file mode 100644 index 00000000000..8833cd91de1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/idn/example_test.go @@ -0,0 +1,18 @@ +package idn_test + +import ( + "fmt" + "github.com/miekg/dns/idn" +) + +func ExampleToPunycode() { + name := "インターネット.テスト" + fmt.Printf("%s -> %s", name, idn.ToPunycode(name)) + // Output: インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah +} + +func ExampleFromPunycode() { + name := "xn--mgbaja8a1hpac.xn--mgbachtv" + fmt.Printf("%s -> %s", name, idn.FromPunycode(name)) + // Output: xn--mgbaja8a1hpac.xn--mgbachtv -> الانترنت.اختبار +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode.go b/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode.go new file mode 100644 index 00000000000..faab4027b90 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode.go @@ -0,0 +1,268 @@ +// Package idn implements encoding from and to punycode as speficied by RFC 3492. +package idn + +import ( + "bytes" + "github.com/miekg/dns" + "strings" + "unicode" +) + +// Implementation idea from RFC itself and from from IDNA::Punycode created by +// Tatsuhiko Miyagawa and released under Perl Artistic +// License in 2002. + +const ( + _MIN rune = 1 + _MAX rune = 26 + _SKEW rune = 38 + _BASE rune = 36 + _BIAS rune = 72 + _N rune = 128 + _DAMP rune = 700 + + _DELIMITER = '-' + _PREFIX = "xn--" +) + +// ToPunycode converts unicode domain names to DNS-appropriate punycode names. +// This function would return incorrect result for strings for non-canonical +// unicode strings. +func ToPunycode(s string) string { + tokens := dns.SplitDomainName(s) + switch { + case s == "": + return "" + case tokens == nil: // s == . + return "." + case s[len(s)-1] == '.': + tokens = append(tokens, "") + } + + for i := range tokens { + tokens[i] = string(encode([]byte(tokens[i]))) + } + return strings.Join(tokens, ".") +} + +// FromPunycode returns unicode domain name from provided punycode string. +func FromPunycode(s string) string { + tokens := dns.SplitDomainName(s) + switch { + case s == "": + return "" + case tokens == nil: // s == . + return "." + case s[len(s)-1] == '.': + tokens = append(tokens, "") + } + for i := range tokens { + tokens[i] = string(decode([]byte(tokens[i]))) + } + return strings.Join(tokens, ".") +} + +// digitval converts single byte into meaningful value that's used to calculate decoded unicode character. +const errdigit = 0xffff + +func digitval(code rune) rune { + switch { + case code >= 'A' && code <= 'Z': + return code - 'A' + case code >= 'a' && code <= 'z': + return code - 'a' + case code >= '0' && code <= '9': + return code - '0' + 26 + } + return errdigit +} + +// lettercode finds BASE36 byte (a-z0-9) based on calculated number. +func lettercode(digit rune) rune { + switch { + case digit >= 0 && digit <= 25: + return digit + 'a' + case digit >= 26 && digit <= 36: + return digit - 26 + '0' + } + panic("dns: not reached") +} + +// adapt calculates next bias to be used for next iteration delta. +func adapt(delta rune, numpoints int, firsttime bool) rune { + if firsttime { + delta /= _DAMP + } else { + delta /= 2 + } + + var k rune + for delta = delta + delta/rune(numpoints); delta > (_BASE-_MIN)*_MAX/2; k += _BASE { + delta /= _BASE - _MIN + } + + return k + ((_BASE-_MIN+1)*delta)/(delta+_SKEW) +} + +// next finds minimal rune (one with lowest codepoint value) that should be equal or above boundary. +func next(b []rune, boundary rune) rune { + if len(b) == 0 { + panic("dns: invalid set of runes to determine next one") + } + m := b[0] + for _, x := range b[1:] { + if x >= boundary && (m < boundary || x < m) { + m = x + } + } + return m +} + +// preprune converts unicode rune to lower case. At this time it's not +// supporting all things described in RFCs +func preprune(r rune) rune { + if unicode.IsUpper(r) { + r = unicode.ToLower(r) + } + return r +} + +// tfunc is a function that helps calculate each character weight +func tfunc(k, bias rune) rune { + switch { + case k <= bias: + return _MIN + case k >= bias+_MAX: + return _MAX + } + return k - bias +} + +// encode transforms Unicode input bytes (that represent DNS label) into punycode bytestream +func encode(input []byte) []byte { + n, bias := _N, _BIAS + + b := bytes.Runes(input) + for i := range b { + b[i] = preprune(b[i]) + } + + basic := make([]byte, 0, len(b)) + for _, ltr := range b { + if ltr <= 0x7f { + basic = append(basic, byte(ltr)) + } + } + basiclen := len(basic) + fulllen := len(b) + if basiclen == fulllen { + return basic + } + + var out bytes.Buffer + + out.WriteString(_PREFIX) + if basiclen > 0 { + out.Write(basic) + out.WriteByte(_DELIMITER) + } + + var ( + ltr, nextltr rune + delta, q rune // delta calculation (see rfc) + t, k, cp rune // weight and codepoint calculation + ) + + s := &bytes.Buffer{} + for h := basiclen; h < fulllen; n, delta = n+1, delta+1 { + nextltr = next(b, n) + s.Truncate(0) + s.WriteRune(nextltr) + delta, n = delta+(nextltr-n)*rune(h+1), nextltr + + for _, ltr = range b { + if ltr < n { + delta++ + } + if ltr == n { + q = delta + for k = _BASE; ; k += _BASE { + t = tfunc(k, bias) + if q < t { + break + } + cp = t + ((q - t) % (_BASE - t)) + out.WriteRune(lettercode(cp)) + q = (q - t) / (_BASE - t) + } + + out.WriteRune(lettercode(q)) + + bias = adapt(delta, h+1, h == basiclen) + h, delta = h+1, 0 + } + } + } + return out.Bytes() +} + +// decode transforms punycode input bytes (that represent DNS label) into Unicode bytestream +func decode(b []byte) []byte { + src := b // b would move and we need to keep it + + n, bias := _N, _BIAS + if !bytes.HasPrefix(b, []byte(_PREFIX)) { + return b + } + out := make([]rune, 0, len(b)) + b = b[len(_PREFIX):] + for pos, x := range b { + if x == _DELIMITER { + out = append(out, bytes.Runes(b[:pos])...) + b = b[pos+1:] // trim source string + break + } + } + if len(b) == 0 { + return src + } + var ( + i, oldi, w rune + ch byte + t, digit rune + ln int + ) + + for i = 0; len(b) > 0; i++ { + oldi, w = i, 1 + for k := _BASE; len(b) > 0; k += _BASE { + ch, b = b[0], b[1:] + digit = digitval(rune(ch)) + if digit == errdigit { + return src + } + i += digit * w + + t = tfunc(k, bias) + if digit < t { + break + } + + w *= _BASE - t + } + ln = len(out) + 1 + bias = adapt(i-oldi, ln, oldi == 0) + n += i / rune(ln) + i = i % rune(ln) + // insert + out = append(out, 0) + copy(out[i+1:], out[i:]) + out[i] = n + } + + var ret bytes.Buffer + for _, r := range out { + ret.WriteRune(r) + } + return ret.Bytes() +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode_test.go b/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode_test.go new file mode 100644 index 00000000000..3202450aa52 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/idn/punycode_test.go @@ -0,0 +1,94 @@ +package idn + +import ( + "strings" + "testing" +) + +var testcases = [][2]string{ + {"", ""}, + {"a", "a"}, + {"A-B", "a-b"}, + {"AbC", "abc"}, + {"я", "xn--41a"}, + {"zя", "xn--z-0ub"}, + {"ЯZ", "xn--z-zub"}, + {"إختبار", "xn--kgbechtv"}, + {"آزمایشی", "xn--hgbk6aj7f53bba"}, + {"测试", "xn--0zwm56d"}, + {"測試", "xn--g6w251d"}, + {"Испытание", "xn--80akhbyknj4f"}, + {"परीक्षा", "xn--11b5bs3a9aj6g"}, + {"δοκιμή", "xn--jxalpdlp"}, + {"테스트", "xn--9t4b11yi5a"}, + {"טעסט", "xn--deba0ad"}, + {"テスト", "xn--zckzah"}, + {"பரிட்சை", "xn--hlcj6aya9esc7a"}, +} + +func TestEncodeDecodePunycode(t *testing.T) { + for _, tst := range testcases { + enc := encode([]byte(tst[0])) + if string(enc) != tst[1] { + t.Errorf("%s encodeded as %s but should be %s", tst[0], enc, tst[1]) + } + dec := decode([]byte(tst[1])) + if string(dec) != strings.ToLower(tst[0]) { + t.Errorf("%s decoded as %s but should be %s", tst[1], dec, strings.ToLower(tst[0])) + } + } +} + +func TestToFromPunycode(t *testing.T) { + for _, tst := range testcases { + // assert unicode.com == punycode.com + full := ToPunycode(tst[0] + ".com") + if full != tst[1]+".com" { + t.Errorf("invalid result from string conversion to punycode, %s and should be %s.com", full, tst[1]) + } + // assert punycode.punycode == unicode.unicode + decoded := FromPunycode(tst[1] + "." + tst[1]) + if decoded != strings.ToLower(tst[0]+"."+tst[0]) { + t.Errorf("invalid result from string conversion to punycode, %s and should be %s.%s", decoded, tst[0], tst[0]) + } + } +} + +func TestEncodeDecodeFinalPeriod(t *testing.T) { + for _, tst := range testcases { + // assert unicode.com. == punycode.com. + full := ToPunycode(tst[0] + ".") + if full != tst[1]+"." { + t.Errorf("invalid result from string conversion to punycode when period added at the end, %#v and should be %#v", full, tst[1]+".") + } + // assert punycode.com. == unicode.com. + decoded := FromPunycode(tst[1] + ".") + if decoded != strings.ToLower(tst[0]+".") { + t.Errorf("invalid result from string conversion to punycode when period added, %#v and should be %#v", decoded, tst[0]+".") + } + full = ToPunycode(tst[0]) + if full != tst[1] { + t.Errorf("invalid result from string conversion to punycode when no period added at the end, %#v and should be %#v", full, tst[1]+".") + } + // assert punycode.com. == unicode.com. + decoded = FromPunycode(tst[1]) + if decoded != strings.ToLower(tst[0]) { + t.Errorf("invalid result from string conversion to punycode when no period added, %#v and should be %#v", decoded, tst[0]+".") + } + } +} + +var invalid = []string{ + "xn--*", + "xn--", + "xn---", +} + +func TestInvalidPunycode(t *testing.T) { + for _, d := range invalid { + s := FromPunycode(d) + if s != d { + t.Errorf("Changed invalid name %s to %#v", d, s) + } + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/keygen.go b/Godeps/_workspace/src/github.com/miekg/dns/keygen.go new file mode 100644 index 00000000000..dfe328ecd43 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/keygen.go @@ -0,0 +1,157 @@ +package dns + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "math/big" + "strconv" +) + +const _FORMAT = "Private-key-format: v1.3\n" + +// Empty interface that is used as a wrapper around all possible +// private key implementations from the crypto package. +type PrivateKey interface{} + +// Generate generates a DNSKEY of the given bit size. +// The public part is put inside the DNSKEY record. +// The Algorithm in the key must be set as this will define +// what kind of DNSKEY will be generated. +// The ECDSA algorithms imply a fixed keysize, in that case +// bits should be set to the size of the algorithm. +func (r *DNSKEY) Generate(bits int) (PrivateKey, error) { + switch r.Algorithm { + case DSA, DSANSEC3SHA1: + if bits != 1024 { + return nil, ErrKeySize + } + case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: + if bits < 512 || bits > 4096 { + return nil, ErrKeySize + } + case RSASHA512: + if bits < 1024 || bits > 4096 { + return nil, ErrKeySize + } + case ECDSAP256SHA256: + if bits != 256 { + return nil, ErrKeySize + } + case ECDSAP384SHA384: + if bits != 384 { + return nil, ErrKeySize + } + } + + switch r.Algorithm { + case DSA, DSANSEC3SHA1: + params := new(dsa.Parameters) + if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { + return nil, err + } + priv := new(dsa.PrivateKey) + priv.PublicKey.Parameters = *params + err := dsa.GenerateKey(priv, rand.Reader) + if err != nil { + return nil, err + } + r.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) + return priv, nil + case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, err + } + r.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) + return priv, nil + case ECDSAP256SHA256, ECDSAP384SHA384: + var c elliptic.Curve + switch r.Algorithm { + case ECDSAP256SHA256: + c = elliptic.P256() + case ECDSAP384SHA384: + c = elliptic.P384() + } + priv, err := ecdsa.GenerateKey(c, rand.Reader) + if err != nil { + return nil, err + } + r.setPublicKeyCurve(priv.PublicKey.X, priv.PublicKey.Y) + return priv, nil + default: + return nil, ErrAlg + } + return nil, nil // Dummy return +} + +// 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 (hashing, keytag), so its a method of the DNSKEY. +func (r *DNSKEY) PrivateKeyString(p PrivateKey) (s string) { + switch t := p.(type) { + case *rsa.PrivateKey: + algorithm := strconv.Itoa(int(r.Algorithm)) + " (" + AlgorithmToString[r.Algorithm] + ")" + modulus := toBase64(t.PublicKey.N.Bytes()) + e := big.NewInt(int64(t.PublicKey.E)) + publicExponent := toBase64(e.Bytes()) + privateExponent := toBase64(t.D.Bytes()) + prime1 := toBase64(t.Primes[0].Bytes()) + prime2 := toBase64(t.Primes[1].Bytes()) + // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm + // and from: http://code.google.com/p/go/issues/detail?id=987 + one := big.NewInt(1) + minusone := big.NewInt(-1) + p_1 := big.NewInt(0).Sub(t.Primes[0], one) + q_1 := big.NewInt(0).Sub(t.Primes[1], one) + exp1 := big.NewInt(0).Mod(t.D, p_1) + exp2 := big.NewInt(0).Mod(t.D, q_1) + coeff := big.NewInt(0).Exp(t.Primes[1], minusone, t.Primes[0]) + + exponent1 := toBase64(exp1.Bytes()) + exponent2 := toBase64(exp2.Bytes()) + coefficient := toBase64(coeff.Bytes()) + + s = _FORMAT + + "Algorithm: " + algorithm + "\n" + + "Modules: " + modulus + "\n" + + "PublicExponent: " + publicExponent + "\n" + + "PrivateExponent: " + privateExponent + "\n" + + "Prime1: " + prime1 + "\n" + + "Prime2: " + prime2 + "\n" + + "Exponent1: " + exponent1 + "\n" + + "Exponent2: " + exponent2 + "\n" + + "Coefficient: " + coefficient + "\n" + case *ecdsa.PrivateKey: + algorithm := strconv.Itoa(int(r.Algorithm)) + " (" + AlgorithmToString[r.Algorithm] + ")" + var intlen int + switch r.Algorithm { + case ECDSAP256SHA256: + intlen = 32 + case ECDSAP384SHA384: + intlen = 48 + } + private := toBase64(intToBytes(t.D, intlen)) + s = _FORMAT + + "Algorithm: " + algorithm + "\n" + + "PrivateKey: " + private + "\n" + case *dsa.PrivateKey: + algorithm := strconv.Itoa(int(r.Algorithm)) + " (" + AlgorithmToString[r.Algorithm] + ")" + T := divRoundUp(divRoundUp(t.PublicKey.Parameters.G.BitLen(), 8)-64, 8) + prime := toBase64(intToBytes(t.PublicKey.Parameters.P, 64+T*8)) + subprime := toBase64(intToBytes(t.PublicKey.Parameters.Q, 20)) + base := toBase64(intToBytes(t.PublicKey.Parameters.G, 64+T*8)) + priv := toBase64(intToBytes(t.X, 20)) + pub := toBase64(intToBytes(t.PublicKey.Y, 64+T*8)) + s = _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" + } + return +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/kscan.go b/Godeps/_workspace/src/github.com/miekg/dns/kscan.go new file mode 100644 index 00000000000..c48ca2d24b7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/kscan.go @@ -0,0 +1,244 @@ +package dns + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "io" + "math/big" + "strings" +) + +func (k *DNSKEY) NewPrivateKey(s string) (PrivateKey, error) { + if s[len(s)-1] != '\n' { // We need a closing newline + return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") + } + return k.ReadPrivateKey(strings.NewReader(s), "") +} + +// ReadPrivateKey reads a private key from the io.Reader q. The string file is +// only used in error reporting. +// The public key must be +// known, because some cryptographic algorithms embed the public inside the privatekey. +func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (PrivateKey, error) { + m, e := parseKey(q, file) + if m == nil { + return nil, e + } + if _, ok := m["private-key-format"]; !ok { + return nil, ErrPrivKey + } + if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { + return nil, ErrPrivKey + } + // TODO(mg): check if the pubkey matches the private key + switch m["algorithm"] { + case "3 (DSA)": + p, e := readPrivateKeyDSA(m) + if e != nil { + return nil, e + } + if !k.setPublicKeyInPrivate(p) { + return nil, ErrKey + } + return p, e + case "1 (RSAMD5)": + fallthrough + case "5 (RSASHA1)": + fallthrough + case "7 (RSASHA1NSEC3SHA1)": + fallthrough + case "8 (RSASHA256)": + fallthrough + case "10 (RSASHA512)": + p, e := readPrivateKeyRSA(m) + if e != nil { + return nil, e + } + if !k.setPublicKeyInPrivate(p) { + return nil, ErrKey + } + return p, e + case "12 (ECC-GOST)": + p, e := readPrivateKeyGOST(m) + if e != nil { + return nil, e + } + // setPublicKeyInPrivate(p) + return p, e + case "13 (ECDSAP256SHA256)": + fallthrough + case "14 (ECDSAP384SHA384)": + p, e := readPrivateKeyECDSA(m) + if e != nil { + return nil, e + } + if !k.setPublicKeyInPrivate(p) { + return nil, ErrKey + } + return p, e + } + return nil, ErrPrivKey +} + +// Read a private key (file) string and create a public key. Return the private key. +func readPrivateKeyRSA(m map[string]string) (PrivateKey, error) { + p := new(rsa.PrivateKey) + p.Primes = []*big.Int{nil, nil} + for k, v := range m { + switch k { + case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": + v1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + switch k { + case "modulus": + p.PublicKey.N = big.NewInt(0) + p.PublicKey.N.SetBytes(v1) + case "publicexponent": + i := big.NewInt(0) + i.SetBytes(v1) + p.PublicKey.E = int(i.Int64()) // int64 should be large enough + case "privateexponent": + p.D = big.NewInt(0) + p.D.SetBytes(v1) + case "prime1": + p.Primes[0] = big.NewInt(0) + p.Primes[0].SetBytes(v1) + case "prime2": + p.Primes[1] = big.NewInt(0) + p.Primes[1].SetBytes(v1) + } + case "exponent1", "exponent2", "coefficient": + // not used in Go (yet) + case "created", "publish", "activate": + // not used in Go (yet) + } + } + return p, nil +} + +func readPrivateKeyDSA(m map[string]string) (PrivateKey, error) { + p := new(dsa.PrivateKey) + p.X = big.NewInt(0) + for k, v := range m { + switch k { + case "private_value(x)": + v1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + p.X.SetBytes(v1) + case "created", "publish", "activate": + /* not used in Go (yet) */ + } + } + return p, nil +} + +func readPrivateKeyECDSA(m map[string]string) (PrivateKey, error) { + p := new(ecdsa.PrivateKey) + p.D = big.NewInt(0) + // TODO: validate that the required flags are present + for k, v := range m { + switch k { + case "privatekey": + v1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + p.D.SetBytes(v1) + case "created", "publish", "activate": + /* not used in Go (yet) */ + } + } + return p, nil +} + +func readPrivateKeyGOST(m map[string]string) (PrivateKey, error) { + // TODO(miek) + return nil, nil +} + +// parseKey reads a private key from r. It returns a map[string]string, +// with the key-value pairs, or an error when the file is not correct. +func parseKey(r io.Reader, file string) (map[string]string, error) { + s := scanInit(r) + m := make(map[string]string) + c := make(chan lex) + k := "" + // Start the lexer + go klexer(s, c) + for l := range c { + // It should alternate + switch l.value { + case _KEY: + k = l.token + case _VALUE: + if k == "" { + return nil, &ParseError{file, "no private key seen", l} + } + //println("Setting", strings.ToLower(k), "to", l.token, "b") + m[strings.ToLower(k)] = l.token + k = "" + } + } + return m, nil +} + +// klexer scans the sourcefile and returns tokens on the channel c. +func klexer(s *scan, c chan lex) { + var l lex + str := "" // Hold the current read text + commt := false + key := true + x, err := s.tokenText() + defer close(c) + for err == nil { + l.column = s.position.Column + l.line = s.position.Line + switch x { + case ':': + if commt { + break + } + l.token = str + if key { + l.value = _KEY + c <- l + // Next token is a space, eat it + s.tokenText() + key = false + str = "" + } else { + l.value = _VALUE + } + case ';': + commt = true + case '\n': + if commt { + // Reset a comment + commt = false + } + l.value = _VALUE + l.token = str + c <- l + str = "" + commt = false + key = true + default: + if commt { + break + } + str += string(x) + } + x, err = s.tokenText() + } + if len(str) > 0 { + // Send remainder + l.token = str + l.value = _VALUE + c <- l + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/labels.go b/Godeps/_workspace/src/github.com/miekg/dns/labels.go new file mode 100644 index 00000000000..758e5783de5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/labels.go @@ -0,0 +1,162 @@ +package dns + +// Holds a bunch of helper functions for dealing with labels. + +// SplitDomainName splits a name string into it's labels. +// www.miek.nl. returns []string{"www", "miek", "nl"} +// The root label (.) returns nil. Note that using +// strings.Split(s) will work in most cases, but does not handle +// escaped dots (\.) for instance. +func SplitDomainName(s string) (labels []string) { + if len(s) == 0 { + return nil + } + fqdnEnd := 0 // offset of the final '.' or the length of the name + idx := Split(s) + begin := 0 + if s[len(s)-1] == '.' { + fqdnEnd = len(s) - 1 + } else { + fqdnEnd = len(s) + } + + switch len(idx) { + case 0: + return nil + case 1: + // no-op + default: + end := 0 + for i := 1; i < len(idx); i++ { + end = idx[i] + labels = append(labels, s[begin:end-1]) + begin = end + } + } + + labels = append(labels, s[begin:fqdnEnd]) + return labels +} + +// CompareDomainName compares the names s1 and s2 and +// returns how many labels they have in common starting from the *right*. +// The comparison stops at the first inequality. The names are not downcased +// before the comparison. +// +// www.miek.nl. and miek.nl. have two labels in common: miek and nl +// www.miek.nl. and www.bla.nl. have one label in common: nl +func CompareDomainName(s1, s2 string) (n int) { + s1 = Fqdn(s1) + s2 = Fqdn(s2) + l1 := Split(s1) + l2 := Split(s2) + + // the first check: root label + if l1 == nil || l2 == nil { + return + } + + j1 := len(l1) - 1 // end + i1 := len(l1) - 2 // start + j2 := len(l2) - 1 + i2 := len(l2) - 2 + // the second check can be done here: last/only label + // before we fall through into the for-loop below + if s1[l1[j1]:] == s2[l2[j2]:] { + n++ + } else { + return + } + for { + if i1 < 0 || i2 < 0 { + break + } + if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { + n++ + } else { + break + } + j1-- + i1-- + j2-- + i2-- + } + return +} + +// CountLabel counts the the number of labels in the string s. +func CountLabel(s string) (labels int) { + if s == "." { + return + } + off := 0 + end := false + for { + off, end = NextLabel(s, off) + labels++ + if end { + return + } + } + panic("dns: not reached") +} + +// Split splits a name s into its label indexes. +// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. +// The root name (.) returns nil. Also see dns.SplitDomainName. +func Split(s string) []int { + if s == "." { + return nil + } + idx := make([]int, 1, 3) + off := 0 + end := false + + for { + off, end = NextLabel(s, off) + if end { + return idx + } + idx = append(idx, off) + } + panic("dns: not reached") +} + +// NextLabel returns the index of the start of the next label in the +// string s starting at offset. +// The bool end is true when the end of the string has been reached. +func NextLabel(s string, offset int) (i int, end bool) { + quote := false + for i = offset; i < len(s)-1; i++ { + switch s[i] { + case '\\': + quote = !quote + default: + quote = false + case '.': + if quote { + quote = !quote + continue + } + return i + 1, false + } + } + return i + 1, true +} + +// PrevLabel returns the index of the label when starting from the right and +// jumping n labels to the left. +// The bool start is true when the start of the string has been overshot. +func PrevLabel(s string, n int) (i int, start bool) { + if n == 0 { + return len(s), false + } + lab := Split(s) + if lab == nil { + return 0, true + } + if n > len(lab) { + return 0, true + } + return lab[len(lab)-n], false +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/labels_test.go b/Godeps/_workspace/src/github.com/miekg/dns/labels_test.go new file mode 100644 index 00000000000..1d8da155fd5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/labels_test.go @@ -0,0 +1,214 @@ +package dns + +import ( + "testing" +) + +func TestCompareDomainName(t *testing.T) { + s1 := "www.miek.nl." + s2 := "miek.nl." + s3 := "www.bla.nl." + s4 := "nl.www.bla." + s5 := "nl" + s6 := "miek.nl" + + if CompareDomainName(s1, s2) != 2 { + t.Logf("%s with %s should be %d", s1, s2, 2) + t.Fail() + } + if CompareDomainName(s1, s3) != 1 { + t.Logf("%s with %s should be %d", s1, s3, 1) + t.Fail() + } + if CompareDomainName(s3, s4) != 0 { + t.Logf("%s with %s should be %d", s3, s4, 0) + t.Fail() + } + // Non qualified tests + if CompareDomainName(s1, s5) != 1 { + t.Logf("%s with %s should be %d", s1, s5, 1) + t.Fail() + } + if CompareDomainName(s1, s6) != 2 { + t.Logf("%s with %s should be %d", s1, s5, 2) + t.Fail() + } + + if CompareDomainName(s1, ".") != 0 { + t.Logf("%s with %s should be %d", s1, s5, 0) + t.Fail() + } + if CompareDomainName(".", ".") != 0 { + t.Logf("%s with %s should be %d", ".", ".", 0) + t.Fail() + } +} + +func TestSplit(t *testing.T) { + splitter := map[string]int{ + "www.miek.nl.": 3, + "www.miek.nl": 3, + "www..miek.nl": 4, + `www\.miek.nl.`: 2, + `www\\.miek.nl.`: 3, + ".": 0, + "nl.": 1, + "nl": 1, + "com.": 1, + ".com.": 2, + } + for s, i := range splitter { + if x := len(Split(s)); x != i { + t.Logf("labels should be %d, got %d: %s %v\n", i, x, s, Split(s)) + t.Fail() + } else { + t.Logf("%s %v\n", s, Split(s)) + } + } +} + +func TestSplit2(t *testing.T) { + splitter := map[string][]int{ + "www.miek.nl.": []int{0, 4, 9}, + "www.miek.nl": []int{0, 4, 9}, + "nl": []int{0}, + } + for s, i := range splitter { + x := Split(s) + switch len(i) { + case 1: + if x[0] != i[0] { + t.Logf("labels should be %v, got %v: %s\n", i, x, s) + t.Fail() + } + default: + if x[0] != i[0] || x[1] != i[1] || x[2] != i[2] { + t.Logf("labels should be %v, got %v: %s\n", i, x, s) + t.Fail() + } + } + } +} + +func TestPrevLabel(t *testing.T) { + type prev struct { + string + int + } + prever := map[prev]int{ + prev{"www.miek.nl.", 0}: 12, + prev{"www.miek.nl.", 1}: 9, + prev{"www.miek.nl.", 2}: 4, + + prev{"www.miek.nl", 0}: 11, + prev{"www.miek.nl", 1}: 9, + prev{"www.miek.nl", 2}: 4, + + prev{"www.miek.nl.", 5}: 0, + prev{"www.miek.nl", 5}: 0, + + prev{"www.miek.nl.", 3}: 0, + prev{"www.miek.nl", 3}: 0, + } + for s, i := range prever { + x, ok := PrevLabel(s.string, s.int) + if i != x { + t.Logf("label should be %d, got %d, %t: preving %d, %s\n", i, x, ok, s.int, s.string) + t.Fail() + } + } +} + +func TestCountLabel(t *testing.T) { + splitter := map[string]int{ + "www.miek.nl.": 3, + "www.miek.nl": 3, + "nl": 1, + ".": 0, + } + for s, i := range splitter { + x := CountLabel(s) + if x != i { + t.Logf("CountLabel should have %d, got %d\n", i, x) + t.Fail() + } + } +} + +func TestSplitDomainName(t *testing.T) { + labels := map[string][]string{ + "miek.nl": []string{"miek", "nl"}, + ".": nil, + "www.miek.nl.": []string{"www", "miek", "nl"}, + "www.miek.nl": []string{"www", "miek", "nl"}, + "www..miek.nl": []string{"www", "", "miek", "nl"}, + `www\.miek.nl`: []string{`www\.miek`, "nl"}, + `www\\.miek.nl`: []string{`www\\`, "miek", "nl"}, + } +domainLoop: + for domain, splits := range labels { + parts := SplitDomainName(domain) + if len(parts) != len(splits) { + t.Logf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) + t.Fail() + continue domainLoop + } + for i := range parts { + if parts[i] != splits[i] { + t.Logf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) + t.Fail() + continue domainLoop + } + } + } +} + +func TestIsDomainName(t *testing.T) { + type ret struct { + ok bool + lab int + } + names := map[string]*ret{ + "..": &ret{false, 1}, + "@.": &ret{true, 1}, + "www.example.com": &ret{true, 3}, + "www.e%ample.com": &ret{true, 3}, + "www.example.com.": &ret{true, 3}, + "mi\\k.nl.": &ret{true, 2}, + "mi\\k.nl": &ret{true, 2}, + } + for d, ok := range names { + l, k := IsDomainName(d) + if ok.ok != k || ok.lab != l { + t.Logf(" got %v %d for %s ", k, l, d) + t.Logf("have %v %d for %s ", ok.ok, ok.lab, d) + t.Fail() + } + } +} + +func BenchmarkSplitLabels(b *testing.B) { + for i := 0; i < b.N; i++ { + Split("www.example.com") + } +} + +func BenchmarkLenLabels(b *testing.B) { + for i := 0; i < b.N; i++ { + CountLabel("www.example.com") + } +} + +func BenchmarkCompareLabels(b *testing.B) { + for i := 0; i < b.N; i++ { + CompareDomainName("www.example.com", "aa.example.com") + } +} + +func BenchmarkIsSubDomain(b *testing.B) { + for i := 0; i < b.N; i++ { + IsSubDomain("www.example.com", "aa.example.com") + IsSubDomain("example.com", "aa.example.com") + IsSubDomain("miek.nl", "aa.example.com") + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/msg.go b/Godeps/_workspace/src/github.com/miekg/dns/msg.go new file mode 100644 index 00000000000..ecba463ddda --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/msg.go @@ -0,0 +1,1899 @@ +// DNS packet assembly, see RFC 1035. Converting from - Unpack() - +// and to - Pack() - wire format. +// All the packers and unpackers take a (msg []byte, off int) +// and return (off1 int, ok bool). If they return ok==false, they +// also return off1==len(msg), so that the next unpacker will +// also fail. This lets us avoid checks of ok until the end of a +// packing sequence. + +package dns + +import ( + "encoding/base32" + "encoding/base64" + "encoding/hex" + "math/big" + "math/rand" + "net" + "reflect" + "strconv" + "time" +) + +const maxCompressionOffset = 2 << 13 // We have 14 bits for the compression pointer + +var ( + ErrAlg error = &Error{err: "bad algorithm"} + ErrAuth error = &Error{err: "bad authentication"} + ErrBuf error = &Error{err: "buffer size too small"} + ErrConnEmpty error = &Error{err: "conn has no connection"} + ErrConn error = &Error{err: "conn holds both UDP and TCP connection"} + ErrExtendedRcode error = &Error{err: "bad extended rcode"} + ErrFqdn error = &Error{err: "domain must be fully qualified"} + ErrId error = &Error{err: "id mismatch"} + ErrKeyAlg error = &Error{err: "bad key algorithm"} + ErrKey error = &Error{err: "bad key"} + ErrKeySize error = &Error{err: "bad key size"} + ErrNoSig error = &Error{err: "no signature found"} + ErrPrivKey error = &Error{err: "bad private key"} + ErrRcode error = &Error{err: "bad rcode"} + ErrRdata error = &Error{err: "bad rdata"} + ErrRRset error = &Error{err: "bad rrset"} + ErrSecret error = &Error{err: "no secrets defined"} + ErrServ error = &Error{err: "no servers could be reached"} + ErrShortRead error = &Error{err: "short read"} + ErrSig error = &Error{err: "bad signature"} + ErrSigGen error = &Error{err: "bad signature generation"} + ErrSoa error = &Error{err: "no SOA"} + ErrTime error = &Error{err: "bad time"} +) + +// Id, by default, returns a 16 bits random number to be used as a +// message id. The random provided should be good enough. This being a +// variable the function can be reassigned to a custom function. +// For instance, to make it return a static value: +// +// dns.Id = func() uint16 { return 3 } +var Id func() uint16 = id + +// A manually-unpacked version of (id, bits). +// This is in its own struct for easy printing. +type MsgHdr struct { + Id uint16 + Response bool + Opcode int + Authoritative bool + Truncated bool + RecursionDesired bool + RecursionAvailable bool + Zero bool + AuthenticatedData bool + CheckingDisabled bool + Rcode int +} + +// The layout of a DNS message. +type Msg struct { + MsgHdr + Compress bool `json:"-"` // If true, the message will be compressed when converted to wire format. This not part of the official DNS packet format. + Question []Question // Holds the RR(s) of the question section. + Answer []RR // Holds the RR(s) of the answer section. + Ns []RR // Holds the RR(s) of the authority section. + Extra []RR // Holds the RR(s) of the additional section. +} + +// Map of strings for each RR wire type. +var TypeToString = map[uint16]string{ + TypeA: "A", + TypeAAAA: "AAAA", + TypeAFSDB: "AFSDB", + TypeANY: "ANY", // Meta RR + TypeATMA: "ATMA", + TypeAXFR: "AXFR", // Meta RR + TypeCAA: "CAA", + TypeCDNSKEY: "CDNSKEY", + TypeCDS: "CDS", + TypeCERT: "CERT", + TypeCNAME: "CNAME", + TypeDHCID: "DHCID", + TypeDLV: "DLV", + TypeDNAME: "DNAME", + TypeDNSKEY: "DNSKEY", + TypeDS: "DS", + TypeEID: "EID", + TypeEUI48: "EUI48", + TypeEUI64: "EUI64", + TypeGID: "GID", + TypeGPOS: "GPOS", + TypeHINFO: "HINFO", + TypeHIP: "HIP", + TypeIPSECKEY: "IPSECKEY", + TypeISDN: "ISDN", + TypeIXFR: "IXFR", // Meta RR + TypeKEY: "KEY", + TypeKX: "KX", + TypeL32: "L32", + TypeL64: "L64", + TypeLOC: "LOC", + TypeLP: "LP", + TypeMB: "MB", + TypeMD: "MD", + TypeMF: "MF", + TypeMG: "MG", + TypeMINFO: "MINFO", + TypeMR: "MR", + TypeMX: "MX", + TypeNAPTR: "NAPTR", + TypeNID: "NID", + TypeNINFO: "NINFO", + TypeNIMLOC: "NIMLOC", + TypeNS: "NS", + TypeNSAP: "NSAP", + TypeNSAPPTR: "NSAP-PTR", + TypeNSEC3: "NSEC3", + TypeNSEC3PARAM: "NSEC3PARAM", + TypeNSEC: "NSEC", + TypeNULL: "NULL", + TypeOPT: "OPT", + TypeOPENPGPKEY: "OPENPGPKEY", + TypePTR: "PTR", + TypeRKEY: "RKEY", + TypeRP: "RP", + TypeRRSIG: "RRSIG", + TypeRT: "RT", + TypeSIG: "SIG", + TypeSOA: "SOA", + TypeSPF: "SPF", + TypeSRV: "SRV", + TypeSSHFP: "SSHFP", + TypeTA: "TA", + TypeTALINK: "TALINK", + TypeTKEY: "TKEY", // Meta RR + TypeTLSA: "TLSA", + TypeTSIG: "TSIG", // Meta RR + TypeTXT: "TXT", + TypePX: "PX", + TypeUID: "UID", + TypeUINFO: "UINFO", + TypeUNSPEC: "UNSPEC", + TypeURI: "URI", + TypeWKS: "WKS", + TypeX25: "X25", +} + +// Reverse, needed for string parsing. +var StringToType = reverseInt16(TypeToString) +var StringToClass = reverseInt16(ClassToString) + +// Map of opcodes strings. +var StringToOpcode = reverseInt(OpcodeToString) + +// Map of rcodes strings. +var StringToRcode = reverseInt(RcodeToString) + +// Map of strings for each CLASS wire type. +var ClassToString = map[uint16]string{ + ClassINET: "IN", + ClassCSNET: "CS", + ClassCHAOS: "CH", + ClassHESIOD: "HS", + ClassNONE: "NONE", + ClassANY: "ANY", +} + +// Map of strings for opcodes. +var OpcodeToString = map[int]string{ + OpcodeQuery: "QUERY", + OpcodeIQuery: "IQUERY", + OpcodeStatus: "STATUS", + OpcodeNotify: "NOTIFY", + OpcodeUpdate: "UPDATE", +} + +// Map of strings for rcodes. +var RcodeToString = map[int]string{ + RcodeSuccess: "NOERROR", + RcodeFormatError: "FORMERR", + RcodeServerFailure: "SERVFAIL", + RcodeNameError: "NXDOMAIN", + RcodeNotImplemented: "NOTIMPL", + RcodeRefused: "REFUSED", + RcodeYXDomain: "YXDOMAIN", // From RFC 2136 + RcodeYXRrset: "YXRRSET", + RcodeNXRrset: "NXRRSET", + RcodeNotAuth: "NOTAUTH", + RcodeNotZone: "NOTZONE", + RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 + // RcodeBadVers: "BADVERS", + RcodeBadKey: "BADKEY", + RcodeBadTime: "BADTIME", + RcodeBadMode: "BADMODE", + RcodeBadName: "BADNAME", + RcodeBadAlg: "BADALG", + RcodeBadTrunc: "BADTRUNC", +} + +// Rather than write the usual handful of routines to pack and +// unpack every message that can appear on the wire, we use +// reflection to write a generic pack/unpack for structs and then +// use it. Thus, if in the future we need to define new message +// structs, no new pack/unpack/printing code needs to be written. + +// Domain names are a sequence of counted strings +// split at the dots. They end with a zero-length string. + +// PackDomainName packs a domain name s into msg[off:]. +// If compression is wanted compress must be true and the compression +// map needs to hold a mapping between domain names and offsets +// pointing into msg[]. +func PackDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + off1, _, err = packDomainName(s, msg, off, compression, compress) + return +} + +func packDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, labels int, err error) { + // special case if msg == nil + lenmsg := 256 + if msg != nil { + lenmsg = len(msg) + } + ls := len(s) + if ls == 0 { // Ok, for instance when dealing with update RR without any rdata. + return off, 0, nil + } + // If not fully qualified, error out, but only if msg == nil #ugly + switch { + case msg == nil: + if s[ls-1] != '.' { + s += "." + ls++ + } + case msg != nil: + if s[ls-1] != '.' { + return lenmsg, 0, ErrFqdn + } + } + // Each dot ends a segment of the name. + // We trade each dot byte for a length byte. + // Except for escaped dots (\.), which are normal dots. + // There is also a trailing zero. + + // Compression + nameoffset := -1 + pointer := -1 + // Emit sequence of counted strings, chopping at dots. + begin := 0 + bs := []byte(s) + ro_bs, bs_fresh, escaped_dot := s, true, false + for i := 0; i < ls; i++ { + if bs[i] == '\\' { + for j := i; j < ls-1; j++ { + bs[j] = bs[j+1] + } + ls-- + if off+1 > lenmsg { + return lenmsg, labels, ErrBuf + } + // check for \DDD + if i+2 < ls && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + bs[i] = dddToByte(bs[i:]) + for j := i + 1; j < ls-2; j++ { + bs[j] = bs[j+2] + } + ls -= 2 + } else if bs[i] == 't' { + bs[i] = '\t' + } else if bs[i] == 'r' { + bs[i] = '\r' + } else if bs[i] == 'n' { + bs[i] = '\n' + } + escaped_dot = bs[i] == '.' + bs_fresh = false + continue + } + + if bs[i] == '.' { + if i > 0 && bs[i-1] == '.' && !escaped_dot { + // two dots back to back is not legal + return lenmsg, labels, ErrRdata + } + if i-begin >= 1<<6 { // top two bits of length must be clear + return lenmsg, labels, ErrRdata + } + // off can already (we're in a loop) be bigger than len(msg) + // this happens when a name isn't fully qualified + if off+1 > lenmsg { + return lenmsg, labels, ErrBuf + } + if msg != nil { + msg[off] = byte(i - begin) + } + offset := off + off++ + for j := begin; j < i; j++ { + if off+1 > lenmsg { + return lenmsg, labels, ErrBuf + } + if msg != nil { + msg[off] = bs[j] + } + off++ + } + if compress && !bs_fresh { + ro_bs = string(bs) + bs_fresh = true + } + // Dont try to compress '.' + if compress && ro_bs[begin:] != "." { + if p, ok := compression[ro_bs[begin:]]; !ok { + // Only offsets smaller than this can be used. + if offset < maxCompressionOffset { + compression[ro_bs[begin:]] = offset + } + } else { + // The first hit is the longest matching dname + // keep the pointer offset we get back and store + // the offset of the current name, because that's + // where we need to insert the pointer later + + // If compress is true, we're allowed to compress this dname + if pointer == -1 && compress { + pointer = p // Where to point to + nameoffset = offset // Where to point from + break + } + } + } + labels++ + begin = i + 1 + } + escaped_dot = false + } + // Root label is special + if len(bs) == 1 && bs[0] == '.' { + return off, labels, nil + } + // If we did compression and we find something add the pointer here + if pointer != -1 { + // We have two bytes (14 bits) to put the pointer in + // if msg == nil, we will never do compression + msg[nameoffset], msg[nameoffset+1] = packUint16(uint16(pointer ^ 0xC000)) + off = nameoffset + 1 + goto End + } + if msg != nil { + msg[off] = 0 + } +End: + off++ + return off, labels, nil +} + +// Unpack a domain name. +// In addition to the simple sequences of counted strings above, +// domain names are allowed to refer to strings elsewhere in the +// packet, to avoid repeating common suffixes when returning +// many entries in a single domain. The pointers are marked +// by a length byte with the top two bits set. Ignoring those +// two bits, that byte and the next give a 14 bit offset from msg[0] +// where we should pick up the trail. +// Note that if we jump elsewhere in the packet, +// we return off1 == the offset after the first pointer we found, +// which is where the next record will start. +// In theory, the pointers are only allowed to jump backward. +// We let them jump anywhere and stop jumping after a while. + +// UnpackDomainName unpacks a domain name into a string. +func UnpackDomainName(msg []byte, off int) (string, int, error) { + s := make([]byte, 0, 64) + off1 := 0 + lenmsg := len(msg) + ptr := 0 // number of pointers followed +Loop: + for { + if off >= lenmsg { + return "", lenmsg, ErrBuf + } + c := int(msg[off]) + off++ + switch c & 0xC0 { + case 0x00: + if c == 0x00 { + // end of name + if len(s) == 0 { + return ".", off, nil + } + break Loop + } + // literal string + if off+c > lenmsg { + return "", lenmsg, ErrBuf + } + for j := off; j < off+c; j++ { + switch b := msg[j]; b { + case '.', '(', ')', ';', ' ', '@': + fallthrough + case '"', '\\': + s = append(s, '\\', b) + case '\t': + s = append(s, '\\', 't') + case '\r': + s = append(s, '\\', 'r') + default: + if b < 32 || b >= 127 { // unprintable use \DDD + var buf [3]byte + bufs := strconv.AppendInt(buf[:0], int64(b), 10) + s = append(s, '\\') + for i := 0; i < 3-len(bufs); i++ { + s = append(s, '0') + } + for _, r := range bufs { + s = append(s, r) + } + } else { + s = append(s, b) + } + } + } + s = append(s, '.') + off += c + case 0xC0: + // pointer to somewhere else in msg. + // remember location after first ptr, + // since that's how many bytes we consumed. + // also, don't follow too many pointers -- + // maybe there's a loop. + if off >= lenmsg { + return "", lenmsg, ErrBuf + } + c1 := msg[off] + off++ + if ptr == 0 { + off1 = off + } + if ptr++; ptr > 10 { + return "", lenmsg, &Error{err: "too many compression pointers"} + } + off = (c^0xC0)<<8 | int(c1) + default: + // 0x80 and 0x40 are reserved + return "", lenmsg, ErrRdata + } + } + if ptr == 0 { + off1 = off + } + return string(s), off1, nil +} + +func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) { + var err error + if len(txt) == 0 { + if offset >= len(msg) { + return offset, ErrBuf + } + msg[offset] = 0 + return offset, nil + } + for i := range txt { + if len(txt[i]) > len(tmp) { + return offset, ErrBuf + } + offset, err = packTxtString(txt[i], msg, offset, tmp) + if err != nil { + return offset, err + } + } + return offset, err +} + +func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) { + lenByteOffset := offset + if offset >= len(msg) { + return offset, ErrBuf + } + offset++ + bs := tmp[:len(s)] + copy(bs, s) + for i := 0; i < len(bs); i++ { + if len(msg) <= offset { + return offset, ErrBuf + } + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + // check for \DDD + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg[offset] = dddToByte(bs[i:]) + i += 2 + } else if bs[i] == 't' { + msg[offset] = '\t' + } else if bs[i] == 'r' { + msg[offset] = '\r' + } else if bs[i] == 'n' { + msg[offset] = '\n' + } else { + msg[offset] = bs[i] + } + } else { + msg[offset] = bs[i] + } + offset++ + } + l := offset - lenByteOffset - 1 + if l > 255 { + return offset, &Error{err: "string exceeded 255 bytes in txt"} + } + msg[lenByteOffset] = byte(l) + return offset, nil +} + +func unpackTxt(msg []byte, offset, rdend int) ([]string, int, error) { + var err error + var ss []string + var s string + for offset < rdend && err == nil { + s, offset, err = unpackTxtString(msg, offset) + if err == nil { + ss = append(ss, s) + } + } + return ss, offset, err +} + +func unpackTxtString(msg []byte, offset int) (string, int, error) { + if offset+1 > len(msg) { + return "", offset, &Error{err: "overflow unpacking txt"} + } + l := int(msg[offset]) + if offset+l+1 > len(msg) { + return "", offset, &Error{err: "overflow unpacking txt"} + } + s := make([]byte, 0, l) + for _, b := range msg[offset+1 : offset+1+l] { + switch b { + case '"', '\\': + s = append(s, '\\', b) + case '\t': + s = append(s, `\t`...) + case '\r': + s = append(s, `\r`...) + case '\n': + s = append(s, `\n`...) + default: + if b < 32 || b > 127 { // unprintable + var buf [3]byte + bufs := strconv.AppendInt(buf[:0], int64(b), 10) + s = append(s, '\\') + for i := 0; i < 3-len(bufs); i++ { + s = append(s, '0') + } + for _, r := range bufs { + s = append(s, r) + } + } else { + s = append(s, b) + } + } + } + offset += 1 + l + return string(s), offset, nil +} + +// Pack a reflect.StructValue into msg. Struct members can only be uint8, uint16, uint32, string, +// slices and other (often anonymous) structs. +func packStructValue(val reflect.Value, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + var txtTmp []byte + lenmsg := len(msg) + numfield := val.NumField() + for i := 0; i < numfield; i++ { + typefield := val.Type().Field(i) + if typefield.Tag == `dns:"-"` { + continue + } + switch fv := val.Field(i); fv.Kind() { + default: + return lenmsg, &Error{err: "bad kind packing"} + case reflect.Interface: + // PrivateRR is the only RR implementation that has interface field. + // therefore it's expected that this interface would be PrivateRdata + switch data := fv.Interface().(type) { + case PrivateRdata: + n, err := data.Pack(msg[off:]) + if err != nil { + return lenmsg, err + } + off += n + default: + return lenmsg, &Error{err: "bad kind interface packing"} + } + case reflect.Slice: + switch typefield.Tag { + default: + return lenmsg, &Error{"bad tag packing slice: " + typefield.Tag.Get("dns")} + case `dns:"domain-name"`: + for j := 0; j < val.Field(i).Len(); j++ { + element := val.Field(i).Index(j).String() + off, err = PackDomainName(element, msg, off, compression, false && compress) + if err != nil { + return lenmsg, err + } + } + case `dns:"txt"`: + if txtTmp == nil { + txtTmp = make([]byte, 256*4+1) + } + off, err = packTxt(fv.Interface().([]string), msg, off, txtTmp) + if err != nil { + return lenmsg, err + } + case `dns:"opt"`: // edns + for j := 0; j < val.Field(i).Len(); j++ { + element := val.Field(i).Index(j).Interface() + b, e := element.(EDNS0).pack() + if e != nil { + return lenmsg, &Error{err: "overflow packing opt"} + } + // Option code + msg[off], msg[off+1] = packUint16(element.(EDNS0).Option()) + // Length + msg[off+2], msg[off+3] = packUint16(uint16(len(b))) + off += 4 + if off+len(b) > lenmsg { + copy(msg[off:], b) + off = lenmsg + continue + } + // Actual data + copy(msg[off:off+len(b)], b) + off += len(b) + } + case `dns:"a"`: + // It must be a slice of 4, even if it is 16, we encode + // only the first 4 + if off+net.IPv4len > lenmsg { + return lenmsg, &Error{err: "overflow packing a"} + } + switch fv.Len() { + case net.IPv6len: + msg[off] = byte(fv.Index(12).Uint()) + msg[off+1] = byte(fv.Index(13).Uint()) + msg[off+2] = byte(fv.Index(14).Uint()) + msg[off+3] = byte(fv.Index(15).Uint()) + off += net.IPv4len + case net.IPv4len: + msg[off] = byte(fv.Index(0).Uint()) + msg[off+1] = byte(fv.Index(1).Uint()) + msg[off+2] = byte(fv.Index(2).Uint()) + msg[off+3] = byte(fv.Index(3).Uint()) + off += net.IPv4len + case 0: + // Allowed, for dynamic updates + default: + return lenmsg, &Error{err: "overflow packing a"} + } + case `dns:"aaaa"`: + if fv.Len() == 0 { + break + } + if fv.Len() > net.IPv6len || off+fv.Len() > lenmsg { + return lenmsg, &Error{err: "overflow packing aaaa"} + } + for j := 0; j < net.IPv6len; j++ { + msg[off] = byte(fv.Index(j).Uint()) + off++ + } + case `dns:"wks"`: + // TODO(miek): this is wrong should be lenrd + if off == lenmsg { + break // dyn. updates + } + if val.Field(i).Len() == 0 { + break + } + var bitmapbyte uint16 + for j := 0; j < val.Field(i).Len(); j++ { + serv := uint16((fv.Index(j).Uint())) + bitmapbyte = uint16(serv / 8) + if int(bitmapbyte) > lenmsg { + return lenmsg, &Error{err: "overflow packing wks"} + } + bit := uint16(serv) - bitmapbyte*8 + msg[bitmapbyte] = byte(1 << (7 - bit)) + } + off += int(bitmapbyte) + case `dns:"nsec"`: // NSEC/NSEC3 + // This is the uint16 type bitmap + if val.Field(i).Len() == 0 { + // Do absolutely nothing + break + } + + lastwindow := uint16(0) + length := uint16(0) + if off+2 > lenmsg { + return lenmsg, &Error{err: "overflow packing nsecx"} + } + for j := 0; j < val.Field(i).Len(); j++ { + t := uint16((fv.Index(j).Uint())) + window := uint16(t / 256) + if lastwindow != window { + // New window, jump to the new offset + off += int(length) + 3 + if off > lenmsg { + return lenmsg, &Error{err: "overflow packing nsecx bitmap"} + } + } + length = (t - window*256) / 8 + bit := t - (window * 256) - (length * 8) + if off+2+int(length) > lenmsg { + return lenmsg, &Error{err: "overflow packing nsecx bitmap"} + } + + // Setting the window # + msg[off] = byte(window) + // Setting the octets length + msg[off+1] = byte(length + 1) + // Setting the bit value for the type in the right octet + msg[off+2+int(length)] |= byte(1 << (7 - bit)) + lastwindow = window + } + off += 2 + int(length) + off++ + if off > lenmsg { + return lenmsg, &Error{err: "overflow packing nsecx bitmap"} + } + } + case reflect.Struct: + off, err = packStructValue(fv, msg, off, compression, compress) + if err != nil { + return lenmsg, err + } + case reflect.Uint8: + if off+1 > lenmsg { + return lenmsg, &Error{err: "overflow packing uint8"} + } + msg[off] = byte(fv.Uint()) + off++ + case reflect.Uint16: + if off+2 > lenmsg { + return lenmsg, &Error{err: "overflow packing uint16"} + } + i := fv.Uint() + msg[off] = byte(i >> 8) + msg[off+1] = byte(i) + off += 2 + case reflect.Uint32: + if off+4 > lenmsg { + return lenmsg, &Error{err: "overflow packing uint32"} + } + i := fv.Uint() + msg[off] = byte(i >> 24) + msg[off+1] = byte(i >> 16) + msg[off+2] = byte(i >> 8) + msg[off+3] = byte(i) + off += 4 + case reflect.Uint64: + switch typefield.Tag { + default: + if off+8 > lenmsg { + return lenmsg, &Error{err: "overflow packing uint64"} + } + i := fv.Uint() + msg[off] = byte(i >> 56) + msg[off+1] = byte(i >> 48) + msg[off+2] = byte(i >> 40) + msg[off+3] = byte(i >> 32) + msg[off+4] = byte(i >> 24) + msg[off+5] = byte(i >> 16) + msg[off+6] = byte(i >> 8) + msg[off+7] = byte(i) + off += 8 + case `dns:"uint48"`: + // Used in TSIG, where it stops at 48 bits, so we discard the upper 16 + if off+6 > lenmsg { + return lenmsg, &Error{err: "overflow packing uint64 as uint48"} + } + i := fv.Uint() + msg[off] = byte(i >> 40) + msg[off+1] = byte(i >> 32) + msg[off+2] = byte(i >> 24) + msg[off+3] = byte(i >> 16) + msg[off+4] = byte(i >> 8) + msg[off+5] = byte(i) + off += 6 + } + case reflect.String: + // There are multiple string encodings. + // The tag distinguishes ordinary strings from domain names. + s := fv.String() + switch typefield.Tag { + default: + return lenmsg, &Error{"bad tag packing string: " + typefield.Tag.Get("dns")} + case `dns:"base64"`: + b64, e := fromBase64([]byte(s)) + if e != nil { + return lenmsg, e + } + copy(msg[off:off+len(b64)], b64) + off += len(b64) + case `dns:"domain-name"`: + if off, err = PackDomainName(s, msg, off, compression, false && compress); err != nil { + return lenmsg, err + } + case `dns:"cdomain-name"`: + if off, err = PackDomainName(s, msg, off, compression, true && compress); err != nil { + return lenmsg, err + } + case `dns:"size-base32"`: + // This is purely for NSEC3 atm, the previous byte must + // holds the length of the encoded string. As NSEC3 + // is only defined to SHA1, the hashlength is 20 (160 bits) + msg[off-1] = 20 + fallthrough + case `dns:"base32"`: + b32, e := fromBase32([]byte(s)) + if e != nil { + return lenmsg, e + } + copy(msg[off:off+len(b32)], b32) + off += len(b32) + case `dns:"size-hex"`: + fallthrough + case `dns:"hex"`: + // There is no length encoded here + h, e := hex.DecodeString(s) + if e != nil { + return lenmsg, e + } + if off+hex.DecodedLen(len(s)) > lenmsg { + return lenmsg, &Error{err: "overflow packing hex"} + } + copy(msg[off:off+hex.DecodedLen(len(s))], h) + off += hex.DecodedLen(len(s)) + case `dns:"size"`: + // the size is already encoded in the RR, we can safely use the + // length of string. String is RAW (not encoded in hex, nor base64) + copy(msg[off:off+len(s)], s) + off += len(s) + case `dns:"txt"`: + fallthrough + case "": + if txtTmp == nil { + txtTmp = make([]byte, 256*4+1) + } + off, err = packTxtString(fv.String(), msg, off, txtTmp) + if err != nil { + return lenmsg, err + } + } + } + } + return off, nil +} + +func structValue(any interface{}) reflect.Value { + return reflect.ValueOf(any).Elem() +} + +// PackStruct packs any structure to wire format. +func PackStruct(any interface{}, msg []byte, off int) (off1 int, err error) { + off, err = packStructValue(structValue(any), msg, off, nil, false) + return off, err +} + +func packStructCompress(any interface{}, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + off, err = packStructValue(structValue(any), msg, off, compression, compress) + return off, err +} + +// TODO(miek): Fix use of rdlength here + +// Unpack a reflect.StructValue from msg. +// Same restrictions as packStructValue. +func unpackStructValue(val reflect.Value, msg []byte, off int) (off1 int, err error) { + var lenrd int + lenmsg := len(msg) + for i := 0; i < val.NumField(); i++ { + if lenrd != 0 && lenrd == off { + break + } + if off > lenmsg { + return lenmsg, &Error{"bad offset unpacking"} + } + switch fv := val.Field(i); fv.Kind() { + default: + return lenmsg, &Error{err: "bad kind unpacking"} + case reflect.Interface: + // PrivateRR is the only RR implementation that has interface field. + // therefore it's expected that this interface would be PrivateRdata + switch data := fv.Interface().(type) { + case PrivateRdata: + n, err := data.Unpack(msg[off:lenrd]) + if err != nil { + return lenmsg, err + } + off += n + default: + return lenmsg, &Error{err: "bad kind interface unpacking"} + } + case reflect.Slice: + switch val.Type().Field(i).Tag { + default: + return lenmsg, &Error{"bad tag unpacking slice: " + val.Type().Field(i).Tag.Get("dns")} + case `dns:"domain-name"`: + // HIP record slice of name (or none) + servers := make([]string, 0) + var s string + for off < lenrd { + s, off, err = UnpackDomainName(msg, off) + if err != nil { + return lenmsg, err + } + servers = append(servers, s) + } + fv.Set(reflect.ValueOf(servers)) + case `dns:"txt"`: + if off == lenmsg || lenrd == off { + break + } + var txt []string + txt, off, err = unpackTxt(msg, off, lenrd) + if err != nil { + return lenmsg, err + } + fv.Set(reflect.ValueOf(txt)) + case `dns:"opt"`: // edns0 + if off == lenrd { + // This is an EDNS0 (OPT Record) with no rdata + // We can safely return here. + break + } + edns := make([]EDNS0, 0) + Option: + code := uint16(0) + if off+2 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking opt"} + } + code, off = unpackUint16(msg, off) + optlen, off1 := unpackUint16(msg, off) + if off1+int(optlen) > lenrd { + return lenmsg, &Error{err: "overflow unpacking opt"} + } + switch code { + case EDNS0NSID: + e := new(EDNS0_NSID) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + case EDNS0SUBNET, EDNS0SUBNETDRAFT: + e := new(EDNS0_SUBNET) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + if code == EDNS0SUBNETDRAFT { + e.DraftOption = true + } + case EDNS0UL: + e := new(EDNS0_UL) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + case EDNS0LLQ: + e := new(EDNS0_LLQ) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + case EDNS0DAU: + e := new(EDNS0_DAU) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + case EDNS0DHU: + e := new(EDNS0_DHU) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + case EDNS0N3U: + e := new(EDNS0_N3U) + if err := e.unpack(msg[off1 : off1+int(optlen)]); err != nil { + return lenmsg, err + } + edns = append(edns, e) + off = off1 + int(optlen) + default: + // do nothing? + off = off1 + int(optlen) + } + if off < lenrd { + goto Option + } + fv.Set(reflect.ValueOf(edns)) + case `dns:"a"`: + if off == lenrd { + break // dyn. update + } + if off+net.IPv4len > lenrd || off+net.IPv4len > lenmsg { + return lenmsg, &Error{err: "overflow unpacking a"} + } + fv.Set(reflect.ValueOf(net.IPv4(msg[off], msg[off+1], msg[off+2], msg[off+3]))) + off += net.IPv4len + case `dns:"aaaa"`: + if off == lenrd { + break + } + if off+net.IPv6len > lenrd || off+net.IPv6len > lenmsg { + return lenmsg, &Error{err: "overflow unpacking aaaa"} + } + fv.Set(reflect.ValueOf(net.IP{msg[off], msg[off+1], msg[off+2], msg[off+3], msg[off+4], + msg[off+5], msg[off+6], msg[off+7], msg[off+8], msg[off+9], msg[off+10], + msg[off+11], msg[off+12], msg[off+13], msg[off+14], msg[off+15]})) + off += net.IPv6len + case `dns:"wks"`: + // Rest of the record is the bitmap + serv := make([]uint16, 0) + j := 0 + for off < lenrd { + if off+1 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking wks"} + } + b := msg[off] + // Check the bits one by one, and set the type + if b&0x80 == 0x80 { + serv = append(serv, uint16(j*8+0)) + } + if b&0x40 == 0x40 { + serv = append(serv, uint16(j*8+1)) + } + if b&0x20 == 0x20 { + serv = append(serv, uint16(j*8+2)) + } + if b&0x10 == 0x10 { + serv = append(serv, uint16(j*8+3)) + } + if b&0x8 == 0x8 { + serv = append(serv, uint16(j*8+4)) + } + if b&0x4 == 0x4 { + serv = append(serv, uint16(j*8+5)) + } + if b&0x2 == 0x2 { + serv = append(serv, uint16(j*8+6)) + } + if b&0x1 == 0x1 { + serv = append(serv, uint16(j*8+7)) + } + j++ + off++ + } + fv.Set(reflect.ValueOf(serv)) + case `dns:"nsec"`: // NSEC/NSEC3 + if off == lenrd { + break + } + // Rest of the record is the type bitmap + if off+2 > lenrd || off+2 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking nsecx"} + } + nsec := make([]uint16, 0) + length := 0 + window := 0 + for off+2 < lenrd { + window = int(msg[off]) + length = int(msg[off+1]) + //println("off, windows, length, end", off, window, length, endrr) + if length == 0 { + // A length window of zero is strange. If there + // the window should not have been specified. Bail out + // println("dns: length == 0 when unpacking NSEC") + return lenmsg, &Error{err: "overflow unpacking nsecx"} + } + if length > 32 { + return lenmsg, &Error{err: "overflow unpacking nsecx"} + } + + // Walk the bytes in the window - and check the bit settings... + off += 2 + for j := 0; j < length; j++ { + if off+j+1 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking nsecx"} + } + b := msg[off+j] + // Check the bits one by one, and set the type + if b&0x80 == 0x80 { + nsec = append(nsec, uint16(window*256+j*8+0)) + } + if b&0x40 == 0x40 { + nsec = append(nsec, uint16(window*256+j*8+1)) + } + if b&0x20 == 0x20 { + nsec = append(nsec, uint16(window*256+j*8+2)) + } + if b&0x10 == 0x10 { + nsec = append(nsec, uint16(window*256+j*8+3)) + } + if b&0x8 == 0x8 { + nsec = append(nsec, uint16(window*256+j*8+4)) + } + if b&0x4 == 0x4 { + nsec = append(nsec, uint16(window*256+j*8+5)) + } + if b&0x2 == 0x2 { + nsec = append(nsec, uint16(window*256+j*8+6)) + } + if b&0x1 == 0x1 { + nsec = append(nsec, uint16(window*256+j*8+7)) + } + } + off += length + } + fv.Set(reflect.ValueOf(nsec)) + } + case reflect.Struct: + off, err = unpackStructValue(fv, msg, off) + if err != nil { + return lenmsg, err + } + if val.Type().Field(i).Name == "Hdr" { + lenrd = off + int(val.FieldByName("Hdr").FieldByName("Rdlength").Uint()) + } + case reflect.Uint8: + if off == lenmsg { + break + } + if off+1 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking uint8"} + } + fv.SetUint(uint64(uint8(msg[off]))) + off++ + case reflect.Uint16: + if off == lenmsg { + break + } + var i uint16 + if off+2 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking uint16"} + } + i, off = unpackUint16(msg, off) + fv.SetUint(uint64(i)) + case reflect.Uint32: + if off == lenmsg { + break + } + if off+4 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking uint32"} + } + fv.SetUint(uint64(uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3]))) + off += 4 + case reflect.Uint64: + switch val.Type().Field(i).Tag { + default: + if off+8 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking uint64"} + } + fv.SetUint(uint64(uint64(msg[off])<<56 | uint64(msg[off+1])<<48 | uint64(msg[off+2])<<40 | + uint64(msg[off+3])<<32 | uint64(msg[off+4])<<24 | uint64(msg[off+5])<<16 | uint64(msg[off+6])<<8 | uint64(msg[off+7]))) + off += 8 + case `dns:"uint48"`: + // Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes) + if off+6 > lenmsg { + return lenmsg, &Error{err: "overflow unpacking uint64 as uint48"} + } + fv.SetUint(uint64(uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 | + uint64(msg[off+4])<<8 | uint64(msg[off+5]))) + off += 6 + } + case reflect.String: + var s string + if off == lenmsg { + break + } + switch val.Type().Field(i).Tag { + default: + return lenmsg, &Error{"bad tag unpacking string: " + val.Type().Field(i).Tag.Get("dns")} + case `dns:"hex"`: + hexend := lenrd + if val.FieldByName("Hdr").FieldByName("Rrtype").Uint() == uint64(TypeHIP) { + hexend = off + int(val.FieldByName("HitLength").Uint()) + } + if hexend > lenrd || hexend > lenmsg { + return lenmsg, &Error{err: "overflow unpacking hex"} + } + s = hex.EncodeToString(msg[off:hexend]) + off = hexend + case `dns:"base64"`: + // Rest of the RR is base64 encoded value + b64end := lenrd + if val.FieldByName("Hdr").FieldByName("Rrtype").Uint() == uint64(TypeHIP) { + b64end = off + int(val.FieldByName("PublicKeyLength").Uint()) + } + if b64end > lenrd || b64end > lenmsg { + return lenmsg, &Error{err: "overflow unpacking base64"} + } + s = toBase64(msg[off:b64end]) + off = b64end + case `dns:"cdomain-name"`: + fallthrough + case `dns:"domain-name"`: + if off == lenmsg { + // zero rdata foo, OK for dyn. updates + break + } + s, off, err = UnpackDomainName(msg, off) + if err != nil { + return lenmsg, err + } + case `dns:"size-base32"`: + var size int + switch val.Type().Name() { + case "NSEC3": + switch val.Type().Field(i).Name { + case "NextDomain": + name := val.FieldByName("HashLength") + size = int(name.Uint()) + } + } + if off+size > lenmsg { + return lenmsg, &Error{err: "overflow unpacking base32"} + } + s = toBase32(msg[off : off+size]) + off += size + case `dns:"size-hex"`: + // a "size" string, but it must be encoded in hex in the string + var size int + switch val.Type().Name() { + case "NSEC3": + switch val.Type().Field(i).Name { + case "Salt": + name := val.FieldByName("SaltLength") + size = int(name.Uint()) + case "NextDomain": + name := val.FieldByName("HashLength") + size = int(name.Uint()) + } + case "TSIG": + switch val.Type().Field(i).Name { + case "MAC": + name := val.FieldByName("MACSize") + size = int(name.Uint()) + case "OtherData": + name := val.FieldByName("OtherLen") + size = int(name.Uint()) + } + } + if off+size > lenmsg { + return lenmsg, &Error{err: "overflow unpacking hex"} + } + s = hex.EncodeToString(msg[off : off+size]) + off += size + case `dns:"txt"`: + fallthrough + case "": + s, off, err = unpackTxtString(msg, off) + } + fv.SetString(s) + } + } + return off, nil +} + +// Helpers for dealing with escaped bytes +func isDigit(b byte) bool { return b >= '0' && b <= '9' } + +func dddToByte(s []byte) byte { + return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) +} + +// UnpackStruct unpacks a binary message from offset off to the interface +// value given. +func UnpackStruct(any interface{}, msg []byte, off int) (int, error) { + return unpackStructValue(structValue(any), msg, off) +} + +// Helper function for packing and unpacking +func intToBytes(i *big.Int, length int) []byte { + buf := i.Bytes() + if len(buf) < length { + b := make([]byte, length) + copy(b[length-len(buf):], buf) + return b + } + return buf +} + +func unpackUint16(msg []byte, off int) (uint16, int) { + return uint16(msg[off])<<8 | uint16(msg[off+1]), off + 2 +} + +func packUint16(i uint16) (byte, byte) { + return byte(i >> 8), byte(i) +} + +func toBase32(b []byte) string { + return base32.HexEncoding.EncodeToString(b) +} + +func fromBase32(s []byte) (buf []byte, err error) { + buflen := base32.HexEncoding.DecodedLen(len(s)) + buf = make([]byte, buflen) + n, err := base32.HexEncoding.Decode(buf, s) + buf = buf[:n] + return +} + +func toBase64(b []byte) string { + return base64.StdEncoding.EncodeToString(b) +} + +func fromBase64(s []byte) (buf []byte, err error) { + buflen := base64.StdEncoding.DecodedLen(len(s)) + buf = make([]byte, buflen) + n, err := base64.StdEncoding.Decode(buf, s) + buf = buf[:n] + return +} + +// PackRR packs a resource record rr into msg[off:]. +// See PackDomainName for documentation about the compression. +func PackRR(rr RR, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + if rr == nil { + return len(msg), &Error{err: "nil rr"} + } + + off1, err = packStructCompress(rr, msg, off, compression, compress) + if err != nil { + return len(msg), err + } + if rawSetRdlength(msg, off, off1) { + return off1, nil + } + return off, ErrRdata +} + +// UnpackRR unpacks msg[off:] into an RR. +func UnpackRR(msg []byte, off int) (rr RR, off1 int, err error) { + // unpack just the header, to find the rr type and length + var h RR_Header + off0 := off + if off, err = UnpackStruct(&h, msg, off); err != nil { + return nil, len(msg), err + } + end := off + int(h.Rdlength) + // make an rr of that type and re-unpack. + mk, known := typeToRR[h.Rrtype] + if !known { + rr = new(RFC3597) + } else { + rr = mk() + } + off, err = UnpackStruct(rr, msg, off0) + if off != end { + return &h, end, &Error{err: "bad rdlength"} + } + return rr, off, err +} + +// Reverse a map +func reverseInt8(m map[uint8]string) map[string]uint8 { + n := make(map[string]uint8) + for u, s := range m { + n[s] = u + } + return n +} + +func reverseInt16(m map[uint16]string) map[string]uint16 { + n := make(map[string]uint16) + for u, s := range m { + n[s] = u + } + return n +} + +func reverseInt(m map[int]string) map[string]int { + n := make(map[string]int) + for u, s := range m { + n[s] = u + } + return n +} + +// Convert a MsgHdr to a string, with dig-like headers: +// +//;; opcode: QUERY, status: NOERROR, id: 48404 +// +//;; flags: qr aa rd ra; +func (h *MsgHdr) String() string { + if h == nil { + return " MsgHdr" + } + + s := ";; opcode: " + OpcodeToString[h.Opcode] + s += ", status: " + RcodeToString[h.Rcode] + s += ", id: " + strconv.Itoa(int(h.Id)) + "\n" + + s += ";; flags:" + if h.Response { + s += " qr" + } + if h.Authoritative { + s += " aa" + } + if h.Truncated { + s += " tc" + } + if h.RecursionDesired { + s += " rd" + } + if h.RecursionAvailable { + s += " ra" + } + if h.Zero { // Hmm + s += " z" + } + if h.AuthenticatedData { + s += " ad" + } + if h.CheckingDisabled { + s += " cd" + } + + s += ";" + return s +} + +// Pack packs a Msg: it is converted to to wire format. +// If the dns.Compress is true the message will be in compressed wire format. +func (dns *Msg) Pack() (msg []byte, err error) { + return dns.PackBuffer(nil) +} + +// PackBuffer packs a Msg, using the given buffer buf. If buf is too small +// a new buffer is allocated. +func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { + var dh Header + var compression map[string]int + if dns.Compress { + compression = make(map[string]int) // Compression pointer mappings + } + + if dns.Rcode < 0 || dns.Rcode > 0xFFF { + return nil, ErrRcode + } + if dns.Rcode > 0xF { + // Regular RCODE field is 4 bits + opt := dns.IsEdns0() + if opt == nil { + return nil, ErrExtendedRcode + } + opt.SetExtendedRcode(uint8(dns.Rcode >> 4)) + dns.Rcode &= 0xF + } + + // Convert convenient Msg into wire-like Header. + dh.Id = dns.Id + dh.Bits = uint16(dns.Opcode)<<11 | uint16(dns.Rcode) + if dns.Response { + dh.Bits |= _QR + } + if dns.Authoritative { + dh.Bits |= _AA + } + if dns.Truncated { + dh.Bits |= _TC + } + if dns.RecursionDesired { + dh.Bits |= _RD + } + if dns.RecursionAvailable { + dh.Bits |= _RA + } + if dns.Zero { + dh.Bits |= _Z + } + if dns.AuthenticatedData { + dh.Bits |= _AD + } + if dns.CheckingDisabled { + dh.Bits |= _CD + } + + // Prepare variable sized arrays. + question := dns.Question + answer := dns.Answer + ns := dns.Ns + extra := dns.Extra + + dh.Qdcount = uint16(len(question)) + dh.Ancount = uint16(len(answer)) + dh.Nscount = uint16(len(ns)) + dh.Arcount = uint16(len(extra)) + + // We need the uncompressed length here, because we first pack it and then compress it. + msg = buf + compress := dns.Compress + dns.Compress = false + if packLen := dns.Len() + 1; len(msg) < packLen { + msg = make([]byte, packLen) + } + dns.Compress = compress + + // Pack it in: header and then the pieces. + off := 0 + off, err = packStructCompress(&dh, msg, off, compression, dns.Compress) + if err != nil { + return nil, err + } + for i := 0; i < len(question); i++ { + off, err = packStructCompress(&question[i], msg, off, compression, dns.Compress) + if err != nil { + return nil, err + } + } + for i := 0; i < len(answer); i++ { + off, err = PackRR(answer[i], msg, off, compression, dns.Compress) + if err != nil { + return nil, err + } + } + for i := 0; i < len(ns); i++ { + off, err = PackRR(ns[i], msg, off, compression, dns.Compress) + if err != nil { + return nil, err + } + } + for i := 0; i < len(extra); i++ { + off, err = PackRR(extra[i], msg, off, compression, dns.Compress) + if err != nil { + return nil, err + } + } + return msg[:off], nil +} + +// Unpack unpacks a binary message to a Msg structure. +func (dns *Msg) Unpack(msg []byte) (err error) { + // Header. + var dh Header + off := 0 + if off, err = UnpackStruct(&dh, msg, off); err != nil { + return err + } + dns.Id = dh.Id + dns.Response = (dh.Bits & _QR) != 0 + dns.Opcode = int(dh.Bits>>11) & 0xF + dns.Authoritative = (dh.Bits & _AA) != 0 + dns.Truncated = (dh.Bits & _TC) != 0 + dns.RecursionDesired = (dh.Bits & _RD) != 0 + dns.RecursionAvailable = (dh.Bits & _RA) != 0 + dns.Zero = (dh.Bits & _Z) != 0 + dns.AuthenticatedData = (dh.Bits & _AD) != 0 + dns.CheckingDisabled = (dh.Bits & _CD) != 0 + dns.Rcode = int(dh.Bits & 0xF) + + // Arrays. + dns.Question = make([]Question, dh.Qdcount) + dns.Answer = make([]RR, dh.Ancount) + dns.Ns = make([]RR, dh.Nscount) + dns.Extra = make([]RR, dh.Arcount) + + for i := 0; i < len(dns.Question); i++ { + off, err = UnpackStruct(&dns.Question[i], msg, off) + if err != nil { + return err + } + } + // If we see a TC bit being set we return here, without + // an error, because technically it isn't an error. So return + // without parsing the potentially corrupt packet and hitting an error. + // TODO(miek): this isn't the best strategy! + if dns.Truncated { + dns.Answer = nil + dns.Ns = nil + dns.Extra = nil + return nil + } + for i := 0; i < len(dns.Answer); i++ { + dns.Answer[i], off, err = UnpackRR(msg, off) + if err != nil { + return err + } + } + for i := 0; i < len(dns.Ns); i++ { + dns.Ns[i], off, err = UnpackRR(msg, off) + if err != nil { + return err + } + } + for i := 0; i < len(dns.Extra); i++ { + dns.Extra[i], off, err = UnpackRR(msg, off) + if err != nil { + return err + } + } + if off != len(msg) { + // TODO(miek) make this an error? + // use PackOpt to let people tell how detailed the error reporting should be? + // println("dns: extra bytes in dns packet", off, "<", len(msg)) + } + return nil +} + +// Convert a complete message to a string with dig-like output. +func (dns *Msg) String() string { + if dns == nil { + return " MsgHdr" + } + s := dns.MsgHdr.String() + " " + s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", " + s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", " + s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", " + s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n" + if len(dns.Question) > 0 { + s += "\n;; QUESTION SECTION:\n" + for i := 0; i < len(dns.Question); i++ { + s += dns.Question[i].String() + "\n" + } + } + if len(dns.Answer) > 0 { + s += "\n;; ANSWER SECTION:\n" + for i := 0; i < len(dns.Answer); i++ { + if dns.Answer[i] != nil { + s += dns.Answer[i].String() + "\n" + } + } + } + if len(dns.Ns) > 0 { + s += "\n;; AUTHORITY SECTION:\n" + for i := 0; i < len(dns.Ns); i++ { + if dns.Ns[i] != nil { + s += dns.Ns[i].String() + "\n" + } + } + } + if len(dns.Extra) > 0 { + s += "\n;; ADDITIONAL SECTION:\n" + for i := 0; i < len(dns.Extra); i++ { + if dns.Extra[i] != nil { + s += dns.Extra[i].String() + "\n" + } + } + } + return s +} + +// Len returns the message length when in (un)compressed wire format. +// If dns.Compress is true compression it is taken into account. Len() +// is provided to be a faster way to get the size of the resulting packet, +// than packing it, measuring the size and discarding the buffer. +func (dns *Msg) Len() int { + // We always return one more than needed. + l := 12 // Message header is always 12 bytes + var compression map[string]int + if dns.Compress { + compression = make(map[string]int) + } + for i := 0; i < len(dns.Question); i++ { + l += dns.Question[i].len() + if dns.Compress { + compressionLenHelper(compression, dns.Question[i].Name) + } + } + for i := 0; i < len(dns.Answer); i++ { + l += dns.Answer[i].len() + if dns.Compress { + k, ok := compressionLenSearch(compression, dns.Answer[i].Header().Name) + if ok { + l += 1 - k + } + compressionLenHelper(compression, dns.Answer[i].Header().Name) + k, ok = compressionLenSearchType(compression, dns.Answer[i]) + if ok { + l += 1 - k + } + compressionLenHelperType(compression, dns.Answer[i]) + } + } + for i := 0; i < len(dns.Ns); i++ { + l += dns.Ns[i].len() + if dns.Compress { + k, ok := compressionLenSearch(compression, dns.Ns[i].Header().Name) + if ok { + l += 1 - k + } + compressionLenHelper(compression, dns.Ns[i].Header().Name) + k, ok = compressionLenSearchType(compression, dns.Ns[i]) + if ok { + l += 1 - k + } + compressionLenHelperType(compression, dns.Ns[i]) + } + } + for i := 0; i < len(dns.Extra); i++ { + l += dns.Extra[i].len() + if dns.Compress { + k, ok := compressionLenSearch(compression, dns.Extra[i].Header().Name) + if ok { + l += 1 - k + } + compressionLenHelper(compression, dns.Extra[i].Header().Name) + k, ok = compressionLenSearchType(compression, dns.Extra[i]) + if ok { + l += 1 - k + } + compressionLenHelperType(compression, dns.Extra[i]) + } + } + return l +} + +// Put the parts of the name in the compression map. +func compressionLenHelper(c map[string]int, s string) { + pref := "" + lbs := Split(s) + for j := len(lbs) - 1; j >= 0; j-- { + pref = s[lbs[j]:] + if _, ok := c[pref]; !ok { + c[pref] = len(pref) + } + } +} + +// Look for each part in the compression map and returns its length, +// keep on searching so we get the longest match. +func compressionLenSearch(c map[string]int, s string) (int, bool) { + off := 0 + end := false + if s == "" { // don't bork on bogus data + return 0, false + } + for { + if _, ok := c[s[off:]]; ok { + return len(s[off:]), true + } + if end { + break + } + off, end = NextLabel(s, off) + } + return 0, false +} + +// TODO(miek): should add all types, because the all can be *used* for compression. +func compressionLenHelperType(c map[string]int, r RR) { + switch x := r.(type) { + case *NS: + compressionLenHelper(c, x.Ns) + case *MX: + compressionLenHelper(c, x.Mx) + case *CNAME: + compressionLenHelper(c, x.Target) + case *PTR: + compressionLenHelper(c, x.Ptr) + case *SOA: + compressionLenHelper(c, x.Ns) + compressionLenHelper(c, x.Mbox) + case *MB: + compressionLenHelper(c, x.Mb) + case *MG: + compressionLenHelper(c, x.Mg) + case *MR: + compressionLenHelper(c, x.Mr) + case *MF: + compressionLenHelper(c, x.Mf) + case *MD: + compressionLenHelper(c, x.Md) + case *RT: + compressionLenHelper(c, x.Host) + case *MINFO: + compressionLenHelper(c, x.Rmail) + compressionLenHelper(c, x.Email) + case *AFSDB: + compressionLenHelper(c, x.Hostname) + } +} + +// Only search on compressing these types. +func compressionLenSearchType(c map[string]int, r RR) (int, bool) { + switch x := r.(type) { + case *NS: + return compressionLenSearch(c, x.Ns) + case *MX: + return compressionLenSearch(c, x.Mx) + case *CNAME: + return compressionLenSearch(c, x.Target) + case *PTR: + return compressionLenSearch(c, x.Ptr) + case *SOA: + k, ok := compressionLenSearch(c, x.Ns) + k1, ok1 := compressionLenSearch(c, x.Mbox) + if !ok && !ok1 { + return 0, false + } + return k + k1, true + case *MB: + return compressionLenSearch(c, x.Mb) + case *MG: + return compressionLenSearch(c, x.Mg) + case *MR: + return compressionLenSearch(c, x.Mr) + case *MF: + return compressionLenSearch(c, x.Mf) + case *MD: + return compressionLenSearch(c, x.Md) + case *RT: + return compressionLenSearch(c, x.Host) + case *MINFO: + k, ok := compressionLenSearch(c, x.Rmail) + k1, ok1 := compressionLenSearch(c, x.Email) + if !ok && !ok1 { + return 0, false + } + return k + k1, true + case *AFSDB: + return compressionLenSearch(c, x.Hostname) + } + return 0, false +} + +// id returns a 16 bits random number to be used as a +// message id. The random provided should be good enough. +func id() uint16 { + return uint16(rand.Int()) ^ uint16(time.Now().Nanosecond()) +} + +// Copy returns a new RR which is a deep-copy of r. +func Copy(r RR) RR { + r1 := r.copy() + return r1 +} + +// Copy returns a new *Msg which is a deep-copy of dns. +func (dns *Msg) Copy() *Msg { + r1 := new(Msg) + r1.MsgHdr = dns.MsgHdr + r1.Compress = dns.Compress + + if len(dns.Question) > 0 { + r1.Question = make([]Question, len(dns.Question)) + copy(r1.Question, dns.Question) // TODO(miek): Question is an immutable value, ok to do a shallow-copy + } + + if len(dns.Answer) > 0 { + r1.Answer = make([]RR, len(dns.Answer)) + for i := 0; i < len(dns.Answer); i++ { + r1.Answer[i] = dns.Answer[i].copy() + } + } + + if len(dns.Ns) > 0 { + r1.Ns = make([]RR, len(dns.Ns)) + for i := 0; i < len(dns.Ns); i++ { + r1.Ns[i] = dns.Ns[i].copy() + } + } + + if len(dns.Extra) > 0 { + r1.Extra = make([]RR, len(dns.Extra)) + for i := 0; i < len(dns.Extra); i++ { + r1.Extra[i] = dns.Extra[i].copy() + } + } + + return r1 +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/nsecx.go b/Godeps/_workspace/src/github.com/miekg/dns/nsecx.go new file mode 100644 index 00000000000..ac48da0f5ac --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/nsecx.go @@ -0,0 +1,110 @@ +package dns + +import ( + "crypto/sha1" + "hash" + "io" + "strings" +) + +type saltWireFmt struct { + Salt string `dns:"size-hex"` +} + +// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in +// uppercase. +func HashName(label string, ha uint8, iter uint16, salt string) string { + saltwire := new(saltWireFmt) + saltwire.Salt = salt + wire := make([]byte, DefaultMsgSize) + n, err := PackStruct(saltwire, wire, 0) + if err != nil { + return "" + } + wire = wire[:n] + name := make([]byte, 255) + off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) + if err != nil { + return "" + } + name = name[:off] + var s hash.Hash + switch ha { + case SHA1: + s = sha1.New() + default: + return "" + } + + // k = 0 + name = append(name, wire...) + io.WriteString(s, string(name)) + nsec3 := s.Sum(nil) + // k > 0 + for k := uint16(0); k < iter; k++ { + s.Reset() + nsec3 = append(nsec3, wire...) + io.WriteString(s, string(nsec3)) + nsec3 = s.Sum(nil) + } + return toBase32(nsec3) +} + +type Denialer interface { + // Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3. + Cover(name string) bool + // Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3. + Match(name string) bool +} + +// Cover implements the Denialer interface. +func (rr *NSEC) Cover(name string) bool { + return true +} + +// Match implements the Denialer interface. +func (rr *NSEC) Match(name string) bool { + return true +} + +// Cover implements the Denialer interface. +func (rr *NSEC3) Cover(name string) bool { + // FIXME(miek): check if the zones match + // FIXME(miek): check if we're not dealing with parent nsec3 + hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) + labels := Split(rr.Hdr.Name) + if len(labels) < 2 { + return false + } + hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot + if hash == rr.NextDomain { + return false // empty interval + } + if hash > rr.NextDomain { // last name, points to apex + // hname > hash + // hname > rr.NextDomain + // TODO(miek) + } + if hname <= hash { + return false + } + if hname >= rr.NextDomain { + return false + } + return true +} + +// Match implements the Denialer interface. +func (rr *NSEC3) Match(name string) bool { + // FIXME(miek): Check if we are in the same zone + hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) + labels := Split(rr.Hdr.Name) + if len(labels) < 2 { + return false + } + hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the . + if hash == hname { + return true + } + return false +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/nsecx_test.go b/Godeps/_workspace/src/github.com/miekg/dns/nsecx_test.go new file mode 100644 index 00000000000..72f641a6673 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/nsecx_test.go @@ -0,0 +1,33 @@ +package dns + +import ( + "testing" +) + +func TestPackNsec3(t *testing.T) { + nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD") + if nsec3 != "ROCCJAE8BJJU7HN6T7NG3TNM8ACRS87J" { + t.Logf("%v\n", nsec3) + t.Fail() + } + + nsec3 = HashName("a.b.c.example.org.", SHA1, 2, "DEAD") + if nsec3 != "6LQ07OAHBTOOEU2R9ANI2AT70K5O0RCG" { + t.Logf("%v\n", nsec3) + t.Fail() + } +} + +func TestNsec3(t *testing.T) { + // examples taken from .nl + nsec3, _ := NewRR("39p91242oslggest5e6a7cci4iaeqvnk.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6 NS DS RRSIG") + if !nsec3.(*NSEC3).Cover("snasajsksasasa.nl.") { // 39p94jrinub66hnpem8qdpstrec86pg3 + t.Logf("39p94jrinub66hnpem8qdpstrec86pg3. should be covered by 39p91242oslggest5e6a7cci4iaeqvnk.nl. - 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6") + t.Fail() + } + nsec3, _ = NewRR("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 SK4F38CQ0ATIEI8MH3RGD0P5I4II6QAN NS SOA TXT RRSIG DNSKEY NSEC3PARAM") + if !nsec3.(*NSEC3).Match("nl.") { // sk4e8fj94u78smusb40o1n0oltbblu2r.nl. + t.Logf("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. should match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/parse_test.go b/Godeps/_workspace/src/github.com/miekg/dns/parse_test.go new file mode 100644 index 00000000000..dd2799d1f14 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/parse_test.go @@ -0,0 +1,1276 @@ +package dns + +import ( + "bytes" + "crypto/rsa" + "encoding/hex" + "fmt" + "math/rand" + "net" + "reflect" + "strconv" + "strings" + "testing" + "testing/quick" + "time" +) + +func TestDotInName(t *testing.T) { + buf := make([]byte, 20) + PackDomainName("aa\\.bb.nl.", buf, 0, nil, false) + // index 3 must be a real dot + if buf[3] != '.' { + t.Log("dot should be a real dot") + t.Fail() + } + + if buf[6] != 2 { + t.Log("this must have the value 2") + t.Fail() + } + dom, _, _ := UnpackDomainName(buf, 0) + // printing it should yield the backspace again + if dom != "aa\\.bb.nl." { + t.Log("dot should have been escaped: " + dom) + t.Fail() + } +} + +func TestDotLastInLabel(t *testing.T) { + sample := "aa\\..au." + buf := make([]byte, 20) + _, err := PackDomainName(sample, buf, 0, nil, false) + if err != nil { + t.Fatalf("unexpected error packing domain: %s", err) + } + dom, _, _ := UnpackDomainName(buf, 0) + if dom != sample { + t.Fatalf("unpacked domain `%s' doesn't match packed domain", dom) + } +} + +func TestTooLongDomainName(t *testing.T) { + l := "aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt." + dom := l + l + l + l + l + l + l + _, e := NewRR(dom + " IN A 127.0.0.1") + if e == nil { + t.Log("should be too long") + t.Fail() + } else { + t.Logf("error is %s", e.Error()) + } + _, e = NewRR("..com. IN A 127.0.0.1") + if e == nil { + t.Log("should fail") + t.Fail() + } else { + t.Logf("error is %s", e.Error()) + } +} + +func TestDomainName(t *testing.T) { + tests := []string{"r\\.gieben.miek.nl.", "www\\.www.miek.nl.", + "www.*.miek.nl.", "www.*.miek.nl.", + } + dbuff := make([]byte, 40) + + for _, ts := range tests { + if _, err := PackDomainName(ts, dbuff, 0, nil, false); err != nil { + t.Log("not a valid domain name") + t.Fail() + continue + } + n, _, err := UnpackDomainName(dbuff, 0) + if err != nil { + t.Log("failed to unpack packed domain name") + t.Fail() + continue + } + if ts != n { + t.Logf("must be equal: in: %s, out: %s\n", ts, n) + t.Fail() + } + } +} + +func TestDomainNameAndTXTEscapes(t *testing.T) { + tests := []byte{'.', '(', ')', ';', ' ', '@', '"', '\\', '\t', '\r', '\n', 0, 255} + for _, b := range tests { + rrbytes := []byte{ + 1, b, 0, // owner + byte(TypeTXT >> 8), byte(TypeTXT), + byte(ClassINET >> 8), byte(ClassINET), + 0, 0, 0, 1, // TTL + 0, 2, 1, b, // Data + } + rr1, _, err := UnpackRR(rrbytes, 0) + if err != nil { + panic(err) + } + s := rr1.String() + rr2, err := NewRR(s) + if err != nil { + t.Logf("Error parsing unpacked RR's string: %v", err) + t.Logf(" Bytes: %v\n", rrbytes) + t.Logf("String: %v\n", s) + t.Fail() + } + repacked := make([]byte, len(rrbytes)) + if _, err := PackRR(rr2, repacked, 0, nil, false); err != nil { + t.Logf("error packing parsed RR: %v", err) + t.Logf(" original Bytes: %v\n", rrbytes) + t.Logf("unpacked Struct: %V\n", rr1) + t.Logf(" parsed Struct: %V\n", rr2) + t.Fail() + } + if !bytes.Equal(repacked, rrbytes) { + t.Log("packed bytes don't match original bytes") + t.Logf(" original bytes: %v", rrbytes) + t.Logf(" packed bytes: %v", repacked) + t.Logf("unpacked struct: %V", rr1) + t.Logf(" parsed struct: %V", rr2) + t.Fail() + } + } +} + +func TestTXTEscapeParsing(t *testing.T) { + test := [][]string{ + {`";"`, `";"`}, + {`\;`, `";"`}, + {`"\t"`, `"\t"`}, + {`"\r"`, `"\r"`}, + {`"\ "`, `" "`}, + {`"\;"`, `";"`}, + {`"\;\""`, `";\""`}, + {`"\(a\)"`, `"(a)"`}, + {`"\(a)"`, `"(a)"`}, + {`"(a\)"`, `"(a)"`}, + {`"(a)"`, `"(a)"`}, + {`"\048"`, `"0"`}, + {`"\` + "\n" + `"`, `"\n"`}, + {`"\` + "\r" + `"`, `"\r"`}, + {`"\` + "\x11" + `"`, `"\017"`}, + {`"\'"`, `"'"`}, + } + for _, s := range test { + rr, err := NewRR(fmt.Sprintf("example.com. IN TXT %v", s[0])) + if err != nil { + t.Errorf("Could not parse %v TXT: %s", s[0], err) + continue + } + + txt := sprintTxt(rr.(*TXT).Txt) + if txt != s[1] { + t.Errorf("Mismatch after parsing `%v` TXT record: `%v` != `%v`", s[0], txt, s[1]) + } + } +} + +func GenerateDomain(r *rand.Rand, size int) []byte { + dnLen := size % 70 // artificially limit size so there's less to intrepret if a failure occurs + var dn []byte + done := false + for i := 0; i < dnLen && !done; { + max := dnLen - i + if max > 63 { + max = 63 + } + lLen := max + if lLen != 0 { + lLen = int(r.Int31()) % max + } + done = lLen == 0 + if done { + continue + } + l := make([]byte, lLen+1) + l[0] = byte(lLen) + for j := 0; j < lLen; j++ { + l[j+1] = byte(rand.Int31()) + } + dn = append(dn, l...) + i += 1 + lLen + } + return append(dn, 0) +} + +func TestDomainQuick(t *testing.T) { + r := rand.New(rand.NewSource(0)) + f := func(l int) bool { + db := GenerateDomain(r, l) + ds, _, err := UnpackDomainName(db, 0) + if err != nil { + panic(err) + } + buf := make([]byte, 255) + off, err := PackDomainName(ds, buf, 0, nil, false) + if err != nil { + t.Logf("error packing domain: %s", err.Error()) + t.Logf(" bytes: %v\n", db) + t.Logf("string: %v\n", ds) + return false + } + if !bytes.Equal(db, buf[:off]) { + t.Logf("repacked domain doesn't match original:") + t.Logf("src bytes: %v", db) + t.Logf(" string: %v", ds) + t.Logf("out bytes: %v", buf[:off]) + return false + } + return true + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} + +func GenerateTXT(r *rand.Rand, size int) []byte { + rdLen := size % 300 // artificially limit size so there's less to intrepret if a failure occurs + var rd []byte + for i := 0; i < rdLen; { + max := rdLen - 1 + if max > 255 { + max = 255 + } + sLen := max + if max != 0 { + sLen = int(r.Int31()) % max + } + s := make([]byte, sLen+1) + s[0] = byte(sLen) + for j := 0; j < sLen; j++ { + s[j+1] = byte(rand.Int31()) + } + rd = append(rd, s...) + i += 1 + sLen + } + return rd +} + +func TestTXTRRQuick(t *testing.T) { + s := rand.NewSource(0) + r := rand.New(s) + typeAndClass := []byte{ + byte(TypeTXT >> 8), byte(TypeTXT), + byte(ClassINET >> 8), byte(ClassINET), + 0, 0, 0, 1, // TTL + } + f := func(l int) bool { + owner := GenerateDomain(r, l) + rdata := GenerateTXT(r, l) + rrbytes := make([]byte, 0, len(owner)+2+2+4+2+len(rdata)) + rrbytes = append(rrbytes, owner...) + rrbytes = append(rrbytes, typeAndClass...) + rrbytes = append(rrbytes, byte(len(rdata)>>8)) + rrbytes = append(rrbytes, byte(len(rdata))) + rrbytes = append(rrbytes, rdata...) + rr, _, err := UnpackRR(rrbytes, 0) + if err != nil { + panic(err) + } + buf := make([]byte, len(rrbytes)*3) + off, err := PackRR(rr, buf, 0, nil, false) + if err != nil { + t.Logf("pack Error: %s\nRR: %V", err.Error(), rr) + return false + } + buf = buf[:off] + if !bytes.Equal(buf, rrbytes) { + t.Logf("packed bytes don't match original bytes") + t.Logf("src bytes: %v", rrbytes) + t.Logf(" struct: %V", rr) + t.Logf("oUt bytes: %v", buf) + return false + } + if len(rdata) == 0 { + // string'ing won't produce any data to parse + return true + } + rrString := rr.String() + rr2, err := NewRR(rrString) + if err != nil { + t.Logf("error parsing own output: %s", err.Error()) + t.Logf("struct: %V", rr) + t.Logf("string: %v", rrString) + return false + } + if rr2.String() != rrString { + t.Logf("parsed rr.String() doesn't match original string") + t.Logf("original: %v", rrString) + t.Logf(" parsed: %v", rr2.String()) + return false + } + + buf = make([]byte, len(rrbytes)*3) + off, err = PackRR(rr2, buf, 0, nil, false) + if err != nil { + t.Logf("error packing parsed rr: %s", err.Error()) + t.Logf("unpacked Struct: %V", rr) + t.Logf(" string: %v", rrString) + t.Logf(" parsed Struct: %V", rr2) + return false + } + buf = buf[:off] + if !bytes.Equal(buf, rrbytes) { + t.Logf("parsed packed bytes don't match original bytes") + t.Logf(" source bytes: %v", rrbytes) + t.Logf("unpacked struct: %V", rr) + t.Logf(" string: %v", rrString) + t.Logf(" parsed struct: %V", rr2) + t.Logf(" repacked bytes: %v", buf) + return false + } + return true + } + c := &quick.Config{MaxCountScale: 10} + if err := quick.Check(f, c); err != nil { + t.Error(err) + } +} + +func TestParseDirectiveMisc(t *testing.T) { + tests := map[string]string{ + "$ORIGIN miek.nl.\na IN NS b": "a.miek.nl.\t3600\tIN\tNS\tb.miek.nl.", + "$TTL 2H\nmiek.nl. IN NS b.": "miek.nl.\t7200\tIN\tNS\tb.", + "miek.nl. 1D IN NS b.": "miek.nl.\t86400\tIN\tNS\tb.", + `name. IN SOA a6.nstld.com. hostmaster.nic.name. ( + 203362132 ; serial + 5m ; refresh (5 minutes) + 5m ; retry (5 minutes) + 2w ; expire (2 weeks) + 300 ; minimum (5 minutes) +)`: "name.\t3600\tIN\tSOA\ta6.nstld.com. hostmaster.nic.name. 203362132 300 300 1209600 300", + ". 3600000 IN NS ONE.MY-ROOTS.NET.": ".\t3600000\tIN\tNS\tONE.MY-ROOTS.NET.", + "ONE.MY-ROOTS.NET. 3600000 IN A 192.168.1.1": "ONE.MY-ROOTS.NET.\t3600000\tIN\tA\t192.168.1.1", + } + for i, o := range tests { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestNSEC(t *testing.T) { + nsectests := map[string]string{ + "nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F", + "p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM", + "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC", + "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534", + "localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSec Type65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534", + } + for i, o := range nsectests { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +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", + } + for i, o := range lt { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestParseDS(t *testing.T) { + dt := map[string]string{ + "example.net. 3600 IN DS 40692 12 3 22261A8B0E0D799183E35E24E2AD6BB58533CBA7E3B14D659E9CA09B 2071398F": "example.net.\t3600\tIN\tDS\t40692 12 3 22261A8B0E0D799183E35E24E2AD6BB58533CBA7E3B14D659E9CA09B2071398F", + } + for i, o := range dt { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestQuotes(t *testing.T) { + tests := map[string]string{ + `t.example.com. IN TXT "a bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a bc\"", + `t.example.com. IN TXT "a + bc"`: "t.example.com.\t3600\tIN\tTXT\t\"a\\n bc\"", + `t.example.com. IN TXT ""`: "t.example.com.\t3600\tIN\tTXT\t\"\"", + `t.example.com. IN TXT "a"`: "t.example.com.\t3600\tIN\tTXT\t\"a\"", + `t.example.com. IN TXT "aa"`: "t.example.com.\t3600\tIN\tTXT\t\"aa\"", + `t.example.com. IN TXT "aaa" ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", + `t.example.com. IN TXT "abc" "DEF"`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", + `t.example.com. IN TXT "abc" ( "DEF" )`: "t.example.com.\t3600\tIN\tTXT\t\"abc\" \"DEF\"", + `t.example.com. IN TXT aaa ;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa \"", + `t.example.com. IN TXT aaa aaa;`: "t.example.com.\t3600\tIN\tTXT\t\"aaa aaa\"", + `t.example.com. IN TXT aaa aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa aaa\"", + `t.example.com. IN TXT aaa`: "t.example.com.\t3600\tIN\tTXT\t\"aaa\"", + "cid.urn.arpa. NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu.", + "cid.urn.arpa. NAPTR 100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"rcds+I2C\" \"\" _rcds._udp.gatech.edu.", + "cid.urn.arpa. NAPTR 100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 50 \"s\" \"http+I2L+I2C+I2R\" \"\" _http._tcp.gatech.edu.", + "cid.urn.arpa. NAPTR 100 10 \"\" \"\" \"/urn:cid:.+@([^\\.]+\\.)(.*)$/\\2/i\" .": "cid.urn.arpa.\t3600\tIN\tNAPTR\t100 10 \"\" \"\" \"/urn:cid:.+@([^\\.]+\\.)(.*)$/\\2/i\" .", + } + for i, o := range tests { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is\n`%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestParseClass(t *testing.T) { + tests := map[string]string{ + "t.example.com. IN A 127.0.0.1": "t.example.com. 3600 IN A 127.0.0.1", + "t.example.com. CS A 127.0.0.1": "t.example.com. 3600 CS A 127.0.0.1", + "t.example.com. CH A 127.0.0.1": "t.example.com. 3600 CH A 127.0.0.1", + // ClassANY can not occur in zone files + // "t.example.com. ANY A 127.0.0.1": "t.example.com. 3600 ANY A 127.0.0.1", + "t.example.com. NONE A 127.0.0.1": "t.example.com. 3600 NONE A 127.0.0.1", + } + for i, o := range tests { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is\n`%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestBrace(t *testing.T) { + tests := map[string]string{ + "(miek.nl.) 3600 IN A 127.0.1.1": "miek.nl.\t3600\tIN\tA\t127.0.1.1", + "miek.nl. (3600) IN MX (10) elektron.atoom.net.": "miek.nl.\t3600\tIN\tMX\t10 elektron.atoom.net.", + `miek.nl. IN ( + 3600 A 127.0.0.1)`: "miek.nl.\t3600\tIN\tA\t127.0.0.1", + "(miek.nl.) (A) (127.0.2.1)": "miek.nl.\t3600\tIN\tA\t127.0.2.1", + "miek.nl A 127.0.3.1": "miek.nl.\t3600\tIN\tA\t127.0.3.1", + "_ssh._tcp.local. 60 IN (PTR) stora._ssh._tcp.local.": "_ssh._tcp.local.\t60\tIN\tPTR\tstora._ssh._tcp.local.", + "miek.nl. NS ns.miek.nl": "miek.nl.\t3600\tIN\tNS\tns.miek.nl.", + `(miek.nl.) ( + (IN) + (AAAA) + (::1) )`: "miek.nl.\t3600\tIN\tAAAA\t::1", + `(miek.nl.) ( + (IN) + (AAAA) + (::1))`: "miek.nl.\t3600\tIN\tAAAA\t::1", + "miek.nl. IN AAAA ::2": "miek.nl.\t3600\tIN\tAAAA\t::2", + `((m)(i)ek.(n)l.) (SOA) (soa.) (soa.) ( + 2009032802 ; serial + 21600 ; refresh (6 hours) + 7(2)00 ; retry (2 hours) + 604()800 ; expire (1 week) + 3600 ; minimum (1 hour) + )`: "miek.nl.\t3600\tIN\tSOA\tsoa. soa. 2009032802 21600 7200 604800 3600", + "miek\\.nl. IN A 127.0.0.10": "miek\\.nl.\t3600\tIN\tA\t127.0.0.10", + "miek.nl. IN A 127.0.0.11": "miek.nl.\t3600\tIN\tA\t127.0.0.11", + "miek.nl. A 127.0.0.12": "miek.nl.\t3600\tIN\tA\t127.0.0.12", + `miek.nl. 86400 IN SOA elektron.atoom.net. miekg.atoom.net. ( + 2009032802 ; serial + 21600 ; refresh (6 hours) + 7200 ; retry (2 hours) + 604800 ; expire (1 week) + 3600 ; minimum (1 hour) + )`: "miek.nl.\t86400\tIN\tSOA\telektron.atoom.net. miekg.atoom.net. 2009032802 21600 7200 604800 3600", + } + for i, o := range tests { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error() + "\n\t" + i) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestParseFailure(t *testing.T) { + tests := []string{"miek.nl. IN A 327.0.0.1", + "miek.nl. IN AAAA ::x", + "miek.nl. IN MX a0 miek.nl.", + "miek.nl aap IN MX mx.miek.nl.", + "miek.nl 200 IN mxx 10 mx.miek.nl.", + "miek.nl. inn MX 10 mx.miek.nl.", + // "miek.nl. IN CNAME ", // actually valid nowadays, zero size rdata + "miek.nl. IN CNAME ..", + "miek.nl. PA MX 10 miek.nl.", + "miek.nl. ) IN MX 10 miek.nl.", + } + + for _, s := range tests { + _, err := NewRR(s) + if err == nil { + t.Logf("should have triggered an error: \"%s\"", s) + t.Fail() + } + } +} + +func TestZoneParsing(t *testing.T) { + // parse_test.db + db := ` +a.example.com. IN A 127.0.0.1 +8db7._openpgpkey.example.com. IN OPENPGPKEY mQCNAzIG +$ORIGIN a.example.com. +test IN A 127.0.0.1 +$ORIGIN b.example.com. +test IN CNAME test.a.example.com. +` + start := time.Now().UnixNano() + to := ParseZone(strings.NewReader(db), "", "parse_test.db") + var i int + for x := range to { + i++ + if x.Error != nil { + t.Logf("%s\n", x.Error) + t.Fail() + continue + } + t.Logf("%s\n", x.RR) + } + delta := time.Now().UnixNano() - start + t.Logf("%d RRs parsed in %.2f s (%.2f RR/s)", i, float32(delta)/1e9, float32(i)/(float32(delta)/1e9)) +} + +func ExampleZone() { + zone := `$ORIGIN . +$TTL 3600 ; 1 hour +name IN SOA a6.nstld.com. hostmaster.nic.name. ( + 203362132 ; serial + 300 ; refresh (5 minutes) + 300 ; retry (5 minutes) + 1209600 ; expire (2 weeks) + 300 ; minimum (5 minutes) + ) +$TTL 10800 ; 3 hours +name. 10800 IN NS name. + IN NS g6.nstld.com. + 7200 NS h6.nstld.com. + 3600 IN NS j6.nstld.com. + IN 3600 NS k6.nstld.com. + NS l6.nstld.com. + NS a6.nstld.com. + NS c6.nstld.com. + NS d6.nstld.com. + NS f6.nstld.com. + NS m6.nstld.com. +( + NS m7.nstld.com. +) +$ORIGIN name. +0-0onlus NS ns7.ehiweb.it. + NS ns8.ehiweb.it. +0-g MX 10 mx01.nic + MX 10 mx02.nic + MX 10 mx03.nic + MX 10 mx04.nic +$ORIGIN 0-g.name +moutamassey NS ns01.yahoodomains.jp. + NS ns02.yahoodomains.jp. +` + to := ParseZone(strings.NewReader(zone), "", "testzone") + for x := range to { + fmt.Printf("%s\n", x.RR) + } + // Output: + // name. 3600 IN SOA a6.nstld.com. hostmaster.nic.name. 203362132 300 300 1209600 300 + // name. 10800 IN NS name. + // name. 10800 IN NS g6.nstld.com. + // name. 7200 IN NS h6.nstld.com. + // name. 3600 IN NS j6.nstld.com. + // name. 3600 IN NS k6.nstld.com. + // name. 10800 IN NS l6.nstld.com. + // name. 10800 IN NS a6.nstld.com. + // name. 10800 IN NS c6.nstld.com. + // name. 10800 IN NS d6.nstld.com. + // name. 10800 IN NS f6.nstld.com. + // name. 10800 IN NS m6.nstld.com. + // name. 10800 IN NS m7.nstld.com. + // 0-0onlus.name. 10800 IN NS ns7.ehiweb.it. + // 0-0onlus.name. 10800 IN NS ns8.ehiweb.it. + // 0-g.name. 10800 IN MX 10 mx01.nic.name. + // 0-g.name. 10800 IN MX 10 mx02.nic.name. + // 0-g.name. 10800 IN MX 10 mx03.nic.name. + // 0-g.name. 10800 IN MX 10 mx04.nic.name. + // moutamassey.0-g.name.name. 10800 IN NS ns01.yahoodomains.jp. + // moutamassey.0-g.name.name. 10800 IN NS ns02.yahoodomains.jp. +} + +func ExampleHIP() { + h := `www.example.com IN HIP ( 2 200100107B1A74DF365639CC39F1D578 + AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p +9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQ +b1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D + rvs.example.com. )` + if hip, err := NewRR(h); err == nil { + fmt.Printf("%s\n", hip.String()) + } + // Output: + // www.example.com. 3600 IN HIP 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. +} + +func TestHIP(t *testing.T) { + h := `www.example.com. IN HIP ( 2 200100107B1A74DF365639CC39F1D578 + AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p +9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQ +b1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D + rvs1.example.com. + rvs2.example.com. )` + rr, err := NewRR(h) + if err != nil { + t.Fatalf("failed to parse RR: %s", err) + } + t.Logf("RR: %s", rr) + msg := new(Msg) + msg.Answer = []RR{rr, rr} + bytes, err := msg.Pack() + if err != nil { + t.Fatalf("failed to pack msg: %s", err) + } + if err := msg.Unpack(bytes); err != nil { + t.Fatalf("failed to unpack msg: %s", err) + } + if len(msg.Answer) != 2 { + t.Fatalf("2 answers expected: %V", msg) + } + for i, rr := range msg.Answer { + rr := rr.(*HIP) + t.Logf("RR: %s", rr) + if l := len(rr.RendezvousServers); l != 2 { + t.Fatalf("2 servers expected, only %d in record %d:\n%V", l, i, msg) + } + for j, s := range []string{"rvs1.example.com.", "rvs2.example.com."} { + if rr.RendezvousServers[j] != s { + t.Fatalf("expected server %d of record %d to be %s:\n%V", j, i, s, msg) + } + } + } +} + +func ExampleSOA() { + s := "example.com. 1000 SOA master.example.com. admin.example.com. 1 4294967294 4294967293 4294967295 100" + if soa, err := NewRR(s); err == nil { + fmt.Printf("%s\n", soa.String()) + } + // Output: + // example.com. 1000 IN SOA master.example.com. admin.example.com. 1 4294967294 4294967293 4294967295 100 +} + +func TestLineNumberError(t *testing.T) { + s := "example.com. 1000 SOA master.example.com. admin.example.com. monkey 4294967294 4294967293 4294967295 100" + if _, err := NewRR(s); err != nil { + if err.Error() != "dns: bad SOA zone parameter: \"monkey\" at line: 1:68" { + t.Logf("not expecting this error: " + err.Error()) + t.Fail() + } + } +} + +// Test with no known RR on the line +func TestLineNumberError2(t *testing.T) { + tests := map[string]string{ + "example.com. 1000 SO master.example.com. admin.example.com. 1 4294967294 4294967293 4294967295 100": "dns: expecting RR type or class, not this...: \"SO\" at line: 1:21", + "example.com 1000 IN TALINK a.example.com. b..example.com.": "dns: bad TALINK NextName: \"b..example.com.\" at line: 1:57", + "example.com 1000 IN TALINK ( a.example.com. b..example.com. )": "dns: bad TALINK NextName: \"b..example.com.\" at line: 1:60", + `example.com 1000 IN TALINK ( a.example.com. + bb..example.com. )`: "dns: bad TALINK NextName: \"bb..example.com.\" at line: 2:18", + // This is a bug, it should report an error on line 1, but the new is already processed. + `example.com 1000 IN TALINK ( a.example.com. b...example.com. + )`: "dns: bad TALINK NextName: \"b...example.com.\" at line: 2:1"} + + for in, err := range tests { + _, e := NewRR(in) + if e == nil { + t.Fail() + } else { + if e.Error() != err { + t.Logf("%s\n", in) + t.Logf("error should be %s is %s\n", err, e.Error()) + t.Fail() + } + } + } +} + +// Test if the calculations are correct +func TestRfc1982(t *testing.T) { + // If the current time and the timestamp are more than 68 years apart + // it means the date has wrapped. 0 is 1970 + + // fall in the current 68 year span + strtests := []string{"20120525134203", "19700101000000", "20380119031408"} + for _, v := range strtests { + if x, _ := StringToTime(v); v != TimeToString(x) { + t.Logf("1982 arithmetic string failure %s (%s:%d)", v, TimeToString(x), x) + t.Fail() + } + } + + inttests := map[uint32]string{0: "19700101000000", + 1 << 31: "20380119031408", + 1<<32 - 1: "21060207062815", + } + for i, v := range inttests { + if TimeToString(i) != v { + t.Logf("1982 arithmetic int failure %d:%s (%s)", i, v, TimeToString(i)) + t.Fail() + } + } + + // Future tests, these dates get parsed to a date within the current 136 year span + future := map[string]string{"22680119031408": "20631123173144", + "19010101121212": "20370206184028", + "19210101121212": "20570206184028", + "19500101121212": "20860206184028", + "19700101000000": "19700101000000", + "19690101000000": "21050207062816", + "29210101121212": "21040522212236", + } + for from, to := range future { + x, _ := StringToTime(from) + y := TimeToString(x) + if y != to { + t.Logf("1982 arithmetic future failure %s:%s (%s)", from, to, y) + t.Fail() + } + } +} + +func TestEmpty(t *testing.T) { + for _ = range ParseZone(strings.NewReader(""), "", "") { + t.Logf("should be empty") + t.Fail() + } +} + +func TestLowercaseTokens(t *testing.T) { + var testrecords = []string{ + "example.org. 300 IN a 1.2.3.4", + "example.org. 300 in A 1.2.3.4", + "example.org. 300 in a 1.2.3.4", + "example.org. 300 a 1.2.3.4", + "example.org. 300 A 1.2.3.4", + "example.org. IN a 1.2.3.4", + "example.org. in A 1.2.3.4", + "example.org. in a 1.2.3.4", + "example.org. a 1.2.3.4", + "example.org. A 1.2.3.4", + "example.org. a 1.2.3.4", + "$ORIGIN example.org.\n a 1.2.3.4", + "$Origin example.org.\n a 1.2.3.4", + "$origin example.org.\n a 1.2.3.4", + "example.org. Class1 Type1 1.2.3.4", + } + for _, testrr := range testrecords { + _, err := NewRR(testrr) + if err != nil { + t.Errorf("failed to parse %#v, got %s", testrr, err.Error()) + } + } +} + +func ExampleGenerate() { + // From the manual: http://www.bind9.net/manual/bind/9.3.2/Bv9ARM.ch06.html#id2566761 + zone := "$GENERATE 1-2 0 NS SERVER$.EXAMPLE.\n$GENERATE 1-8 $ CNAME $.0" + to := ParseZone(strings.NewReader(zone), "0.0.192.IN-ADDR.ARPA.", "") + for x := range to { + if x.Error == nil { + fmt.Printf("%s\n", x.RR.String()) + } + } + // Output: + // 0.0.0.192.IN-ADDR.ARPA. 3600 IN NS SERVER1.EXAMPLE. + // 0.0.0.192.IN-ADDR.ARPA. 3600 IN NS SERVER2.EXAMPLE. + // 1.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 1.0.0.0.192.IN-ADDR.ARPA. + // 2.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 2.0.0.0.192.IN-ADDR.ARPA. + // 3.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 3.0.0.0.192.IN-ADDR.ARPA. + // 4.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 4.0.0.0.192.IN-ADDR.ARPA. + // 5.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 5.0.0.0.192.IN-ADDR.ARPA. + // 6.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 6.0.0.0.192.IN-ADDR.ARPA. + // 7.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 7.0.0.0.192.IN-ADDR.ARPA. + // 8.0.0.192.IN-ADDR.ARPA. 3600 IN CNAME 8.0.0.0.192.IN-ADDR.ARPA. +} + +func TestSRVPacking(t *testing.T) { + msg := Msg{} + + things := []string{"1.2.3.4:8484", + "45.45.45.45:8484", + "84.84.84.84:8484", + } + + for i, n := range things { + h, p, err := net.SplitHostPort(n) + if err != nil { + continue + } + port := 8484 + tmp, err := strconv.Atoi(p) + if err == nil { + port = tmp + } + + rr := &SRV{ + Hdr: RR_Header{Name: "somename.", + Rrtype: TypeSRV, + Class: ClassINET, + Ttl: 5}, + Priority: uint16(i), + Weight: 5, + Port: uint16(port), + Target: h + ".", + } + + msg.Answer = append(msg.Answer, rr) + } + + _, err := msg.Pack() + if err != nil { + t.Fatalf("couldn't pack %v\n", msg) + } +} + +func TestParseBackslash(t *testing.T) { + if r, e := NewRR("nul\\000gap.test.globnix.net. 600 IN A 192.0.2.10"); e != nil { + t.Fatalf("could not create RR with \\000 in it") + } else { + t.Logf("parsed %s\n", r.String()) + } + if r, e := NewRR(`nul\000gap.test.globnix.net. 600 IN TXT "Hello\123"`); e != nil { + t.Fatalf("could not create RR with \\000 in it") + } else { + t.Logf("parsed %s\n", r.String()) + } + if r, e := NewRR(`m\ @\ iek.nl. IN 3600 A 127.0.0.1`); e != nil { + t.Fatalf("could not create RR with \\ and \\@ in it") + } else { + t.Logf("parsed %s\n", r.String()) + } +} + +func TestILNP(t *testing.T) { + tests := []string{ + "host1.example.com.\t3600\tIN\tNID\t10 0014:4fff:ff20:ee64", + "host1.example.com.\t3600\tIN\tNID\t20 0015:5fff:ff21:ee65", + "host2.example.com.\t3600\tIN\tNID\t10 0016:6fff:ff22:ee66", + "host1.example.com.\t3600\tIN\tL32\t10 10.1.2.0", + "host1.example.com.\t3600\tIN\tL32\t20 10.1.4.0", + "host2.example.com.\t3600\tIN\tL32\t10 10.1.8.0", + "host1.example.com.\t3600\tIN\tL64\t10 2001:0DB8:1140:1000", + "host1.example.com.\t3600\tIN\tL64\t20 2001:0DB8:2140:2000", + "host2.example.com.\t3600\tIN\tL64\t10 2001:0DB8:4140:4000", + "host1.example.com.\t3600\tIN\tLP\t10 l64-subnet1.example.com.", + "host1.example.com.\t3600\tIN\tLP\t10 l64-subnet2.example.com.", + "host1.example.com.\t3600\tIN\tLP\t20 l32-subnet1.example.com.", + } + for _, t1 := range tests { + r, e := NewRR(t1) + if e != nil { + t.Fatalf("an error occured: %s\n", e.Error()) + } else { + if t1 != r.String() { + t.Fatalf("strings should be equal %s %s", t1, r.String()) + } + } + } +} + +func TestNsapGposEidNimloc(t *testing.T) { + dt := map[string]string{ + "foo.bar.com. IN NSAP 21 47000580ffff000000321099991111222233334444": "foo.bar.com.\t3600\tIN\tNSAP\t21 47000580ffff000000321099991111222233334444", + "host.school.de IN NSAP 17 39276f3100111100002222333344449876": "host.school.de.\t3600\tIN\tNSAP\t17 39276f3100111100002222333344449876", + "444433332222111199990123000000ff. NSAP-PTR foo.bar.com.": "444433332222111199990123000000ff.\t3600\tIN\tNSAP-PTR\tfoo.bar.com.", + "lillee. IN GPOS -32.6882 116.8652 10.0": "lillee.\t3600\tIN\tGPOS\t-32.6882 116.8652 10.0", + "hinault. IN GPOS -22.6882 116.8652 250.0": "hinault.\t3600\tIN\tGPOS\t-22.6882 116.8652 250.0", + "VENERA. IN NIMLOC 75234159EAC457800920": "VENERA.\t3600\tIN\tNIMLOC\t75234159EAC457800920", + "VAXA. IN EID 3141592653589793": "VAXA.\t3600\tIN\tEID\t3141592653589793", + } + for i, o := range dt { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestPX(t *testing.T) { + dt := map[string]string{ + "*.net2.it. IN PX 10 net2.it. PRMD-net2.ADMD-p400.C-it.": "*.net2.it.\t3600\tIN\tPX\t10 net2.it. PRMD-net2.ADMD-p400.C-it.", + "ab.net2.it. IN PX 10 ab.net2.it. O-ab.PRMD-net2.ADMDb.C-it.": "ab.net2.it.\t3600\tIN\tPX\t10 ab.net2.it. O-ab.PRMD-net2.ADMDb.C-it.", + } + for i, o := range dt { + rr, e := NewRR(i) + if e != nil { + t.Log("failed to parse RR: " + e.Error()) + t.Fail() + continue + } + if rr.String() != o { + t.Logf("`%s' should be equal to\n`%s', but is `%s'\n", i, o, rr.String()) + t.Fail() + } else { + t.Logf("RR is OK: `%s'", rr.String()) + } + } +} + +func TestComment(t *testing.T) { + // Comments we must see + comments := map[string]bool{"; this is comment 1": true, + "; this is comment 4": true, "; this is comment 6": true, + "; this is comment 7": true, "; this is comment 8": true} + zone := ` +foo. IN A 10.0.0.1 ; this is comment 1 +foo. IN A ( + 10.0.0.2 ; this is comment2 +) +; this is comment3 +foo. IN A 10.0.0.3 +foo. IN A ( 10.0.0.4 ); this is comment 4 + +foo. IN A 10.0.0.5 +; this is comment5 + +foo. IN A 10.0.0.6 + +foo. IN DNSKEY 256 3 5 AwEAAb+8l ; this is comment 6 +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.Logf("wrong comment %s", x.Comment) + t.Fail() + } + } + } + } +} + +func TestEUIxx(t *testing.T) { + tests := map[string]string{ + "host.example. IN EUI48 00-00-5e-90-01-2a": "host.example.\t3600\tIN\tEUI48\t00-00-5e-90-01-2a", + "host.example. IN EUI64 00-00-5e-ef-00-00-00-2a": "host.example.\t3600\tIN\tEUI64\t00-00-5e-ef-00-00-00-2a", + } + for i, o := range tests { + r, e := NewRR(i) + if e != nil { + t.Logf("failed to parse %s: %s\n", i, e.Error()) + t.Fail() + } + if r.String() != o { + t.Logf("want %s, got %s\n", o, r.String()) + t.Fail() + } + } +} + +func TestUserRR(t *testing.T) { + tests := map[string]string{ + "host.example. IN UID 1234": "host.example.\t3600\tIN\tUID\t1234", + "host.example. IN GID 1234556": "host.example.\t3600\tIN\tGID\t1234556", + "host.example. IN UINFO \"Miek Gieben\"": "host.example.\t3600\tIN\tUINFO\t\"Miek Gieben\"", + } + for i, o := range tests { + r, e := NewRR(i) + if e != nil { + t.Logf("failed to parse %s: %s\n", i, e.Error()) + t.Fail() + } + if r.String() != o { + t.Logf("want %s, got %s\n", o, r.String()) + t.Fail() + } + } +} + +func TestTXT(t *testing.T) { + // Test single entry TXT record + rr, err := NewRR(`_raop._tcp.local. 60 IN TXT "single value"`) + if err != nil { + t.Error("failed to parse single value TXT record", err) + } else if rr, ok := rr.(*TXT); !ok { + t.Error("wrong type, record should be of type TXT") + } else { + if len(rr.Txt) != 1 { + t.Error("bad size of TXT value:", len(rr.Txt)) + } else if rr.Txt[0] != "single value" { + t.Error("bad single value") + } + if rr.String() != `_raop._tcp.local. 60 IN TXT "single value"` { + t.Error("bad representation of TXT record:", rr.String()) + } + if rr.len() != 28+1+12 { + t.Error("bad size of serialized record:", rr.len()) + } + } + + // Test multi entries TXT record + rr, err = NewRR(`_raop._tcp.local. 60 IN TXT "a=1" "b=2" "c=3" "d=4"`) + if err != nil { + t.Error("failed to parse multi-values TXT record", err) + } else if rr, ok := rr.(*TXT); !ok { + t.Error("wrong type, record should be of type TXT") + } else { + if len(rr.Txt) != 4 { + t.Error("bad size of TXT multi-value:", len(rr.Txt)) + } else if rr.Txt[0] != "a=1" || rr.Txt[1] != "b=2" || rr.Txt[2] != "c=3" || rr.Txt[3] != "d=4" { + t.Error("bad values in TXT records") + } + if rr.String() != `_raop._tcp.local. 60 IN TXT "a=1" "b=2" "c=3" "d=4"` { + t.Error("bad representation of TXT multi value record:", rr.String()) + } + if rr.len() != 28+1+3+1+3+1+3+1+3 { + t.Error("bad size of serialized multi value record:", rr.len()) + } + } + + // Test empty-string in TXT record + rr, err = NewRR(`_raop._tcp.local. 60 IN TXT ""`) + if err != nil { + t.Error("failed to parse empty-string TXT record", err) + } else if rr, ok := rr.(*TXT); !ok { + t.Error("wrong type, record should be of type TXT") + } else { + if len(rr.Txt) != 1 { + t.Error("bad size of TXT empty-string value:", len(rr.Txt)) + } else if rr.Txt[0] != "" { + t.Error("bad value for empty-string TXT record") + } + if rr.String() != `_raop._tcp.local. 60 IN TXT ""` { + t.Error("bad representation of empty-string TXT record:", rr.String()) + } + if rr.len() != 28+1 { + t.Error("bad size of serialized record:", rr.len()) + } + } +} + +func TestTypeXXXX(t *testing.T) { + _, err := NewRR("example.com IN TYPE1234 \\# 4 aabbccdd") + if err != nil { + t.Logf("failed to parse TYPE1234 RR: %s", err.Error()) + t.Fail() + } + _, err = NewRR("example.com IN TYPE655341 \\# 8 aabbccddaabbccdd") + if err == nil { + t.Logf("this should not work, for TYPE655341") + t.Fail() + } + _, err = NewRR("example.com IN TYPE1 \\# 4 0a000001") + if err == nil { + t.Logf("this should not work") + t.Fail() + } +} + +func TestPTR(t *testing.T) { + _, err := NewRR("144.2.0.192.in-addr.arpa. 900 IN PTR ilouse03146p0\\(.example.com.") + if err != nil { + t.Error("failed to parse ", err.Error()) + } +} + +func TestDigit(t *testing.T) { + tests := map[string]byte{ + "miek\\000.nl. 100 IN TXT \"A\"": 0, + "miek\\001.nl. 100 IN TXT \"A\"": 1, + "miek\\254.nl. 100 IN TXT \"A\"": 254, + "miek\\255.nl. 100 IN TXT \"A\"": 255, + "miek\\256.nl. 100 IN TXT \"A\"": 0, + "miek\\257.nl. 100 IN TXT \"A\"": 1, + "miek\\004.nl. 100 IN TXT \"A\"": 4, + } + for s, i := range tests { + r, e := NewRR(s) + buf := make([]byte, 40) + if e != nil { + t.Fatalf("failed to parse %s\n", e.Error()) + } + PackRR(r, buf, 0, nil, false) + t.Logf("%v\n", buf) + if buf[5] != i { + t.Fatalf("5 pos must be %d, is %d", i, buf[5]) + } + r1, _, _ := UnpackRR(buf, 0) + if r1.Header().Ttl != 100 { + t.Fatalf("TTL should %d, is %d", 100, r1.Header().Ttl) + } + } +} + +func TestParseRRSIGTimestamp(t *testing.T) { + tests := map[string]bool{ + `miek.nl. IN RRSIG SOA 8 2 43200 20140210031301 20140111031301 12051 miek.nl. MVZUyrYwq0iZhMFDDnVXD2BvuNiUJjSYlJAgzyAE6CF875BMvvZa+Sb0 RlSCL7WODQSQHhCx/fegHhVVF+Iz8N8kOLrmXD1+jO3Bm6Prl5UhcsPx WTBsg/kmxbp8sR1kvH4oZJtVfakG3iDerrxNaf0sQwhZzyfJQAqpC7pcBoc=`: true, + `miek.nl. IN RRSIG SOA 8 2 43200 315565800 4102477800 12051 miek.nl. MVZUyrYwq0iZhMFDDnVXD2BvuNiUJjSYlJAgzyAE6CF875BMvvZa+Sb0 RlSCL7WODQSQHhCx/fegHhVVF+Iz8N8kOLrmXD1+jO3Bm6Prl5UhcsPx WTBsg/kmxbp8sR1kvH4oZJtVfakG3iDerrxNaf0sQwhZzyfJQAqpC7pcBoc=`: true, + } + for r, _ := range tests { + _, e := NewRR(r) + if e != nil { + t.Fail() + t.Logf("%s\n", e.Error()) + } + } +} + +func TestTxtEqual(t *testing.T) { + rr1 := new(TXT) + rr1.Hdr = RR_Header{Name: ".", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} + rr1.Txt = []string{"a\"a", "\"", "b"} + rr2, _ := NewRR(rr1.String()) + if rr1.String() != rr2.String() { + t.Logf("these two TXT records should match") + t.Logf("\n%s\n%s\n", rr1.String(), rr2.String()) + t.Fail() // This is not an error, but keep this test. + } + t.Logf("\n%s\n%s\n", rr1.String(), rr2.String()) +} + +func TestTxtLong(t *testing.T) { + rr1 := new(TXT) + rr1.Hdr = RR_Header{Name: ".", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} + // Make a long txt record, this breaks when sending the packet, + // but not earlier. + rr1.Txt = []string{"start-"} + for i := 0; i < 200; i++ { + rr1.Txt[0] += "start-" + } + str := rr1.String() + if len(str) < len(rr1.Txt[0]) { + t.Logf("string conversion should work") + t.Fail() + } +} + +// Basically, don't crash. +func TestMalformedPackets(t *testing.T) { + var packets = []string{ + "0021641c0000000100000000000078787878787878787878787303636f6d0000100001", + } + + // com = 63 6f 6d + for _, packet := range packets { + data, _ := hex.DecodeString(packet) + // for _, v := range data { + // t.Logf("%s ", string(v)) + // } + var msg Msg + msg.Unpack(data) + // println(msg.String()) + } +} + +type algorithm struct { + name uint8 + bits int +} + +func TestNewPrivateKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + algorithms := []algorithm{ + algorithm{ECDSAP256SHA256, 256}, + algorithm{ECDSAP384SHA384, 384}, + algorithm{RSASHA1, 1024}, + algorithm{RSASHA256, 2048}, + algorithm{DSA, 1024}, + } + + for _, algo := range algorithms { + key := new(DNSKEY) + key.Hdr.Rrtype = TypeDNSKEY + key.Hdr.Name = "miek.nl." + key.Hdr.Class = ClassINET + key.Hdr.Ttl = 14400 + key.Flags = 256 + key.Protocol = 3 + key.Algorithm = algo.name + privkey, err := key.Generate(algo.bits) + if err != nil { + t.Fatal(err.Error()) + } + + newPrivKey, err := key.NewPrivateKey(key.PrivateKeyString(privkey)) + if err != nil { + t.Log(key.String()) + t.Log(key.PrivateKeyString(privkey)) + + t.Fatal(err.Error()) + } + + switch newPrivKey := newPrivKey.(type) { + case *rsa.PrivateKey: + newPrivKey.Precompute() + } + + if !reflect.DeepEqual(privkey, newPrivKey) { + t.Errorf("[%v] Private keys differ:\n%#v\n%#v\n", AlgorithmToString[algo.name], privkey, newPrivKey) + } + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/privaterr.go b/Godeps/_workspace/src/github.com/miekg/dns/privaterr.go new file mode 100644 index 00000000000..225713743c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/privaterr.go @@ -0,0 +1,122 @@ +/* +PRIVATE RR + +RFC 6895 sets aside a range of type codes for private use. This range +is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these +can be used, before requesting an official type code from IANA. +*/ +package dns + +import ( + "fmt" + "strings" +) + +// PrivateRdata is an interface used for implementing "Private Use" RR types, see +// RFC 6895. This allows one to experiment with new RR types, without requesting an +// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. +type PrivateRdata interface { + // String returns the text presentaton of the Rdata of the Private RR. + String() string + // Parse parses the Rdata of the private RR. + Parse([]string) error + // 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. + Copy(PrivateRdata) error + // Len returns the length in octets of the Rdata. + Len() int +} + +// PrivateRR represents an RR that uses a PrivateRdata user-defined type. +// It mocks normal RRs and implements dns.RR interface. +type PrivateRR struct { + Hdr RR_Header + Data PrivateRdata +} + +func mkPrivateRR(rrtype uint16) *PrivateRR { + // Panics if RR is not an instance of PrivateRR. + rrfunc, ok := typeToRR[rrtype] + if !ok { + panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) + } + + anyrr := rrfunc() + switch rr := anyrr.(type) { + case *PrivateRR: + return rr + } + panic(fmt.Sprintf("dns: RR is not a PrivateRR, typeToRR[%d] generator returned %T", rrtype, anyrr)) +} + +func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } +func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } + +// Private len and copy parts to satisfy RR interface. +func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } +func (r *PrivateRR) copy() RR { + // make new RR like this: + rr := mkPrivateRR(r.Hdr.Rrtype) + newh := r.Hdr.copyHeader() + rr.Hdr = *newh + + err := r.Data.Copy(rr.Data) + if err != nil { + panic("dns: got value that could not be used to copy Private rdata") + } + return rr +} + +// PrivateHandle registers a private resource record type. It requires +// string and numeric representation of private RR type and generator function as argument. +func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { + rtypestr = strings.ToUpper(rtypestr) + + typeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } + TypeToString[rtype] = rtypestr + StringToType[rtypestr] = rtype + + setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := mkPrivateRR(h.Rrtype) + rr.Hdr = h + + var l lex + text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 + FETCH: + for { + // TODO(miek): we could also be returning _QUOTE, this might or might not + // be an issue (basically parsing TXT becomes hard) + switch l = <-c; l.value { + case _NEWLINE, _EOF: + break FETCH + case _STRING: + text = append(text, l.token) + } + } + + err := rr.Data.Parse(text) + if err != nil { + return nil, &ParseError{f, err.Error(), l}, "" + } + + return rr, nil, "" + } + + typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} +} + +// PrivateHandleRemove removes defenitions required to support private RR type. +func PrivateHandleRemove(rtype uint16) { + rtypestr, ok := TypeToString[rtype] + if ok { + delete(typeToRR, rtype) + delete(TypeToString, rtype) + delete(typeToparserFunc, rtype) + delete(StringToType, rtypestr) + } + return +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/privaterr_test.go b/Godeps/_workspace/src/github.com/miekg/dns/privaterr_test.go new file mode 100644 index 00000000000..6f1dff89c6a --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/privaterr_test.go @@ -0,0 +1,169 @@ +package dns_test + +import ( + "github.com/miekg/dns" + "strings" + "testing" +) + +const TypeISBN uint16 = 0x0F01 + +// A crazy new RR type :) +type ISBN struct { + x string // rdata with 10 or 13 numbers, dashes or spaces allowed +} + +func NewISBN() dns.PrivateRdata { return &ISBN{""} } + +func (rd *ISBN) Len() int { return len([]byte(rd.x)) } +func (rd *ISBN) String() string { return rd.x } + +func (rd *ISBN) Parse(txt []string) error { + rd.x = strings.TrimSpace(strings.Join(txt, " ")) + return nil +} + +func (rd *ISBN) Pack(buf []byte) (int, error) { + b := []byte(rd.x) + n := copy(buf, b) + if n != len(b) { + return n, dns.ErrBuf + } + return n, nil +} + +func (rd *ISBN) Unpack(buf []byte) (int, error) { + rd.x = string(buf) + return len(buf), nil +} + +func (rd *ISBN) Copy(dest dns.PrivateRdata) error { + isbn, ok := dest.(*ISBN) + if !ok { + return dns.ErrRdata + } + isbn.x = rd.x + return nil +} + +var testrecord = strings.Join([]string{"example.org.", "3600", "IN", "ISBN", "12-3 456789-0-123"}, "\t") + +func TestPrivateText(t *testing.T) { + dns.PrivateHandle("ISBN", TypeISBN, NewISBN) + defer dns.PrivateHandleRemove(TypeISBN) + + rr, err := dns.NewRR(testrecord) + if err != nil { + t.Fatal(err) + } + if rr.String() != testrecord { + t.Errorf("record string representation did not match original %#v != %#v", rr.String(), testrecord) + } else { + t.Log(rr.String()) + } +} + +func TestPrivateByteSlice(t *testing.T) { + dns.PrivateHandle("ISBN", TypeISBN, NewISBN) + defer dns.PrivateHandleRemove(TypeISBN) + + rr, err := dns.NewRR(testrecord) + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, 100) + off, err := dns.PackRR(rr, buf, 0, nil, false) + if err != nil { + t.Errorf("got error packing ISBN: %s", err) + } + + custrr := rr.(*dns.PrivateRR) + if ln := custrr.Data.Len() + len(custrr.Header().Name) + 11; ln != off { + t.Errorf("offset is not matching to length of Private RR: %d!=%d", off, ln) + } + + rr1, off1, err := dns.UnpackRR(buf[:off], 0) + if err != nil { + t.Errorf("got error unpacking ISBN: %s", err) + } + + if off1 != off { + t.Errorf("Offset after unpacking differs: %d != %d", off1, off) + } + + if rr1.String() != testrecord { + t.Errorf("Record string representation did not match original %#v != %#v", rr1.String(), testrecord) + } else { + t.Log(rr1.String()) + } +} + +const TypeVERSION uint16 = 0x0F02 + +type VERSION struct { + x string +} + +func NewVersion() dns.PrivateRdata { return &VERSION{""} } + +func (rd *VERSION) String() string { return rd.x } +func (rd *VERSION) Parse(txt []string) error { + rd.x = strings.TrimSpace(strings.Join(txt, " ")) + return nil +} + +func (rd *VERSION) Pack(buf []byte) (int, error) { + b := []byte(rd.x) + n := copy(buf, b) + if n != len(b) { + return n, dns.ErrBuf + } + return n, nil +} + +func (rd *VERSION) Unpack(buf []byte) (int, error) { + rd.x = string(buf) + return len(buf), nil +} + +func (rd *VERSION) Copy(dest dns.PrivateRdata) error { + isbn, ok := dest.(*VERSION) + if !ok { + return dns.ErrRdata + } + isbn.x = rd.x + return nil +} + +func (rd *VERSION) Len() int { + return len([]byte(rd.x)) +} + +var smallzone = `$ORIGIN example.org. +@ SOA sns.dns.icann.org. noc.dns.icann.org. ( + 2014091518 7200 3600 1209600 3600 +) + A 1.2.3.4 +ok ISBN 1231-92110-12 +go VERSION ( + 1.3.1 ; comment +) +www ISBN 1231-92110-16 +* CNAME @ +` + +func TestPrivateZoneParser(t *testing.T) { + dns.PrivateHandle("ISBN", TypeISBN, NewISBN) + dns.PrivateHandle("VERSION", TypeVERSION, NewVersion) + defer dns.PrivateHandleRemove(TypeISBN) + defer dns.PrivateHandleRemove(TypeVERSION) + + r := strings.NewReader(smallzone) + for x := range dns.ParseZone(r, ".", "") { + if err := x.Error; err != nil { + t.Fatal(err) + } + t.Log(x.RR) + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/rawmsg.go b/Godeps/_workspace/src/github.com/miekg/dns/rawmsg.go new file mode 100644 index 00000000000..f138b7761df --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/rawmsg.go @@ -0,0 +1,95 @@ +package dns + +// These raw* functions do not use reflection, they directly set the values +// in the buffer. There are faster than their reflection counterparts. + +// RawSetId sets the message id in buf. +func rawSetId(msg []byte, i uint16) bool { + if len(msg) < 2 { + return false + } + msg[0], msg[1] = packUint16(i) + return true +} + +// rawSetQuestionLen sets the length of the question section. +func rawSetQuestionLen(msg []byte, i uint16) bool { + if len(msg) < 6 { + return false + } + msg[4], msg[5] = packUint16(i) + return true +} + +// rawSetAnswerLen sets the lenght of the answer section. +func rawSetAnswerLen(msg []byte, i uint16) bool { + if len(msg) < 8 { + return false + } + msg[6], msg[7] = packUint16(i) + return true +} + +// rawSetsNsLen sets the lenght of the authority section. +func rawSetNsLen(msg []byte, i uint16) bool { + if len(msg) < 10 { + return false + } + msg[8], msg[9] = packUint16(i) + return true +} + +// rawSetExtraLen sets the lenght of the additional section. +func rawSetExtraLen(msg []byte, i uint16) bool { + if len(msg) < 12 { + return false + } + msg[10], msg[11] = packUint16(i) + return true +} + +// rawSetRdlength sets the rdlength in the header of +// the RR. The offset 'off' must be positioned at the +// start of the header of the RR, 'end' must be the +// end of the RR. +func rawSetRdlength(msg []byte, off, end int) bool { + l := len(msg) +Loop: + for { + if off+1 > l { + return false + } + c := int(msg[off]) + off++ + switch c & 0xC0 { + case 0x00: + if c == 0x00 { + // End of the domainname + break Loop + } + if off+c > l { + return false + } + off += c + + case 0xC0: + // pointer, next byte included, ends domainname + off++ + break Loop + } + } + // The domainname has been seen, we at the start of the fixed part in the header. + // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. + off += 2 + 2 + 4 + if off+2 > l { + return false + } + //off+1 is the end of the header, 'end' is the end of the rr + //so 'end' - 'off+2' is the length of the rdata + rdatalen := end - (off + 2) + if rdatalen > 0xFFFF { + return false + } + msg[off], msg[off+1] = packUint16(uint16(rdatalen)) + return true +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/scanner.go b/Godeps/_workspace/src/github.com/miekg/dns/scanner.go new file mode 100644 index 00000000000..c29bc2f388e --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/scanner.go @@ -0,0 +1,43 @@ +package dns + +// Implement a simple scanner, return a byte stream from an io reader. + +import ( + "bufio" + "io" + "text/scanner" +) + +type scan struct { + src *bufio.Reader + position scanner.Position + eof bool // Have we just seen a eof +} + +func scanInit(r io.Reader) *scan { + s := new(scan) + s.src = bufio.NewReader(r) + s.position.Line = 1 + return s +} + +// tokenText returns the next byte from the input +func (s *scan) tokenText() (byte, error) { + c, err := s.src.ReadByte() + if err != nil { + return c, err + } + // delay the newline handling until the next token is delivered, + // fixes off-by-one errors when reporting a parse error. + if s.eof == true { + s.position.Line++ + s.position.Column = 0 + s.eof = false + } + if c == '\n' { + s.eof = true + return c, nil + } + s.position.Column++ + return c, nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/server.go b/Godeps/_workspace/src/github.com/miekg/dns/server.go new file mode 100644 index 00000000000..c250ccc26a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/server.go @@ -0,0 +1,626 @@ +// DNS server implementation. + +package dns + +import ( + "bytes" + "io" + "net" + "sync" + "time" +) + +type Handler interface { + ServeDNS(w ResponseWriter, r *Msg) +} + +// A ResponseWriter interface is used by an DNS handler to +// construct an DNS response. +type ResponseWriter interface { + // LocalAddr returns the net.Addr of the server + LocalAddr() net.Addr + // RemoteAddr returns the net.Addr of the client that sent the current request. + RemoteAddr() net.Addr + // WriteMsg writes a reply back to the client. + WriteMsg(*Msg) error + // Write writes a raw buffer back to the client. + Write([]byte) (int, error) + // Close closes the connection. + Close() error + // TsigStatus returns the status of the Tsig. + TsigStatus() error + // TsigTimersOnly sets the tsig timers only boolean. + TsigTimersOnly(bool) + // Hijack lets the caller take over the connection. + // After a call to Hijack(), the DNS package will not do anything with the connection. + Hijack() +} + +type response struct { + hijacked bool // connection has been hijacked by handler + tsigStatus error + tsigTimersOnly bool + tsigRequestMAC string + tsigSecret map[string]string // the tsig secrets + udp *net.UDPConn // i/o connection if UDP was used + tcp *net.TCPConn // i/o connection if TCP was used + udpSession *sessionUDP // oob data to get egress interface right + remoteAddr net.Addr // address of the client +} + +// ServeMux is an DNS request multiplexer. It matches the +// zone name of each incoming request against a list of +// registered patterns add calls the handler for the pattern +// that most closely matches the zone name. ServeMux is DNSSEC aware, meaning +// that queries for the DS record are redirected to the parent zone (if that +// is also registered), otherwise the child gets the query. +// ServeMux is also safe for concurrent access from multiple goroutines. +type ServeMux struct { + z map[string]Handler + m *sync.RWMutex +} + +// NewServeMux allocates and returns a new ServeMux. +func NewServeMux() *ServeMux { return &ServeMux{z: make(map[string]Handler), m: new(sync.RWMutex)} } + +// DefaultServeMux is the default ServeMux used by Serve. +var DefaultServeMux = NewServeMux() + +// The HandlerFunc type is an adapter to allow the use of +// ordinary functions as DNS handlers. If f is a function +// with the appropriate signature, HandlerFunc(f) is a +// Handler object that calls f. +type HandlerFunc func(ResponseWriter, *Msg) + +// ServerDNS calls f(w, r) +func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) { + f(w, r) +} + +// FailedHandler returns a HandlerFunc that returns SERVFAIL for every request it gets. +func HandleFailed(w ResponseWriter, r *Msg) { + m := new(Msg) + m.SetRcode(r, RcodeServerFailure) + // does not matter if this write fails + w.WriteMsg(m) +} + +func failedHandler() Handler { return HandlerFunc(HandleFailed) } + +// ListenAndServe Starts a server on addresss and network speficied. Invoke handler +// for incoming queries. +func ListenAndServe(addr string, network string, handler Handler) error { + server := &Server{Addr: addr, Net: network, Handler: handler} + return server.ListenAndServe() +} + +// ActivateAndServe activates a server with a listener from systemd, +// l and p should not both be non-nil. +// If both l and p are not nil only p will be used. +// Invoke handler for incoming queries. +func ActivateAndServe(l net.Listener, p net.PacketConn, handler Handler) error { + server := &Server{Listener: l, PacketConn: p, Handler: handler} + return server.ActivateAndServe() +} + +func (mux *ServeMux) match(q string, t uint16) Handler { + mux.m.RLock() + defer mux.m.RUnlock() + var handler Handler + b := make([]byte, len(q)) // worst case, one label of length q + off := 0 + end := false + for { + l := len(q[off:]) + for i := 0; i < l; i++ { + b[i] = q[off+i] + if b[i] >= 'A' && b[i] <= 'Z' { + b[i] |= ('a' - 'A') + } + } + if h, ok := mux.z[string(b[:l])]; ok { // 'causes garbage, might want to change the map key + if t != TypeDS { + return h + } else { + // Continue for DS to see if we have a parent too, if so delegeate to the parent + handler = h + } + } + off, end = NextLabel(q, off) + if end { + break + } + } + // Wildcard match, if we have found nothing try the root zone as a last resort. + if h, ok := mux.z["."]; ok { + return h + } + return handler +} + +// Handle adds a handler to the ServeMux for pattern. +func (mux *ServeMux) Handle(pattern string, handler Handler) { + if pattern == "" { + panic("dns: invalid pattern " + pattern) + } + mux.m.Lock() + mux.z[Fqdn(pattern)] = handler + mux.m.Unlock() +} + +// Handle adds a handler to the ServeMux for pattern. +func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { + mux.Handle(pattern, HandlerFunc(handler)) +} + +// HandleRemove deregistrars the handler specific for pattern from the ServeMux. +func (mux *ServeMux) HandleRemove(pattern string) { + if pattern == "" { + panic("dns: invalid pattern " + pattern) + } + // don't need a mutex here, because deleting is OK, even if the + // entry is note there. + delete(mux.z, Fqdn(pattern)) +} + +// ServeDNS dispatches the request to the handler whose +// pattern most closely matches the request message. If DefaultServeMux +// is used the correct thing for DS queries is done: a possible parent +// is sought. +// If no handler is found a standard SERVFAIL message is returned +// If the request message does not have exactly one question in the +// question section a SERVFAIL is returned, unlesss Unsafe is true. +func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) { + var h Handler + if len(request.Question) < 1 { // allow more than one question + h = failedHandler() + } else { + if h = mux.match(request.Question[0].Name, request.Question[0].Qtype); h == nil { + h = failedHandler() + } + } + h.ServeDNS(w, request) +} + +// Handle registers the handler with the given pattern +// in the DefaultServeMux. The documentation for +// ServeMux explains how patterns are matched. +func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } + +// HandleRemove deregisters the handle with the given pattern +// in the DefaultServeMux. +func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) } + +// HandleFunc registers the handler function with the given pattern +// in the DefaultServeMux. +func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { + DefaultServeMux.HandleFunc(pattern, handler) +} + +// A Server defines parameters for running an DNS server. +type Server struct { + // Address to listen on, ":dns" if empty. + Addr string + // if "tcp" it will invoke a TCP listener, otherwise an UDP one. + Net string + // TCP Listener to use, this is to aid in systemd's socket activation. + Listener net.Listener + // UDP "Listener" to use, this is to aid in systemd's socket activation. + PacketConn net.PacketConn + // Handler to invoke, dns.DefaultServeMux if nil. + Handler Handler + // Default buffer size to use to read incoming UDP messages. If not set + // it defaults to MinMsgSize (512 B). + UDPSize int + // The net.Conn.SetReadTimeout value for new connections, defaults to 2 * time.Second. + ReadTimeout time.Duration + // The net.Conn.SetWriteTimeout value for new connections, defaults to 2 * time.Second. + WriteTimeout time.Duration + // TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966). + IdleTimeout func() time.Duration + // Secret(s) for Tsig map[]. + TsigSecret map[string]string + // Unsafe instructs the server to disregard any sanity checks and directly hand the message to + // the handler. It will specfically not check if the query has the QR bit not set. + Unsafe bool + // If NotifyStartedFunc is set is is called, once the server has started listening. + NotifyStartedFunc func() + + // For graceful shutdown. + stopUDP chan bool + stopTCP chan bool + wgUDP sync.WaitGroup + wgTCP sync.WaitGroup + + // make start/shutdown not racy + lock sync.Mutex + started bool +} + +// ListenAndServe starts a nameserver on the configured address in *Server. +func (srv *Server) ListenAndServe() error { + srv.lock.Lock() + if srv.started { + return &Error{err: "server already started"} + } + srv.stopUDP, srv.stopTCP = make(chan bool), make(chan bool) + srv.started = true + srv.lock.Unlock() + addr := srv.Addr + if addr == "" { + addr = ":domain" + } + if srv.UDPSize == 0 { + srv.UDPSize = MinMsgSize + } + switch srv.Net { + case "tcp", "tcp4", "tcp6": + a, e := net.ResolveTCPAddr(srv.Net, addr) + if e != nil { + return e + } + l, e := net.ListenTCP(srv.Net, a) + if e != nil { + return e + } + return srv.serveTCP(l) + case "udp", "udp4", "udp6": + a, e := net.ResolveUDPAddr(srv.Net, addr) + if e != nil { + return e + } + l, e := net.ListenUDP(srv.Net, a) + if e != nil { + return e + } + if e := setUDPSocketOptions(l); e != nil { + return e + } + return srv.serveUDP(l) + } + return &Error{err: "bad network"} +} + +// ActivateAndServe starts a nameserver with the PacketConn or Listener +// configured in *Server. Its main use is to start a server from systemd. +func (srv *Server) ActivateAndServe() error { + srv.lock.Lock() + if srv.started { + return &Error{err: "server already started"} + } + srv.stopUDP, srv.stopTCP = make(chan bool), make(chan bool) + srv.started = true + srv.lock.Unlock() + if srv.PacketConn != nil { + if srv.UDPSize == 0 { + srv.UDPSize = MinMsgSize + } + if t, ok := srv.PacketConn.(*net.UDPConn); ok { + if e := setUDPSocketOptions(t); e != nil { + return e + } + return srv.serveUDP(t) + } + } + if srv.Listener != nil { + if t, ok := srv.Listener.(*net.TCPListener); ok { + return srv.serveTCP(t) + } + } + return &Error{err: "bad listeners"} +} + +// Shutdown gracefully shuts down a server. After a call to Shutdown, ListenAndServe and +// ActivateAndServe will return. All in progress queries are completed before the server +// is taken down. If the Shutdown is taking longer than the reading timeout and error +// is returned. +func (srv *Server) Shutdown() error { + srv.lock.Lock() + if !srv.started { + return &Error{err: "server not started"} + } + srv.started = false + srv.lock.Unlock() + net, addr := srv.Net, srv.Addr + switch { + case srv.Listener != nil: + a := srv.Listener.Addr() + net, addr = a.Network(), a.String() + case srv.PacketConn != nil: + a := srv.PacketConn.LocalAddr() + net, addr = a.Network(), a.String() + } + + fin := make(chan bool) + switch net { + case "tcp", "tcp4", "tcp6": + go func() { + srv.stopTCP <- true + srv.wgTCP.Wait() + fin <- true + }() + + case "udp", "udp4", "udp6": + go func() { + srv.stopUDP <- true + srv.wgUDP.Wait() + fin <- true + }() + } + + c := &Client{Net: net} + go c.Exchange(new(Msg), addr) // extra query to help ReadXXX loop to pass + + select { + case <-time.After(srv.getReadTimeout()): + return &Error{err: "server shutdown is pending"} + case <-fin: + return nil + } +} + +// getReadTimeout is a helper func to use system timeout if server did not intend to change it. +func (srv *Server) getReadTimeout() time.Duration { + rtimeout := dnsTimeout + if srv.ReadTimeout != 0 { + rtimeout = srv.ReadTimeout + } + return rtimeout +} + +// serveTCP starts a TCP listener for the server. +// Each request is handled in a seperate goroutine. +func (srv *Server) serveTCP(l *net.TCPListener) error { + defer l.Close() + + if srv.NotifyStartedFunc != nil { + srv.NotifyStartedFunc() + } + + handler := srv.Handler + if handler == nil { + handler = DefaultServeMux + } + rtimeout := srv.getReadTimeout() + // deadline is not used here + for { + rw, e := l.AcceptTCP() + if e != nil { + continue + } + m, e := srv.readTCP(rw, rtimeout) + select { + case <-srv.stopTCP: + return nil + default: + } + if e != nil { + continue + } + srv.wgTCP.Add(1) + go srv.serve(rw.RemoteAddr(), handler, m, nil, nil, rw) + } + panic("dns: not reached") +} + +// serveUDP starts a UDP listener for the server. +// Each request is handled in a seperate goroutine. +func (srv *Server) serveUDP(l *net.UDPConn) error { + defer l.Close() + + if srv.NotifyStartedFunc != nil { + srv.NotifyStartedFunc() + } + + handler := srv.Handler + if handler == nil { + handler = DefaultServeMux + } + rtimeout := srv.getReadTimeout() + // deadline is not used here + for { + m, s, e := srv.readUDP(l, rtimeout) + select { + case <-srv.stopUDP: + return nil + default: + } + if e != nil { + continue + } + srv.wgUDP.Add(1) + go srv.serve(s.RemoteAddr(), handler, m, l, s, nil) + } + panic("dns: not reached") +} + +// Serve a new connection. +func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *sessionUDP, t *net.TCPConn) { + w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s} + q := 0 + defer func() { + if u != nil { + srv.wgUDP.Done() + } + if t != nil { + srv.wgTCP.Done() + } + }() +Redo: + // Ideally we want use isMsg here before we allocate memory to actually parse the packet. + req := new(Msg) + err := req.Unpack(m) + if err != nil { // Send a FormatError back + x := new(Msg) + x.SetRcodeFormatError(req) + w.WriteMsg(x) + goto Exit + } + if !srv.Unsafe && req.Response { + goto Exit + } + + w.tsigStatus = nil + if w.tsigSecret != nil { + if t := req.IsTsig(); t != nil { + secret := t.Hdr.Name + if _, ok := w.tsigSecret[secret]; !ok { + w.tsigStatus = ErrKeyAlg + } + w.tsigStatus = TsigVerify(m, w.tsigSecret[secret], "", false) + w.tsigTimersOnly = false + w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC + } + } + h.ServeDNS(w, req) // Writes back to the client + +Exit: + if w.hijacked { + return // client calls Close() + } + if u != nil { // UDP, "close" and return + w.Close() + return + } + idleTimeout := tcpIdleTimeout + if srv.IdleTimeout != nil { + idleTimeout = srv.IdleTimeout() + } + m, e := srv.readTCP(w.tcp, idleTimeout) + if e == nil { + q++ + // TODO(miek): make this number configurable? + if q > 128 { // close socket after this many queries + w.Close() + return + } + goto Redo + } + w.Close() + return +} + +func (srv *Server) readTCP(conn *net.TCPConn, timeout time.Duration) ([]byte, error) { + conn.SetReadDeadline(time.Now().Add(timeout)) + l := make([]byte, 2) + n, err := conn.Read(l) + if err != nil || n != 2 { + if err != nil { + return nil, err + } + return nil, ErrShortRead + } + length, _ := unpackUint16(l, 0) + if length == 0 { + return nil, ErrShortRead + } + m := make([]byte, int(length)) + n, err = conn.Read(m[:int(length)]) + if err != nil || n == 0 { + if err != nil { + return nil, err + } + return nil, ErrShortRead + } + i := n + for i < int(length) { + j, err := conn.Read(m[i:int(length)]) + if err != nil { + return nil, err + } + i += j + } + n = i + m = m[:n] + return m, nil +} + +func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *sessionUDP, error) { + conn.SetReadDeadline(time.Now().Add(timeout)) + m := make([]byte, srv.UDPSize) + n, s, e := readFromSessionUDP(conn, m) + if e != nil || n == 0 { + if e != nil { + return nil, nil, e + } + return nil, nil, ErrShortRead + } + m = m[:n] + return m, s, nil +} + +// WriteMsg implements the ResponseWriter.WriteMsg method. +func (w *response) WriteMsg(m *Msg) (err error) { + var data []byte + if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check) + if t := m.IsTsig(); t != nil { + data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly) + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + } + data, err = m.Pack() + if err != nil { + return err + } + _, err = w.Write(data) + return err +} + +// Write implements the ResponseWriter.Write method. +func (w *response) Write(m []byte) (int, error) { + switch { + case w.udp != nil: + n, err := writeToSessionUDP(w.udp, m, w.udpSession) + return n, err + case w.tcp != nil: + lm := len(m) + if lm < 2 { + return 0, io.ErrShortBuffer + } + if lm > MaxMsgSize { + return 0, &Error{err: "message too large"} + } + l := make([]byte, 2, 2+lm) + l[0], l[1] = packUint16(uint16(lm)) + m = append(l, m...) + + n, err := io.Copy(w.tcp, bytes.NewReader(m)) + return int(n), err + } + panic("not reached") +} + +// LocalAddr implements the ResponseWriter.LocalAddr method. +func (w *response) LocalAddr() net.Addr { + if w.tcp != nil { + return w.tcp.LocalAddr() + } + return w.udp.LocalAddr() +} + +// RemoteAddr implements the ResponseWriter.RemoteAddr method. +func (w *response) RemoteAddr() net.Addr { return w.remoteAddr } + +// TsigStatus implements the ResponseWriter.TsigStatus method. +func (w *response) TsigStatus() error { return w.tsigStatus } + +// TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method. +func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b } + +// Hijack implements the ResponseWriter.Hijack method. +func (w *response) Hijack() { w.hijacked = true } + +// Close implements the ResponseWriter.Close method +func (w *response) Close() error { + // Can't close the udp conn, as that is actually the listener. + if w.tcp != nil { + e := w.tcp.Close() + w.tcp = nil + return e + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/server_test.go b/Godeps/_workspace/src/github.com/miekg/dns/server_test.go new file mode 100644 index 00000000000..5efb1cf8973 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/server_test.go @@ -0,0 +1,401 @@ +package dns + +import ( + "fmt" + "net" + "runtime" + "sync" + "testing" +) + +func HelloServer(w ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + + 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) +} + +func AnotherHelloServer(w ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + + 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 example"}} + w.WriteMsg(m) +} + +func RunLocalUDPServer(laddr string) (*Server, string, error) { + pc, err := net.ListenPacket("udp", laddr) + if err != nil { + return nil, "", err + } + server := &Server{PacketConn: pc} + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + + go func() { + server.ActivateAndServe() + pc.Close() + }() + + waitLock.Lock() + return server, pc.LocalAddr().String(), nil +} + +func RunLocalUDPServerUnsafe(laddr string) (*Server, string, error) { + pc, err := net.ListenPacket("udp", laddr) + if err != nil { + return nil, "", err + } + server := &Server{PacketConn: pc, Unsafe: true} + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + + go func() { + server.ActivateAndServe() + pc.Close() + }() + + waitLock.Lock() + return server, pc.LocalAddr().String(), nil +} + +func RunLocalTCPServer(laddr string) (*Server, string, error) { + l, err := net.Listen("tcp", laddr) + if err != nil { + return nil, "", err + } + + server := &Server{Listener: l} + + waitLock := sync.Mutex{} + waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock + + go func() { + server.ActivateAndServe() + l.Close() + }() + + waitLock.Lock() + return server, l.Addr().String(), nil +} + +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("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", 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.Log("failed to exchange miek.nl", err) + t.Fatal() + } + txt := r.Extra[0].(*TXT).Txt[0] + if txt != "Hello world" { + t.Log("Unexpected result for miek.nl", txt, "!= Hello world") + t.Fail() + } + + m.SetQuestion("example.com.", TypeTXT) + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Log("failed to exchange example.com", err) + t.Fatal() + } + txt = r.Extra[0].(*TXT).Txt[0] + if txt != "Hello example" { + t.Log("Unexpected result for example.com", txt, "!= Hello example") + t.Fail() + } + + // Test Mixes cased as noticed by Ask. + m.SetQuestion("eXaMplE.cOm.", TypeTXT) + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Log("failed to exchange eXaMplE.cOm", err) + t.Fail() + } + txt = r.Extra[0].(*TXT).Txt[0] + if txt != "Hello example" { + t.Log("Unexpected result for example.com", txt, "!= Hello example") + t.Fail() + } +} + +func BenchmarkServe(b *testing.B) { + b.StopTimer() + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + a := runtime.GOMAXPROCS(4) + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + b.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + c := new(Client) + m := new(Msg) + m.SetQuestion("miek.nl", TypeSOA) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, addrstr) + } + runtime.GOMAXPROCS(a) +} + +func benchmarkServe6(b *testing.B) { + b.StopTimer() + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + a := runtime.GOMAXPROCS(4) + s, addrstr, err := RunLocalUDPServer("[::1]:0") + if err != nil { + b.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + c := new(Client) + m := new(Msg) + m.SetQuestion("miek.nl", TypeSOA) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, addrstr) + } + runtime.GOMAXPROCS(a) +} + +func HelloServerCompress(w ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + 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"}} + m.Compress = true + w.WriteMsg(m) +} + +func BenchmarkServeCompress(b *testing.B) { + b.StopTimer() + HandleFunc("miek.nl.", HelloServerCompress) + defer HandleRemove("miek.nl.") + a := runtime.GOMAXPROCS(4) + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + b.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + c := new(Client) + m := new(Msg) + m.SetQuestion("miek.nl", TypeSOA) + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, addrstr) + } + runtime.GOMAXPROCS(a) +} + +func TestDotAsCatchAllWildcard(t *testing.T) { + mux := NewServeMux() + mux.Handle(".", HandlerFunc(HelloServer)) + mux.Handle("example.com.", HandlerFunc(AnotherHelloServer)) + + handler := mux.match("www.miek.nl.", TypeTXT) + if handler == nil { + t.Error("wildcard match failed") + } + + handler = mux.match("www.example.com.", TypeTXT) + if handler == nil { + t.Error("example.com match failed") + } + + handler = mux.match("a.www.example.com.", TypeTXT) + if handler == nil { + t.Error("a.www.example.com match failed") + } + + handler = mux.match("boe.", TypeTXT) + if handler == nil { + t.Error("boe. match failed") + } +} + +func TestCaseFolding(t *testing.T) { + mux := NewServeMux() + mux.Handle("_udp.example.com.", HandlerFunc(HelloServer)) + + handler := mux.match("_dns._udp.example.com.", TypeSRV) + if handler == nil { + t.Error("case sensitive characters folded") + } + + handler = mux.match("_DNS._UDP.EXAMPLE.COM.", TypeSRV) + if handler == nil { + t.Error("case insensitive characters not folded") + } +} + +func TestRootServer(t *testing.T) { + mux := NewServeMux() + mux.Handle(".", HandlerFunc(HelloServer)) + + handler := mux.match(".", TypeNS) + if handler == nil { + t.Error("root match failed") + } +} + +type maxRec struct { + max int + sync.RWMutex +} + +var M = new(maxRec) + +func HelloServerLargeResponse(resp ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + m.Authoritative = true + m1 := 0 + M.RLock() + m1 = M.max + M.RUnlock() + for i := 0; i < m1; i++ { + aRec := &A{ + Hdr: RR_Header{ + Name: req.Question[0].Name, + Rrtype: TypeA, + Class: ClassINET, + Ttl: 0, + }, + A: net.ParseIP(fmt.Sprintf("127.0.0.%d", i+1)).To4(), + } + m.Answer = append(m.Answer, aRec) + } + resp.WriteMsg(m) +} + +func TestServingLargeResponses(t *testing.T) { + HandleFunc("example.", HelloServerLargeResponse) + defer HandleRemove("example.") + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + // Create request + m := new(Msg) + m.SetQuestion("web.service.example.", TypeANY) + + c := new(Client) + c.Net = "udp" + M.Lock() + M.max = 2 + M.Unlock() + _, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Logf("failed to exchange: %s", err.Error()) + t.Fail() + } + // This must fail + M.Lock() + M.max = 20 + M.Unlock() + _, _, err = c.Exchange(m, addrstr) + if err == nil { + t.Logf("failed to fail exchange, this should generate packet error") + t.Fail() + } + // But this must work again + c.UDPSize = 7000 + _, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Logf("failed to exchange: %s", err.Error()) + t.Fail() + } +} + +func TestServingResponse(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + HandleFunc("miek.nl.", HelloServer) + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + + c := new(Client) + m := new(Msg) + m.SetQuestion("miek.nl.", TypeTXT) + m.Response = false + _, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Log("failed to exchange", err) + t.Fatal() + } + m.Response = true + _, _, err = c.Exchange(m, addrstr) + if err == nil { + t.Log("exchanged response message") + t.Fatal() + } + + s.Shutdown() + s, addrstr, err = RunLocalUDPServerUnsafe("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + defer s.Shutdown() + + m.Response = true + _, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Log("could exchanged response message in Unsafe mode") + t.Fatal() + } +} + +func TestShutdownTCP(t *testing.T) { + s, _, err := RunLocalTCPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + err = s.Shutdown() + if err != nil { + t.Errorf("Could not shutdown test TCP server, %s", err) + } +} + +func TestShutdownUDP(t *testing.T) { + s, _, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to run test server: %s", err) + } + err = s.Shutdown() + if err != nil { + t.Errorf("Could not shutdown test UDP server, %s", err) + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/sig0.go b/Godeps/_workspace/src/github.com/miekg/dns/sig0.go new file mode 100644 index 00000000000..d96b31be06b --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/sig0.go @@ -0,0 +1,262 @@ +// SIG(0) +// +// From RFC 2931: +// +// SIG(0) provides protection for DNS transactions and requests .... +// ... protection for glue records, DNS requests, protection for message headers +// 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, ECDSAP384SHA384, RSASHA1, RSASHA256 and +// RSASHA512. +// +// Signing subsequent messages in multi-message sessions is not implemented. +// +package dns + +import ( + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "math/big" + "strings" + "time" +) + +// Sign signs a dns.Msg. It fills the signature with the appropriate data. +// The SIG record should have the SignerName, KeyTag, Algorithm, Inception +// and Expiration set. +func (rr *SIG) Sign(k PrivateKey, m *Msg) ([]byte, error) { + if k == nil { + return nil, ErrPrivKey + } + if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + return nil, ErrKey + } + rr.Header().Rrtype = TypeSIG + rr.Header().Class = ClassANY + rr.Header().Ttl = 0 + rr.Header().Name = "." + rr.OrigTtl = 0 + rr.TypeCovered = 0 + rr.Labels = 0 + + buf := make([]byte, m.Len()+rr.len()) + mbuf, err := m.PackBuffer(buf) + if err != nil { + return nil, err + } + if &buf[0] != &mbuf[0] { + return nil, ErrBuf + } + off, err := PackRR(rr, buf, len(mbuf), nil, false) + if err != nil { + return nil, err + } + buf = buf[:off:cap(buf)] + var hash crypto.Hash + var intlen int + switch rr.Algorithm { + case DSA, RSASHA1: + hash = crypto.SHA1 + case RSASHA256, ECDSAP256SHA256: + hash = crypto.SHA256 + intlen = 32 + case ECDSAP384SHA384: + hash = crypto.SHA384 + intlen = 48 + case RSASHA512: + hash = crypto.SHA512 + default: + return nil, ErrAlg + } + hasher := hash.New() + // Write SIG rdata + hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) + // Write message + hasher.Write(buf[:len(mbuf)]) + hashed := hasher.Sum(nil) + + var sig []byte + switch p := k.(type) { + case *dsa.PrivateKey: + t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8) + r1, s1, err := dsa.Sign(rand.Reader, p, hashed) + if err != nil { + return nil, err + } + sig = append(sig, byte(t)) + sig = append(sig, intToBytes(r1, 20)...) + sig = append(sig, intToBytes(s1, 20)...) + case *rsa.PrivateKey: + sig, err = rsa.SignPKCS1v15(rand.Reader, p, hash, hashed) + if err != nil { + return nil, err + } + case *ecdsa.PrivateKey: + r1, s1, err := ecdsa.Sign(rand.Reader, p, hashed) + if err != nil { + return nil, err + } + sig = intToBytes(r1, intlen) + sig = append(sig, intToBytes(s1, intlen)...) + default: + return nil, ErrAlg + } + rr.Signature = toBase64(sig) + buf = append(buf, sig...) + if len(buf) > int(^uint16(0)) { + return nil, ErrBuf + } + // Adjust sig data length + rdoff := len(mbuf) + 1 + 2 + 2 + 4 + rdlen, _ := unpackUint16(buf, rdoff) + rdlen += uint16(len(sig)) + buf[rdoff], buf[rdoff+1] = packUint16(rdlen) + // Adjust additional count + adc, _ := unpackUint16(buf, 10) + adc += 1 + buf[10], buf[11] = packUint16(adc) + return buf, nil +} + +// Verify validates the message buf using the key k. +// It's assumed that buf is a valid message from which rr was unpacked. +func (rr *SIG) Verify(k *KEY, buf []byte) error { + if k == nil { + return ErrKey + } + if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + return ErrKey + } + + var hash crypto.Hash + switch rr.Algorithm { + case DSA, RSASHA1: + hash = crypto.SHA1 + case RSASHA256, ECDSAP256SHA256: + hash = crypto.SHA256 + case ECDSAP384SHA384: + hash = crypto.SHA384 + case RSASHA512: + hash = crypto.SHA512 + default: + return ErrAlg + } + hasher := hash.New() + + buflen := len(buf) + qdc, _ := unpackUint16(buf, 4) + anc, _ := unpackUint16(buf, 6) + auc, _ := unpackUint16(buf, 8) + adc, offset := unpackUint16(buf, 10) + var err error + for i := uint16(0); i < qdc && offset < buflen; i++ { + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip past Type and Class + offset += 2 + 2 + } + for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip past Type, Class and TTL + offset += 2 + 2 + 4 + if offset+1 >= buflen { + continue + } + var rdlen uint16 + rdlen, offset = unpackUint16(buf, offset) + offset += int(rdlen) + } + if offset >= buflen { + return &Error{err: "overflowing unpacking signed message"} + } + + // offset should be just prior to SIG + bodyend := offset + // owner name SHOULD be root + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip Type, Class, TTL, RDLen + offset += 2 + 2 + 4 + 2 + sigstart := offset + // Skip Type Covered, Algorithm, Labels, Original TTL + offset += 2 + 1 + 1 + 4 + if offset+4+4 >= buflen { + return &Error{err: "overflow unpacking signed message"} + } + expire := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) + offset += 4 + incept := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) + offset += 4 + now := uint32(time.Now().Unix()) + if now < incept || now > expire { + return ErrTime + } + // Skip key tag + offset += 2 + var signername string + signername, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // If key has come from the DNS name compression might + // have mangled the case of the name + if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { + return &Error{err: "signer name doesn't match key name"} + } + sigend := offset + hasher.Write(buf[sigstart:sigend]) + hasher.Write(buf[:10]) + hasher.Write([]byte{ + byte((adc - 1) << 8), + byte(adc - 1), + }) + hasher.Write(buf[12:bodyend]) + + hashed := hasher.Sum(nil) + sig := buf[sigend:] + switch k.Algorithm { + case DSA: + pk := k.publicKeyDSA() + sig = sig[1:] + r := big.NewInt(0) + r.SetBytes(sig[:len(sig)/2]) + s := big.NewInt(0) + s.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 { + return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) + } + case ECDSAP256SHA256, ECDSAP384SHA384: + pk := k.publicKeyCurve() + r := big.NewInt(0) + r.SetBytes(sig[:len(sig)/2]) + s := big.NewInt(0) + s.SetBytes(sig[len(sig)/2:]) + if pk != nil { + if ecdsa.Verify(pk, hashed, r, s) { + return nil + } + return ErrSig + } + } + return ErrKeyAlg +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/sig0_test.go b/Godeps/_workspace/src/github.com/miekg/dns/sig0_test.go new file mode 100644 index 00000000000..6ca76fb848a --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/sig0_test.go @@ -0,0 +1,96 @@ +package dns + +import ( + "testing" + "time" +) + +func TestSIG0(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + m := new(Msg) + m.SetQuestion("example.org.", TypeSOA) + for _, alg := range []uint8{DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} { + algstr := AlgorithmToString[alg] + keyrr := new(KEY) + keyrr.Hdr.Name = algstr + "." + keyrr.Hdr.Rrtype = TypeKEY + keyrr.Hdr.Class = ClassINET + keyrr.Algorithm = alg + keysize := 1024 + switch alg { + case ECDSAP256SHA256: + keysize = 256 + case ECDSAP384SHA384: + keysize = 384 + } + pk, err := keyrr.Generate(keysize) + if err != nil { + t.Logf("Failed to generate key for “%s”: %v", algstr, err) + t.Fail() + continue + } + now := uint32(time.Now().Unix()) + sigrr := new(SIG) + sigrr.Hdr.Name = "." + sigrr.Hdr.Rrtype = TypeSIG + sigrr.Hdr.Class = ClassANY + sigrr.Algorithm = alg + sigrr.Expiration = now + 300 + sigrr.Inception = now - 300 + sigrr.KeyTag = keyrr.KeyTag() + sigrr.SignerName = keyrr.Hdr.Name + mb, err := sigrr.Sign(pk, m) + if err != nil { + t.Logf("Failed to sign message using “%s”: %v", algstr, err) + t.Fail() + continue + } + m := new(Msg) + if err := m.Unpack(mb); err != nil { + t.Logf("Failed to unpack message signed using “%s”: %v", algstr, err) + t.Fail() + continue + } + if len(m.Extra) != 1 { + t.Logf("Missing SIG for message signed using “%s”", algstr) + t.Fail() + continue + } + var sigrrwire *SIG + switch rr := m.Extra[0].(type) { + case *SIG: + sigrrwire = rr + default: + t.Logf("Expected SIG RR, instead: %v", rr) + t.Fail() + continue + } + for _, rr := range []*SIG{sigrr, sigrrwire} { + id := "sigrr" + if rr == sigrrwire { + id = "sigrrwire" + } + if err := rr.Verify(keyrr, mb); err != nil { + t.Logf("Failed to verify “%s” signed SIG(%s): %v", algstr, id, err) + t.Fail() + continue + } + } + mb[13]++ + if err := sigrr.Verify(keyrr, mb); err == nil { + t.Logf("Verify succeeded on an altered message using “%s”", algstr) + t.Fail() + continue + } + sigrr.Expiration = 2 + sigrr.Inception = 1 + mb, _ = sigrr.Sign(pk, m) + if err := sigrr.Verify(keyrr, mb); err == nil { + t.Logf("Verify succeeded on an expired message using “%s”", algstr) + t.Fail() + continue + } + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/singleinflight.go b/Godeps/_workspace/src/github.com/miekg/dns/singleinflight.go new file mode 100644 index 00000000000..9573c7d0b8c --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/singleinflight.go @@ -0,0 +1,57 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted for dns package usage by Miek Gieben. + +package dns + +import "sync" +import "time" + +// call is an in-flight or completed singleflight.Do call +type call struct { + wg sync.WaitGroup + val *Msg + rtt time.Duration + err error + dups int +} + +// singleflight represents a class of work and forms a namespace in +// which units of work can be executed with duplicate suppression. +type singleflight struct { + sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +// The return value shared indicates whether v was given to multiple callers. +func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { + g.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.Unlock() + c.wg.Wait() + return c.val, c.rtt, c.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.Unlock() + + c.val, c.rtt, c.err = fn() + c.wg.Done() + + g.Lock() + delete(g.m, key) + g.Unlock() + + return c.val, c.rtt, c.err, c.dups > 0 +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/tlsa.go b/Godeps/_workspace/src/github.com/miekg/dns/tlsa.go new file mode 100644 index 00000000000..d3bc3b0217f --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/tlsa.go @@ -0,0 +1,84 @@ +package dns + +import ( + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/hex" + "errors" + "io" + "net" + "strconv" +) + +// CertificateToDANE converts a certificate to a hex string as used in the TLSA record. +func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { + switch matchingType { + case 0: + switch selector { + case 0: + return hex.EncodeToString(cert.Raw), nil + case 1: + return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil + } + case 1: + h := sha256.New() + switch selector { + case 0: + return hex.EncodeToString(cert.Raw), nil + case 1: + io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) + return hex.EncodeToString(h.Sum(nil)), nil + } + case 2: + h := sha512.New() + switch selector { + case 0: + return hex.EncodeToString(cert.Raw), nil + case 1: + io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) + return hex.EncodeToString(h.Sum(nil)), nil + } + } + return "", errors.New("dns: bad TLSA MatchingType or TLSA Selector") +} + +// Sign creates a TLSA record from an SSL certificate. +func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { + r.Hdr.Rrtype = TypeTLSA + r.Usage = uint8(usage) + r.Selector = uint8(selector) + r.MatchingType = uint8(matchingType) + + r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) + if err != nil { + return err + } + return nil +} + +// Verify verifies a TLSA record against an SSL certificate. If it is OK +// a nil error is returned. +func (r *TLSA) Verify(cert *x509.Certificate) error { + c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) + if err != nil { + return err // Not also ErrSig? + } + if r.Certificate == c { + return nil + } + return ErrSig // ErrSig, really? +} + +// TLSAName returns the ownername of a TLSA resource record as per the +// rules specified in RFC 6698, Section 3. +func TLSAName(name, service, network string) (string, error) { + if !IsFqdn(name) { + return "", ErrFqdn + } + p, e := net.LookupPort(network, service) + if e != nil { + return "", e + } + return "_" + strconv.Itoa(p) + "_" + network + "." + name, nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/tsig.go b/Godeps/_workspace/src/github.com/miekg/dns/tsig.go new file mode 100644 index 00000000000..2c64ee8d548 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/tsig.go @@ -0,0 +1,378 @@ +// TRANSACTION SIGNATURE +// +// An TSIG or transaction signature adds a HMAC TSIG record to each message sent. +// The supported algorithms include: HmacMD5, HmacSHA1 and HmacSHA256. +// +// Basic use pattern when querying with a TSIG name "axfr." (note that these key names +// must be fully qualified - as they are domain names) and the base64 secret +// "so6ZGir4GPAqINNh9U5c3A==": +// +// c := new(dns.Client) +// c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} +// m := new(dns.Msg) +// m.SetQuestion("miek.nl.", dns.TypeMX) +// m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) +// ... +// // When sending the TSIG RR is calculated and filled in before sending +// +// When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with +// TSIG, this is the basic use pattern. In this example we request an AXFR for +// miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A==" +// and using the server 176.58.119.54: +// +// t := new(dns.Transfer) +// m := new(dns.Msg) +// t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} +// m.SetAxfr("miek.nl.") +// m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) +// c, err := t.In(m, "176.58.119.54:53") +// for r := range c { /* r.RR */ } +// +// You can now read the records from the transfer as they come in. Each envelope is checked with TSIG. +// If something is not correct an error is returned. +// +// Basic use pattern validating and replying to a message that has TSIG set. +// +// server := &dns.Server{Addr: ":53", Net: "udp"} +// server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} +// go server.ListenAndServe() +// dns.HandleFunc(".", handleRequest) +// +// func handleRequest(w dns.ResponseWriter, r *dns.Msg) { +// m := new(Msg) +// m.SetReply(r) +// if r.IsTsig() { +// if w.TsigStatus() == nil { +// // *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 +// } +// } +// w.WriteMsg(m) +// } +package dns + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "hash" + "io" + "strconv" + "strings" + "time" +) + +// HMAC hashing codes. These are transmitted as domain names. +const ( + HmacMD5 = "hmac-md5.sig-alg.reg.int." + HmacSHA1 = "hmac-sha1." + HmacSHA256 = "hmac-sha256." +) + +type TSIG struct { + Hdr RR_Header + Algorithm string `dns:"domain-name"` + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 + MACSize uint16 + MAC string `dns:"size-hex"` + OrigId uint16 + Error uint16 + OtherLen uint16 + OtherData string `dns:"size-hex"` +} + +func (rr *TSIG) Header() *RR_Header { + return &rr.Hdr +} + +// TSIG has no official presentation format, but this will suffice. + +func (rr *TSIG) String() string { + s := "\n;; TSIG PSEUDOSECTION:\n" + s += rr.Hdr.String() + + " " + rr.Algorithm + + " " + tsigTimeToString(rr.TimeSigned) + + " " + strconv.Itoa(int(rr.Fudge)) + + " " + strconv.Itoa(int(rr.MACSize)) + + " " + strings.ToUpper(rr.MAC) + + " " + strconv.Itoa(int(rr.OrigId)) + + " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR + " " + strconv.Itoa(int(rr.OtherLen)) + + " " + rr.OtherData + return s +} + +func (rr *TSIG) len() int { + return rr.Hdr.len() + len(rr.Algorithm) + 1 + 6 + + 4 + len(rr.MAC)/2 + 1 + 6 + len(rr.OtherData)/2 + 1 +} + +func (rr *TSIG) copy() RR { + return &TSIG{*rr.Hdr.copyHeader(), rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData} +} + +// The following values must be put in wireformat, so that the MAC can be calculated. +// RFC 2845, section 3.4.2. TSIG Variables. +type tsigWireFmt struct { + // From RR_Header + Name string `dns:"domain-name"` + Class uint16 + Ttl uint32 + // Rdata of the TSIG + Algorithm string `dns:"domain-name"` + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 + // MACSize, MAC and OrigId excluded + Error uint16 + OtherLen uint16 + OtherData string `dns:"size-hex"` +} + +// If we have the MAC use this type to convert it to wiredata. +// Section 3.4.3. Request MAC +type macWireFmt struct { + MACSize uint16 + MAC string `dns:"size-hex"` +} + +// 3.3. Time values used in TSIG calculations +type timerWireFmt struct { + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 +} + +// TsigGenerate fills out the TSIG record attached to the message. +// The message should contain +// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), +// time fudge (defaults to 300 seconds) and the current time +// The TSIG MAC is saved in that Tsig RR. +// When TsigGenerate is called for the first time requestMAC is set to the empty string and +// timersOnly is false. +// If something goes wrong an error is returned, otherwise it is nil. +func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { + if m.IsTsig() == nil { + panic("dns: TSIG not last RR in additional") + } + // If we barf here, the caller is to blame + rawsecret, err := fromBase64([]byte(secret)) + if err != nil { + return nil, "", err + } + + rr := m.Extra[len(m.Extra)-1].(*TSIG) + m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg + mbuf, err := m.Pack() + if err != nil { + return nil, "", err + } + buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) + + t := new(TSIG) + var h hash.Hash + switch rr.Algorithm { + case HmacMD5: + h = hmac.New(md5.New, []byte(rawsecret)) + case HmacSHA1: + h = hmac.New(sha1.New, []byte(rawsecret)) + case HmacSHA256: + h = hmac.New(sha256.New, []byte(rawsecret)) + default: + return nil, "", ErrKeyAlg + } + io.WriteString(h, string(buf)) + 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, t.len()) + if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { + tbuf = tbuf[:off] // reset to actual size used + } else { + return nil, "", err + } + mbuf = append(mbuf, tbuf...) + rawSetExtraLen(mbuf, uint16(len(m.Extra)+1)) + return mbuf, t.MAC, nil +} + +// TsigVerify verifies the TSIG on a message. +// If the signature does not validate err contains the +// error, otherwise it is nil. +func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { + rawsecret, err := fromBase64([]byte(secret)) + if err != nil { + return err + } + // Strip the TSIG from the incoming msg + stripped, tsig, err := stripTsig(msg) + if err != nil { + return err + } + + msgMAC, err := hex.DecodeString(tsig.MAC) + if err != nil { + 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 + } + + var h hash.Hash + switch tsig.Algorithm { + case HmacMD5: + h = hmac.New(md5.New, rawsecret) + case HmacSHA1: + h = hmac.New(sha1.New, rawsecret) + case HmacSHA256: + h = hmac.New(sha256.New, rawsecret) + default: + return ErrKeyAlg + } + h.Write(buf) + if !hmac.Equal(h.Sum(nil), msgMAC) { + return ErrSig + } + return nil +} + +// Create a wiredata buffer for the MAC calculation. +func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { + var buf []byte + if rr.TimeSigned == 0 { + rr.TimeSigned = uint64(time.Now().Unix()) + } + if rr.Fudge == 0 { + rr.Fudge = 300 // Standard (RFC) default. + } + + if requestMAC != "" { + m := new(macWireFmt) + m.MACSize = uint16(len(requestMAC) / 2) + m.MAC = requestMAC + buf = make([]byte, len(requestMAC)) // long enough + n, _ := PackStruct(m, buf, 0) + buf = buf[:n] + } + + tsigvar := make([]byte, DefaultMsgSize) + if timersOnly { + tsig := new(timerWireFmt) + tsig.TimeSigned = rr.TimeSigned + tsig.Fudge = rr.Fudge + n, _ := PackStruct(tsig, tsigvar, 0) + tsigvar = tsigvar[:n] + } else { + tsig := new(tsigWireFmt) + tsig.Name = strings.ToLower(rr.Hdr.Name) + tsig.Class = ClassANY + tsig.Ttl = rr.Hdr.Ttl + tsig.Algorithm = strings.ToLower(rr.Algorithm) + tsig.TimeSigned = rr.TimeSigned + tsig.Fudge = rr.Fudge + tsig.Error = rr.Error + tsig.OtherLen = rr.OtherLen + tsig.OtherData = rr.OtherData + n, _ := PackStruct(tsig, tsigvar, 0) + tsigvar = tsigvar[:n] + } + + if requestMAC != "" { + x := append(buf, msgbuf...) + buf = append(x, tsigvar...) + } else { + buf = append(msgbuf, tsigvar...) + } + return buf +} + +// Strip the TSIG from the raw message. +func stripTsig(msg []byte) ([]byte, *TSIG, error) { + // Copied from msg.go's Unpack() + // Header. + var dh Header + var err error + dns := new(Msg) + rr := new(TSIG) + off := 0 + tsigoff := 0 + if off, err = UnpackStruct(&dh, msg, off); err != nil { + return nil, nil, err + } + if dh.Arcount == 0 { + return nil, nil, ErrNoSig + } + // Rcode, see msg.go Unpack() + if int(dh.Bits&0xF) == RcodeNotAuth { + return nil, nil, ErrAuth + } + + // Arrays. + dns.Question = make([]Question, dh.Qdcount) + dns.Answer = make([]RR, dh.Ancount) + dns.Ns = make([]RR, dh.Nscount) + dns.Extra = make([]RR, dh.Arcount) + + for i := 0; i < len(dns.Question); i++ { + off, err = UnpackStruct(&dns.Question[i], msg, off) + if err != nil { + return nil, nil, err + } + } + for i := 0; i < len(dns.Answer); i++ { + dns.Answer[i], off, err = UnpackRR(msg, off) + if err != nil { + return nil, nil, err + } + } + for i := 0; i < len(dns.Ns); i++ { + dns.Ns[i], off, err = UnpackRR(msg, off) + if err != nil { + return nil, nil, err + } + } + for i := 0; i < len(dns.Extra); i++ { + tsigoff = off + dns.Extra[i], off, err = UnpackRR(msg, off) + if err != nil { + return nil, nil, err + } + if dns.Extra[i].Header().Rrtype == TypeTSIG { + rr = dns.Extra[i].(*TSIG) + // Adjust Arcount. + arcount, _ := unpackUint16(msg, 10) + msg[10], msg[11] = packUint16(arcount - 1) + break + } + } + if rr == nil { + return nil, nil, ErrNoSig + } + return msg[:tsigoff], rr, nil +} + +// Translate the TSIG time signed into a date. There is no +// need for RFC1982 calculations as this date is 48 bits. +func tsigTimeToString(t uint64) string { + ti := time.Unix(int64(t), 0).UTC() + return ti.Format("20060102150405") +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/types.go b/Godeps/_workspace/src/github.com/miekg/dns/types.go new file mode 100644 index 00000000000..16bb181b9b0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/types.go @@ -0,0 +1,1697 @@ +package dns + +import ( + "encoding/base64" + "fmt" + "net" + "strconv" + "strings" + "time" +) + +type ( + Type uint16 // Type is a DNS type. + Class uint16 // Class is a DNS class. + Name string // Name is a DNS domain name. +) + +// Packet formats + +// Wire constants and supported types. +const ( + // valid RR_Header.Rrtype and Question.qtype + TypeNone uint16 = 0 + TypeA uint16 = 1 + TypeNS uint16 = 2 + TypeMD uint16 = 3 + TypeMF uint16 = 4 + TypeCNAME uint16 = 5 + TypeSOA uint16 = 6 + TypeMB uint16 = 7 + TypeMG uint16 = 8 + TypeMR uint16 = 9 + TypeNULL uint16 = 10 + TypeWKS uint16 = 11 + TypePTR uint16 = 12 + TypeHINFO uint16 = 13 + TypeMINFO uint16 = 14 + TypeMX uint16 = 15 + TypeTXT uint16 = 16 + TypeRP uint16 = 17 + TypeAFSDB uint16 = 18 + TypeX25 uint16 = 19 + TypeISDN uint16 = 20 + TypeRT uint16 = 21 + TypeNSAP uint16 = 22 + TypeNSAPPTR uint16 = 23 + TypeSIG uint16 = 24 + TypeKEY uint16 = 25 + TypePX uint16 = 26 + TypeGPOS uint16 = 27 + TypeAAAA uint16 = 28 + TypeLOC uint16 = 29 + TypeNXT uint16 = 30 + TypeEID uint16 = 31 + TypeNIMLOC uint16 = 32 + TypeSRV uint16 = 33 + TypeATMA uint16 = 34 + TypeNAPTR uint16 = 35 + TypeKX uint16 = 36 + TypeCERT uint16 = 37 + TypeDNAME uint16 = 39 + TypeOPT uint16 = 41 // EDNS + TypeDS uint16 = 43 + TypeSSHFP uint16 = 44 + TypeIPSECKEY uint16 = 45 + TypeRRSIG uint16 = 46 + TypeNSEC uint16 = 47 + TypeDNSKEY uint16 = 48 + TypeDHCID uint16 = 49 + TypeNSEC3 uint16 = 50 + TypeNSEC3PARAM uint16 = 51 + TypeTLSA uint16 = 52 + TypeHIP uint16 = 55 + TypeNINFO uint16 = 56 + TypeRKEY uint16 = 57 + TypeTALINK uint16 = 58 + TypeCDS uint16 = 59 + TypeCDNSKEY uint16 = 60 + TypeOPENPGPKEY uint16 = 61 + TypeSPF uint16 = 99 + TypeUINFO uint16 = 100 + TypeUID uint16 = 101 + TypeGID uint16 = 102 + TypeUNSPEC uint16 = 103 + TypeNID uint16 = 104 + TypeL32 uint16 = 105 + TypeL64 uint16 = 106 + TypeLP uint16 = 107 + TypeEUI48 uint16 = 108 + TypeEUI64 uint16 = 109 + + TypeTKEY uint16 = 249 + TypeTSIG uint16 = 250 + // valid Question.Qtype only + TypeIXFR uint16 = 251 + TypeAXFR uint16 = 252 + TypeMAILB uint16 = 253 + TypeMAILA uint16 = 254 + TypeANY uint16 = 255 + + TypeURI uint16 = 256 + TypeCAA uint16 = 257 + TypeTA uint16 = 32768 + TypeDLV uint16 = 32769 + TypeReserved uint16 = 65535 + + // valid Question.Qclass + ClassINET = 1 + ClassCSNET = 2 + ClassCHAOS = 3 + ClassHESIOD = 4 + ClassNONE = 254 + ClassANY = 255 + + // Msg.rcode + RcodeSuccess = 0 + RcodeFormatError = 1 + RcodeServerFailure = 2 + RcodeNameError = 3 + RcodeNotImplemented = 4 + RcodeRefused = 5 + RcodeYXDomain = 6 + RcodeYXRrset = 7 + RcodeNXRrset = 8 + RcodeNotAuth = 9 + RcodeNotZone = 10 + RcodeBadSig = 16 // TSIG + RcodeBadVers = 16 // EDNS0 + RcodeBadKey = 17 + RcodeBadTime = 18 + RcodeBadMode = 19 // TKEY + RcodeBadName = 20 + RcodeBadAlg = 21 + RcodeBadTrunc = 22 // TSIG + + // Opcode + OpcodeQuery = 0 + OpcodeIQuery = 1 + OpcodeStatus = 2 + // There is no 3 + OpcodeNotify = 4 + OpcodeUpdate = 5 +) + +// The wire format for the DNS packet header. +type Header struct { + Id uint16 + Bits uint16 + Qdcount, Ancount, Nscount, Arcount uint16 +} + +const ( + // Header.Bits + _QR = 1 << 15 // query/response (response=1) + _AA = 1 << 10 // authoritative + _TC = 1 << 9 // truncated + _RD = 1 << 8 // recursion desired + _RA = 1 << 7 // recursion available + _Z = 1 << 6 // Z + _AD = 1 << 5 // authticated data + _CD = 1 << 4 // checking disabled + + LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2. + LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2. + + LOC_HOURS = 60 * 1000 + LOC_DEGREES = 60 * LOC_HOURS + + LOC_ALTITUDEBASE = 100000 +) + +// RFC 4398, Section 2.1 +const ( + CertPKIX = 1 + iota + CertSPKI + CertPGP + CertIPIX + CertISPKI + CertIPGP + CertACPKIX + CertIACPKIX + CertURI = 253 + CertOID = 254 +) + +var CertTypeToString = map[uint16]string{ + CertPKIX: "PKIX", + CertSPKI: "SPKI", + CertPGP: "PGP", + CertIPIX: "IPIX", + CertISPKI: "ISPKI", + CertIPGP: "IPGP", + CertACPKIX: "ACPKIX", + CertIACPKIX: "IACPKIX", + CertURI: "URI", + CertOID: "OID", +} + +var StringToCertType = reverseInt16(CertTypeToString) + +// DNS queries. +type Question struct { + Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed) + Qtype uint16 + Qclass uint16 +} + +func (q *Question) String() (s string) { + // prefix with ; (as in dig) + s = ";" + sprintName(q.Name) + "\t" + s += Class(q.Qclass).String() + "\t" + s += " " + Type(q.Qtype).String() + return s +} + +func (q *Question) len() int { + l := len(q.Name) + 1 + return l + 4 +} + +type ANY struct { + Hdr RR_Header + // Does not have any rdata +} + +func (rr *ANY) Header() *RR_Header { return &rr.Hdr } +func (rr *ANY) copy() RR { return &ANY{*rr.Hdr.copyHeader()} } +func (rr *ANY) String() string { return rr.Hdr.String() } +func (rr *ANY) len() int { return rr.Hdr.len() } + +type CNAME struct { + Hdr RR_Header + Target string `dns:"cdomain-name"` +} + +func (rr *CNAME) Header() *RR_Header { return &rr.Hdr } +func (rr *CNAME) copy() RR { return &CNAME{*rr.Hdr.copyHeader(), sprintName(rr.Target)} } +func (rr *CNAME) String() string { return rr.Hdr.String() + rr.Target } +func (rr *CNAME) len() int { return rr.Hdr.len() + len(rr.Target) + 1 } + +type HINFO struct { + Hdr RR_Header + Cpu string + Os string +} + +func (rr *HINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *HINFO) copy() RR { return &HINFO{*rr.Hdr.copyHeader(), rr.Cpu, rr.Os} } +func (rr *HINFO) String() string { return rr.Hdr.String() + rr.Cpu + " " + rr.Os } +func (rr *HINFO) len() int { return rr.Hdr.len() + len(rr.Cpu) + len(rr.Os) } + +type MB struct { + Hdr RR_Header + Mb string `dns:"cdomain-name"` +} + +func (rr *MB) Header() *RR_Header { return &rr.Hdr } +func (rr *MB) copy() RR { return &MB{*rr.Hdr.copyHeader(), sprintName(rr.Mb)} } + +func (rr *MB) String() string { return rr.Hdr.String() + rr.Mb } +func (rr *MB) len() int { return rr.Hdr.len() + len(rr.Mb) + 1 } + +type MG struct { + Hdr RR_Header + Mg string `dns:"cdomain-name"` +} + +func (rr *MG) Header() *RR_Header { return &rr.Hdr } +func (rr *MG) copy() RR { return &MG{*rr.Hdr.copyHeader(), rr.Mg} } +func (rr *MG) len() int { l := len(rr.Mg) + 1; return rr.Hdr.len() + l } +func (rr *MG) String() string { return rr.Hdr.String() + sprintName(rr.Mg) } + +type MINFO struct { + Hdr RR_Header + Rmail string `dns:"cdomain-name"` + Email string `dns:"cdomain-name"` +} + +func (rr *MINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *MINFO) copy() RR { return &MINFO{*rr.Hdr.copyHeader(), rr.Rmail, rr.Email} } + +func (rr *MINFO) String() string { + return rr.Hdr.String() + sprintName(rr.Rmail) + " " + sprintName(rr.Email) +} + +func (rr *MINFO) len() int { + l := len(rr.Rmail) + 1 + n := len(rr.Email) + 1 + return rr.Hdr.len() + l + n +} + +type MR struct { + Hdr RR_Header + Mr string `dns:"cdomain-name"` +} + +func (rr *MR) Header() *RR_Header { return &rr.Hdr } +func (rr *MR) copy() RR { return &MR{*rr.Hdr.copyHeader(), rr.Mr} } +func (rr *MR) len() int { l := len(rr.Mr) + 1; return rr.Hdr.len() + l } + +func (rr *MR) String() string { + return rr.Hdr.String() + sprintName(rr.Mr) +} + +type MF struct { + Hdr RR_Header + Mf string `dns:"cdomain-name"` +} + +func (rr *MF) Header() *RR_Header { return &rr.Hdr } +func (rr *MF) copy() RR { return &MF{*rr.Hdr.copyHeader(), rr.Mf} } +func (rr *MF) len() int { return rr.Hdr.len() + len(rr.Mf) + 1 } + +func (rr *MF) String() string { + return rr.Hdr.String() + sprintName(rr.Mf) +} + +type MD struct { + Hdr RR_Header + Md string `dns:"cdomain-name"` +} + +func (rr *MD) Header() *RR_Header { return &rr.Hdr } +func (rr *MD) copy() RR { return &MD{*rr.Hdr.copyHeader(), rr.Md} } +func (rr *MD) len() int { return rr.Hdr.len() + len(rr.Md) + 1 } + +func (rr *MD) String() string { + return rr.Hdr.String() + sprintName(rr.Md) +} + +type MX struct { + Hdr RR_Header + Preference uint16 + Mx string `dns:"cdomain-name"` +} + +func (rr *MX) Header() *RR_Header { return &rr.Hdr } +func (rr *MX) copy() RR { return &MX{*rr.Hdr.copyHeader(), rr.Preference, rr.Mx} } +func (rr *MX) len() int { l := len(rr.Mx) + 1; return rr.Hdr.len() + l + 2 } + +func (rr *MX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Mx) +} + +type AFSDB struct { + Hdr RR_Header + Subtype uint16 + Hostname string `dns:"cdomain-name"` +} + +func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } +func (rr *AFSDB) copy() RR { return &AFSDB{*rr.Hdr.copyHeader(), rr.Subtype, rr.Hostname} } +func (rr *AFSDB) len() int { l := len(rr.Hostname) + 1; return rr.Hdr.len() + l + 2 } + +func (rr *AFSDB) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Subtype)) + " " + sprintName(rr.Hostname) +} + +type X25 struct { + Hdr RR_Header + PSDNAddress string +} + +func (rr *X25) Header() *RR_Header { return &rr.Hdr } +func (rr *X25) copy() RR { return &X25{*rr.Hdr.copyHeader(), rr.PSDNAddress} } +func (rr *X25) len() int { return rr.Hdr.len() + len(rr.PSDNAddress) + 1 } + +func (rr *X25) String() string { + return rr.Hdr.String() + rr.PSDNAddress +} + +type RT struct { + Hdr RR_Header + Preference uint16 + Host string `dns:"cdomain-name"` +} + +func (rr *RT) Header() *RR_Header { return &rr.Hdr } +func (rr *RT) copy() RR { return &RT{*rr.Hdr.copyHeader(), rr.Preference, rr.Host} } +func (rr *RT) len() int { l := len(rr.Host) + 1; return rr.Hdr.len() + l + 2 } + +func (rr *RT) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Host) +} + +type NS struct { + Hdr RR_Header + Ns string `dns:"cdomain-name"` +} + +func (rr *NS) Header() *RR_Header { return &rr.Hdr } +func (rr *NS) len() int { l := len(rr.Ns) + 1; return rr.Hdr.len() + l } +func (rr *NS) copy() RR { return &NS{*rr.Hdr.copyHeader(), rr.Ns} } + +func (rr *NS) String() string { + return rr.Hdr.String() + sprintName(rr.Ns) +} + +type PTR struct { + Hdr RR_Header + Ptr string `dns:"cdomain-name"` +} + +func (rr *PTR) Header() *RR_Header { return &rr.Hdr } +func (rr *PTR) copy() RR { return &PTR{*rr.Hdr.copyHeader(), rr.Ptr} } +func (rr *PTR) len() int { l := len(rr.Ptr) + 1; return rr.Hdr.len() + l } + +func (rr *PTR) String() string { + return rr.Hdr.String() + sprintName(rr.Ptr) +} + +type RP struct { + Hdr RR_Header + Mbox string `dns:"domain-name"` + Txt string `dns:"domain-name"` +} + +func (rr *RP) Header() *RR_Header { return &rr.Hdr } +func (rr *RP) copy() RR { return &RP{*rr.Hdr.copyHeader(), rr.Mbox, rr.Txt} } +func (rr *RP) len() int { return rr.Hdr.len() + len(rr.Mbox) + 1 + len(rr.Txt) + 1 } + +func (rr *RP) String() string { + return rr.Hdr.String() + rr.Mbox + " " + sprintTxt([]string{rr.Txt}) +} + +type SOA struct { + Hdr RR_Header + Ns string `dns:"cdomain-name"` + Mbox string `dns:"cdomain-name"` + Serial uint32 + Refresh uint32 + Retry uint32 + Expire uint32 + Minttl uint32 +} + +func (rr *SOA) Header() *RR_Header { return &rr.Hdr } +func (rr *SOA) copy() RR { + return &SOA{*rr.Hdr.copyHeader(), rr.Ns, rr.Mbox, rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl} +} + +func (rr *SOA) String() string { + return rr.Hdr.String() + sprintName(rr.Ns) + " " + sprintName(rr.Mbox) + + " " + strconv.FormatInt(int64(rr.Serial), 10) + + " " + strconv.FormatInt(int64(rr.Refresh), 10) + + " " + strconv.FormatInt(int64(rr.Retry), 10) + + " " + strconv.FormatInt(int64(rr.Expire), 10) + + " " + strconv.FormatInt(int64(rr.Minttl), 10) +} + +func (rr *SOA) len() int { + l := len(rr.Ns) + 1 + n := len(rr.Mbox) + 1 + return rr.Hdr.len() + l + n + 20 +} + +type TXT struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *TXT) Header() *RR_Header { return &rr.Hdr } +func (rr *TXT) copy() RR { + cp := make([]string, len(rr.Txt), cap(rr.Txt)) + copy(cp, rr.Txt) + return &TXT{*rr.Hdr.copyHeader(), cp} +} + +func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + +func sprintName(s string) string { + src := []byte(s) + dst := make([]byte, 0, len(src)) + for i := 0; i < len(src); { + if i+1 < len(src) && src[i] == '\\' && src[i+1] == '.' { + dst = append(dst, src[i:i+2]...) + i += 2 + } else { + b, n := nextByte(src, i) + if n == 0 { + i++ // dangling back slash + } else if b == '.' { + dst = append(dst, b) + } else { + dst = appendDomainNameByte(dst, b) + } + i += n + } + } + return string(dst) +} + +func sprintTxt(txt []string) string { + var out []byte + for i, s := range txt { + if i > 0 { + out = append(out, ` "`...) + } else { + out = append(out, '"') + } + bs := []byte(s) + for j := 0; j < len(bs); { + b, n := nextByte(bs, j) + if n == 0 { + break + } + out = appendTXTStringByte(out, b) + j += n + } + out = append(out, '"') + } + return string(out) +} + +func appendDomainNameByte(s []byte, b byte) []byte { + switch b { + case '.', ' ', '\'', '@', ';', '(', ')': // additional chars to escape + return append(s, '\\', b) + } + return appendTXTStringByte(s, b) +} + +func appendTXTStringByte(s []byte, b byte) []byte { + switch b { + case '\t': + return append(s, '\\', 't') + case '\r': + return append(s, '\\', 'r') + case '\n': + return append(s, '\\', 'n') + case '"', '\\': + return append(s, '\\', b) + } + if b < ' ' || b > '~' { + var buf [3]byte + bufs := strconv.AppendInt(buf[:0], int64(b), 10) + s = append(s, '\\') + for i := 0; i < 3-len(bufs); i++ { + s = append(s, '0') + } + for _, r := range bufs { + s = append(s, r) + } + return s + + } + return append(s, b) +} + +func nextByte(b []byte, offset int) (byte, int) { + if offset >= len(b) { + return 0, 0 + } + if b[offset] != '\\' { + // not an escape sequence + return b[offset], 1 + } + switch len(b) - offset { + case 1: // dangling escape + return 0, 0 + case 2, 3: // too short to be \ddd + default: // maybe \ddd + if isDigit(b[offset+1]) && isDigit(b[offset+2]) && isDigit(b[offset+3]) { + return dddToByte(b[offset+1:]), 4 + } + } + // not \ddd, maybe a control char + switch b[offset+1] { + case 't': + return '\t', 2 + case 'r': + return '\r', 2 + case 'n': + return '\n', 2 + default: + return b[offset+1], 2 + } +} + +func (rr *TXT) len() int { + l := rr.Hdr.len() + for _, t := range rr.Txt { + l += len(t) + 1 + } + return l +} + +type SPF struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *SPF) Header() *RR_Header { return &rr.Hdr } +func (rr *SPF) copy() RR { + cp := make([]string, len(rr.Txt), cap(rr.Txt)) + copy(cp, rr.Txt) + return &SPF{*rr.Hdr.copyHeader(), cp} +} + +func (rr *SPF) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + +func (rr *SPF) len() int { + l := rr.Hdr.len() + for _, t := range rr.Txt { + l += len(t) + 1 + } + return l +} + +type SRV struct { + Hdr RR_Header + Priority uint16 + Weight uint16 + Port uint16 + Target string `dns:"domain-name"` +} + +func (rr *SRV) Header() *RR_Header { return &rr.Hdr } +func (rr *SRV) len() int { l := len(rr.Target) + 1; return rr.Hdr.len() + l + 6 } +func (rr *SRV) copy() RR { + return &SRV{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Port, rr.Target} +} + +func (rr *SRV) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Priority)) + " " + + strconv.Itoa(int(rr.Weight)) + " " + + strconv.Itoa(int(rr.Port)) + " " + sprintName(rr.Target) +} + +type NAPTR struct { + Hdr RR_Header + Order uint16 + Preference uint16 + Flags string + Service string + Regexp string + Replacement string `dns:"domain-name"` +} + +func (rr *NAPTR) Header() *RR_Header { return &rr.Hdr } +func (rr *NAPTR) copy() RR { + return &NAPTR{*rr.Hdr.copyHeader(), rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement} +} + +func (rr *NAPTR) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Order)) + " " + + strconv.Itoa(int(rr.Preference)) + " " + + "\"" + rr.Flags + "\" " + + "\"" + rr.Service + "\" " + + "\"" + rr.Regexp + "\" " + + rr.Replacement +} + +func (rr *NAPTR) len() int { + return rr.Hdr.len() + 4 + len(rr.Flags) + 1 + len(rr.Service) + 1 + + len(rr.Regexp) + 1 + len(rr.Replacement) + 1 +} + +// See RFC 4398. +type CERT struct { + Hdr RR_Header + Type uint16 + KeyTag uint16 + Algorithm uint8 + Certificate string `dns:"base64"` +} + +func (rr *CERT) Header() *RR_Header { return &rr.Hdr } +func (rr *CERT) copy() RR { + return &CERT{*rr.Hdr.copyHeader(), rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate} +} + +func (rr *CERT) String() string { + var ( + ok bool + certtype, algorithm string + ) + if certtype, ok = CertTypeToString[rr.Type]; !ok { + certtype = strconv.Itoa(int(rr.Type)) + } + if algorithm, ok = AlgorithmToString[rr.Algorithm]; !ok { + algorithm = strconv.Itoa(int(rr.Algorithm)) + } + return rr.Hdr.String() + certtype + + " " + strconv.Itoa(int(rr.KeyTag)) + + " " + algorithm + + " " + rr.Certificate +} + +func (rr *CERT) len() int { + return rr.Hdr.len() + 5 + + base64.StdEncoding.DecodedLen(len(rr.Certificate)) +} + +// See RFC 2672. +type DNAME struct { + Hdr RR_Header + Target string `dns:"domain-name"` +} + +func (rr *DNAME) Header() *RR_Header { return &rr.Hdr } +func (rr *DNAME) copy() RR { return &DNAME{*rr.Hdr.copyHeader(), rr.Target} } +func (rr *DNAME) len() int { l := len(rr.Target) + 1; return rr.Hdr.len() + l } + +func (rr *DNAME) String() string { + return rr.Hdr.String() + sprintName(rr.Target) +} + +type A struct { + Hdr RR_Header + A net.IP `dns:"a"` +} + +func (rr *A) Header() *RR_Header { return &rr.Hdr } +func (rr *A) copy() RR { return &A{*rr.Hdr.copyHeader(), copyIP(rr.A)} } +func (rr *A) len() int { return rr.Hdr.len() + net.IPv4len } + +func (rr *A) String() string { + if rr.A == nil { + return rr.Hdr.String() + } + return rr.Hdr.String() + rr.A.String() +} + +type AAAA struct { + Hdr RR_Header + AAAA net.IP `dns:"aaaa"` +} + +func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } +func (rr *AAAA) copy() RR { return &AAAA{*rr.Hdr.copyHeader(), copyIP(rr.AAAA)} } +func (rr *AAAA) len() int { return rr.Hdr.len() + net.IPv6len } + +func (rr *AAAA) String() string { + if rr.AAAA == nil { + return rr.Hdr.String() + } + return rr.Hdr.String() + rr.AAAA.String() +} + +type PX struct { + Hdr RR_Header + Preference uint16 + Map822 string `dns:"domain-name"` + Mapx400 string `dns:"domain-name"` +} + +func (rr *PX) Header() *RR_Header { return &rr.Hdr } +func (rr *PX) copy() RR { return &PX{*rr.Hdr.copyHeader(), rr.Preference, rr.Map822, rr.Mapx400} } +func (rr *PX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Map822) + " " + sprintName(rr.Mapx400) +} +func (rr *PX) len() int { return rr.Hdr.len() + 2 + len(rr.Map822) + 1 + len(rr.Mapx400) + 1 } + +type GPOS struct { + Hdr RR_Header + Longitude string + Latitude string + Altitude string +} + +func (rr *GPOS) Header() *RR_Header { return &rr.Hdr } +func (rr *GPOS) copy() RR { return &GPOS{*rr.Hdr.copyHeader(), rr.Longitude, rr.Latitude, rr.Altitude} } +func (rr *GPOS) len() int { + return rr.Hdr.len() + len(rr.Longitude) + len(rr.Latitude) + len(rr.Altitude) + 3 +} +func (rr *GPOS) String() string { + return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude +} + +type LOC struct { + Hdr RR_Header + Version uint8 + Size uint8 + HorizPre uint8 + VertPre uint8 + Latitude uint32 + Longitude uint32 + Altitude uint32 +} + +func (rr *LOC) Header() *RR_Header { return &rr.Hdr } +func (rr *LOC) len() int { return rr.Hdr.len() + 4 + 12 } +func (rr *LOC) copy() RR { + return &LOC{*rr.Hdr.copyHeader(), rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude} +} + +// cmToM takes a cm value expressed in RFC1876 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 { + m *= 10 + } + + return fmt.Sprintf("0.%02d", m) + } + + s := fmt.Sprintf("%d", m) + for e > 2 { + s += "0" + e -= 1 + } + return s +} + +// String returns a string version of a LOC +func (rr *LOC) String() string { + s := rr.Hdr.String() + + lat := rr.Latitude + ns := "N" + if lat > LOC_EQUATOR { + lat = lat - LOC_EQUATOR + } else { + ns = "S" + lat = LOC_EQUATOR - lat + } + h := lat / LOC_DEGREES + lat = lat % LOC_DEGREES + m := lat / LOC_HOURS + lat = lat % LOC_HOURS + s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lat) / 1000), ns) + + lon := rr.Longitude + ew := "E" + if lon > LOC_PRIMEMERIDIAN { + lon = lon - LOC_PRIMEMERIDIAN + } else { + ew = "W" + lon = LOC_PRIMEMERIDIAN - lon + } + h = lon / LOC_DEGREES + lon = lon % LOC_DEGREES + m = lon / LOC_HOURS + lon = lon % LOC_HOURS + s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, (float64(lon) / 1000), ew) + + var alt float64 = float64(rr.Altitude) / 100 + alt -= LOC_ALTITUDEBASE + if rr.Altitude%100 != 0 { + s += fmt.Sprintf("%.2fm ", alt) + } else { + s += fmt.Sprintf("%.0fm ", alt) + } + + s += cmToM((rr.Size&0xf0)>>4, rr.Size&0x0f) + "m " + s += cmToM((rr.HorizPre&0xf0)>>4, rr.HorizPre&0x0f) + "m " + s += cmToM((rr.VertPre&0xf0)>>4, rr.VertPre&0x0f) + "m" + + return s +} + +// SIG is identical to RRSIG and nowadays only used for SIG(0), RFC2931. +type SIG struct { + RRSIG +} + +type RRSIG struct { + Hdr RR_Header + TypeCovered uint16 + Algorithm uint8 + Labels uint8 + OrigTtl uint32 + Expiration uint32 + Inception uint32 + KeyTag uint16 + SignerName string `dns:"domain-name"` + Signature string `dns:"base64"` +} + +func (rr *RRSIG) Header() *RR_Header { return &rr.Hdr } +func (rr *RRSIG) copy() RR { + return &RRSIG{*rr.Hdr.copyHeader(), rr.TypeCovered, rr.Algorithm, rr.Labels, rr.OrigTtl, rr.Expiration, rr.Inception, rr.KeyTag, rr.SignerName, rr.Signature} +} + +func (rr *RRSIG) String() string { + s := rr.Hdr.String() + s += Type(rr.TypeCovered).String() + s += " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.Labels)) + + " " + strconv.FormatInt(int64(rr.OrigTtl), 10) + + " " + TimeToString(rr.Expiration) + + " " + TimeToString(rr.Inception) + + " " + strconv.Itoa(int(rr.KeyTag)) + + " " + sprintName(rr.SignerName) + + " " + rr.Signature + return s +} + +func (rr *RRSIG) len() int { + return rr.Hdr.len() + len(rr.SignerName) + 1 + + base64.StdEncoding.DecodedLen(len(rr.Signature)) + 18 +} + +type NSEC struct { + Hdr RR_Header + NextDomain string `dns:"domain-name"` + TypeBitMap []uint16 `dns:"nsec"` +} + +func (rr *NSEC) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC) copy() RR { + cp := make([]uint16, len(rr.TypeBitMap), cap(rr.TypeBitMap)) + copy(cp, rr.TypeBitMap) + return &NSEC{*rr.Hdr.copyHeader(), rr.NextDomain, cp} +} + +func (rr *NSEC) String() string { + s := rr.Hdr.String() + sprintName(rr.NextDomain) + for i := 0; i < len(rr.TypeBitMap); i++ { + s += " " + Type(rr.TypeBitMap[i]).String() + } + return s +} + +func (rr *NSEC) len() int { + l := rr.Hdr.len() + len(rr.NextDomain) + 1 + lastwindow := uint32(2 ^ 32 + 1) + for _, t := range rr.TypeBitMap { + window := t / 256 + if uint32(window) != lastwindow { + l += 1 + 32 + } + lastwindow = uint32(window) + } + return l +} + +type DLV struct { + DS +} + +type CDS struct { + DS +} + +type DS struct { + Hdr RR_Header + KeyTag uint16 + Algorithm uint8 + DigestType uint8 + Digest string `dns:"hex"` +} + +func (rr *DS) Header() *RR_Header { return &rr.Hdr } +func (rr *DS) len() int { return rr.Hdr.len() + 4 + len(rr.Digest)/2 } +func (rr *DS) copy() RR { + return &DS{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} +} + +func (rr *DS) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.DigestType)) + + " " + strings.ToUpper(rr.Digest) +} + +type KX struct { + Hdr RR_Header + Preference uint16 + Exchanger string `dns:"domain-name"` +} + +func (rr *KX) Header() *RR_Header { return &rr.Hdr } +func (rr *KX) len() int { return rr.Hdr.len() + 2 + len(rr.Exchanger) + 1 } +func (rr *KX) copy() RR { return &KX{*rr.Hdr.copyHeader(), rr.Preference, rr.Exchanger} } + +func (rr *KX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + + " " + sprintName(rr.Exchanger) +} + +type TA struct { + Hdr RR_Header + KeyTag uint16 + Algorithm uint8 + DigestType uint8 + Digest string `dns:"hex"` +} + +func (rr *TA) Header() *RR_Header { return &rr.Hdr } +func (rr *TA) len() int { return rr.Hdr.len() + 4 + len(rr.Digest)/2 } +func (rr *TA) copy() RR { + return &TA{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} +} + +func (rr *TA) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.DigestType)) + + " " + strings.ToUpper(rr.Digest) +} + +type TALINK struct { + Hdr RR_Header + PreviousName string `dns:"domain-name"` + NextName string `dns:"domain-name"` +} + +func (rr *TALINK) Header() *RR_Header { return &rr.Hdr } +func (rr *TALINK) copy() RR { return &TALINK{*rr.Hdr.copyHeader(), rr.PreviousName, rr.NextName} } +func (rr *TALINK) len() int { return rr.Hdr.len() + len(rr.PreviousName) + len(rr.NextName) + 2 } + +func (rr *TALINK) String() string { + return rr.Hdr.String() + + sprintName(rr.PreviousName) + " " + sprintName(rr.NextName) +} + +type SSHFP struct { + Hdr RR_Header + Algorithm uint8 + Type uint8 + FingerPrint string `dns:"hex"` +} + +func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr } +func (rr *SSHFP) len() int { return rr.Hdr.len() + 2 + len(rr.FingerPrint)/2 } +func (rr *SSHFP) copy() RR { + return &SSHFP{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Type, rr.FingerPrint} +} + +func (rr *SSHFP) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.Type)) + + " " + strings.ToUpper(rr.FingerPrint) +} + +type IPSECKEY struct { + Hdr RR_Header + Precedence uint8 + GatewayType uint8 + Algorithm uint8 + Gateway string `dns:"ipseckey"` + PublicKey string `dns:"base64"` +} + +func (rr *IPSECKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *IPSECKEY) copy() RR { + return &IPSECKEY{*rr.Hdr.copyHeader(), rr.Precedence, rr.GatewayType, rr.Algorithm, rr.Gateway, rr.PublicKey} +} + +func (rr *IPSECKEY) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Precedence)) + + " " + strconv.Itoa(int(rr.GatewayType)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + rr.Gateway + + " " + rr.PublicKey +} + +func (rr *IPSECKEY) len() int { + return rr.Hdr.len() + 3 + len(rr.Gateway) + 1 + + base64.StdEncoding.DecodedLen(len(rr.PublicKey)) +} + +type KEY struct { + DNSKEY +} + +type CDNSKEY struct { + DNSKEY +} + +type DNSKEY struct { + Hdr RR_Header + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` +} + +func (rr *DNSKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *DNSKEY) len() int { + return rr.Hdr.len() + 4 + base64.StdEncoding.DecodedLen(len(rr.PublicKey)) +} +func (rr *DNSKEY) copy() RR { + return &DNSKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} +} + +func (rr *DNSKEY) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Protocol)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + rr.PublicKey +} + +type RKEY struct { + Hdr RR_Header + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` +} + +func (rr *RKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *RKEY) len() int { return rr.Hdr.len() + 4 + base64.StdEncoding.DecodedLen(len(rr.PublicKey)) } +func (rr *RKEY) copy() RR { + return &RKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} +} + +func (rr *RKEY) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Protocol)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + rr.PublicKey +} + +type NSAP struct { + Hdr RR_Header + Length uint8 + Nsap string +} + +func (rr *NSAP) Header() *RR_Header { return &rr.Hdr } +func (rr *NSAP) copy() RR { return &NSAP{*rr.Hdr.copyHeader(), rr.Length, rr.Nsap} } +func (rr *NSAP) String() string { return rr.Hdr.String() + strconv.Itoa(int(rr.Length)) + " " + rr.Nsap } +func (rr *NSAP) len() int { return rr.Hdr.len() + 1 + len(rr.Nsap) + 1 } + +type NSAPPTR struct { + Hdr RR_Header + Ptr string `dns:"domain-name"` +} + +func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr } +func (rr *NSAPPTR) copy() RR { return &NSAPPTR{*rr.Hdr.copyHeader(), rr.Ptr} } +func (rr *NSAPPTR) String() string { return rr.Hdr.String() + sprintName(rr.Ptr) } +func (rr *NSAPPTR) len() int { return rr.Hdr.len() + len(rr.Ptr) } + +type NSEC3 struct { + Hdr RR_Header + Hash uint8 + Flags uint8 + Iterations uint16 + SaltLength uint8 + Salt string `dns:"size-hex"` + HashLength uint8 + NextDomain string `dns:"size-base32"` + TypeBitMap []uint16 `dns:"nsec"` +} + +func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC3) copy() RR { + cp := make([]uint16, len(rr.TypeBitMap), cap(rr.TypeBitMap)) + copy(cp, rr.TypeBitMap) + return &NSEC3{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, cp} +} + +func (rr *NSEC3) String() string { + s := rr.Hdr.String() + s += strconv.Itoa(int(rr.Hash)) + + " " + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Iterations)) + + " " + saltToString(rr.Salt) + + " " + rr.NextDomain + for i := 0; i < len(rr.TypeBitMap); i++ { + s += " " + Type(rr.TypeBitMap[i]).String() + } + return s +} + +func (rr *NSEC3) len() int { + l := rr.Hdr.len() + 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1 + lastwindow := uint32(2 ^ 32 + 1) + for _, t := range rr.TypeBitMap { + window := t / 256 + if uint32(window) != lastwindow { + l += 1 + 32 + } + lastwindow = uint32(window) + } + return l +} + +type NSEC3PARAM struct { + Hdr RR_Header + Hash uint8 + Flags uint8 + Iterations uint16 + SaltLength uint8 + Salt string `dns:"hex"` +} + +func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC3PARAM) len() int { return rr.Hdr.len() + 2 + 4 + 1 + len(rr.Salt)/2 } +func (rr *NSEC3PARAM) copy() RR { + return &NSEC3PARAM{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt} +} + +func (rr *NSEC3PARAM) String() string { + s := rr.Hdr.String() + s += strconv.Itoa(int(rr.Hash)) + + " " + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Iterations)) + + " " + saltToString(rr.Salt) + return s +} + +type TKEY struct { + Hdr RR_Header + Algorithm string `dns:"domain-name"` + Inception uint32 + Expiration uint32 + Mode uint16 + Error uint16 + KeySize uint16 + Key string + OtherLen uint16 + OtherData string +} + +func (rr *TKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *TKEY) copy() RR { + return &TKEY{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Inception, rr.Expiration, rr.Mode, rr.Error, rr.KeySize, rr.Key, rr.OtherLen, rr.OtherData} +} + +func (rr *TKEY) String() string { + // It has no presentation format + return "" +} + +func (rr *TKEY) len() int { + return rr.Hdr.len() + len(rr.Algorithm) + 1 + 4 + 4 + 6 + + len(rr.Key) + 2 + len(rr.OtherData) +} + +// RFC3597 represents an unknown/generic RR. +type RFC3597 struct { + Hdr RR_Header + Rdata string `dns:"hex"` +} + +func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr } +func (rr *RFC3597) copy() RR { return &RFC3597{*rr.Hdr.copyHeader(), rr.Rdata} } +func (rr *RFC3597) len() int { return rr.Hdr.len() + len(rr.Rdata)/2 + 2 } + +func (rr *RFC3597) String() string { + // Let's call it a hack + s := rfc3597Header(rr.Hdr) + + s += "\\# " + strconv.Itoa(len(rr.Rdata)/2) + " " + rr.Rdata + return s +} + +func rfc3597Header(h RR_Header) string { + var s string + + s += sprintName(h.Name) + "\t" + s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" + s += "CLASS" + strconv.Itoa(int(h.Class)) + "\t" + s += "TYPE" + strconv.Itoa(int(h.Rrtype)) + "\t" + return s +} + +type URI struct { + Hdr RR_Header + Priority uint16 + Weight uint16 + Target []string `dns:"txt"` +} + +func (rr *URI) Header() *RR_Header { return &rr.Hdr } +func (rr *URI) copy() RR { + cp := make([]string, len(rr.Target), cap(rr.Target)) + copy(cp, rr.Target) + return &URI{*rr.Hdr.copyHeader(), rr.Weight, rr.Priority, cp} +} + +func (rr *URI) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) + + " " + strconv.Itoa(int(rr.Weight)) + sprintTxt(rr.Target) +} + +func (rr *URI) len() int { + l := rr.Hdr.len() + 4 + for _, t := range rr.Target { + l += len(t) + 1 + } + return l +} + +type DHCID struct { + Hdr RR_Header + Digest string `dns:"base64"` +} + +func (rr *DHCID) Header() *RR_Header { return &rr.Hdr } +func (rr *DHCID) copy() RR { return &DHCID{*rr.Hdr.copyHeader(), rr.Digest} } +func (rr *DHCID) String() string { return rr.Hdr.String() + rr.Digest } +func (rr *DHCID) len() int { return rr.Hdr.len() + base64.StdEncoding.DecodedLen(len(rr.Digest)) } + +type TLSA struct { + Hdr RR_Header + Usage uint8 + Selector uint8 + MatchingType uint8 + Certificate string `dns:"hex"` +} + +func (rr *TLSA) Header() *RR_Header { return &rr.Hdr } +func (rr *TLSA) len() int { return rr.Hdr.len() + 3 + len(rr.Certificate)/2 } + +func (rr *TLSA) copy() RR { + return &TLSA{*rr.Hdr.copyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} +} + +func (rr *TLSA) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Usage)) + + " " + strconv.Itoa(int(rr.Selector)) + + " " + strconv.Itoa(int(rr.MatchingType)) + + " " + rr.Certificate +} + +type HIP struct { + Hdr RR_Header + HitLength uint8 + PublicKeyAlgorithm uint8 + PublicKeyLength uint16 + Hit string `dns:"hex"` + PublicKey string `dns:"base64"` + RendezvousServers []string `dns:"domain-name"` +} + +func (rr *HIP) Header() *RR_Header { return &rr.Hdr } +func (rr *HIP) copy() RR { + cp := make([]string, len(rr.RendezvousServers), cap(rr.RendezvousServers)) + copy(cp, rr.RendezvousServers) + return &HIP{*rr.Hdr.copyHeader(), rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, cp} +} + +func (rr *HIP) String() string { + s := rr.Hdr.String() + + strconv.Itoa(int(rr.PublicKeyAlgorithm)) + + " " + rr.Hit + + " " + rr.PublicKey + for _, d := range rr.RendezvousServers { + s += " " + sprintName(d) + } + return s +} + +func (rr *HIP) len() int { + l := rr.Hdr.len() + 4 + + len(rr.Hit)/2 + + base64.StdEncoding.DecodedLen(len(rr.PublicKey)) + for _, d := range rr.RendezvousServers { + l += len(d) + 1 + } + return l +} + +type NINFO struct { + Hdr RR_Header + ZSData []string `dns:"txt"` +} + +func (rr *NINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *NINFO) copy() RR { + cp := make([]string, len(rr.ZSData), cap(rr.ZSData)) + copy(cp, rr.ZSData) + return &NINFO{*rr.Hdr.copyHeader(), cp} +} + +func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) } + +func (rr *NINFO) len() int { + l := rr.Hdr.len() + for _, t := range rr.ZSData { + l += len(t) + 1 + } + return l +} + +type WKS struct { + Hdr RR_Header + Address net.IP `dns:"a"` + Protocol uint8 + BitMap []uint16 `dns:"wks"` +} + +func (rr *WKS) Header() *RR_Header { return &rr.Hdr } +func (rr *WKS) len() int { return rr.Hdr.len() + net.IPv4len + 1 } + +func (rr *WKS) copy() RR { + cp := make([]uint16, len(rr.BitMap), cap(rr.BitMap)) + copy(cp, rr.BitMap) + return &WKS{*rr.Hdr.copyHeader(), copyIP(rr.Address), rr.Protocol, cp} +} + +func (rr *WKS) String() (s string) { + s = rr.Hdr.String() + if rr.Address != nil { + s += rr.Address.String() + } + for i := 0; i < len(rr.BitMap); i++ { + // should lookup the port + s += " " + strconv.Itoa(int(rr.BitMap[i])) + } + return s +} + +type NID struct { + Hdr RR_Header + Preference uint16 + NodeID uint64 +} + +func (rr *NID) Header() *RR_Header { return &rr.Hdr } +func (rr *NID) copy() RR { return &NID{*rr.Hdr.copyHeader(), rr.Preference, rr.NodeID} } +func (rr *NID) len() int { return rr.Hdr.len() + 2 + 8 } + +func (rr *NID) String() string { + s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + node := fmt.Sprintf("%0.16x", rr.NodeID) + s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] + return s +} + +type L32 struct { + Hdr RR_Header + Preference uint16 + Locator32 net.IP `dns:"a"` +} + +func (rr *L32) Header() *RR_Header { return &rr.Hdr } +func (rr *L32) copy() RR { return &L32{*rr.Hdr.copyHeader(), rr.Preference, copyIP(rr.Locator32)} } +func (rr *L32) len() int { return rr.Hdr.len() + net.IPv4len } + +func (rr *L32) String() string { + if rr.Locator32 == nil { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + } + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + + " " + rr.Locator32.String() +} + +type L64 struct { + Hdr RR_Header + Preference uint16 + Locator64 uint64 +} + +func (rr *L64) Header() *RR_Header { return &rr.Hdr } +func (rr *L64) copy() RR { return &L64{*rr.Hdr.copyHeader(), rr.Preference, rr.Locator64} } +func (rr *L64) len() int { return rr.Hdr.len() + 2 + 8 } + +func (rr *L64) String() string { + s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + node := fmt.Sprintf("%0.16X", rr.Locator64) + s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] + return s +} + +type LP struct { + Hdr RR_Header + Preference uint16 + Fqdn string `dns:"domain-name"` +} + +func (rr *LP) Header() *RR_Header { return &rr.Hdr } +func (rr *LP) copy() RR { return &LP{*rr.Hdr.copyHeader(), rr.Preference, rr.Fqdn} } +func (rr *LP) len() int { return rr.Hdr.len() + 2 + len(rr.Fqdn) + 1 } + +func (rr *LP) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Fqdn) +} + +type EUI48 struct { + Hdr RR_Header + Address uint64 `dns:"uint48"` +} + +func (rr *EUI48) Header() *RR_Header { return &rr.Hdr } +func (rr *EUI48) copy() RR { return &EUI48{*rr.Hdr.copyHeader(), rr.Address} } +func (rr *EUI48) String() string { return rr.Hdr.String() + euiToString(rr.Address, 48) } +func (rr *EUI48) len() int { return rr.Hdr.len() + 6 } + +type EUI64 struct { + Hdr RR_Header + Address uint64 +} + +func (rr *EUI64) Header() *RR_Header { return &rr.Hdr } +func (rr *EUI64) copy() RR { return &EUI64{*rr.Hdr.copyHeader(), rr.Address} } +func (rr *EUI64) String() string { return rr.Hdr.String() + euiToString(rr.Address, 64) } +func (rr *EUI64) len() int { return rr.Hdr.len() + 8 } + +// Support in incomplete - just handle it as unknown record +/* +type CAA struct { + Hdr RR_Header + Flag uint8 + Tag string + Value string `dns:"octet"` +} + +func (rr *CAA) Header() *RR_Header { return &rr.Hdr } +func (rr *CAA) copy() RR { return &CAA{*rr.Hdr.copyHeader(), rr.Flag, rr.Tag, rr.Value} } +func (rr *CAA) len() int { return rr.Hdr.len() + 1 + len(rr.Tag) + 1 + len(rr.Value) } + +func (rr *CAA) String() string { + s := rr.Hdr.String() + strconv.FormatInt(int64(rr.Flag), 10) + " " + rr.Tag + s += strconv.QuoteToASCII(rr.Value) + return s +} +*/ + +type UID struct { + Hdr RR_Header + Uid uint32 +} + +func (rr *UID) Header() *RR_Header { return &rr.Hdr } +func (rr *UID) copy() RR { return &UID{*rr.Hdr.copyHeader(), rr.Uid} } +func (rr *UID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Uid), 10) } +func (rr *UID) len() int { return rr.Hdr.len() + 4 } + +type GID struct { + Hdr RR_Header + Gid uint32 +} + +func (rr *GID) Header() *RR_Header { return &rr.Hdr } +func (rr *GID) copy() RR { return &GID{*rr.Hdr.copyHeader(), rr.Gid} } +func (rr *GID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Gid), 10) } +func (rr *GID) len() int { return rr.Hdr.len() + 4 } + +type UINFO struct { + Hdr RR_Header + Uinfo string +} + +func (rr *UINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *UINFO) copy() RR { return &UINFO{*rr.Hdr.copyHeader(), rr.Uinfo} } +func (rr *UINFO) String() string { return rr.Hdr.String() + sprintTxt([]string{rr.Uinfo}) } +func (rr *UINFO) len() int { return rr.Hdr.len() + len(rr.Uinfo) + 1 } + +type EID struct { + Hdr RR_Header + Endpoint string `dns:"hex"` +} + +func (rr *EID) Header() *RR_Header { return &rr.Hdr } +func (rr *EID) copy() RR { return &EID{*rr.Hdr.copyHeader(), rr.Endpoint} } +func (rr *EID) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Endpoint) } +func (rr *EID) len() int { return rr.Hdr.len() + len(rr.Endpoint)/2 } + +type NIMLOC struct { + Hdr RR_Header + Locator string `dns:"hex"` +} + +func (rr *NIMLOC) Header() *RR_Header { return &rr.Hdr } +func (rr *NIMLOC) copy() RR { return &NIMLOC{*rr.Hdr.copyHeader(), rr.Locator} } +func (rr *NIMLOC) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Locator) } +func (rr *NIMLOC) len() int { return rr.Hdr.len() + len(rr.Locator)/2 } + +type OPENPGPKEY struct { + Hdr RR_Header + PublicKey string `dns:"base64"` +} + +func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *OPENPGPKEY) copy() RR { return &OPENPGPKEY{*rr.Hdr.copyHeader(), rr.PublicKey} } +func (rr *OPENPGPKEY) String() string { return rr.Hdr.String() + rr.PublicKey } +func (rr *OPENPGPKEY) len() int { + return rr.Hdr.len() + base64.StdEncoding.DecodedLen(len(rr.PublicKey)) +} + +// 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. +func TimeToString(t uint32) string { + mod := ((int64(t) - time.Now().Unix()) / year68) - 1 + if mod < 0 { + mod = 0 + } + ti := time.Unix(int64(t)-(mod*year68), 0).UTC() + return ti.Format("20060102150405") +} + +// StringToTime translates the RRSIG's incep. and expir. times from +// string values like "20110403154150" to an 32 bit integer. +// It takes serial arithmetic (RFC 1982) into account. +func StringToTime(s string) (uint32, error) { + t, e := time.Parse("20060102150405", s) + if e != nil { + return 0, e + } + mod := (t.Unix() / year68) - 1 + if mod < 0 { + mod = 0 + } + return uint32(t.Unix() - (mod * year68)), nil +} + +// saltToString converts a NSECX salt to uppercase and +// returns "-" when it is empty +func saltToString(s string) string { + if len(s) == 0 { + return "-" + } + return strings.ToUpper(s) +} + +func euiToString(eui uint64, bits int) (hex string) { + switch bits { + case 64: + hex = fmt.Sprintf("%16.16x", eui) + hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + + "-" + hex[8:10] + "-" + hex[10:12] + "-" + hex[12:14] + "-" + hex[14:16] + case 48: + hex = fmt.Sprintf("%12.12x", eui) + hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + + "-" + hex[8:10] + "-" + hex[10:12] + } + return +} + +// copyIP returns a copy of ip. +func copyIP(ip net.IP) net.IP { + p := make(net.IP, len(ip)) + copy(p, ip) + return p +} + +// Map of constructors for each RR type. +var typeToRR = map[uint16]func() RR{ + TypeA: func() RR { return new(A) }, + TypeAAAA: func() RR { return new(AAAA) }, + TypeAFSDB: func() RR { return new(AFSDB) }, + // TypeCAA: func() RR { return new(CAA) }, + TypeCDS: func() RR { return new(CDS) }, + TypeCERT: func() RR { return new(CERT) }, + TypeCNAME: func() RR { return new(CNAME) }, + TypeDHCID: func() RR { return new(DHCID) }, + TypeDLV: func() RR { return new(DLV) }, + TypeDNAME: func() RR { return new(DNAME) }, + TypeKEY: func() RR { return new(KEY) }, + TypeDNSKEY: func() RR { return new(DNSKEY) }, + TypeDS: func() RR { return new(DS) }, + TypeEUI48: func() RR { return new(EUI48) }, + TypeEUI64: func() RR { return new(EUI64) }, + TypeGID: func() RR { return new(GID) }, + TypeGPOS: func() RR { return new(GPOS) }, + TypeEID: func() RR { return new(EID) }, + TypeHINFO: func() RR { return new(HINFO) }, + TypeHIP: func() RR { return new(HIP) }, + TypeKX: func() RR { return new(KX) }, + TypeL32: func() RR { return new(L32) }, + TypeL64: func() RR { return new(L64) }, + TypeLOC: func() RR { return new(LOC) }, + TypeLP: func() RR { return new(LP) }, + TypeMB: func() RR { return new(MB) }, + TypeMD: func() RR { return new(MD) }, + TypeMF: func() RR { return new(MF) }, + TypeMG: func() RR { return new(MG) }, + TypeMINFO: func() RR { return new(MINFO) }, + TypeMR: func() RR { return new(MR) }, + TypeMX: func() RR { return new(MX) }, + TypeNAPTR: func() RR { return new(NAPTR) }, + TypeNID: func() RR { return new(NID) }, + TypeNINFO: func() RR { return new(NINFO) }, + TypeNIMLOC: func() RR { return new(NIMLOC) }, + TypeNS: func() RR { return new(NS) }, + TypeNSAP: func() RR { return new(NSAP) }, + TypeNSAPPTR: func() RR { return new(NSAPPTR) }, + TypeNSEC3: func() RR { return new(NSEC3) }, + TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) }, + TypeNSEC: func() RR { return new(NSEC) }, + TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) }, + TypeOPT: func() RR { return new(OPT) }, + TypePTR: func() RR { return new(PTR) }, + TypeRKEY: func() RR { return new(RKEY) }, + TypeRP: func() RR { return new(RP) }, + TypePX: func() RR { return new(PX) }, + TypeSIG: func() RR { return new(SIG) }, + TypeRRSIG: func() RR { return new(RRSIG) }, + TypeRT: func() RR { return new(RT) }, + TypeSOA: func() RR { return new(SOA) }, + TypeSPF: func() RR { return new(SPF) }, + TypeSRV: func() RR { return new(SRV) }, + TypeSSHFP: func() RR { return new(SSHFP) }, + TypeTA: func() RR { return new(TA) }, + TypeTALINK: func() RR { return new(TALINK) }, + TypeTKEY: func() RR { return new(TKEY) }, + TypeTLSA: func() RR { return new(TLSA) }, + TypeTSIG: func() RR { return new(TSIG) }, + TypeTXT: func() RR { return new(TXT) }, + TypeUID: func() RR { return new(UID) }, + TypeUINFO: func() RR { return new(UINFO) }, + TypeURI: func() RR { return new(URI) }, + TypeWKS: func() RR { return new(WKS) }, + TypeX25: func() RR { return new(X25) }, +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/types_test.go b/Godeps/_workspace/src/github.com/miekg/dns/types_test.go new file mode 100644 index 00000000000..118612946bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/types_test.go @@ -0,0 +1,42 @@ +package dns + +import ( + "testing" +) + +func TestCmToM(t *testing.T) { + s := cmToM(0, 0) + if s != "0.00" { + t.Error("0, 0") + } + + s = cmToM(1, 0) + if s != "0.01" { + t.Error("1, 0") + } + + s = cmToM(3, 1) + if s != "0.30" { + t.Error("3, 1") + } + + s = cmToM(4, 2) + if s != "4" { + t.Error("4, 2") + } + + s = cmToM(5, 3) + if s != "50" { + t.Error("5, 3") + } + + s = cmToM(7, 5) + if s != "7000" { + t.Error("7, 5") + } + + s = cmToM(9, 9) + if s != "90000000" { + t.Error("9, 9") + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/udp.go b/Godeps/_workspace/src/github.com/miekg/dns/udp.go new file mode 100644 index 00000000000..0342543bf73 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/udp.go @@ -0,0 +1,55 @@ +// +build !windows + +package dns + +import ( + "net" + "syscall" +) + +type sessionUDP struct { + raddr *net.UDPAddr + context []byte +} + +func (s *sessionUDP) RemoteAddr() net.Addr { return s.raddr } + +// setUDPSocketOptions sets the UDP socket options. +// This function is implemented on a per platform basis. See udp_*.go for more details +func setUDPSocketOptions(conn *net.UDPConn) error { + sa, err := getUDPSocketName(conn) + if err != nil { + return err + } + switch sa.(type) { + case *syscall.SockaddrInet6: + v6only, err := getUDPSocketOptions6Only(conn) + if err != nil { + return err + } + setUDPSocketOptions6(conn) + if !v6only { + setUDPSocketOptions4(conn) + } + case *syscall.SockaddrInet4: + setUDPSocketOptions4(conn) + } + return nil +} + +// readFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a +// net.UDPAddr. +func readFromSessionUDP(conn *net.UDPConn, b []byte) (int, *sessionUDP, error) { + oob := make([]byte, 40) + n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) + if err != nil { + return n, nil, err + } + return n, &sessionUDP{raddr, oob[:oobn]}, err +} + +// writeToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *sessionUDP instead of a net.Addr. +func writeToSessionUDP(conn *net.UDPConn, b []byte, session *sessionUDP) (int, error) { + n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) + return n, err +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/udp_linux.go b/Godeps/_workspace/src/github.com/miekg/dns/udp_linux.go new file mode 100644 index 00000000000..7a107857e13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/udp_linux.go @@ -0,0 +1,63 @@ +// +build linux + +package dns + +// See: +// * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and +// * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ +// +// Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing +// interface, this might not always be the correct one. This code will make sure the egress +// packet's interface matched the ingress' one. + +import ( + "net" + "syscall" +) + +// setUDPSocketOptions4 prepares the v4 socket for sessions. +func setUDPSocketOptions4(conn *net.UDPConn) error { + file, err := conn.File() + if err != nil { + return err + } + if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { + return err + } + return nil +} + +// setUDPSocketOptions6 prepares the v6 socket for sessions. +func setUDPSocketOptions6(conn *net.UDPConn) error { + file, err := conn.File() + if err != nil { + return err + } + if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { + return err + } + return nil +} + +// getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined +// (dualstack). +func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { + file, err := conn.File() + if err != nil { + return false, err + } + // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections + v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) + if err != nil { + return false, err + } + return v6only == 1, nil +} + +func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { + file, err := conn.File() + if err != nil { + return nil, err + } + return syscall.Getsockname(int(file.Fd())) +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/udp_other.go b/Godeps/_workspace/src/github.com/miekg/dns/udp_other.go new file mode 100644 index 00000000000..c38dd3e7f0e --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/udp_other.go @@ -0,0 +1,17 @@ +// +build !linux + +package dns + +import ( + "net" + "syscall" +) + +// These do nothing. See udp_linux.go for an example of how to implement this. + +// We tried to adhire to some kind of naming scheme. + +func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } +func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } +func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } +func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil } diff --git a/Godeps/_workspace/src/github.com/miekg/dns/udp_windows.go b/Godeps/_workspace/src/github.com/miekg/dns/udp_windows.go new file mode 100644 index 00000000000..4c48723b565 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/udp_windows.go @@ -0,0 +1,34 @@ +// +build windows + +package dns + +import "net" + +type sessionUDP struct { + raddr *net.UDPAddr +} + +// readFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a +// net.UDPAddr. +func readFromSessionUDP(conn *net.UDPConn, b []byte) (int, *sessionUDP, error) { + n, raddr, err := conn.ReadFrom(b) + if err != nil { + return n, nil, err + } + session := &sessionUDP{raddr.(*net.UDPAddr)} + return n, session, err +} + +// writeToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *sessionUDP instead of a net.Addr. +func writeToSessionUDP(conn *net.UDPConn, b []byte, session *sessionUDP) (int, error) { + n, err := conn.WriteTo(b, session.raddr) + return n, err +} + +func (s *sessionUDP) RemoteAddr() net.Addr { return s.raddr } + +// setUDPSocketOptions sets the UDP socket options. +// This function is implemented on a per platform basis. See udp_*.go for more details +func setUDPSocketOptions(conn *net.UDPConn) error { + return nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/update.go b/Godeps/_workspace/src/github.com/miekg/dns/update.go new file mode 100644 index 00000000000..275d4e6cc27 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/update.go @@ -0,0 +1,138 @@ +// DYNAMIC UPDATES +// +// Dynamic updates reuses the DNS message format, but renames three of +// the sections. Question is Zone, Answer is Prerequisite, Authority is +// Update, only the Additional is not renamed. See RFC 2136 for the gory details. +// +// You can set a rather complex set of rules for the existence of absence of +// certain resource records or names in a zone to specify if resource records +// should be added or removed. The table from RFC 2136 supplemented with the Go +// DNS function shows which functions exist to specify the prerequisites. +// +// 3.2.4 - Table Of Metavalues Used In Prerequisite Section +// +// CLASS TYPE RDATA Meaning Function +// -------------------------------------------------------------- +// ANY ANY empty Name is in use dns.NameUsed +// ANY rrset empty RRset exists (value indep) dns.RRsetUsed +// NONE ANY empty Name is not in use dns.NameNotUsed +// NONE rrset empty RRset does not exist dns.RRsetNotUsed +// zone rrset rr RRset exists (value dep) dns.Used +// +// The prerequisite section can also be left empty. +// If you have decided on the prerequisites you can tell what RRs should +// be added or deleted. The next table shows the options you have and +// what functions to call. +// +// 3.4.2.6 - Table Of Metavalues Used In Update Section +// +// CLASS TYPE RDATA Meaning Function +// --------------------------------------------------------------- +// ANY ANY empty Delete all RRsets from name dns.RemoveName +// ANY rrset empty Delete an RRset dns.RemoveRRset +// NONE rrset rr Delete an RR from RRset dns.Remove +// zone rrset rr Add to an RRset dns.Insert +// +package dns + +// NameUsed sets the RRs in the prereq section to +// "Name is in use" RRs. RFC 2136 section 2.4.4. +func (u *Msg) NameUsed(rr []RR) { + u.Answer = make([]RR, len(rr)) + for i, r := range rr { + u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} + } +} + +// NameNotUsed sets the RRs in the prereq section to +// "Name is in not use" RRs. RFC 2136 section 2.4.5. +func (u *Msg) NameNotUsed(rr []RR) { + u.Answer = make([]RR, len(rr)) + for i, r := range rr { + u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}} + } +} + +// Used sets the RRs in the prereq section to +// "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. +func (u *Msg) Used(rr []RR) { + if len(u.Question) == 0 { + panic("dns: empty question section") + } + u.Answer = make([]RR, len(rr)) + for i, r := range rr { + u.Answer[i] = r + u.Answer[i].Header().Class = u.Question[0].Qclass + } +} + +// RRsetUsed sets the RRs in the prereq section to +// "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. +func (u *Msg) RRsetUsed(rr []RR) { + u.Answer = make([]RR, len(rr)) + for i, r := range rr { + u.Answer[i] = r + u.Answer[i].Header().Class = ClassANY + u.Answer[i].Header().Ttl = 0 + u.Answer[i].Header().Rdlength = 0 + } +} + +// RRsetNotUsed sets the RRs in the prereq section to +// "RRset does not exist" RRs. RFC 2136 section 2.4.3. +func (u *Msg) RRsetNotUsed(rr []RR) { + u.Answer = make([]RR, len(rr)) + for i, r := range rr { + u.Answer[i] = r + u.Answer[i].Header().Class = ClassNONE + u.Answer[i].Header().Rdlength = 0 + u.Answer[i].Header().Ttl = 0 + } +} + +// Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. +func (u *Msg) Insert(rr []RR) { + if len(u.Question) == 0 { + panic("dns: empty question section") + } + u.Ns = make([]RR, len(rr)) + for i, r := range rr { + u.Ns[i] = r + u.Ns[i].Header().Class = u.Question[0].Qclass + } +} + +// RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. +func (u *Msg) RemoveRRset(rr []RR) { + m := make(map[RR_Header]struct{}) + u.Ns = make([]RR, 0, len(rr)) + for _, r := range rr { + h := *r.Header().copyHeader() + h.Class = ClassANY + h.Ttl = 0 + h.Rdlength = 0 + if _, ok := m[h]; ok { + continue + } + m[h] = struct{}{} + u.Ns = append(u.Ns, &ANY{h}) + } +} + +// RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 +func (u *Msg) RemoveName(rr []RR) { + u.Ns = make([]RR, len(rr)) + for i, r := range rr { + u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} + } +} + +// Remove creates a dynamic update packet deletes RR from the RRSset, see RFC 2136 section 2.5.4 +func (u *Msg) Remove(rr []RR) { + u.Ns = make([]RR, len(rr)) + for i, r := range rr { + u.Ns[i] = r + u.Ns[i].Header().Class = ClassNONE + u.Ns[i].Header().Ttl = 0 + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/update_test.go b/Godeps/_workspace/src/github.com/miekg/dns/update_test.go new file mode 100644 index 00000000000..fc22536e38e --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/update_test.go @@ -0,0 +1,105 @@ +package dns + +import ( + "bytes" + "testing" +) + +func TestDynamicUpdateParsing(t *testing.T) { + prefix := "example.com. IN " + for _, typ := range TypeToString { + if typ == "CAA" || typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" || + typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" { + continue + } + r, e := NewRR(prefix + typ) + if e != nil { + t.Log("failure to parse: " + prefix + typ) + t.Fail() + } else { + t.Logf("parsed: %s", r.String()) + } + } +} + +func TestDynamicUpdateUnpack(t *testing.T) { + // From https://github.com/miekg/dns/issues/150#issuecomment-62296803 + // It should be an update message for the zone "example.", + // deleting the A RRset "example." and then adding an A record at "example.". + // class ANY, TYPE A + buf := []byte{171, 68, 40, 0, 0, 1, 0, 0, 0, 2, 0, 0, 7, 101, 120, 97, 109, 112, 108, 101, 0, 0, 6, 0, 1, 192, 12, 0, 1, 0, 255, 0, 0, 0, 0, 0, 0, 192, 12, 0, 1, 0, 1, 0, 0, 0, 0, 0, 4, 127, 0, 0, 1} + msg := new(Msg) + err := msg.Unpack(buf) + if err != nil { + t.Log("failed to unpack: " + err.Error() + "\n" + msg.String()) + t.Fail() + } +} + +func TestDynamicUpdateZeroRdataUnpack(t *testing.T) { + m := new(Msg) + rr := &RR_Header{Name: ".", Rrtype: 0, Class: 1, Ttl: ^uint32(0), Rdlength: 0} + m.Answer = []RR{rr, rr, rr, rr, rr} + m.Ns = m.Answer + for n, s := range TypeToString { + rr.Rrtype = n + bytes, err := m.Pack() + if err != nil { + t.Logf("failed to pack %s: %v", s, err) + t.Fail() + continue + } + if err := new(Msg).Unpack(bytes); err != nil { + t.Logf("failed to unpack %s: %v", s, err) + t.Fail() + } + } +} + +func TestRemoveRRset(t *testing.T) { + // Should add a zero data RR in Class ANY with a TTL of 0 + // for each set mentioned in the RRs provided to it. + rr, err := NewRR(". 100 IN A 127.0.0.1") + if err != nil { + t.Fatalf("Error constructing RR: %v", err) + } + m := new(Msg) + m.Ns = []RR{&RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY, Ttl: 0, Rdlength: 0}} + expectstr := m.String() + expect, err := m.Pack() + if err != nil { + t.Fatalf("Error packing expected msg: %v", err) + } + + m.Ns = nil + m.RemoveRRset([]RR{rr}) + actual, err := m.Pack() + if err != nil { + t.Fatalf("Error packing actual msg: %v", err) + } + if !bytes.Equal(actual, expect) { + tmp := new(Msg) + if err := tmp.Unpack(actual); err != nil { + t.Fatalf("Error unpacking actual msg: %v", err) + } + t.Logf("Expected msg:\n%s", expectstr) + t.Logf("Actual msg:\n%v", tmp) + t.Fail() + } + + m.Ns = nil + m.RemoveRRset([]RR{rr, rr}) + actual, err = m.Pack() + if err != nil { + t.Fatalf("Error packing actual msg: %v", err) + } + if !bytes.Equal(actual, expect) { + tmp := new(Msg) + if err := tmp.Unpack(actual); err != nil { + t.Fatalf("Error unpacking actual msg: %v", err) + } + t.Logf("Expected msg:\n%v", expectstr) + t.Logf("Actual msg:\n%v", tmp) + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/xfr.go b/Godeps/_workspace/src/github.com/miekg/dns/xfr.go new file mode 100644 index 00000000000..57bfb1676f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/xfr.go @@ -0,0 +1,236 @@ +package dns + +import ( + "time" +) + +// Envelope is used when doing a zone transfer with a remote server. +type Envelope struct { + RR []RR // The set of RRs in the answer section of the xfr reply message. + Error error // If something went wrong, this contains the error. +} + +// A Transfer defines parameters that are used during a zone transfer. +type Transfer struct { + *Conn + DialTimeout time.Duration // net.DialTimeout (ns), defaults to 2 * 1e9 + ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections (ns), defaults to 2 * 1e9 + WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections (ns), defaults to 2 * 1e9 + TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be fully qualified + tsigTimersOnly bool +} + +// Think we need to away to stop the transfer + +// In performs an incoming transfer with the server in a. +func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { + timeout := dnsTimeout + if t.DialTimeout != 0 { + timeout = t.DialTimeout + } + t.Conn, err = DialTimeout("tcp", a, timeout) + if err != nil { + return nil, err + } + if err := t.WriteMsg(q); err != nil { + return nil, err + } + env = make(chan *Envelope) + go func() { + if q.Question[0].Qtype == TypeAXFR { + go t.inAxfr(q.Id, env) + return + } + if q.Question[0].Qtype == TypeIXFR { + go t.inIxfr(q.Id, env) + return + } + }() + return env, nil +} + +func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { + first := true + defer t.Close() + defer close(c) + timeout := dnsTimeout + if t.ReadTimeout != 0 { + timeout = t.ReadTimeout + } + for { + t.Conn.SetReadDeadline(time.Now().Add(timeout)) + in, err := t.ReadMsg() + if err != nil { + c <- &Envelope{nil, err} + return + } + if id != in.Id { + c <- &Envelope{in.Answer, ErrId} + return + } + if first { + if !isSOAFirst(in) { + c <- &Envelope{in.Answer, ErrSoa} + return + } + first = !first + // only one answer that is SOA, receive more + if len(in.Answer) == 1 { + t.tsigTimersOnly = true + c <- &Envelope{in.Answer, nil} + continue + } + } + + if !first { + t.tsigTimersOnly = true // Subsequent envelopes use this. + if isSOALast(in) { + c <- &Envelope{in.Answer, nil} + return + } + c <- &Envelope{in.Answer, nil} + } + } + panic("dns: not reached") +} + +func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { + serial := uint32(0) // The first serial seen is the current server serial + first := true + defer t.Close() + defer close(c) + timeout := dnsTimeout + if t.ReadTimeout != 0 { + timeout = t.ReadTimeout + } + for { + t.SetReadDeadline(time.Now().Add(timeout)) + in, err := t.ReadMsg() + if err != nil { + c <- &Envelope{in.Answer, err} + return + } + if id != in.Id { + c <- &Envelope{in.Answer, ErrId} + return + } + if first { + // A single SOA RR signals "no changes" + if len(in.Answer) == 1 && isSOAFirst(in) { + c <- &Envelope{in.Answer, nil} + return + } + + // Check if the returned answer is ok + if !isSOAFirst(in) { + c <- &Envelope{in.Answer, ErrSoa} + return + } + // This serial is important + serial = in.Answer[0].(*SOA).Serial + first = !first + } + + // Now we need to check each message for SOA records, to see what we need to do + if !first { + t.tsigTimersOnly = true + // If the last record in the IXFR contains the servers' SOA, we should quit + if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { + if v.Serial == serial { + c <- &Envelope{in.Answer, nil} + return + } + } + c <- &Envelope{in.Answer, nil} + } + } +} + +// Out performs an outgoing transfer with the client connecting in w. +// Basic use pattern: +// +// ch := make(chan *dns.Envelope) +// tr := new(dns.Transfer) +// tr.Out(w, r, ch) +// c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} +// close(ch) +// w.Hijack() +// // w.Close() // Client closes connection +// +// The server is responsible for sending the correct sequence of RRs through the +// channel ch. +func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { + r := new(Msg) + // Compress? + r.SetReply(q) + r.Authoritative = true + + go func() { + for x := range ch { + // assume it fits TODO(miek): fix + r.Answer = append(r.Answer, x.RR...) + if err := w.WriteMsg(r); err != nil { + return + } + } + w.TsigTimersOnly(true) + r.Answer = nil + }() + return nil +} + +// ReadMsg reads a message from the transfer connection t. +func (t *Transfer) ReadMsg() (*Msg, error) { + m := new(Msg) + p := make([]byte, MaxMsgSize) + n, err := t.Read(p) + if err != nil && n == 0 { + return nil, err + } + p = p[:n] + if err := m.Unpack(p); err != nil { + return nil, err + } + if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { + if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { + return m, ErrSecret + } + // Need to work on the original message p, as that was used to calculate the tsig. + err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) + } + return m, err +} + +// WriteMsg writes a message through the transfer connection t. +func (t *Transfer) WriteMsg(m *Msg) (err error) { + var out []byte + if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { + if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { + return ErrSecret + } + out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) + } else { + out, err = m.Pack() + } + if err != nil { + return err + } + if _, err = t.Write(out); err != nil { + return err + } + return nil +} + +func isSOAFirst(in *Msg) bool { + if len(in.Answer) > 0 { + return in.Answer[0].Header().Rrtype == TypeSOA + } + return false +} + +func isSOALast(in *Msg) bool { + if len(in.Answer) > 0 { + return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA + } + return false +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/zgenerate.go b/Godeps/_workspace/src/github.com/miekg/dns/zgenerate.go new file mode 100644 index 00000000000..7f1183e8740 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/zgenerate.go @@ -0,0 +1,157 @@ +package dns + +import ( + "fmt" + "strconv" + "strings" +) + +// Parse the $GENERATE statement as used in BIND9 zones. +// See http://www.zytrax.com/books/dns/ch8/generate.html for instance. +// We are called after '$GENERATE '. After which we expect: +// * the range (12-24/2) +// * lhs (ownername) +// * [[ttl][class]] +// * type +// * rhs (rdata) +// But we are lazy here, only the range is parsed *all* occurences +// of $ after that are interpreted. +// Any error are returned as a string value, the empty string signals +// "no error". +func generate(l lex, c chan lex, t chan *Token, o string) string { + step := 1 + if i := strings.IndexAny(l.token, "/"); i != -1 { + if i+1 == len(l.token) { + return "bad step in $GENERATE range" + } + if s, e := strconv.Atoi(l.token[i+1:]); e != nil { + return "bad step in $GENERATE range" + } else { + if s < 0 { + return "bad step in $GENERATE range" + } + step = s + } + l.token = l.token[:i] + } + sx := strings.SplitN(l.token, "-", 2) + if len(sx) != 2 { + return "bad start-stop in $GENERATE range" + } + start, err := strconv.Atoi(sx[0]) + if err != nil { + return "bad start in $GENERATE range" + } + end, err := strconv.Atoi(sx[1]) + if err != nil { + return "bad stop in $GENERATE range" + } + if end < 0 || start < 0 || end <= start { + return "bad range in $GENERATE range" + } + + <-c // _BLANK + // Create a complete new string, which we then parse again. + s := "" +BuildRR: + l = <-c + if l.value != _NEWLINE && l.value != _EOF { + s += l.token + goto BuildRR + } + for i := start; i <= end; i += step { + var ( + escape bool + dom string + mod string + err string + offset int + ) + + for j := 0; j < len(s); j++ { // No 'range' because we need to jump around + switch s[j] { + case '\\': + if escape { + dom += "\\" + escape = false + continue + } + escape = true + case '$': + mod = "%d" + offset = 0 + if escape { + dom += "$" + escape = false + continue + } + escape = false + if j+1 >= len(s) { // End of the string + dom += fmt.Sprintf(mod, i+offset) + continue + } else { + if s[j+1] == '$' { + dom += "$" + j++ + continue + } + } + // Search for { and } + if s[j+1] == '{' { // Modifier block + sep := strings.Index(s[j+2:], "}") + if sep == -1 { + return "bad modifier in $GENERATE" + } + mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) + if err != "" { + return err + } + j += 2 + sep // Jump to it + } + dom += fmt.Sprintf(mod, i+offset) + default: + if escape { // Pretty useless here + escape = false + continue + } + dom += string(s[j]) + } + } + // Re-parse the RR and send it on the current channel t + rx, e := NewRR("$ORIGIN " + o + "\n" + dom) + if e != nil { + return e.(*ParseError).err + } + t <- &Token{RR: rx} + // Its more efficient to first built the rrlist and then parse it in + // one go! But is this a problem? + } + return "" +} + +// Convert a $GENERATE modifier 0,0,d to something Printf can deal with. +func modToPrintf(s string) (string, int, string) { + xs := strings.SplitN(s, ",", 3) + if len(xs) != 3 { + return "", 0, "bad modifier in $GENERATE" + } + // xs[0] is offset, xs[1] is width, xs[2] is base + if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { + return "", 0, "bad base in $GENERATE" + } + offset, err := strconv.Atoi(xs[0]) + if err != nil { + return "", 0, "bad offset in $GENERATE" + } + width, err := strconv.Atoi(xs[1]) + if err != nil { + return "", offset, "bad width in $GENERATE" + } + switch { + case width < 0: + return "", offset, "bad width in $GENERATE" + case width == 0: + return "%" + xs[1] + xs[2], offset, "" + } + return "%0" + xs[1] + xs[2], offset, "" +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/zscan.go b/Godeps/_workspace/src/github.com/miekg/dns/zscan.go new file mode 100644 index 00000000000..7ba51986953 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/zscan.go @@ -0,0 +1,956 @@ +package dns + +import ( + "io" + "log" + "os" + "strconv" + "strings" +) + +type debugging bool + +const debug debugging = false + +func (d debugging) Printf(format string, args ...interface{}) { + if d { + log.Printf(format, args...) + } +} + +const maxTok = 2048 // Largest token we can return. +const maxUint16 = 1<<16 - 1 + +// Tokinize a RFC 1035 zone file. The tokenizer will normalize it: +// * Add ownernames if they are left blank; +// * Suppress sequences of spaces; +// * Make each RR fit on one line (_NEWLINE is send as last) +// * Handle comments: ; +// * Handle braces - anywhere. +const ( + // Zonefile + _EOF = iota + _STRING + _BLANK + _QUOTE + _NEWLINE + _RRTYPE + _OWNER + _CLASS + _DIRORIGIN // $ORIGIN + _DIRTTL // $TTL + _DIRINCLUDE // $INCLUDE + _DIRGENERATE // $GENERATE + + // Privatekey file + _VALUE + _KEY + + _EXPECT_OWNER_DIR // Ownername + _EXPECT_OWNER_BL // Whitespace after the ownername + _EXPECT_ANY // Expect rrtype, ttl or class + _EXPECT_ANY_NOCLASS // Expect rrtype or ttl + _EXPECT_ANY_NOCLASS_BL // The whitespace after _EXPECT_ANY_NOCLASS + _EXPECT_ANY_NOTTL // Expect rrtype or class + _EXPECT_ANY_NOTTL_BL // Whitespace after _EXPECT_ANY_NOTTL + _EXPECT_RRTYPE // Expect rrtype + _EXPECT_RRTYPE_BL // Whitespace BEFORE rrtype + _EXPECT_RDATA // The first element of the rdata + _EXPECT_DIRTTL_BL // Space after directive $TTL + _EXPECT_DIRTTL // Directive $TTL + _EXPECT_DIRORIGIN_BL // Space after directive $ORIGIN + _EXPECT_DIRORIGIN // Directive $ORIGIN + _EXPECT_DIRINCLUDE_BL // Space after directive $INCLUDE + _EXPECT_DIRINCLUDE // Directive $INCLUDE + _EXPECT_DIRGENERATE // Directive $GENERATE + _EXPECT_DIRGENERATE_BL // Space after directive $GENERATE +) + +// ParseError is a parsing error. It contains the parse error and the location in the io.Reader +// where the error occured. +type ParseError struct { + file string + err string + lex lex +} + +func (e *ParseError) Error() (s string) { + if e.file != "" { + s = e.file + ": " + } + s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " + + strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column) + return +} + +type lex struct { + token string // text of the token + tokenUpper string // uppercase text of the token + length int // lenght of the token + err bool // when true, token text has lexer error + value uint8 // value: _STRING, _BLANK, etc. + line int // line in the file + column int // column in the file + torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar + comment string // any comment text seen +} + +// *Tokens are returned when a zone file is parsed. +type Token struct { + RR // the scanned resource record when error is not nil + Error *ParseError // when an error occured, this has the error specifics + Comment string // a potential comment positioned after the RR and on the same line +} + +// NewRR reads the RR contained in the string s. Only the first RR is returned. +// 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 s[len(s)-1] != '\n' { // We need a closing newline + return ReadRR(strings.NewReader(s+"\n"), "") + } + return ReadRR(strings.NewReader(s), "") +} + +// ReadRR reads the RR contained in q. +// See NewRR for more documentation. +func ReadRR(q io.Reader, filename string) (RR, error) { + r := <-parseZoneHelper(q, ".", filename, 1) + if r.Error != nil { + return nil, r.Error + } + return r.RR, nil +} + +// ParseZone reads a RFC 1035 style one from r. It returns *Tokens on the +// returned channel, which consist out the parsed RR, a potential comment or an error. +// If there is an error the RR is nil. The string file is only used +// in error reporting. The string origin is used as the initial origin, as +// if the file would start with: $ORIGIN origin . +// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported. +// The channel t is closed by ParseZone when the end of r is reached. +// +// 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 { +// // 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 discarded. Comments on a line by themselves are discarded too. +func ParseZone(r io.Reader, origin, file string) chan *Token { + return parseZoneHelper(r, origin, file, 10000) +} + +func parseZoneHelper(r io.Reader, origin, file string, chansize int) chan *Token { + t := make(chan *Token, chansize) + go parseZone(r, origin, file, t, 0) + return t +} + +func parseZone(r io.Reader, origin, f string, t chan *Token, include int) { + defer func() { + if include == 0 { + close(t) + } + }() + s := scanInit(r) + c := make(chan lex, 1000) + // Start the lexer + go zlexer(s, c) + // 6 possible beginnings of a line, _ is a space + // 0. _RRTYPE -> all omitted until the rrtype + // 1. _OWNER _ _RRTYPE -> class/ttl omitted + // 2. _OWNER _ _STRING _ _RRTYPE -> class omitted + // 3. _OWNER _ _STRING _ _CLASS _ _RRTYPE -> ttl/class + // 4. _OWNER _ _CLASS _ _RRTYPE -> ttl omitted + // 5. _OWNER _ _CLASS _ _STRING _ _RRTYPE -> class/ttl (reversed) + // After detecting these, we know the _RRTYPE so we can jump to functions + // handling the rdata for each of these types. + + if origin == "" { + origin = "." + } + origin = Fqdn(origin) + if _, ok := IsDomainName(origin); !ok { + t <- &Token{Error: &ParseError{f, "bad initial origin name", lex{}}} + return + } + + st := _EXPECT_OWNER_DIR // initial state + var h RR_Header + var defttl uint32 = defaultTtl + var prevName string + for l := range c { + // Lexer spotted an error already + if l.err == true { + t <- &Token{Error: &ParseError{f, l.token, l}} + return + + } + switch st { + case _EXPECT_OWNER_DIR: + // We can also expect a directive, like $TTL or $ORIGIN + h.Ttl = defttl + h.Class = ClassINET + switch l.value { + case _NEWLINE: // Empty line + st = _EXPECT_OWNER_DIR + case _OWNER: + h.Name = l.token + if l.token[0] == '@' { + h.Name = origin + prevName = h.Name + st = _EXPECT_OWNER_BL + break + } + if h.Name[l.length-1] != '.' { + h.Name = appendOrigin(h.Name, origin) + } + _, ok := IsDomainName(l.token) + if !ok { + t <- &Token{Error: &ParseError{f, "bad owner name", l}} + return + } + prevName = h.Name + st = _EXPECT_OWNER_BL + case _DIRTTL: + st = _EXPECT_DIRTTL_BL + case _DIRORIGIN: + st = _EXPECT_DIRORIGIN_BL + case _DIRINCLUDE: + st = _EXPECT_DIRINCLUDE_BL + case _DIRGENERATE: + st = _EXPECT_DIRGENERATE_BL + case _RRTYPE: // Everthing has been omitted, this is the first thing on the line + h.Name = prevName + h.Rrtype = l.torc + st = _EXPECT_RDATA + case _CLASS: // First thing on the line is the class + h.Name = prevName + h.Class = l.torc + st = _EXPECT_ANY_NOCLASS_BL + case _BLANK: + // Discard, can happen when there is nothing on the + // line except the RR type + case _STRING: // First thing on the is the ttl + if ttl, ok := stringToTtl(l.token); !ok { + t <- &Token{Error: &ParseError{f, "not a TTL", l}} + return + } else { + h.Ttl = ttl + // Don't about the defttl, we should take the $TTL value + // defttl = ttl + } + st = _EXPECT_ANY_NOTTL_BL + + default: + t <- &Token{Error: &ParseError{f, "syntax error at beginning", l}} + return + } + case _EXPECT_DIRINCLUDE_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank after $INCLUDE-directive", l}} + return + } + st = _EXPECT_DIRINCLUDE + case _EXPECT_DIRINCLUDE: + if l.value != _STRING { + t <- &Token{Error: &ParseError{f, "expecting $INCLUDE value, not this...", l}} + return + } + neworigin := origin // There may be optionally a new origin set after the filename, if not use current one + l := <-c + switch l.value { + case _BLANK: + l := <-c + if l.value == _STRING { + if _, ok := IsDomainName(l.token); !ok { + t <- &Token{Error: &ParseError{f, "bad origin name", l}} + return + } + // a new origin is specified. + if l.token[l.length-1] != '.' { + if origin != "." { // Prevent .. endings + neworigin = l.token + "." + origin + } else { + neworigin = l.token + origin + } + } else { + neworigin = l.token + } + } + case _NEWLINE, _EOF: + // Ok + default: + t <- &Token{Error: &ParseError{f, "garbage after $INCLUDE", l}} + return + } + // Start with the new file + r1, e1 := os.Open(l.token) + if e1 != nil { + t <- &Token{Error: &ParseError{f, "failed to open `" + l.token + "'", l}} + return + } + if include+1 > 7 { + t <- &Token{Error: &ParseError{f, "too deeply nested $INCLUDE", l}} + return + } + parseZone(r1, l.token, neworigin, t, include+1) + st = _EXPECT_OWNER_DIR + case _EXPECT_DIRTTL_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank after $TTL-directive", l}} + return + } + st = _EXPECT_DIRTTL + case _EXPECT_DIRTTL: + if l.value != _STRING { + t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} + return + } + if e, _ := slurpRemainder(c, f); e != nil { + t <- &Token{Error: e} + return + } + if ttl, ok := stringToTtl(l.token); !ok { + t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} + return + } else { + defttl = ttl + } + st = _EXPECT_OWNER_DIR + case _EXPECT_DIRORIGIN_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank after $ORIGIN-directive", l}} + return + } + st = _EXPECT_DIRORIGIN + case _EXPECT_DIRORIGIN: + if l.value != _STRING { + t <- &Token{Error: &ParseError{f, "expecting $ORIGIN value, not this...", l}} + return + } + if e, _ := slurpRemainder(c, f); e != nil { + t <- &Token{Error: e} + } + if _, ok := IsDomainName(l.token); !ok { + t <- &Token{Error: &ParseError{f, "bad origin name", l}} + return + } + if l.token[l.length-1] != '.' { + if origin != "." { // Prevent .. endings + origin = l.token + "." + origin + } else { + origin = l.token + origin + } + } else { + origin = l.token + } + st = _EXPECT_OWNER_DIR + case _EXPECT_DIRGENERATE_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}} + return + } + st = _EXPECT_DIRGENERATE + case _EXPECT_DIRGENERATE: + if l.value != _STRING { + t <- &Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}} + return + } + if e := generate(l, c, t, origin); e != "" { + t <- &Token{Error: &ParseError{f, e, l}} + return + } + st = _EXPECT_OWNER_DIR + case _EXPECT_OWNER_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank after owner", l}} + return + } + st = _EXPECT_ANY + case _EXPECT_ANY: + switch l.value { + case _RRTYPE: + h.Rrtype = l.torc + st = _EXPECT_RDATA + case _CLASS: + h.Class = l.torc + st = _EXPECT_ANY_NOCLASS_BL + case _STRING: // TTL is this case + if ttl, ok := stringToTtl(l.token); !ok { + t <- &Token{Error: &ParseError{f, "not a TTL", l}} + return + } else { + h.Ttl = ttl + // defttl = ttl // don't set the defttl here + } + st = _EXPECT_ANY_NOTTL_BL + default: + t <- &Token{Error: &ParseError{f, "expecting RR type, TTL or class, not this...", l}} + return + } + case _EXPECT_ANY_NOCLASS_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank before class", l}} + return + } + st = _EXPECT_ANY_NOCLASS + case _EXPECT_ANY_NOTTL_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank before TTL", l}} + return + } + st = _EXPECT_ANY_NOTTL + case _EXPECT_ANY_NOTTL: + switch l.value { + case _CLASS: + h.Class = l.torc + st = _EXPECT_RRTYPE_BL + case _RRTYPE: + h.Rrtype = l.torc + st = _EXPECT_RDATA + default: + t <- &Token{Error: &ParseError{f, "expecting RR type or class, not this...", l}} + return + } + case _EXPECT_ANY_NOCLASS: + switch l.value { + case _STRING: // TTL + if ttl, ok := stringToTtl(l.token); !ok { + t <- &Token{Error: &ParseError{f, "not a TTL", l}} + return + } else { + h.Ttl = ttl + // defttl = ttl // don't set the def ttl anymore + } + st = _EXPECT_RRTYPE_BL + case _RRTYPE: + h.Rrtype = l.torc + st = _EXPECT_RDATA + default: + t <- &Token{Error: &ParseError{f, "expecting RR type or TTL, not this...", l}} + return + } + case _EXPECT_RRTYPE_BL: + if l.value != _BLANK { + t <- &Token{Error: &ParseError{f, "no blank before RR type", l}} + return + } + st = _EXPECT_RRTYPE + case _EXPECT_RRTYPE: + if l.value != _RRTYPE { + t <- &Token{Error: &ParseError{f, "unknown RR type", l}} + return + } + h.Rrtype = l.torc + st = _EXPECT_RDATA + case _EXPECT_RDATA: + r, e, c1 := setRR(h, c, origin, f) + if e != nil { + // If e.lex is nil than we have encounter a unknown RR type + // in that case we substitute our current lex token + if e.lex.token == "" && e.lex.value == 0 { + e.lex = l // Uh, dirty + } + t <- &Token{Error: e} + return + } + t <- &Token{RR: r, Comment: c1} + st = _EXPECT_OWNER_DIR + } + } + // If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this + // is not an error, because an empty zone file is still a zone file. +} + +// zlexer scans the sourcefile and returns tokens on the channel c. +func zlexer(s *scan, c chan lex) { + var l lex + str := make([]byte, maxTok) // Should be enough for any token + stri := 0 // Offset in str (0 means empty) + com := make([]byte, maxTok) // Hold comment text + comi := 0 + quote := false + escape := false + space := false + commt := false + rrtype := false + owner := true + brace := 0 + x, err := s.tokenText() + defer close(c) + for err == nil { + l.column = s.position.Column + l.line = s.position.Line + if stri > maxTok { + l.token = "token length insufficient for parsing" + l.err = true + debug.Printf("[%+v]", l.token) + c <- l + return + } + if comi > maxTok { + l.token = "comment length insufficient for parsing" + l.err = true + debug.Printf("[%+v]", l.token) + c <- l + return + } + + switch x { + case ' ', '\t': + if escape { + escape = false + str[stri] = x + stri++ + break + } + if quote { + // Inside quotes this is legal + str[stri] = x + stri++ + break + } + if commt { + com[comi] = x + comi++ + break + } + if stri == 0 { + // Space directly in the beginning, handled in the grammar + } else if owner { + // If we have a string and its the first, make it an owner + l.value = _OWNER + l.token = string(str[:stri]) + l.tokenUpper = strings.ToUpper(l.token) + l.length = stri + // escape $... start with a \ not a $, so this will work + switch l.tokenUpper { + case "$TTL": + l.value = _DIRTTL + case "$ORIGIN": + l.value = _DIRORIGIN + case "$INCLUDE": + l.value = _DIRINCLUDE + case "$GENERATE": + l.value = _DIRGENERATE + } + debug.Printf("[7 %+v]", l.token) + c <- l + } else { + l.value = _STRING + l.token = string(str[:stri]) + l.tokenUpper = strings.ToUpper(l.token) + l.length = stri + if !rrtype { + if t, ok := StringToType[l.tokenUpper]; ok { + l.value = _RRTYPE + l.torc = t + rrtype = true + } else { + if strings.HasPrefix(l.tokenUpper, "TYPE") { + if t, ok := typeToInt(l.token); !ok { + l.token = "unknown RR type" + l.err = true + c <- l + return + } else { + l.value = _RRTYPE + l.torc = t + } + } + } + if t, ok := StringToClass[l.tokenUpper]; ok { + l.value = _CLASS + l.torc = t + } else { + if strings.HasPrefix(l.tokenUpper, "CLASS") { + if t, ok := classToInt(l.token); !ok { + l.token = "unknown class" + l.err = true + c <- l + return + } else { + l.value = _CLASS + l.torc = t + } + } + } + } + debug.Printf("[6 %+v]", l.token) + c <- l + } + stri = 0 + // I reverse space stuff here + if !space && !commt { + l.value = _BLANK + l.token = " " + l.length = 1 + debug.Printf("[5 %+v]", l.token) + c <- l + } + owner = false + space = true + case ';': + if escape { + escape = false + str[stri] = x + stri++ + break + } + if quote { + // Inside quotes this is legal + str[stri] = x + stri++ + break + } + if stri > 0 { + l.value = _STRING + l.token = string(str[:stri]) + l.length = stri + debug.Printf("[4 %+v]", l.token) + c <- l + stri = 0 + } + commt = true + com[comi] = ';' + comi++ + case '\r': + escape = false + if quote { + str[stri] = x + stri++ + break + } + // discard if outside of quotes + case '\n': + escape = false + // Escaped newline + if quote { + str[stri] = x + stri++ + break + } + // inside quotes this is legal + if commt { + // Reset a comment + commt = false + rrtype = false + stri = 0 + // If not in a brace this ends the comment AND the RR + if brace == 0 { + owner = true + owner = true + l.value = _NEWLINE + l.token = "\n" + l.length = 1 + l.comment = string(com[:comi]) + debug.Printf("[3 %+v %+v]", l.token, l.comment) + c <- l + l.comment = "" + comi = 0 + break + } + com[comi] = ' ' // convert newline to space + comi++ + break + } + + if brace == 0 { + // If there is previous text, we should output it here + if stri != 0 { + l.value = _STRING + l.token = string(str[:stri]) + l.tokenUpper = strings.ToUpper(l.token) + + l.length = stri + if !rrtype { + if t, ok := StringToType[l.tokenUpper]; ok { + l.value = _RRTYPE + l.torc = t + rrtype = true + } + } + debug.Printf("[2 %+v]", l.token) + c <- l + } + l.value = _NEWLINE + l.token = "\n" + l.length = 1 + debug.Printf("[1 %+v]", l.token) + c <- l + stri = 0 + commt = false + rrtype = false + owner = true + comi = 0 + } + case '\\': + // comments do not get escaped chars, everything is copied + if commt { + com[comi] = x + comi++ + break + } + // something already escaped must be in string + if escape { + str[stri] = x + stri++ + escape = false + break + } + // something escaped outside of string gets added to string + str[stri] = x + stri++ + escape = true + case '"': + if commt { + com[comi] = x + comi++ + break + } + if escape { + str[stri] = x + stri++ + escape = false + break + } + space = false + // send previous gathered text and the quote + if stri != 0 { + l.value = _STRING + l.token = string(str[:stri]) + l.length = stri + + debug.Printf("[%+v]", l.token) + c <- l + stri = 0 + } + + // send quote itself as separate token + l.value = _QUOTE + l.token = "\"" + l.length = 1 + c <- l + quote = !quote + case '(', ')': + if commt { + com[comi] = x + comi++ + break + } + if escape { + str[stri] = x + stri++ + escape = false + break + } + if quote { + str[stri] = x + stri++ + break + } + switch x { + case ')': + brace-- + if brace < 0 { + l.token = "extra closing brace" + l.err = true + debug.Printf("[%+v]", l.token) + c <- l + return + } + case '(': + brace++ + } + default: + escape = false + if commt { + com[comi] = x + comi++ + break + } + str[stri] = x + stri++ + space = false + } + x, err = s.tokenText() + } + if stri > 0 { + // Send remainder + l.token = string(str[:stri]) + l.length = stri + l.value = _STRING + debug.Printf("[%+v]", l.token) + c <- l + } +} + +// Extract the class number from CLASSxx +func classToInt(token string) (uint16, bool) { + class, ok := strconv.Atoi(token[5:]) + if ok != nil || class > maxUint16 { + return 0, false + } + return uint16(class), true +} + +// Extract the rr number from TYPExxx +func typeToInt(token string) (uint16, bool) { + typ, ok := strconv.Atoi(token[4:]) + if ok != nil || typ > maxUint16 { + return 0, false + } + return uint16(typ), true +} + +// Parse things like 2w, 2m, etc, Return the time in seconds. +func stringToTtl(token string) (uint32, bool) { + s := uint32(0) + i := uint32(0) + for _, c := range token { + switch c { + case 's', 'S': + s += i + i = 0 + case 'm', 'M': + s += i * 60 + i = 0 + case 'h', 'H': + s += i * 60 * 60 + i = 0 + case 'd', 'D': + s += i * 60 * 60 * 24 + i = 0 + case 'w', 'W': + s += i * 60 * 60 * 24 * 7 + i = 0 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i *= 10 + i += uint32(c) - '0' + default: + return 0, false + } + } + return s + i, true +} + +// Parse LOC records' [.][mM] into a +// mantissa exponent format. Token should contain the entire +// string (i.e. no spaces allowed) +func stringToCm(token string) (e, m uint8, ok bool) { + if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' { + token = token[0 : len(token)-1] + } + s := strings.SplitN(token, ".", 2) + var meters, cmeters, val int + var err error + switch len(s) { + case 2: + if cmeters, err = strconv.Atoi(s[1]); err != nil { + return + } + fallthrough + case 1: + if meters, err = strconv.Atoi(s[0]); err != nil { + return + } + case 0: + // huh? + return 0, 0, false + } + ok = true + if meters > 0 { + e = 2 + val = meters + } else { + e = 0 + val = cmeters + } + for val > 10 { + e++ + val /= 10 + } + if e > 9 { + ok = false + } + m = uint8(val) + return +} + +func appendOrigin(name, origin string) string { + if origin == "." { + return name + origin + } + return name + "." + origin +} + +// LOC record helper function +func locCheckNorth(token string, latitude uint32) (uint32, bool) { + switch token { + case "n", "N": + return LOC_EQUATOR + latitude, true + case "s", "S": + return LOC_EQUATOR - latitude, true + } + return latitude, false +} + +// LOC record helper function +func locCheckEast(token string, longitude uint32) (uint32, bool) { + switch token { + case "e", "E": + return LOC_EQUATOR + longitude, true + case "w", "W": + return LOC_EQUATOR - longitude, true + } + return longitude, false +} + +// "Eat" the rest of the "line". Return potential comments +func slurpRemainder(c chan lex, f string) (*ParseError, string) { + l := <-c + com := "" + switch l.value { + case _BLANK: + l = <-c + com = l.comment + if l.value != _NEWLINE && l.value != _EOF { + return &ParseError{f, "garbage after rdata", l}, "" + } + case _NEWLINE: + com = l.comment + case _EOF: + default: + return &ParseError{f, "garbage after rdata", l}, "" + } + return nil, com +} + +// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64" +// Used for NID and L64 record. +func stringToNodeID(l lex) (uint64, *ParseError) { + if len(l.token) < 19 { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + // There must be three colons at fixes postitions, if not its a parse error + if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19] + u, e := strconv.ParseUint(s, 16, 64) + if e != nil { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + return u, nil +} diff --git a/Godeps/_workspace/src/github.com/miekg/dns/zscan_rr.go b/Godeps/_workspace/src/github.com/miekg/dns/zscan_rr.go new file mode 100644 index 00000000000..5088cdb58e2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/miekg/dns/zscan_rr.go @@ -0,0 +1,2155 @@ +package dns + +import ( + "encoding/base64" + "net" + "strconv" + "strings" +) + +type parserFunc struct { + // Func defines the function that parses the tokens and returns the RR + // or an error. The last string contains any comments in the line as + // they returned by the lexer as well. + Func func(h RR_Header, c chan lex, origin string, file string) (RR, *ParseError, string) + // Signals if the RR ending is of variable length, like TXT or records + // that have Hexadecimal or Base64 as their last element in the Rdata. Records + // that have a fixed ending or for instance A, AAAA, SOA and etc. + Variable bool +} + +// Parse the rdata of each rrtype. +// All data from the channel c is either _STRING or _BLANK. +// After the rdata there may come a _BLANK and then a _NEWLINE +// or immediately a _NEWLINE. If this is not the case we flag +// an *ParseError: garbage after rdata. +func setRR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + parserfunc, ok := typeToparserFunc[h.Rrtype] + if ok { + r, e, cm := parserfunc.Func(h, c, o, f) + if parserfunc.Variable { + return r, e, cm + } + if e != nil { + return nil, e, "" + } + e, cm = slurpRemainder(c, f) + if e != nil { + return nil, e, "" + } + return r, nil, cm + } + // RFC3957 RR (Unknown RR handling) + return setRFC3597(h, c, o, f) +} + +// A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces) +// or an error +func endingToString(c chan lex, errstr, f string) (string, *ParseError, string) { + s := "" + l := <-c // _STRING + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _STRING: + s += l.token + case _BLANK: // Ok + default: + return "", &ParseError{f, errstr, l}, "" + } + l = <-c + } + return s, nil, l.comment +} + +// A remainder of the rdata with embedded spaces, return the parsed string slice (sans the spaces) +// or an error +func endingToTxtSlice(c chan lex, errstr, f string) ([]string, *ParseError, string) { + // Get the remaining data until we see a NEWLINE + quote := false + l := <-c + var s []string + switch l.value == _QUOTE { + case true: // A number of quoted string + s = make([]string, 0) + empty := true + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _STRING: + empty = false + s = append(s, l.token) + case _BLANK: + if quote { + // _BLANK can only be seen in between txt parts. + return nil, &ParseError{f, errstr, l}, "" + } + case _QUOTE: + if empty && quote { + s = append(s, "") + } + quote = !quote + empty = true + default: + return nil, &ParseError{f, errstr, l}, "" + } + l = <-c + } + if quote { + return nil, &ParseError{f, errstr, l}, "" + } + case false: // Unquoted text record + s = make([]string, 1) + for l.value != _NEWLINE && l.value != _EOF { + s[0] += l.token + l = <-c + } + } + return s, nil, l.comment +} + +func setA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(A) + rr.Hdr = h + + l := <-c + if l.length == 0 { // Dynamic updates. + return rr, nil, "" + } + rr.A = net.ParseIP(l.token) + if rr.A == nil { + return nil, &ParseError{f, "bad A A", l}, "" + } + return rr, nil, "" +} + +func setAAAA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(AAAA) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + rr.AAAA = net.ParseIP(l.token) + if rr.AAAA == nil { + return nil, &ParseError{f, "bad AAAA AAAA", l}, "" + } + return rr, nil, "" +} + +func setNS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NS) + rr.Hdr = h + + l := <-c + rr.Ns = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Ns = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad NS Ns", l}, "" + } + if rr.Ns[l.length-1] != '.' { + rr.Ns = appendOrigin(rr.Ns, o) + } + return rr, nil, "" +} + +func setPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(PTR) + rr.Hdr = h + + l := <-c + rr.Ptr = l.token + if l.length == 0 { // dynamic update rr. + return rr, nil, "" + } + if l.token == "@" { + rr.Ptr = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad PTR Ptr", l}, "" + } + if rr.Ptr[l.length-1] != '.' { + rr.Ptr = appendOrigin(rr.Ptr, o) + } + return rr, nil, "" +} + +func setNSAPPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NSAPPTR) + rr.Hdr = h + + l := <-c + rr.Ptr = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Ptr = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad NSAP-PTR Ptr", l}, "" + } + if rr.Ptr[l.length-1] != '.' { + rr.Ptr = appendOrigin(rr.Ptr, o) + } + return rr, nil, "" +} + +func setRP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(RP) + rr.Hdr = h + + l := <-c + rr.Mbox = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Mbox = o + } else { + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad RP Mbox", l}, "" + } + if rr.Mbox[l.length-1] != '.' { + rr.Mbox = appendOrigin(rr.Mbox, o) + } + } + <-c // _BLANK + l = <-c + rr.Txt = l.token + if l.token == "@" { + rr.Txt = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad RP Txt", l}, "" + } + if rr.Txt[l.length-1] != '.' { + rr.Txt = appendOrigin(rr.Txt, o) + } + return rr, nil, "" +} + +func setMR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MR) + rr.Hdr = h + + l := <-c + rr.Mr = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Mr = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MR Mr", l}, "" + } + if rr.Mr[l.length-1] != '.' { + rr.Mr = appendOrigin(rr.Mr, o) + } + return rr, nil, "" +} + +func setMB(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MB) + rr.Hdr = h + + l := <-c + rr.Mb = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Mb = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MB Mb", l}, "" + } + if rr.Mb[l.length-1] != '.' { + rr.Mb = appendOrigin(rr.Mb, o) + } + return rr, nil, "" +} + +func setMG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MG) + rr.Hdr = h + + l := <-c + rr.Mg = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Mg = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MG Mg", l}, "" + } + if rr.Mg[l.length-1] != '.' { + rr.Mg = appendOrigin(rr.Mg, o) + } + return rr, nil, "" +} + +func setHINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(HINFO) + rr.Hdr = h + + l := <-c + rr.Cpu = l.token + <-c // _BLANK + l = <-c // _STRING + rr.Os = l.token + + return rr, nil, "" +} + +func setMINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MINFO) + rr.Hdr = h + + l := <-c + rr.Rmail = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Rmail = o + } else { + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MINFO Rmail", l}, "" + } + if rr.Rmail[l.length-1] != '.' { + rr.Rmail = appendOrigin(rr.Rmail, o) + } + } + <-c // _BLANK + l = <-c + rr.Email = l.token + if l.token == "@" { + rr.Email = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MINFO Email", l}, "" + } + if rr.Email[l.length-1] != '.' { + rr.Email = appendOrigin(rr.Email, o) + } + return rr, nil, "" +} + +func setMF(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MF) + rr.Hdr = h + + l := <-c + rr.Mf = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Mf = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MF Mf", l}, "" + } + if rr.Mf[l.length-1] != '.' { + rr.Mf = appendOrigin(rr.Mf, o) + } + return rr, nil, "" +} + +func setMD(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MD) + rr.Hdr = h + + l := <-c + rr.Md = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Md = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad MD Md", l}, "" + } + if rr.Md[l.length-1] != '.' { + rr.Md = appendOrigin(rr.Md, o) + } + return rr, nil, "" +} + +func setMX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(MX) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad MX Pref", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Mx = l.token + if l.token == "@" { + rr.Mx = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad MX Mx", l}, "" + } + if rr.Mx[l.length-1] != '.' { + rr.Mx = appendOrigin(rr.Mx, o) + } + return rr, nil, "" +} + +func setRT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(RT) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad RT Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Host = l.token + if l.token == "@" { + rr.Host = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad RT Host", l}, "" + } + if rr.Host[l.length-1] != '.' { + rr.Host = appendOrigin(rr.Host, o) + } + return rr, nil, "" +} + +func setAFSDB(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(AFSDB) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad AFSDB Subtype", l}, "" + } else { + rr.Subtype = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Hostname = l.token + if l.token == "@" { + rr.Hostname = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad AFSDB Hostname", l}, "" + } + if rr.Hostname[l.length-1] != '.' { + rr.Hostname = appendOrigin(rr.Hostname, o) + } + return rr, nil, "" +} + +func setX25(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(X25) + rr.Hdr = h + + l := <-c + rr.PSDNAddress = l.token + return rr, nil, "" +} + +func setKX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(KX) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad KX Pref", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Exchanger = l.token + if l.token == "@" { + rr.Exchanger = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad KX Exchanger", l}, "" + } + if rr.Exchanger[l.length-1] != '.' { + rr.Exchanger = appendOrigin(rr.Exchanger, o) + } + return rr, nil, "" +} + +func setCNAME(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(CNAME) + rr.Hdr = h + + l := <-c + rr.Target = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Target = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad CNAME Target", l}, "" + } + if rr.Target[l.length-1] != '.' { + rr.Target = appendOrigin(rr.Target, o) + } + return rr, nil, "" +} + +func setDNAME(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(DNAME) + rr.Hdr = h + + l := <-c + rr.Target = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Target = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad CNAME Target", l}, "" + } + if rr.Target[l.length-1] != '.' { + rr.Target = appendOrigin(rr.Target, o) + } + return rr, nil, "" +} + +func setSOA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(SOA) + rr.Hdr = h + + l := <-c + rr.Ns = l.token + if l.length == 0 { + return rr, nil, "" + } + <-c // _BLANK + if l.token == "@" { + rr.Ns = o + } else { + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad SOA Ns", l}, "" + } + if rr.Ns[l.length-1] != '.' { + rr.Ns = appendOrigin(rr.Ns, o) + } + } + + l = <-c + rr.Mbox = l.token + if l.token == "@" { + rr.Mbox = o + } else { + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad SOA Mbox", l}, "" + } + if rr.Mbox[l.length-1] != '.' { + rr.Mbox = appendOrigin(rr.Mbox, o) + } + } + <-c // _BLANK + + var ( + v uint32 + ok bool + ) + for i := 0; i < 5; i++ { + l = <-c + if j, e := strconv.Atoi(l.token); e != nil { + if i == 0 { + // Serial should be a number + return nil, &ParseError{f, "bad SOA zone parameter", l}, "" + } + if v, ok = stringToTtl(l.token); !ok { + return nil, &ParseError{f, "bad SOA zone parameter", l}, "" + + } + } else { + v = uint32(j) + } + switch i { + case 0: + rr.Serial = v + <-c // _BLANK + case 1: + rr.Refresh = v + <-c // _BLANK + case 2: + rr.Retry = v + <-c // _BLANK + case 3: + rr.Expire = v + <-c // _BLANK + case 4: + rr.Minttl = v + } + } + return rr, nil, "" +} + +func setSRV(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(SRV) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad SRV Priority", l}, "" + } else { + rr.Priority = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad SRV Weight", l}, "" + } else { + rr.Weight = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad SRV Port", l}, "" + } else { + rr.Port = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Target = l.token + if l.token == "@" { + rr.Target = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad SRV Target", l}, "" + } + if rr.Target[l.length-1] != '.' { + rr.Target = appendOrigin(rr.Target, o) + } + return rr, nil, "" +} + +func setNAPTR(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NAPTR) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NAPTR Order", l}, "" + } else { + rr.Order = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NAPTR Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + // Flags + <-c // _BLANK + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Flags", l}, "" + } + l = <-c // Either String or Quote + if l.value == _STRING { + rr.Flags = l.token + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Flags", l}, "" + } + } else if l.value == _QUOTE { + rr.Flags = "" + } else { + return nil, &ParseError{f, "bad NAPTR Flags", l}, "" + } + + // Service + <-c // _BLANK + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Service", l}, "" + } + l = <-c // Either String or Quote + if l.value == _STRING { + rr.Service = l.token + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Service", l}, "" + } + } else if l.value == _QUOTE { + rr.Service = "" + } else { + return nil, &ParseError{f, "bad NAPTR Service", l}, "" + } + + // Regexp + <-c // _BLANK + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" + } + l = <-c // Either String or Quote + if l.value == _STRING { + rr.Regexp = l.token + l = <-c // _QUOTE + if l.value != _QUOTE { + return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" + } + } else if l.value == _QUOTE { + rr.Regexp = "" + } else { + return nil, &ParseError{f, "bad NAPTR Regexp", l}, "" + } + // After quote no space?? + <-c // _BLANK + l = <-c // _STRING + rr.Replacement = l.token + if l.token == "@" { + rr.Replacement = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad NAPTR Replacement", l}, "" + } + if rr.Replacement[l.length-1] != '.' { + rr.Replacement = appendOrigin(rr.Replacement, o) + } + return rr, nil, "" +} + +func setTALINK(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(TALINK) + rr.Hdr = h + + l := <-c + rr.PreviousName = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.PreviousName = o + } else { + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad TALINK PreviousName", l}, "" + } + if rr.PreviousName[l.length-1] != '.' { + rr.PreviousName = appendOrigin(rr.PreviousName, o) + } + } + <-c // _BLANK + l = <-c + rr.NextName = l.token + if l.token == "@" { + rr.NextName = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad TALINK NextName", l}, "" + } + if rr.NextName[l.length-1] != '.' { + rr.NextName = appendOrigin(rr.NextName, o) + } + return rr, nil, "" +} + +func setLOC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(LOC) + rr.Hdr = h + // Non zero defaults for LOC record, see RFC 1876, Section 3. + rr.HorizPre = 165 // 10000 + rr.VertPre = 162 // 10 + rr.Size = 18 // 1 + ok := false + // North + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad LOC Latitude", l}, "" + } else { + rr.Latitude = 1000 * 60 * 60 * uint32(i) + } + <-c // _BLANK + // Either number, 'N' or 'S' + l = <-c + if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { + goto East + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad LOC Latitude minutes", l}, "" + } else { + rr.Latitude += 1000 * 60 * uint32(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.ParseFloat(l.token, 32); e != nil { + return nil, &ParseError{f, "bad LOC Latitude seconds", l}, "" + } else { + rr.Latitude += uint32(1000 * i) + } + <-c // _BLANK + // Either number, 'N' or 'S' + l = <-c + if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { + goto East + } + // If still alive, flag an error + return nil, &ParseError{f, "bad LOC Latitude North/South", l}, "" + +East: + // East + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad LOC Longitude", l}, "" + } else { + rr.Longitude = 1000 * 60 * 60 * uint32(i) + } + <-c // _BLANK + // Either number, 'E' or 'W' + l = <-c + if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { + goto Altitude + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad LOC Longitude minutes", l}, "" + } else { + rr.Longitude += 1000 * 60 * uint32(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.ParseFloat(l.token, 32); e != nil { + return nil, &ParseError{f, "bad LOC Longitude seconds", l}, "" + } else { + rr.Longitude += uint32(1000 * i) + } + <-c // _BLANK + // Either number, 'E' or 'W' + l = <-c + if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { + goto Altitude + } + // If still alive, flag an error + return nil, &ParseError{f, "bad LOC Longitude East/West", l}, "" + +Altitude: + <-c // _BLANK + l = <-c + 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 { + return nil, &ParseError{f, "bad LOC Altitude", l}, "" + } else { + rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5) + } + + // And now optionally the other values + l = <-c + count := 0 + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _STRING: + switch count { + case 0: // Size + if e, m, ok := stringToCm(l.token); !ok { + return nil, &ParseError{f, "bad LOC Size", l}, "" + } else { + rr.Size = (e & 0x0f) | (m << 4 & 0xf0) + } + case 1: // HorizPre + if e, m, ok := stringToCm(l.token); !ok { + return nil, &ParseError{f, "bad LOC HorizPre", l}, "" + } else { + rr.HorizPre = (e & 0x0f) | (m << 4 & 0xf0) + } + case 2: // VertPre + if e, m, ok := stringToCm(l.token); !ok { + return nil, &ParseError{f, "bad LOC VertPre", l}, "" + } else { + rr.VertPre = (e & 0x0f) | (m << 4 & 0xf0) + } + } + count++ + case _BLANK: + // Ok + default: + return nil, &ParseError{f, "bad LOC Size, HorizPre or VertPre", l}, "" + } + l = <-c + } + return rr, nil, "" +} + +func setHIP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(HIP) + rr.Hdr = h + + // HitLength is not represented + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad HIP PublicKeyAlgorithm", l}, "" + } else { + rr.PublicKeyAlgorithm = uint8(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6. + rr.HitLength = uint8(len(rr.Hit)) / 2 + + <-c // _BLANK + l = <-c // _STRING + rr.PublicKey = l.token // This cannot contain spaces + rr.PublicKeyLength = uint16(base64.StdEncoding.DecodedLen(len(rr.PublicKey))) + + // RendezvousServers (if any) + l = <-c + xs := make([]string, 0) + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _STRING: + if l.token == "@" { + xs = append(xs, o) + continue + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad HIP RendezvousServers", l}, "" + } + if l.token[l.length-1] != '.' { + l.token = appendOrigin(l.token, o) + } + xs = append(xs, l.token) + case _BLANK: + // Ok + default: + return nil, &ParseError{f, "bad HIP RendezvousServers", l}, "" + } + l = <-c + } + rr.RendezvousServers = xs + return rr, nil, l.comment +} + +func setCERT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(CERT) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if v, ok := StringToCertType[l.token]; ok { + rr.Type = v + } else if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad CERT Type", l}, "" + } else { + rr.Type = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad CERT KeyTag", l}, "" + } else { + rr.KeyTag = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if v, ok := StringToAlgorithm[l.token]; ok { + rr.Algorithm = v + } else if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad CERT Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + s, e, c1 := endingToString(c, "bad CERT Certificate", f) + if e != nil { + return nil, e, c1 + } + rr.Certificate = s + return rr, nil, c1 +} + +func setOPENPGPKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(OPENPGPKEY) + rr.Hdr = h + + s, e, c1 := endingToString(c, "bad OPENPGPKEY PublicKey", f) + if e != nil { + return nil, e, c1 + } + rr.PublicKey = s + return rr, nil, c1 +} + +func setSIG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setRRSIG(h, c, o, f) + if r != nil { + return &SIG{*r.(*RRSIG)}, e, s + } + return nil, e, s +} + +func setRRSIG(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(RRSIG) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if t, ok := StringToType[l.tokenUpper]; !ok { + if strings.HasPrefix(l.tokenUpper, "TYPE") { + if t, ok = typeToInt(l.tokenUpper); !ok { + return nil, &ParseError{f, "bad RRSIG Typecovered", l}, "" + } else { + rr.TypeCovered = t + } + } else { + return nil, &ParseError{f, "bad RRSIG Typecovered", l}, "" + } + } else { + rr.TypeCovered = t + } + <-c // _BLANK + l = <-c + if i, err := strconv.Atoi(l.token); err != nil { + return nil, &ParseError{f, "bad RRSIG Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + <-c // _BLANK + l = <-c + if i, err := strconv.Atoi(l.token); err != nil { + return nil, &ParseError{f, "bad RRSIG Labels", l}, "" + } else { + rr.Labels = uint8(i) + } + <-c // _BLANK + l = <-c + if i, err := strconv.Atoi(l.token); err != nil { + return nil, &ParseError{f, "bad RRSIG OrigTtl", l}, "" + } else { + rr.OrigTtl = uint32(i) + } + <-c // _BLANK + l = <-c + 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 + rr.Expiration = uint32(i) + } else { + return nil, &ParseError{f, "bad RRSIG Expiration", l}, "" + } + } else { + rr.Expiration = i + } + <-c // _BLANK + l = <-c + if i, err := StringToTime(l.token); err != nil { + if i, err := strconv.ParseInt(l.token, 10, 64); err == nil { + rr.Inception = uint32(i) + } else { + return nil, &ParseError{f, "bad RRSIG Inception", l}, "" + } + } else { + rr.Inception = i + } + <-c // _BLANK + l = <-c + if i, err := strconv.Atoi(l.token); err != nil { + return nil, &ParseError{f, "bad RRSIG KeyTag", l}, "" + } else { + rr.KeyTag = uint16(i) + } + <-c // _BLANK + l = <-c + rr.SignerName = l.token + if l.token == "@" { + rr.SignerName = o + } else { + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad RRSIG SignerName", l}, "" + } + if rr.SignerName[l.length-1] != '.' { + rr.SignerName = appendOrigin(rr.SignerName, o) + } + } + s, e, c1 := endingToString(c, "bad RRSIG Signature", f) + if e != nil { + return nil, e, c1 + } + rr.Signature = s + return rr, nil, c1 +} + +func setNSEC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NSEC) + rr.Hdr = h + + l := <-c + rr.NextDomain = l.token + if l.length == 0 { + return rr, nil, l.comment + } + if l.token == "@" { + rr.NextDomain = o + } else { + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad NSEC NextDomain", l}, "" + } + if rr.NextDomain[l.length-1] != '.' { + rr.NextDomain = appendOrigin(rr.NextDomain, o) + } + } + + rr.TypeBitMap = make([]uint16, 0) + var ( + k uint16 + ok bool + ) + l = <-c + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _BLANK: + // Ok + case _STRING: + if k, ok = StringToType[l.tokenUpper]; !ok { + if k, ok = typeToInt(l.tokenUpper); !ok { + return nil, &ParseError{f, "bad NSEC TypeBitMap", l}, "" + } + } + rr.TypeBitMap = append(rr.TypeBitMap, k) + default: + return nil, &ParseError{f, "bad NSEC TypeBitMap", l}, "" + } + l = <-c + } + return rr, nil, l.comment +} + +func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NSEC3) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3 Hash", l}, "" + } else { + rr.Hash = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3 Flags", l}, "" + } else { + rr.Flags = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3 Iterations", l}, "" + } else { + rr.Iterations = uint16(i) + } + <-c + l = <-c + if len(l.token) == 0 { + return nil, &ParseError{f, "bad NSEC3 Salt", l}, "" + } + rr.SaltLength = uint8(len(l.token)) / 2 + rr.Salt = l.token + + <-c + l = <-c + rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits) + rr.NextDomain = l.token + + rr.TypeBitMap = make([]uint16, 0) + var ( + k uint16 + ok bool + ) + l = <-c + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _BLANK: + // Ok + case _STRING: + if k, ok = StringToType[l.tokenUpper]; !ok { + if k, ok = typeToInt(l.tokenUpper); !ok { + return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l}, "" + } + } + rr.TypeBitMap = append(rr.TypeBitMap, k) + default: + return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l}, "" + } + l = <-c + } + return rr, nil, l.comment +} + +func setNSEC3PARAM(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NSEC3PARAM) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3PARAM Hash", l}, "" + } else { + rr.Hash = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3PARAM Flags", l}, "" + } else { + rr.Flags = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSEC3PARAM Iterations", l}, "" + } else { + rr.Iterations = uint16(i) + } + <-c + l = <-c + rr.SaltLength = uint8(len(l.token)) + rr.Salt = l.token + return rr, nil, "" +} + +func setEUI48(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(EUI48) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if l.length != 17 { + return nil, &ParseError{f, "bad EUI48 Address", l}, "" + } + addr := make([]byte, 12) + dash := 0 + for i := 0; i < 10; i += 2 { + addr[i] = l.token[i+dash] + addr[i+1] = l.token[i+1+dash] + dash++ + if l.token[i+1+dash] != '-' { + return nil, &ParseError{f, "bad EUI48 Address", l}, "" + } + } + addr[10] = l.token[15] + addr[11] = l.token[16] + + if i, e := strconv.ParseUint(string(addr), 16, 48); e != nil { + return nil, &ParseError{f, "bad EUI48 Address", l}, "" + } else { + rr.Address = i + } + return rr, nil, "" +} + +func setEUI64(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(EUI64) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if l.length != 23 { + return nil, &ParseError{f, "bad EUI64 Address", l}, "" + } + addr := make([]byte, 16) + dash := 0 + for i := 0; i < 14; i += 2 { + addr[i] = l.token[i+dash] + addr[i+1] = l.token[i+1+dash] + dash++ + if l.token[i+1+dash] != '-' { + return nil, &ParseError{f, "bad EUI64 Address", l}, "" + } + } + addr[14] = l.token[21] + addr[15] = l.token[22] + + if i, e := strconv.ParseUint(string(addr), 16, 64); e != nil { + return nil, &ParseError{f, "bad EUI68 Address", l}, "" + } else { + rr.Address = uint64(i) + } + return rr, nil, "" +} + +func setWKS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(WKS) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + rr.Address = net.ParseIP(l.token) + if rr.Address == nil { + return nil, &ParseError{f, "bad WKS Address", l}, "" + } + + <-c // _BLANK + l = <-c + proto := "tcp" + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad WKS Protocol", l}, "" + } else { + rr.Protocol = uint8(i) + switch rr.Protocol { + case 17: + proto = "udp" + case 6: + proto = "tcp" + default: + return nil, &ParseError{f, "bad WKS Protocol", l}, "" + } + } + + <-c + l = <-c + rr.BitMap = make([]uint16, 0) + var ( + k int + err error + ) + for l.value != _NEWLINE && l.value != _EOF { + switch l.value { + case _BLANK: + // Ok + case _STRING: + if k, err = net.LookupPort(proto, l.token); err != nil { + if i, e := strconv.Atoi(l.token); e != nil { // If a number use that + rr.BitMap = append(rr.BitMap, uint16(i)) + } else { + return nil, &ParseError{f, "bad WKS BitMap", l}, "" + } + } + rr.BitMap = append(rr.BitMap, uint16(k)) + default: + return nil, &ParseError{f, "bad WKS BitMap", l}, "" + } + l = <-c + } + return rr, nil, l.comment +} + +func setSSHFP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(SSHFP) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad SSHFP Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad SSHFP Type", l}, "" + } else { + rr.Type = uint8(i) + } + <-c // _BLANK + l = <-c + rr.FingerPrint = l.token + return rr, nil, "" +} + +func setDNSKEYs(h RR_Header, c chan lex, o, f, typ string) (RR, *ParseError, string) { + rr := new(DNSKEY) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad " + typ + " Flags", l}, "" + } else { + rr.Flags = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad " + typ + " Protocol", l}, "" + } else { + rr.Protocol = uint8(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad " + typ + " Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + s, e, c1 := endingToString(c, "bad "+typ+" PublicKey", f) + if e != nil { + return nil, e, c1 + } + rr.PublicKey = s + return rr, nil, c1 +} + +func setKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDNSKEYs(h, c, o, f, "KEY") + if r != nil { + return &KEY{*r.(*DNSKEY)}, e, s + } + return nil, e, s +} + +func setDNSKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDNSKEYs(h, c, o, f, "DNSKEY") + return r, e, s +} + +func setCDNSKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDNSKEYs(h, c, o, f, "CDNSKEY") + if r != nil { + return &CDNSKEY{*r.(*DNSKEY)}, e, s + } + return nil, e, s +} + +func setRKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(RKEY) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad RKEY Flags", l}, "" + } else { + rr.Flags = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad RKEY Protocol", l}, "" + } else { + rr.Protocol = uint8(i) + } + <-c // _BLANK + l = <-c // _STRING + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad RKEY Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + s, e, c1 := endingToString(c, "bad RKEY PublicKey", f) + if e != nil { + return nil, e, c1 + } + rr.PublicKey = s + return rr, nil, c1 +} + +func setEID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(EID) + rr.Hdr = h + s, e, c1 := endingToString(c, "bad EID Endpoint", f) + if e != nil { + return nil, e, c1 + } + rr.Endpoint = s + return rr, nil, c1 +} + +func setNIMLOC(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NIMLOC) + rr.Hdr = h + s, e, c1 := endingToString(c, "bad NIMLOC Locator", f) + if e != nil { + return nil, e, c1 + } + rr.Locator = s + return rr, nil, c1 +} + +func setNSAP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NSAP) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NSAP Length", l}, "" + } else { + rr.Length = uint8(i) + } + <-c // _BLANK + s, e, c1 := endingToString(c, "bad NSAP Nsap", f) + if e != nil { + return nil, e, c1 + } + rr.Nsap = s + return rr, nil, c1 +} + +func setGPOS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(GPOS) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if _, e := strconv.ParseFloat(l.token, 64); e != nil { + return nil, &ParseError{f, "bad GPOS Longitude", l}, "" + } else { + rr.Longitude = l.token + } + <-c // _BLANK + l = <-c + if _, e := strconv.ParseFloat(l.token, 64); e != nil { + return nil, &ParseError{f, "bad GPOS Latitude", l}, "" + } else { + rr.Latitude = l.token + } + <-c // _BLANK + l = <-c + if _, e := strconv.ParseFloat(l.token, 64); e != nil { + return nil, &ParseError{f, "bad GPOS Altitude", l}, "" + } else { + rr.Altitude = l.token + } + return rr, nil, "" +} + +func setDSs(h RR_Header, c chan lex, o, f, typ string) (RR, *ParseError, string) { + rr := new(DS) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad " + typ + " KeyTag", l}, "" + } else { + rr.KeyTag = uint16(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + if i, ok := StringToAlgorithm[l.tokenUpper]; !ok { + return nil, &ParseError{f, "bad " + typ + " Algorithm", l}, "" + } else { + rr.Algorithm = i + } + } else { + rr.Algorithm = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad " + typ + " DigestType", l}, "" + } else { + rr.DigestType = uint8(i) + } + s, e, c1 := endingToString(c, "bad "+typ+" Digest", f) + if e != nil { + return nil, e, c1 + } + rr.Digest = s + return rr, nil, c1 +} + +func setDS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDSs(h, c, o, f, "DS") + return r, e, s +} + +func setDLV(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDSs(h, c, o, f, "DLV") + if r != nil { + return &DLV{*r.(*DS)}, e, s + } + return nil, e, s +} + +func setCDS(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + r, e, s := setDSs(h, c, o, f, "DLV") + if r != nil { + return &CDS{*r.(*DS)}, e, s + } + return nil, e, s +} + +func setTA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(TA) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad TA KeyTag", l}, "" + } else { + rr.KeyTag = uint16(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + if i, ok := StringToAlgorithm[l.tokenUpper]; !ok { + return nil, &ParseError{f, "bad TA Algorithm", l}, "" + } else { + rr.Algorithm = i + } + } else { + rr.Algorithm = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad TA DigestType", l}, "" + } else { + rr.DigestType = uint8(i) + } + s, e, c1 := endingToString(c, "bad TA Digest", f) + if e != nil { + return nil, e, c1 + } + rr.Digest = s + return rr, nil, c1 +} + +func setTLSA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(TLSA) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad TLSA Usage", l}, "" + } else { + rr.Usage = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad TLSA Selector", l}, "" + } else { + rr.Selector = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad TLSA MatchingType", l}, "" + } else { + rr.MatchingType = uint8(i) + } + s, e, c1 := endingToString(c, "bad TLSA Certificate", f) + if e != nil { + return nil, e, c1 + } + rr.Certificate = s + return rr, nil, c1 +} + +func setRFC3597(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(RFC3597) + rr.Hdr = h + l := <-c + if l.token != "\\#" { + return nil, &ParseError{f, "bad RFC3597 Rdata", l}, "" + } + <-c // _BLANK + l = <-c + rdlength, e := strconv.Atoi(l.token) + if e != nil { + return nil, &ParseError{f, "bad RFC3597 Rdata ", l}, "" + } + + s, e1, c1 := endingToString(c, "bad RFC3597 Rdata", f) + if e1 != nil { + return nil, e1, c1 + } + if rdlength*2 != len(s) { + return nil, &ParseError{f, "bad RFC3597 Rdata", l}, "" + } + rr.Rdata = s + return rr, nil, c1 +} + +func setSPF(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(SPF) + rr.Hdr = h + + s, e, c1 := endingToTxtSlice(c, "bad SPF Txt", f) + if e != nil { + return nil, e, "" + } + rr.Txt = s + return rr, nil, c1 +} + +func setTXT(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(TXT) + rr.Hdr = h + + // No _BLANK reading here, because this is all rdata is TXT + s, e, c1 := endingToTxtSlice(c, "bad TXT Txt", f) + if e != nil { + return nil, e, "" + } + rr.Txt = s + return rr, nil, c1 +} + +// identical to setTXT +func setNINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NINFO) + rr.Hdr = h + + s, e, c1 := endingToTxtSlice(c, "bad NINFO ZSData", f) + if e != nil { + return nil, e, "" + } + rr.ZSData = s + return rr, nil, c1 +} + +func setURI(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(URI) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad URI Priority", l}, "" + } else { + rr.Priority = uint16(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad URI Weight", l}, "" + } else { + rr.Weight = uint16(i) + } + + <-c // _BLANK + s, e, c1 := endingToTxtSlice(c, "bad URI Target", f) + if e != nil { + return nil, e, "" + } + rr.Target = s + return rr, nil, c1 +} + +func setIPSECKEY(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(IPSECKEY) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, l.comment + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad IPSECKEY Precedence", l}, "" + } else { + rr.Precedence = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad IPSECKEY GatewayType", l}, "" + } else { + rr.GatewayType = uint8(i) + } + <-c // _BLANK + l = <-c + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad IPSECKEY Algorithm", l}, "" + } else { + rr.Algorithm = uint8(i) + } + <-c + l = <-c + rr.Gateway = l.token + s, e, c1 := endingToString(c, "bad IPSECKEY PublicKey", f) + if e != nil { + return nil, e, c1 + } + rr.PublicKey = s + return rr, nil, c1 +} + +func setDHCID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + // awesome record to parse! + rr := new(DHCID) + rr.Hdr = h + + s, e, c1 := endingToString(c, "bad DHCID Digest", f) + if e != nil { + return nil, e, c1 + } + rr.Digest = s + return rr, nil, c1 +} + +func setNID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(NID) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad NID Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + u, err := stringToNodeID(l) + if err != nil { + return nil, err, "" + } + rr.NodeID = u + return rr, nil, "" +} + +func setL32(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(L32) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad L32 Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Locator32 = net.ParseIP(l.token) + if rr.Locator32 == nil { + return nil, &ParseError{f, "bad L32 Locator", l}, "" + } + return rr, nil, "" +} + +func setLP(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(LP) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad LP Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Fqdn = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Fqdn = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad LP Fqdn", l}, "" + } + if rr.Fqdn[l.length-1] != '.' { + rr.Fqdn = appendOrigin(rr.Fqdn, o) + } + return rr, nil, "" +} + +func setL64(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(L64) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad L64 Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + u, err := stringToNodeID(l) + if err != nil { + return nil, err, "" + } + rr.Locator64 = u + return rr, nil, "" +} + +func setUID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(UID) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad UID Uid", l}, "" + } else { + rr.Uid = uint32(i) + } + return rr, nil, "" +} + +func setGID(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(GID) + rr.Hdr = h + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad GID Gid", l}, "" + } else { + rr.Gid = uint32(i) + } + return rr, nil, "" +} + +func setUINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(UINFO) + rr.Hdr = h + s, e, c1 := endingToTxtSlice(c, "bad UINFO Uinfo", f) + if e != nil { + return nil, e, "" + } + rr.Uinfo = s[0] // silently discard anything above + return rr, nil, c1 +} + +func setPX(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rr := new(PX) + rr.Hdr = h + + l := <-c + if l.length == 0 { + return rr, nil, "" + } + if i, e := strconv.Atoi(l.token); e != nil { + return nil, &ParseError{f, "bad PX Preference", l}, "" + } else { + rr.Preference = uint16(i) + } + <-c // _BLANK + l = <-c // _STRING + rr.Map822 = l.token + if l.length == 0 { + return rr, nil, "" + } + if l.token == "@" { + rr.Map822 = o + return rr, nil, "" + } + _, ok := IsDomainName(l.token) + if !ok { + return nil, &ParseError{f, "bad PX Map822", l}, "" + } + if rr.Map822[l.length-1] != '.' { + rr.Map822 = appendOrigin(rr.Map822, o) + } + <-c // _BLANK + l = <-c // _STRING + rr.Mapx400 = l.token + if l.token == "@" { + rr.Mapx400 = o + return rr, nil, "" + } + _, ok = IsDomainName(l.token) + if !ok || l.length == 0 { + return nil, &ParseError{f, "bad PX Mapx400", l}, "" + } + if rr.Mapx400[l.length-1] != '.' { + rr.Mapx400 = appendOrigin(rr.Mapx400, o) + } + return rr, nil, "" +} + +var typeToparserFunc = map[uint16]parserFunc{ + TypeAAAA: parserFunc{setAAAA, false}, + TypeAFSDB: parserFunc{setAFSDB, false}, + TypeA: parserFunc{setA, false}, + TypeCDS: parserFunc{setCDS, true}, + TypeCDNSKEY: parserFunc{setCDNSKEY, true}, + TypeCERT: parserFunc{setCERT, true}, + TypeCNAME: parserFunc{setCNAME, false}, + TypeDHCID: parserFunc{setDHCID, true}, + TypeDLV: parserFunc{setDLV, true}, + TypeDNAME: parserFunc{setDNAME, false}, + TypeKEY: parserFunc{setKEY, true}, + TypeDNSKEY: parserFunc{setDNSKEY, true}, + TypeDS: parserFunc{setDS, true}, + TypeEID: parserFunc{setEID, true}, + TypeEUI48: parserFunc{setEUI48, false}, + TypeEUI64: parserFunc{setEUI64, false}, + TypeGID: parserFunc{setGID, false}, + TypeGPOS: parserFunc{setGPOS, false}, + TypeHINFO: parserFunc{setHINFO, false}, + TypeHIP: parserFunc{setHIP, true}, + TypeIPSECKEY: parserFunc{setIPSECKEY, true}, + TypeKX: parserFunc{setKX, false}, + TypeL32: parserFunc{setL32, false}, + TypeL64: parserFunc{setL64, false}, + TypeLOC: parserFunc{setLOC, true}, + TypeLP: parserFunc{setLP, false}, + TypeMB: parserFunc{setMB, false}, + TypeMD: parserFunc{setMD, false}, + TypeMF: parserFunc{setMF, false}, + TypeMG: parserFunc{setMG, false}, + TypeMINFO: parserFunc{setMINFO, false}, + TypeMR: parserFunc{setMR, false}, + TypeMX: parserFunc{setMX, false}, + TypeNAPTR: parserFunc{setNAPTR, false}, + TypeNID: parserFunc{setNID, false}, + TypeNIMLOC: parserFunc{setNIMLOC, true}, + TypeNINFO: parserFunc{setNINFO, true}, + TypeNSAP: parserFunc{setNSAP, true}, + TypeNSAPPTR: parserFunc{setNSAPPTR, false}, + TypeNSEC3PARAM: parserFunc{setNSEC3PARAM, false}, + TypeNSEC3: parserFunc{setNSEC3, true}, + TypeNSEC: parserFunc{setNSEC, true}, + TypeNS: parserFunc{setNS, false}, + TypeOPENPGPKEY: parserFunc{setOPENPGPKEY, true}, + TypePTR: parserFunc{setPTR, false}, + TypePX: parserFunc{setPX, false}, + TypeSIG: parserFunc{setSIG, true}, + TypeRKEY: parserFunc{setRKEY, true}, + TypeRP: parserFunc{setRP, false}, + TypeRRSIG: parserFunc{setRRSIG, true}, + TypeRT: parserFunc{setRT, false}, + TypeSOA: parserFunc{setSOA, false}, + TypeSPF: parserFunc{setSPF, true}, + TypeSRV: parserFunc{setSRV, false}, + TypeSSHFP: parserFunc{setSSHFP, false}, + TypeTALINK: parserFunc{setTALINK, false}, + TypeTA: parserFunc{setTA, true}, + TypeTLSA: parserFunc{setTLSA, true}, + TypeTXT: parserFunc{setTXT, true}, + TypeUID: parserFunc{setUID, false}, + TypeUINFO: parserFunc{setUINFO, true}, + TypeURI: parserFunc{setURI, true}, + TypeWKS: parserFunc{setWKS, true}, + TypeX25: parserFunc{setX25, false}, +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go new file mode 100644 index 00000000000..564363a1b76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go @@ -0,0 +1,95 @@ +// Copyright (c) 2014 The SkyDNS Authors. All rights reserved. +// Use of this source code is governed by The MIT License (MIT) that can be +// found in the LICENSE file. + +package msg + +import ( + "net" + "path" + "strings" + + "github.com/miekg/dns" +) + +// This *is* the rdata from a SRV record, but with a twist. +// Host (Target in SRV) must be a domain name, but if it looks like an IP +// address (4/6), we will treat it like an IP address. +type Service struct { + Host string `json:"host,omitempty"` + Port int `json:"port,omitempty"` + Priority int `json:"priority,omitempty"` + Weight int `json:"weight,omitempty"` + Ttl uint32 `json:"ttl,omitempty"` + // etcd key where we found this service and ignore from json un-/marshalling + Key string `json:"-"` +} + +// NewSRV returns a new SRV record based on the Service. +func (s *Service) NewSRV(name string, weight uint16) *dns.SRV { + return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.Ttl}, + Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: dns.Fqdn(s.Host)} +} + +// NewA returns a new A record based on the Service. +func (s *Service) NewA(name string, ip net.IP) *dns.A { + return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.Ttl}, A: ip} +} + +// NewAAAA returns a new AAAA record based on the Service. +func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA { + return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.Ttl}, AAAA: ip} +} + +// NewCNAME returns a new CNAME record based on the Service. +func (s *Service) NewCNAME(name string, target string) *dns.CNAME { + return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.Ttl}, Target: target} +} + +// NewNS returns a new NS record based on the Service. +func (s *Service) NewNS(name string, target string) *dns.NS { + return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.Ttl}, Ns: target} +} + +// NewPTR returns a new PTR record based on the Service. +func (s *Service) NewPTR(name string, ttl uint32) *dns.PTR { + return &dns.PTR{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}, Ptr: dns.Fqdn(s.Host)} +} + +// As Path, but +// if a name contains wildcards (*), the name will be chopped of before the (first) wildcard, and +// we do a highler evel search and later find the matching names. +// So service.*.skydns.local, will look for all services under skydns.local and will later check +// for names that match service.*.skydns.local. If a wildcard is found the returned bool is true. +func PathWithWildcard(s string) (string, bool) { + l := dns.SplitDomainName(s) + for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { + l[i], l[j] = l[j], l[i] + } + for i, k := range l { + if k == "*" { + return path.Join(append([]string{"/skydns/"}, l[:i]...)...), true + } + } + return path.Join(append([]string{"/skydns/"}, l...)...), false +} + +// Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local., +// the resulting key will be /skydns/local/skydns/staging/service . +func Path(s string) string { + l := dns.SplitDomainName(s) + for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { + l[i], l[j] = l[j], l[i] + } + return path.Join(append([]string{"/skydns/"}, l...)...) +} + +// Domain is the opposite of Path. +func Domain(s string) string { + l := strings.Split(s, "/") + // start with 1, to strip /skydns + for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 { + l[i], l[j] = l[j], l[i] + } + return dns.Fqdn(strings.Join(l[1:len(l)-1], ".")) +} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/.gitignore b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/.gitignore deleted file mode 100644 index 1f0a99f2f2b..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -[568].out -_go* -_test* -_obj diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/License b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/License deleted file mode 100644 index 480a3280599..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/License +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2012 Keith Rarick - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/Readme b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/Readme deleted file mode 100644 index c589fc622b3..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/Readme +++ /dev/null @@ -1,9 +0,0 @@ -package pretty - - import "github.com/kr/pretty" - - Package pretty provides pretty-printing for Go values. - -Documentation - - http://godoc.org/github.com/kr/pretty diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog deleted file mode 100644 index 9350c05782b..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -golang-pretty (0.0~git20130613-1) unstable; urgency=low - - * Initial release. Closes: #722983 - - -- Tonnerre Lombard Wed, 11 Sep 2013 02:36:12 +0200 diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat deleted file mode 100644 index ec635144f60..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control deleted file mode 100644 index a852fe7fc3a..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control +++ /dev/null @@ -1,22 +0,0 @@ -Source: golang-pretty -Section: devel -Priority: extra -Maintainer: Tonnerre Lombard -Build-Depends: debhelper (>= 9), golang-go, dh-golang, - golang-text-dev -Standards-Version: 3.9.4 -Homepage: https://github.com/kr/pretty/ -Vcs-Git: git://anonscm.debian.org/pkg-go/packages/golang-pretty.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-go/packages/golang-pretty.git;a=summary - -Package: golang-pretty-dev -Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends}, golang-text-dev -Description: Pretty printing for go values - Package pretty provides pretty-printing for Go values. This is useful - during debugging, to avoid wrapping long output lines in the - terminal. - . - It provides a function, Formatter, that can be used with any function - that accepts a format string. It also provides convenience wrappers - for functions in packages fmt and log. diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright deleted file mode 100644 index 80b0807ee73..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright +++ /dev/null @@ -1,30 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: golang-pretty -Source: https://github.com/kr/pretty/ - -Files: * -Copyright: 2011, 2012, 2013 Keith Rarick -License: Expat - -Files: debian/* -Copyright: 2013 Tonnerre Lombard -License: Expat - -License: Expat - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - . - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs deleted file mode 100644 index 304360caba4..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs +++ /dev/null @@ -1 +0,0 @@ -Readme diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules deleted file mode 100644 index c48a3adc3f7..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/make -f - -# Uncomment this to turn on verbose mode. -export DH_VERBOSE=1 - -# DH_GOPKG is the upstream path which you would normally “go get”. -# Using it allows us to build applications without patching locations. -export DH_GOPKG := github.com/kr/pretty - -%: - dh $@ --buildsystem=golang --with=golang diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format deleted file mode 100644 index 163aaf8d82b..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff.go deleted file mode 100644 index 64fac6403c8..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff.go +++ /dev/null @@ -1,148 +0,0 @@ -package pretty - -import ( - "fmt" - "io" - "reflect" -) - -type sbuf []string - -func (s *sbuf) Write(b []byte) (int, error) { - *s = append(*s, string(b)) - return len(b), nil -} - -// Diff returns a slice where each element describes -// a difference between a and b. -func Diff(a, b interface{}) (desc []string) { - Fdiff((*sbuf)(&desc), a, b) - return desc -} - -// Fdiff writes to w a description of the differences between a and b. -func Fdiff(w io.Writer, a, b interface{}) { - diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) -} - -type diffWriter struct { - w io.Writer - l string // label -} - -func (w diffWriter) printf(f string, a ...interface{}) { - var l string - if w.l != "" { - l = w.l + ": " - } - fmt.Fprintf(w.w, l+f, a...) -} - -func (w diffWriter) diff(av, bv reflect.Value) { - if !av.IsValid() && bv.IsValid() { - w.printf("nil != %#v", bv.Interface()) - return - } - if av.IsValid() && !bv.IsValid() { - w.printf("%#v != nil", av.Interface()) - return - } - if !av.IsValid() && !bv.IsValid() { - return - } - - at := av.Type() - bt := bv.Type() - if at != bt { - w.printf("%v != %v", at, bt) - return - } - - // numeric types, including bool - if at.Kind() < reflect.Array { - a, b := av.Interface(), bv.Interface() - if a != b { - w.printf("%#v != %#v", a, b) - } - return - } - - switch at.Kind() { - case reflect.String: - a, b := av.Interface(), bv.Interface() - if a != b { - w.printf("%q != %q", a, b) - } - case reflect.Ptr: - switch { - case av.IsNil() && !bv.IsNil(): - w.printf("nil != %v", bv.Interface()) - case !av.IsNil() && bv.IsNil(): - w.printf("%v != nil", av.Interface()) - case !av.IsNil() && !bv.IsNil(): - w.diff(av.Elem(), bv.Elem()) - } - case reflect.Struct: - for i := 0; i < av.NumField(); i++ { - w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) - } - case reflect.Map: - ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) - for _, k := range ak { - w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) - w.printf("%q != (missing)", av.MapIndex(k)) - } - for _, k := range both { - w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) - w.diff(av.MapIndex(k), bv.MapIndex(k)) - } - for _, k := range bk { - w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) - w.printf("(missing) != %q", bv.MapIndex(k)) - } - case reflect.Interface: - w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface())) - default: - if !reflect.DeepEqual(av.Interface(), bv.Interface()) { - w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface())) - } - } -} - -func (d diffWriter) relabel(name string) (d1 diffWriter) { - d1 = d - if d.l != "" && name[0] != '[' { - d1.l += "." - } - d1.l += name - return d1 -} - -func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { - for _, av := range a { - inBoth := false - for _, bv := range b { - if reflect.DeepEqual(av.Interface(), bv.Interface()) { - inBoth = true - both = append(both, av) - break - } - } - if !inBoth { - ak = append(ak, av) - } - } - for _, bv := range b { - inBoth := false - for _, av := range a { - if reflect.DeepEqual(av.Interface(), bv.Interface()) { - inBoth = true - break - } - } - if !inBoth { - bk = append(bk, bv) - } - } - return -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go deleted file mode 100644 index 02d1953530e..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package pretty - -import ( - "testing" -) - -type difftest struct { - a interface{} - b interface{} - exp []string -} - -type S struct { - A int - S *S - I interface{} - C []int -} - -var diffs = []difftest{ - {a: nil, b: nil}, - {a: S{A: 1}, b: S{A: 1}}, - - {0, "", []string{`int != string`}}, - {0, 1, []string{`0 != 1`}}, - {S{}, new(S), []string{`pretty.S != *pretty.S`}}, - {"a", "b", []string{`"a" != "b"`}}, - {S{}, S{A: 1}, []string{`A: 0 != 1`}}, - {new(S), &S{A: 1}, []string{`A: 0 != 1`}}, - {S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}}, - {S{}, S{I: 0}, []string{`I: nil != 0`}}, - {S{I: 1}, S{I: "x"}, []string{`I: int != string`}}, - {S{}, S{C: []int{1}}, []string{`C: []int(nil) != []int{1}`}}, - {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}}, - {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 []}`}}, -} - -func TestDiff(t *testing.T) { - for _, tt := range diffs { - got := Diff(tt.a, tt.b) - eq := len(got) == len(tt.exp) - if eq { - for i := range got { - eq = eq && got[i] == tt.exp[i] - } - } - if !eq { - t.Errorf("diffing % #v", tt.a) - t.Errorf("with % #v", tt.b) - diffdiff(t, got, tt.exp) - continue - } - } -} - -func diffdiff(t *testing.T, got, exp []string) { - minus(t, "unexpected:", got, exp) - minus(t, "missing:", exp, got) -} - -func minus(t *testing.T, s string, a, b []string) { - var i, j int - for i = 0; i < len(a); i++ { - for j = 0; j < len(b); j++ { - if a[i] == b[j] { - break - } - } - if j == len(b) { - t.Error(s, a[i]) - } - } -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go deleted file mode 100644 index ecf40f3fcc6..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package pretty_test - -import ( - "fmt" - "github.com/kr/pretty" -) - -func Example() { - type myType struct { - a, b int - } - var x = []myType{{1, 2}, {3, 4}, {5, 6}} - fmt.Printf("%# v", pretty.Formatter(x)) - // output: - // []pretty_test.myType{ - // {a:1, b:2}, - // {a:3, b:4}, - // {a:5, b:6}, - // } -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go deleted file mode 100644 index 1161de7ee04..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go +++ /dev/null @@ -1,300 +0,0 @@ -package pretty - -import ( - "fmt" - "github.com/kr/text" - "io" - "reflect" - "strconv" - "text/tabwriter" -) - -const ( - limit = 50 -) - -type formatter struct { - x interface{} - force bool - quote bool -} - -// Formatter makes a wrapper, f, that will format x as go source with line -// breaks and tabs. Object f responds to the "%v" formatting verb when both the -// "#" and " " (space) flags are set, for example: -// -// fmt.Sprintf("%# v", Formatter(x)) -// -// If one of these two flags is not set, or any other verb is used, f will -// format x according to the usual rules of package fmt. -// In particular, if x satisfies fmt.Formatter, then x.Format will be called. -func Formatter(x interface{}) (f fmt.Formatter) { - return formatter{x: x, quote: true} -} - -func (fo formatter) String() string { - return fmt.Sprint(fo.x) // unwrap it -} - -func (fo formatter) passThrough(f fmt.State, c rune) { - s := "%" - for i := 0; i < 128; i++ { - if f.Flag(i) { - s += string(i) - } - } - if w, ok := f.Width(); ok { - s += fmt.Sprintf("%d", w) - } - if p, ok := f.Precision(); ok { - s += fmt.Sprintf(".%d", p) - } - s += string(c) - fmt.Fprintf(f, s, fo.x) -} - -func (fo formatter) Format(f fmt.State, c rune) { - if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { - w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) - p := &printer{tw: w, Writer: w} - p.printValue(reflect.ValueOf(fo.x), true, fo.quote) - w.Flush() - return - } - fo.passThrough(f, c) -} - -type printer struct { - io.Writer - tw *tabwriter.Writer -} - -func (p *printer) indent() *printer { - q := *p - q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) - q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) - return &q -} - -func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { - if showType { - io.WriteString(p, v.Type().String()) - fmt.Fprintf(p, "(%#v)", x) - } else { - fmt.Fprintf(p, "%#v", x) - } -} - -func (p *printer) printValue(v reflect.Value, showType, quote bool) { - switch v.Kind() { - case reflect.Bool: - p.printInline(v, v.Bool(), showType) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - p.printInline(v, v.Int(), showType) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - p.printInline(v, v.Uint(), showType) - case reflect.Float32, reflect.Float64: - p.printInline(v, v.Float(), showType) - case reflect.Complex64, reflect.Complex128: - fmt.Fprintf(p, "%#v", v.Complex()) - case reflect.String: - p.fmtString(v.String(), quote) - case reflect.Map: - t := v.Type() - if showType { - io.WriteString(p, t.String()) - } - writeByte(p, '{') - if nonzero(v) { - expand := !canInline(v.Type()) - pp := p - if expand { - writeByte(p, '\n') - pp = p.indent() - } - keys := v.MapKeys() - for i := 0; i < v.Len(); i++ { - showTypeInStruct := true - k := keys[i] - mv := v.MapIndex(k) - pp.printValue(k, false, true) - writeByte(pp, ':') - if expand { - writeByte(pp, '\t') - } - showTypeInStruct = t.Elem().Kind() == reflect.Interface - pp.printValue(mv, showTypeInStruct, true) - if expand { - io.WriteString(pp, ",\n") - } else if i < v.Len()-1 { - io.WriteString(pp, ", ") - } - } - if expand { - pp.tw.Flush() - } - } - writeByte(p, '}') - case reflect.Struct: - t := v.Type() - if showType { - io.WriteString(p, t.String()) - } - writeByte(p, '{') - if nonzero(v) { - expand := !canInline(v.Type()) - pp := p - if expand { - writeByte(p, '\n') - pp = p.indent() - } - for i := 0; i < v.NumField(); i++ { - showTypeInStruct := true - if f := t.Field(i); f.Name != "" { - io.WriteString(pp, f.Name) - writeByte(pp, ':') - if expand { - writeByte(pp, '\t') - } - showTypeInStruct = f.Type.Kind() == reflect.Interface - } - pp.printValue(getField(v, i), showTypeInStruct, true) - if expand { - io.WriteString(pp, ",\n") - } else if i < v.NumField()-1 { - io.WriteString(pp, ", ") - } - } - if expand { - pp.tw.Flush() - } - } - writeByte(p, '}') - case reflect.Interface: - switch e := v.Elem(); { - case e.Kind() == reflect.Invalid: - io.WriteString(p, "nil") - case e.IsValid(): - p.printValue(e, showType, true) - default: - io.WriteString(p, v.Type().String()) - io.WriteString(p, "(nil)") - } - case reflect.Array, reflect.Slice: - t := v.Type() - if showType { - io.WriteString(p, t.String()) - } - if v.Kind() == reflect.Slice && v.IsNil() && showType { - io.WriteString(p, "(nil)") - break - } - if v.Kind() == reflect.Slice && v.IsNil() { - io.WriteString(p, "nil") - break - } - writeByte(p, '{') - expand := !canInline(v.Type()) - pp := p - if expand { - writeByte(p, '\n') - pp = p.indent() - } - for i := 0; i < v.Len(); i++ { - showTypeInSlice := t.Elem().Kind() == reflect.Interface - pp.printValue(v.Index(i), showTypeInSlice, true) - if expand { - io.WriteString(pp, ",\n") - } else if i < v.Len()-1 { - io.WriteString(pp, ", ") - } - } - if expand { - pp.tw.Flush() - } - writeByte(p, '}') - case reflect.Ptr: - e := v.Elem() - if !e.IsValid() { - writeByte(p, '(') - io.WriteString(p, v.Type().String()) - io.WriteString(p, ")(nil)") - } else { - writeByte(p, '&') - p.printValue(e, true, true) - } - case reflect.Chan: - x := v.Pointer() - if showType { - writeByte(p, '(') - io.WriteString(p, v.Type().String()) - fmt.Fprintf(p, ")(%#v)", x) - } else { - fmt.Fprintf(p, "%#v", x) - } - case reflect.Func: - io.WriteString(p, v.Type().String()) - io.WriteString(p, " {...}") - case reflect.UnsafePointer: - p.printInline(v, v.Pointer(), showType) - case reflect.Invalid: - io.WriteString(p, "nil") - } -} - -func canInline(t reflect.Type) bool { - switch t.Kind() { - case reflect.Map: - return !canExpand(t.Elem()) - case reflect.Struct: - for i := 0; i < t.NumField(); i++ { - if canExpand(t.Field(i).Type) { - return false - } - } - return true - case reflect.Interface: - return false - case reflect.Array, reflect.Slice: - return !canExpand(t.Elem()) - case reflect.Ptr: - return false - case reflect.Chan, reflect.Func, reflect.UnsafePointer: - return false - } - return true -} - -func canExpand(t reflect.Type) bool { - switch t.Kind() { - case reflect.Map, reflect.Struct, - reflect.Interface, reflect.Array, reflect.Slice, - reflect.Ptr: - return true - } - return false -} - -func (p *printer) fmtString(s string, quote bool) { - if quote { - s = strconv.Quote(s) - } - io.WriteString(p, s) -} - -func tryDeepEqual(a, b interface{}) bool { - defer func() { recover() }() - return reflect.DeepEqual(a, b) -} - -func writeByte(w io.Writer, b byte) { - w.Write([]byte{b}) -} - -func getField(v reflect.Value, i int) reflect.Value { - val := v.Field(i) - if val.Kind() == reflect.Interface && !val.IsNil() { - val = val.Elem() - } - return val -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go deleted file mode 100644 index 303f033c432..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package pretty - -import ( - "fmt" - "io" - "testing" - "unsafe" -) - -type test struct { - v interface{} - s string -} - -type LongStructTypeName struct { - longFieldName interface{} - otherLongFieldName interface{} -} - -type SA struct { - t *T -} - -type T struct { - x, y int -} - -type F int - -func (f F) Format(s fmt.State, c rune) { - fmt.Fprintf(s, "F(%d)", int(f)) -} - -var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -var gosyntax = []test{ - {nil, `nil`}, - {"", `""`}, - {"a", `"a"`}, - {1, "int(1)"}, - {1.0, "float64(1)"}, - {[]int(nil), "[]int(nil)"}, - {[0]int{}, "[0]int{}"}, - {complex(1, 0), "(1+0i)"}, - //{make(chan int), "(chan int)(0x1234)"}, - {unsafe.Pointer(uintptr(1)), "unsafe.Pointer(0x1)"}, - {func(int) {}, "func(int) {...}"}, - {map[int]int{1: 1}, "map[int]int{1:1}"}, - {int32(1), "int32(1)"}, - {io.EOF, `&errors.errorString{s:"EOF"}`}, - {[]string{"a"}, `[]string{"a"}`}, - { - []string{long}, - `[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`, - }, - {F(5), "pretty.F(5)"}, - { - SA{&T{1, 2}}, - `pretty.SA{ - t: &pretty.T{x:1, y:2}, -}`, - }, - { - map[int][]byte{1: []byte{}}, - `map[int][]uint8{ - 1: {}, -}`, - }, - { - map[int]T{1: T{}}, - `map[int]pretty.T{ - 1: {}, -}`, - }, - { - long, - `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`, - }, - { - LongStructTypeName{ - longFieldName: LongStructTypeName{}, - otherLongFieldName: long, - }, - `pretty.LongStructTypeName{ - longFieldName: pretty.LongStructTypeName{}, - otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", -}`, - }, - { - &LongStructTypeName{ - longFieldName: &LongStructTypeName{}, - otherLongFieldName: (*LongStructTypeName)(nil), - }, - `&pretty.LongStructTypeName{ - longFieldName: &pretty.LongStructTypeName{}, - otherLongFieldName: (*pretty.LongStructTypeName)(nil), -}`, - }, - { - []LongStructTypeName{ - {nil, nil}, - {3, 3}, - {long, nil}, - }, - `[]pretty.LongStructTypeName{ - {}, - { - longFieldName: int(3), - otherLongFieldName: int(3), - }, - { - longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - otherLongFieldName: nil, - }, -}`, - }, - { - []interface{}{ - LongStructTypeName{nil, nil}, - []byte{1, 2, 3}, - T{3, 4}, - LongStructTypeName{long, nil}, - }, - `[]interface {}{ - pretty.LongStructTypeName{}, - []uint8{0x1, 0x2, 0x3}, - pretty.T{x:3, y:4}, - pretty.LongStructTypeName{ - longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", - otherLongFieldName: nil, - }, -}`, - }, -} - -func TestGoSyntax(t *testing.T) { - for _, tt := range gosyntax { - s := fmt.Sprintf("%# v", Formatter(tt.v)) - if tt.s != s { - t.Errorf("expected %q", tt.s) - t.Errorf("got %q", s) - t.Errorf("expraw\n%s", tt.s) - t.Errorf("gotraw\n%s", s) - } - } -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/pretty.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/pretty.go deleted file mode 100644 index d3df8686ce8..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/pretty.go +++ /dev/null @@ -1,98 +0,0 @@ -// Package pretty provides pretty-printing for Go values. This is -// useful during debugging, to avoid wrapping long output lines in -// the terminal. -// -// It provides a function, Formatter, that can be used with any -// function that accepts a format string. It also provides -// convenience wrappers for functions in packages fmt and log. -package pretty - -import ( - "fmt" - "io" - "log" -) - -// Errorf is a convenience wrapper for fmt.Errorf. -// -// Calling Errorf(f, x, y) is equivalent to -// fmt.Errorf(f, Formatter(x), Formatter(y)). -func Errorf(format string, a ...interface{}) error { - return fmt.Errorf(format, wrap(a, false)...) -} - -// Fprintf is a convenience wrapper for fmt.Fprintf. -// -// Calling Fprintf(w, f, x, y) is equivalent to -// fmt.Fprintf(w, f, Formatter(x), Formatter(y)). -func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) { - return fmt.Fprintf(w, format, wrap(a, false)...) -} - -// Log is a convenience wrapper for log.Printf. -// -// Calling Log(x, y) is equivalent to -// log.Print(Formatter(x), Formatter(y)), but each operand is -// formatted with "%# v". -func Log(a ...interface{}) { - log.Print(wrap(a, true)...) -} - -// Logf is a convenience wrapper for log.Printf. -// -// Calling Logf(f, x, y) is equivalent to -// log.Printf(f, Formatter(x), Formatter(y)). -func Logf(format string, a ...interface{}) { - log.Printf(format, wrap(a, false)...) -} - -// Logln is a convenience wrapper for log.Printf. -// -// Calling Logln(x, y) is equivalent to -// log.Println(Formatter(x), Formatter(y)), but each operand is -// formatted with "%# v". -func Logln(a ...interface{}) { - log.Println(wrap(a, true)...) -} - -// Print pretty-prints its operands and writes to standard output. -// -// Calling Print(x, y) is equivalent to -// fmt.Print(Formatter(x), Formatter(y)), but each operand is -// formatted with "%# v". -func Print(a ...interface{}) (n int, errno error) { - return fmt.Print(wrap(a, true)...) -} - -// Printf is a convenience wrapper for fmt.Printf. -// -// Calling Printf(f, x, y) is equivalent to -// fmt.Printf(f, Formatter(x), Formatter(y)). -func Printf(format string, a ...interface{}) (n int, errno error) { - return fmt.Printf(format, wrap(a, false)...) -} - -// Println pretty-prints its operands and writes to standard output. -// -// Calling Print(x, y) is equivalent to -// fmt.Println(Formatter(x), Formatter(y)), but each operand is -// formatted with "%# v". -func Println(a ...interface{}) (n int, errno error) { - return fmt.Println(wrap(a, true)...) -} - -// Sprintf is a convenience wrapper for fmt.Sprintf. -// -// Calling Sprintf(f, x, y) is equivalent to -// fmt.Sprintf(f, Formatter(x), Formatter(y)). -func Sprintf(format string, a ...interface{}) string { - return fmt.Sprintf(format, wrap(a, false)...) -} - -func wrap(a []interface{}, force bool) []interface{} { - w := make([]interface{}, len(a)) - for i, x := range a { - w[i] = formatter{x: x, force: force} - } - return w -} diff --git a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/zero.go b/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/zero.go deleted file mode 100644 index abb5b6fc14c..00000000000 --- a/Godeps/_workspace/src/github.com/tonnerre/golang-pretty/zero.go +++ /dev/null @@ -1,41 +0,0 @@ -package pretty - -import ( - "reflect" -) - -func nonzero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() != 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() != 0 - case reflect.Float32, reflect.Float64: - return v.Float() != 0 - case reflect.Complex64, reflect.Complex128: - return v.Complex() != complex(0, 0) - case reflect.String: - return v.String() != "" - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if nonzero(getField(v, i)) { - return true - } - } - return false - case reflect.Array: - for i := 0; i < v.Len(); i++ { - if nonzero(v.Index(i)) { - return true - } - } - return false - case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func: - return !v.IsNil() - case reflect.UnsafePointer: - return v.Pointer() != 0 - } - return true -}