From 3ec5d920bbb2866df8b5001cfc5cd336780c0c91 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 8 Mar 2015 20:42:40 -0400 Subject: [PATCH 1/4] Make it easy to export certs to curl Now you can run `source hack/export-certs.sh ` and the proper certs are set in your env --- hack/export-certs.sh | 29 +++++++++++++++++++++++++ hack/util.sh | 51 ++++++++++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 18 deletions(-) create mode 100755 hack/export-certs.sh diff --git a/hack/export-certs.sh b/hack/export-certs.sh new file mode 100755 index 000000000000..e1ec461bbf2a --- /dev/null +++ b/hack/export-certs.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# This command attempts to export the correct arguments for a curl client. +# Exports CURL_ARGS which should be used with curl: +# +# $ source hack/export-certs.sh ./openshift.local.certificates/admin +# $ curl $CURL_ARGS + +set -o errexit +set -o nounset +set -o pipefail + +OS_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${OS_ROOT}/hack/util.sh" + +set -e + +DEF="${1:-}" +CERT_DIR="${CERT_DIR:-$DEF}" +if [[ -z "${CERT_DIR}" ]]; then + echo "Please set CERT_DIR or pass an argument corresponding to the directory to use for loading certificates" + exit 1 +fi + +export CURL_CA_BUNDLE="${CERT_DIR}/root.crt" +export CURL_CERT="${CERT_DIR}/cert.crt" +export CURL_KEY="${CERT_DIR}/key.key" + +set_curl_args \ No newline at end of file diff --git a/hack/util.sh b/hack/util.sh index fe4ad16fc0a4..47e3553f59d2 100644 --- a/hack/util.sh +++ b/hack/util.sh @@ -111,6 +111,35 @@ function wait_for_url { wait=${3:-0.2} times=${4:-10} + set_curl_args $wait $times + + set +e + for i in $(seq 1 $times); do + out=$(curl ${clientcert_args} -fs $url 2>/dev/null) + if [ $? -eq 0 ]; then + set -e + echo ${prefix}${out} + return 0 + fi + sleep $wait + done + echo "ERROR: gave up waiting for $url" + curl ${CURL_ARGS} $url + set -e + return 1 +} + +# set_curl_args tries to export CURL_ARGS for a program to use. +# will do a wait for the files to exist when using curl with +# SecureTransport (because we must convert the keys to a different +# form). +# +# $1 - Optional time to sleep between attempts (Default: 0.2s) +# $2 - Optional number of attemps to make (Default: 10) +function set_curl_args { + wait=${1:-0.2} + times=${2:-10} + CURL_CERT=${CURL_CERT:-} CURL_KEY=${CURL_KEY:-} clientcert_args="" @@ -119,9 +148,9 @@ function wait_for_url { if [ -n "${CURL_KEY}" ]; then if [[ `curl -V` == *"SecureTransport"* ]]; then # Convert to a p12 cert for SecureTransport - CURL_CERT_DIR=$(dirname "${CURL_CERT}") - CURL_CERT_P12=${CURL_CERT_P12:-${CURL_CERT_DIR}/cert.p12} - CURL_CERT_P12_PASSWORD=${CURL_CERT_P12_PASSWORD:-password} + export CURL_CERT_DIR=$(dirname "${CURL_CERT}") + export CURL_CERT_P12=${CURL_CERT_P12:-${CURL_CERT_DIR}/cert.p12} + export CURL_CERT_P12_PASSWORD=${CURL_CERT_P12_PASSWORD:-password} if [ ! -f "${CURL_CERT_P12}" ]; then wait_for_file "${CURL_CERT}" $wait $times wait_for_file "${CURL_KEY}" $wait $times @@ -133,21 +162,7 @@ function wait_for_url { fi fi fi - - set +e - for i in $(seq 1 $times); do - out=$(curl ${clientcert_args} -fs $url 2>/dev/null) - if [ $? -eq 0 ]; then - set -e - echo ${prefix}${out} - return 0 - fi - sleep $wait - done - echo "ERROR: gave up waiting for $url" - curl ${clientcert_args} $url - set -e - return 1 + export CURL_ARGS="${clientcert_args}" } # Search for a regular expression in a HTTP response. From 26b535aa45f8a070aba19570dd751df64c64c70b Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 7 Mar 2015 23:15:35 -0500 Subject: [PATCH 2/4] bump(github.com/skynetservices/skydns):f18bd625a71b5d013b6e6288d1c7ec8796a80188 --- Godeps/Godeps.json | 29 + .../coreos/go-systemd/activation/files.go | 54 + .../go-systemd/activation/files_test.go | 82 + .../coreos/go-systemd/activation/listeners.go | 40 + .../go-systemd/activation/listeners_test.go | 86 + .../go-systemd/activation/packetconns.go | 39 + .../go-systemd/activation/packetconns_test.go | 68 + .../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 +++++++++++++++++ .../skydns/backends/etcd/etcd.go | 231 ++ .../skynetservices/skydns/cache/cache.go | 175 ++ .../skynetservices/skydns/msg/service.go | 122 + .../skynetservices/skydns/msg/service_test.go | 41 + .../skynetservices/skydns/server/config.go | 141 ++ .../skynetservices/skydns/server/dnssec.go | 161 ++ .../skynetservices/skydns/server/doc.go | 8 + .../skydns/server/forwarding.go | 133 + .../skynetservices/skydns/server/nsec3.go | 155 ++ .../skynetservices/skydns/server/server.go | 713 ++++++ .../skydns/server/server_test.go | 897 +++++++ .../skydns/server/singleinflight.go | 54 + .../skynetservices/skydns/server/stats.go | 25 + 73 files changed, 19614 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns_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/backends/etcd/etcd.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/cache/cache.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service_test.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/config.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/dnssec.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/doc.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/forwarding.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/nsec3.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/server_test.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/singleinflight.go create mode 100644 Godeps/_workspace/src/github.com/skynetservices/skydns/server/stats.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 72dd0ca9e775..2f0ee6e2ee3f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -448,6 +448,11 @@ "Comment": "v0.2.0-rc1-120-g23142f6", "Rev": "23142f6773a676cc2cae8dd0cb90b2ea761c853f" }, + { + "ImportPath": "github.com/coreos/go-systemd/activation", + "Comment": "v2-43-g2d21675", + "Rev": "2d21675230a81a503f4363f4aa3490af06d52bb8" + }, { "ImportPath": "github.com/coreos/go-systemd/daemon", "Rev": "2d21675230a81a503f4363f4aa3490af06d52bb8" @@ -594,6 +599,10 @@ "ImportPath": "github.com/matttproud/golang_protobuf_extensions/ext", "Rev": "7a864a042e844af638df17ebbabf8183dace556a" }, + { + "ImportPath": "github.com/miekg/dns", + "Rev": "3f504e8dabd5d562e997d19ce0200aa41973e1b2" + }, { "ImportPath": "github.com/openshift/source-to-image/pkg/api", "Comment": "v0.2", @@ -672,6 +681,26 @@ "ImportPath": "github.com/prometheus/procfs", "Rev": "92faa308558161acab0ada1db048e9996ecec160" }, + { + "ImportPath": "github.com/skynetservices/skydns/backends/etcd", + "Comment": "2.0.1d-50-g73f6fae", + "Rev": "73f6fae00933f23a08b1e436537721e499d4410a" + }, + { + "ImportPath": "github.com/skynetservices/skydns/cache", + "Comment": "2.0.1d-50-g73f6fae", + "Rev": "73f6fae00933f23a08b1e436537721e499d4410a" + }, + { + "ImportPath": "github.com/skynetservices/skydns/msg", + "Comment": "2.0.1d-50-g73f6fae", + "Rev": "73f6fae00933f23a08b1e436537721e499d4410a" + }, + { + "ImportPath": "github.com/skynetservices/skydns/server", + "Comment": "2.0.1d-50-g73f6fae", + "Rev": "73f6fae00933f23a08b1e436537721e499d4410a" + }, { "ImportPath": "github.com/spf13/cobra", "Rev": "f8e1ec56bdd7494d309c69681267859a6bfb7549" diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files.go new file mode 100644 index 000000000000..4e6e77a10f4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files.go @@ -0,0 +1,54 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package activation implements primitives for systemd socket activation. +package activation + +import ( + "os" + "strconv" + "syscall" +) + +// based on: https://gist.github.com/alberts/4640792 +const ( + listenFdsStart = 3 +) + +func Files(unsetEnv bool) []*os.File { + if unsetEnv { + // there is no way to unset env in golang os package for now + // https://code.google.com/p/go/issues/detail?id=6423 + defer os.Setenv("LISTEN_PID", "") + defer os.Setenv("LISTEN_FDS", "") + } + + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds == 0 { + return nil + } + + var files []*os.File + for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { + syscall.CloseOnExec(fd) + files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) + } + + return files +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files_test.go new file mode 100644 index 000000000000..8e15f2a10e3b --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/files_test.go @@ -0,0 +1,82 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "bytes" + "io" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWritten(t *testing.T, r *os.File, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestActivation(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + + r1, w1, _ := os.Pipe() + r2, w2, _ := os.Pipe() + cmd.ExtraFiles = []*os.File{ + w1, + w2, + } + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + err := cmd.Run() + if err != nil { + t.Fatalf(err.Error()) + } + + correctStringWritten(t, r1, "Hello world") + correctStringWritten(t, r2, "Goodbye world") +} + +func TestActivationNoFix(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} + +func TestActivationNoFiles(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners.go new file mode 100644 index 000000000000..e83d8193a9ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners.go @@ -0,0 +1,40 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "net" +) + +// Listeners returns a slice containing a net.Listener for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener} +func Listeners(unsetEnv bool) ([]net.Listener, error) { + files := Files(unsetEnv) + listeners := make([]net.Listener, 0) + + for i := 0; i < len(files); i++ { + if pc, err := net.FileListener(files[i]); err == nil { + listeners = append(listeners, pc) + continue + } else { + listeners = append(listeners, nil) + } + } + return listeners, nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners_test.go new file mode 100644 index 000000000000..72fb0ff62820 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/listeners_test.go @@ -0,0 +1,86 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "io" + "net" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestListeners(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/listen.go") + + l1, err := net.Listen("tcp", ":9999") + if err != nil { + t.Fatalf(err.Error()) + } + l2, err := net.Listen("tcp", ":1234") + if err != nil { + t.Fatalf(err.Error()) + } + + t1 := l1.(*net.TCPListener) + t2 := l2.(*net.TCPListener) + + f1, _ := t1.File() + f2, _ := t2.File() + + cmd.ExtraFiles = []*os.File{ + f1, + f2, + } + + r1, err := net.Dial("tcp", "127.0.0.1:9999") + if err != nil { + t.Fatalf(err.Error()) + } + r1.Write([]byte("Hi")) + + r2, err := net.Dial("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatalf(err.Error()) + } + r2.Write([]byte("Hi")) + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + out, err := cmd.Output() + if err != nil { + println(string(out)) + t.Fatalf(err.Error()) + } + + correctStringWrittenNet(t, r1, "Hello world") + correctStringWrittenNet(t, r2, "Goodbye world") +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns.go new file mode 100644 index 000000000000..44124b216a0c --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns.go @@ -0,0 +1,39 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "net" +) + +// PacketConns returns a slice containing a net.PacketConn for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn} +func PacketConns(unsetEnv bool) ([]net.PacketConn, error) { + files := Files(unsetEnv) + conns := make([]net.PacketConn, 0) + for i := 0; i < len(files); i++ { + if pc, err := net.FilePacketConn(files[i]); err == nil { + conns = append(conns, pc) + continue + } else { + conns = append(conns, nil) + } + } + return conns, nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns_test.go new file mode 100644 index 000000000000..8449756cbe07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/activation/packetconns_test.go @@ -0,0 +1,68 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "net" + "os" + "os/exec" + "testing" +) + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestPacketConns(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/udpconn.go") + + u1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 9999}) + if err != nil { + t.Fatalf(err.Error()) + } + u2, err := net.ListenUDP("udp", &net.UDPAddr{Port: 1234}) + if err != nil { + t.Fatalf(err.Error()) + } + + f1, _ := u1.File() + f2, _ := u2.File() + + cmd.ExtraFiles = []*os.File{ + f1, + f2, + } + + r1, err := net.Dial("udp", "127.0.0.1:9999") + if err != nil { + t.Fatalf(err.Error()) + } + r1.Write([]byte("Hi")) + + r2, err := net.Dial("udp", "127.0.0.1:1234") + if err != nil { + t.Fatalf(err.Error()) + } + r2.Write([]byte("Hi")) + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Cmd output '%s', err: '%s'\n", out, err) + } + + correctStringWrittenNet(t, r1, "Hello world") + correctStringWrittenNet(t, r2, "Goodbye world") +} 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 000000000000..776cd950c25c --- /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 000000000000..4485679769dc --- /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 000000000000..1965683525ad --- /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 000000000000..f77e8a895f91 --- /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 000000000000..35702b10e87e --- /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 000000000000..5763fa7fe5d9 --- /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 000000000000..3cb850a0b07d --- /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 000000000000..ee8e22333b1a --- /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 000000000000..2113f3b0e5bb --- /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 000000000000..87cf89618622 --- /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 000000000000..0c8fa9c84b6b --- /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 000000000000..7540c0d5bca1 --- /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 000000000000..16c86f474f17 --- /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 000000000000..d1c2ae62582f --- /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 000000000000..f6263d505f46 --- /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 000000000000..09986a5e4ef8 --- /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 000000000000..8b676e612415 --- /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 000000000000..8ee82ab42641 --- /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 000000000000..1578a4d0539b --- /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 000000000000..8833cd91de13 --- /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 000000000000..faab4027b905 --- /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 000000000000..3202450aa52a --- /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 000000000000..dfe328ecd436 --- /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 000000000000..c48ca2d24b71 --- /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 000000000000..758e5783de5a --- /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 000000000000..1d8da155fd55 --- /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 000000000000..ecba463dddab --- /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 000000000000..ac48da0f5ac9 --- /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 000000000000..72f641a66731 --- /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 000000000000..dd2799d1f14a --- /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 000000000000..225713743c94 --- /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 000000000000..6f1dff89c6aa --- /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 000000000000..f138b7761dfd --- /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 000000000000..c29bc2f388e8 --- /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 000000000000..c250ccc26a16 --- /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 000000000000..5efb1cf89734 --- /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 000000000000..d96b31be06b4 --- /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 000000000000..6ca76fb848a2 --- /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 000000000000..9573c7d0b8c2 --- /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 000000000000..d3bc3b0217fe --- /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 000000000000..2c64ee8d548d --- /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 000000000000..16bb181b9b0c --- /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 000000000000..118612946bce --- /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 000000000000..0342543bf731 --- /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 000000000000..7a107857e133 --- /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 000000000000..c38dd3e7f0ed --- /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 000000000000..4c48723b565f --- /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 000000000000..275d4e6cc279 --- /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 000000000000..fc22536e38e6 --- /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 000000000000..57bfb1676f91 --- /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 000000000000..7f1183e87403 --- /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 000000000000..7ba51986953c --- /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 000000000000..5088cdb58e26 --- /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/backends/etcd/etcd.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/backends/etcd/etcd.go new file mode 100644 index 000000000000..795e6f0a528c --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/backends/etcd/etcd.go @@ -0,0 +1,231 @@ +// 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 etcd provides the default SkyDNS server Backend implementation, +// which looks up records stored under the `/skydns` key in etcd when queried. +package etcd + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + + "github.com/coreos/go-etcd/etcd" + "github.com/skynetservices/skydns/msg" +) + +// Config represents configuration for the Etcd backend - these values +// should be taken directly from server.Config +type Config struct { + Ttl uint32 + Priority uint16 +} + +type Backend struct { + client *etcd.Client + config *Config + inflight *etcdSingle +} + +// NewBackend returns a new Backend for SkyDNS, backed by etcd. +func NewBackend(client *etcd.Client, config *Config) *Backend { + return &Backend{ + client: client, + config: config, + inflight: new(etcdSingle), + } +} + +func (g *Backend) Records(name string, exact bool) ([]msg.Service, error) { + path, star := msg.PathWithWildcard(name) + r, err := g.get(path, true) + if err != nil { + return nil, err + } + segments := strings.Split(msg.Path(name), "/") + switch { + case exact && r.Node.Dir: + return nil, nil + case r.Node.Dir: + return g.loopNodes(&r.Node.Nodes, segments, star, nil) + default: + return g.loopNodes(&etcd.Nodes{r.Node}, segments, false, nil) + } +} + +func (g *Backend) ReverseRecord(name string) (*msg.Service, error) { + path, star := msg.PathWithWildcard(name) + if star { + return nil, fmt.Errorf("reverse can not contain wildcards") + } + r, err := g.get(path, true) + if err != nil { + return nil, err + } + if r.Node.Dir { + return nil, fmt.Errorf("reverse must not be a directory") + } + segments := strings.Split(msg.Path(name), "/") + records, err := g.loopNodes(&etcd.Nodes{r.Node}, segments, false, nil) + if err != nil { + return nil, err + } + if len(records) != 1 { + return nil, fmt.Errorf("must be only one service record") + } + return &records[0], nil +} + +// get is a wrapper for client.Get that uses SingleInflight to suppress multiple +// outstanding queries. +func (g *Backend) get(path string, recursive bool) (*etcd.Response, error) { + resp, err, _ := g.inflight.Do(path, func() (*etcd.Response, error) { + r, e := g.client.Get(path, false, recursive) + if e != nil { + return nil, e + } + return r, e + }) + if err != nil { + return resp, err + } + // shared? + return resp, err +} + +type bareService struct { + Host string + Port int + Priority int + Weight int + Text string +} + +// skydns/local/skydns/east/staging/web +// skydns/local/skydns/west/production/web +// +// skydns/local/skydns/*/*/web +// skydns/local/skydns/*/web + +// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname +// will be match against any wildcards when star is true. +func (g *Backend) loopNodes(n *etcd.Nodes, nameParts []string, star bool, bx map[bareService]bool) (sx []msg.Service, err error) { + if bx == nil { + bx = make(map[bareService]bool) + } +Nodes: + for _, n := range *n { + if n.Dir { + nodes, err := g.loopNodes(&n.Nodes, nameParts, star, bx) + if err != nil { + return nil, err + } + sx = append(sx, nodes...) + continue + } + if star { + keyParts := strings.Split(n.Key, "/") + for i, n := range nameParts { + if i > len(keyParts)-1 { + // name is longer than key + continue Nodes + } + if n == "*" { + continue + } + if keyParts[i] != n { + continue Nodes + } + } + } + serv := new(msg.Service) + if err := json.Unmarshal([]byte(n.Value), serv); err != nil { + return nil, err + } + b := bareService{serv.Host, serv.Port, serv.Priority, serv.Weight, serv.Text} + if _, ok := bx[b]; ok { + continue + } + bx[b] = true + + serv.Key = n.Key + serv.Ttl = g.calculateTtl(n, serv) + if serv.Priority == 0 { + serv.Priority = int(g.config.Priority) + } + sx = append(sx, *serv) + } + return sx, nil +} + +// calculateTtl returns the smaller of the etcd TTL and the service's +// TTL. If neither of these are set (have a zero value), the server +// default is used. +func (g *Backend) calculateTtl(node *etcd.Node, serv *msg.Service) uint32 { + etcdTtl := uint32(node.TTL) + + if etcdTtl == 0 && serv.Ttl == 0 { + return g.config.Ttl + } + if etcdTtl == 0 { + return serv.Ttl + } + if serv.Ttl == 0 { + return etcdTtl + } + if etcdTtl < serv.Ttl { + return etcdTtl + } + return serv.Ttl +} + +// Client exposes the underlying Etcd client. +func (g *Backend) Client() *etcd.Client { + return g.client +} + +// UpdateClient allows the etcd client used by this backend +// to be replaced on the fly. +func (g *Backend) UpdateClient(client *etcd.Client) { + g.client = client +} + +type etcdCall struct { + wg sync.WaitGroup + val *etcd.Response + err error + dups int +} + +type etcdSingle struct { + sync.Mutex + m map[string]*etcdCall +} + +func (g *etcdSingle) Do(key string, fn func() (*etcd.Response, error)) (*etcd.Response, error, bool) { + g.Lock() + if g.m == nil { + g.m = make(map[string]*etcdCall) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.Unlock() + c.wg.Wait() + return c.val, c.err, true + } + c := new(etcdCall) + c.wg.Add(1) + g.m[key] = c + g.Unlock() + + c.val, c.err = fn() + c.wg.Done() + + g.Lock() + delete(g.m, key) + g.Unlock() + + return c.val, c.err, c.dups > 0 +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/cache/cache.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/cache/cache.go new file mode 100644 index 000000000000..d0489e9bc542 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/cache/cache.go @@ -0,0 +1,175 @@ +// 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 cache + +// LRU cache that holds RRs and for DNSSEC an RRSIG. + +// TODO(miek): try to kill the mutex or at least don't write when we read. +// TODO(miek): split elem in a rrsig and msg one so we store RRSIGs more efficient. + +import ( + "container/list" + "crypto/sha1" + "sync" + "time" + + "github.com/miekg/dns" +) + +// Elem hold an answer and additional section that returned from the cache. +// The signature is put in answer, extra is empty there. This wastes some memory. +type elem struct { + key string + expiration time.Time // time added + TTL, after this the elem is invalid + msg *dns.Msg +} + +// Cache is a ... +type Cache struct { + sync.Mutex + l *list.List + m map[string]*list.Element + capacity uint // number of RRs + size uint // current size + ttl time.Duration +} + +// TODO(miek): add setCapacity so it can be set runtime. +// TODO(miek): makes this lockfree(er). + +// New returns a new cache with the capacity and the ttl specified. +func New(capacity, ttl int) *Cache { + c := new(Cache) + c.l = list.New() + c.m = make(map[string]*list.Element) + c.capacity = uint(capacity) + c.ttl = time.Duration(ttl) * time.Second + return c +} + +// Remove removes the element under key s from the cache. +func (c *Cache) Remove(s string) { + c.Lock() + defer c.Unlock() + e := c.m[s] + if e == nil { + return + } + c.size -= 1 + c.l.Remove(e) + delete(c.m, s) + c.shrink() +} + +// shrink ... +func (c *Cache) shrink() { + for c.size > c.capacity { + e := c.l.Back() + if e == nil { // nothing left + break + } + v := e.Value.(*elem) + c.l.Remove(e) + delete(c.m, v.key) + c.size -= uint(len(v.msg.Answer) + len(v.msg.Ns) + len(v.msg.Extra)) + } +} + +// InsertMessage inserts a message in the Cache. We will cache it for ttl seconds, which +// should be a small (60...300) integer. +func (c *Cache) InsertMessage(s string, msg *dns.Msg) { + if c.capacity == 0 { + return + } + c.Lock() + defer c.Unlock() + if _, ok := c.m[s]; !ok { + e := c.l.PushFront(&elem{s, time.Now().UTC().Add(c.ttl), msg}) + c.m[s] = e + } + c.size += uint(len(msg.Answer) + len(msg.Ns) + len(msg.Extra)) + c.shrink() +} + +// InsertSignature inserts a signature, the expiration time is used as the cache ttl. +func (c *Cache) InsertSignature(s string, sig *dns.RRSIG) { + if c.capacity == 0 { + return + } + c.Lock() + defer c.Unlock() + if _, ok := c.m[s]; !ok { + m := ((int64(sig.Expiration) - time.Now().Unix()) / (1 << 31)) - 1 + if m < 0 { + m = 0 + } + t := time.Unix(int64(sig.Expiration)-(m*(1<<31)), 0).UTC() + e := c.l.PushFront(&elem{s, t, &dns.Msg{Answer: []dns.RR{sig}}}) + c.m[s] = e + } + c.size += 1 + c.shrink() +} + +// Search returns .... and a boolean indicating if we found something +// in the cache. +func (c *Cache) Search(s string) (*dns.Msg, time.Time, bool) { + if c.capacity == 0 { + return nil, time.Time{}, false + } + c.Lock() + defer c.Unlock() + if e, ok := c.m[s]; ok { + c.l.MoveToFront(e) + e := e.Value.(*elem) + e1 := e.msg.Copy() + return e1, e.expiration, true + } + return nil, time.Time{}, false +} + +// QuestionKey creates a hash key from a question section. It creates a different key +// for requests with DNSSEC. +func QuestionKey(q dns.Question, dnssec bool) string { + h := sha1.New() + i := append([]byte(q.Name), packUint16(q.Qtype)...) + if dnssec { + i = append(i, byte(255)) + } + return string(h.Sum(i)) +} + +// Key uses the name, type and rdata, which is serialized and then hashed as the key for the lookup. +func Key(rrs []dns.RR) string { + h := sha1.New() + i := []byte(rrs[0].Header().Name) + i = append(i, packUint16(rrs[0].Header().Rrtype)...) + for _, r := range rrs { + switch t := r.(type) { // we only do a few type, serialize these manually + case *dns.SOA: + // We only fiddle with the serial so store that. + i = append(i, packUint32(t.Serial)...) + case *dns.SRV: + i = append(i, packUint16(t.Priority)...) + i = append(i, packUint16(t.Weight)...) + i = append(i, packUint16(t.Weight)...) + i = append(i, []byte(t.Target)...) + case *dns.A: + i = append(i, []byte(t.A)...) + case *dns.AAAA: + i = append(i, []byte(t.AAAA)...) + case *dns.NSEC3: + i = append(i, []byte(t.NextDomain)...) + // Bitmap does not differentiate in SkyDNS. + case *dns.DNSKEY: + case *dns.NS: + case *dns.TXT: + } + } + return string(h.Sum(i)) +} + +func packUint16(i uint16) []byte { return []byte{byte(i >> 8), byte(i)} } +func packUint32(i uint32) []byte { return []byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)} } 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 000000000000..a6da0afeb3b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service.go @@ -0,0 +1,122 @@ +// 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"` + Text string `json:"text,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} +} + +// NewTXT returns a new TXT record based on the Service. +func (s *Service) NewTXT(name string) *dns.TXT { + return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.Ttl}, Txt: split255(s.Text)} +} + +// 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], ".")) +} + +// Split255 splits a string into 255 byte chunks. +func split255(s string) []string { + if len(s) < 255 { + return []string{s} + } + sx := []string{} + p, i := 0, 255 + for { + if i <= len(s) { + sx = append(sx, s[p:i]) + } else { + sx = append(sx, s[p:]) + break + + } + p, i = p+255, i+255 + } + + return sx +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service_test.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service_test.go new file mode 100644 index 000000000000..4417a99170da --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/msg/service_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2015 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 "testing" + +func TestSplit255(t *testing.T) { + xs := split255("abc") + if len(xs) != 1 && xs[0] != "abc" { + t.Logf("Failure to split abc") + t.Fail() + } + s := "" + for i := 0; i < 255; i++ { + s += "a" + } + xs = split255(s) + if len(xs) != 1 && xs[0] != s { + t.Logf("Failure to split 255 char long string") + t.Logf("%s %v\n", s, xs) + t.Fail() + } + s += "b" + xs = split255(s) + if len(xs) != 2 || xs[1] != "b" { + t.Logf("Failure to split 256 char long string: %d", len(xs)) + t.Logf("%s %v\n", s, xs) + t.Fail() + } + for i := 0; i < 255; i++ { + s += "a" + } + xs = split255(s) + if len(xs) != 3 || xs[2] != "a" { + t.Logf("Failure to split 510 char long string: %d", len(xs)) + t.Logf("%s %v\n", s, xs) + t.Fail() + } +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/config.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/config.go new file mode 100644 index 000000000000..893a578cc04e --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/config.go @@ -0,0 +1,141 @@ +// 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 server + +import ( + "fmt" + "net" + "strings" + "time" + + "github.com/miekg/dns" +) + +const ( + SCacheCapacity = 10000 + RCacheCapacity = 100000 + RCacheTtl = 60 +) + +// Config provides options to the SkyDNS resolver. +type Config struct { + // The ip:port SkyDNS should be listening on for incoming DNS requests. + DnsAddr string `json:"dns_addr,omitempty"` + // bind to port(s) activated by systemd. If set to true, this overrides DnsAddr. + Systemd bool `json:"systemd,omitempty"` + // The domain SkyDNS is authoritative for, defaults to skydns.local. + Domain string `json:"domain,omitempty"` + // Domain pointing to a key where service info is stored when being queried + // for local.dns.skydns.local. + Local string `json:"local,omitempty"` + // The hostmaster responsible for this domain, defaults to hostmaster.. + Hostmaster string `json:"hostmaster,omitempty"` + DNSSEC string `json:"dnssec,omitempty"` + // Round robin A/AAAA replies. Default is true. + RoundRobin bool `json:"round_robin,omitempty"` + // List of ip:port, seperated by commas of recursive nameservers to forward queries to. + Nameservers []string `json:"nameservers,omitempty"` + ReadTimeout time.Duration `json:"read_timeout,omitempty"` + // Default priority on SRV records when none is given. Defaults to 10. + Priority uint16 `json:"priority"` + // Default TTL, in seconds, when none is given in etcd. Defaults to 3600. + Ttl uint32 `json:"ttl,omitempty"` + // Minimum TTL, in seconds, for NXDOMAIN responses. Defaults to 300. + MinTtl uint32 `json:"min_ttl,omitempty"` + // SCache, capacity of the signature cache in signatures stored. + SCache int `json:"scache,omitempty"` + // RCache, capacity of response cache in resource records stored. + RCache int `json:"rcache,omitempty"` + // RCacheTtl, how long to cache in seconds. + RCacheTtl int `json:"rcache_ttl,omitempty"` + // How many labels a name should have before we allow forwarding. Default to 2. + Ndots int `json:"ndot,omitempty"` + + // DNSSEC key material + PubKey *dns.DNSKEY `json:"-"` + KeyTag uint16 `json:"-"` + PrivKey dns.PrivateKey `json:"-"` + + Verbose bool `json:"-"` + + // some predefined string "constants" + localDomain string // "local.dns." + config.Domain + dnsDomain string // "dns". + config.Domain +} + +func SetDefaults(config *Config) error { + if config.ReadTimeout == 0 { + config.ReadTimeout = 2 * time.Second + } + if config.DnsAddr == "" { + config.DnsAddr = "127.0.0.1:53" + } + if config.Domain == "" { + config.Domain = "skydns.local." + } + if config.Hostmaster == "" { + config.Hostmaster = appendDomain("hostmaster", config.Domain) + } + // People probably don't know that SOA's email addresses cannot + // contain @-signs, replace them with dots + config.Hostmaster = dns.Fqdn(strings.Replace(config.Hostmaster, "@", ".", -1)) + if config.MinTtl == 0 { + config.MinTtl = 60 + } + if config.Ttl == 0 { + config.Ttl = 3600 + } + if config.Priority == 0 { + config.Priority = 10 + } + if config.RCache < 0 { + config.RCache = 0 + } + if config.SCache < 0 { + config.SCache = 0 + } + if config.RCacheTtl == 0 { + config.RCacheTtl = RCacheTtl + } + if config.Ndots <= 0 { + config.Ndots = 2 + } + + if len(config.Nameservers) == 0 { + c, err := dns.ClientConfigFromFile("/etc/resolv.conf") + if err != nil { + return err + } + for _, s := range c.Servers { + config.Nameservers = append(config.Nameservers, net.JoinHostPort(s, c.Port)) + } + } + config.Domain = dns.Fqdn(strings.ToLower(config.Domain)) + if config.DNSSEC != "" { + // For some reason the + are replaces by spaces in etcd. Re-replace them + keyfile := strings.Replace(config.DNSSEC, " ", "+", -1) + k, p, err := ParseKeyFile(keyfile) + if err != nil { + return err + } + if k.Header().Name != dns.Fqdn(config.Domain) { + return fmt.Errorf("ownername of DNSKEY must match SkyDNS domain") + } + k.Header().Ttl = config.Ttl + config.PubKey = k + config.KeyTag = k.KeyTag() + config.PrivKey = p + } + config.localDomain = appendDomain("local.dns", config.Domain) + config.dnsDomain = appendDomain("dns", config.Domain) + return nil +} + +func appendDomain(s1, s2 string) string { + if len(s2) > 0 && s2[0] == '.' { + return s1 + s2 + } + return s1 + "." + s2 +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/dnssec.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/dnssec.go new file mode 100644 index 000000000000..bc2f7c0671ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/dnssec.go @@ -0,0 +1,161 @@ +// Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved. +// Use of this source code is governed by The MIT License (MIT) that can be +// found in the LICENSE file. + +package server + +import ( + "log" + "os" + "time" + + "github.com/miekg/dns" + "github.com/skynetservices/skydns/cache" +) + +// ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other +// utilities. It add ".key" for the public key and ".private" for the private key. +func ParseKeyFile(file string) (*dns.DNSKEY, dns.PrivateKey, error) { + f, e := os.Open(file + ".key") + if e != nil { + return nil, nil, e + } + k, e := dns.ReadRR(f, file+".key") + if e != nil { + return nil, nil, e + } + f, e = os.Open(file + ".private") + if e != nil { + return nil, nil, e + } + p, e := k.(*dns.DNSKEY).ReadPrivateKey(f, file+".private") + if e != nil { + return nil, nil, e + } + return k.(*dns.DNSKEY), p, nil +} + +// Sign signs a message m, it takes care of negative or nodata responses as +// well by synthesising NSEC3 records. It will also cache the signatures, using +// a hash of the signed data as a key. +// We also fake the origin TTL in the signature, because we don't want to +// throw away signatures when services decide to have longer TTL. So we just +// set the origTTL to 60. +// TODO(miek): revisit origTTL +func (s *server) Sign(m *dns.Msg, bufsize uint16) { + now := time.Now().UTC() + incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such + expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week + + for _, r := range rrSets(m.Answer) { + if r[0].Header().Rrtype == dns.TypeRRSIG { + continue + } + if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { + continue + } + if sig, err := s.signSet(r, now, incep, expir); err == nil { + m.Answer = append(m.Answer, sig) + } + } + for _, r := range rrSets(m.Ns) { + if r[0].Header().Rrtype == dns.TypeRRSIG { + continue + } + if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { + continue + } + if sig, err := s.signSet(r, now, incep, expir); err == nil { + m.Ns = append(m.Ns, sig) + } + } + for _, r := range rrSets(m.Extra) { + if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { + continue + } + if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { + continue + } + if sig, err := s.signSet(r, now, incep, expir); err == nil { + m.Extra = append(m.Extra, sig) + } + } + if bufsize >= 512 || bufsize <= 4096 { + m.Truncated = m.Len() > int(bufsize) + } + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + o.SetDo() + o.SetUDPSize(4096) // TODO(miek): echo client + m.Extra = append(m.Extra, o) + return +} + +func (s *server) signSet(r []dns.RR, now time.Time, incep, expir uint32) (*dns.RRSIG, error) { + key := cache.Key(r) + if m, exp, hit := s.scache.Search(key); hit { // There can only be one sig in this cache. + // Is it still valid 24 hours from now? + if now.Add(+24*time.Hour).Sub(exp) < -24*time.Hour { + return m.Answer[0].(*dns.RRSIG), nil + } + s.scache.Remove(key) + } + log.Printf("skydns: scache miss for %s type %d", r[0].Header().Name, r[0].Header().Rrtype) + StatsDnssecCacheMiss.Inc(1) + sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { + sig1 := s.NewRRSIG(incep, expir) + sig1.Header().Ttl = r[0].Header().Ttl + if r[0].Header().Rrtype == dns.TypeTXT { + sig1.OrigTtl = 0 + } + e := sig1.Sign(s.config.PrivKey, r) + if e != nil { + log.Printf("skydns: failed to sign: %s", e.Error()) + } + return sig1, e + }) + if err != nil { + return nil, err + } + if !shared { + s.scache.InsertSignature(key, sig) + } + return dns.Copy(sig).(*dns.RRSIG), nil +} + +func (s *server) NewRRSIG(incep, expir uint32) *dns.RRSIG { + sig := new(dns.RRSIG) + sig.Hdr.Rrtype = dns.TypeRRSIG + sig.Hdr.Ttl = s.config.Ttl + sig.OrigTtl = s.config.Ttl + sig.Algorithm = s.config.PubKey.Algorithm + sig.KeyTag = s.config.KeyTag + sig.Inception = incep + sig.Expiration = expir + sig.SignerName = s.config.PubKey.Hdr.Name + return sig +} + +type rrset struct { + qname string + qtype uint16 +} + +func rrSets(rrs []dns.RR) map[rrset][]dns.RR { + m := make(map[rrset][]dns.RR) + for _, r := range rrs { + if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok { + s = append(s, r) + m[rrset{r.Header().Name, r.Header().Rrtype}] = s + } else { + s := make([]dns.RR, 1, 3) + s[0] = r + m[rrset{r.Header().Name, r.Header().Rrtype}] = s + } + } + if len(m) > 0 { + return m + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/doc.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/doc.go new file mode 100644 index 000000000000..d2009a65141f --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/doc.go @@ -0,0 +1,8 @@ +// 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 server provides a DNS server implementation that handles DNS +// queries. To answer a query, the server asks the provided Backend for +// DNS records, which are then converted to the proper answers. +package server diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/forwarding.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/forwarding.go new file mode 100644 index 000000000000..671242d68135 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/forwarding.go @@ -0,0 +1,133 @@ +// 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 server + +import ( + "fmt" + "log" + "net" + + "github.com/miekg/dns" +) + +// ServeDNSForward forwards a request to a nameservers and returns the response. +func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) { + StatsForwardCount.Inc(1) + if len(s.config.Nameservers) == 0 || dns.CountLabel(req.Question[0].Name) < s.config.Ndots { + if len(s.config.Nameservers) == 0 { + log.Printf("skydns: can not forward, no nameservers defined") + } else { + log.Printf("skydns: can not forward, name too short (less than %d labels): `%s'", s.config.Ndots, req.Question[0].Name) + } + m := new(dns.Msg) + m.SetReply(req) + m.SetRcode(req, dns.RcodeServerFailure) + m.Authoritative = false // no matter what set to false + m.RecursionAvailable = true // and this is still true + w.WriteMsg(m) + return + } + tcp := false + if _, ok := w.RemoteAddr().(*net.TCPAddr); ok { + tcp = true + } + + var ( + r *dns.Msg + err error + try int + ) + // Use request Id for "random" nameserver selection. + nsid := int(req.Id) % len(s.config.Nameservers) +Redo: + switch tcp { + case false: + r, _, err = s.dnsUDPclient.Exchange(req, s.config.Nameservers[nsid]) + case true: + r, _, err = s.dnsTCPclient.Exchange(req, s.config.Nameservers[nsid]) + } + if err == nil { + r.Compress = true + r.Id = req.Id + w.WriteMsg(r) + return + } + // Seen an error, this can only mean, "server not reached", try again + // but only if we have not exausted our nameservers. + if try < len(s.config.Nameservers) { + try++ + nsid = (nsid + 1) % len(s.config.Nameservers) + goto Redo + } + + log.Printf("skydns: failure to forward request %q", err) + m := new(dns.Msg) + m.SetReply(req) + m.SetRcode(req, dns.RcodeServerFailure) + w.WriteMsg(m) +} + +// ServeDNSReverse is the handler for DNS requests for the reverse zone. If nothing is found +// locally the request is forwarded to the forwarder for resolution. +func (s *server) ServeDNSReverse(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + m.Compress = true + m.Authoritative = false // Set to false, because I don't know what to do wrt DNSSEC. + m.RecursionAvailable = true + var err error + if m.Answer, err = s.PTRRecords(req.Question[0]); err == nil { + // TODO(miek): Reverse DNSSEC. We should sign this, but requires a key....and more + // Probably not worth the hassle? + if err := w.WriteMsg(m); err != nil { + log.Printf("skydns: failure to return reply %q", err) + } + } + // Always forward if not found locally. + s.ServeDNSForward(w, req) +} + +// Lookup looks up name,type using the recursive nameserver defines +// in the server's config. If none defined it returns an error +func (s *server) Lookup(n string, t, bufsize uint16, dnssec bool) (*dns.Msg, error) { + StatsLookupCount.Inc(1) + if len(s.config.Nameservers) == 0 { + return nil, fmt.Errorf("no nameservers configured can not lookup name") + } + if dns.CountLabel(n) < s.config.Ndots { + return nil, fmt.Errorf("name has fewer than three labels") + } + m := new(dns.Msg) + m.SetQuestion(n, t) + m.SetEdns0(bufsize, dnssec) + + c := &dns.Client{Net: "udp", ReadTimeout: 2 * s.config.ReadTimeout, WriteTimeout: 2 * s.config.ReadTimeout} + nsid := int(m.Id) % len(s.config.Nameservers) + try := 0 +Redo: + r, _, err := c.Exchange(m, s.config.Nameservers[nsid]) + if err == nil { + if r.Rcode != dns.RcodeSuccess { + return nil, fmt.Errorf("rcode is not equal to success") + } + // Reset TTLs to rcache TTL to make some of the other code + // and the tests not care about TTLs + for _, rr := range r.Answer { + rr.Header().Ttl = uint32(s.config.RCacheTtl) + } + for _, rr := range r.Extra { + rr.Header().Ttl = uint32(s.config.RCacheTtl) + } + return r, nil + } + // Seen an error, this can only mean, "server not reached", try again + // but only if we have not exausted our nameservers. + if try < len(s.config.Nameservers) { + try++ + nsid = (nsid + 1) % len(s.config.Nameservers) + goto Redo + } + return nil, fmt.Errorf("failure to lookup name") +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/nsec3.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/nsec3.go new file mode 100644 index 000000000000..bf911534c35f --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/nsec3.go @@ -0,0 +1,155 @@ +// Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved. +// Use of this source code is governed by The MIT License (MIT) that can be +// found in the LICENSE file. + +package server + +import ( + "encoding/base32" + "strings" + + "github.com/miekg/dns" +) + +// Do DNSSEC NXDOMAIN with NSEC3 whitelies: rfc 7129, appendix B. +// The closest encloser will be qname - the left most label and the +// next closer will be the full qname which we then will deny. +// Idem for source of synthesis. + +func (s *server) Denial(m *dns.Msg) { + if m.Rcode == dns.RcodeNameError { + // ce is qname minus the left label + idx := dns.Split(m.Question[0].Name) + ce := m.Question[0].Name[idx[1]:] + + nsec3ce, nsec3wildcard := newNSEC3CEandWildcard(s.config.Domain, ce, s.config.MinTtl) + // Add ce and wildcard + m.Ns = append(m.Ns, nsec3ce) + m.Ns = append(m.Ns, nsec3wildcard) + // Deny Qname nsec3 + m.Ns = append(m.Ns, s.newNSEC3NameError(m.Question[0].Name)) + } + if m.Rcode == dns.RcodeSuccess && len(m.Ns) == 1 { + // NODATA + if _, ok := m.Ns[0].(*dns.SOA); ok { + m.Ns = append(m.Ns, s.newNSEC3NoData(m.Question[0].Name)) + } + } +} + +func packBase32(s string) []byte { + b32len := base32.HexEncoding.DecodedLen(len(s)) + buf := make([]byte, b32len) + n, _ := base32.HexEncoding.Decode(buf, []byte(s)) + buf = buf[:n] + return buf +} + +func unpackBase32(b []byte) string { + b32 := make([]byte, base32.HexEncoding.EncodedLen(len(b))) + base32.HexEncoding.Encode(b32, b) + return string(b32) +} + +// newNSEC3NameError returns the NSEC3 record needed to denial qname. +func (s *server) newNSEC3NameError(qname string) *dns.NSEC3 { + n := new(dns.NSEC3) + n.Hdr.Class = dns.ClassINET + n.Hdr.Rrtype = dns.TypeNSEC3 + n.Hdr.Ttl = s.config.MinTtl + n.Hash = dns.SHA1 + n.Flags = 0 + n.Salt = "" + n.TypeBitMap = []uint16{} + + covername := dns.HashName(qname, dns.SHA1, 0, "") + + buf := packBase32(covername) + byteArith(buf, false) // one before + n.Hdr.Name = appendDomain(strings.ToLower(unpackBase32(buf)), s.config.Domain) + byteArith(buf, true) // one next + byteArith(buf, true) // and another one + n.NextDomain = unpackBase32(buf) + return n +} + +// newNSEC3NoData returns the NSEC3 record needed to denial the types +func (s *server) newNSEC3NoData(qname string) *dns.NSEC3 { + n := new(dns.NSEC3) + n.Hdr.Class = dns.ClassINET + n.Hdr.Rrtype = dns.TypeNSEC3 + n.Hdr.Ttl = s.config.MinTtl + n.Hash = dns.SHA1 + n.Flags = 0 + n.Salt = "" + n.TypeBitMap = []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeSRV, dns.TypeRRSIG} + + n.Hdr.Name = dns.HashName(qname, dns.SHA1, 0, "") + buf := packBase32(n.Hdr.Name) + byteArith(buf, true) // one next + n.NextDomain = unpackBase32(buf) + + n.Hdr.Name += appendDomain("", s.config.Domain) + return n +} + +// newNSEC3CEandWildcard returns the NSEC3 for the closest encloser +// and the NSEC3 that denies that wildcard at that level. +func newNSEC3CEandWildcard(apex, ce string, ttl uint32) (*dns.NSEC3, *dns.NSEC3) { + n1 := new(dns.NSEC3) + n1.Hdr.Class = dns.ClassINET + n1.Hdr.Rrtype = dns.TypeNSEC3 + n1.Hdr.Ttl = ttl + n1.Hash = dns.SHA1 + n1.Flags = 0 + n1.Iterations = 0 + n1.Salt = "" + // for the apex we need another bitmap + n1.TypeBitMap = []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeSRV, dns.TypeRRSIG} + prev := dns.HashName(ce, dns.SHA1, n1.Iterations, n1.Salt) + n1.Hdr.Name = strings.ToLower(prev) + "." + apex + buf := packBase32(prev) + byteArith(buf, true) // one next + n1.NextDomain = unpackBase32(buf) + + n2 := new(dns.NSEC3) + n2.Hdr.Class = dns.ClassINET + n2.Hdr.Rrtype = dns.TypeNSEC3 + n2.Hdr.Ttl = ttl + n2.Hash = dns.SHA1 + n2.Flags = 0 + n2.Iterations = 0 + n2.Salt = "" + + prev = dns.HashName("*."+ce, dns.SHA1, n2.Iterations, n2.Salt) + buf = packBase32(prev) + byteArith(buf, false) // one before + n2.Hdr.Name = appendDomain(strings.ToLower(unpackBase32(buf)), apex) + byteArith(buf, true) // one next + byteArith(buf, true) // and another one + n2.NextDomain = unpackBase32(buf) + + return n1, n2 +} + +// byteArith adds either 1 or -1 to b, there is no check for under- or overflow. +func byteArith(b []byte, x bool) { + if x { + for i := len(b) - 1; i >= 0; i-- { + if b[i] == 255 { + b[i] = 0 + continue + } + b[i]++ + return + } + } + for i := len(b) - 1; i >= 0; i-- { + if b[i] == 0 { + b[i] = 255 + continue + } + b[i]-- + return + } +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go new file mode 100644 index 000000000000..2e9bacaf7e81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go @@ -0,0 +1,713 @@ +// 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 server + +import ( + "fmt" + "log" + "math" + "net" + "strings" + "sync" + "time" + + "github.com/coreos/go-etcd/etcd" + "github.com/coreos/go-systemd/activation" + "github.com/miekg/dns" + "github.com/skynetservices/skydns/cache" + "github.com/skynetservices/skydns/msg" +) + +const Version = "2.0.1b" + +type server struct { + backend Backend + config *Config + + group *sync.WaitGroup + dnsUDPclient *dns.Client // used for forwarding queries + dnsTCPclient *dns.Client // used for forwarding queries + scache *cache.Cache + rcache *cache.Cache +} + +type Backend interface { + Records(name string, exact bool) ([]msg.Service, error) + ReverseRecord(name string) (*msg.Service, error) +} + +// FirstBackend exposes the Backend interface over multiple Backends, returning +// the first Backend that answers the provided record request. If no Backend answers +// a record request, the last error seen will be returned. +type FirstBackend []Backend + +// FirstBackend implements Backend +var _ Backend = FirstBackend{} + +func (g FirstBackend) Records(name string, exact bool) (records []msg.Service, err error) { + var lastError error + for _, backend := range g { + if records, err = backend.Records(name, exact); err == nil && len(records) > 0 { + return records, nil + } + if err != nil { + lastError = err + } + } + return nil, lastError +} + +func (g FirstBackend) ReverseRecord(name string) (record *msg.Service, err error) { + var lastError error + for _, backend := range g { + if record, err = backend.ReverseRecord(name); err == nil && record != nil { + return record, nil + } + if err != nil { + lastError = err + } + } + return nil, lastError +} + +// New returns a new SkyDNS server. +func New(backend Backend, config *Config) *server { + return &server{ + backend: backend, + config: config, + + group: new(sync.WaitGroup), + scache: cache.New(config.SCache, 0), + rcache: cache.New(config.RCache, config.RCacheTtl), + dnsUDPclient: &dns.Client{Net: "udp", ReadTimeout: 2 * config.ReadTimeout, WriteTimeout: 2 * config.ReadTimeout, SingleInflight: true}, + dnsTCPclient: &dns.Client{Net: "tcp", ReadTimeout: 2 * config.ReadTimeout, WriteTimeout: 2 * config.ReadTimeout, SingleInflight: true}, + } +} + +// Run is a blocking operation that starts the server listening on the DNS ports. +func (s *server) Run() error { + mux := dns.NewServeMux() + mux.Handle(".", s) + + dnsReadyMsg := func(addr, net string) { + if s.config.DNSSEC == "" { + log.Printf("skydns: ready for queries on %s for %s://%s [rcache %d]", s.config.Domain, net, addr, s.config.RCache) + } else { + log.Printf("skydns: ready for queries on %s for %s://%s [rcache %d], signing with %s [scache %d]", s.config.Domain, net, addr, s.config.RCache, s.config.DNSSEC, s.config.SCache) + } + } + + if s.config.Systemd { + packetConns, err := activation.PacketConns(false) + if err != nil { + return err + } + listeners, err := activation.Listeners(true) + if err != nil { + return err + } + if len(packetConns) == 0 && len(listeners) == 0 { + return fmt.Errorf("no UDP or TCP sockets supplied by systemd") + } + for _, p := range packetConns { + if u, ok := p.(*net.UDPConn); ok { + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ActivateAndServe(nil, u, mux); err != nil { + log.Fatalf("skydns: %s", err) + } + }() + dnsReadyMsg(u.LocalAddr().String(), "udp") + } + } + for _, l := range listeners { + if t, ok := l.(*net.TCPListener); ok { + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ActivateAndServe(t, nil, mux); err != nil { + log.Fatalf("skydns: %s", err) + } + }() + dnsReadyMsg(t.Addr().String(), "tcp") + } + } + } else { + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ListenAndServe(s.config.DnsAddr, "tcp", mux); err != nil { + log.Fatalf("skydns: %s", err) + } + }() + dnsReadyMsg(s.config.DnsAddr, "tcp") + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ListenAndServe(s.config.DnsAddr, "udp", mux); err != nil { + log.Fatalf("skydns: %s", err) + } + }() + dnsReadyMsg(s.config.DnsAddr, "udp") + } + + s.group.Wait() + return nil +} + +// Stop stops a server. +func (s *server) Stop() { + // TODO(miek) + //s.group.Add(-2) +} + +// ServeDNS is the handler for DNS requests, responsible for parsing DNS request, possibly forwarding +// it to a real dns server and returning a response. +func (s *server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + m.Authoritative = true + m.RecursionAvailable = true + m.Compress = true + bufsize := uint16(512) + dnssec := false + tcp := false + + if req.Question[0].Qtype == dns.TypeANY { + m.Authoritative = false + m.Rcode = dns.RcodeRefused + m.RecursionAvailable = false + m.RecursionDesired = false + m.Compress = false + // if write fails don't care + w.WriteMsg(m) + return + } + + if o := req.IsEdns0(); o != nil { + bufsize = o.UDPSize() + dnssec = o.Do() + } + if bufsize < 512 { + bufsize = 512 + } + // with TCP we can send 64K + if _, ok := w.RemoteAddr().(*net.TCPAddr); ok { + bufsize = dns.MaxMsgSize - 1 + tcp = true + } + // Check cache first. + key := cache.QuestionKey(req.Question[0], dnssec) + m1, exp, hit := s.rcache.Search(key) + if hit { + // Cache hit! \o/ + if time.Since(exp) < 0 { + m1.Id = m.Id + m1.Compress = true + if dnssec { + StatsDnssecOkCount.Inc(1) + // The key for DNS/DNSSEC in cache is different, no + // need to do Denial/Sign here. + //if s.config.PubKey != nil { + //s.Denial(m1) // not needed for cache hits + //s.Sign(m1, bufsize) + //} + } + if m1.Len() > int(bufsize) && !tcp { + m1.Truncated = true + } + // Still round-robin even with hits from the cache. + // Only shuffle A and AAAA records with each other. + if req.Question[0].Qtype == dns.TypeA || req.Question[0].Qtype == dns.TypeAAAA { + s.RoundRobin(m1.Answer) + } + + if err := w.WriteMsg(m1); err != nil { + log.Printf("skydns: failure to return reply %q", err) + } + return + } + // Expired! /o\ + s.rcache.Remove(key) + } + + q := req.Question[0] + name := strings.ToLower(q.Name) + StatsRequestCount.Inc(1) + if s.config.Verbose { + log.Printf("skydns: received DNS Request for %q from %q with type %d", q.Name, w.RemoteAddr(), q.Qtype) + } + // If the qname is local.dns.skydns.local. and s.config.Local != "", substitute that name. + if s.config.Local != "" && name == s.config.localDomain { + name = s.config.Local + } + + if q.Qtype == dns.TypePTR && strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { + s.ServeDNSReverse(w, req) + return + } + + if q.Qclass != dns.ClassCHAOS && !strings.HasSuffix(name, s.config.Domain) { + s.ServeDNSForward(w, req) + return + } + + defer func() { + if m.Rcode == dns.RcodeServerFailure { + if err := w.WriteMsg(m); err != nil { + log.Printf("skydns: failure to return reply %q", err) + } + return + } + // Set TTL to the minimum of the RRset. + minttl := s.config.Ttl + if len(m.Answer) > 1 { + for _, r := range m.Answer { + if r.Header().Ttl < minttl { + minttl = r.Header().Ttl + } + } + for _, r := range m.Answer { + r.Header().Ttl = minttl + } + } + + s.rcache.InsertMessage(cache.QuestionKey(req.Question[0], dnssec), m) + + if dnssec { + StatsDnssecOkCount.Inc(1) + if s.config.PubKey != nil { + m.AuthenticatedData = true + s.Denial(m) + s.Sign(m, bufsize) + } + } + if m.Len() > int(bufsize) && !tcp { + // TODO(miek): this is a little brain dead, better is to not add + // RRs in the message in the first place. + m.Truncated = true + } + if err := w.WriteMsg(m); err != nil { + log.Printf("skydns: failure to return reply %q", err) + } + }() + + if name == s.config.Domain { + if q.Qtype == dns.TypeSOA { + m.Answer = []dns.RR{s.NewSOA()} + return + } + if q.Qtype == dns.TypeDNSKEY { + if s.config.PubKey != nil { + m.Answer = []dns.RR{s.config.PubKey} + return + } + } + } + if q.Qclass == dns.ClassCHAOS { + if q.Qtype == dns.TypeTXT { + switch name { + case "authors.bind.": + fallthrough + case s.config.Domain: + hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} + authors := []string{"Erik St. Martin", "Brian Ketelsen", "Miek Gieben", "Michael Crosby"} + for _, a := range authors { + m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{a}}) + } + for j := 0; j < len(authors)*(int(dns.Id())%4+1); j++ { + q := int(dns.Id()) % len(authors) + p := int(dns.Id()) % len(authors) + if q == p { + p = (p + 1) % len(authors) + } + m.Answer[q], m.Answer[p] = m.Answer[p], m.Answer[q] + } + return + case "version.bind.": + fallthrough + case "version.server.": + hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} + m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{Version}}} + return + case "hostname.bind.": + fallthrough + case "id.server.": + // TODO(miek): machine name to return + hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} + m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{"localhost"}}} + return + } + } + // still here, fail + m.SetReply(req) + m.SetRcode(req, dns.RcodeServerFailure) + return + } + + switch q.Qtype { + case dns.TypeNS: + if name != s.config.Domain { + break + } + // Lookup s.config.DnsDomain + records, extra, err := s.NSRecords(q, s.config.dnsDomain) + if err != nil { + if e, ok := err.(*etcd.EtcdError); ok { + if e.ErrorCode == 100 { + s.NameError(m, req) + return + } + } + } + m.Answer = append(m.Answer, records...) + m.Extra = append(m.Extra, extra...) + case dns.TypeA, dns.TypeAAAA: + records, err := s.AddressRecords(q, name, nil) + if err != nil { + if e, ok := err.(*etcd.EtcdError); ok { + if e.ErrorCode == 100 { + s.NameError(m, req) + return + } + } + if err.Error() == "incomplete CNAME chain" { + // We can not complete the CNAME internally, *iff* there is a + // external name in the set, take it, and try to resolve it externally. + if len(records) == 0 { + s.NameError(m, req) + return + } + target := "" + for _, r := range records { + if v, ok := r.(*dns.CNAME); ok { + if !dns.IsSubDomain(s.config.Domain, v.Target) { + target = v.Target + break + } + } + } + if target == "" { + log.Printf("skydns: incomplete CNAME chain for %s", name) + s.NoDataError(m, req) + return + } + m1, e1 := s.Lookup(target, req.Question[0].Qtype, bufsize, dnssec) + if e1 != nil { + log.Printf("skydns: %s", err) + s.NoDataError(m, req) + return + } + records = append(records, m1.Answer...) + } + } + m.Answer = append(m.Answer, records...) + case dns.TypeTXT: + records, err := s.TXTRecords(q, name) + if err != nil { + if e, ok := err.(*etcd.EtcdError); ok { + if e.ErrorCode == 100 { + s.NameError(m, req) + return + } + } + } + m.Answer = append(m.Answer, records...) + case dns.TypeCNAME: + records, err := s.CNAMERecords(q, name) + if err != nil { + if e, ok := err.(*etcd.EtcdError); ok { + if e.ErrorCode == 100 { + s.NameError(m, req) + return + } + } + } + m.Answer = append(m.Answer, records...) + default: + fallthrough // also catch other types, so that they return NODATA + case dns.TypeSRV, dns.TypeANY: + records, extra, err := s.SRVRecords(q, name, bufsize, dnssec) + if err != nil { + if e, ok := err.(*etcd.EtcdError); ok { + if e.ErrorCode == 100 { + s.NameError(m, req) + return + } + } + } + // if we are here again, check the types, because an answer may only + // be given for SRV or ANY. All other types should return NODATA, the + // NXDOMAIN part is handled in the above code. TODO(miek): yes this + // can be done in a more elegant manor. + if q.Qtype == dns.TypeSRV || q.Qtype == dns.TypeANY { + m.Answer = append(m.Answer, records...) + m.Extra = append(m.Extra, extra...) + } + } + + if len(m.Answer) == 0 { // NODATA response + StatsNoDataCount.Inc(1) + m.Ns = []dns.RR{s.NewSOA()} + m.Ns[0].Header().Ttl = s.config.MinTtl + } +} + +func (s *server) AddressRecords(q dns.Question, name string, previousRecords []dns.RR) (records []dns.RR, err error) { + services, err := s.backend.Records(name, false) + if err != nil { + return nil, err + } + for _, serv := range services { + ip := net.ParseIP(serv.Host) + switch { + case ip == nil: + // TODO: deduplicate with above code + // Try to resolve as CNAME if it's not an IP. + newRecord := serv.NewCNAME(q.Name, dns.Fqdn(serv.Host)) + if len(previousRecords) > 7 { + log.Printf("skydns: CNAME lookup limit of 8 exceeded for %s", newRecord) + return nil, fmt.Errorf("exceeded CNAME lookup limit") + } + if s.isDuplicateCNAME(newRecord, previousRecords) { + log.Printf("skydns: CNAME loop detected for record %s", newRecord) + return nil, fmt.Errorf("detected CNAME loop") + } + + records = append(records, newRecord) + nextRecords, err := s.AddressRecords(dns.Question{Name: dns.Fqdn(serv.Host), Qtype: q.Qtype, Qclass: q.Qclass}, strings.ToLower(dns.Fqdn(serv.Host)), append(previousRecords, newRecord)) + if err != nil { + // This means we can not complete the CNAME, this is OK, but + // if we return an error this will trigger an NXDOMAIN. + // We also don't want to return the CNAME, because of the + // no other data rule. So return nothing and let NODATA + // kick in (via a hack). + return records, fmt.Errorf("incomplete CNAME chain") + } + records = append(records, nextRecords...) + case ip.To4() != nil && q.Qtype == dns.TypeA: + records = append(records, serv.NewA(q.Name, ip.To4())) + case ip.To4() == nil && q.Qtype == dns.TypeAAAA: + records = append(records, serv.NewAAAA(q.Name, ip.To16())) + } + } + if s.config.RoundRobin { + s.RoundRobin(records) + } + return records, nil +} + +// NSRecords returns NS records from etcd. +func (s *server) NSRecords(q dns.Question, name string) (records []dns.RR, extra []dns.RR, err error) { + services, err := s.backend.Records(name, false) + if err != nil { + return nil, nil, err + } + + for _, serv := range services { + ip := net.ParseIP(serv.Host) + switch { + case ip == nil: + return nil, nil, fmt.Errorf("NS record must be an IP address") + case ip.To4() != nil: + serv.Host = msg.Domain(serv.Key) + records = append(records, serv.NewNS(q.Name, serv.Host)) + extra = append(extra, serv.NewA(serv.Host, ip.To4())) + case ip.To4() == nil: + serv.Host = msg.Domain(serv.Key) + records = append(records, serv.NewNS(q.Name, serv.Host)) + extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) + } + } + return records, extra, nil +} + +// SRVRecords returns SRV records from etcd. +// If the Target is not an name but an IP address, an name is created . +func (s *server) SRVRecords(q dns.Question, name string, bufsize uint16, dnssec bool) (records []dns.RR, extra []dns.RR, err error) { + services, err := s.backend.Records(name, false) + if err != nil { + return nil, nil, err + } + + // Looping twice to get the right weight vs priority + w := make(map[int]int) + for _, serv := range services { + weight := 100 + if serv.Weight != 0 { + weight = serv.Weight + } + if _, ok := w[serv.Priority]; !ok { + w[serv.Priority] = weight + continue + } + w[serv.Priority] += weight + } + lookup := make(map[string]bool) + for _, serv := range services { + w1 := 100.0 / float64(w[serv.Priority]) + if serv.Weight == 0 { + w1 *= 100 + } else { + w1 *= float64(serv.Weight) + } + weight := uint16(math.Floor(w1)) + ip := net.ParseIP(serv.Host) + switch { + case ip == nil: + srv := serv.NewSRV(q.Name, weight) + records = append(records, srv) + if _, ok := lookup[srv.Target]; !ok { + if !dns.IsSubDomain(s.config.Domain, srv.Target) { + m1, e1 := s.Lookup(srv.Target, dns.TypeA, bufsize, dnssec) + if e1 == nil { + extra = append(extra, m1.Answer...) + } + m1, e1 = s.Lookup(srv.Target, dns.TypeAAAA, bufsize, dnssec) + if e1 == nil { + // If we have seen CNAME's we *assume* that they are already added. + for _, a := range m1.Answer { + if _, ok := a.(*dns.CNAME); !ok { + extra = append(extra, a) + } + } + } + } + } + lookup[srv.Target] = true + case ip.To4() != nil: + serv.Host = msg.Domain(serv.Key) + records = append(records, serv.NewSRV(q.Name, weight)) + extra = append(extra, serv.NewA(serv.Host, ip.To4())) + case ip.To4() == nil: + serv.Host = msg.Domain(serv.Key) + records = append(records, serv.NewSRV(q.Name, weight)) + extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) + } + } + return records, extra, nil +} + +func (s *server) CNAMERecords(q dns.Question, name string) (records []dns.RR, err error) { + services, err := s.backend.Records(name, true) + if err != nil { + return nil, err + } + + if len(services) > 0 { + serv := services[0] + if ip := net.ParseIP(serv.Host); ip == nil { + records = append(records, serv.NewCNAME(q.Name, dns.Fqdn(serv.Host))) + } + } + return records, nil +} + +func (s *server) TXTRecords(q dns.Question, name string) (records []dns.RR, err error) { + services, err := s.backend.Records(name, false) + if err != nil { + return nil, err + } + + for _, serv := range services { + if serv.Text == "" { + continue + } + records = append(records, serv.NewTXT(q.Name)) + } + return records, nil +} + +func (s *server) PTRRecords(q dns.Question) (records []dns.RR, err error) { + name := strings.ToLower(q.Name) + serv, err := s.backend.ReverseRecord(name) + if err != nil { + return nil, err + } + + // If serv.Host is parseble as a IP address we should not return anything. + // TODO(miek). + records = append(records, serv.NewPTR(q.Name, serv.Ttl)) + return records, nil +} + +// SOA returns a SOA record for this SkyDNS instance. +func (s *server) NewSOA() dns.RR { + return &dns.SOA{Hdr: dns.RR_Header{Name: s.config.Domain, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: s.config.Ttl}, + Ns: appendDomain("ns.dns", s.config.Domain), + Mbox: s.config.Hostmaster, + Serial: uint32(time.Now().Truncate(time.Hour).Unix()), + Refresh: 28800, + Retry: 7200, + Expire: 604800, + Minttl: s.config.MinTtl, + } +} + +func (s *server) isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool { + for _, rec := range records { + if v, ok := rec.(*dns.CNAME); ok { + if v.Target == r.Target { + return true + } + } + } + return false +} + +func (s *server) NameError(m, req *dns.Msg) { + m.SetRcode(req, dns.RcodeNameError) + m.Ns = []dns.RR{s.NewSOA()} + m.Ns[0].Header().Ttl = s.config.MinTtl + StatsNameErrorCount.Inc(1) +} + +func (s *server) NoDataError(m, req *dns.Msg) { + m.SetRcode(req, dns.RcodeSuccess) + m.Ns = []dns.RR{s.NewSOA()} + m.Ns[0].Header().Ttl = s.config.MinTtl + // StatsNoDataCount.Inc(1) +} + +func (s *server) logNoConnection(e error) { + if e.(*etcd.EtcdError).ErrorCode == etcd.ErrCodeEtcdNotReachable { + log.Printf("skydns: failure to connect to etcd: %s", e) + } +} + +func (s *server) RoundRobin(rrs []dns.RR) { + if !s.config.RoundRobin { + return + } + // If we have more than 1 CNAME don't touch the packet, because some stub resolver (=glibc) + // can't deal with the returned packet if the CNAMEs need to be accesses in the reverse order. + cname := 0 + for _, r := range rrs { + if r.Header().Rrtype == dns.TypeCNAME { + cname++ + if cname > 1 { + return + } + } + } + + switch l := len(rrs); l { + case 2: + if dns.Id()%2 == 0 { + rrs[0], rrs[1] = rrs[1], rrs[0] + } + default: + for j := 0; j < l*(int(dns.Id())%4+1); j++ { + q := int(dns.Id()) % l + p := int(dns.Id()) % l + if q == p { + p = (p + 1) % l + } + rrs[q], rrs[p] = rrs[p], rrs[q] + } + } + +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server_test.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server_test.go new file mode 100644 index 000000000000..b9549420895d --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server_test.go @@ -0,0 +1,897 @@ +// 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 server + +// etcd needs to be running on http://127.0.0.1:4001 + +import ( + "encoding/json" + "sort" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/coreos/go-etcd/etcd" + "github.com/miekg/dns" + backendetcd "github.com/skynetservices/skydns/backends/etcd" + "github.com/skynetservices/skydns/cache" + "github.com/skynetservices/skydns/msg" +) + +// Keep global port counter that increments with 10 for each +// new call to newTestServer. The dns server is started on port 'Port'. +var Port = 9400 +var StrPort = "9400" // string equivalent of Port + +func addService(t *testing.T, s *server, k string, ttl uint64, m *msg.Service) { + b, err := json.Marshal(m) + if err != nil { + t.Fatal(err) + } + path, _ := msg.PathWithWildcard(k) + t.Logf("Adding path %s:", path) + _, err = s.getter.(*backendetcd.Getter).Client().Create(path, string(b), ttl) + if err != nil { + // TODO(miek): allow for existing keys... + t.Fatal(err) + } +} + +func delService(t *testing.T, s *server, k string) { + path, _ := msg.PathWithWildcard(k) + _, err := s.getter.(*backendetcd.Getter).Client().Delete(path, false) + if err != nil { + t.Fatal(err) + } +} + +func newTestServer(t *testing.T, c bool) *server { + Port += 10 + StrPort = strconv.Itoa(Port) + s := new(server) + client := etcd.NewClient([]string{"http://127.0.0.1:4001"}) + client.SyncCluster() + + // TODO(miek): why don't I use NewServer?? + s.group = new(sync.WaitGroup) + s.scache = cache.New(100, 0) + s.rcache = cache.New(100, 0) + if c { + s.rcache = cache.New(100, 60) // 100 items, 60s ttl + } + s.config = new(Config) + s.config.Domain = "skydns.test." + s.config.DnsAddr = "127.0.0.1:" + StrPort + s.config.Nameservers = []string{"8.8.4.4:53"} + SetDefaults(s.config) + s.config.Local = "104.server1.development.region1.skydns.test." + s.config.Priority = 10 + s.config.RCacheTtl = RCacheTtl + s.config.Ttl = 3600 + s.config.Ndots = 2 + + s.dnsUDPclient = &dns.Client{Net: "udp", ReadTimeout: 2 * s.config.ReadTimeout, WriteTimeout: 2 * s.config.ReadTimeout, SingleInflight: true} + s.dnsTCPclient = &dns.Client{Net: "tcp", ReadTimeout: 2 * s.config.ReadTimeout, WriteTimeout: 2 * s.config.ReadTimeout, SingleInflight: true} + + s.getter = backendetcd.NewGetter(client, &backendetcd.Config{ + Ttl: s.config.Ttl, + Priority: s.config.Priority, + }) + + go s.Run() + // Yeah, yeah, should do a proper fix. + time.Sleep(500 * time.Millisecond) + return s +} + +func newTestServerDNSSEC(t *testing.T, cache bool) *server { + var err error + s := newTestServer(t, cache) + s.config.PubKey = newDNSKEY("skydns.test. IN DNSKEY 256 3 5 AwEAAaXfO+DOBMJsQ5H4TfiabwSpqE4cGL0Qlvh5hrQumrjr9eNSdIOjIHJJKCe56qBU5mH+iBlXP29SVf6UiiMjIrAPDVhClLeWFe0PC+XlWseAyRgiLHdQ8r95+AfkhO5aZgnCwYf9FGGSaT0+CRYN+PyDbXBTLK5FN+j5b6bb7z+d") + s.config.KeyTag = s.config.PubKey.KeyTag() + s.config.PrivKey, err = s.config.PubKey.ReadPrivateKey(strings.NewReader(`Private-key-format: v1.3 +Algorithm: 5 (RSASHA1) +Modulus: pd874M4EwmxDkfhN+JpvBKmoThwYvRCW+HmGtC6auOv141J0g6MgckkoJ7nqoFTmYf6IGVc/b1JV/pSKIyMisA8NWEKUt5YV7Q8L5eVax4DJGCIsd1Dyv3n4B+SE7lpmCcLBh/0UYZJpPT4JFg34/INtcFMsrkU36PlvptvvP50= +PublicExponent: AQAB +PrivateExponent: C6e08GXphbPPx6j36ZkIZf552gs1XcuVoB4B7hU8P/Qske2QTFOhCwbC8I+qwdtVWNtmuskbpvnVGw9a6X8lh7Z09RIgzO/pI1qau7kyZcuObDOjPw42exmjqISFPIlS1wKA8tw+yVzvZ19vwRk1q6Rne+C1romaUOTkpA6UXsE= +Prime1: 2mgJ0yr+9vz85abrWBWnB8Gfa1jOw/ccEg8ZToM9GLWI34Qoa0D8Dxm8VJjr1tixXY5zHoWEqRXciTtY3omQDQ== +Prime2: wmxLpp9rTzU4OREEVwF43b/TxSUBlUq6W83n2XP8YrCm1nS480w4HCUuXfON1ncGYHUuq+v4rF+6UVI3PZT50Q== +Exponent1: wkdTngUcIiau67YMmSFBoFOq9Lldy9HvpVzK/R0e5vDsnS8ZKTb4QJJ7BaG2ADpno7pISvkoJaRttaEWD3a8rQ== +Exponent2: YrC8OglEXIGkV3tm2494vf9ozPL6+cBkFsPPg9dXbvVCyyuW0pGHDeplvfUqs4nZp87z8PsoUL+LAUqdldnwcQ== +Coefficient: mMFr4+rDY5V24HZU3Oa5NEb55iQ56ZNa182GnNhWqX7UqWjcUUGjnkCy40BqeFAQ7lp52xKHvP5Zon56mwuQRw== +`), "stdin") + if err != nil { + t.Fatal(err) + } + return s +} + +func TestDNSForward(t *testing.T) { + s := newTestServer(t, false) + defer s.Stop() + + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion("www.example.com.", dns.TypeA) + resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) + if err != nil { + // try twice + resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) + if err != nil { + t.Fatal(err) + } + } + if len(resp.Answer) == 0 || resp.Rcode != dns.RcodeSuccess { + t.Fatal("Answer expected to have A records or rcode not equal to RcodeSuccess") + } + // TCP + c.Net = "tcp" + resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) + if err != nil { + t.Fatal(err) + } + if len(resp.Answer) == 0 || resp.Rcode != dns.RcodeSuccess { + t.Fatal("Answer expected to have A records or rcode not equal to RcodeSuccess") + } +} +func TestDNSTtlRRset(t *testing.T) { + s := newTestServerDNSSEC(t, false) + defer s.Stop() + + ttl := uint32(60) + for _, serv := range services { + addService(t, s, serv.Key, uint64(ttl), serv) + defer delService(t, s, serv.Key) + ttl += 60 + } + c := new(dns.Client) + tc := dnsTestCases[9] + t.Logf("%v\n", tc) + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + if tc.dnssec == true { + m.SetEdns0(4096, true) + } + resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) + if err != nil { + t.Fatalf("failing: %s: %s\n", m.String(), err.Error()) + } + t.Logf("%s\n", resp) + ttl = 360 + for i, a := range resp.Answer { + if a.Header().Ttl != ttl { + t.Errorf("Answer %d should have a Header TTL of %d, but has %d", i, ttl, a.Header().Ttl) + } + } +} + +type rrSet []dns.RR + +func (p rrSet) Len() int { return len(p) } +func (p rrSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p rrSet) Less(i, j int) bool { return p[i].String() < p[j].String() } + +func TestDNS(t *testing.T) { + s := newTestServerDNSSEC(t, false) + defer s.Stop() + + for _, serv := range services { + addService(t, s, serv.Key, 0, serv) + defer delService(t, s, serv.Key) + } + c := new(dns.Client) + for _, tc := range dnsTestCases { + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + if tc.dnssec { + m.SetEdns0(4096, true) + } + if tc.chaos { + m.Question[0].Qclass = dns.ClassCHAOS + } + resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) + t.Logf("question: %s\n", m.Question[0].String()) + if err != nil { + // try twice, be more resilent against remote lookups + // timing out. + resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) + if err != nil { + t.Fatalf("failing: %s: %s\n", m.String(), err.Error()) + } + } + sort.Sort(rrSet(resp.Answer)) + sort.Sort(rrSet(resp.Ns)) + sort.Sort(rrSet(resp.Extra)) + t.Logf("%s\n", resp) + if resp.Rcode != tc.Rcode { + t.Fatalf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + } + if len(resp.Answer) != len(tc.Answer) { + t.Fatalf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) + } + for i, a := range resp.Answer { + if a.Header().Name != tc.Answer[i].Header().Name { + t.Fatalf("answer %d should have a Header Name of %q, but has %q", i, tc.Answer[i].Header().Name, a.Header().Name) + } + if a.Header().Ttl != tc.Answer[i].Header().Ttl { + t.Fatalf("Answer %d should have a Header TTL of %d, but has %d", i, tc.Answer[i].Header().Ttl, a.Header().Ttl) + } + if a.Header().Rrtype != tc.Answer[i].Header().Rrtype { + t.Fatalf("answer %d should have a header response type of %d, but has %d", i, tc.Answer[i].Header().Rrtype, a.Header().Rrtype) + } + switch x := a.(type) { + case *dns.SRV: + if x.Priority != tc.Answer[i].(*dns.SRV).Priority { + t.Fatalf("answer %d should have a Priority of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Priority, x.Priority) + } + if x.Weight != tc.Answer[i].(*dns.SRV).Weight { + t.Fatalf("answer %d should have a Weight of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Weight, x.Weight) + } + if x.Port != tc.Answer[i].(*dns.SRV).Port { + t.Fatalf("answer %d should have a Port of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Port, x.Port) + } + if x.Target != tc.Answer[i].(*dns.SRV).Target { + t.Fatalf("answer %d should have a Target of %q, but has %q", i, tc.Answer[i].(*dns.SRV).Target, x.Target) + } + case *dns.A: + if x.A.String() != tc.Answer[i].(*dns.A).A.String() { + t.Fatalf("answer %d should have a Address of %q, but has %q", i, tc.Answer[i].(*dns.A).A.String(), x.A.String()) + } + case *dns.AAAA: + if x.AAAA.String() != tc.Answer[i].(*dns.AAAA).AAAA.String() { + t.Fatalf("answer %d should have a Address of %q, but has %q", i, tc.Answer[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) + } + case *dns.TXT: + for j, txt := range x.Txt { + if txt != tc.Answer[i].(*dns.TXT).Txt[j] { + t.Fatalf("answer %d should have a Txt of %q, but has %q", i, tc.Answer[i].(*dns.TXT).Txt[j], txt) + } + } + case *dns.DNSKEY: + tt := tc.Answer[i].(*dns.DNSKEY) + if x.Flags != tt.Flags { + t.Fatalf("DNSKEY flags should be %q, but is %q", x.Flags, tt.Flags) + } + if x.Protocol != tt.Protocol { + t.Fatalf("DNSKEY protocol should be %q, but is %q", x.Protocol, tt.Protocol) + } + if x.Algorithm != tt.Algorithm { + t.Fatalf("DNSKEY algorithm should be %q, but is %q", x.Algorithm, tt.Algorithm) + } + case *dns.RRSIG: + tt := tc.Answer[i].(*dns.RRSIG) + if x.TypeCovered != tt.TypeCovered { + t.Fatalf("RRSIG type-covered should be %d, but is %d", x.TypeCovered, tt.TypeCovered) + } + if x.Algorithm != tt.Algorithm { + t.Fatalf("RRSIG algorithm should be %d, but is %d", x.Algorithm, tt.Algorithm) + } + if x.Labels != tt.Labels { + t.Fatalf("RRSIG label should be %d, but is %d", x.Labels, tt.Labels) + } + if x.OrigTtl != tt.OrigTtl { + t.Fatalf("RRSIG orig-ttl should be %d, but is %d", x.OrigTtl, tt.OrigTtl) + } + if x.KeyTag != tt.KeyTag { + t.Fatalf("RRSIG key-tag should be %d, but is %d", x.KeyTag, tt.KeyTag) + } + if x.SignerName != tt.SignerName { + t.Fatalf("RRSIG signer-name should be %q, but is %q", x.SignerName, tt.SignerName) + } + case *dns.SOA: + tt := tc.Answer[i].(*dns.SOA) + if x.Ns != tt.Ns { + t.Fatalf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) + } + case *dns.PTR: + tt := tc.Answer[i].(*dns.PTR) + if x.Ptr != tt.Ptr { + t.Fatalf("PTR ptr should be %q, but is %q", x.Ptr, tt.Ptr) + } + case *dns.CNAME: + tt := tc.Answer[i].(*dns.CNAME) + if x.Target != tt.Target { + t.Fatalf("CNAME target should be %q, but is %q", x.Target, tt.Target) + } + } + } + if len(resp.Ns) != len(tc.Ns) { + t.Fatalf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) + } + for i, n := range resp.Ns { + switch x := n.(type) { + case *dns.SOA: + tt := tc.Ns[i].(*dns.SOA) + if x.Ns != tt.Ns { + t.Fatalf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) + } + case *dns.NS: + tt := tc.Ns[i].(*dns.NS) + if x.Ns != tt.Ns { + t.Fatalf("NS nameserver should be %q, but is %q", x.Ns, tt.Ns) + } + case *dns.NSEC3: + tt := tc.Ns[i].(*dns.NSEC3) + if x.NextDomain != tt.NextDomain { + t.Fatalf("NSEC3 nextdomain should be %q, but is %q", x.NextDomain, tt.NextDomain) + } + if x.Hdr.Name != tt.Hdr.Name { + t.Fatalf("NSEC3 ownername should be %q, but is %q", x.Hdr.Name, tt.Hdr.Name) + } + for j, y := range x.TypeBitMap { + if y != tt.TypeBitMap[j] { + t.Fatalf("NSEC3 bitmap should have %q, but is %q", dns.TypeToString[y], dns.TypeToString[tt.TypeBitMap[j]]) + } + } + } + } + if len(resp.Extra) != len(tc.Extra) { + t.Fatalf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) + } + for i, e := range resp.Extra { + switch x := e.(type) { + case *dns.A: + if x.A.String() != tc.Extra[i].(*dns.A).A.String() { + t.Fatalf("extra %d should have a address of %q, but has %q", i, tc.Extra[i].(*dns.A).A.String(), x.A.String()) + } + case *dns.AAAA: + if x.AAAA.String() != tc.Extra[i].(*dns.AAAA).AAAA.String() { + t.Fatalf("extra %d should have a address of %q, but has %q", i, tc.Extra[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) + } + case *dns.CNAME: + tt := tc.Extra[i].(*dns.CNAME) + if x.Target != tt.Target { + t.Fatalf("CNAME target should be %q, but is %q", x.Target, tt.Target) + } + } + } + } +} + +type dnsTestCase struct { + Qname string + Qtype uint16 + dnssec bool + chaos bool + Rcode int + Answer []dns.RR + Ns []dns.RR + Extra []dns.RR +} + +var services = []*msg.Service{ + {Host: "server1", Port: 8080, Key: "100.server1.development.region1.skydns.test."}, + {Host: "server2", Port: 80, Key: "101.server2.production.region1.skydns.test."}, + {Host: "server4", Port: 80, Priority: 333, Key: "102.server4.development.region6.skydns.test."}, + {Host: "server3", Key: "103.server4.development.region2.skydns.test."}, + {Host: "172.16.1.1", Key: "a.ipaddr.skydns.test."}, + {Host: "172.16.1.2", Key: "b.ipaddr.skydns.test."}, + {Host: "ipaddr.skydns.test", Key: "1.backend.in.skydns.test."}, + {Host: "10.0.0.1", Key: "104.server1.development.region1.skydns.test."}, + {Host: "2001::8:8:8:8", Key: "105.server3.production.region2.skydns.test."}, + {Host: "104.server1.development.region1.skydns.test", Key: "1.cname.skydns.test."}, + {Host: "100.server1.development.region1.skydns.test", Key: "2.cname.skydns.test."}, + {Host: "www.miek.nl", Key: "external1.cname.skydns.test."}, + {Host: "www.miek.nl", Key: "ext1.cname2.skydns.test."}, + {Host: "www.miek.nl", Key: "ext2.cname2.skydns.test."}, + {Host: "wwwwwww.miek.nl", Key: "external2.cname.skydns.test."}, + {Host: "4.cname.skydns.test", Key: "3.cname.skydns.test."}, + {Host: "3.cname.skydns.test", Key: "4.cname.skydns.test."}, + {Host: "10.0.0.2", Key: "ttl.skydns.test.", Ttl: 360}, + {Host: "reverse.example.com", Key: "1.0.0.10.in-addr.arpa."}, // 10.0.0.1 + {Host: "server1", Weight: 130, Key: "100.server1.region5.skydns.test."}, + {Host: "server2", Weight: 80, Key: "101.server2.region5.skydns.test."}, + {Host: "server3", Weight: 150, Key: "103.server3.region5.skydns.test."}, + {Host: "server4", Priority: 30, Key: "104.server4.region5.skydns.test."}, + // nameserver + {Host: "10.0.0.2", Key: "ns.dns.skydns.test."}, + {Host: "10.0.0.3", Key: "ns2.dns.skydns.test."}, + // txt + {Text: "abc", Key: "a1.txt.skydns.test."}, + {Text: "abc abc", Key: "a2.txt.skydns.test."}, +} + +var dnsTestCases = []dnsTestCase{ + // Full Name Test + { + Qname: "100.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{newSRV("100.server1.development.region1.skydns.test. 3600 SRV 10 100 8080 server1.")}, + }, + // SOA Record Test + { + Qname: "skydns.test.", Qtype: dns.TypeSOA, + Answer: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, + }, + // NS Record Test + { + Qname: "skydns.test.", Qtype: dns.TypeNS, + Answer: []dns.RR{ + newNS("skydns.test. 3600 NS ns1.dns.skydns.test."), + newNS("skydns.test. 3600 NS ns2.dns.skydns.test."), + }, + Extra: []dns.RR{ + newA("ns.dns.skydns.test. 3600 A 10.0.0.2"), + newA("ns2.dns.skydns.test. 3600 A 10.0.0.3"), + }, + }, + // A Record For NS Record Test + { + Qname: "ns.dns.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{newA("ns.dns.skydns.test. 3600 A 10.0.0.2")}, + }, + // A Record Test + { + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, + }, + // Multiple A Record Test + { + Qname: "ipaddr.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newA("ipaddr.skydns.test. 3600 A 172.16.1.1"), + newA("ipaddr.skydns.test. 3600 A 172.16.1.2"), + }, + }, + // A Record Test with SRV + { + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, + Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, + }, + // AAAAA Record Test + { + Qname: "105.server3.production.region2.skydns.test.", Qtype: dns.TypeAAAA, + Answer: []dns.RR{newAAAA("105.server3.production.region2.skydns.test. 3600 AAAA 2001::8:8:8:8")}, + }, + // Multi SRV with the same target, should be dedupped. + { + Qname: "*.cname2.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("*.cname2.skydns.test. 3600 IN SRV 10 100 0 www.miek.nl."), + }, + Extra: []dns.RR{ + newA("a.miek.nl. 3600 IN A 176.58.119.54"), + newAAAA("a.miek.nl. 3600 IN AAAA 2a01:7e00::f03c:91ff:feae:e74c"), + newCNAME("www.miek.nl. 3600 IN CNAME a.miek.nl."), + }, + }, + // TTL Test + { + // This test is referenced by number from DNSTtlRRset + Qname: "ttl.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{newA("ttl.skydns.test. 360 A 10.0.0.2")}, + }, + // CNAME Test + { + Qname: "1.cname.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newCNAME("1.cname.skydns.test. 3600 CNAME 104.server1.development.region1.skydns.test."), + newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), + }, + }, + // Direct CNAME Test + { + Qname: "1.cname.skydns.test.", Qtype: dns.TypeCNAME, + Answer: []dns.RR{ + newCNAME("1.cname.skydns.test. 3600 CNAME 104.server1.development.region1.skydns.test."), + }, + }, + // CNAME (unresolvable internal name) + { + Qname: "2.cname.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{}, + Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, + }, + // CNAME loop detection + { + Qname: "3.cname.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{}, + Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, + }, + // CNAME (resolvable external name) + { + Qname: "external1.cname.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newA("a.miek.nl. 60 IN A 176.58.119.54"), + newCNAME("external1.cname.skydns.test. 60 IN CNAME www.miek.nl."), + newCNAME("www.miek.nl. 60 IN CNAME a.miek.nl."), + }, + }, + // CNAME (unresolvable external name) + { + Qname: "external2.cname.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{}, + Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, + }, + // Priority Test + { + Qname: "region6.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{newSRV("region6.skydns.test. 3600 SRV 333 100 80 server4.")}, + }, + // Subdomain Test + { + Qname: "region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("region1.skydns.test. 3600 SRV 10 33 0 104.server1.development.region1.skydns.test."), + newSRV("region1.skydns.test. 3600 SRV 10 33 80 server2"), + newSRV("region1.skydns.test. 3600 SRV 10 33 8080 server1.")}, + Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, + }, + // Subdomain Weight Test + { + Qname: "region5.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("region5.skydns.test. 3600 SRV 10 22 0 server2."), + newSRV("region5.skydns.test. 3600 SRV 10 36 0 server1."), + newSRV("region5.skydns.test. 3600 SRV 10 41 0 server3."), + newSRV("region5.skydns.test. 3600 SRV 30 100 0 server4.")}, + }, + // Wildcard Test + { + Qname: "*.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("*.region1.skydns.test. 3600 SRV 10 33 0 104.server1.development.region1.skydns.test."), + newSRV("*.region1.skydns.test. 3600 SRV 10 33 80 server2"), + newSRV("*.region1.skydns.test. 3600 SRV 10 33 8080 server1.")}, + Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, + }, + // Wildcard Test + { + Qname: "production.*.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("production.*.skydns.test. 3600 IN SRV 10 50 0 105.server3.production.region2.skydns.test."), + newSRV("production.*.skydns.test. 3600 IN SRV 10 50 80 server2.")}, + Extra: []dns.RR{newAAAA("105.server3.production.region2.skydns.test. 3600 IN AAAA 2001::8:8:8:8")}, + }, + // NXDOMAIN Test + { + Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, + Rcode: dns.RcodeNameError, + Ns: []dns.RR{ + newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), + }, + }, + // NODATA Test + { + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeTXT, + Ns: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, + }, + // NODATA Test 2 + { + Qname: "100.server1.development.region1.skydns.test.", Qtype: dns.TypeA, + Rcode: dns.RcodeSuccess, + Ns: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, + }, + // CNAME Test that targets multiple A records (hits a directory in etcd) + { + Qname: "1.backend.in.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newCNAME("1.backend.in.skydns.test. IN CNAME ipaddr.skydns.test."), + newA("ipaddr.skydns.test. IN A 172.16.1.1"), + newA("ipaddr.skydns.test. IN A 172.16.1.2"), + }, + }, + // Query a etcd directory key + { + Qname: "backend.in.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{ + newCNAME("backend.in.skydns.test. IN CNAME ipaddr.skydns.test."), + newA("ipaddr.skydns.test. IN A 172.16.1.1"), + newA("ipaddr.skydns.test. IN A 172.16.1.2"), + }, + }, + // Txt + { + Qname: "a1.txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("a1.txt.skydns.test. IN TXT \"abc\""), + }, + }, + { + Qname: "a2.txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("a2.txt.skydns.test. IN TXT \"abc abc\""), + }, + }, + { + Qname: "txt.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + newTXT("txt.skydns.test. IN TXT \"abc abc\""), + newTXT("txt.skydns.test. IN TXT \"abc\""), + }, + }, + + // DNSSEC + + // DNSKEY Test + { + dnssec: true, + Qname: "skydns.test.", Qtype: dns.TypeDNSKEY, + Answer: []dns.RR{ + newDNSKEY("skydns.test. 3600 DNSKEY 256 3 5 deadbeaf"), + newRRSIG("skydns.test. 3600 RRSIG DNSKEY 5 2 3600 0 0 51945 skydns.test. deadbeaf"), + }, + Extra: []dns.RR{new(dns.OPT)}, + }, + // Signed Response Test + { + dnssec: true, + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newRRSIG("104.server1.development.region1.skydns.test. 3600 RRSIG SRV 5 6 3600 0 0 51945 skydns.test. deadbeaf"), + newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, + Extra: []dns.RR{ + newRRSIG("104.server1.developmen.region1.skydns.test. 3600 RRSIG A 5 6 3600 0 0 51945 skydns.test. deadbeaf"), + newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), + new(dns.OPT), + }, + }, + // Signed Response Test, ask twice to check cache + { + dnssec: true, + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newRRSIG("104.server1.development.region1.skydns.test. 3600 RRSIG SRV 5 6 3600 0 0 51945 skydns.test. deadbeaf"), + newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, + Extra: []dns.RR{ + newRRSIG("104.server1.developmen.region1.skydns.test. 3600 RRSIG A 5 6 3600 0 0 51945 skydns.test. deadbeaf"), + newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), + new(dns.OPT), + }, + }, + // NXDOMAIN Test + { + dnssec: true, + Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, + Rcode: dns.RcodeNameError, + Ns: []dns.RR{ + newNSEC3("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 NSEC3 1 0 0 - 44OHAQ2NJB0IDNVOLT9GGTHVSK1E1UVA"), + newRRSIG("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newNSEC3("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 NSEC3 1 0 0 - AH4V7G5QOIRI26ARMRB3BLDQI1SNG6A3 A AAAA SRV RRSIG"), + newRRSIG("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newNSEC3("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 NSEC3 1 0 0 - LKSD858F4CLDL7EMDORD75K5JEKS49PA"), + newRRSIG("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814205559 20140807175559 51945 skydns.test. deadbeaf"), + newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), + }, + Extra: []dns.RR{new(dns.OPT)}, + }, + // NXDOMAIN Test, cache test + { + dnssec: true, + Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, + Rcode: dns.RcodeNameError, + Ns: []dns.RR{ + newNSEC3("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 NSEC3 1 0 0 - 44OHAQ2NJB0IDNVOLT9GGTHVSK1E1UVA"), + newRRSIG("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newNSEC3("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 NSEC3 1 0 0 - AH4V7G5QOIRI26ARMRB3BLDQI1SNG6A3 A AAAA SRV RRSIG"), + newRRSIG("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newNSEC3("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 NSEC3 1 0 0 - LKSD858F4CLDL7EMDORD75K5JEKS49PA"), + newRRSIG("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), + newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814205559 20140807175559 51945 skydns.test. deadbeaf"), + newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), + }, + Extra: []dns.RR{new(dns.OPT)}, + }, + // NODATA Test + { + dnssec: true, + Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeTXT, + Rcode: dns.RcodeSuccess, + Ns: []dns.RR{ + newNSEC3("E76CLEL5E7TQHRTFLTBVH0645NEKFJV9.skydns.test. 60 NSEC3 1 0 0 - E76CLEL5E7TQHRTFLTBVH0645NEKFJVA A AAAA SRV RRSIG"), + newRRSIG("E76CLEL5E7TQHRTFLTBVH0645NEKFJV9.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814211641 20140807181641 51945 skydns.test. deadbeef"), + newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814211641 20140807181641 51945 skydns.test. deadbeef"), + newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407445200 28800 7200 604800 60"), + }, + Extra: []dns.RR{new(dns.OPT)}, + }, + // Reverse v4 local answer + { + Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, + Answer: []dns.RR{newPTR("1.0.0.10.in-addr.arpa. 3600 PTR reverse.example.com.")}, + }, + // Reverse v6 local answer + + // Reverse forwarding answer, TODO(miek) does not work + // { + // Qname: "1.0.16.172.in-addr.arpa.", Qtype: dns.TypePTR, + // Rcode: dns.RcodeNameError, + // Ns: []dns.RR{newSOA("16.172.in-addr.arpa. 10800 SOA localhost. nobody.invalid. 0 0 0 0 0")}, + // }, + + // Reverse no answer + + // Local data query + { + Qname: "local.dns.skydns.test.", Qtype: dns.TypeA, + Answer: []dns.RR{newA("local.dns.skydns.test. 3600 A 10.0.0.1")}, + }, + // Author test + { + Qname: "skydns.test.", Qtype: dns.TypeTXT, + chaos: true, + Answer: []dns.RR{ + newTXT("skydns.test. 0 TXT \"Brian Ketelsen\""), + newTXT("skydns.test. 0 TXT \"Erik St. Martin\""), + newTXT("skydns.test. 0 TXT \"Michael Crosby\""), + newTXT("skydns.test. 0 TXT \"Miek Gieben\""), + }, + }, + // Author test 2 + { + Qname: "authors.bind.", Qtype: dns.TypeTXT, + chaos: true, + Answer: []dns.RR{ + newTXT("authors.bind. 0 TXT \"Brian Ketelsen\""), + newTXT("authors.bind. 0 TXT \"Erik St. Martin\""), + newTXT("authors.bind. 0 TXT \"Michael Crosby\""), + newTXT("authors.bind. 0 TXT \"Miek Gieben\""), + }, + }, + // Author test, caps test + { + Qname: "AUTHOrs.BIND.", Qtype: dns.TypeTXT, + chaos: true, + Answer: []dns.RR{ + newTXT("AUTHOrs.BIND. 0 TXT \"Brian Ketelsen\""), + newTXT("AUTHOrs.BIND. 0 TXT \"Erik St. Martin\""), + newTXT("AUTHOrs.BIND. 0 TXT \"Michael Crosby\""), + newTXT("AUTHOrs.BIND. 0 TXT \"Miek Gieben\""), + }, + }, + // Author test 3, no answer. + { + Qname: "local.dns.skydns.test.", Qtype: dns.TypeA, + Rcode: dns.RcodeServerFailure, + chaos: true, + }, +} + +func newA(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } +func newAAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } +func newCNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } +func newSRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } +func newSOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } +func newNS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } +func newDNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) } +func newRRSIG(rr string) *dns.RRSIG { r, _ := dns.NewRR(rr); return r.(*dns.RRSIG) } +func newNSEC3(rr string) *dns.NSEC3 { r, _ := dns.NewRR(rr); return r.(*dns.NSEC3) } +func newPTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } +func newTXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } + +func BenchmarkDNSSingleCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, true) + defer s.Stop() + + serv := services[0] + addService(t, s, serv.Key, 0, serv) + defer delService(t, s, serv.Key) + + c := new(dns.Client) + tc := dnsTestCases[0] + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} + +func BenchmarkDNSWildcardCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, true) + defer s.Stop() + + for _, serv := range services { + m := &msg.Service{Host: serv.Host, Port: serv.Port} + addService(t, s, serv.Key, 0, m) + defer delService(t, s, serv.Key) + } + + c := new(dns.Client) + tc := dnsTestCases[8] // Wildcard Test + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} + +func BenchmarkDNSSECSingleCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, true) + defer s.Stop() + + serv := services[0] + addService(t, s, serv.Key, 0, serv) + defer delService(t, s, serv.Key) + + c := new(dns.Client) + tc := dnsTestCases[0] + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + m.SetEdns0(4096, true) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} + +func BenchmarkDNSSingleNoCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, false) + defer s.Stop() + + serv := services[0] + addService(t, s, serv.Key, 0, serv) + defer delService(t, s, serv.Key) + + c := new(dns.Client) + tc := dnsTestCases[0] + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} + +func BenchmarkDNSWildcardNoCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, false) + defer s.Stop() + + for _, serv := range services { + m := &msg.Service{Host: serv.Host, Port: serv.Port} + addService(t, s, serv.Key, 0, m) + defer delService(t, s, serv.Key) + } + + c := new(dns.Client) + tc := dnsTestCases[8] // Wildcard Test + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} + +func BenchmarkDNSSECSingleNoCache(b *testing.B) { + b.StopTimer() + t := new(testing.T) + s := newTestServerDNSSEC(t, false) + defer s.Stop() + + serv := services[0] + addService(t, s, serv.Key, 0, serv) + defer delService(t, s, serv.Key) + + c := new(dns.Client) + tc := dnsTestCases[0] + m := new(dns.Msg) + m.SetQuestion(tc.Qname, tc.Qtype) + m.SetEdns0(4096, true) + + b.StartTimer() + for i := 0; i < b.N; i++ { + c.Exchange(m, "127.0.0.1:"+StrPort) + } +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/singleinflight.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/singleinflight.go new file mode 100644 index 000000000000..2b4b093f6c24 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/singleinflight.go @@ -0,0 +1,54 @@ +// Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved. +// Use of this source code is governed by The MIT License (MIT) that can be +// found in the LICENSE file. + +package server + +import ( + "sync" + + "github.com/miekg/dns" +) + +var ( + inflight = new(single) +) + +// Adapted from singleinflight.go from the original Go Code. Copyright 2013 The Go Authors. +type call struct { + wg sync.WaitGroup + val *dns.RRSIG + err error + dups int +} + +type single struct { + sync.Mutex + m map[string]*call +} + +func (g *single) Do(key string, fn func() (*dns.RRSIG, error)) (*dns.RRSIG, error, 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.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.Unlock() + + c.val, c.err = fn() + c.wg.Done() + + g.Lock() + delete(g.m, key) + g.Unlock() + + return c.val, c.err, c.dups > 0 +} diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/stats.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/stats.go new file mode 100644 index 000000000000..1f8dd213b651 --- /dev/null +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/stats.go @@ -0,0 +1,25 @@ +// 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 server + +// Counter is the metric interface used by this package +type Counter interface { + Inc(i int64) +} + +type nopCounter struct{} + +func (nopCounter) Inc(_ int64) {} + +// These are the stat variables defined by this package. +var ( + StatsForwardCount Counter = nopCounter{} + StatsLookupCount Counter = nopCounter{} + StatsRequestCount Counter = nopCounter{} + StatsDnssecOkCount Counter = nopCounter{} + StatsDnssecCacheMiss Counter = nopCounter{} + StatsNameErrorCount Counter = nopCounter{} + StatsNoDataCount Counter = nopCounter{} +) From 55b2caea78180a7445261e9cb130ceb58a0a0396 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 8 Mar 2015 23:03:50 -0400 Subject: [PATCH 3/4] UPSTREAM: Disable systemd activation for DNS Need to work with go-systemd to see if we can abstract the syscall.CloseOnExec which is not supported on windows. --- .../skynetservices/skydns/server/server.go | 67 ++++--------------- 1 file changed, 14 insertions(+), 53 deletions(-) diff --git a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go index 2e9bacaf7e81..87e12878fbc4 100644 --- a/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go +++ b/Godeps/_workspace/src/github.com/skynetservices/skydns/server/server.go @@ -14,7 +14,6 @@ import ( "time" "github.com/coreos/go-etcd/etcd" - "github.com/coreos/go-systemd/activation" "github.com/miekg/dns" "github.com/skynetservices/skydns/cache" "github.com/skynetservices/skydns/msg" @@ -99,60 +98,22 @@ func (s *server) Run() error { } } - if s.config.Systemd { - packetConns, err := activation.PacketConns(false) - if err != nil { - return err - } - listeners, err := activation.Listeners(true) - if err != nil { - return err - } - if len(packetConns) == 0 && len(listeners) == 0 { - return fmt.Errorf("no UDP or TCP sockets supplied by systemd") - } - for _, p := range packetConns { - if u, ok := p.(*net.UDPConn); ok { - s.group.Add(1) - go func() { - defer s.group.Done() - if err := dns.ActivateAndServe(nil, u, mux); err != nil { - log.Fatalf("skydns: %s", err) - } - }() - dnsReadyMsg(u.LocalAddr().String(), "udp") - } + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ListenAndServe(s.config.DnsAddr, "tcp", mux); err != nil { + log.Fatalf("skydns: %s", err) } - for _, l := range listeners { - if t, ok := l.(*net.TCPListener); ok { - s.group.Add(1) - go func() { - defer s.group.Done() - if err := dns.ActivateAndServe(t, nil, mux); err != nil { - log.Fatalf("skydns: %s", err) - } - }() - dnsReadyMsg(t.Addr().String(), "tcp") - } + }() + dnsReadyMsg(s.config.DnsAddr, "tcp") + s.group.Add(1) + go func() { + defer s.group.Done() + if err := dns.ListenAndServe(s.config.DnsAddr, "udp", mux); err != nil { + log.Fatalf("skydns: %s", err) } - } else { - s.group.Add(1) - go func() { - defer s.group.Done() - if err := dns.ListenAndServe(s.config.DnsAddr, "tcp", mux); err != nil { - log.Fatalf("skydns: %s", err) - } - }() - dnsReadyMsg(s.config.DnsAddr, "tcp") - s.group.Add(1) - go func() { - defer s.group.Done() - if err := dns.ListenAndServe(s.config.DnsAddr, "udp", mux); err != nil { - log.Fatalf("skydns: %s", err) - } - }() - dnsReadyMsg(s.config.DnsAddr, "udp") - } + }() + dnsReadyMsg(s.config.DnsAddr, "udp") s.group.Wait() return nil From dc16de5dd8a2915a4da9dd13d56f047b327101ed Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 7 Mar 2015 20:02:45 -0500 Subject: [PATCH 4/4] Add DNS support to OpenShift Nodes will now assume the master is a DNS server (unless OPENSHIFT_DNS_ADDR is set) and will pass that value in to the containers they run. The master will listen on 53 (if it can bind to it) or 8053 to serve DNS queries. --- pkg/cmd/server/command.go | 8 + pkg/cmd/server/config.go | 15 +- pkg/cmd/server/kube_master.go | 1 - pkg/cmd/server/kubernetes/node.go | 22 ++- pkg/cmd/server/node_config.go | 11 +- pkg/cmd/server/origin/config.go | 2 + pkg/cmd/server/origin/master.go | 24 +++ pkg/cmd/server/origin_master.go | 2 +- pkg/cmd/server/start.go | 2 +- pkg/cmd/util/net.go | 11 ++ pkg/dns/server.go | 85 +++++++++++ pkg/dns/serviceaccessor.go | 122 ++++++++++++++++ pkg/dns/serviceresolver.go | 137 ++++++++++++++++++ test/integration/dns_test.go | 70 +++++++++ .../{test_server.go => server_test.go} | 0 15 files changed, 505 insertions(+), 7 deletions(-) create mode 100644 pkg/dns/server.go create mode 100644 pkg/dns/serviceaccessor.go create mode 100644 pkg/dns/serviceresolver.go create mode 100644 test/integration/dns_test.go rename test/integration/{test_server.go => server_test.go} (100%) diff --git a/pkg/cmd/server/command.go b/pkg/cmd/server/command.go index 9df582f12284..5b944ef476a7 100644 --- a/pkg/cmd/server/command.go +++ b/pkg/cmd/server/command.go @@ -3,6 +3,7 @@ package server import ( "errors" "fmt" + "net" _ "net/http/pprof" "strings" @@ -140,5 +141,12 @@ func (cfg *Config) Complete(args []string) { } cfg.NodeList = nodeList.List() + + // in the all-in-one, default ClusterDNS to the master's address + if url, err := cfg.GetMasterAddress(); err == nil { + if host, _, err := net.SplitHostPort(url.Host); err == nil { + cfg.ClusterDNS = net.ParseIP(host) + } + } } } diff --git a/pkg/cmd/server/config.go b/pkg/cmd/server/config.go index 4651e915a227..d11a097af699 100644 --- a/pkg/cmd/server/config.go +++ b/pkg/cmd/server/config.go @@ -3,7 +3,6 @@ package server import ( "fmt" "net" - _ "net/http/pprof" "net/url" "os/exec" "strconv" @@ -21,6 +20,7 @@ import ( "github.com/openshift/origin/pkg/api/latest" "github.com/openshift/origin/pkg/cmd/flagtypes" "github.com/openshift/origin/pkg/cmd/util" + cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/docker" "github.com/openshift/origin/pkg/cmd/util/variable" ) @@ -41,6 +41,7 @@ type Config struct { EtcdAddr flagtypes.Addr KubernetesAddr flagtypes.Addr PortalNet flagtypes.IPNet + DNSBindAddr flagtypes.Addr // addresses for external clients MasterPublicAddr flagtypes.Addr KubernetesPublicAddr flagtypes.Addr @@ -57,6 +58,8 @@ type Config struct { CertDir string + ClusterDNS net.IP + StorageVersion string NodeList kutil.StringList @@ -99,6 +102,16 @@ func NewDefaultConfig() *Config { Hostname: hostname, } + // TODO: allow DNS binding to be disabled. + dnsAddr := flagtypes.Addr{Value: config.BindAddr.Host, DefaultPort: 53}.Default() + if !cmdutil.TryListen(dnsAddr.URL.Host) { + original := dnsAddr.URL.Host + dnsAddr.DefaultPort = 8053 + dnsAddr = dnsAddr.Default() + glog.Warningf("Unable to bind DNS as %s (you may need to run as root), using %s which will not resolve from all locations", original, dnsAddr.URL.Host) + } + config.DNSBindAddr = dnsAddr + config.ClientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&config.ClientConfigLoadingRules, &clientcmd.ConfigOverrides{}) return config diff --git a/pkg/cmd/server/kube_master.go b/pkg/cmd/server/kube_master.go index 7f253feab989..ded402f1dafd 100644 --- a/pkg/cmd/server/kube_master.go +++ b/pkg/cmd/server/kube_master.go @@ -3,7 +3,6 @@ package server import ( "fmt" "net" - _ "net/http/pprof" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" klatest "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" diff --git a/pkg/cmd/server/kubernetes/node.go b/pkg/cmd/server/kubernetes/node.go index 0a976560a298..870e10c2fedf 100644 --- a/pkg/cmd/server/kubernetes/node.go +++ b/pkg/cmd/server/kubernetes/node.go @@ -27,6 +27,7 @@ import ( "github.com/golang/glog" cadvisor "github.com/google/cadvisor/client" + cmdutil "github.com/openshift/origin/pkg/cmd/util" dockerutil "github.com/openshift/origin/pkg/cmd/util/docker" "github.com/openshift/origin/pkg/kubelet/app" "github.com/openshift/origin/pkg/service" @@ -67,6 +68,9 @@ type NodeConfig struct { // The directory that volumes will be stored under VolumeDir string + ClusterDomain string + ClusterDNS net.IP + // The image used as the Kubelet network namespace and volume container. NetworkContainerImage string @@ -139,6 +143,17 @@ func (c *NodeConfig) initializeVolumeDir(ce commandExecutor, path string) (strin // RunKubelet starts the Kubelet. func (c *NodeConfig) RunKubelet() { + // TODO: clean this up and make it more formal (service named 'dns'?). Use multiple ports. + clusterDNS := c.ClusterDNS + if clusterDNS == nil { + if service, err := c.Client.Endpoints(kapi.NamespaceDefault).Get("kubernetes"); err == nil && len(service.Endpoints) > 0 { + firstIP := service.Endpoints[0].IP + if err := cmdutil.WaitForSuccessfulDial(false, "tcp", fmt.Sprintf("%s:%d", firstIP, 53), 50*time.Millisecond, 0, 2); err == nil { + clusterDNS = net.ParseIP(firstIP) + } + } + } + // initialize Kubelet // Allow privileged containers // TODO: make this configurable and not the default https://github.com/openshift/origin/issues/662 @@ -158,8 +173,8 @@ func (c *NodeConfig) RunKubelet() { 1*time.Second, 5, cfg.IsSourceSeen, - "", - nil, + c.ClusterDomain, + clusterDNS, kapi.NamespaceDefault, app.ProbeVolumePlugins(), 5*time.Minute) @@ -180,6 +195,9 @@ func (c *NodeConfig) RunKubelet() { go util.Forever(func() { glog.Infof("Started Kubelet for node %s, server at %s:%d", c.NodeHost, c.BindHost, NodePort) + if clusterDNS != nil { + glog.Infof(" Kubelet is setting %s as a DNS nameserver for domain %q", clusterDNS, c.ClusterDomain) + } if c.TLS { server.TLSConfig = &tls.Config{ diff --git a/pkg/cmd/server/node_config.go b/pkg/cmd/server/node_config.go index 170e0ee28491..2ce82bba841b 100644 --- a/pkg/cmd/server/node_config.go +++ b/pkg/cmd/server/node_config.go @@ -1,7 +1,7 @@ package server import ( - _ "net/http/pprof" + "net" "github.com/openshift/origin/pkg/cmd/server/kubernetes" ) @@ -16,6 +16,12 @@ func (cfg Config) BuildKubernetesNodeConfig() (*kubernetes.NodeConfig, error) { return nil, err } + dnsDomain := env("OPENSHIFT_DNS_DOMAIN", "local") + dnsIP := cfg.ClusterDNS + if clusterDNS := env("OPENSHIFT_DNS_ADDR", ""); len(clusterDNS) > 0 { + dnsIP = net.ParseIP(clusterDNS) + } + // define a function for resolving components to names imageResolverFn := cfg.ImageTemplate.ExpandOrDie @@ -24,6 +30,9 @@ func (cfg Config) BuildKubernetesNodeConfig() (*kubernetes.NodeConfig, error) { NodeHost: cfg.Hostname, MasterHost: kubernetesAddr.String(), + ClusterDomain: dnsDomain, + ClusterDNS: dnsIP, + VolumeDir: cfg.VolumeDir, NetworkContainerImage: imageResolverFn("pod"), diff --git a/pkg/cmd/server/origin/config.go b/pkg/cmd/server/origin/config.go index e308615acf88..645dd6e7ec43 100644 --- a/pkg/cmd/server/origin/config.go +++ b/pkg/cmd/server/origin/config.go @@ -51,6 +51,8 @@ type MasterConfigParameters struct { MasterPublicAddr string KubernetesPublicAddr string AssetPublicAddr string + // host:port to bind DNS on. The default is port 53. + DNSBindAddr string // LogoutURI is an optional, absolute URI to redirect web browsers to after logging out of the web console. // If not specified, the built-in logout page is shown. LogoutURI string diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index b83acdadda80..d102dd93d03a 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -51,6 +51,7 @@ import ( deployconfigregistry "github.com/openshift/origin/pkg/deploy/registry/deployconfig" deployetcd "github.com/openshift/origin/pkg/deploy/registry/etcd" deployrollback "github.com/openshift/origin/pkg/deploy/rollback" + "github.com/openshift/origin/pkg/dns" imageetcd "github.com/openshift/origin/pkg/image/registry/etcd" "github.com/openshift/origin/pkg/image/registry/image" "github.com/openshift/origin/pkg/image/registry/imagerepository" @@ -124,6 +125,13 @@ func (c *MasterConfig) DeploymentClient() *kclient.Client { return c.MasterConfigParameters.KubeClient } +// DNSServerClient returns the DNS server client object +// It must have the following capabilities: +// list, watch all services in all namespaces +func (c *MasterConfig) DNSServerClient() *kclient.Client { + return c.MasterConfigParameters.KubeClient +} + // BuildLogClient returns the build log client object func (c *MasterConfig) BuildLogClient() *kclient.Client { return c.MasterConfigParameters.KubeClient @@ -564,6 +572,22 @@ func (c *MasterConfig) RunAssetServer() { glog.Infof("OpenShift UI available at %s", c.AssetPublicAddr) } +func (c *MasterConfig) RunDNSServer() { + config, err := dns.NewServerDefaults() + if err != nil { + glog.Fatalf("Could not start DNS: %v", err) + } + config.DnsAddr = c.DNSBindAddr + go func() { + err := dns.ListenAndServe(config, c.DNSServerClient(), c.EtcdHelper.Client.(*etcdclient.Client)) + glog.Fatalf("Could not start DNS: %v", err) + }() + + cmdutil.WaitForSuccessfulDial(false, "tcp", c.DNSBindAddr, 100*time.Millisecond, 100*time.Millisecond, 100) + + glog.Infof("OpenShift DNS listening at %s", c.DNSBindAddr) +} + // RunBuildController starts the build sync loop for builds and buildConfig processing. func (c *MasterConfig) RunBuildController() { // initialize build controller diff --git a/pkg/cmd/server/origin_master.go b/pkg/cmd/server/origin_master.go index 1904379f1869..9714e6897d6b 100644 --- a/pkg/cmd/server/origin_master.go +++ b/pkg/cmd/server/origin_master.go @@ -4,7 +4,6 @@ import ( "crypto/x509" "fmt" "net" - _ "net/http/pprof" "strings" "time" @@ -87,6 +86,7 @@ func (cfg Config) BuildOriginMasterConfig() (*origin.MasterConfig, error) { openshiftConfigParameters := origin.MasterConfigParameters{ MasterBindAddr: cfg.BindAddr.URL.Host, AssetBindAddr: cfg.GetAssetBindAddress(), + DNSBindAddr: cfg.DNSBindAddr.URL.Host, MasterAddr: masterAddr.String(), KubernetesAddr: kubeAddr.String(), MasterPublicAddr: masterPublicAddr.String(), diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 9a6bc64857d8..7ea148b0086f 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -4,7 +4,6 @@ import ( "fmt" "net" "net/http" - _ "net/http/pprof" "net/url" "os" "strconv" @@ -97,6 +96,7 @@ func (cfg Config) startMaster() error { glog.Infof("Using images from %q", openshiftConfig.ImageFor("")) + openshiftConfig.RunDNSServer() openshiftConfig.RunAssetServer() openshiftConfig.RunBuildController() openshiftConfig.RunBuildPodController() diff --git a/pkg/cmd/util/net.go b/pkg/cmd/util/net.go index c907c826f09a..e4aa185c5939 100644 --- a/pkg/cmd/util/net.go +++ b/pkg/cmd/util/net.go @@ -8,6 +8,17 @@ import ( "github.com/golang/glog" ) +// TryListen tries to open a connection on the given port and returns true if it succeeded. +func TryListen(hostPort string) bool { + l, err := net.Listen("tcp", hostPort) + if err != nil { + glog.V(5).Infof("Failure while checking listen on %s: %v", err) + return false + } + defer l.Close() + return true +} + // WaitForDial attempts to connect to the given address, closing and returning nil on the first successful connection. func WaitForSuccessfulDial(https bool, network, address string, timeout, interval time.Duration, retries int) error { var ( diff --git a/pkg/dns/server.go b/pkg/dns/server.go new file mode 100644 index 000000000000..a8ea8a4a3b57 --- /dev/null +++ b/pkg/dns/server.go @@ -0,0 +1,85 @@ +package dns + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + + "github.com/coreos/go-etcd/etcd" + "github.com/prometheus/client_golang/prometheus" + backendetcd "github.com/skynetservices/skydns/backends/etcd" + "github.com/skynetservices/skydns/server" +) + +// NewServerDefaults returns the default SkyDNS server configuration for a DNS server. +func NewServerDefaults() (*server.Config, error) { + config := &server.Config{ + Domain: "local.", + } + return config, server.SetDefaults(config) +} + +// ListenAndServe starts a DNS server that exposes services and values stored in etcd (if etcdclient +// is not nil). It will block until the server exits. +// TODO: hoist the service accessor out of this package so it can be reused. +func ListenAndServe(config *server.Config, client *client.Client, etcdclient *etcd.Client) error { + stop := make(chan struct{}) + accessor := NewCachedServiceAccessor(client, stop) + resolver := NewServiceResolver(config, accessor) + resolvers := server.FirstBackend{resolver} + if etcdclient != nil { + resolvers = append(resolvers, backendetcd.NewBackend(etcdclient, &backendetcd.Config{ + Ttl: config.Ttl, + Priority: config.Priority, + })) + } + + s := server.New(resolvers, config) + defer close(stop) + return s.Run() +} + +// counter is a SkyDNS compatible Counter +type counter struct { + prometheus.Counter +} + +// newCounter registers a prometheus counter and wraps it to match SkyDNS +func newCounter(c prometheus.Counter) server.Counter { + prometheus.MustRegister(c) + return counter{c} +} + +func (c counter) Inc(val int64) { + c.Counter.Add(float64(val)) +} + +// Add prometheus logging to SkyDNS +func init() { + server.StatsForwardCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_forward_count", + Help: "Counter of DNS requests forwarded", + })) + server.StatsLookupCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_lookup_count", + Help: "Counter of DNS lookups performed", + })) + server.StatsRequestCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_request_count", + Help: "Counter of DNS requests made", + })) + server.StatsDnssecOkCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_dnssec_ok_count", + Help: "Counter of DNSSEC requests that were valid", + })) + server.StatsDnssecCacheMiss = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_dnssec_cache_miss_count", + Help: "Counter of DNSSEC requests that missed the cache", + })) + server.StatsNameErrorCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_name_error_count", + Help: "Counter of DNS requests resulting in a name error", + })) + server.StatsNoDataCount = newCounter(prometheus.NewCounter(prometheus.CounterOpts{ + Name: "dns_no_data_count", + Help: "Counter of DNS requests that contained no data", + })) +} diff --git a/pkg/dns/serviceaccessor.go b/pkg/dns/serviceaccessor.go new file mode 100644 index 000000000000..a79d0f7c64df --- /dev/null +++ b/pkg/dns/serviceaccessor.go @@ -0,0 +1,122 @@ +package dns + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" +) + +// Service Accessor is the interface used by the ServiceResolver to access +// services. +type ServiceAccessor interface { + client.ServicesNamespacer + ServiceByPortalIP(ip string) (*api.Service, error) +} + +// cachedServiceAccessor provides a cache of services that can answer queries +// about service lookups efficiently. +type cachedServiceAccessor struct { + reflector *cache.Reflector + store cache.Indexer +} + +// cachedServiceAccessor implements ServiceAccessor +var _ ServiceAccessor = &cachedServiceAccessor{} + +// NewCachedServiceAccessor returns a service accessor that can answer queries about services. +// It uses a backing cache to make PortalIP lookups efficient. +func NewCachedServiceAccessor(client *client.Client, stopCh <-chan struct{}) ServiceAccessor { + lw := cache.NewListWatchFromClient(client, "services", api.NamespaceAll, labels.Everything()) + store := cache.NewIndexer(cache.MetaNamespaceKeyFunc, map[string]cache.IndexFunc{ + "portalIP": indexServiceByPortalIP, // for reverse lookups + "namespace": cache.MetaNamespaceIndexFunc, + }) + reflector := cache.NewReflector(lw, &api.Service{}, store) + if stopCh != nil { + reflector.RunUntil(stopCh) + } else { + reflector.Run() + } + return &cachedServiceAccessor{ + reflector: reflector, + store: store, + } +} + +// ServiceByPortalIP returns the first service that matches the provided portalIP value. +// errors.IsNotFound(err) will be true if no such service exists. +func (a *cachedServiceAccessor) ServiceByPortalIP(ip string) (*api.Service, error) { + items, err := a.store.Index("portalIP", &api.Service{Spec: api.ServiceSpec{PortalIP: ip}}) + if err != nil { + return nil, err + } + if len(items) == 0 { + return nil, errors.NewNotFound("service", "portalIP="+ip) + } + return items[0].(*api.Service), nil +} + +// indexServiceByPortalIP creates an index between a portalIP and the service that +// uses it. +func indexServiceByPortalIP(obj interface{}) (string, error) { + return obj.(*api.Service).Spec.PortalIP, nil +} + +func (a *cachedServiceAccessor) Services(namespace string) client.ServiceInterface { + return cachedServiceNamespacer{a, namespace} +} + +// TODO: needs to be unified with Registry interfaces once that work is done. +type cachedServiceNamespacer struct { + accessor *cachedServiceAccessor + namespace string +} + +var _ client.ServiceInterface = cachedServiceNamespacer{} + +func (a cachedServiceNamespacer) Get(name string) (*api.Service, error) { + item, ok, err := a.accessor.store.Get(&api.Service{ObjectMeta: api.ObjectMeta{Namespace: a.namespace, Name: name}}) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.NewNotFound("service", name) + } + return item.(*api.Service), nil +} + +func (a cachedServiceNamespacer) List(label labels.Selector) (*api.ServiceList, error) { + if !label.Empty() { + return nil, fmt.Errorf("label selection on the cache is not currently implemented") + } + items, err := a.accessor.store.Index("namespace", &api.Service{ObjectMeta: api.ObjectMeta{Namespace: a.namespace}}) + if err != nil { + return nil, err + } + services := make([]api.Service, 0, len(items)) + for i := range items { + services = append(services, *items[i].(*api.Service)) + } + return &api.ServiceList{ + // TODO: set ResourceVersion so that we can make watch work. + Items: services, + }, nil +} + +func (a cachedServiceNamespacer) Create(srv *api.Service) (*api.Service, error) { + return nil, fmt.Errorf("not implemented") +} +func (a cachedServiceNamespacer) Update(srv *api.Service) (*api.Service, error) { + return nil, fmt.Errorf("not implemented") +} +func (a cachedServiceNamespacer) Delete(name string) error { + return fmt.Errorf("not implemented") +} +func (a cachedServiceNamespacer) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/pkg/dns/serviceresolver.go b/pkg/dns/serviceresolver.go new file mode 100644 index 000000000000..3c94b87c6894 --- /dev/null +++ b/pkg/dns/serviceresolver.go @@ -0,0 +1,137 @@ +package dns + +import ( + "fmt" + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + + "github.com/skynetservices/skydns/msg" + "github.com/skynetservices/skydns/server" +) + +// ServiceResolver is a SkyDNS backend that will serve lookups for DNS entries +// based on Kubernetes service entries. The default DNS name for each service +// will be `..` where base can be an arbitrary depth +// DNS suffix. Queries not recognized within this base will return an error. +type ServiceResolver struct { + accessor ServiceAccessor + config *server.Config + base string +} + +// ServiceResolver implements server.Backend +var _ server.Backend = &ServiceResolver{} + +// NewServiceResolver creates an object that will return DNS record entries for +// SkyDNS based on service names. +func NewServiceResolver(config *server.Config, accessor ServiceAccessor) *ServiceResolver { + domain := config.Domain + if !strings.HasSuffix(domain, ".") { + domain = domain + "." + } + return &ServiceResolver{ + accessor: accessor, + config: config, + base: domain, + } +} + +// Records implements the SkyDNS Backend interface and returns standard records for +// a name. +func (b *ServiceResolver) Records(name string, exact bool) ([]msg.Service, error) { + if !strings.HasSuffix(name, b.base) { + return nil, nil + } + prefix := strings.Trim(strings.TrimSuffix(name, b.base), ".") + segments := strings.Split(prefix, ".") + switch len(segments) { + case 0: + case 1: + items, err := b.accessor.Services(segments[0]).List(labels.Everything()) + if err != nil { + return nil, err + } + services := make([]msg.Service, 0, len(items.Items)) + for _, svc := range items.Items { + services = append(services, msg.Service{ + Host: fmt.Sprintf("%s.%s", svc.Name, name), + Port: svc.Spec.Port, + + Priority: 10, + Weight: 10, + Ttl: 30, + + Text: "", + Key: msg.Path(name), + }) + } + return services, nil + case 2: + svc, err := b.accessor.Services(segments[1]).Get(segments[0]) + if err != nil { + return nil, err + } + return []msg.Service{ + { + Host: svc.Spec.PortalIP, + Port: svc.Spec.Port, + + Priority: 10, + Weight: 10, + Ttl: 30, + + Text: "", + Key: msg.Path(name), + }, + }, nil + default: + return nil, nil + } + return nil, nil +} + +// ReverseRecord implements the SkyDNS Backend interface and returns standard records for +// a name. +func (b *ServiceResolver) ReverseRecord(name string) (*msg.Service, error) { + portalIP, ok := extractIP(name) + if !ok { + return nil, fmt.Errorf("does not support reverse lookup with %s", name) + } + + svc, err := b.accessor.ServiceByPortalIP(portalIP) + if err != nil { + return nil, err + } + return &msg.Service{ + Host: fmt.Sprintf("%s.%s.%s", svc.Name, svc.Namespace, b.base), + Port: svc.Spec.Port, + + Priority: 10, + Weight: 10, + Ttl: 30, + + Text: "", + Key: msg.Path(name), + }, nil +} + +// arpaSuffix is the standard suffix for PTR IP reverse lookups. +const arpaSuffix = ".in-addr.arpa." + +// extractIP turns a standard PTR reverse record lookup name +// into an IP address +func extractIP(reverseName string) (string, bool) { + if !strings.HasSuffix(reverseName, arpaSuffix) { + return "", false + } + search := strings.TrimSuffix(reverseName, arpaSuffix) + + // reverse the segments and then combine them + segments := strings.Split(search, ".") + for i := 0; i < len(segments)/2; i++ { + j := len(segments) - i - 1 + segments[i], segments[j] = segments[j], segments[i] + } + return strings.Join(segments, "."), true +} diff --git a/test/integration/dns_test.go b/test/integration/dns_test.go new file mode 100644 index 000000000000..a4d466101fed --- /dev/null +++ b/test/integration/dns_test.go @@ -0,0 +1,70 @@ +// +build integration,!no-etcd + +package integration + +import ( + "fmt" + "testing" + "time" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + + "github.com/miekg/dns" +) + +func TestDNS(t *testing.T) { + cfg, err := StartTestAllInOne() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + port := cfg.DNSBindAddr.Port + server := fmt.Sprintf("127.0.0.1:%d", port) + + // verify service DNS entry is visible + stop := make(chan struct{}) + util.Until(func() { + m1 := &dns.Msg{ + MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true}, + Question: []dns.Question{{"kubernetes.default.local.", dns.TypeA, dns.ClassINET}}, + } + in, err := dns.Exchange(m1, server) + if err != nil { + t.Logf("unexpected error: %v", err) + return + } + if len(in.Answer) != 1 { + t.Logf("unexpected answer: %#v", in) + return + } + if a, ok := in.Answer[0].(*dns.A); ok { + if a.A == nil { + t.Errorf("expected an A record with an IP: %#v", a) + } + } else { + t.Errorf("expected an A record: %#v", in) + } + t.Log(in) + close(stop) + }, 50*time.Millisecond, stop) + + // verify recursive DNS lookup is visible + m1 := &dns.Msg{ + MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true}, + Question: []dns.Question{{"www.google.com.", dns.TypeA, dns.ClassINET}}, + } + in, err := dns.Exchange(m1, server) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(in.Answer) == 0 { + t.Fatalf("unexpected response: %#v", in) + } + if a, ok := in.Answer[0].(*dns.A); ok { + if a.A == nil { + t.Errorf("expected an A record with an IP: %#v", a) + } + } else { + t.Errorf("expected an A record: %#v", in) + } + t.Log(in) +} diff --git a/test/integration/test_server.go b/test/integration/server_test.go similarity index 100% rename from test/integration/test_server.go rename to test/integration/server_test.go