├── .gitignore ├── AUTHORS ├── Dockerfile ├── CONTRIBUTORS ├── server ├── doc.go ├── log.go ├── exchange.go ├── msg.go ├── backend.go ├── metrics_test.go ├── cache_test.go ├── stub.go ├── forwarding.go ├── nsec3.go ├── dnssec.go ├── config.go ├── server.go └── server_test.go ├── .travis.yml ├── cache ├── hit.go ├── cache_test.go └── cache.go ├── LICENSE ├── Gopkg.toml ├── singleflight └── singleflight.go ├── msg ├── service_test.go └── service.go ├── backends ├── etcd3 │ └── etcd3.go └── etcd │ └── etcd.go ├── metrics └── metrics.go ├── Gopkg.lock ├── main.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | skydns 2 | tags 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Erik St. Martin 2 | Brian Ketelsen 3 | Miek Gieben 4 | Michael Crosby 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | MAINTAINER Miek Gieben (@miekg) 3 | 4 | RUN apk --update add bind-tools && rm -rf /var/cache/apk/* 5 | 6 | ADD skydns skydns 7 | 8 | EXPOSE 53 53/udp 9 | ENTRYPOINT ["/skydns"] 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | The following people have contributed to this project: 2 | 3 | Erik St. Martin 4 | Brian Ketelsen 5 | Miek Gieben 6 | Sean Carey 7 | Michael Crosby 8 | -------------------------------------------------------------------------------- /server/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | // Package server provides a DNS server implementation that handles DNS 6 | // queries. To answer a query, the server asks the provided Backend for 7 | // DNS records, which are then converted to the proper answers. 8 | package server 9 | -------------------------------------------------------------------------------- /server/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import "log" 8 | 9 | // printf calls log.Printf with the parameters given. 10 | func logf(format string, a ...interface{}) { 11 | log.Printf("skydns: "+format, a...) 12 | } 13 | 14 | // fatalf calls log.Fatalf with the parameters given. 15 | func fatalf(format string, a ...interface{}) { 16 | log.Fatalf("skydns: "+format, a...) 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | env: 3 | - DEP_VERSION="0.4.1" 4 | go: 5 | - 1.9 6 | before_install: 7 | - curl -L -s https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -o $GOPATH/bin/dep 8 | - chmod +x $GOPATH/bin/dep 9 | install: 10 | - dep ensure 11 | before_script: 12 | - curl -L https://github.com/coreos/etcd/releases/download/v2.3.1/etcd-v2.3.1-linux-amd64.tar.gz -o etcd-v2.3.1-linux-amd64.tar.gz 13 | - tar xzvf etcd-v2.3.1-linux-amd64.tar.gz 14 | - ./etcd-v2.3.1-linux-amd64/etcd & 15 | - go get 16 | 17 | - go get github.com/rcrowley/go-metrics/stathat 18 | script: 19 | - go test -tags etcd -bench=. ./... 20 | -------------------------------------------------------------------------------- /cache/hit.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package cache 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/miekg/dns" 11 | ) 12 | 13 | // Hit returns a dns message from the cache. If the message's TTL is expired nil 14 | // is returned and the message is removed from the cache. 15 | func (c *Cache) Hit(question dns.Question, dnssec, tcp bool, msgid uint16) *dns.Msg { 16 | key := Key(question, dnssec, tcp) 17 | m1, exp, hit := c.Search(key) 18 | if hit { 19 | // Cache hit! \o/ 20 | if time.Since(exp) < 0 { 21 | m1.Id = msgid 22 | m1.Compress = true 23 | // Even if something ended up with the TC bit *in* the cache, set it to off 24 | m1.Truncated = false 25 | return m1 26 | } 27 | // Expired! /o\ 28 | c.Remove(key) 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /server/exchange.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import "github.com/miekg/dns" 8 | 9 | // exchangeMsg returns a new dns message based on name, type, bufsize and dnssec. 10 | func newExchangeMsg(name string, typ, bufsize uint16, dnssec bool) *dns.Msg { 11 | m := new(dns.Msg) 12 | m.SetQuestion(name, typ) 13 | m.SetEdns0(bufsize, dnssec) 14 | return m 15 | } 16 | 17 | // exchangeWithRetry sends message m to server, but retries on ServerFailure. 18 | func exchangeWithRetry(c *dns.Client, m *dns.Msg, server string) (*dns.Msg, error) { 19 | r, _, err := c.Exchange(m, server) 20 | if err == nil && r.Rcode == dns.RcodeServerFailure { 21 | // redo the query 22 | r, _, err = c.Exchange(m, server) 23 | } 24 | return r, err 25 | } 26 | 27 | func (s *server) randomNameserverID(id uint16) int { 28 | nsid := 0 29 | if s.config.NSRotate { 30 | // Use request Id for "random" nameserver selection. 31 | nsid = int(id) % len(s.config.Nameservers) 32 | } 33 | return nsid 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 The SkyDNS Authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | [[override]] 28 | name = "github.com/ugorji/go" 29 | revision = "8c0409fcbb70099c748d71f714529204975f6c3f" 30 | 31 | [[override]] 32 | name = "github.com/coreos/etcd" 33 | version = "=3.2.15" 34 | 35 | [[constraint]] 36 | name = "github.com/coreos/go-systemd" 37 | version = "16.0.0" 38 | 39 | [[constraint]] 40 | name = "github.com/miekg/dns" 41 | version = "1.0.4" 42 | 43 | [[constraint]] 44 | name = "github.com/prometheus/client_golang" 45 | version = "1.1.0" 46 | 47 | [[constraint]] 48 | branch = "master" 49 | name = "golang.org/x/net" 50 | 51 | [[override]] 52 | name = "github.com/prometheus/procfs" 53 | version = "v0.0.5" 54 | 55 | [prune] 56 | go-tests = true 57 | unused-packages = true 58 | -------------------------------------------------------------------------------- /server/msg.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import "github.com/miekg/dns" 8 | 9 | // Fit will make m fit the size. If a message is larger than size then entire 10 | // additional section is dropped. If it is still to large and the transport 11 | // is udp we return a truncated message. 12 | // If the transport is tcp we are going to drop RR from the answer section 13 | // until it fits. When this is case the returned bool is true. 14 | func Fit(m *dns.Msg, size int, tcp bool) (*dns.Msg, bool) { 15 | if m.Len() > size { 16 | // Check for OPT Records at the end and keep those. TODO(miek) 17 | m.Extra = nil 18 | } 19 | if m.Len() < size { 20 | return m, false 21 | } 22 | 23 | // With TCP setting TC does not mean anything. 24 | if !tcp { 25 | m.Truncated = true 26 | // fall through here, so we at least return a message that can 27 | // fit the udp buffer. 28 | } 29 | 30 | // Additional section is gone, binary search until we have length that fits. 31 | min, max := 0, len(m.Answer) 32 | original := make([]dns.RR, len(m.Answer)) 33 | copy(original, m.Answer) 34 | for { 35 | if min == max { 36 | break 37 | } 38 | 39 | mid := (min + max) / 2 40 | m.Answer = original[:mid] 41 | 42 | if m.Len() < size { 43 | min++ 44 | continue 45 | } 46 | max = mid 47 | 48 | } 49 | if max > 1 { 50 | max-- 51 | } 52 | m.Answer = m.Answer[:max] 53 | return m, true 54 | } 55 | -------------------------------------------------------------------------------- /server/backend.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import "github.com/skynetservices/skydns/msg" 8 | 9 | type Backend interface { 10 | HasSynced() bool 11 | Records(name string, exact bool) ([]msg.Service, error) 12 | ReverseRecord(name string) (*msg.Service, error) 13 | } 14 | 15 | // FirstBackend exposes the Backend interface over multiple Backends, returning 16 | // the first Backend that answers the provided record request. If no Backend answers 17 | // a record request, the last error seen will be returned. 18 | type FirstBackend []Backend 19 | 20 | // FirstBackend implements Backend 21 | var _ Backend = FirstBackend{} 22 | 23 | func (g FirstBackend) Records(name string, exact bool) (records []msg.Service, err error) { 24 | var lastError error 25 | for _, backend := range g { 26 | if records, err = backend.Records(name, exact); err == nil && len(records) > 0 { 27 | return records, nil 28 | } 29 | if err != nil { 30 | lastError = err 31 | } 32 | } 33 | return nil, lastError 34 | } 35 | 36 | func (g FirstBackend) ReverseRecord(name string) (record *msg.Service, err error) { 37 | var lastError error 38 | for _, backend := range g { 39 | if record, err = backend.ReverseRecord(name); err == nil && record != nil { 40 | return record, nil 41 | } 42 | if err != nil { 43 | lastError = err 44 | } 45 | } 46 | return nil, lastError 47 | } 48 | 49 | func (g FirstBackend) HasSynced() bool { 50 | // Stub implementation only to satisfy interface. 51 | return true 52 | } 53 | -------------------------------------------------------------------------------- /server/metrics_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "net/http" 11 | "strconv" 12 | "testing" 13 | 14 | "github.com/skynetservices/skydns/metrics" 15 | 16 | "github.com/miekg/dns" 17 | ) 18 | 19 | func query(n string, t uint16) { 20 | m := new(dns.Msg) 21 | m.SetQuestion(n, t) 22 | dns.Exchange(m, "127.0.0.1:"+StrPort) 23 | } 24 | 25 | func scrape(t *testing.T, key string) int { 26 | resp, err := http.Get("http://localhost:12300/metrics") 27 | if err != nil { 28 | t.Logf("could not get metrics") 29 | return -1 30 | } 31 | 32 | body, err := ioutil.ReadAll(resp.Body) 33 | if err != nil { 34 | return -1 35 | } 36 | 37 | // Find value for key. 38 | n := bytes.Index(body, []byte(key)) 39 | if n == -1 { 40 | return -1 41 | } 42 | 43 | i := n 44 | for i < len(body) { 45 | if body[i] == '\n' { 46 | break 47 | } 48 | if body[i] == ' ' { 49 | n = i + 1 50 | } 51 | i++ 52 | } 53 | value, err := strconv.Atoi(string(body[n:i])) 54 | if err != nil { 55 | t.Fatal("failed to get value") 56 | } 57 | return value 58 | } 59 | 60 | func TestMetrics(t *testing.T) { 61 | s := newTestServer(t, false) 62 | defer s.Stop() 63 | 64 | metrics.Port = "12300" 65 | metrics.Subsystem = "test" 66 | metrics.Namespace = "test" 67 | metrics.Metrics() 68 | 69 | query("miek.nl.", dns.TypeMX) 70 | v0 := scrape(t, "test_test_dns_request_count_total{system=\"recursive\"}") 71 | query("miek.nl.", dns.TypeMX) 72 | v1 := scrape(t, "test_test_dns_request_count_total{system=\"recursive\"}") 73 | 74 | if v1 != v0+1 { 75 | t.Fatalf("expecting %d, got %d", v0+1, v1) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /singleflight/singleflight.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package singleflight provides a duplicate function call suppression 18 | // mechanism. 19 | package singleflight 20 | 21 | import "sync" 22 | 23 | // call is an in-flight or completed Do call 24 | type call struct { 25 | wg sync.WaitGroup 26 | val interface{} 27 | err error 28 | } 29 | 30 | // Group represents a class of work and forms a namespace in which 31 | // units of work can be executed with duplicate suppression. 32 | type Group struct { 33 | mu sync.Mutex // protects m 34 | m map[string]*call // lazily initialized 35 | } 36 | 37 | // Do executes and returns the results of the given function, making 38 | // sure that only one execution is in-flight for a given key at a 39 | // time. If a duplicate comes in, the duplicate caller waits for the 40 | // original to complete and receives the same results. 41 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 42 | g.mu.Lock() 43 | if g.m == nil { 44 | g.m = make(map[string]*call) 45 | } 46 | if c, ok := g.m[key]; ok { 47 | g.mu.Unlock() 48 | c.wg.Wait() 49 | return c.val, c.err 50 | } 51 | c := new(call) 52 | c.wg.Add(1) 53 | g.m[key] = c 54 | g.mu.Unlock() 55 | 56 | c.val, c.err = fn() 57 | c.wg.Done() 58 | 59 | g.mu.Lock() 60 | delete(g.m, key) 61 | g.mu.Unlock() 62 | 63 | return c.val, c.err 64 | } 65 | -------------------------------------------------------------------------------- /server/cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/miekg/dns" 11 | "github.com/skynetservices/skydns/cache" 12 | ) 13 | 14 | func TestFit(t *testing.T) { 15 | m := new(dns.Msg) 16 | m.SetQuestion("miek.nl", dns.TypeA) 17 | 18 | rr, _ := dns.NewRR("www.miek.nl. IN SRV 10 10 8080 blaat.miek.nl.") 19 | for i := 0; i < 101; i++ { 20 | m.Answer = append(m.Answer, rr) 21 | } 22 | // Uncompresses length is now 4424. Try trimming this to 1927 23 | Fit(m, 1927, true) 24 | 25 | if m.Len() > 1927 { 26 | t.Fatalf("failed to fix message, expected < %d, got %d", 1927, m.Len()) 27 | } 28 | } 29 | 30 | func TestCacheTruncated(t *testing.T) { 31 | s := newTestServer(t, true) 32 | m := &dns.Msg{} 33 | m.SetQuestion("skydns.test.", dns.TypeSRV) 34 | m.Truncated = true 35 | s.rcache.InsertMessage(cache.Key(m.Question[0], false, false), m) 36 | 37 | // Now asking for this should result in a non-truncated answer. 38 | resp, _ := dns.Exchange(m, "127.0.0.1:"+StrPort) 39 | if resp.Truncated { 40 | t.Fatal("truncated bit should be false") 41 | } 42 | } 43 | 44 | // Store a large message in the cache, then query with a smaller bufsize and check 45 | // we get back a smaller message. 46 | // TODO(miek). 47 | /* 48 | func testCacheStoreLarge(t *testing.T) { 49 | s := newTestServer(t, true) 50 | defer s.Stop() 51 | 52 | c := new(dns.Client) 53 | m := new(dns.Msg) 54 | 55 | for i := 0; i < 2000; i++ { 56 | is := strconv.Itoa(i) 57 | m := &msg.Service{ 58 | Host: "2001::" + is, Key: "machine" + is + ".machines.skydns.test.", 59 | } 60 | addService(t, s, m.Key, 0, m) 61 | defer delService(t, s, m.Key) 62 | } 63 | m.SetQuestion("machines.skydns.test.", dns.TypeSRV) 64 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | t.Logf("%s", resp) 69 | 70 | if resp.Rcode != dns.RcodeSuccess { 71 | t.Fatalf("expecting server failure, got %d", resp.Rcode) 72 | } 73 | } 74 | */ 75 | -------------------------------------------------------------------------------- /cache/cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package cache 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | const testTTL = 2 15 | 16 | type testcase struct { 17 | m *dns.Msg 18 | dnssec, tcp bool 19 | } 20 | 21 | func newMsg(zone string, typ uint16) *dns.Msg { 22 | m := &dns.Msg{} 23 | m.SetQuestion(zone, typ) 24 | return m 25 | } 26 | 27 | func TestInsertMessage(t *testing.T) { 28 | c := New(10, testTTL) 29 | 30 | testcases := []testcase{ 31 | {newMsg("miek.nl.", dns.TypeMX), false, false}, 32 | {newMsg("miek2.nl.", dns.TypeNS), false, false}, 33 | {newMsg("miek3.nl.", dns.TypeMX), true, false}, 34 | } 35 | 36 | for _, tc := range testcases { 37 | c.InsertMessage(Key(tc.m.Question[0], tc.dnssec, tc.tcp), tc.m) 38 | 39 | m1 := c.Hit(tc.m.Question[0], tc.dnssec, tc.tcp, tc.m.Id) 40 | if m1.Question[0].Qtype != tc.m.Question[0].Qtype { 41 | t.Fatalf("bad Qtype, expected %d, got %d:", tc.m.Question[0].Qtype, m1.Question[0].Qtype) 42 | } 43 | if m1.Question[0].Name != tc.m.Question[0].Name { 44 | t.Fatalf("bad Qtype, expected %s, got %s:", tc.m.Question[0].Name, m1.Question[0].Name) 45 | } 46 | 47 | m1 = c.Hit(tc.m.Question[0], !tc.dnssec, tc.tcp, tc.m.Id) 48 | if m1 != nil { 49 | t.Fatalf("bad cache hit, expected , got %s:", m1) 50 | } 51 | m1 = c.Hit(tc.m.Question[0], !tc.dnssec, !tc.tcp, tc.m.Id) 52 | if m1 != nil { 53 | t.Fatalf("bad cache hit, expected , got %s:", m1) 54 | } 55 | m1 = c.Hit(tc.m.Question[0], tc.dnssec, !tc.tcp, tc.m.Id) 56 | if m1 != nil { 57 | t.Fatalf("bad cache hit, expected , got %s:", m1) 58 | } 59 | } 60 | } 61 | 62 | func TestExpireMessage(t *testing.T) { 63 | c := New(10, testTTL-1) 64 | 65 | tc := testcase{newMsg("miek.nl.", dns.TypeMX), false, false} 66 | c.InsertMessage(Key(tc.m.Question[0], tc.dnssec, tc.tcp), tc.m) 67 | 68 | m1 := c.Hit(tc.m.Question[0], tc.dnssec, tc.tcp, tc.m.Id) 69 | if m1.Question[0].Qtype != tc.m.Question[0].Qtype { 70 | t.Fatalf("bad Qtype, expected %d, got %d:", tc.m.Question[0].Qtype, m1.Question[0].Qtype) 71 | } 72 | if m1.Question[0].Name != tc.m.Question[0].Name { 73 | t.Fatalf("bad Qtype, expected %s, got %s:", tc.m.Question[0].Name, m1.Question[0].Name) 74 | } 75 | 76 | time.Sleep(testTTL) 77 | 78 | m1 = c.Hit(tc.m.Question[0], tc.dnssec, tc.tcp, tc.m.Id) 79 | if m1.Question[0].Qtype != tc.m.Question[0].Qtype { 80 | t.Fatalf("bad Qtype, expected %d, got %d:", tc.m.Question[0].Qtype, m1.Question[0].Qtype) 81 | } 82 | if m1.Question[0].Name != tc.m.Question[0].Name { 83 | t.Fatalf("bad Qtype, expected %s, got %s:", tc.m.Question[0].Name, m1.Question[0].Name) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /server/stub.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "net" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/miekg/dns" 13 | "github.com/skynetservices/skydns/msg" 14 | ) 15 | 16 | const ednsStubCode = dns.EDNS0LOCALSTART + 10 17 | 18 | // ednsStub is the EDNS0 record we add to stub queries. Queries which have this record are 19 | // not forwarded again. 20 | var ednsStub = func() *dns.OPT { 21 | o := new(dns.OPT) 22 | o.Hdr.Name = "." 23 | o.Hdr.Rrtype = dns.TypeOPT 24 | e := new(dns.EDNS0_LOCAL) 25 | e.Code = ednsStubCode 26 | e.Data = []byte{1} 27 | o.Option = append(o.Option, e) 28 | return o 29 | }() 30 | 31 | // Look in .../dns/stub//xx for msg.Services. Loop through them 32 | // extract and add them as forwarders (ip:port-combos) for 33 | // the stub zones. Only numeric (i.e. IP address) hosts are used. 34 | func (s *server) UpdateStubZones() { 35 | stubmap := make(map[string][]string) 36 | 37 | services, err := s.backend.Records("stub.dns."+s.config.Domain, false) 38 | if err != nil { 39 | logf("stub zone update failed: %s", err) 40 | return 41 | } 42 | for _, serv := range services { 43 | if serv.Port == 0 { 44 | serv.Port = 53 45 | } 46 | ip := net.ParseIP(serv.Host) 47 | if ip == nil { 48 | logf("stub zone non-address %s seen for: %s", serv.Key, serv.Host) 49 | continue 50 | } 51 | 52 | domain := msg.Domain(serv.Key) 53 | // Chop of left most label, because that is used as the nameserver place holder 54 | // and drop the right most labels that belong to localDomain. 55 | labels := dns.SplitDomainName(domain) 56 | domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(s.config.localDomain)], ".")) 57 | 58 | // If the remaining name equals s.config.LocalDomain we ignore it. 59 | if domain == s.config.localDomain { 60 | logf("not adding stub zone for my own domain") 61 | continue 62 | } 63 | stubmap[domain] = append(stubmap[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port))) 64 | } 65 | 66 | s.config.stub = &stubmap 67 | } 68 | 69 | // ServeDNSStubForward forwards a request to a nameservers and returns the response. 70 | func (s *server) ServeDNSStubForward(w dns.ResponseWriter, req *dns.Msg, ns []string) *dns.Msg { 71 | // Check EDNS0 Stub option, if set drop the packet. 72 | option := req.IsEdns0() 73 | if option != nil { 74 | for _, o := range option.Option { 75 | if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 && 76 | o.(*dns.EDNS0_LOCAL).Data[0] == 1 { 77 | // Maybe log source IP here? 78 | logf("not fowarding stub request to another stub") 79 | return nil 80 | } 81 | } 82 | } 83 | 84 | // Add a custom EDNS0 option to the packet, so we can detect loops 85 | // when 2 stubs are forwarding to each other. 86 | if option != nil { 87 | option.Option = append(option.Option, &dns.EDNS0_LOCAL{ednsStubCode, []byte{1}}) 88 | } else { 89 | req.Extra = append(req.Extra, ednsStub) 90 | } 91 | 92 | var ( 93 | r *dns.Msg 94 | err error 95 | ) 96 | 97 | // Use request Id for "random" nameserver selection. 98 | nsid := int(req.Id) % len(ns) 99 | try := 0 100 | Redo: 101 | if isTCP(w) { 102 | r, err = exchangeWithRetry(s.dnsTCPclient, req, ns[nsid]) 103 | } else { 104 | r, err = exchangeWithRetry(s.dnsUDPclient, req, ns[nsid]) 105 | } 106 | if err == nil { 107 | r.Compress = true 108 | r.Id = req.Id 109 | w.WriteMsg(r) 110 | return r 111 | } 112 | // Seen an error, this can only mean, "server not reached", try again 113 | // but only if we have not exausted our nameservers. 114 | if try < len(ns) { 115 | try++ 116 | nsid = (nsid + 1) % len(ns) 117 | goto Redo 118 | } 119 | 120 | logf("failure to forward stub request %q", err) 121 | m := s.ServerFailure(req) 122 | w.WriteMsg(m) 123 | return m 124 | } 125 | -------------------------------------------------------------------------------- /server/forwarding.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/miekg/dns" 11 | ) 12 | 13 | // ServeDNSForward forwards a request to a nameservers and returns the response. 14 | func (s *server) ServeDNSForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { 15 | if s.config.NoRec { 16 | m := s.ServerFailure(req) 17 | w.WriteMsg(m) 18 | return m 19 | } 20 | 21 | if len(s.config.Nameservers) == 0 || dns.CountLabel(req.Question[0].Name) < s.config.Ndots { 22 | if s.config.Verbose { 23 | if len(s.config.Nameservers) == 0 { 24 | logf("can not forward, no nameservers defined") 25 | } else { 26 | logf("can not forward, name too short (less than %d labels): `%s'", s.config.Ndots, req.Question[0].Name) 27 | } 28 | } 29 | m := s.ServerFailure(req) 30 | m.RecursionAvailable = true // this is still true 31 | w.WriteMsg(m) 32 | return m 33 | } 34 | 35 | var ( 36 | r *dns.Msg 37 | err error 38 | ) 39 | 40 | nsid := s.randomNameserverID(req.Id) 41 | try := 0 42 | Redo: 43 | if isTCP(w) { 44 | r, err = exchangeWithRetry(s.dnsTCPclient, req, s.config.Nameservers[nsid]) 45 | } else { 46 | r, err = exchangeWithRetry(s.dnsUDPclient, req, s.config.Nameservers[nsid]) 47 | } 48 | if err == nil { 49 | r.Compress = true 50 | r.Id = req.Id 51 | w.WriteMsg(r) 52 | return r 53 | } 54 | // Seen an error, this can only mean, "server not reached", try again 55 | // but only if we have not exausted our nameservers. 56 | if try < len(s.config.Nameservers) { 57 | try++ 58 | nsid = (nsid + 1) % len(s.config.Nameservers) 59 | goto Redo 60 | } 61 | 62 | logf("failure to forward request %q", err) 63 | m := s.ServerFailure(req) 64 | return m 65 | } 66 | 67 | // ServeDNSReverse is the handler for DNS requests for the reverse zone. If nothing is found 68 | // locally the request is forwarded to the forwarder for resolution. 69 | func (s *server) ServeDNSReverse(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { 70 | m := new(dns.Msg) 71 | m.SetReply(req) 72 | m.Compress = true 73 | m.Authoritative = false // Set to false, because I don't know what to do wrt DNSSEC. 74 | m.RecursionAvailable = true 75 | var err error 76 | if m.Answer, err = s.PTRRecords(req.Question[0]); err == nil { 77 | // TODO(miek): Reverse DNSSEC. We should sign this, but requires a key....and more 78 | // Probably not worth the hassle? 79 | if err := w.WriteMsg(m); err != nil { 80 | logf("failure to return reply %q", err) 81 | } 82 | return m 83 | } 84 | // Always forward if not found locally. 85 | return s.ServeDNSForward(w, req) 86 | } 87 | 88 | // Lookup looks up name,type using the recursive nameserver defines 89 | // in the server's config. If none defined it returns an error. 90 | func (s *server) Lookup(n string, t, bufsize uint16, dnssec bool) (*dns.Msg, error) { 91 | if len(s.config.Nameservers) == 0 { 92 | return nil, fmt.Errorf("no nameservers configured can not lookup name") 93 | } 94 | if dns.CountLabel(n) < s.config.Ndots { 95 | return nil, fmt.Errorf("name has fewer than %d labels", s.config.Ndots) 96 | } 97 | m := newExchangeMsg(n, t, bufsize, dnssec) 98 | 99 | nsid := s.randomNameserverID(m.Id) 100 | try := 0 101 | Redo: 102 | r, err := exchangeWithRetry(s.dnsUDPclient, m, s.config.Nameservers[nsid]) 103 | if err == nil { 104 | if r.Rcode != dns.RcodeSuccess { 105 | return nil, fmt.Errorf("rcode %d is not equal to success", r.Rcode) 106 | } 107 | // Reset TTLs to rcache TTL to make some of the other code 108 | // and the tests not care about TTLs 109 | for _, rr := range r.Answer { 110 | rr.Header().Ttl = uint32(s.config.RCacheTtl) 111 | } 112 | for _, rr := range r.Extra { 113 | rr.Header().Ttl = uint32(s.config.RCacheTtl) 114 | } 115 | return r, nil 116 | } 117 | // Seen an error, this can only mean, "server not reached", try again 118 | // but only if we have not exausted our nameservers. 119 | if try < len(s.config.Nameservers) { 120 | try++ 121 | nsid = (nsid + 1) % len(s.config.Nameservers) 122 | goto Redo 123 | } 124 | return nil, fmt.Errorf("failure to lookup name") 125 | } 126 | -------------------------------------------------------------------------------- /msg/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package msg 6 | 7 | import "testing" 8 | 9 | func TestPath(t *testing.T) { 10 | PathPrefix = "mydns" 11 | result := Path("service.staging.skydns.local.") 12 | 13 | if result != "/mydns/local/skydns/staging/service" { 14 | t.Logf("Failure to get domain's path with prefix: mydns") 15 | t.Fail() 16 | } 17 | 18 | PathPrefix = "skydns" 19 | result = Path("service.staging.skydns.local.") 20 | 21 | if result != "/skydns/local/skydns/staging/service" { 22 | t.Logf("Failure to get domain's path with default prefix: skydns") 23 | t.Fail() 24 | } 25 | } 26 | 27 | func TestSplit255(t *testing.T) { 28 | xs := split255("abc") 29 | if len(xs) != 1 && xs[0] != "abc" { 30 | t.Logf("Failure to split abc") 31 | t.Fail() 32 | } 33 | s := "" 34 | for i := 0; i < 255; i++ { 35 | s += "a" 36 | } 37 | xs = split255(s) 38 | if len(xs) != 1 && xs[0] != s { 39 | t.Logf("failure to split 255 char long string") 40 | t.Logf("%s %v\n", s, xs) 41 | t.Fail() 42 | } 43 | s += "b" 44 | xs = split255(s) 45 | if len(xs) != 2 || xs[1] != "b" { 46 | t.Logf("failure to split 256 char long string: %d", len(xs)) 47 | t.Logf("%s %v\n", s, xs) 48 | t.Fail() 49 | } 50 | for i := 0; i < 255; i++ { 51 | s += "a" 52 | } 53 | xs = split255(s) 54 | if len(xs) != 3 || xs[2] != "a" { 55 | t.Logf("failure to split 510 char long string: %d", len(xs)) 56 | t.Logf("%s %v\n", s, xs) 57 | t.Fail() 58 | } 59 | } 60 | 61 | func TestGroup(t *testing.T) { 62 | // Key are in the wrong order, but for this test it does not matter. 63 | 64 | sx := Group( 65 | []Service{ 66 | {Host: "127.0.0.1", Group: "g1", Key: "b/sub/dom1/skydns/test"}, 67 | {Host: "127.0.0.2", Group: "g2", Key: "a/dom1/skydns/test"}, 68 | }, 69 | ) 70 | // Expecting to return the shortest key with a Group attribute. 71 | if len(sx) != 1 { 72 | t.Fatalf("failure to group zeroth set: %v", sx) 73 | } 74 | if sx[0].Key != "a/dom1/skydns/test" { 75 | t.Fatalf("failure to group zeroth set: %v, wrong Key", sx) 76 | } 77 | 78 | // Groups disagree, so we will not do anything. 79 | sx = Group( 80 | []Service{ 81 | {Host: "server1", Group: "g1", Key: "region1/skydns/test"}, 82 | {Host: "server2", Group: "g2", Key: "region1/skydns/test"}, 83 | }, 84 | ) 85 | if len(sx) != 2 { 86 | t.Fatalf("failure to group first set: %v", sx) 87 | } 88 | 89 | // Group is g1, include only the top-level one. 90 | sx = Group( 91 | []Service{ 92 | {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, 93 | {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, 94 | }, 95 | ) 96 | if len(sx) != 1 { 97 | t.Fatalf("failure to group second set: %v", sx) 98 | } 99 | 100 | // Groupless services must be included. 101 | sx = Group( 102 | []Service{ 103 | {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, 104 | {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, 105 | {Host: "server2", Group: "", Key: "b/subdom/dom/region1/skydns/test"}, 106 | }, 107 | ) 108 | if len(sx) != 2 { 109 | t.Fatalf("failure to group third set: %v", sx) 110 | } 111 | 112 | // Empty group on the highest level: include that one also. 113 | sx = Group( 114 | []Service{ 115 | {Host: "server1", Group: "g1", Key: "a/dom/region1/skydns/test"}, 116 | {Host: "server1", Group: "", Key: "b/dom/region1/skydns/test"}, 117 | {Host: "server2", Group: "g2", Key: "a/subdom/dom/region1/skydns/test"}, 118 | }, 119 | ) 120 | if len(sx) != 2 { 121 | t.Fatalf("failure to group fourth set: %v", sx) 122 | } 123 | 124 | // Empty group on the highest level: include that one also, and the rest. 125 | sx = Group( 126 | []Service{ 127 | {Host: "server1", Group: "g5", Key: "a/dom/region1/skydns/test"}, 128 | {Host: "server1", Group: "", Key: "b/dom/region1/skydns/test"}, 129 | {Host: "server2", Group: "g5", Key: "a/subdom/dom/region1/skydns/test"}, 130 | }, 131 | ) 132 | if len(sx) != 3 { 133 | t.Fatalf("failure to group fith set: %v", sx) 134 | } 135 | 136 | // One group. 137 | sx = Group( 138 | []Service{ 139 | {Host: "server1", Group: "g6", Key: "a/dom/region1/skydns/test"}, 140 | }, 141 | ) 142 | if len(sx) != 1 { 143 | t.Fatalf("failure to group sixth set: %v", sx) 144 | } 145 | 146 | // No group, once service 147 | sx = Group( 148 | []Service{ 149 | {Host: "server1", Key: "a/dom/region1/skydns/test"}, 150 | }, 151 | ) 152 | if len(sx) != 1 { 153 | t.Fatalf("failure to group seventh set: %v", sx) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /backends/etcd3/etcd3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | // Package etcd provides the default SkyDNS server Backend implementation, 6 | // which looks up records stored under the `/skydns` key in etcd when queried. 7 | // This one particularly concerns with the support of etcd version 3. 8 | package etcd3 9 | 10 | import ( 11 | "context" 12 | "encoding/json" 13 | "fmt" 14 | "strings" 15 | 16 | etcdv3 "github.com/coreos/etcd/clientv3" 17 | "github.com/coreos/etcd/mvcc/mvccpb" 18 | "github.com/skynetservices/skydns/msg" 19 | "github.com/skynetservices/skydns/singleflight" 20 | ) 21 | 22 | type Config struct { 23 | Ttl uint32 24 | Priority uint16 25 | } 26 | 27 | type Backendv3 struct { 28 | client etcdv3.Client 29 | ctx context.Context 30 | config *Config 31 | inflight *singleflight.Group 32 | } 33 | 34 | // NewBackendv3 returns a new Backend for SkyDNS, backed by etcd v3 35 | func NewBackendv3(client etcdv3.Client, ctx context.Context, config *Config) *Backendv3 { 36 | return &Backendv3{ 37 | client: client, 38 | ctx: ctx, 39 | config: config, 40 | inflight: &singleflight.Group{}, 41 | } 42 | } 43 | 44 | func (g *Backendv3) HasSynced() bool { 45 | return true 46 | } 47 | 48 | func (g *Backendv3) Records(name string, exact bool) ([]msg.Service, error) { 49 | path, star := msg.PathWithWildcard(name) 50 | r, err := g.get(path, true) 51 | if err != nil { 52 | return nil, err 53 | } 54 | segments := strings.Split(msg.Path(name), "/") 55 | 56 | return g.loopNodes(r.Kvs, segments, star, nil) 57 | } 58 | 59 | func (g *Backendv3) ReverseRecord(name string) (*msg.Service, error) { 60 | path, star := msg.PathWithWildcard(name) 61 | if star { 62 | return nil, fmt.Errorf("reverse can not contain wildcards") 63 | } 64 | 65 | r, err := g.get(path, true) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | segments := strings.Split(msg.Path(name), "/") 71 | records, err := g.loopNodes(r.Kvs, segments, false, nil) 72 | if err != nil { 73 | return nil, err 74 | } 75 | if len(records) != 1 { 76 | return nil, fmt.Errorf("must be only one service record") 77 | } 78 | return &records[0], nil 79 | } 80 | 81 | func (g *Backendv3) get(path string, recursive bool) (*etcdv3.GetResponse, error) { 82 | resp, err := g.inflight.Do(path, func() (interface{}, error) { 83 | if recursive == true { 84 | r, e := g.client.Get(g.ctx, path, etcdv3.WithPrefix()) 85 | if e != nil { 86 | return nil, e 87 | } 88 | return r, e 89 | } else { 90 | r, e := g.client.Get(g.ctx, path) 91 | if e != nil { 92 | return nil, e 93 | } 94 | return r, e 95 | } 96 | }) 97 | 98 | if err != nil { 99 | return nil, err 100 | } 101 | return resp.(*etcdv3.GetResponse), err 102 | } 103 | 104 | type bareService struct { 105 | Host string 106 | Port int 107 | Priority int 108 | Weight int 109 | Text string 110 | } 111 | 112 | func (g *Backendv3) loopNodes(kv []*mvccpb.KeyValue, nameParts []string, star bool, bx map[bareService]bool) (sx []msg.Service, err error) { 113 | if bx == nil { 114 | bx = make(map[bareService]bool) 115 | } 116 | Nodes: 117 | for _, item := range kv { 118 | 119 | if star { 120 | s := string(item.Key[:]) 121 | keyParts := strings.Split(s, "/") 122 | for i, n := range nameParts { 123 | if i > len(keyParts)-1 { 124 | continue Nodes 125 | } 126 | if n == "*" || n == "any" { 127 | continue 128 | } 129 | if keyParts[i] != n { 130 | continue Nodes 131 | } 132 | } 133 | } 134 | 135 | serv := new(msg.Service) 136 | if err := json.Unmarshal(item.Value, serv); err != nil { 137 | return nil, err 138 | } 139 | 140 | b := bareService{serv.Host, 141 | serv.Port, 142 | serv.Priority, 143 | serv.Weight, 144 | serv.Text} 145 | 146 | bx[b] = true 147 | serv.Key = string(item.Key) 148 | //TODO: another call (LeaseRequest) for TTL when RPC in etcdv3 is ready 149 | serv.Ttl = g.calculateTtl(item, serv) 150 | 151 | if serv.Priority == 0 { 152 | serv.Priority = int(g.config.Priority) 153 | } 154 | 155 | sx = append(sx, *serv) 156 | } 157 | return sx, nil 158 | } 159 | 160 | func (g *Backendv3) calculateTtl(kv *mvccpb.KeyValue, serv *msg.Service) uint32 { 161 | etcdTtl := uint32(kv.Lease) //TODO: default value for now, should be an rpc call for least request when it becomes available in etcdv3's api 162 | 163 | if etcdTtl == 0 && serv.Ttl == 0 { 164 | return g.config.Ttl 165 | } 166 | if etcdTtl == 0 { 167 | return serv.Ttl 168 | } 169 | if serv.Ttl == 0 { 170 | return etcdTtl 171 | } 172 | if etcdTtl < serv.Ttl { 173 | return etcdTtl 174 | } 175 | return serv.Ttl 176 | } 177 | 178 | func (g *Backendv3) Client() etcdv3.Client { 179 | return g.client 180 | } 181 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package cache 6 | 7 | // Cache that holds RRs and for DNSSEC an RRSIG. 8 | 9 | // TODO(miek): there is a lot of copying going on to copy myself out of data 10 | // races. This should be optimized. 11 | 12 | import ( 13 | "crypto/sha1" 14 | "sync" 15 | "time" 16 | 17 | "github.com/miekg/dns" 18 | ) 19 | 20 | // Elem hold an answer and additional section that returned from the cache. 21 | // The signature is put in answer, extra is empty there. This wastes some memory. 22 | type elem struct { 23 | expiration time.Time // time added + TTL, after this the elem is invalid 24 | msg *dns.Msg 25 | } 26 | 27 | // Cache is a cache that holds on the a number of RRs or DNS messages. The cache 28 | // eviction is randomized. 29 | type Cache struct { 30 | sync.RWMutex 31 | 32 | capacity int 33 | m map[string]*elem 34 | ttl time.Duration 35 | } 36 | 37 | // New returns a new cache with the capacity and the ttl specified. 38 | func New(capacity, ttl int) *Cache { 39 | c := new(Cache) 40 | c.m = make(map[string]*elem) 41 | c.capacity = capacity 42 | c.ttl = time.Duration(ttl) * time.Second 43 | return c 44 | } 45 | 46 | func (c *Cache) Capacity() int { return c.capacity } 47 | 48 | func (c *Cache) Remove(s string) { 49 | c.Lock() 50 | delete(c.m, s) 51 | c.Unlock() 52 | } 53 | 54 | // EvictRandom removes a random member a the cache. 55 | // Must be called under a write lock. 56 | func (c *Cache) EvictRandom() { 57 | clen := len(c.m) 58 | if clen <= c.capacity { 59 | return 60 | } 61 | i := clen - c.capacity 62 | for k, _ := range c.m { 63 | delete(c.m, k) 64 | i-- 65 | if i == 0 { 66 | break 67 | } 68 | } 69 | } 70 | 71 | // InsertMessage inserts a message in the Cache. We will cache it for ttl seconds, which 72 | // should be a small (60...300) integer. 73 | func (c *Cache) InsertMessage(s string, msg *dns.Msg) { 74 | if c.capacity <= 0 { 75 | return 76 | } 77 | 78 | c.Lock() 79 | if _, ok := c.m[s]; !ok { 80 | c.m[s] = &elem{time.Now().UTC().Add(c.ttl), msg.Copy()} 81 | 82 | } 83 | c.EvictRandom() 84 | c.Unlock() 85 | } 86 | 87 | // InsertSignature inserts a signature, the expiration time is used as the cache ttl. 88 | func (c *Cache) InsertSignature(s string, sig *dns.RRSIG) { 89 | if c.capacity <= 0 { 90 | return 91 | } 92 | c.Lock() 93 | 94 | if _, ok := c.m[s]; !ok { 95 | m := ((int64(sig.Expiration) - time.Now().Unix()) / (1 << 31)) - 1 96 | if m < 0 { 97 | m = 0 98 | } 99 | t := time.Unix(int64(sig.Expiration)-(m*(1<<31)), 0).UTC() 100 | c.m[s] = &elem{t, &dns.Msg{Answer: []dns.RR{dns.Copy(sig)}}} 101 | } 102 | c.EvictRandom() 103 | c.Unlock() 104 | } 105 | 106 | // Search returns a dns.Msg, the expiration time and a boolean indicating if we found something 107 | // in the cache. 108 | func (c *Cache) Search(s string) (*dns.Msg, time.Time, bool) { 109 | if c.capacity <= 0 { 110 | return nil, time.Time{}, false 111 | } 112 | c.RLock() 113 | if e, ok := c.m[s]; ok { 114 | e1 := e.msg.Copy() 115 | c.RUnlock() 116 | return e1, e.expiration, true 117 | } 118 | c.RUnlock() 119 | return nil, time.Time{}, false 120 | } 121 | 122 | // Key creates a hash key from a question section. It creates a different key 123 | // for requests with DNSSEC. 124 | func Key(q dns.Question, dnssec, tcp bool) string { 125 | h := sha1.New() 126 | i := append([]byte(q.Name), packUint16(q.Qtype)...) 127 | if dnssec { 128 | i = append(i, byte(255)) 129 | } 130 | if tcp { 131 | i = append(i, byte(254)) 132 | } 133 | return string(h.Sum(i)) 134 | } 135 | 136 | // Key uses the name, type and rdata, which is serialized and then hashed as the key for the lookup. 137 | func KeyRRset(rrs []dns.RR) string { 138 | h := sha1.New() 139 | i := []byte(rrs[0].Header().Name) 140 | i = append(i, packUint16(rrs[0].Header().Rrtype)...) 141 | for _, r := range rrs { 142 | switch t := r.(type) { // we only do a few type, serialize these manually 143 | case *dns.SOA: 144 | // We only fiddle with the serial so store that. 145 | i = append(i, packUint32(t.Serial)...) 146 | case *dns.SRV: 147 | i = append(i, packUint16(t.Priority)...) 148 | i = append(i, packUint16(t.Weight)...) 149 | i = append(i, packUint16(t.Weight)...) 150 | i = append(i, []byte(t.Target)...) 151 | case *dns.A: 152 | i = append(i, []byte(t.A)...) 153 | case *dns.AAAA: 154 | i = append(i, []byte(t.AAAA)...) 155 | case *dns.NSEC3: 156 | i = append(i, []byte(t.NextDomain)...) 157 | // Bitmap does not differentiate in SkyDNS. 158 | case *dns.DNSKEY: 159 | case *dns.NS: 160 | case *dns.TXT: 161 | } 162 | } 163 | return string(h.Sum(i)) 164 | } 165 | 166 | func packUint16(i uint16) []byte { return []byte{byte(i >> 8), byte(i)} } 167 | func packUint32(i uint32) []byte { return []byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)} } 168 | -------------------------------------------------------------------------------- /server/nsec3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "crypto/sha1" 9 | "encoding/base32" 10 | "strings" 11 | 12 | "github.com/miekg/dns" 13 | ) 14 | 15 | // Do DNSSEC NXDOMAIN with NSEC3 whitelies: rfc 7129, appendix B. 16 | // The closest encloser will be qname - the left most label and the 17 | // next closer will be the full qname which we then will deny. 18 | // Idem for source of synthesis. 19 | 20 | func (s *server) Denial(m *dns.Msg) { 21 | if m.Rcode == dns.RcodeNameError { 22 | // ce is qname minus the left label 23 | idx := dns.Split(m.Question[0].Name) 24 | ce := m.Question[0].Name[idx[1]:] 25 | 26 | nsec3ce, nsec3wildcard := newNSEC3CEandWildcard(s.config.Domain, ce, s.config.MinTtl) 27 | // Add ce and wildcard 28 | m.Ns = append(m.Ns, nsec3ce) 29 | m.Ns = append(m.Ns, nsec3wildcard) 30 | // Deny Qname nsec3 31 | m.Ns = append(m.Ns, s.newNSEC3NameError(m.Question[0].Name)) 32 | } 33 | if m.Rcode == dns.RcodeSuccess && len(m.Ns) == 1 { 34 | // NODATA 35 | if _, ok := m.Ns[0].(*dns.SOA); ok { 36 | m.Ns = append(m.Ns, s.newNSEC3NoData(m.Question[0].Name)) 37 | } 38 | } 39 | } 40 | 41 | func packBase32(s string) []byte { 42 | b32len := base32.HexEncoding.DecodedLen(len(s)) 43 | buf := make([]byte, b32len) 44 | n, _ := base32.HexEncoding.Decode(buf, []byte(s)) 45 | buf = buf[:n] 46 | return buf 47 | } 48 | 49 | func unpackBase32(b []byte) string { 50 | b32 := make([]byte, base32.HexEncoding.EncodedLen(len(b))) 51 | base32.HexEncoding.Encode(b32, b) 52 | return string(b32) 53 | } 54 | 55 | // newNSEC3NameError returns the NSEC3 record needed to denial qname. 56 | func (s *server) newNSEC3NameError(qname string) *dns.NSEC3 { 57 | n := new(dns.NSEC3) 58 | n.Hdr.Class = dns.ClassINET 59 | n.Hdr.Rrtype = dns.TypeNSEC3 60 | n.Hdr.Ttl = s.config.MinTtl 61 | n.Hash = dns.SHA1 62 | n.HashLength = sha1.Size 63 | n.Flags = 0 64 | n.Salt = "" 65 | n.TypeBitMap = []uint16{} 66 | 67 | covername := dns.HashName(qname, dns.SHA1, 0, "") 68 | 69 | buf := packBase32(covername) 70 | byteArith(buf, false) // one before 71 | n.Hdr.Name = appendDomain(strings.ToLower(unpackBase32(buf)), s.config.Domain) 72 | byteArith(buf, true) // one next 73 | byteArith(buf, true) // and another one 74 | n.NextDomain = unpackBase32(buf) 75 | return n 76 | } 77 | 78 | // newNSEC3NoData returns the NSEC3 record needed to denial the types 79 | func (s *server) newNSEC3NoData(qname string) *dns.NSEC3 { 80 | n := new(dns.NSEC3) 81 | n.Hdr.Class = dns.ClassINET 82 | n.Hdr.Rrtype = dns.TypeNSEC3 83 | n.Hdr.Ttl = s.config.MinTtl 84 | n.Hash = dns.SHA1 85 | n.HashLength = sha1.Size 86 | n.Flags = 0 87 | n.Salt = "" 88 | n.TypeBitMap = []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeSRV, dns.TypeRRSIG} 89 | 90 | n.Hdr.Name = dns.HashName(qname, dns.SHA1, 0, "") 91 | buf := packBase32(n.Hdr.Name) 92 | byteArith(buf, true) // one next 93 | n.NextDomain = unpackBase32(buf) 94 | 95 | n.Hdr.Name += appendDomain("", s.config.Domain) 96 | return n 97 | } 98 | 99 | // newNSEC3CEandWildcard returns the NSEC3 for the closest encloser 100 | // and the NSEC3 that denies that wildcard at that level. 101 | func newNSEC3CEandWildcard(apex, ce string, ttl uint32) (*dns.NSEC3, *dns.NSEC3) { 102 | n1 := new(dns.NSEC3) 103 | n1.Hdr.Class = dns.ClassINET 104 | n1.Hdr.Rrtype = dns.TypeNSEC3 105 | n1.Hdr.Ttl = ttl 106 | n1.Hash = dns.SHA1 107 | n1.HashLength = sha1.Size 108 | n1.Flags = 0 109 | n1.Iterations = 0 110 | n1.Salt = "" 111 | // for the apex we need another bitmap 112 | n1.TypeBitMap = []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeSRV, dns.TypeRRSIG} 113 | prev := dns.HashName(ce, dns.SHA1, n1.Iterations, n1.Salt) 114 | n1.Hdr.Name = strings.ToLower(prev) + "." + apex 115 | buf := packBase32(prev) 116 | byteArith(buf, true) // one next 117 | n1.NextDomain = unpackBase32(buf) 118 | 119 | n2 := new(dns.NSEC3) 120 | n2.Hdr.Class = dns.ClassINET 121 | n2.Hdr.Rrtype = dns.TypeNSEC3 122 | n2.Hdr.Ttl = ttl 123 | n2.Hash = dns.SHA1 124 | n2.HashLength = sha1.Size 125 | n2.Flags = 0 126 | n2.Iterations = 0 127 | n2.Salt = "" 128 | 129 | prev = dns.HashName("*."+ce, dns.SHA1, n2.Iterations, n2.Salt) 130 | buf = packBase32(prev) 131 | byteArith(buf, false) // one before 132 | n2.Hdr.Name = appendDomain(strings.ToLower(unpackBase32(buf)), apex) 133 | byteArith(buf, true) // one next 134 | byteArith(buf, true) // and another one 135 | n2.NextDomain = unpackBase32(buf) 136 | 137 | return n1, n2 138 | } 139 | 140 | // byteArith adds either 1 or -1 to b, there is no check for under- or overflow. 141 | func byteArith(b []byte, x bool) { 142 | if x { 143 | for i := len(b) - 1; i >= 0; i-- { 144 | if b[i] == 255 { 145 | b[i] = 0 146 | continue 147 | } 148 | b[i]++ 149 | return 150 | } 151 | } 152 | for i := len(b) - 1; i >= 0; i-- { 153 | if b[i] == 0 { 154 | b[i] = 255 155 | continue 156 | } 157 | b[i]-- 158 | return 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /server/dnssec.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Erik St. Martin, Brian Ketelsen. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "crypto" 9 | "crypto/ecdsa" 10 | "crypto/rsa" 11 | "os" 12 | "time" 13 | 14 | "github.com/skynetservices/skydns/cache" 15 | "github.com/skynetservices/skydns/metrics" 16 | "github.com/skynetservices/skydns/singleflight" 17 | 18 | "github.com/miekg/dns" 19 | ) 20 | var ( 21 | inflight = &singleflight.Group{} 22 | ) 23 | 24 | 25 | // ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other 26 | // utilities. It add ".key" for the public key and ".private" for the private key. 27 | func ParseKeyFile(file string) (*dns.DNSKEY, crypto.Signer, error) { 28 | f, e := os.Open(file + ".key") 29 | if e != nil { 30 | return nil, nil, e 31 | } 32 | k, e := dns.ReadRR(f, file+".key") 33 | if e != nil { 34 | return nil, nil, e 35 | } 36 | f, e = os.Open(file + ".private") 37 | if e != nil { 38 | return nil, nil, e 39 | } 40 | p, e := k.(*dns.DNSKEY).ReadPrivateKey(f, file+".private") 41 | if e != nil { 42 | return nil, nil, e 43 | } 44 | 45 | if v, ok := p.(*rsa.PrivateKey); ok { 46 | return k.(*dns.DNSKEY), v, nil 47 | } 48 | if v, ok := p.(*ecdsa.PrivateKey); ok { 49 | return k.(*dns.DNSKEY), v, nil 50 | } 51 | return k.(*dns.DNSKEY), nil, nil 52 | } 53 | 54 | // Sign signs a message m, it takes care of negative or nodata responses as 55 | // well by synthesising NSEC3 records. It will also cache the signatures, using 56 | // a hash of the signed data as a key. 57 | // We also fake the origin TTL in the signature, because we don't want to 58 | // throw away signatures when services decide to have longer TTL. So we just 59 | // set the origTTL to 60. 60 | // TODO(miek): revisit origTTL 61 | func (s *server) Sign(m *dns.Msg, bufsize uint16) { 62 | now := time.Now().UTC() 63 | incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such 64 | expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week 65 | 66 | for _, r := range rrSets(m.Answer) { 67 | if r[0].Header().Rrtype == dns.TypeRRSIG { 68 | continue 69 | } 70 | if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { 71 | continue 72 | } 73 | if sig, err := s.signSet(r, now, incep, expir); err == nil { 74 | m.Answer = append(m.Answer, sig) 75 | } 76 | } 77 | for _, r := range rrSets(m.Ns) { 78 | if r[0].Header().Rrtype == dns.TypeRRSIG { 79 | continue 80 | } 81 | if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { 82 | continue 83 | } 84 | if sig, err := s.signSet(r, now, incep, expir); err == nil { 85 | m.Ns = append(m.Ns, sig) 86 | } 87 | } 88 | for _, r := range rrSets(m.Extra) { 89 | if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { 90 | continue 91 | } 92 | if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { 93 | continue 94 | } 95 | if sig, err := s.signSet(r, now, incep, expir); err == nil { 96 | m.Extra = append(m.Extra, sig) 97 | } 98 | } 99 | 100 | o := new(dns.OPT) 101 | o.Hdr.Name = "." 102 | o.Hdr.Rrtype = dns.TypeOPT 103 | o.SetDo() 104 | o.SetUDPSize(4096) // TODO(miek): echo client 105 | m.Extra = append(m.Extra, o) 106 | return 107 | } 108 | 109 | func (s *server) signSet(r []dns.RR, now time.Time, incep, expir uint32) (*dns.RRSIG, error) { 110 | key := cache.KeyRRset(r) 111 | if m, exp, hit := s.scache.Search(key); hit { // There can only be one sig in this cache. 112 | // Is it still valid 24 hours from now? 113 | if now.Add(+24*time.Hour).Sub(exp) < -24*time.Hour { 114 | return m.Answer[0].(*dns.RRSIG), nil 115 | } 116 | s.scache.Remove(key) 117 | } 118 | if s.config.Verbose { 119 | logf("scache miss for %s type %d", r[0].Header().Name, r[0].Header().Rrtype) 120 | } 121 | 122 | metrics.ReportCacheMiss("signature") 123 | 124 | sig, err := inflight.Do(key, func() (interface{}, error) { 125 | sig1 := s.NewRRSIG(incep, expir) 126 | sig1.Header().Ttl = r[0].Header().Ttl 127 | if r[0].Header().Rrtype == dns.TypeTXT { 128 | sig1.OrigTtl = 0 129 | } 130 | e := sig1.Sign(s.config.PrivKey, r) 131 | if e != nil { 132 | logf("failed to sign: %s", e.Error()) 133 | } 134 | return sig1, e 135 | }) 136 | if err != nil { 137 | return nil, err 138 | } 139 | s.scache.InsertSignature(key, sig.(*dns.RRSIG)) 140 | return dns.Copy(sig.(*dns.RRSIG)).(*dns.RRSIG), nil 141 | } 142 | 143 | func (s *server) NewRRSIG(incep, expir uint32) *dns.RRSIG { 144 | sig := new(dns.RRSIG) 145 | sig.Hdr.Rrtype = dns.TypeRRSIG 146 | sig.Hdr.Ttl = s.config.Ttl 147 | sig.OrigTtl = s.config.Ttl 148 | sig.Algorithm = s.config.PubKey.Algorithm 149 | sig.KeyTag = s.config.KeyTag 150 | sig.Inception = incep 151 | sig.Expiration = expir 152 | sig.SignerName = s.config.PubKey.Hdr.Name 153 | return sig 154 | } 155 | 156 | type rrset struct { 157 | qname string 158 | qtype uint16 159 | } 160 | 161 | func rrSets(rrs []dns.RR) map[rrset][]dns.RR { 162 | m := make(map[rrset][]dns.RR) 163 | for _, r := range rrs { 164 | if s, ok := m[rrset{r.Header().Name, r.Header().Rrtype}]; ok { 165 | s = append(s, r) 166 | m[rrset{r.Header().Name, r.Header().Rrtype}] = s 167 | } else { 168 | s := make([]dns.RR, 1, 3) 169 | s[0] = r 170 | m[rrset{r.Header().Name, r.Header().Rrtype}] = s 171 | } 172 | } 173 | if len(m) > 0 { 174 | return m 175 | } 176 | return nil 177 | } 178 | -------------------------------------------------------------------------------- /backends/etcd/etcd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | // Package etcd provides the default SkyDNS server Backend implementation, 6 | // which looks up records stored under the `/skydns` key in etcd when queried. 7 | package etcd 8 | 9 | import ( 10 | "context" 11 | "encoding/json" 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/skynetservices/skydns/msg" 16 | "github.com/skynetservices/skydns/singleflight" 17 | 18 | etcd "github.com/coreos/etcd/client" 19 | ) 20 | 21 | // Config represents configuration for the Etcd backend - these values 22 | // should be taken directly from server.Config 23 | type Config struct { 24 | Ttl uint32 25 | Priority uint16 26 | } 27 | 28 | type Backend struct { 29 | client etcd.KeysAPI 30 | ctx context.Context 31 | config *Config 32 | inflight *singleflight.Group 33 | } 34 | 35 | // NewBackend returns a new Backend for SkyDNS, backed by etcd. 36 | func NewBackend(client etcd.KeysAPI, ctx context.Context, config *Config) *Backend { 37 | return &Backend{ 38 | client: client, 39 | ctx: ctx, 40 | config: config, 41 | inflight: &singleflight.Group{}, 42 | } 43 | } 44 | 45 | func (g *Backend) HasSynced() bool { 46 | return true 47 | } 48 | 49 | func (g *Backend) Records(name string, exact bool) ([]msg.Service, error) { 50 | path, star := msg.PathWithWildcard(name) 51 | r, err := g.get(path, true) 52 | if err != nil { 53 | return nil, err 54 | } 55 | segments := strings.Split(msg.Path(name), "/") 56 | switch { 57 | case exact && r.Node.Dir: 58 | return nil, nil 59 | case r.Node.Dir: 60 | return g.loopNodes(r.Node.Nodes, segments, star, nil) 61 | default: 62 | return g.loopNodes([]*etcd.Node{r.Node}, segments, false, nil) 63 | } 64 | } 65 | 66 | func (g *Backend) ReverseRecord(name string) (*msg.Service, error) { 67 | path, star := msg.PathWithWildcard(name) 68 | if star { 69 | return nil, fmt.Errorf("reverse can not contain wildcards") 70 | } 71 | r, err := g.get(path, true) 72 | if err != nil { 73 | return nil, err 74 | } 75 | if r.Node.Dir { 76 | return nil, fmt.Errorf("reverse must not be a directory") 77 | } 78 | segments := strings.Split(msg.Path(name), "/") 79 | records, err := g.loopNodes([]*etcd.Node{r.Node}, segments, false, nil) 80 | if err != nil { 81 | return nil, err 82 | } 83 | if len(records) != 1 { 84 | return nil, fmt.Errorf("must be only one service record") 85 | } 86 | return &records[0], nil 87 | } 88 | 89 | // get is a wrapper for client.Get that uses SingleInflight to suppress multiple 90 | // outstanding queries. 91 | func (g *Backend) get(path string, recursive bool) (*etcd.Response, error) { 92 | resp, err := g.inflight.Do(path, func() (interface{}, error) { 93 | r, e := g.client.Get(g.ctx, path, &etcd.GetOptions{Sort: false, Recursive: recursive}) 94 | if e != nil { 95 | return nil, e 96 | } 97 | return r, e 98 | }) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return resp.(*etcd.Response), err 103 | } 104 | 105 | type bareService struct { 106 | Host string 107 | Port int 108 | Priority int 109 | Weight int 110 | Text string 111 | } 112 | 113 | // skydns/local/skydns/east/staging/web 114 | // skydns/local/skydns/west/production/web 115 | // 116 | // skydns/local/skydns/*/*/web 117 | // skydns/local/skydns/*/web 118 | 119 | // loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname 120 | // will be match against any wildcards when star is true. 121 | func (g *Backend) loopNodes(ns []*etcd.Node, nameParts []string, star bool, bx map[bareService]bool) (sx []msg.Service, err error) { 122 | if bx == nil { 123 | bx = make(map[bareService]bool) 124 | } 125 | Nodes: 126 | for _, n := range ns { 127 | if n.Dir { 128 | nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx) 129 | if err != nil { 130 | return nil, err 131 | } 132 | sx = append(sx, nodes...) 133 | continue 134 | } 135 | if star { 136 | keyParts := strings.Split(n.Key, "/") 137 | for i, n := range nameParts { 138 | if i > len(keyParts)-1 { 139 | // name is longer than key 140 | continue Nodes 141 | } 142 | if n == "*" || n == "any" { 143 | continue 144 | } 145 | if keyParts[i] != n { 146 | continue Nodes 147 | } 148 | } 149 | } 150 | serv := new(msg.Service) 151 | if err := json.Unmarshal([]byte(n.Value), serv); err != nil { 152 | return nil, err 153 | } 154 | b := bareService{serv.Host, serv.Port, serv.Priority, serv.Weight, serv.Text} 155 | if _, ok := bx[b]; ok { 156 | continue 157 | } 158 | bx[b] = true 159 | 160 | serv.Key = n.Key 161 | serv.Ttl = g.calculateTtl(n, serv) 162 | if serv.Priority == 0 { 163 | serv.Priority = int(g.config.Priority) 164 | } 165 | sx = append(sx, *serv) 166 | } 167 | return sx, nil 168 | } 169 | 170 | // calculateTtl returns the smaller of the etcd TTL and the service's 171 | // TTL. If neither of these are set (have a zero value), the server 172 | // default is used. 173 | func (g *Backend) calculateTtl(node *etcd.Node, serv *msg.Service) uint32 { 174 | etcdTtl := uint32(node.TTL) 175 | 176 | if etcdTtl == 0 && serv.Ttl == 0 { 177 | return g.config.Ttl 178 | } 179 | if etcdTtl == 0 { 180 | return serv.Ttl 181 | } 182 | if serv.Ttl == 0 { 183 | return etcdTtl 184 | } 185 | if etcdTtl < serv.Ttl { 186 | return etcdTtl 187 | } 188 | return serv.Ttl 189 | } 190 | 191 | // Client exposes the underlying Etcd client (used in tests). 192 | func (g *Backend) Client() etcd.KeysAPI { 193 | return g.client 194 | } 195 | -------------------------------------------------------------------------------- /server/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "crypto" 9 | "fmt" 10 | "net" 11 | "os" 12 | "strings" 13 | "time" 14 | 15 | "github.com/miekg/dns" 16 | ) 17 | 18 | const ( 19 | SCacheCapacity = 10000 20 | RCacheCapacity = 100000 21 | RCacheTtl = 60 22 | Ndots = 2 23 | ) 24 | 25 | // Config provides options to the SkyDNS resolver. 26 | type Config struct { 27 | // The ip:port SkyDNS should be listening on for incoming DNS requests. 28 | DnsAddr string `json:"dns_addr,omitempty"` 29 | // bind to port(s) activated by systemd. If set to true, this overrides DnsAddr. 30 | Systemd bool `json:"systemd,omitempty"` 31 | // The domain SkyDNS is authoritative for, defaults to skydns.local. 32 | Domain string `json:"domain,omitempty"` 33 | // Domain pointing to a key where service info is stored when being queried 34 | // for local.dns.skydns.local. 35 | Local string `json:"local,omitempty"` 36 | // The hostmaster responsible for this domain, defaults to hostmaster.. 37 | Hostmaster string `json:"hostmaster,omitempty"` 38 | DNSSEC string `json:"dnssec,omitempty"` 39 | // Round robin A/AAAA replies. Default is true. 40 | RoundRobin bool `json:"round_robin,omitempty"` 41 | // Round robin selection of nameservers from among those listed, rather than have all forwarded requests try the first listed server first every time. 42 | NSRotate bool `json:"ns_rotate,omitempty"` 43 | // List of ip:port, separated by commas of recursive nameservers to forward queries to. 44 | Nameservers []string `json:"nameservers,omitempty"` 45 | // Never provide a recursive service. 46 | NoRec bool `json:"no_rec,omitempty"` 47 | ReadTimeout time.Duration `json:"read_timeout,omitempty"` 48 | // Default priority on SRV records when none is given. Defaults to 10. 49 | Priority uint16 `json:"priority"` 50 | // Default TTL, in seconds, when none is given in etcd. Defaults to 3600. 51 | Ttl uint32 `json:"ttl,omitempty"` 52 | // Minimum TTL, in seconds, for NXDOMAIN responses. Defaults to 300. 53 | MinTtl uint32 `json:"min_ttl,omitempty"` 54 | // SCache, capacity of the signature cache in signatures stored. 55 | SCache int `json:"scache,omitempty"` 56 | // RCache, capacity of response cache in resource records stored. 57 | RCache int `json:"rcache,omitempty"` 58 | // RCacheTtl, how long to cache in seconds. 59 | RCacheTtl int `json:"rcache_ttl,omitempty"` 60 | // How many labels a name should have before we allow forwarding. Default to 2. 61 | Ndots int `json:"ndot,omitempty"` 62 | // Etcd flag that dictates if etcd version 3 is supported during skydns' run. Default to false. 63 | Etcd3 bool 64 | 65 | // DNSSEC key material 66 | PubKey *dns.DNSKEY `json:"-"` 67 | KeyTag uint16 `json:"-"` 68 | PrivKey crypto.Signer `json:"-"` 69 | 70 | Verbose bool `json:"-"` 71 | 72 | Version bool 73 | 74 | // some predefined string "constants" 75 | localDomain string // "local.dns." + config.Domain 76 | dnsDomain string // "ns.dns". + config.Domain 77 | 78 | // Stub zones support. Pointer to a map that we refresh when we see 79 | // an update. Map contains domainname -> nameserver:port 80 | stub *map[string][]string 81 | } 82 | 83 | func SetDefaults(config *Config) error { 84 | if config.ReadTimeout == 0 { 85 | config.ReadTimeout = 2 * time.Second 86 | } 87 | if config.DnsAddr == "" { 88 | config.DnsAddr = "127.0.0.1:53" 89 | } 90 | if config.Domain == "" { 91 | config.Domain = "skydns.local." 92 | } 93 | if config.Hostmaster == "" { 94 | config.Hostmaster = appendDomain("hostmaster", config.Domain) 95 | } 96 | // People probably don't know that SOA's email addresses cannot 97 | // contain @-signs, replace them with dots 98 | config.Hostmaster = dns.Fqdn(strings.Replace(config.Hostmaster, "@", ".", -1)) 99 | if config.MinTtl == 0 { 100 | config.MinTtl = 60 101 | } 102 | if config.Ttl == 0 { 103 | config.Ttl = 3600 104 | } 105 | if config.Priority == 0 { 106 | config.Priority = 10 107 | } 108 | if config.RCache < 0 { 109 | config.RCache = 0 110 | } 111 | if config.SCache < 0 { 112 | config.SCache = 0 113 | } 114 | if config.RCacheTtl == 0 { 115 | config.RCacheTtl = RCacheTtl 116 | } 117 | if config.Ndots <= 0 { 118 | config.Ndots = Ndots 119 | } 120 | 121 | if len(config.Nameservers) == 0 { 122 | c, err := dns.ClientConfigFromFile("/etc/resolv.conf") 123 | if !os.IsNotExist(err) { 124 | if err != nil { 125 | return err 126 | } 127 | for _, s := range c.Servers { 128 | config.Nameservers = append(config.Nameservers, net.JoinHostPort(s, c.Port)) 129 | } 130 | } 131 | } 132 | config.Domain = dns.Fqdn(strings.ToLower(config.Domain)) 133 | if config.DNSSEC != "" { 134 | // For some reason the + are replaces by spaces in etcd. Re-replace them 135 | keyfile := strings.Replace(config.DNSSEC, " ", "+", -1) 136 | k, p, err := ParseKeyFile(keyfile) 137 | if err != nil { 138 | return err 139 | } 140 | if k.Header().Name != dns.Fqdn(config.Domain) { 141 | return fmt.Errorf("ownername of DNSKEY must match SkyDNS domain") 142 | } 143 | k.Header().Ttl = config.Ttl 144 | config.PubKey = k 145 | config.KeyTag = k.KeyTag() 146 | config.PrivKey = p 147 | } 148 | config.localDomain = appendDomain("local.dns", config.Domain) 149 | config.dnsDomain = appendDomain("ns.dns", config.Domain) 150 | stubmap := make(map[string][]string) 151 | config.stub = &stubmap 152 | return nil 153 | } 154 | 155 | func appendDomain(s1, s2 string) string { 156 | if len(s2) > 0 && s2[0] == '.' { 157 | return s1 + s2 158 | } 159 | return s1 + "." + s2 160 | } 161 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package metrics 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/miekg/dns" 15 | "github.com/prometheus/client_golang/prometheus" 16 | "github.com/prometheus/client_golang/prometheus/promhttp" 17 | ) 18 | 19 | var ( 20 | Port = os.Getenv("PROMETHEUS_PORT") 21 | Path = envOrDefault("PROMETHEUS_PATH", "/metrics") 22 | Namespace = envOrDefault("PROMETHEUS_NAMESPACE", "skydns") 23 | Subsystem = envOrDefault("PROMETHEUS_SUBSYSTEM", "skydns") 24 | 25 | requestCount *prometheus.CounterVec 26 | requestDuration *prometheus.HistogramVec 27 | responseSize *prometheus.HistogramVec 28 | errorCount *prometheus.CounterVec 29 | cacheMiss *prometheus.CounterVec 30 | ) 31 | 32 | type ( 33 | System string 34 | Cause string 35 | CacheType string 36 | ) 37 | 38 | var ( 39 | Auth System = "auth" 40 | Cache System = "cache" 41 | Rec System = "recursive" 42 | Reverse System = "reverse" 43 | Stub System = "stub" 44 | 45 | Nxdomain Cause = "nxdomain" 46 | Nodata Cause = "nodata" 47 | Truncated Cause = "truncated" 48 | Refused Cause = "refused" 49 | Overflow Cause = "overflow" 50 | Fail Cause = "servfail" 51 | 52 | Response CacheType = "response" 53 | Signature CacheType = "signature" 54 | ) 55 | 56 | func defineMetrics() { 57 | requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{ 58 | Namespace: Namespace, 59 | Subsystem: Subsystem, 60 | Name: "dns_request_count_total", 61 | Help: "Counter of DNS requests made.", 62 | }, []string{"system"}) 63 | 64 | requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 65 | Namespace: Namespace, 66 | Subsystem: Subsystem, 67 | Name: "dns_request_duration_seconds", 68 | Help: "Histogram of the time (in seconds) each request took to resolve.", 69 | Buckets: append([]float64{0.001, 0.003}, prometheus.DefBuckets...), 70 | }, []string{"system"}) 71 | 72 | responseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 73 | Namespace: Namespace, 74 | Subsystem: Subsystem, 75 | Name: "dns_response_size_bytes", 76 | Help: "Size of the returns response in bytes.", 77 | Buckets: []float64{0, 512, 1024, 1500, 2048, 4096, 78 | 8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864, 79 | 40960, 45056, 49152, 53248, 57344, 61440, 65536, 80 | }, 81 | }, []string{"system"}) 82 | 83 | errorCount = prometheus.NewCounterVec(prometheus.CounterOpts{ 84 | Namespace: Namespace, 85 | Subsystem: Subsystem, 86 | Name: "dns_error_count_total", 87 | Help: "Counter of DNS requests resulting in an error.", 88 | }, []string{"system", "cause"}) 89 | 90 | cacheMiss = prometheus.NewCounterVec(prometheus.CounterOpts{ 91 | Namespace: Namespace, 92 | Subsystem: Subsystem, 93 | Name: "dns_cachemiss_count_total", 94 | Help: "Counter of DNS requests that result in a cache miss.", 95 | }, []string{"cache"}) 96 | } 97 | 98 | // Metrics registers the DNS metrics to Prometheus, and starts the internal metrics 99 | // server if the environment variable PROMETHEUS_PORT is set. 100 | func Metrics() error { 101 | // We do this in a function instead of using var + init(), because we want to 102 | // able to set Namespace and/or Subsystem. 103 | if Port == "" { 104 | return nil 105 | } 106 | 107 | _, err := strconv.Atoi(Port) 108 | if err != nil { 109 | fmt.Errorf("bad port for prometheus: %s", Port) 110 | } 111 | 112 | defineMetrics() 113 | 114 | prometheus.MustRegister(requestCount) 115 | prometheus.MustRegister(requestDuration) 116 | prometheus.MustRegister(responseSize) 117 | prometheus.MustRegister(errorCount) 118 | prometheus.MustRegister(cacheMiss) 119 | 120 | http.Handle(Path, promhttp.Handler()) 121 | go func() { 122 | fmt.Errorf("%s", http.ListenAndServe(":"+Port, nil)) 123 | }() 124 | return nil 125 | } 126 | 127 | func ReportDuration(resp *dns.Msg, start time.Time, sys System) { 128 | if requestDuration == nil || responseSize == nil { 129 | return 130 | } 131 | 132 | rlen := float64(0) 133 | if resp != nil { 134 | rlen = float64(resp.Len()) 135 | } 136 | requestDuration.WithLabelValues(string(sys)).Observe(float64(time.Since(start)) / float64(time.Second)) 137 | responseSize.WithLabelValues(string(sys)).Observe(rlen) 138 | } 139 | 140 | func ReportRequestCount(req *dns.Msg, sys System) { 141 | if requestCount == nil { 142 | return 143 | } 144 | 145 | requestCount.WithLabelValues(string(sys)).Inc() 146 | } 147 | 148 | func ReportErrorCount(resp *dns.Msg, sys System) { 149 | if resp == nil || errorCount == nil { 150 | return 151 | } 152 | 153 | if resp.Truncated { 154 | errorCount.WithLabelValues(string(sys), string(Truncated)).Inc() 155 | return 156 | } 157 | if resp.Len() > dns.MaxMsgSize { 158 | errorCount.WithLabelValues(string(sys), string(Overflow)).Inc() 159 | return 160 | } 161 | 162 | switch resp.Rcode { 163 | case dns.RcodeServerFailure: 164 | errorCount.WithLabelValues(string(sys), string(Fail)).Inc() 165 | case dns.RcodeRefused: 166 | errorCount.WithLabelValues(string(sys), string(Refused)).Inc() 167 | case dns.RcodeNameError: 168 | errorCount.WithLabelValues(string(sys), string(Nxdomain)).Inc() 169 | // nodata ?? 170 | } 171 | 172 | } 173 | 174 | func ReportCacheMiss(ca CacheType) { 175 | if cacheMiss == nil { 176 | return 177 | } 178 | cacheMiss.WithLabelValues(string(ca)).Inc() 179 | } 180 | 181 | func envOrDefault(env, def string) string { 182 | e := os.Getenv(env) 183 | if e != "" { 184 | return e 185 | } 186 | return def 187 | } 188 | -------------------------------------------------------------------------------- /msg/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package msg 6 | 7 | import ( 8 | "net" 9 | "path" 10 | "strings" 11 | 12 | "github.com/miekg/dns" 13 | ) 14 | 15 | // PathPrefix is the prefix used to store SkyDNS data in the backend. 16 | // It defaults to `skydns`. 17 | // You can change it by set `path-prefix` configuration or SKYDNS_PATH_PREFIX env. variable. 18 | // Then: 19 | // The SkyDNS's configuration object should be stored under the key "/mydns/config"; 20 | // The etcd path of domain `service.staging.skydns.local.` will be "/mydns/local/skydns/staging/service". 21 | var PathPrefix string = "skydns" 22 | 23 | // This *is* the rdata from a SRV record, but with a twist. 24 | // Host (Target in SRV) must be a domain name, but if it looks like an IP 25 | // address (4/6), we will treat it like an IP address. 26 | type Service struct { 27 | Host string `json:"host,omitempty"` 28 | Port int `json:"port,omitempty"` 29 | Priority int `json:"priority,omitempty"` 30 | Weight int `json:"weight,omitempty"` 31 | Text string `json:"text,omitempty"` 32 | Mail bool `json:"mail,omitempty"` // Be an MX record. Priority becomes Preference. 33 | Ttl uint32 `json:"ttl,omitempty"` 34 | 35 | // When a SRV record with a "Host: IP-address" is added, we synthesize 36 | // a srv.Target domain name. Normally we convert the full Key where 37 | // the record lives to a DNS name and use this as the srv.Target. When 38 | // TargetStrip > 0 we strip the left most TargetStrip labels from the 39 | // DNS name. 40 | TargetStrip int `json:"targetstrip,omitempty"` 41 | 42 | // Group is used to group (or *not* to group) different services 43 | // together. Services with an identical Group are returned in the same 44 | // answer. 45 | Group string `json:"group,omitempty"` 46 | 47 | // Etcd key where we found this service and ignored from json un-/marshalling 48 | Key string `json:"-"` 49 | } 50 | 51 | // NewSRV returns a new SRV record based on the Service. 52 | func (s *Service) NewSRV(name string, weight uint16) *dns.SRV { 53 | host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) 54 | 55 | return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.Ttl}, 56 | Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: host} 57 | } 58 | 59 | // NewMX returns a new MX record based on the Service. 60 | func (s *Service) NewMX(name string) *dns.MX { 61 | host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip) 62 | 63 | return &dns.MX{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: s.Ttl}, 64 | Preference: uint16(s.Priority), Mx: host} 65 | } 66 | 67 | // NewA returns a new A record based on the Service. 68 | func (s *Service) NewA(name string, ip net.IP) *dns.A { 69 | return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.Ttl}, A: ip} 70 | } 71 | 72 | // NewAAAA returns a new AAAA record based on the Service. 73 | func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA { 74 | return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.Ttl}, AAAA: ip} 75 | } 76 | 77 | // NewCNAME returns a new CNAME record based on the Service. 78 | func (s *Service) NewCNAME(name string, target string) *dns.CNAME { 79 | return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.Ttl}, Target: target} 80 | } 81 | 82 | // NewNS returns a new NS record based on the Service. 83 | func (s *Service) NewNS(name string, target string) *dns.NS { 84 | return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.Ttl}, Ns: target} 85 | } 86 | 87 | // NewTXT returns a new TXT record based on the Service. 88 | func (s *Service) NewTXT(name string) *dns.TXT { 89 | return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.Ttl}, Txt: split255(s.Text)} 90 | } 91 | 92 | // NewPTR returns a new PTR record based on the Service. 93 | func (s *Service) NewPTR(name string, ttl uint32) *dns.PTR { 94 | return &dns.PTR{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}, Ptr: dns.Fqdn(s.Host)} 95 | } 96 | 97 | // As Path, but if a name contains wildcards (* or any), the name will be 98 | // chopped of before the (first) wildcard, and we do a highler evel search and 99 | // later find the matching names. So service.*.skydns.local, will look for all 100 | // services under skydns.local and will later check for names that match 101 | // service.*.skydns.local. If a wildcard is found the returned bool is true. 102 | func PathWithWildcard(s string) (string, bool) { 103 | l := dns.SplitDomainName(s) 104 | for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { 105 | l[i], l[j] = l[j], l[i] 106 | } 107 | for i, k := range l { 108 | if k == "*" || k == "any" { 109 | return path.Join(append([]string{"/" + PathPrefix + "/"}, l[:i]...)...), true 110 | } 111 | } 112 | return path.Join(append([]string{"/" + PathPrefix + "/"}, l...)...), false 113 | } 114 | 115 | // Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local., 116 | // the resulting key will be /skydns/local/skydns/staging/service . 117 | func Path(s string) string { 118 | l := dns.SplitDomainName(s) 119 | for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { 120 | l[i], l[j] = l[j], l[i] 121 | } 122 | return path.Join(append([]string{"/" + PathPrefix + "/"}, l...)...) 123 | } 124 | 125 | // Domain is the opposite of Path. 126 | func Domain(s string) string { 127 | l := strings.Split(s, "/") 128 | // start with 1, to strip /skydns 129 | for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 { 130 | l[i], l[j] = l[j], l[i] 131 | } 132 | return dns.Fqdn(strings.Join(l[1:len(l)-1], ".")) 133 | } 134 | 135 | // Group checks the services in sx, it looks for a Group attribute on the shortest 136 | // keys. If there are multiple shortest keys *and* the group attribute disagrees (and 137 | // is not empty), we don't consider it a group. 138 | // If a group is found, only services with *that* group (or no group) will be returned. 139 | func Group(sx []Service) []Service { 140 | if len(sx) == 0 { 141 | return sx 142 | } 143 | 144 | // Shortest key with group attribute sets the group for this set. 145 | group := sx[0].Group 146 | slashes := strings.Count(sx[0].Key, "/") 147 | length := make([]int, len(sx)) 148 | for i, s := range sx { 149 | x := strings.Count(s.Key, "/") 150 | length[i] = x 151 | if x < slashes { 152 | if s.Group == "" { 153 | break 154 | } 155 | slashes = x 156 | group = s.Group 157 | } 158 | } 159 | 160 | if group == "" { 161 | return sx 162 | } 163 | 164 | ret := []Service{} // with slice-tricks in sx we can prolly save this allocation (TODO) 165 | 166 | for i, s := range sx { 167 | if s.Group == "" { 168 | ret = append(ret, s) 169 | continue 170 | } 171 | 172 | // Disagreement on the same level 173 | if length[i] == slashes && s.Group != group { 174 | return sx 175 | } 176 | 177 | if s.Group == group { 178 | ret = append(ret, s) 179 | } 180 | } 181 | return ret 182 | } 183 | 184 | // Split255 splits a string into 255 byte chunks. 185 | func split255(s string) []string { 186 | if len(s) < 255 { 187 | return []string{s} 188 | } 189 | sx := []string{} 190 | p, i := 0, 255 191 | for { 192 | if i <= len(s) { 193 | sx = append(sx, s[p:i]) 194 | } else { 195 | sx = append(sx, s[p:]) 196 | break 197 | 198 | } 199 | p, i = p+255, i+255 200 | } 201 | 202 | return sx 203 | } 204 | 205 | // targetStrip strips "targetstrip" labels from the left side of the fully qualified name. 206 | func targetStrip(name string, targetStrip int) string { 207 | if targetStrip == 0 { 208 | return name 209 | } 210 | 211 | offset, end := 0, false 212 | for i := 0; i < targetStrip; i++ { 213 | offset, end = dns.NextLabel(name, offset) 214 | } 215 | if end { 216 | // We overshot the name, use the orignal one. 217 | offset = 0 218 | } 219 | name = name[offset:] 220 | return name 221 | } 222 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:5bb36304653e73c2ced864d49c9f344e7141a7ceef852442edcea212094ebc3c" 7 | name = "github.com/beorn7/perks" 8 | packages = ["quantile"] 9 | pruneopts = "UT" 10 | revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" 11 | 12 | [[projects]] 13 | digest = "1:621fa2c00bde38b9ccf908fd64adaac543ce433135f2a2805a9a98eb7a782759" 14 | name = "github.com/coreos/etcd" 15 | packages = [ 16 | "auth/authpb", 17 | "client", 18 | "clientv3", 19 | "etcdserver/api/v3rpc/rpctypes", 20 | "etcdserver/etcdserverpb", 21 | "mvcc/mvccpb", 22 | "pkg/pathutil", 23 | "pkg/srv", 24 | "pkg/tlsutil", 25 | "pkg/transport", 26 | "pkg/types", 27 | "version", 28 | ] 29 | pruneopts = "UT" 30 | revision = "1b3ac99e8a431b381e633802cc42fe70e663baf5" 31 | version = "v3.2.15" 32 | 33 | [[projects]] 34 | digest = "1:0ef770954bca104ee99b3b6b7f9b240605ac03517d9f98cbc1893daa03f3c038" 35 | name = "github.com/coreos/go-semver" 36 | packages = ["semver"] 37 | pruneopts = "UT" 38 | revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6" 39 | version = "v0.2.0" 40 | 41 | [[projects]] 42 | digest = "1:97590846d4c85cfd1d68adb3e0a1e2028ce3fb9868b7112bf13c1f3d77395eeb" 43 | name = "github.com/coreos/go-systemd" 44 | packages = ["activation"] 45 | pruneopts = "UT" 46 | revision = "40e2722dffead74698ca12a750f64ef313ddce05" 47 | version = "v16" 48 | 49 | [[projects]] 50 | digest = "1:f52e4148b9bbc9de7574513218171e86231741e5c559ffcbbbe1671e6d702eca" 51 | name = "github.com/golang/protobuf" 52 | packages = [ 53 | "proto", 54 | "protoc-gen-go/descriptor", 55 | "ptypes", 56 | "ptypes/any", 57 | "ptypes/duration", 58 | "ptypes/timestamp", 59 | ] 60 | pruneopts = "UT" 61 | revision = "925541529c1fa6821df4e44ce2723319eb2be768" 62 | version = "v1.0.0" 63 | 64 | [[projects]] 65 | digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6" 66 | name = "github.com/matttproud/golang_protobuf_extensions" 67 | packages = ["pbutil"] 68 | pruneopts = "UT" 69 | revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" 70 | version = "v1.0.0" 71 | 72 | [[projects]] 73 | digest = "1:e761c7c3b761147b20a0500d4bce55c78fb088ff5bb0bc76b3feb57a30e64fd1" 74 | name = "github.com/miekg/dns" 75 | packages = ["."] 76 | pruneopts = "UT" 77 | revision = "5364553f1ee9cddc7ac8b62dce148309c386695b" 78 | version = "v1.0.4" 79 | 80 | [[projects]] 81 | digest = "1:eb04f69c8991e52eff33c428bd729e04208bf03235be88e4df0d88497c6861b9" 82 | name = "github.com/prometheus/client_golang" 83 | packages = [ 84 | "prometheus", 85 | "prometheus/internal", 86 | "prometheus/promhttp", 87 | ] 88 | pruneopts = "UT" 89 | revision = "170205fb58decfd011f1550d4cfb737230d7ae4f" 90 | version = "v1.1.0" 91 | 92 | [[projects]] 93 | branch = "master" 94 | digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b" 95 | name = "github.com/prometheus/client_model" 96 | packages = ["go"] 97 | pruneopts = "UT" 98 | revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" 99 | 100 | [[projects]] 101 | branch = "master" 102 | digest = "1:fcce8c26e13e3d5018d5c42de857e8b700354d36afb900dd82bc642383981661" 103 | name = "github.com/prometheus/common" 104 | packages = [ 105 | "expfmt", 106 | "internal/bitbucket.org/ww/goautoneg", 107 | "model", 108 | ] 109 | pruneopts = "UT" 110 | revision = "89604d197083d4781071d3c65855d24ecfb0a563" 111 | 112 | [[projects]] 113 | digest = "1:a210815b437763623ecca8eb91e6a0bf4f2d6773c5a6c9aec0e28f19e5fd6deb" 114 | name = "github.com/prometheus/procfs" 115 | packages = [ 116 | ".", 117 | "internal/fs", 118 | "internal/util", 119 | ] 120 | pruneopts = "UT" 121 | revision = "499c85531f756d1129edd26485a5f73871eeb308" 122 | version = "v0.0.5" 123 | 124 | [[projects]] 125 | branch = "master" 126 | digest = "1:8ceeb397506d4f497f349efc4613abc86a5aba9e8291b229e3592cb655118e19" 127 | name = "github.com/skynetservices/skydns" 128 | packages = [ 129 | "backends/etcd", 130 | "backends/etcd3", 131 | "cache", 132 | "metrics", 133 | "msg", 134 | "server", 135 | "singleflight", 136 | ] 137 | pruneopts = "UT" 138 | revision = "fc00e571c471eef3d6d15f500b6c3a6f55d2ee90" 139 | 140 | [[projects]] 141 | digest = "1:65946d7902d5aa57acdf6c3771c1de1ecd23f0dc32536849cfa28bd95a4400a5" 142 | name = "github.com/ugorji/go" 143 | packages = ["codec"] 144 | pruneopts = "UT" 145 | revision = "8c0409fcbb70099c748d71f714529204975f6c3f" 146 | 147 | [[projects]] 148 | branch = "master" 149 | digest = "1:6c463ff0ea9cac4e7ad09f636bbd61393ac6179ba9b78a11a4227ead4131181c" 150 | name = "golang.org/x/crypto" 151 | packages = [ 152 | "ed25519", 153 | "ed25519/internal/edwards25519", 154 | ] 155 | pruneopts = "UT" 156 | revision = "5119cf507ed5294cc409c092980c7497ee5d6fd2" 157 | 158 | [[projects]] 159 | branch = "master" 160 | digest = "1:5791cf3cb703a908aa3b75ff98d1f20afd4cd9352af032b49ac39bfa17a056a6" 161 | name = "golang.org/x/net" 162 | packages = [ 163 | "bpf", 164 | "context", 165 | "http2", 166 | "http2/hpack", 167 | "idna", 168 | "internal/iana", 169 | "internal/socket", 170 | "internal/timeseries", 171 | "ipv4", 172 | "ipv6", 173 | "lex/httplex", 174 | "trace", 175 | ] 176 | pruneopts = "UT" 177 | revision = "f5dfe339be1d06f81b22525fe34671ee7d2c8904" 178 | 179 | [[projects]] 180 | branch = "master" 181 | digest = "1:ebd63f7372d4fb878b0297af6c28b505fc8d33880320d38cb34c47528bf5868f" 182 | name = "golang.org/x/sys" 183 | packages = ["windows"] 184 | pruneopts = "UT" 185 | revision = "c178f38b412c7b426e4e97be2e75d11ff7b8d4d4" 186 | 187 | [[projects]] 188 | branch = "master" 189 | digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" 190 | name = "golang.org/x/text" 191 | packages = [ 192 | "collate", 193 | "collate/build", 194 | "internal/colltab", 195 | "internal/gen", 196 | "internal/tag", 197 | "internal/triegen", 198 | "internal/ucd", 199 | "language", 200 | "secure/bidirule", 201 | "transform", 202 | "unicode/bidi", 203 | "unicode/cldr", 204 | "unicode/norm", 205 | "unicode/rangetable", 206 | ] 207 | pruneopts = "UT" 208 | revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1" 209 | 210 | [[projects]] 211 | branch = "master" 212 | digest = "1:c2dee8dbcc504d1a7858f5dbaed7c8b256c512c5e9e81480158c30185bbd2792" 213 | name = "google.golang.org/genproto" 214 | packages = [ 215 | "googleapis/api/annotations", 216 | "googleapis/rpc/status", 217 | ] 218 | pruneopts = "UT" 219 | revision = "2b5a72b8730b0b16380010cfe5286c42108d88e7" 220 | 221 | [[projects]] 222 | digest = "1:b754c408bc1bb05c1853908d9b35da0adeef72404b54337ef19d53a2705d6db6" 223 | name = "google.golang.org/grpc" 224 | packages = [ 225 | ".", 226 | "balancer", 227 | "balancer/base", 228 | "balancer/roundrobin", 229 | "codes", 230 | "connectivity", 231 | "credentials", 232 | "encoding", 233 | "grpclb/grpc_lb_v1/messages", 234 | "grpclog", 235 | "health/grpc_health_v1", 236 | "internal", 237 | "keepalive", 238 | "metadata", 239 | "naming", 240 | "peer", 241 | "resolver", 242 | "resolver/dns", 243 | "resolver/passthrough", 244 | "stats", 245 | "status", 246 | "tap", 247 | "transport", 248 | ] 249 | pruneopts = "UT" 250 | revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef" 251 | version = "v1.9.2" 252 | 253 | [solve-meta] 254 | analyzer-name = "dep" 255 | analyzer-version = 1 256 | input-imports = [ 257 | "github.com/coreos/etcd/client", 258 | "github.com/coreos/etcd/clientv3", 259 | "github.com/coreos/etcd/mvcc/mvccpb", 260 | "github.com/coreos/etcd/pkg/transport", 261 | "github.com/coreos/go-systemd/activation", 262 | "github.com/miekg/dns", 263 | "github.com/prometheus/client_golang/prometheus", 264 | "github.com/prometheus/client_golang/prometheus/promhttp", 265 | "github.com/skynetservices/skydns/backends/etcd", 266 | "github.com/skynetservices/skydns/backends/etcd3", 267 | "github.com/skynetservices/skydns/cache", 268 | "github.com/skynetservices/skydns/metrics", 269 | "github.com/skynetservices/skydns/msg", 270 | "github.com/skynetservices/skydns/server", 271 | "github.com/skynetservices/skydns/singleflight", 272 | "golang.org/x/net/context", 273 | ] 274 | solver-name = "gps-cdcl" 275 | solver-version = 1 276 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "flag" 11 | "fmt" 12 | "log" 13 | "math/rand" 14 | "net" 15 | "net/http" 16 | "os" 17 | "strconv" 18 | "strings" 19 | "time" 20 | 21 | backendetcd "github.com/skynetservices/skydns/backends/etcd" 22 | backendetcdv3 "github.com/skynetservices/skydns/backends/etcd3" 23 | "github.com/skynetservices/skydns/metrics" 24 | "github.com/skynetservices/skydns/msg" 25 | "github.com/skynetservices/skydns/server" 26 | 27 | etcd "github.com/coreos/etcd/client" 28 | etcdv3 "github.com/coreos/etcd/clientv3" 29 | "github.com/coreos/etcd/pkg/transport" 30 | "github.com/miekg/dns" 31 | ) 32 | 33 | var ( 34 | tlskey = "" 35 | tlspem = "" 36 | cacert = "" 37 | username = "" 38 | password = "" 39 | config = &server.Config{ReadTimeout: 0, Domain: "", DnsAddr: "", DNSSEC: ""} 40 | nameserver = "" 41 | machine = "" 42 | stub = false 43 | ctx = context.Background() 44 | ) 45 | 46 | func env(key, def string) string { 47 | if x := os.Getenv(key); x != "" { 48 | return x 49 | } 50 | return def 51 | } 52 | 53 | func intEnv(key string, def int) int { 54 | if x := os.Getenv(key); x != "" { 55 | if v, err := strconv.ParseInt(x, 10, 0); err == nil { 56 | return int(v) 57 | } 58 | } 59 | return def 60 | } 61 | 62 | func boolEnv(key string, def bool) bool { 63 | if x := os.Getenv(key); x != "" { 64 | if v, err := strconv.ParseBool(x); err == nil { 65 | return v 66 | } 67 | } 68 | return def 69 | } 70 | 71 | func init() { 72 | flag.StringVar(&config.Domain, "domain", env("SKYDNS_DOMAIN", "skydns.local."), "domain to anchor requests to (SKYDNS_DOMAIN)") 73 | flag.StringVar(&config.DnsAddr, "addr", env("SKYDNS_ADDR", "127.0.0.1:53"), "ip:port to bind to (SKYDNS_ADDR)") 74 | flag.StringVar(&nameserver, "nameservers", env("SKYDNS_NAMESERVERS", ""), "nameserver address(es) to forward (non-local) queries to e.g. 8.8.8.8:53,8.8.4.4:53") 75 | flag.BoolVar(&config.NoRec, "no-rec", false, "do not provide a recursive service") 76 | flag.StringVar(&machine, "machines", env("ETCD_MACHINES", "http://127.0.0.1:2379"), "machine address(es) running etcd") 77 | flag.StringVar(&config.DNSSEC, "dnssec", "", "basename of DNSSEC key file e.q. Kskydns.local.+005+38250") 78 | flag.StringVar(&config.Local, "local", "", "optional unique value for this skydns instance") 79 | flag.StringVar(&tlskey, "tls-key", env("ETCD_TLSKEY", ""), "SSL key file used to secure etcd communication") 80 | flag.StringVar(&tlspem, "tls-pem", env("ETCD_TLSPEM", ""), "SSL certification file used to secure etcd communication") 81 | flag.StringVar(&cacert, "ca-cert", env("ETCD_CACERT", ""), "SSL Certificate Authority file used to secure etcd communication") 82 | flag.StringVar(&username, "username", env("ETCD_USERNAME", ""), "Username used to support etcd basic auth") 83 | flag.StringVar(&password, "password", env("ETCD_PASSWORD", ""), "Password used to support etcd basic auth") 84 | flag.DurationVar(&config.ReadTimeout, "rtimeout", 2*time.Second, "read timeout") 85 | flag.BoolVar(&config.RoundRobin, "round-robin", true, "round robin A/AAAA replies") 86 | flag.BoolVar(&config.NSRotate, "ns-rotate", true, "round robin selection of nameservers from among those listed") 87 | flag.BoolVar(&stub, "stubzones", false, "support stub zones") 88 | flag.BoolVar(&config.Verbose, "verbose", false, "log queries") 89 | flag.BoolVar(&config.Systemd, "systemd", boolEnv("SKYDNS_SYSTEMD", false), "bind to socket(s) activated by systemd (ignore -addr)") 90 | 91 | // Version 92 | flag.BoolVar(&config.Version, "version", false, "Print the version and exit.") 93 | 94 | // TTl 95 | // Minttl 96 | flag.StringVar(&config.Hostmaster, "hostmaster", "hostmaster@skydns.local.", "hostmaster email address to use") 97 | flag.IntVar(&config.SCache, "scache", server.SCacheCapacity, "capacity of the signature cache") 98 | flag.IntVar(&config.RCache, "rcache", 0, "capacity of the response cache") // default to 0 for now 99 | flag.IntVar(&config.RCacheTtl, "rcache-ttl", server.RCacheTtl, "TTL of the response cache") 100 | 101 | // Ndots 102 | flag.IntVar(&config.Ndots, "ndots", intEnv("SKYDNS_NDOTS", server.Ndots), "How many labels a name should have before we allow forwarding") 103 | 104 | flag.StringVar(&msg.PathPrefix, "path-prefix", env("SKYDNS_PATH_PREFIX", "skydns"), "backend(etcd) path prefix, default: skydns") 105 | 106 | flag.BoolVar(&config.Etcd3, "etcd3", false, "flag that denotes the etcd version to be supported by skydns during runtime. Defaults to false.") 107 | } 108 | 109 | func main() { 110 | flag.Parse() 111 | 112 | if config.Version { 113 | fmt.Printf("skydns server version: %s\n", server.Version) 114 | os.Exit(0) 115 | } 116 | 117 | machines := strings.Split(machine, ",") 118 | 119 | var clientptr *etcdv3.Client 120 | var err error 121 | var clientv3 etcdv3.Client 122 | var clientv2 etcd.KeysAPI 123 | 124 | if config.Etcd3 { 125 | clientptr, err = newEtcdV3Client(machines, tlspem, tlskey, cacert) 126 | clientv3 = *clientptr 127 | } else { 128 | clientv2, err = newEtcdV2Client(machines, tlspem, tlskey, cacert, username, password) 129 | } 130 | 131 | if err != nil { 132 | panic(err) 133 | } 134 | 135 | if nameserver != "" { 136 | for _, hostPort := range strings.Split(nameserver, ",") { 137 | if err := validateHostPort(hostPort); err != nil { 138 | log.Fatalf("skydns: nameserver is invalid: %s", err) 139 | } 140 | config.Nameservers = append(config.Nameservers, hostPort) 141 | } 142 | } 143 | if err := validateHostPort(config.DnsAddr); err != nil { 144 | log.Fatalf("skydns: addr is invalid: %s", err) 145 | } 146 | 147 | if config.Etcd3 { 148 | if err := loadEtcdV3Config(clientv3, config); err != nil { 149 | log.Fatalf("skydns: %s", err) 150 | } 151 | } else { 152 | if err := loadEtcdV2Config(clientv2, config); err != nil { 153 | log.Fatalf("skydns: %s", err) 154 | } 155 | } 156 | 157 | if err := server.SetDefaults(config); err != nil { 158 | log.Fatalf("skydns: defaults could not be set from /etc/resolv.conf: %v", err) 159 | } 160 | 161 | if config.Local != "" { 162 | config.Local = dns.Fqdn(config.Local) 163 | } 164 | 165 | var backend server.Backend 166 | if config.Etcd3 { 167 | backend = backendetcdv3.NewBackendv3(clientv3, ctx, &backendetcdv3.Config{ 168 | Ttl: config.Ttl, 169 | Priority: config.Priority, 170 | }) 171 | } else { 172 | backend = backendetcd.NewBackend(clientv2, ctx, &backendetcd.Config{ 173 | Ttl: config.Ttl, 174 | Priority: config.Priority, 175 | }) 176 | } 177 | 178 | s := server.New(backend, config) 179 | if stub { 180 | s.UpdateStubZones() 181 | go func() { 182 | duration := 1 * time.Second 183 | 184 | if config.Etcd3 { 185 | var watcher etcdv3.WatchChan 186 | watcher = clientv3.Watch(ctx, msg.Path(config.Domain)+"/dns/stub/", etcdv3.WithPrefix()) 187 | 188 | for wresp := range watcher { 189 | if wresp.Err() != nil { 190 | log.Printf("skydns: stubzone update failed, sleeping %s + ~3s", duration) 191 | time.Sleep(duration + (time.Duration(rand.Float32() * 3e9))) 192 | duration *= 2 193 | if duration > 32*time.Second { 194 | duration = 32 * time.Second 195 | } 196 | } else { 197 | s.UpdateStubZones() 198 | log.Printf("skydns: stubzone update") 199 | duration = 1 * time.Second //reset 200 | } 201 | } 202 | } else { 203 | var watcher etcd.Watcher 204 | 205 | watcher = clientv2.Watcher(msg.Path(config.Domain)+"/dns/stub/", &etcd.WatcherOptions{AfterIndex: 0, Recursive: true}) 206 | 207 | for { 208 | _, err := watcher.Next(ctx) 209 | 210 | if err != nil { 211 | // 212 | log.Printf("skydns: stubzone update failed, sleeping %s + ~3s", duration) 213 | time.Sleep(duration + (time.Duration(rand.Float32() * 3e9))) // Add some random. 214 | duration *= 2 215 | if duration > 32*time.Second { 216 | duration = 32 * time.Second 217 | } 218 | } else { 219 | s.UpdateStubZones() 220 | log.Printf("skydns: stubzone update") 221 | duration = 1 * time.Second // reset 222 | } 223 | } 224 | } 225 | }() 226 | } 227 | 228 | if err := metrics.Metrics(); err != nil { 229 | log.Fatalf("skydns: %s", err) 230 | } else { 231 | log.Printf("skydns: metrics enabled on :%s%s", metrics.Port, metrics.Path) 232 | } 233 | 234 | if err := s.Run(); err != nil { 235 | log.Fatalf("skydns: %s", err) 236 | } 237 | } 238 | 239 | func loadEtcdV2Config(client etcd.KeysAPI, config *server.Config) error { 240 | // Override what isn't set yet from the command line. 241 | configPath := "/" + msg.PathPrefix + "/config" 242 | resp, err := client.Get(ctx, configPath, nil) 243 | if err != nil { 244 | log.Printf("skydns: falling back to default configuration, could not read from etcd: %s", err) 245 | return nil 246 | } 247 | if err := json.Unmarshal([]byte(resp.Node.Value), config); err != nil { 248 | return fmt.Errorf("failed to unmarshal config: %s", err.Error()) 249 | } 250 | return nil 251 | } 252 | 253 | func loadEtcdV3Config(client etcdv3.Client, config *server.Config) error { 254 | configPath := "/" + msg.PathPrefix + "/config" 255 | resp, err := client.Get(ctx, configPath) 256 | if err != nil { 257 | log.Printf("skydns: falling back to default configuration, could not read from etcd: %s", err) 258 | return nil 259 | } 260 | for _, ev := range resp.Kvs { 261 | if err := json.Unmarshal([]byte(ev.Value), config); err != nil { 262 | return fmt.Errorf("failed to unmarshal config: %s", err.Error()) 263 | } 264 | } 265 | return nil 266 | } 267 | 268 | func validateHostPort(hostPort string) error { 269 | host, port, err := net.SplitHostPort(hostPort) 270 | if err != nil { 271 | return err 272 | } 273 | if ip := net.ParseIP(host); ip == nil { 274 | return fmt.Errorf("bad IP address: %s", host) 275 | } 276 | 277 | if p, _ := strconv.Atoi(port); p < 1 || p > 65535 { 278 | return fmt.Errorf("bad port number %s", port) 279 | } 280 | return nil 281 | } 282 | 283 | func newEtcdV2Client(machines []string, certFile, keyFile, caFile, username, password string) (etcd.KeysAPI, error) { 284 | t, err := newHTTPSTransport(certFile, keyFile, caFile) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | cli, err := etcd.New(etcd.Config{ 290 | Endpoints: machines, 291 | Transport: t, 292 | Username: username, 293 | Password: password, 294 | }) 295 | if err != nil { 296 | return nil, err 297 | } 298 | return etcd.NewKeysAPI(cli), nil 299 | } 300 | 301 | func newEtcdV3Client(machines []string, tlsCert, tlsKey, tlsCACert string) (*etcdv3.Client, error) { 302 | 303 | tr, err := newHTTPSTransport(tlsCert, tlsKey, tlsCACert) 304 | if err != nil { 305 | return nil, err 306 | } 307 | 308 | etcdCfg := etcdv3.Config{ 309 | Endpoints: machines, 310 | TLS: tr.TLSClientConfig, 311 | } 312 | cli, err := etcdv3.New(etcdCfg) 313 | if err != nil { 314 | return nil, err 315 | } 316 | return cli, nil 317 | } 318 | 319 | func newHTTPSTransport(certFile, keyFile, caFile string) (*http.Transport, error) { 320 | info := transport.TLSInfo{ 321 | CertFile: certFile, 322 | KeyFile: keyFile, 323 | CAFile: caFile, 324 | } 325 | cfg, err := info.ClientConfig() 326 | if err != nil { 327 | return nil, err 328 | } 329 | 330 | tr := &http.Transport{ 331 | Proxy: http.ProxyFromEnvironment, 332 | Dial: (&net.Dialer{ 333 | Timeout: 30 * time.Second, 334 | KeepAlive: 30 * time.Second, 335 | }).Dial, 336 | TLSHandshakeTimeout: 10 * time.Second, 337 | TLSClientConfig: cfg, 338 | } 339 | 340 | return tr, nil 341 | } 342 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "net" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/skynetservices/skydns/cache" 17 | "github.com/skynetservices/skydns/metrics" 18 | "github.com/skynetservices/skydns/msg" 19 | 20 | etcd "github.com/coreos/etcd/client" 21 | "github.com/coreos/go-systemd/activation" 22 | "github.com/miekg/dns" 23 | ) 24 | 25 | const Version = "2.5.3a" 26 | 27 | type server struct { 28 | backend Backend 29 | config *Config 30 | 31 | group *sync.WaitGroup 32 | dnsUDPclient *dns.Client // used for forwarding queries 33 | dnsTCPclient *dns.Client // used for forwarding queries 34 | scache *cache.Cache 35 | rcache *cache.Cache 36 | } 37 | 38 | // New returns a new SkyDNS server. 39 | func New(backend Backend, config *Config) *server { 40 | return &server{ 41 | backend: backend, 42 | config: config, 43 | 44 | group: new(sync.WaitGroup), 45 | scache: cache.New(config.SCache, 0), 46 | rcache: cache.New(config.RCache, config.RCacheTtl), 47 | dnsUDPclient: &dns.Client{Net: "udp", ReadTimeout: config.ReadTimeout, WriteTimeout: config.ReadTimeout, SingleInflight: true}, 48 | dnsTCPclient: &dns.Client{Net: "tcp", ReadTimeout: config.ReadTimeout, WriteTimeout: config.ReadTimeout, SingleInflight: true}, 49 | } 50 | } 51 | 52 | // Run is a blocking operation that starts the server listening on the DNS ports. 53 | func (s *server) Run() error { 54 | mux := dns.NewServeMux() 55 | mux.Handle(".", s) 56 | 57 | dnsReadyMsg := func(addr, net string) { 58 | if s.config.DNSSEC == "" { 59 | logf("ready for queries on %s for %s://%s [rcache %d]", s.config.Domain, net, addr, s.config.RCache) 60 | } else { 61 | logf("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) 62 | } 63 | } 64 | 65 | if s.config.Systemd { 66 | packetConns, err := activation.PacketConns(false) 67 | if err != nil { 68 | return err 69 | } 70 | listeners, err := activation.Listeners(true) 71 | if err != nil { 72 | return err 73 | } 74 | if len(packetConns) == 0 && len(listeners) == 0 { 75 | return fmt.Errorf("no UDP or TCP sockets supplied by systemd") 76 | } 77 | for _, p := range packetConns { 78 | if u, ok := p.(*net.UDPConn); ok { 79 | s.group.Add(1) 80 | go func() { 81 | defer s.group.Done() 82 | if err := dns.ActivateAndServe(nil, u, mux); err != nil { 83 | fatalf("%s", err) 84 | } 85 | }() 86 | dnsReadyMsg(u.LocalAddr().String(), "udp") 87 | } 88 | } 89 | for _, l := range listeners { 90 | if t, ok := l.(*net.TCPListener); ok { 91 | s.group.Add(1) 92 | go func() { 93 | defer s.group.Done() 94 | if err := dns.ActivateAndServe(t, nil, mux); err != nil { 95 | fatalf("%s", err) 96 | } 97 | }() 98 | dnsReadyMsg(t.Addr().String(), "tcp") 99 | } 100 | } 101 | } else { 102 | s.group.Add(1) 103 | go func() { 104 | defer s.group.Done() 105 | if err := dns.ListenAndServe(s.config.DnsAddr, "tcp", mux); err != nil { 106 | fatalf("%s", err) 107 | } 108 | }() 109 | dnsReadyMsg(s.config.DnsAddr, "tcp") 110 | s.group.Add(1) 111 | go func() { 112 | defer s.group.Done() 113 | if err := dns.ListenAndServe(s.config.DnsAddr, "udp", mux); err != nil { 114 | fatalf("%s", err) 115 | } 116 | }() 117 | dnsReadyMsg(s.config.DnsAddr, "udp") 118 | } 119 | 120 | s.group.Wait() 121 | return nil 122 | } 123 | 124 | // Stop stops a server. 125 | func (s *server) Stop() { 126 | // TODO(miek) 127 | //s.group.Add(-2) 128 | } 129 | 130 | // ServeDNS is the handler for DNS requests, responsible for parsing DNS request, possibly forwarding 131 | // it to a real dns server and returning a response. 132 | func (s *server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { 133 | m := new(dns.Msg) 134 | m.SetReply(req) 135 | m.Authoritative = true 136 | m.RecursionAvailable = true 137 | m.Compress = true 138 | 139 | bufsize := uint16(512) 140 | dnssec := false 141 | tcp := false 142 | start := time.Now() 143 | 144 | q := req.Question[0] 145 | name := strings.ToLower(q.Name) 146 | 147 | if q.Qtype == dns.TypeANY || !s.backend.HasSynced() { 148 | m.Authoritative = false 149 | m.Rcode = dns.RcodeRefused 150 | m.RecursionAvailable = false 151 | m.RecursionDesired = false 152 | m.Compress = false 153 | w.WriteMsg(m) 154 | 155 | metrics.ReportRequestCount(m, metrics.Auth) 156 | metrics.ReportDuration(m, start, metrics.Auth) 157 | metrics.ReportErrorCount(m, metrics.Auth) 158 | 159 | return 160 | } 161 | 162 | if o := req.IsEdns0(); o != nil { 163 | bufsize = o.UDPSize() 164 | dnssec = o.Do() 165 | } 166 | if bufsize < 512 { 167 | bufsize = 512 168 | } 169 | // with TCP we can send 64K 170 | if tcp = isTCP(w); tcp { 171 | bufsize = dns.MaxMsgSize - 1 172 | } 173 | 174 | if s.config.Verbose { 175 | logf("received DNS Request for %q from %q with type %d", q.Name, w.RemoteAddr(), q.Qtype) 176 | } 177 | 178 | // Check cache first. 179 | m1 := s.rcache.Hit(q, dnssec, tcp, m.Id) 180 | if m1 != nil { 181 | metrics.ReportRequestCount(req, metrics.Cache) 182 | 183 | if send := s.overflowOrTruncated(w, m1, int(bufsize), metrics.Cache); send { 184 | return 185 | } 186 | 187 | // Still round-robin even with hits from the cache. 188 | // Only shuffle A and AAAA records with each other. 189 | if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { 190 | s.RoundRobin(m1.Answer) 191 | } 192 | 193 | if err := w.WriteMsg(m1); err != nil { 194 | logf("failure to return reply %q", err) 195 | } 196 | 197 | metrics.ReportDuration(m1, start, metrics.Cache) 198 | metrics.ReportErrorCount(m1, metrics.Cache) 199 | return 200 | } 201 | 202 | for zone, ns := range *s.config.stub { 203 | if strings.HasSuffix(name, "."+zone) || name == zone { 204 | metrics.ReportRequestCount(req, metrics.Stub) 205 | 206 | resp := s.ServeDNSStubForward(w, req, ns) 207 | if resp != nil { 208 | s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) 209 | } 210 | 211 | metrics.ReportDuration(resp, start, metrics.Stub) 212 | metrics.ReportErrorCount(resp, metrics.Stub) 213 | return 214 | } 215 | } 216 | 217 | // If the qname is local.ds.skydns.local. and s.config.Local != "", substitute that name. 218 | if s.config.Local != "" && name == s.config.localDomain { 219 | name = s.config.Local 220 | } 221 | 222 | if q.Qtype == dns.TypePTR && strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { 223 | metrics.ReportRequestCount(req, metrics.Reverse) 224 | 225 | resp := s.ServeDNSReverse(w, req) 226 | if resp != nil { 227 | s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) 228 | } 229 | 230 | metrics.ReportDuration(resp, start, metrics.Reverse) 231 | metrics.ReportErrorCount(resp, metrics.Reverse) 232 | return 233 | } 234 | 235 | if q.Qclass != dns.ClassCHAOS && !strings.HasSuffix(name, "."+s.config.Domain) && name != s.config.Domain { 236 | metrics.ReportRequestCount(req, metrics.Rec) 237 | 238 | resp := s.ServeDNSForward(w, req) 239 | if resp != nil { 240 | s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) 241 | } 242 | 243 | metrics.ReportDuration(resp, start, metrics.Rec) 244 | metrics.ReportErrorCount(resp, metrics.Rec) 245 | return 246 | } 247 | 248 | metrics.ReportCacheMiss(metrics.Response) 249 | 250 | defer func() { 251 | metrics.ReportRequestCount(req, metrics.Auth) 252 | metrics.ReportDuration(m, start, metrics.Auth) 253 | metrics.ReportErrorCount(m, metrics.Auth) 254 | 255 | if m.Rcode == dns.RcodeServerFailure { 256 | if err := w.WriteMsg(m); err != nil { 257 | logf("failure to return reply %q", err) 258 | } 259 | return 260 | } 261 | // Set TTL to the minimum of the RRset and dedup the message, i.e. remove identical RRs. 262 | m = s.dedup(m) 263 | 264 | minttl := s.config.Ttl 265 | if len(m.Answer) > 1 { 266 | for _, r := range m.Answer { 267 | if r.Header().Ttl < minttl { 268 | minttl = r.Header().Ttl 269 | } 270 | } 271 | for _, r := range m.Answer { 272 | r.Header().Ttl = minttl 273 | } 274 | } 275 | 276 | if dnssec { 277 | if s.config.PubKey != nil { 278 | m.AuthenticatedData = true 279 | s.Denial(m) 280 | s.Sign(m, bufsize) 281 | } 282 | } 283 | 284 | if send := s.overflowOrTruncated(w, m, int(bufsize), metrics.Auth); send { 285 | return 286 | } 287 | 288 | s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), m) 289 | 290 | if err := w.WriteMsg(m); err != nil { 291 | logf("failure to return reply %q", err) 292 | } 293 | }() 294 | 295 | if name == s.config.Domain { 296 | if q.Qtype == dns.TypeSOA { 297 | m.Answer = []dns.RR{s.NewSOA()} 298 | return 299 | } 300 | if q.Qtype == dns.TypeDNSKEY { 301 | if s.config.PubKey != nil { 302 | m.Answer = []dns.RR{s.config.PubKey} 303 | return 304 | } 305 | } 306 | } 307 | if q.Qclass == dns.ClassCHAOS { 308 | if q.Qtype == dns.TypeTXT { 309 | switch name { 310 | case "authors.bind.": 311 | fallthrough 312 | case s.config.Domain: 313 | hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} 314 | authors := []string{"Erik St. Martin", "Brian Ketelsen", "Miek Gieben", "Michael Crosby"} 315 | for _, a := range authors { 316 | m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{a}}) 317 | } 318 | for j := 0; j < len(authors)*(int(dns.Id())%4+1); j++ { 319 | q := int(dns.Id()) % len(authors) 320 | p := int(dns.Id()) % len(authors) 321 | if q == p { 322 | p = (p + 1) % len(authors) 323 | } 324 | m.Answer[q], m.Answer[p] = m.Answer[p], m.Answer[q] 325 | } 326 | return 327 | case "version.bind.": 328 | fallthrough 329 | case "version.server.": 330 | hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} 331 | m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{Version}}} 332 | return 333 | case "hostname.bind.": 334 | fallthrough 335 | case "id.server.": 336 | // TODO(miek): machine name to return 337 | hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} 338 | m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{"localhost"}}} 339 | return 340 | } 341 | } 342 | // still here, fail 343 | m.SetReply(req) 344 | m.SetRcode(req, dns.RcodeServerFailure) 345 | return 346 | } 347 | 348 | switch q.Qtype { 349 | case dns.TypeNS: 350 | if name != s.config.Domain { 351 | break 352 | } 353 | // Lookup s.config.DnsDomain 354 | records, extra, err := s.NSRecords(q, s.config.dnsDomain) 355 | if isEtcdNameError(err, s) { 356 | m = s.NameError(req) 357 | return 358 | } 359 | m.Answer = append(m.Answer, records...) 360 | m.Extra = append(m.Extra, extra...) 361 | case dns.TypeA, dns.TypeAAAA: 362 | records, err := s.AddressRecords(q, name, nil, bufsize, dnssec, false) 363 | if isEtcdNameError(err, s) { 364 | m = s.NameError(req) 365 | return 366 | } 367 | m.Answer = append(m.Answer, records...) 368 | case dns.TypeTXT: 369 | records, err := s.TXTRecords(q, name) 370 | if isEtcdNameError(err, s) { 371 | m = s.NameError(req) 372 | return 373 | } 374 | m.Answer = append(m.Answer, records...) 375 | case dns.TypeCNAME: 376 | records, err := s.CNAMERecords(q, name) 377 | if isEtcdNameError(err, s) { 378 | m = s.NameError(req) 379 | return 380 | } 381 | m.Answer = append(m.Answer, records...) 382 | case dns.TypeMX: 383 | records, extra, err := s.MXRecords(q, name, bufsize, dnssec) 384 | if isEtcdNameError(err, s) { 385 | m = s.NameError(req) 386 | return 387 | } 388 | m.Answer = append(m.Answer, records...) 389 | m.Extra = append(m.Extra, extra...) 390 | default: 391 | fallthrough // also catch other types, so that they return NODATA 392 | case dns.TypeSRV: 393 | records, extra, err := s.SRVRecords(q, name, bufsize, dnssec) 394 | if err != nil { 395 | if isEtcdNameError(err, s) { 396 | m = s.NameError(req) 397 | return 398 | } 399 | logf("got error from backend: %s", err) 400 | if q.Qtype == dns.TypeSRV { // Otherwise NODATA 401 | m = s.ServerFailure(req) 402 | return 403 | } 404 | } 405 | // if we are here again, check the types, because an answer may only 406 | // be given for SRV. All other types should return NODATA, the 407 | // NXDOMAIN part is handled in the above code. TODO(miek): yes this 408 | // can be done in a more elegant manor. 409 | if q.Qtype == dns.TypeSRV { 410 | m.Answer = append(m.Answer, records...) 411 | m.Extra = append(m.Extra, extra...) 412 | } 413 | } 414 | 415 | if len(m.Answer) == 0 { // NODATA response 416 | m.Ns = []dns.RR{s.NewSOA()} 417 | m.Ns[0].Header().Ttl = s.config.MinTtl 418 | } 419 | } 420 | 421 | func (s *server) AddressRecords(q dns.Question, name string, previousRecords []dns.RR, bufsize uint16, dnssec, both bool) (records []dns.RR, err error) { 422 | services, err := s.backend.Records(name, false) 423 | if err != nil { 424 | return nil, err 425 | } 426 | 427 | services = msg.Group(services) 428 | 429 | for _, serv := range services { 430 | ip := net.ParseIP(serv.Host) 431 | switch { 432 | case ip == nil: 433 | // Try to resolve as CNAME if it's not an IP, but only if we don't create loops. 434 | if q.Name == dns.Fqdn(serv.Host) { 435 | logf("CNAME loop detected: %q -> %q", q.Name, q.Name) 436 | // x CNAME x is a direct loop, don't add those 437 | continue 438 | } 439 | 440 | newRecord := serv.NewCNAME(q.Name, dns.Fqdn(serv.Host)) 441 | if len(previousRecords) > 7 { 442 | logf("CNAME lookup limit of 8 exceeded for %s", newRecord) 443 | // don't add it, and just continue 444 | continue 445 | } 446 | if s.isDuplicateCNAME(newRecord, previousRecords) { 447 | logf("CNAME loop detected for record %s", newRecord) 448 | continue 449 | } 450 | 451 | nextRecords, err := s.AddressRecords(dns.Question{Name: dns.Fqdn(serv.Host), Qtype: q.Qtype, Qclass: q.Qclass}, 452 | strings.ToLower(dns.Fqdn(serv.Host)), append(previousRecords, newRecord), bufsize, dnssec, both) 453 | if err == nil { 454 | // Only have we found something we should add the CNAME and the IP addresses. 455 | if len(nextRecords) > 0 { 456 | records = append(records, newRecord) 457 | records = append(records, nextRecords...) 458 | } 459 | continue 460 | } 461 | // This means we can not complete the CNAME, try to look else where. 462 | target := newRecord.Target 463 | if dns.IsSubDomain(s.config.Domain, target) { 464 | // We should already have found it 465 | continue 466 | } 467 | m1, e1 := s.Lookup(target, q.Qtype, bufsize, dnssec) 468 | if e1 != nil { 469 | logf("incomplete CNAME chain from %q: %s", target, e1) 470 | continue 471 | } 472 | // Len(m1.Answer) > 0 here is well? 473 | records = append(records, newRecord) 474 | records = append(records, m1.Answer...) 475 | continue 476 | case ip.To4() != nil && (q.Qtype == dns.TypeA || both): 477 | records = append(records, serv.NewA(q.Name, ip.To4())) 478 | case ip.To4() == nil && (q.Qtype == dns.TypeAAAA || both): 479 | records = append(records, serv.NewAAAA(q.Name, ip.To16())) 480 | } 481 | } 482 | s.RoundRobin(records) 483 | return records, nil 484 | } 485 | 486 | // NSRecords returns NS records from etcd. 487 | func (s *server) NSRecords(q dns.Question, name string) (records []dns.RR, extra []dns.RR, err error) { 488 | services, err := s.backend.Records(name, false) 489 | if err != nil { 490 | return nil, nil, err 491 | } 492 | 493 | services = msg.Group(services) 494 | 495 | for _, serv := range services { 496 | ip := net.ParseIP(serv.Host) 497 | switch { 498 | case ip == nil: 499 | return nil, nil, fmt.Errorf("NS record must be an IP address") 500 | case ip.To4() != nil: 501 | serv.Host = msg.Domain(serv.Key) 502 | records = append(records, serv.NewNS(q.Name, serv.Host)) 503 | extra = append(extra, serv.NewA(serv.Host, ip.To4())) 504 | case ip.To4() == nil: 505 | serv.Host = msg.Domain(serv.Key) 506 | records = append(records, serv.NewNS(q.Name, serv.Host)) 507 | extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) 508 | } 509 | } 510 | return records, extra, nil 511 | } 512 | 513 | // SRVRecords returns SRV records from etcd. 514 | // If the Target is not a name but an IP address, a name is created. 515 | func (s *server) SRVRecords(q dns.Question, name string, bufsize uint16, dnssec bool) (records []dns.RR, extra []dns.RR, err error) { 516 | services, err := s.backend.Records(name, false) 517 | if err != nil { 518 | return nil, nil, err 519 | } 520 | 521 | services = msg.Group(services) 522 | 523 | // Looping twice to get the right weight vs priority 524 | w := make(map[int]int) 525 | for _, serv := range services { 526 | weight := 100 527 | if serv.Weight != 0 { 528 | weight = serv.Weight 529 | } 530 | if _, ok := w[serv.Priority]; !ok { 531 | w[serv.Priority] = weight 532 | continue 533 | } 534 | w[serv.Priority] += weight 535 | } 536 | lookup := make(map[string]bool) 537 | for _, serv := range services { 538 | w1 := 100.0 / float64(w[serv.Priority]) 539 | if serv.Weight == 0 { 540 | w1 *= 100 541 | } else { 542 | w1 *= float64(serv.Weight) 543 | } 544 | weight := uint16(math.Floor(w1)) 545 | ip := net.ParseIP(serv.Host) 546 | switch { 547 | case ip == nil: 548 | srv := serv.NewSRV(q.Name, weight) 549 | records = append(records, srv) 550 | 551 | if _, ok := lookup[srv.Target]; ok { 552 | break 553 | } 554 | 555 | lookup[srv.Target] = true 556 | 557 | if !dns.IsSubDomain(s.config.Domain, srv.Target) { 558 | m1, e1 := s.Lookup(srv.Target, dns.TypeA, bufsize, dnssec) 559 | if e1 == nil { 560 | extra = append(extra, m1.Answer...) 561 | } 562 | m1, e1 = s.Lookup(srv.Target, dns.TypeAAAA, bufsize, dnssec) 563 | if e1 == nil { 564 | // If we have seen CNAME's we *assume* that they are already added. 565 | for _, a := range m1.Answer { 566 | if _, ok := a.(*dns.CNAME); !ok { 567 | extra = append(extra, a) 568 | } 569 | } 570 | } 571 | break 572 | } 573 | // Internal name, we should have some info on them, either v4 or v6 574 | // Clients expect a complete answer, because we are a recursor in their 575 | // view. 576 | addr, e1 := s.AddressRecords(dns.Question{srv.Target, dns.ClassINET, dns.TypeA}, 577 | srv.Target, nil, bufsize, dnssec, true) 578 | if e1 == nil { 579 | extra = append(extra, addr...) 580 | } 581 | case ip.To4() != nil: 582 | serv.Host = msg.Domain(serv.Key) 583 | srv := serv.NewSRV(q.Name, weight) 584 | 585 | records = append(records, srv) 586 | extra = append(extra, serv.NewA(srv.Target, ip.To4())) 587 | case ip.To4() == nil: 588 | serv.Host = msg.Domain(serv.Key) 589 | srv := serv.NewSRV(q.Name, weight) 590 | 591 | records = append(records, srv) 592 | extra = append(extra, serv.NewAAAA(srv.Target, ip.To16())) 593 | } 594 | } 595 | return records, extra, nil 596 | } 597 | 598 | // MXRecords returns MX records from etcd. 599 | // If the Target is not a name but an IP address, a name is created. 600 | func (s *server) MXRecords(q dns.Question, name string, bufsize uint16, dnssec bool) (records []dns.RR, extra []dns.RR, err error) { 601 | services, err := s.backend.Records(name, false) 602 | if err != nil { 603 | return nil, nil, err 604 | } 605 | 606 | lookup := make(map[string]bool) 607 | for _, serv := range services { 608 | if !serv.Mail { 609 | continue 610 | } 611 | ip := net.ParseIP(serv.Host) 612 | switch { 613 | case ip == nil: 614 | mx := serv.NewMX(q.Name) 615 | records = append(records, mx) 616 | if _, ok := lookup[mx.Mx]; ok { 617 | break 618 | } 619 | 620 | lookup[mx.Mx] = true 621 | 622 | if !dns.IsSubDomain(s.config.Domain, mx.Mx) { 623 | m1, e1 := s.Lookup(mx.Mx, dns.TypeA, bufsize, dnssec) 624 | if e1 == nil { 625 | extra = append(extra, m1.Answer...) 626 | } 627 | m1, e1 = s.Lookup(mx.Mx, dns.TypeAAAA, bufsize, dnssec) 628 | if e1 == nil { 629 | // If we have seen CNAME's we *assume* that they are already added. 630 | for _, a := range m1.Answer { 631 | if _, ok := a.(*dns.CNAME); !ok { 632 | extra = append(extra, a) 633 | } 634 | } 635 | } 636 | break 637 | } 638 | // Internal name 639 | addr, e1 := s.AddressRecords(dns.Question{mx.Mx, dns.ClassINET, dns.TypeA}, 640 | mx.Mx, nil, bufsize, dnssec, true) 641 | if e1 == nil { 642 | extra = append(extra, addr...) 643 | } 644 | case ip.To4() != nil: 645 | serv.Host = msg.Domain(serv.Key) 646 | records = append(records, serv.NewMX(q.Name)) 647 | extra = append(extra, serv.NewA(serv.Host, ip.To4())) 648 | case ip.To4() == nil: 649 | serv.Host = msg.Domain(serv.Key) 650 | records = append(records, serv.NewMX(q.Name)) 651 | extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) 652 | } 653 | } 654 | return records, extra, nil 655 | } 656 | 657 | func (s *server) CNAMERecords(q dns.Question, name string) (records []dns.RR, err error) { 658 | services, err := s.backend.Records(name, true) 659 | if err != nil { 660 | return nil, err 661 | } 662 | 663 | services = msg.Group(services) 664 | 665 | if len(services) > 0 { 666 | serv := services[0] 667 | if ip := net.ParseIP(serv.Host); ip == nil { 668 | records = append(records, serv.NewCNAME(q.Name, dns.Fqdn(serv.Host))) 669 | } 670 | } 671 | return records, nil 672 | } 673 | 674 | func (s *server) TXTRecords(q dns.Question, name string) (records []dns.RR, err error) { 675 | services, err := s.backend.Records(name, false) 676 | if err != nil { 677 | return nil, err 678 | } 679 | 680 | services = msg.Group(services) 681 | 682 | for _, serv := range services { 683 | if serv.Text == "" { 684 | continue 685 | } 686 | records = append(records, serv.NewTXT(q.Name)) 687 | } 688 | return records, nil 689 | } 690 | 691 | func (s *server) PTRRecords(q dns.Question) (records []dns.RR, err error) { 692 | name := strings.ToLower(q.Name) 693 | serv, err := s.backend.ReverseRecord(name) 694 | if err != nil { 695 | return nil, err 696 | } 697 | 698 | records = append(records, serv.NewPTR(q.Name, serv.Ttl)) 699 | return records, nil 700 | } 701 | 702 | // SOA returns a SOA record for this SkyDNS instance. 703 | func (s *server) NewSOA() dns.RR { 704 | return &dns.SOA{Hdr: dns.RR_Header{Name: s.config.Domain, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: s.config.Ttl}, 705 | Ns: appendDomain("ns.dns", s.config.Domain), 706 | Mbox: s.config.Hostmaster, 707 | Serial: uint32(time.Now().Truncate(time.Hour).Unix()), 708 | Refresh: 28800, 709 | Retry: 7200, 710 | Expire: 604800, 711 | Minttl: s.config.MinTtl, 712 | } 713 | } 714 | 715 | func (s *server) isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool { 716 | for _, rec := range records { 717 | if v, ok := rec.(*dns.CNAME); ok { 718 | if v.Target == r.Target { 719 | return true 720 | } 721 | } 722 | } 723 | return false 724 | } 725 | 726 | func (s *server) NameError(req *dns.Msg) *dns.Msg { 727 | m := new(dns.Msg) 728 | m.SetRcode(req, dns.RcodeNameError) 729 | m.Ns = []dns.RR{s.NewSOA()} 730 | m.Ns[0].Header().Ttl = s.config.MinTtl 731 | return m 732 | } 733 | 734 | func (s *server) ServerFailure(req *dns.Msg) *dns.Msg { 735 | m := new(dns.Msg) 736 | m.SetRcode(req, dns.RcodeServerFailure) 737 | return m 738 | } 739 | 740 | func (s *server) RoundRobin(rrs []dns.RR) { 741 | if !s.config.RoundRobin { 742 | return 743 | } 744 | // If we have more than 1 CNAME don't touch the packet, because some stub resolver (=glibc) 745 | // can't deal with the returned packet if the CNAMEs need to be accesses in the reverse order. 746 | cname := 0 747 | for _, r := range rrs { 748 | if r.Header().Rrtype == dns.TypeCNAME { 749 | cname++ 750 | if cname > 1 { 751 | return 752 | } 753 | } 754 | } 755 | 756 | switch l := len(rrs); l { 757 | case 2: 758 | if dns.Id()%2 == 0 { 759 | rrs[0], rrs[1] = rrs[1], rrs[0] 760 | } 761 | default: 762 | for j := 0; j < l*(int(dns.Id())%4+1); j++ { 763 | q := int(dns.Id()) % l 764 | p := int(dns.Id()) % l 765 | if q == p { 766 | p = (p + 1) % l 767 | } 768 | rrs[q], rrs[p] = rrs[p], rrs[q] 769 | } 770 | } 771 | 772 | } 773 | 774 | // dedup will de-duplicate a message on a per section basis. 775 | // Multiple identical (same name, class, type and rdata) RRs will be coalesced into one. 776 | func (s *server) dedup(m *dns.Msg) *dns.Msg { 777 | // Answer section 778 | ma := make(map[string]dns.RR) 779 | for _, a := range m.Answer { 780 | // Or use Pack()... Think this function also could be placed in go dns. 781 | s1 := a.Header().Name 782 | s1 += strconv.Itoa(int(a.Header().Class)) 783 | s1 += strconv.Itoa(int(a.Header().Rrtype)) 784 | // there can only be one CNAME for an ownername 785 | if a.Header().Rrtype == dns.TypeCNAME { 786 | if _, ok := ma[s1]; ok { 787 | // already exist, randomly overwrite if roundrobin is true 788 | // Note: even with roundrobin *off* this depends on the 789 | // order we get the names. 790 | if s.config.RoundRobin && dns.Id()%2 == 0 { 791 | ma[s1] = a 792 | continue 793 | } 794 | } 795 | ma[s1] = a 796 | continue 797 | } 798 | for i := 1; i <= dns.NumField(a); i++ { 799 | s1 += dns.Field(a, i) 800 | } 801 | ma[s1] = a 802 | } 803 | // Only is our map is smaller than the #RR in the answer section we should reset the RRs 804 | // in the section it self 805 | if len(ma) < len(m.Answer) { 806 | i := 0 807 | for _, v := range ma { 808 | m.Answer[i] = v 809 | i++ 810 | } 811 | m.Answer = m.Answer[:len(ma)] 812 | } 813 | 814 | // Additional section 815 | me := make(map[string]dns.RR) 816 | for _, e := range m.Extra { 817 | s1 := e.Header().Name 818 | s1 += strconv.Itoa(int(e.Header().Class)) 819 | s1 += strconv.Itoa(int(e.Header().Rrtype)) 820 | // there can only be one CNAME for an ownername 821 | if e.Header().Rrtype == dns.TypeCNAME { 822 | if _, ok := me[s1]; ok { 823 | // already exist, randomly overwrite if roundrobin is true 824 | if s.config.RoundRobin && dns.Id()%2 == 0 { 825 | me[s1] = e 826 | continue 827 | } 828 | } 829 | me[s1] = e 830 | continue 831 | } 832 | for i := 1; i <= dns.NumField(e); i++ { 833 | s1 += dns.Field(e, i) 834 | } 835 | me[s1] = e 836 | } 837 | 838 | if len(me) < len(m.Extra) { 839 | i := 0 840 | for _, v := range me { 841 | m.Extra[i] = v 842 | i++ 843 | } 844 | m.Extra = m.Extra[:len(me)] 845 | } 846 | 847 | return m 848 | } 849 | 850 | // overflowOrTruncated writes back an error to the client if the message does not fit. 851 | // It updates prometheus metrics. If something has been written to the client, true 852 | // will be returned. 853 | func (s *server) overflowOrTruncated(w dns.ResponseWriter, m *dns.Msg, bufsize int, sy metrics.System) bool { 854 | switch isTCP(w) { 855 | case true: 856 | if _, overflow := Fit(m, dns.MaxMsgSize, true); overflow { 857 | metrics.ReportErrorCount(m, sy) 858 | msgFail := s.ServerFailure(m) 859 | w.WriteMsg(msgFail) 860 | return true 861 | } 862 | case false: 863 | // Overflow with udp always results in TC. 864 | Fit(m, bufsize, false) 865 | metrics.ReportErrorCount(m, sy) 866 | if m.Truncated { 867 | w.WriteMsg(m) 868 | return true 869 | } 870 | } 871 | return false 872 | } 873 | 874 | // isTCP returns true if the client is connecting over TCP. 875 | func isTCP(w dns.ResponseWriter) bool { 876 | _, ok := w.RemoteAddr().(*net.TCPAddr) 877 | return ok 878 | } 879 | 880 | // etcNameError return a NameError to the client if the error 881 | // returned from etcd has ErrorCode == 100. 882 | func isEtcdNameError(err error, s *server) bool { 883 | if e, ok := err.(etcd.Error); ok && e.Code == etcd.ErrorCodeKeyNotFound { 884 | return true 885 | } 886 | if err != nil { 887 | logf("error from backend: %s", err) 888 | } 889 | return false 890 | } 891 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SkyDNS [![Build Status](https://travis-ci.org/skynetservices/skydns.png?branch=master)](https://travis-ci.org/skynetservices/skydns) 2 | *Version 2.5.3a* 3 | 4 | SkyDNS is a distributed service for announcement and discovery of services built 5 | on top of [etcd](https://github.com/coreos/etcd). It utilizes DNS queries to 6 | discover available services. This is done by leveraging SRV records in DNS, with 7 | special meaning given to subdomains, priorities and weights. 8 | 9 | This is the original [announcement blog 10 | post](http://blog.gopheracademy.com/skydns) for version 1. Since then, SkyDNS 11 | has seen some changes, most notably the ability to use etcd as a backend. 12 | [Here you can find the SkyDNS2 announcement](http://miek.nl/posts/2014/Jun/08/announcing%20SkyDNS%20version%202/). 13 | 14 | 15 | # Changes since version 1 16 | 17 | SkyDNS2: 18 | 19 | * Does away with Raft and uses etcd (which uses raft). 20 | * Makes it possible to query arbitrary domain names. 21 | * Is a thin layer above etcd, that translates etcd keys and values to the DNS. 22 | * Does DNSSEC with NSEC3 instead of NSEC. 23 | 24 | Note that bugs in SkyDNS1 will still be fixed, but the main development effort 25 | will be focussed on version 2. [Version 1 of SkyDNS can be found 26 | here](https://github.com/skynetservices/skydns1). 27 | 28 | 29 | ## Setup / Install 30 | 31 | Download/compile and run etcd. See the documentation for etcd at . 32 | 33 | Then get and compile SkyDNS: 34 | 35 | go get github.com/skynetservices/skydns 36 | cd $GOPATH/src/github.com/skynetservices/skydns 37 | go build -v 38 | 39 | SkyDNS' configuration is stored *in* etcd: but there are also flags and 40 | environment variables you can set. To start SkyDNS, set the etcd machines with 41 | the environment variable ETCD_MACHINES: 42 | 43 | export ETCD_MACHINES='http://192.168.0.1:4001,http://192.168.0.2:4001' 44 | ./skydns 45 | 46 | If `ETCD_MACHINES` is not set, SkyDNS will default to using 47 | `http://127.0.0.1:4001` to connect to etcd. Or you can use the flag `-machines`. 48 | Auto-discovering new machines added to the network can be enabled by enabling 49 | the flag `-discover`. 50 | 51 | Optionally (but recommended) give it a nameserver: 52 | 53 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/dns/ns/ns1 \ 54 | -d value='{"host":"192.168.0.1"}' 55 | 56 | Also see the section "NS Records". 57 | 58 | 59 | ## Configuration 60 | 61 | SkyDNS' configuration is stored in etcd as a JSON object under the key 62 | `/skydns/config`. The following parameters may be set: 63 | 64 | * `dns_addr`: IP:port on which SkyDNS should listen, defaults to `127.0.0.1:53`. 65 | * `domain`: domain for which SkyDNS is authoritative, defaults to `skydns.local.`. 66 | * `dnssec`: enable DNSSEC 67 | * `hostmaster`: hostmaster email address to use. 68 | * `local`: optional unique value for this skydns instance, default is none. This is returned 69 | when queried for `local.dns.skydns.local`. 70 | * `round_robin`: enable round-robin sorting for A and AAAA responses, defaults to true. 71 | Note that packets containing more than one CNAME are exempt from this (see issue #128 on Github). 72 | * `nameservers`: forward DNS requests to these (recursive) nameservers (array of IP:port combination), 73 | when not authoritative for a domain. This defaults to the servers listed in `/etc/resolv.conf`. Also 74 | see `no_rec`. 75 | * `no_rec`: never (ever) provide a recursive service (i.e. forward to the servers provided in -nameservers). 76 | * `read_timeout`: network read timeout, for DNS and talking with etcd. 77 | * `ttl`: default TTL in seconds to use on replies when none is set in etcd, defaults to 3600. 78 | * `min_ttl`: minimum TTL in seconds to use on NXDOMAIN, defaults to 30. 79 | * `scache`: the capacity of the DNSSEC signature cache, defaults to 10000 signatures if not set. 80 | * `rcache`: the capacity of the response cache, defaults to 0 messages if not set. 81 | * `rcache_ttl`: the TTL of the response cache, defaults to 60 if not set. 82 | * `ndots`: how many labels a name should have before we allow forwarding. Default to 2. 83 | * `systemd`: bind to socket(s) activated by systemd (ignores -addr). 84 | * `path-prefix`: backend(etcd) path prefix, defaults to skydns (i.e. if it is set to `mydns`, the SkyDNS's configuration object should be stored under the key `/mydns/config`). 85 | * `etcd3`: flag that toggles the etcd version 3 support by skydns during runtime. Defaults to false. 86 | 87 | To set the configuration, use something like: 88 | 89 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/config \ 90 | -d value='{"dns_addr":"127.0.0.1:5354","ttl":3600, "nameservers": ["8.8.8.8:53","8.8.4.4:53"]}' 91 | 92 | SkyDNS needs to be restarted for configuration changes to take effect. This 93 | might change, so that SkyDNS can re-read the config from etcd after a HUP 94 | signal. 95 | 96 | You can also use the command line options, however the settings in etcd take 97 | precedence. 98 | 99 | 100 | ### Commandline flags 101 | 102 | * `-addr`: used to specify the address to listen on (note: this will be changed into `-dns_addr` to match the json. 103 | * `-local`: used to specify a unique service for this SkyDNS instance. This should point to a (unique) domain into etcd, when 104 | SkyDNS receives a query for the name `local.dns.skydns.local` it will fetch this service and return it. 105 | For instance: `-local e2016c14-fbba-11e3-ae08-10604b7efbe2.dockerhosts.skydns.local` and then 106 | 107 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/dockerhosts/e2016c14-fbba-11e3-ae08-10604b7efbe2 \ 108 | -d value='{"host":"10.1.1.16"}' 109 | 110 | To register the local IP address. Now when SkyDNS receives a query for local.dns.skydns.local it will fetch the above 111 | key and returns that one service. In other words skydns will substitute `e2016c14-fbba-11e3-ae08-10604b7efbe2.dockerhosts.skydns.local` 112 | for `local.dns.skydns.local`. This follows the same rules as the other services, so it can also be an external names, which 113 | will be resolved. 114 | 115 | Also see the section Host Local Values. 116 | 117 | 118 | ### Environment Variables 119 | 120 | SkyDNS uses these environment variables: 121 | 122 | * `ETCD_MACHINES` - list of etcd machines, "http://localhost:4001,http://etcd.example.com:4001". Overwrite with `-machines` string flag. 123 | * `ETCD_TLSKEY` - path of TLS client certificate - private key. Overwrite with `-tls-key` string flag. 124 | * `ETCD_TLSPEM` - path of TLS client certificate - public key. Overwrite with `-tls-pem` string flag. 125 | * `ETCD_CACERT` - path of TLS certificate authority public key. Overwrite with `-ca-cert` string flag. 126 | * `ETCD_USERNAME` - username used for basic auth. Overwrite with `-username` string flag. 127 | * `ETCD_PASSWORD` - password used for basic auth. Overwrite with `-password` string flag. 128 | * `SKYDNS_ADDR` - specify address to bind to. Overwrite with `-addr` string flag. 129 | * `SKYDNS_DOMAIN` - set a default domain if not specified by etcd config. Overwrite with `-domain` string flag. 130 | * `SKYDNS_NAMESERVERS` - set a list of nameservers to forward DNS requests to 131 | when not authoritative for a domain, "8.8.8.8:53,8.8.4.4:53". Overwrite with `-nameservers` string flag. 132 | * `SKYDNS_PATH_PREFIX` - backend(etcd) path prefix, defaults to skydns (i.e. if it is set to `mydns`, the SkyDNS's configuration object should be stored under the key `/mydns/config`). Overwrite with `-path-prefix` string flag. 133 | * `SKYDNS_SYSTEMD`: set to `true` to bind to socket(s) activated by systemd (ignores SKYDNS_ADDR). Overwrite with `-systemd` bool flag. 134 | * `SKYDNS_NDOTS`: how many labels a name should have before we allow forwarding. Default to 2. 135 | 136 | For [Prometheus](http://prometheus.io/) the following environment variables 137 | are available: 138 | 139 | * `PROMETHEUS_PORT`: port where the HTTP server for prometheus will run. 140 | * `PROMETHEUS_PATH`: path for the metrics, defaults to `/metrics`. 141 | * `PROMETHEUS_NAMESPACE`: namespace used in the metrics, no default. 142 | * `PROMETHEUS_SUBSYSTEM`: subsystem used in the metric, defaults to `skydns`. 143 | 144 | If `PROMETHEUS_PORT` is set to an integer larger than 0, Prometheus support will 145 | be enabled. 146 | 147 | Current counters are: 148 | 149 | * `dns_request_count_total`, total count of request made against SkyDNS. 150 | * `dns_request_duration_seconds`, duration of the request handling in seconds. 151 | * `dns_response_size_bytes`, size of the repsonses in bytes. 152 | * `dns_error_count_total`, total count of responses containing errors. 153 | * `dns_cachemiss_count_total`, total count of cache misses. 154 | 155 | ### SSL Usage and Authentication with Client Certificates 156 | 157 | In order to connect to an SSL-secured etcd, you will at least need to set 158 | ETCD_CACERT to be the public key of the Certificate Authority which signed the 159 | server certificate. 160 | 161 | If the SSL-secured etcd expects client certificates to authorize connections, 162 | you also need to set ETCD_TLSKEY to the *private* key of the client, and 163 | ETCD_TLSPEM to the *public* key of the client. 164 | 165 | 166 | ## Service Announcements 167 | 168 | Announce your service by submitting JSON over HTTP to etcd with information 169 | about your service. This information will then be available for queries via DNS. 170 | We use the directory `/skydns` to anchor all names. 171 | 172 | When providing information you will need to fill out (some of) the following 173 | values. 174 | 175 | * Path - The path of the key in etcd, e.g. if the domain you want to 176 | register is "rails.production.east.skydns.local", you need to reverse it 177 | and replace the dots with slashes. So the name here becomes: 178 | `local/skydns/east/production/rails`. 179 | Then prefix the `/skydns/` string too, so the final path becomes 180 | `/v2/keys/skydns/local/skydns/east/production/rails` 181 | * Host - The name of your service, e.g., `service5.mydomain.com` or an IP address (either v4 or v6); 182 | * Port - the port where the service can be reached; 183 | * Priority - the priority of the service, the lower the value, the more preferred; 184 | * Weight - a weight factor that will be used for services with the same Priority; 185 | * Text - text you want to add (this returned when doing a TXT query); 186 | * TTL - the time-to-live of the service, overriding the default TTL. If the etcd 187 | key also has a TTL, the minimum of this value and the etcd TTL is used. 188 | * TargetStrip - when synthesising a name for an IP only SRV record, take the path 189 | name and strip `TargetStrip` labels from the ride hand side. 190 | * Group - limit recursion and only return services that share the Group's value. 191 | 192 | Path is the only mandatory field. The lookups into Etcd will be done with 193 | a *lower* cased path name. 194 | 195 | Adding the service can thus be done with: 196 | 197 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/production/rails \ 198 | -d value='{"host":"service5.example.com","priority":20}' 199 | 200 | Or with [`etcdctl`](https://github.com/coreos/etcdctl): 201 | 202 | etcdctl set /skydns/local/skydns/east/production/rails \ 203 | '{"host":"service5.example.com","priority":20}' 204 | 205 | When doing a SRV query for these keys an SRV record is returned with the 206 | priority and a certain weight. The weight of a service is calculated as follows. 207 | We treat weight as a percentage, so if there are 208 | 3 services, the weight is set to 33 for each: 209 | 210 | | Service | Weight | SRV.Weight | 211 | | --------| ------- | ---------- | 212 | | a | 100 | 33 | 213 | | b | 100 | 33 | 214 | | c | 100 | 33 | 215 | 216 | If we add other weights to the equation some services will get a different 217 | Weight: 218 | 219 | | Service | Weight | SRV.Weight | 220 | | --------| ------- | ---------- | 221 | | a | 120 | 34 | 222 | | b | 100 | 28 | 223 | | c | 130 | 37 | 224 | 225 | Note, all calculations are rounded down, so the sum total might be lower than 226 | 100. 227 | 228 | When querying the DNS for services you can use wildcards or query for 229 | subdomains. See the section named "Wildcards" below for more information. 230 | 231 | 232 | ## Service Discovery via the DNS 233 | 234 | You can find services by querying SkyDNS via any DNS client or utility. It uses 235 | a known domain syntax with subdomains to find matching services. 236 | 237 | For the purpose of this document, let's suppose we have added the following 238 | services to etcd: 239 | 240 | * 1.rails.production.east.skydns.local, mapping to service1.example.com 241 | * 2.rails.production.west.skydns.local, mapping to service2.example.com 242 | * 4.rails.staging.east.skydns.local, mapping to 10.0.1.125 243 | * 6.rails.staging.east.skydns.local, mapping to 2003::8:1 244 | 245 | These names can be added with: 246 | 247 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/production/rails/1 \ 248 | -d value='{"host":"service1.example.com","port":8080}' 249 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/west/production/rails/2 \ 250 | -d value='{"host":"service2.example.com","port":8080}' 251 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/staging/rails/4 \ 252 | -d value='{"host":"10.0.1.125","port":8080}' 253 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/staging/rails/6 \ 254 | -d value='{"host":"2003::8:1","port":8080}' 255 | 256 | Testing one of the names with `dig`: 257 | 258 | % dig @localhost SRV 1.rails.production.east.skydns.local 259 | 260 | ;; ANSWER SECTION: 261 | 1.rails.production.east.skydns.local. 3600 IN SRV 10 0 8080 service1.example.com. 262 | 263 | 264 | ### Wildcards 265 | 266 | Of course using the full names isn't *that* useful, so SkyDNS lets you query for 267 | subdomains, and returns responses based upon the amount of services matched by 268 | the subdomain or from the wildcard query. 269 | 270 | If we are interested in all the servers in the `east` region, we simply omit the 271 | rightmost labels from our query: 272 | 273 | % dig @localhost SRV east.skydns.local 274 | 275 | ;; ANSWER SECTION: 276 | east.skydns.local. 3600 IN SRV 10 20 8080 service1.example.com. 277 | east.skydns.local. 3600 IN SRV 10 20 8080 4.rails.staging.east.skydns.local. 278 | east.skydns.local. 3600 IN SRV 10 20 8080 6.rails.staging.east.skydns.local. 279 | 280 | ;; ADDITIONAL SECTION: 281 | 4.rails.staging.east.skydns.local. 3600 IN A 10.0.1.125 282 | 6.rails.staging.east.skydns.local. 3600 IN AAAA 2003::8:1 283 | 284 | Here all three entries of the `east` are returned. 285 | 286 | There is one other feature at play here. The second and third names, 287 | `{4,6}.rails.staging.east.skydns.local`, only had an IP record configured. Here 288 | SkyDNS used the etcd path (also see `TargetStrip`) to 289 | construct a target name and then puts the actual IP address in the additional 290 | section. Directly querying for the A records of 291 | `4.rails.staging.east.skydns.local.` of course also works: 292 | 293 | % dig @localhost -p 5354 +noall +answer A 4.rails.staging.east.skydns.local. 294 | 295 | 4.rails.staging.east.skydns.local. 3600 IN A 10.0.1.125 296 | 297 | Another way to leads to the same result it to query for `*.east.skydns.local`, 298 | you even put the wildcard (the `*` or `any`) in the middle of a name 299 | `staging.*.skydns.local` or `staging.any.skydns.local` is a valid query, which 300 | returns all name in staging, regardless of the region. Multiple wildcards per 301 | name are also permitted. 302 | 303 | Note that `any` is synonymous for a `*`, as shown above. 304 | 305 | 306 | ### Examples 307 | 308 | Now we can try some of our example DNS lookups: 309 | 310 | 311 | #### SRV Records 312 | 313 | Get all Services in staging.east: 314 | 315 | % dig @localhost staging.east.skydns.local. SRV 316 | 317 | ;; ANSWER SECTION: 318 | staging.east.skydns.local. 3600 IN SRV 10 50 8080 4.rails.staging.east.skydns.local. 319 | staging.east.skydns.local. 3600 IN SRV 10 50 8080 6.rails.staging.east.skydns.local. 320 | 321 | ;; ADDITIONAL SECTION: 322 | 4.rails.staging.east.skydns.local. 3600 IN A 10.0.1.125 323 | 6.rails.staging.east.skydns.local. 3600 IN AAAA 2003::8:1 324 | 325 | If you ask for a service who's Host value is an IP address you would (in theory) get 326 | back a SRV record such as: 327 | 328 | % dig @localhost 4.rails.staging.east.skydns.local SRV 329 | 330 | ;; ANSWER SECTION: 331 | 4.rails.staging.east.skydns.local 3600 IN SRV 10 100 8080 10.0.1.125 332 | 333 | Where the target of the SRV is an IP address. This is not how SRV records work. 334 | SkyDNS will in this case synthesize a domain name and add the actual IP 335 | address to the additional section of the response: 336 | 337 | % dig @localhost 4.rails.staging.east.skydns.local SRV 338 | 339 | ;; ANSWER SECTION: 340 | 4.rails.staging.east.skydns.local 3600 IN SRV 10 100 4.rails.staging.east.skydns.local. 341 | 342 | ;; ADDITIONAL SECTION: 343 | 4.rails.staging.east.skydns.local. 3600 IN A 10.0.1.125 344 | 345 | Which conveys the same information and is legal in the DNS. To have some control on how 346 | the target names look you can register a service with `TargetStrip` set to a non-zero 347 | value. Setting TargetStrip to "2" strips 2 labels from the generated target name: 348 | 349 | ;; ANSWER SECTION: 350 | 4.rails.staging.east.skydns.local 3600 IN SRV 10 100 staging.east.skydns.local. 351 | 352 | ;; ADDITIONAL SECTION: 353 | staging.east.skydns.local. 3600 IN A 10.0.1.125 354 | 355 | Which removed the `4.rails` from the target name. 356 | 357 | #### A/AAAA Records 358 | To return A records, simply run a normal DNS query for a service matching the 359 | above patterns. 360 | 361 | Now do a normal DNS query: 362 | 363 | % dig @localhost staging.east.skydns.local. A 364 | 365 | ;; ANSWER SECTION: 366 | staging.east.skydns.local. 3600 IN A 10.0.1.125 367 | 368 | Now you have a list of all known IP Addresses registered running in staging in 369 | the east area. 370 | 371 | Because we're returning A records and not SRV records, there are no ports 372 | listed, so this is only useful when you're querying for services running on 373 | ports known to you in advance. 374 | 375 | 376 | #### MX Records 377 | 378 | If a service is added with `"mail": true` it is *also* an MX record, the Priority 379 | doubles as the MX's Preference. 380 | 381 | 382 | #### CNAME Records 383 | 384 | If for an A or AAAA query the IP address can not be parsed, SkyDNS will try to 385 | see if there is a chain of names that will lead to an IP address. The chain can 386 | not be longer than 8. So for instance if the following services have been 387 | registered: 388 | 389 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/production/rails/1 \ 390 | -d value='{"host":"service1.skydns.local","port":8080}' 391 | 392 | and 393 | 394 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/service1 \ 395 | -d value='{"host":"10.0.2.15","port":8080}' 396 | 397 | We have created the following CNAME chain: 398 | `1.rails.production.east.skydns.local` -> `service1.skydns.local` -> 399 | `10.0.2.15`. If you then query for an A or AAAA for 400 | 1.rails.production.east.skydns.local SkyDNS returns: 401 | 402 | 1.rails.production.east.skydns.local. 3600 IN CNAME service1.skydns.local. 403 | service1.skydns.local. 3600 IN A 10.0.2.15 404 | 405 | 406 | ##### External Names 407 | 408 | If the CNAME chains leads to a name that falls outside of the domain (i.e. does 409 | not end with `skydns.local.`), a.k.a. an external name, SkyDNS will attempt to 410 | resolve that name using the supplied nameservers. If this succeeds the reply is 411 | concatenated to the current one and send to the client. So if we register this 412 | service: 413 | 414 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/east/production/rails/1 \ 415 | -d value='{"host":"www.miek.nl","port":8080}' 416 | 417 | Doing an A/AAAA query for this will lead to the following response: 418 | 419 | 1.rails.production.east.skydns.local. 3600 IN CNAME www.miek.nl. 420 | www.miek.nl. 3600 IN CNAME a.miek.nl. 421 | a.miek.nl. 3600 IN A 176.58.119.54 422 | 423 | The first CNAME is generated from within SkyDNS, the other CNAME is returned 424 | from the remote name server. 425 | 426 | 427 | #### TXT Records 428 | 429 | SkyDNS also allows you to query for TXT records. Just register a json with the 430 | 'text' field set. 431 | 432 | 433 | #### NS Records 434 | 435 | For DNS to work properly SkyDNS needs to tell its parents its nameservers. This 436 | information is stored inside etcd, under the key `local/skydns/dns/ns`. There 437 | multiple services maybe stored. Note these services MUST use IP address, using 438 | names will not work. For instance: 439 | 440 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/dns/ns/ns1 \ 441 | -d value='{"host":"172.16.0.1"}' 442 | 443 | Registers `ns1.ns.dns.skydns.local` as a nameserver with IP address 172.16.0.1: 444 | 445 | % dig @localhost NS skydns.local 446 | 447 | ;; ANSWER SECTION: 448 | skydns.local. 3600 IN NS ns1.ns.dns.skydns.local. 449 | 450 | ;; ADDITIONAL SECTION: 451 | n1.ns.dns.skydns.local. 3600 IN A 172.16.0.1 452 | 453 | Having the nameserver(s) in etcd make sense because usually it is hard for 454 | SkyDNS to figure this out by itself, especially when running behind NAT or 455 | running on 127.0.0.1:53 and being forwarded packets IPv6 packets, etc. etc. 456 | 457 | 458 | #### PTR Records: Reverse Addresses 459 | 460 | When registering a service with an IP address only, you might also want to 461 | register the reverse (adding a hostname the address points to). In the DNS these 462 | records are called PTR records. 463 | 464 | So looking back at some of the services in the section "Service Discovery via 465 | the DNS", we register these IP only ones: 466 | 467 | 4.rails.staging.east.skydns.local. 10.0.1.125 468 | 469 | To add the reverse of this address you need to add the DNS name that will be used 470 | when doing a reverse lookup. With `dig -x ` you can easiliy find 471 | what the reverse name should be: 472 | 473 | % dig -x 10.0.1.125 +noall +question 474 | ;125.1.0.10.in-addr.arpa. IN PTR 475 | 476 | So the name must be `125.1.0.10.in-addr.arpa` which should point to 477 | `4.rails.staging.east.skydns.local`. 478 | 479 | These can be added with the following command. Note that the IP address is 480 | reversed *again* and is actually back in its original form. 481 | 482 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/arpa/in-addr/10/0/1/125 \ 483 | -d value='{"host":"4.rails.staging.east.skydns.local"}' 484 | 485 | If SkyDNS receives a PTR query it will check these paths and will return the 486 | contents. Note that these replies are sent with the AA (Authoritative Answer) 487 | bit *off*. If nothing is found locally the query is forwarded to the local 488 | recursor (if so configured), otherwise SERVFAIL is returned. 489 | 490 | This also works for IPv6 addresses, except that the reverse path is quite long. 491 | 492 | 493 | #### DNS Forwarding 494 | 495 | By specifying nameservers in SkyDNS's config, for instance 496 | `8.8.8.8:53,8.8.4.4:53`, you create a DNS forwarding proxy. In this case it 497 | round-robins between the two nameserver IPs mentioned. 498 | 499 | Requests for which SkyDNS isn't authoritative will be forwarded and proxied back 500 | to the client. This means that you can set SkyDNS as the primary DNS server in 501 | `/etc/resolv.conf` and use it for both service discovery and normal DNS 502 | operations. 503 | 504 | 505 | #### DNSSEC 506 | 507 | SkyDNS supports signing DNS answers, also known as DNSSEC. To use it, you need 508 | to create a DNSSEC keypair and use that in SkyDNS. For instance, if the domain 509 | for SkyDNS is `skydns.local`: 510 | 511 | % dnssec-keygen skydns.local 512 | Generating key pair............++++++ ...................................++++++ 513 | Kskydns.local.+005+49860 514 | 515 | This creates two files with the basename `Kskydns.local.+005.49860`, one with 516 | the extension `.key` (this holds the public key) and one with the extension 517 | `.private` which holds the private key. The basename of these files should be 518 | given to SkyDNS's DNSSEC configuration option like so (together with some other 519 | options): 520 | 521 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/config -d \ 522 | value='{"dns_addr":"127.0.0.1:5354","dnssec":"Kskydns.local.+005+55656"}' 523 | 524 | If you then query with `dig +dnssec` you will get signatures, keys and NSEC3 525 | records returned. Authenticated denial of existence is implemented using NSEC3 526 | white lies, see [RFC7129](http://tools.ietf.org/html/rfc7129), Appendix B. 527 | 528 | 529 | #### Host Local Values 530 | 531 | SkyDNS supports storing values which are specific for that *instance* of SkyDNS. 532 | 533 | This can be useful when you have SkyDNS running on multiple hosts, but want to 534 | store values that are specific for a single host. For example the public 535 | IP-address of the host or the IP-address on the tenant network. 536 | 537 | To do that you need to specify a unique value for that host with `-local`. 538 | A good unique value for that would be an UUID which you can generate with 539 | `uuidgen` for instance. 540 | 541 | That unique value is used as a path in etcd to store the values separately from 542 | the normal values. It is still stored in the etcd backend so a restart of SkyDNS 543 | with the same unique value will give it access to the old data. 544 | 545 | In the example here, we don't use an UUID, we use `public.addresses`: 546 | 547 | % skydns -local public.addresses.skydns.local & 548 | 549 | % curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/local/addresses/public \ 550 | -d value='{"host":"192.0.2.1"}' 551 | 552 | % dig @127.0.0.1 local.dns.skydns.local. A 553 | 554 | ;; ANSWER SECTION: 555 | local.dns.skydns.local. 3600 IN A 192.0.2.1 556 | 557 | The name `local.dns.skydns.local.` is fixed, i.e. you can retrieve the Host 558 | Local Value by querying for `local.dns.`. 559 | 560 | 561 | #### Groups 562 | 563 | Groups can be used to group set of services together. The main use of this is to 564 | limit recursion, i.e. don't give back *all* records, but only a subset. Say that 565 | I have configuration like this: 566 | 567 | /skydns/local/domain/ 568 | /skydns/local/domain/a - {"host": "127.0.0.1", "group": "g1"} 569 | /skydns/local/domain/b - {"host": "127.0.0.2", "group": "g1"} 570 | /skydns/local/domain/subdom/ 571 | /skydns/local/domain/subdom/c - {"host": "127.0.0.3", "group": "g2"} 572 | /skydns/local/domain/subdom/d - {"host": "127.0.0.4", "group": "g2"} 573 | 574 | And you want `domain.local` to return (127.0.0.1 and 127.0.0.2) and 575 | `subdom.domain.local` to return (127.0.0.3 and 127.0.0.4). For this the two 576 | domains, need to be in different groups. What those groups are does not matter, 577 | as long as `a` and `b` belong to the same group which is *different* from the 578 | group `c` and `d` belong to. If a service is found *without* a group it is 579 | *always included*. 580 | 581 | 582 | ## Implementing a custom DNS backend 583 | 584 | The SkyDNS `server` package may be used as a library, which allows a custom 585 | record retrieval implementation (referred to as a `Backend`) to be provided. The 586 | default Etcd implementation resides under `backends/etcd/etcd.go`. To provide 587 | your own backend implementation, you must implement the `server.Backend` 588 | interface. 589 | 590 | If you want to preserve the ability to answer arbitrary queries from etcd, but use 591 | your custom implementation for certain subsets of the namespace, the 592 | `server.FirstBackend` helper type will allow you to chain multiple `Backends` in 593 | order. The first backend that answers a `Records` or `ReverseRecord` call with 594 | a record and with no error will be served. 595 | 596 | 597 | ## Stub Zones 598 | 599 | Stub Zones are pointers that point to *another set* of servers which should 600 | provide an answer for the current query. This is similar to the (recursive) 601 | forwarding SkyDNS does, but different in that you need to specify a domain name 602 | and a set of authoritative servers. Also this can be dynamically controlled by 603 | writing values into Etcd. Note, that when enabled SkyDNS will *first* consult 604 | the stub configuration, potentially bypassing any configured local records. 605 | 606 | The stub zone configuration lives under `stub.dns.skydns.local.`. The following 607 | example shows on how to set this up. Suppose we want to create a stub zone for 608 | `skydns.com` and point to the nameservers reachable by following address *and* 609 | (optional) ports: 610 | 611 | * 172.16.1.1, port 54 612 | * 10.10.244.1, port 53 (53 is the default that will be used if there isn't one 613 | specified) 614 | 615 | We should then register 2 services under the name `skydns.com.stub.dns.skydns.local` 616 | 617 | % curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/dns/stub/com/skydns/ns1 \ 618 | -d value='{"host":"172.16.1.1", "port":54}' 619 | % curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/dns/stub/com/skydns/ns2 \ 620 | -d value='{"host":"10.10.244.1"}' 621 | 622 | So the *leaves* should have the nameserver information. 623 | 624 | xxx..stub.dns.skydns.local 625 | | | 626 | v | 627 | nameservers | 628 | v 629 | stub domain name 630 | 631 | When SkyDNS receives a query for `skydns.com` it will *not* forward it to the 632 | recursors, but instead will query 172.16.1.1 on port 54 and if that fails will 633 | query 10.10.244.1 (on 53) to get an answer. That answer will then be given back 634 | to the original client. 635 | 636 | When forwarding to a stub, SkyDNS adds a EDNS0 meta data RR to the packet 637 | telling the remote server (if its a SkyDNS instance) that this is a stub request. 638 | SkyDNS will not (stub)forward packets with this EDNS0 meta data, instead the request 639 | will be dropped and logged. 640 | 641 | Remember this will only work when SkyDNS is started with `-stubzones`. 642 | 643 | 644 | ## How Do I Create an Address Pool and Round Robin Between Them 645 | 646 | You have 3 machines with 3 different IP addresses and you want to have 647 | 1 name pointing to all 3 possible addresses. The name we want to use is: 648 | `db.skydns.local` and the 3 addresses are 127.0.0.{1,2,3}. For this to work we 649 | create the hosts named `x{1,2,3}.db.skydns.local` in etcd: 650 | 651 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/db/x1 -d \ 652 | value='{"host":"127.0.0.1"}' 653 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/db/x2 -d \ 654 | value='{"host": "127.0.0.2"'} 655 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/db/x3 -d \ 656 | value='{"host": "127.0.0.3"'} 657 | 658 | Now the name `db.skydns.local` is the "load balanced" name for the database, SkyDNS 659 | will round-robin by default in this case unless `-round-robin=false` is enabled. 660 | 661 | 662 | ## How I Do Create Multiple SRV Records For the Same Name 663 | 664 | You want this response from SkyDNS, which says there are 2 open 665 | ports on bar.skydns.local and this name has IP addres 192.168.0.1: 666 | 667 | ;; ANSWER SECTION: 668 | bar.skydns.local. 3600 IN SRV 10 50 80 bar.skydns.local. 669 | bar.skydns.local. 3600 IN SRV 10 50 443 bar.skydns.local. 670 | 671 | ;; ADDITIONAL SECTION: 672 | bar.skydns.local. 3600 IN A 192.168.0.1 673 | 674 | So you register a "dummy" host named `x1`: 675 | 676 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/bar/x1 -d \ 677 | value='{"host":"192.168.0.1","port":80}' 678 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/bar/x2 -d \ 679 | value='{"host": "bar.skydns.local","port":443}' 680 | 681 | And try it out: 682 | 683 | ;; ANSWER SECTION: 684 | bar.skydns.local. 3600 IN SRV 10 50 80 x1.bar.skydns.local. 685 | bar.skydns.local. 3600 IN SRV 10 50 443 bar.skydns.local. 686 | 687 | ;; ADDITIONAL SECTION: 688 | x1.bar.skydns.local. 3600 IN A 192.168.0.1 689 | 690 | Which has `x1` in the name, which is not the name you wanted to see there, and 691 | worse does not match the name in the other SRV record. To makes this work you'll 692 | need `TargetStrip` which allows you to tell SkyDNS to strip labels from the name 693 | it makes up: 694 | 695 | curl -XPUT http://127.0.0.1:4001/v2/keys/skydns/local/skydns/bar/x1 -d \ 696 | value='{"host":"192.168.0.1","port":80,"targetstrip":1}' 697 | 698 | % dig @127.0.0.1 bar.skydns.local. SRV 699 | 700 | ;; ANSWER SECTION: 701 | bar.skydns.local. 3600 IN SRV 10 50 80 bar.skydns.local. 702 | bar.skydns.local. 3600 IN SRV 10 50 443 bar.skydns.local. 703 | 704 | ;; ADDITIONAL SECTION: 705 | bar.skydns.local. 3600 IN A 192.168.0.1 706 | 707 | 708 | ## How do you limit recursion? 709 | 710 | By default SkyDNS will returns *all* records under a name. Suppose you want we have 711 | `bar.skydns.local`... TODO. 712 | 713 | 714 | # Docker 715 | 716 | Official Docker images are at the [Docker Hub](https://registry.hub.docker.com/u/skynetservices/skydns/): 717 | 718 | * master -> skynetservices/skydns:latest 719 | * latest tag -> skynetservices/skydns:latest-tagged 720 | 721 | The supplied `Dockerfile` can be used to build an image as well. Note that the image 722 | is based of Alpine Linux which used musl libc instead of glibc, so when building 723 | SkyDNS you must make sure if does not need glibc when run: 724 | 725 | Build SkyDNS with: 726 | 727 | % GOOS=linux go build -a -tags netgo -installsuffix netgo 728 | 729 | And then build the docker image: 730 | 731 | % docker build -t $USER/skydns . 732 | 733 | If you run it, SkyDNS needs to access Etcd (or whatever backend), which usually 734 | runs on the host server (i.e. when using CoreOS), to make that work, just run: 735 | 736 | docker run --net host 737 | -------------------------------------------------------------------------------- /server/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SkyDNS Authors. All rights reserved. 2 | // Use of this source code is governed by The MIT License (MIT) that can be 3 | // found in the LICENSE file. 4 | 5 | package server 6 | 7 | // etcd needs to be running on http://127.0.0.1:2379 8 | 9 | import ( 10 | "crypto/rsa" 11 | "encoding/json" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "testing" 17 | "time" 18 | 19 | backendetcd "github.com/skynetservices/skydns/backends/etcd" 20 | "github.com/skynetservices/skydns/cache" 21 | "github.com/skynetservices/skydns/msg" 22 | 23 | etcd "github.com/coreos/etcd/client" 24 | "github.com/miekg/dns" 25 | "golang.org/x/net/context" 26 | ) 27 | 28 | // Keep global port counter that increments with 10 for each 29 | // new call to newTestServer. The dns server is started on port 'Port'. 30 | var ( 31 | Port = 9400 32 | StrPort = "9400" // string equivalent of Port 33 | ctx = context.Background() 34 | ) 35 | 36 | func addService(t *testing.T, s *server, k string, ttl time.Duration, m *msg.Service) { 37 | b, err := json.Marshal(m) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | path, _ := msg.PathWithWildcard(k) 42 | 43 | _, err = s.backend.(*backendetcd.Backend).Client().Set(ctx, path, string(b), &etcd.SetOptions{TTL: ttl}) 44 | if err != nil { 45 | // TODO(miek): allow for existing keys... 46 | t.Fatal(err) 47 | } 48 | } 49 | 50 | func delService(t *testing.T, s *server, k string) { 51 | path, _ := msg.PathWithWildcard(k) 52 | _, err := s.backend.(*backendetcd.Backend).Client().Delete(ctx, path, &etcd.DeleteOptions{Recursive: false}) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | } 57 | 58 | func newTestServer(t *testing.T, c bool) *server { 59 | Port += 10 60 | StrPort = strconv.Itoa(Port) 61 | s := new(server) 62 | client, _ := etcd.New(etcd.Config{ 63 | Endpoints: []string{"http://127.0.0.1:2379/"}, 64 | Transport: etcd.DefaultTransport, 65 | }) 66 | kapi := etcd.NewKeysAPI(client) 67 | 68 | // TODO(miek): why don't I use NewServer?? 69 | s.group = new(sync.WaitGroup) 70 | s.scache = cache.New(100, 0) 71 | s.rcache = cache.New(100, 0) 72 | if c { 73 | s.rcache = cache.New(100, 60) // 100 items, 60s ttl 74 | } 75 | s.config = new(Config) 76 | s.config.Domain = "skydns.test." 77 | s.config.DnsAddr = "127.0.0.1:" + StrPort 78 | s.config.Nameservers = []string{"8.8.4.4:53"} 79 | SetDefaults(s.config) 80 | s.config.Local = "104.server1.development.region1.skydns.test." 81 | s.config.Priority = 10 82 | s.config.RCacheTtl = RCacheTtl 83 | s.config.Ttl = 3600 84 | s.config.Ndots = 2 85 | 86 | s.dnsUDPclient = &dns.Client{Net: "udp", ReadTimeout: 2 * s.config.ReadTimeout, WriteTimeout: 2 * s.config.ReadTimeout, SingleInflight: true} 87 | s.dnsTCPclient = &dns.Client{Net: "tcp", ReadTimeout: 2 * s.config.ReadTimeout, WriteTimeout: 2 * s.config.ReadTimeout, SingleInflight: true} 88 | 89 | s.backend = backendetcd.NewBackend(kapi, ctx, &backendetcd.Config{ 90 | Ttl: s.config.Ttl, 91 | Priority: s.config.Priority, 92 | }) 93 | 94 | go s.Run() 95 | time.Sleep(500 * time.Millisecond) // Yeah, yeah, should do a proper fix 96 | return s 97 | } 98 | 99 | func newTestServerDNSSEC(t *testing.T, cache bool) *server { 100 | var err error 101 | s := newTestServer(t, cache) 102 | s.config.PubKey = newDNSKEY("skydns.test. IN DNSKEY 256 3 5 AwEAAaXfO+DOBMJsQ5H4TfiabwSpqE4cGL0Qlvh5hrQumrjr9eNSdIOjIHJJKCe56qBU5mH+iBlXP29SVf6UiiMjIrAPDVhClLeWFe0PC+XlWseAyRgiLHdQ8r95+AfkhO5aZgnCwYf9FGGSaT0+CRYN+PyDbXBTLK5FN+j5b6bb7z+d") 103 | s.config.KeyTag = s.config.PubKey.KeyTag() 104 | privKey, err := s.config.PubKey.ReadPrivateKey(strings.NewReader(`Private-key-format: v1.3 105 | Algorithm: 5 (RSASHA1) 106 | Modulus: pd874M4EwmxDkfhN+JpvBKmoThwYvRCW+HmGtC6auOv141J0g6MgckkoJ7nqoFTmYf6IGVc/b1JV/pSKIyMisA8NWEKUt5YV7Q8L5eVax4DJGCIsd1Dyv3n4B+SE7lpmCcLBh/0UYZJpPT4JFg34/INtcFMsrkU36PlvptvvP50= 107 | PublicExponent: AQAB 108 | PrivateExponent: C6e08GXphbPPx6j36ZkIZf552gs1XcuVoB4B7hU8P/Qske2QTFOhCwbC8I+qwdtVWNtmuskbpvnVGw9a6X8lh7Z09RIgzO/pI1qau7kyZcuObDOjPw42exmjqISFPIlS1wKA8tw+yVzvZ19vwRk1q6Rne+C1romaUOTkpA6UXsE= 109 | Prime1: 2mgJ0yr+9vz85abrWBWnB8Gfa1jOw/ccEg8ZToM9GLWI34Qoa0D8Dxm8VJjr1tixXY5zHoWEqRXciTtY3omQDQ== 110 | Prime2: wmxLpp9rTzU4OREEVwF43b/TxSUBlUq6W83n2XP8YrCm1nS480w4HCUuXfON1ncGYHUuq+v4rF+6UVI3PZT50Q== 111 | Exponent1: wkdTngUcIiau67YMmSFBoFOq9Lldy9HvpVzK/R0e5vDsnS8ZKTb4QJJ7BaG2ADpno7pISvkoJaRttaEWD3a8rQ== 112 | Exponent2: YrC8OglEXIGkV3tm2494vf9ozPL6+cBkFsPPg9dXbvVCyyuW0pGHDeplvfUqs4nZp87z8PsoUL+LAUqdldnwcQ== 113 | Coefficient: mMFr4+rDY5V24HZU3Oa5NEb55iQ56ZNa182GnNhWqX7UqWjcUUGjnkCy40BqeFAQ7lp52xKHvP5Zon56mwuQRw== 114 | `), "stdin") 115 | s.config.PrivKey = privKey.(*rsa.PrivateKey) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | return s 120 | } 121 | 122 | func TestDNSForward(t *testing.T) { 123 | s := newTestServer(t, false) 124 | defer s.Stop() 125 | 126 | c := new(dns.Client) 127 | m := new(dns.Msg) 128 | m.SetQuestion("www.example.com.", dns.TypeA) 129 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 130 | if err != nil { 131 | // try twice 132 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 133 | if err != nil { 134 | t.Fatal(err) 135 | } 136 | } 137 | if len(resp.Answer) == 0 || resp.Rcode != dns.RcodeSuccess { 138 | t.Fatal("answer expected to have A records or rcode not equal to RcodeSuccess") 139 | } 140 | // TCP 141 | c.Net = "tcp" 142 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | if len(resp.Answer) == 0 || resp.Rcode != dns.RcodeSuccess { 147 | t.Fatal("answer expected to have A records or rcode not equal to RcodeSuccess") 148 | } 149 | // disable recursion and check 150 | s.config.NoRec = true 151 | 152 | m.SetQuestion("www.example.com.", dns.TypeA) 153 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 154 | if err != nil { 155 | t.Fatal(err) 156 | } 157 | if resp.Rcode != dns.RcodeServerFailure { 158 | t.Fatal("answer expected to have rcode equal to RcodeFailure") 159 | } 160 | } 161 | 162 | func TestDNSStubForward(t *testing.T) { 163 | s := newTestServer(t, false) 164 | defer s.Stop() 165 | 166 | c := new(dns.Client) 167 | m := new(dns.Msg) 168 | 169 | stubEx := &msg.Service{ 170 | // IP address of a.iana-servers.net. 171 | Host: "199.43.135.53", Key: "a.example.com.stub.dns.skydns.test.", 172 | } 173 | stubBroken := &msg.Service{ 174 | Host: "127.0.0.1", Port: 5454, Key: "b.example.org.stub.dns.skydns.test.", 175 | } 176 | stubLoop := &msg.Service{ 177 | Host: "127.0.0.1", Port: Port, Key: "b.example.net.stub.dns.skydns.test.", 178 | } 179 | addService(t, s, stubEx.Key, 0, stubEx) 180 | defer delService(t, s, stubEx.Key) 181 | addService(t, s, stubBroken.Key, 0, stubBroken) 182 | defer delService(t, s, stubBroken.Key) 183 | addService(t, s, stubLoop.Key, 0, stubLoop) 184 | defer delService(t, s, stubLoop.Key) 185 | 186 | s.UpdateStubZones() 187 | 188 | m.SetQuestion("www.example.com.", dns.TypeA) 189 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 190 | if err != nil { 191 | // try twice 192 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | } 197 | if len(resp.Answer) == 0 || resp.Rcode != dns.RcodeSuccess { 198 | t.Fatal("answer expected to have A records or rcode not equal to RcodeSuccess") 199 | } 200 | // The main diff. here is that we expect the AA bit to be set, because we directly 201 | // queried the authoritative servers. 202 | if resp.Authoritative != true { 203 | t.Fatal("answer expected to have AA bit set") 204 | } 205 | 206 | // This should fail. 207 | m.SetQuestion("www.example.org.", dns.TypeA) 208 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 209 | if len(resp.Answer) != 0 || resp.Rcode != dns.RcodeServerFailure { 210 | t.Fatal("answer expected to fail for example.org") 211 | } 212 | 213 | // This should really fail with a timeout. 214 | m.SetQuestion("www.example.net.", dns.TypeA) 215 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 216 | if err == nil { 217 | t.Fatal("answer expected to fail for example.net") 218 | } else { 219 | t.Logf("succesfully failing %s", err) 220 | } 221 | 222 | // Packet with EDNS0 223 | m.SetEdns0(4096, true) 224 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 225 | if err == nil { 226 | t.Fatal("answer expected to fail for example.net") 227 | } else { 228 | t.Logf("succesfully failing %s", err) 229 | } 230 | 231 | // Now start another SkyDNS instance on a different port, 232 | // add a stubservice for it and check if the forwarding is 233 | // actually working. 234 | oldStrPort := StrPort 235 | 236 | s1 := newTestServer(t, false) 237 | defer s1.Stop() 238 | s1.config.Domain = "skydns.com." 239 | 240 | // Add forwarding IP for internal.skydns.com. Use Port to point to server s. 241 | stubForward := &msg.Service{ 242 | Host: "127.0.0.1", Port: Port, Key: "b.internal.skydns.com.stub.dns.skydns.test.", 243 | } 244 | addService(t, s, stubForward.Key, 0, stubForward) 245 | defer delService(t, s, stubForward.Key) 246 | s.UpdateStubZones() 247 | 248 | // Add an answer for this in our "new" server. 249 | stubReply := &msg.Service{ 250 | Host: "127.1.1.1", Key: "www.internal.skydns.com.", 251 | } 252 | addService(t, s1, stubReply.Key, 0, stubReply) 253 | defer delService(t, s1, stubReply.Key) 254 | 255 | m = new(dns.Msg) 256 | m.SetQuestion("www.internal.skydns.com.", dns.TypeA) 257 | resp, _, err = c.Exchange(m, "127.0.0.1:"+oldStrPort) 258 | if err != nil { 259 | t.Fatalf("failed to forward %s", err) 260 | } 261 | if resp.Answer[0].(*dns.A).A.String() != "127.1.1.1" { 262 | t.Fatalf("failed to get correct reply") 263 | } 264 | 265 | // Adding an in baliwick internal domain forward. 266 | s2 := newTestServer(t, false) 267 | defer s2.Stop() 268 | s2.config.Domain = "internal.skydns.net." 269 | 270 | // Add forwarding IP for internal.skydns.net. Use Port to point to server s. 271 | stubForward1 := &msg.Service{ 272 | Host: "127.0.0.1", Port: Port, Key: "b.internal.skydns.net.stub.dns.skydns.test.", 273 | } 274 | addService(t, s, stubForward1.Key, 0, stubForward1) 275 | defer delService(t, s, stubForward1.Key) 276 | s.UpdateStubZones() 277 | 278 | // Add an answer for this in our "new" server. 279 | stubReply1 := &msg.Service{ 280 | Host: "127.10.10.10", Key: "www.internal.skydns.net.", 281 | } 282 | addService(t, s2, stubReply1.Key, 0, stubReply1) 283 | defer delService(t, s2, stubReply1.Key) 284 | 285 | m = new(dns.Msg) 286 | m.SetQuestion("www.internal.skydns.net.", dns.TypeA) 287 | resp, _, err = c.Exchange(m, "127.0.0.1:"+oldStrPort) 288 | if err != nil { 289 | t.Fatalf("failed to forward %s", err) 290 | } 291 | if resp.Answer[0].(*dns.A).A.String() != "127.10.10.10" { 292 | t.Fatalf("failed to get correct reply") 293 | } 294 | } 295 | 296 | func TestDNSTtlRR(t *testing.T) { 297 | s := newTestServerDNSSEC(t, false) 298 | defer s.Stop() 299 | 300 | serv := &msg.Service{Host: "10.0.0.2", Key: "ttl.skydns.test.", Ttl: 360} 301 | addService(t, s, serv.Key, time.Duration(serv.Ttl)*time.Second, serv) 302 | defer delService(t, s, serv.Key) 303 | 304 | c := new(dns.Client) 305 | 306 | tc := dnsTestCases[9] // TTL Test 307 | t.Logf("%v\n", tc) 308 | m := new(dns.Msg) 309 | m.SetQuestion(tc.Qname, tc.Qtype) 310 | if tc.dnssec == true { 311 | m.SetEdns0(4096, true) 312 | } 313 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 314 | if err != nil { 315 | t.Errorf("failing: %s: %s\n", m.String(), err.Error()) 316 | } 317 | t.Logf("%s\n", resp) 318 | 319 | for i, a := range resp.Answer { 320 | if a.Header().Ttl != 360 { 321 | t.Errorf("Answer %d should have a Header TTL of %d, but has %d", i, 360, a.Header().Ttl) 322 | } 323 | } 324 | } 325 | 326 | type rrSet []dns.RR 327 | 328 | func (p rrSet) Len() int { return len(p) } 329 | func (p rrSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 330 | func (p rrSet) Less(i, j int) bool { return p[i].String() < p[j].String() } 331 | 332 | func TestDNS(t *testing.T) { 333 | s := newTestServerDNSSEC(t, false) 334 | defer s.Stop() 335 | 336 | for _, serv := range services { 337 | addService(t, s, serv.Key, 0, serv) 338 | defer delService(t, s, serv.Key) 339 | } 340 | c := new(dns.Client) 341 | for _, tc := range dnsTestCases { 342 | m := new(dns.Msg) 343 | m.SetQuestion(tc.Qname, tc.Qtype) 344 | if tc.dnssec { 345 | m.SetEdns0(4096, true) 346 | } 347 | if tc.chaos { 348 | m.Question[0].Qclass = dns.ClassCHAOS 349 | } 350 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 351 | if err != nil { 352 | // try twice, be more resilent against remote lookups 353 | // timing out. 354 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 355 | if err != nil { 356 | t.Fatalf("failing: %s: %s\n", m.String(), err.Error()) 357 | } 358 | } 359 | sort.Sort(rrSet(resp.Answer)) 360 | sort.Sort(rrSet(resp.Ns)) 361 | sort.Sort(rrSet(resp.Extra)) 362 | fatal := false 363 | defer func() { 364 | if fatal { 365 | t.Logf("question: %s\n", m.Question[0].String()) 366 | t.Logf("%s\n", resp) 367 | } 368 | }() 369 | if resp.Rcode != tc.Rcode { 370 | fatal = true 371 | t.Fatalf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) 372 | } 373 | if len(resp.Answer) != len(tc.Answer) { 374 | fatal = true 375 | t.Fatalf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) 376 | } 377 | for i, a := range resp.Answer { 378 | if a.Header().Name != tc.Answer[i].Header().Name { 379 | fatal = true 380 | t.Fatalf("answer %d should have a Header Name of %q, but has %q", i, tc.Answer[i].Header().Name, a.Header().Name) 381 | } 382 | if a.Header().Ttl != tc.Answer[i].Header().Ttl { 383 | fatal = true 384 | t.Fatalf("Answer %d should have a Header TTL of %d, but has %d", i, tc.Answer[i].Header().Ttl, a.Header().Ttl) 385 | } 386 | if a.Header().Rrtype != tc.Answer[i].Header().Rrtype { 387 | fatal = true 388 | t.Fatalf("answer %d should have a header response type of %d, but has %d", i, tc.Answer[i].Header().Rrtype, a.Header().Rrtype) 389 | } 390 | switch x := a.(type) { 391 | case *dns.SRV: 392 | if x.Priority != tc.Answer[i].(*dns.SRV).Priority { 393 | fatal = true 394 | t.Fatalf("answer %d should have a Priority of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Priority, x.Priority) 395 | } 396 | if x.Weight != tc.Answer[i].(*dns.SRV).Weight { 397 | fatal = true 398 | t.Fatalf("answer %d should have a Weight of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Weight, x.Weight) 399 | } 400 | if x.Port != tc.Answer[i].(*dns.SRV).Port { 401 | fatal = true 402 | t.Fatalf("answer %d should have a Port of %d, but has %d", i, tc.Answer[i].(*dns.SRV).Port, x.Port) 403 | } 404 | if x.Target != tc.Answer[i].(*dns.SRV).Target { 405 | fatal = true 406 | t.Fatalf("answer %d should have a Target of %q, but has %q", i, tc.Answer[i].(*dns.SRV).Target, x.Target) 407 | } 408 | case *dns.A: 409 | if x.A.String() != tc.Answer[i].(*dns.A).A.String() { 410 | fatal = true 411 | t.Fatalf("answer %d should have a Address of %q, but has %q", i, tc.Answer[i].(*dns.A).A.String(), x.A.String()) 412 | } 413 | case *dns.AAAA: 414 | if x.AAAA.String() != tc.Answer[i].(*dns.AAAA).AAAA.String() { 415 | fatal = true 416 | t.Fatalf("answer %d should have a Address of %q, but has %q", i, tc.Answer[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) 417 | } 418 | case *dns.TXT: 419 | for j, txt := range x.Txt { 420 | if txt != tc.Answer[i].(*dns.TXT).Txt[j] { 421 | fatal = true 422 | t.Fatalf("answer %d should have a Txt of %q, but has %q", i, tc.Answer[i].(*dns.TXT).Txt[j], txt) 423 | } 424 | } 425 | case *dns.DNSKEY: 426 | tt := tc.Answer[i].(*dns.DNSKEY) 427 | if x.Flags != tt.Flags { 428 | fatal = true 429 | t.Fatalf("DNSKEY flags should be %q, but is %q", x.Flags, tt.Flags) 430 | } 431 | if x.Protocol != tt.Protocol { 432 | fatal = true 433 | t.Fatalf("DNSKEY protocol should be %q, but is %q", x.Protocol, tt.Protocol) 434 | } 435 | if x.Algorithm != tt.Algorithm { 436 | fatal = true 437 | t.Fatalf("DNSKEY algorithm should be %q, but is %q", x.Algorithm, tt.Algorithm) 438 | } 439 | case *dns.RRSIG: 440 | tt := tc.Answer[i].(*dns.RRSIG) 441 | if x.TypeCovered != tt.TypeCovered { 442 | fatal = true 443 | t.Fatalf("RRSIG type-covered should be %d, but is %d", x.TypeCovered, tt.TypeCovered) 444 | } 445 | if x.Algorithm != tt.Algorithm { 446 | fatal = true 447 | t.Fatalf("RRSIG algorithm should be %d, but is %d", x.Algorithm, tt.Algorithm) 448 | } 449 | if x.Labels != tt.Labels { 450 | fatal = true 451 | t.Fatalf("RRSIG label should be %d, but is %d", x.Labels, tt.Labels) 452 | } 453 | if x.OrigTtl != tt.OrigTtl { 454 | fatal = true 455 | t.Fatalf("RRSIG orig-ttl should be %d, but is %d", x.OrigTtl, tt.OrigTtl) 456 | } 457 | if x.KeyTag != tt.KeyTag { 458 | fatal = true 459 | t.Fatalf("RRSIG key-tag should be %d, but is %d", x.KeyTag, tt.KeyTag) 460 | } 461 | if x.SignerName != tt.SignerName { 462 | fatal = true 463 | t.Fatalf("RRSIG signer-name should be %q, but is %q", x.SignerName, tt.SignerName) 464 | } 465 | case *dns.SOA: 466 | tt := tc.Answer[i].(*dns.SOA) 467 | if x.Ns != tt.Ns { 468 | fatal = true 469 | t.Fatalf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) 470 | } 471 | case *dns.PTR: 472 | tt := tc.Answer[i].(*dns.PTR) 473 | if x.Ptr != tt.Ptr { 474 | fatal = true 475 | t.Fatalf("PTR ptr should be %q, but is %q", x.Ptr, tt.Ptr) 476 | } 477 | case *dns.CNAME: 478 | tt := tc.Answer[i].(*dns.CNAME) 479 | if x.Target != tt.Target { 480 | fatal = true 481 | t.Fatalf("CNAME target should be %q, but is %q", x.Target, tt.Target) 482 | } 483 | case *dns.MX: 484 | tt := tc.Answer[i].(*dns.MX) 485 | if x.Mx != tt.Mx { 486 | t.Fatalf("MX Mx should be %q, but is %q", x.Mx, tt.Mx) 487 | } 488 | if x.Preference != tt.Preference { 489 | t.Fatalf("MX Preference should be %q, but is %q", x.Preference, tt.Preference) 490 | } 491 | } 492 | } 493 | if len(resp.Ns) != len(tc.Ns) { 494 | fatal = true 495 | t.Fatalf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) 496 | } 497 | for i, n := range resp.Ns { 498 | switch x := n.(type) { 499 | case *dns.SOA: 500 | tt := tc.Ns[i].(*dns.SOA) 501 | if x.Ns != tt.Ns { 502 | fatal = true 503 | t.Fatalf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) 504 | } 505 | case *dns.NS: 506 | tt := tc.Ns[i].(*dns.NS) 507 | if x.Ns != tt.Ns { 508 | fatal = true 509 | t.Fatalf("NS nameserver should be %q, but is %q", x.Ns, tt.Ns) 510 | } 511 | case *dns.NSEC3: 512 | tt := tc.Ns[i].(*dns.NSEC3) 513 | if x.NextDomain != tt.NextDomain { 514 | fatal = true 515 | t.Fatalf("NSEC3 nextdomain should be %q, but is %q", x.NextDomain, tt.NextDomain) 516 | } 517 | if x.Hdr.Name != tt.Hdr.Name { 518 | fatal = true 519 | t.Fatalf("NSEC3 ownername should be %q, but is %q", x.Hdr.Name, tt.Hdr.Name) 520 | } 521 | for j, y := range x.TypeBitMap { 522 | if y != tt.TypeBitMap[j] { 523 | fatal = true 524 | t.Fatalf("NSEC3 bitmap should have %q, but is %q", dns.TypeToString[y], dns.TypeToString[tt.TypeBitMap[j]]) 525 | } 526 | } 527 | } 528 | } 529 | if len(resp.Extra) != len(tc.Extra) { 530 | fatal = true 531 | t.Fatalf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) 532 | } 533 | for i, e := range resp.Extra { 534 | switch x := e.(type) { 535 | case *dns.A: 536 | if x.A.String() != tc.Extra[i].(*dns.A).A.String() { 537 | fatal = true 538 | t.Fatalf("extra %d should have a address of %q, but has %q", i, tc.Extra[i].(*dns.A).A.String(), x.A.String()) 539 | } 540 | case *dns.AAAA: 541 | if x.AAAA.String() != tc.Extra[i].(*dns.AAAA).AAAA.String() { 542 | fatal = true 543 | t.Fatalf("extra %d should have a address of %q, but has %q", i, tc.Extra[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) 544 | } 545 | case *dns.CNAME: 546 | tt := tc.Extra[i].(*dns.CNAME) 547 | if x.Target != tt.Target { 548 | // Super super gross hack. 549 | if x.Target == "a.ipaddr.skydns.test." && tt.Target == "b.ipaddr.skydns.test." { 550 | // These records are randomly choosen, either one is OK. 551 | continue 552 | } 553 | fatal = true 554 | t.Fatalf("CNAME target should be %q, but is %q", x.Target, tt.Target) 555 | } 556 | } 557 | } 558 | } 559 | } 560 | 561 | type dnsTestCase struct { 562 | Qname string 563 | Qtype uint16 564 | dnssec bool 565 | chaos bool 566 | Rcode int 567 | Answer []dns.RR 568 | Ns []dns.RR 569 | Extra []dns.RR 570 | } 571 | 572 | // Note the key is encoded as DNS name, while in "reality" it is a etcd path. 573 | var services = []*msg.Service{ 574 | {Host: "server1", Port: 8080, Key: "100.server1.development.region1.skydns.test."}, 575 | {Host: "server2", Port: 80, Key: "101.server2.production.region1.skydns.test."}, 576 | {Host: "server4", Port: 80, Priority: 333, Key: "102.server4.development.region6.skydns.test."}, 577 | {Host: "server3", Key: "103.server4.development.region2.skydns.test."}, 578 | {Host: "172.16.1.1", Key: "a.ipaddr.skydns.test."}, 579 | {Host: "172.16.1.2", Key: "b.ipaddr.skydns.test."}, 580 | {Host: "ipaddr.skydns.test", Key: "1.backend.in.skydns.test."}, 581 | {Host: "10.0.0.1", Key: "104.server1.development.region1.skydns.test."}, 582 | {Host: "2001::8:8:8:8", Key: "105.server3.production.region2.skydns.test."}, 583 | {Host: "104.server1.development.region1.skydns.test", Key: "1.cname.skydns.test."}, 584 | {Host: "100.server1.development.region1.skydns.test", Key: "2.cname.skydns.test."}, 585 | {Host: "www.miek.nl", Key: "external1.cname.skydns.test."}, 586 | {Host: "www.miek.nl", Key: "ext1.cname2.skydns.test."}, 587 | {Host: "www.miek.nl", Key: "ext2.cname2.skydns.test."}, 588 | {Host: "wwwwwww.miek.nl", Key: "external2.cname.skydns.test."}, 589 | {Host: "4.cname.skydns.test", Key: "3.cname.skydns.test."}, 590 | {Host: "3.cname.skydns.test", Key: "4.cname.skydns.test."}, 591 | {Host: "10.0.0.2", Key: "ttl.skydns.test.", Ttl: 360}, 592 | {Host: "reverse.example.com", Key: "1.0.0.10.in-addr.arpa."}, // 10.0.0.1 593 | {Host: "server1", Weight: 130, Key: "100.server1.region5.skydns.test."}, 594 | {Host: "server2", Weight: 80, Key: "101.server2.region5.skydns.test."}, 595 | {Host: "server3", Weight: 150, Key: "103.server3.region5.skydns.test."}, 596 | {Host: "server4", Priority: 30, Key: "104.server4.region5.skydns.test."}, 597 | {Host: "172.16.1.1", Key: "a.ipaddr2.skydns.test."}, 598 | {Host: "2001::8:8:8:8", Key: "b.ipaddr2.skydns.test."}, 599 | {Host: "ipaddr2.skydns.test", Key: "both.v4v6.test.skydns.test."}, 600 | 601 | // A name: bar.skydns.test with 2 ports open and points to one ip: 192.168.0.1 602 | {Host: "192.168.0.1", Port: 80, Key: "x.bar.skydns.test.", TargetStrip: 1}, 603 | {Host: "bar.skydns.local", Port: 443, Key: "y.bar.skydns.test.", TargetStrip: 0}, 604 | 605 | // nameserver 606 | {Host: "10.0.0.2", Key: "a.ns.dns.skydns.test."}, 607 | {Host: "10.0.0.3", Key: "b.ns.dns.skydns.test."}, 608 | // txt 609 | {Text: "abc", Key: "a1.txt.skydns.test."}, 610 | {Text: "abc abc", Key: "a2.txt.skydns.test."}, 611 | // duplicate ip address 612 | {Host: "10.11.11.10", Key: "http.multiport.http.skydns.test.", Port: 80}, 613 | {Host: "10.11.11.10", Key: "https.multiport.http.skydns.test.", Port: 443}, 614 | 615 | // uppercase name 616 | {Host: "127.0.0.1", Key: "upper.skydns.test.", Port: 443}, 617 | 618 | // mx 619 | {Host: "mx.skydns.test", Priority: 50, Mail: true, Key: "a.mail.skydns.test."}, 620 | {Host: "mx.miek.nl", Priority: 50, Mail: true, Key: "b.mail.skydns.test."}, 621 | {Host: "a.ipaddr.skydns.test", Priority: 30, Mail: true, Key: "a.mx.skydns.test."}, 622 | 623 | // double CNAME, see issue #168 624 | {Host: "mx2.skydns.test", Priority: 50, Mail: true, Key: "a.mail2.skydns.test."}, 625 | {Host: "a.ipaddr.skydns.test", Mail: true, Key: "a.mx2.skydns.test."}, 626 | // Sometimes we *do* get back a.ipaddr.skydns.test, making this test flaky. 627 | {Host: "b.ipaddr.skydns.test", Mail: true, Key: "b.mx2.skydns.test."}, 628 | 629 | // groups 630 | {Host: "127.0.0.1", Key: "a.dom.skydns.test.", Group: "g1"}, 631 | {Host: "127.0.0.2", Key: "b.sub.dom.skydns.test.", Group: "g1"}, 632 | 633 | {Host: "127.0.0.1", Key: "a.dom2.skydns.test.", Group: "g1"}, 634 | {Host: "127.0.0.2", Key: "b.sub.dom2.skydns.test.", Group: ""}, 635 | 636 | {Host: "127.0.0.1", Key: "a.dom1.skydns.test.", Group: "g1"}, 637 | {Host: "127.0.0.2", Key: "b.sub.dom1.skydns.test.", Group: "g2"}, 638 | } 639 | 640 | var dnsTestCases = []dnsTestCase{ 641 | // Full Name Test 642 | { 643 | Qname: "100.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, 644 | Answer: []dns.RR{newSRV("100.server1.development.region1.skydns.test. 3600 SRV 10 100 8080 server1.")}, 645 | }, 646 | // SOA Record Test 647 | { 648 | Qname: "skydns.test.", Qtype: dns.TypeSOA, 649 | Answer: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, 650 | }, 651 | // NS Record Test 652 | { 653 | Qname: "skydns.test.", Qtype: dns.TypeNS, 654 | Answer: []dns.RR{ 655 | newNS("skydns.test. 3600 NS a.ns.dns.skydns.test."), 656 | newNS("skydns.test. 3600 NS b.ns.dns.skydns.test."), 657 | }, 658 | Extra: []dns.RR{ 659 | newA("a.ns.dns.skydns.test. 3600 A 10.0.0.2"), 660 | newA("b.ns.dns.skydns.test. 3600 A 10.0.0.3"), 661 | }, 662 | }, 663 | // A Record For NS Record Test 664 | { 665 | Qname: "ns.dns.skydns.test.", Qtype: dns.TypeA, 666 | Answer: []dns.RR{ 667 | newA("ns.dns.skydns.test. 3600 A 10.0.0.2"), 668 | newA("ns.dns.skydns.test. 3600 A 10.0.0.3"), 669 | }, 670 | }, 671 | // A Record Test 672 | { 673 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeA, 674 | Answer: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, 675 | }, 676 | // Multiple A Record Test 677 | { 678 | Qname: "ipaddr.skydns.test.", Qtype: dns.TypeA, 679 | Answer: []dns.RR{ 680 | newA("ipaddr.skydns.test. 3600 A 172.16.1.1"), 681 | newA("ipaddr.skydns.test. 3600 A 172.16.1.2"), 682 | }, 683 | }, 684 | // A Record Test with SRV 685 | { 686 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, 687 | Answer: []dns.RR{newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, 688 | Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, 689 | }, 690 | // AAAAA Record Test 691 | { 692 | Qname: "105.server3.production.region2.skydns.test.", Qtype: dns.TypeAAAA, 693 | Answer: []dns.RR{newAAAA("105.server3.production.region2.skydns.test. 3600 AAAA 2001::8:8:8:8")}, 694 | }, 695 | // Multi SRV with the same target, should be dedupped. 696 | { 697 | Qname: "*.cname2.skydns.test.", Qtype: dns.TypeSRV, 698 | Answer: []dns.RR{ 699 | newSRV("*.cname2.skydns.test. 3600 IN SRV 10 100 0 www.miek.nl."), 700 | }, 701 | Extra: []dns.RR{ 702 | newA("a.miek.nl. 3600 IN A 176.58.119.54"), 703 | newAAAA("a.miek.nl. 3600 IN AAAA 2a01:7e00::f03c:91ff:fe79:234c"), 704 | newCNAME("www.miek.nl. 3600 IN CNAME a.miek.nl."), 705 | }, 706 | }, 707 | // TTL Test 708 | { 709 | // This test is referenced by number from DNSTtlRRset 710 | Qname: "ttl.skydns.test.", Qtype: dns.TypeA, 711 | Answer: []dns.RR{newA("ttl.skydns.test. 360 A 10.0.0.2")}, 712 | }, 713 | // CNAME Test 714 | { 715 | Qname: "1.cname.skydns.test.", Qtype: dns.TypeA, 716 | Answer: []dns.RR{ 717 | newCNAME("1.cname.skydns.test. 3600 CNAME 104.server1.development.region1.skydns.test."), 718 | newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), 719 | }, 720 | }, 721 | // Direct CNAME Test 722 | { 723 | Qname: "1.cname.skydns.test.", Qtype: dns.TypeCNAME, 724 | Answer: []dns.RR{ 725 | newCNAME("1.cname.skydns.test. 3600 CNAME 104.server1.development.region1.skydns.test."), 726 | }, 727 | }, 728 | // CNAME (unresolvable internal name) 729 | { 730 | Qname: "2.cname.skydns.test.", Qtype: dns.TypeA, 731 | Answer: []dns.RR{}, 732 | Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, 733 | }, 734 | // CNAME loop detection 735 | { 736 | Qname: "3.cname.skydns.test.", Qtype: dns.TypeA, 737 | Answer: []dns.RR{}, 738 | Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, 739 | }, 740 | // CNAME (resolvable external name) 741 | { 742 | Qname: "external1.cname.skydns.test.", Qtype: dns.TypeA, 743 | Answer: []dns.RR{ 744 | newA("a.miek.nl. 60 IN A 176.58.119.54"), 745 | newCNAME("external1.cname.skydns.test. 60 IN CNAME www.miek.nl."), 746 | newCNAME("www.miek.nl. 60 IN CNAME a.miek.nl."), 747 | }, 748 | }, 749 | // CNAME (unresolvable external name) 750 | { 751 | Qname: "external2.cname.skydns.test.", Qtype: dns.TypeA, 752 | Answer: []dns.RR{}, 753 | Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, 754 | }, 755 | // Priority Test 756 | { 757 | Qname: "region6.skydns.test.", Qtype: dns.TypeSRV, 758 | Answer: []dns.RR{newSRV("region6.skydns.test. 3600 SRV 333 100 80 server4.")}, 759 | }, 760 | // Subdomain Test 761 | { 762 | Qname: "region1.skydns.test.", Qtype: dns.TypeSRV, 763 | Answer: []dns.RR{ 764 | newSRV("region1.skydns.test. 3600 SRV 10 33 0 104.server1.development.region1.skydns.test."), 765 | newSRV("region1.skydns.test. 3600 SRV 10 33 80 server2"), 766 | newSRV("region1.skydns.test. 3600 SRV 10 33 8080 server1.")}, 767 | Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, 768 | }, 769 | // Subdomain Weight Test 770 | { 771 | Qname: "region5.skydns.test.", Qtype: dns.TypeSRV, 772 | Answer: []dns.RR{ 773 | newSRV("region5.skydns.test. 3600 SRV 10 22 0 server2."), 774 | newSRV("region5.skydns.test. 3600 SRV 10 36 0 server1."), 775 | newSRV("region5.skydns.test. 3600 SRV 10 41 0 server3."), 776 | newSRV("region5.skydns.test. 3600 SRV 30 100 0 server4.")}, 777 | }, 778 | // Wildcard Test 779 | { 780 | Qname: "*.region1.skydns.test.", Qtype: dns.TypeSRV, 781 | Answer: []dns.RR{ 782 | newSRV("*.region1.skydns.test. 3600 SRV 10 33 0 104.server1.development.region1.skydns.test."), 783 | newSRV("*.region1.skydns.test. 3600 SRV 10 33 80 server2"), 784 | newSRV("*.region1.skydns.test. 3600 SRV 10 33 8080 server1.")}, 785 | Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1")}, 786 | }, 787 | // Wildcard Test 788 | { 789 | Qname: "production.*.skydns.test.", Qtype: dns.TypeSRV, 790 | Answer: []dns.RR{ 791 | newSRV("production.*.skydns.test. 3600 IN SRV 10 50 0 105.server3.production.region2.skydns.test."), 792 | newSRV("production.*.skydns.test. 3600 IN SRV 10 50 80 server2.")}, 793 | Extra: []dns.RR{newAAAA("105.server3.production.region2.skydns.test. 3600 IN AAAA 2001::8:8:8:8")}, 794 | }, 795 | // Wildcard Test 796 | { 797 | Qname: "production.any.skydns.test.", Qtype: dns.TypeSRV, 798 | Answer: []dns.RR{ 799 | newSRV("production.any.skydns.test. 3600 IN SRV 10 50 0 105.server3.production.region2.skydns.test."), 800 | newSRV("production.any.skydns.test. 3600 IN SRV 10 50 80 server2.")}, 801 | Extra: []dns.RR{newAAAA("105.server3.production.region2.skydns.test. 3600 IN AAAA 2001::8:8:8:8")}, 802 | }, 803 | // NXDOMAIN Test 804 | { 805 | Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, 806 | Rcode: dns.RcodeNameError, 807 | Ns: []dns.RR{ 808 | newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), 809 | }, 810 | }, 811 | // NODATA Test 812 | { 813 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeTXT, 814 | Ns: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, 815 | }, 816 | // NODATA Test 2 817 | { 818 | Qname: "100.server1.development.region1.skydns.test.", Qtype: dns.TypeA, 819 | Rcode: dns.RcodeSuccess, 820 | Ns: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, 821 | }, 822 | // CNAME Test that targets multiple A records (hits a directory in etcd) 823 | { 824 | Qname: "1.backend.in.skydns.test.", Qtype: dns.TypeA, 825 | Answer: []dns.RR{ 826 | newCNAME("1.backend.in.skydns.test. IN CNAME ipaddr.skydns.test."), 827 | newA("ipaddr.skydns.test. IN A 172.16.1.1"), 828 | newA("ipaddr.skydns.test. IN A 172.16.1.2"), 829 | }, 830 | }, 831 | // Query a etcd directory key 832 | { 833 | Qname: "backend.in.skydns.test.", Qtype: dns.TypeA, 834 | Answer: []dns.RR{ 835 | newCNAME("backend.in.skydns.test. IN CNAME ipaddr.skydns.test."), 836 | newA("ipaddr.skydns.test. IN A 172.16.1.1"), 837 | newA("ipaddr.skydns.test. IN A 172.16.1.2"), 838 | }, 839 | }, 840 | // Txt 841 | { 842 | Qname: "a1.txt.skydns.test.", Qtype: dns.TypeTXT, 843 | Answer: []dns.RR{ 844 | newTXT("a1.txt.skydns.test. IN TXT \"abc\""), 845 | }, 846 | }, 847 | { 848 | Qname: "a2.txt.skydns.test.", Qtype: dns.TypeTXT, 849 | Answer: []dns.RR{ 850 | newTXT("a2.txt.skydns.test. IN TXT \"abc abc\""), 851 | }, 852 | }, 853 | { 854 | Qname: "txt.skydns.test.", Qtype: dns.TypeTXT, 855 | Answer: []dns.RR{ 856 | newTXT("txt.skydns.test. IN TXT \"abc abc\""), 857 | newTXT("txt.skydns.test. IN TXT \"abc\""), 858 | }, 859 | }, 860 | 861 | // DNSSEC 862 | 863 | // DNSKEY Test 864 | { 865 | dnssec: true, 866 | Qname: "skydns.test.", Qtype: dns.TypeDNSKEY, 867 | Answer: []dns.RR{ 868 | newDNSKEY("skydns.test. 3600 DNSKEY 256 3 5 deadbeaf"), 869 | newRRSIG("skydns.test. 3600 RRSIG DNSKEY 5 2 3600 0 0 51945 skydns.test. deadbeaf"), 870 | }, 871 | Extra: []dns.RR{new(dns.OPT)}, 872 | }, 873 | // Signed Response Test 874 | { 875 | dnssec: true, 876 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, 877 | Answer: []dns.RR{ 878 | newRRSIG("104.server1.development.region1.skydns.test. 3600 RRSIG SRV 5 6 3600 0 0 51945 skydns.test. deadbeaf"), 879 | newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, 880 | Extra: []dns.RR{ 881 | newRRSIG("104.server1.developmen.region1.skydns.test. 3600 RRSIG A 5 6 3600 0 0 51945 skydns.test. deadbeaf"), 882 | newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), 883 | new(dns.OPT), 884 | }, 885 | }, 886 | // Signed Response Test, ask twice to check cache 887 | { 888 | dnssec: true, 889 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeSRV, 890 | Answer: []dns.RR{ 891 | newRRSIG("104.server1.development.region1.skydns.test. 3600 RRSIG SRV 5 6 3600 0 0 51945 skydns.test. deadbeaf"), 892 | newSRV("104.server1.development.region1.skydns.test. 3600 SRV 10 100 0 104.server1.development.region1.skydns.test.")}, 893 | Extra: []dns.RR{ 894 | newRRSIG("104.server1.developmen.region1.skydns.test. 3600 RRSIG A 5 6 3600 0 0 51945 skydns.test. deadbeaf"), 895 | newA("104.server1.development.region1.skydns.test. 3600 A 10.0.0.1"), 896 | new(dns.OPT), 897 | }, 898 | }, 899 | // NXDOMAIN Test 900 | { 901 | dnssec: true, 902 | Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, 903 | Rcode: dns.RcodeNameError, 904 | Ns: []dns.RR{ 905 | newNSEC3("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 NSEC3 1 0 0 - 44OHAQ2NJB0IDNVOLT9GGTHVSK1E1UVA"), 906 | newRRSIG("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 907 | newNSEC3("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 NSEC3 1 0 0 - AH4V7G5QOIRI26ARMRB3BLDQI1SNG6A3 A AAAA SRV RRSIG"), 908 | newRRSIG("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 909 | newNSEC3("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 NSEC3 1 0 0 - LKSD858F4CLDL7EMDORD75K5JEKS49PA"), 910 | newRRSIG("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 911 | newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814205559 20140807175559 51945 skydns.test. deadbeaf"), 912 | newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), 913 | }, 914 | Extra: []dns.RR{new(dns.OPT)}, 915 | }, 916 | // NXDOMAIN Test, cache test 917 | { 918 | dnssec: true, 919 | Qname: "doesnotexist.skydns.test.", Qtype: dns.TypeA, 920 | Rcode: dns.RcodeNameError, 921 | Ns: []dns.RR{ 922 | newNSEC3("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 NSEC3 1 0 0 - 44OHAQ2NJB0IDNVOLT9GGTHVSK1E1UVA"), 923 | newRRSIG("44ohaq2njb0idnvolt9ggthvsk1e1uv8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 924 | newNSEC3("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 NSEC3 1 0 0 - AH4V7G5QOIRI26ARMRB3BLDQI1SNG6A3 A AAAA SRV RRSIG"), 925 | newRRSIG("ah4v7g5qoiri26armrb3bldqi1sng6a2.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 926 | newNSEC3("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 NSEC3 1 0 0 - LKSD858F4CLDL7EMDORD75K5JEKS49PA"), 927 | newRRSIG("lksd858f4cldl7emdord75k5jeks49p8.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814205559 20140807175559 51945 skydns.test. deadbeef"), 928 | newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814205559 20140807175559 51945 skydns.test. deadbeaf"), 929 | newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), 930 | }, 931 | Extra: []dns.RR{new(dns.OPT)}, 932 | }, 933 | // NODATA Test 934 | { 935 | dnssec: true, 936 | Qname: "104.server1.development.region1.skydns.test.", Qtype: dns.TypeTXT, 937 | Rcode: dns.RcodeSuccess, 938 | Ns: []dns.RR{ 939 | newNSEC3("E76CLEL5E7TQHRTFLTBVH0645NEKFJV9.skydns.test. 60 NSEC3 1 0 0 - E76CLEL5E7TQHRTFLTBVH0645NEKFJVA A AAAA SRV RRSIG"), 940 | newRRSIG("E76CLEL5E7TQHRTFLTBVH0645NEKFJV9.skydns.test. 60 RRSIG NSEC3 5 3 3600 20140814211641 20140807181641 51945 skydns.test. deadbeef"), 941 | newRRSIG("skydns.test. 60 RRSIG SOA 5 2 3600 20140814211641 20140807181641 51945 skydns.test. deadbeef"), 942 | newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407445200 28800 7200 604800 60"), 943 | }, 944 | Extra: []dns.RR{new(dns.OPT)}, 945 | }, 946 | // Reverse v4 local answer 947 | { 948 | Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR, 949 | Answer: []dns.RR{newPTR("1.0.0.10.in-addr.arpa. 3600 PTR reverse.example.com.")}, 950 | }, 951 | // Reverse v6 local answer 952 | 953 | // Reverse forwarding answer, TODO(miek) does not work 954 | // { 955 | // Qname: "1.0.16.172.in-addr.arpa.", Qtype: dns.TypePTR, 956 | // Rcode: dns.RcodeNameError, 957 | // Ns: []dns.RR{newSOA("16.172.in-addr.arpa. 10800 SOA localhost. nobody.invalid. 0 0 0 0 0")}, 958 | // }, 959 | 960 | // Reverse no answer 961 | 962 | // Local data query 963 | { 964 | Qname: "local.dns.skydns.test.", Qtype: dns.TypeA, 965 | Answer: []dns.RR{newA("local.dns.skydns.test. 3600 A 10.0.0.1")}, 966 | }, 967 | // Author test 968 | { 969 | Qname: "skydns.test.", Qtype: dns.TypeTXT, 970 | chaos: true, 971 | Answer: []dns.RR{ 972 | newTXT("skydns.test. 0 TXT \"Brian Ketelsen\""), 973 | newTXT("skydns.test. 0 TXT \"Erik St. Martin\""), 974 | newTXT("skydns.test. 0 TXT \"Michael Crosby\""), 975 | newTXT("skydns.test. 0 TXT \"Miek Gieben\""), 976 | }, 977 | }, 978 | // Author test 2 979 | { 980 | Qname: "authors.bind.", Qtype: dns.TypeTXT, 981 | chaos: true, 982 | Answer: []dns.RR{ 983 | newTXT("authors.bind. 0 TXT \"Brian Ketelsen\""), 984 | newTXT("authors.bind. 0 TXT \"Erik St. Martin\""), 985 | newTXT("authors.bind. 0 TXT \"Michael Crosby\""), 986 | newTXT("authors.bind. 0 TXT \"Miek Gieben\""), 987 | }, 988 | }, 989 | // Author test, caps test 990 | { 991 | Qname: "AUTHOrs.BIND.", Qtype: dns.TypeTXT, 992 | chaos: true, 993 | Answer: []dns.RR{ 994 | newTXT("AUTHOrs.BIND. 0 TXT \"Brian Ketelsen\""), 995 | newTXT("AUTHOrs.BIND. 0 TXT \"Erik St. Martin\""), 996 | newTXT("AUTHOrs.BIND. 0 TXT \"Michael Crosby\""), 997 | newTXT("AUTHOrs.BIND. 0 TXT \"Miek Gieben\""), 998 | }, 999 | }, 1000 | // Author test 3, no answer. 1001 | { 1002 | Qname: "local.dns.skydns.test.", Qtype: dns.TypeA, 1003 | Rcode: dns.RcodeServerFailure, 1004 | chaos: true, 1005 | }, 1006 | // HINFO Test, should be nodata for the apex 1007 | { 1008 | Qname: "skydns.test.", Qtype: dns.TypeHINFO, 1009 | Ns: []dns.RR{newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0")}, 1010 | }, 1011 | // One IP, two ports open, ask for the IP only. 1012 | { 1013 | Qname: "bar.skydns.test.", Qtype: dns.TypeA, 1014 | Answer: []dns.RR{ 1015 | newA("bar.skydns.test. 3600 A 192.168.0.1"), 1016 | }, 1017 | }, 1018 | // Then ask for the SRV records. 1019 | { 1020 | Qname: "bar.skydns.test.", Qtype: dns.TypeSRV, 1021 | Answer: []dns.RR{ 1022 | newSRV("bar.skydns.test. 3600 SRV 10 50 443 bar.skydns.local."), 1023 | // Issue 144 says x.bar.skydns.test should be bar.skydns.test 1024 | newSRV("bar.skydns.test. 3600 SRV 10 50 80 bar.skydns.test."), 1025 | }, 1026 | Extra: []dns.RR{ 1027 | newA("bar.skydns.test. 3600 A 192.168.0.1"), 1028 | }, 1029 | }, 1030 | // Duplicate IP address test 1031 | { 1032 | Qname: "multiport.http.skydns.test.", Qtype: dns.TypeA, 1033 | Answer: []dns.RR{newA("multiport.http.skydns.test. IN A 10.11.11.10")}, 1034 | }, 1035 | 1036 | // Casing test 1037 | { 1038 | Qname: "uppeR.skydns.test.", Qtype: dns.TypeA, 1039 | Answer: []dns.RR{newA("uppeR.skydns.test. IN A 127.0.0.1")}, 1040 | }, 1041 | { 1042 | Qname: "upper.skydns.test.", Qtype: dns.TypeA, 1043 | Answer: []dns.RR{newA("upper.skydns.test. IN A 127.0.0.1")}, 1044 | }, 1045 | 1046 | // SRV record with name that is internally resolvable. 1047 | { 1048 | Qname: "1.cname.skydns.test.", Qtype: dns.TypeSRV, 1049 | Answer: []dns.RR{newSRV("1.cname.skydns.test. IN SRV 10 100 0 104.server1.development.region1.skydns.test.")}, 1050 | Extra: []dns.RR{newA("104.server1.development.region1.skydns.test. IN A 10.0.0.1")}, 1051 | }, 1052 | // SRV record with name that is internally resolvable. Get v4 and v6 records. 1053 | { 1054 | Qname: "both.v4v6.test.skydns.test.", Qtype: dns.TypeSRV, 1055 | Answer: []dns.RR{newSRV("both.v4v6.test.skydns.test. IN SRV 10 100 0 ipaddr2.skydns.test.")}, 1056 | Extra: []dns.RR{ 1057 | newA("ipaddr2.skydns.test. IN A 172.16.1.1"), 1058 | newAAAA("ipaddr2.skydns.test. IN AAAA 2001::8:8:8:8"), 1059 | }, 1060 | }, 1061 | // MX Tests 1062 | { 1063 | // NODATA as this is not an Mail: true record. 1064 | Qname: "100.server1.development.region1.skydns.test.", Qtype: dns.TypeMX, 1065 | Ns: []dns.RR{ 1066 | newSOA("skydns.test. 3600 SOA ns.dns.skydns.test. hostmaster.skydns.test. 0 0 0 0 0"), 1067 | }, 1068 | }, 1069 | { 1070 | Qname: "b.mail.skydns.test.", Qtype: dns.TypeMX, 1071 | Answer: []dns.RR{newMX("b.mail.skydns.test. IN MX 50 mx.miek.nl.")}, 1072 | }, 1073 | { 1074 | // See issue #168 1075 | Qname: "a.mail.skydns.test.", Qtype: dns.TypeMX, 1076 | Answer: []dns.RR{newMX("a.mail.skydns.test. IN MX 50 mx.skydns.test.")}, 1077 | Extra: []dns.RR{ 1078 | newA("a.ipaddr.skydns.test. IN A 172.16.1.1"), 1079 | newCNAME("mx.skydns.tests. IN CNAME a.ipaddr.skydns.test."), 1080 | }, 1081 | }, 1082 | { 1083 | Qname: "mx.skydns.test.", Qtype: dns.TypeMX, 1084 | Answer: []dns.RR{ 1085 | newMX("mx.skydns.test. IN MX 30 a.ipaddr.skydns.test."), 1086 | }, 1087 | Extra: []dns.RR{ 1088 | newA("a.ipaddr.skydns.test. A 172.16.1.1"), 1089 | }, 1090 | }, 1091 | // Double CNAMEs in the additional 1092 | { 1093 | Qname: "a.mail2.skydns.test.", Qtype: dns.TypeMX, 1094 | Answer: []dns.RR{ 1095 | newMX("a.mail2.skydns.test. IN MX 50 mx2.skydns.test."), 1096 | }, 1097 | Extra: []dns.RR{ 1098 | newA("a.ipaddr.skydns.test. A 172.16.1.1"), 1099 | newA("b.ipaddr.skydns.test. A 172.16.1.2"), 1100 | // only one CNAME can be here, if we round-robin we randomly choose 1101 | // without it, pick the first 1102 | newCNAME("mx2.skydns.test. CNAME b.ipaddr.skydns.test."), 1103 | }, 1104 | }, 1105 | // Groups 1106 | { 1107 | // hits the group 'g1' and only includes those records 1108 | Qname: "dom.skydns.test.", Qtype: dns.TypeA, 1109 | Answer: []dns.RR{ 1110 | newA("dom.skydns.test. IN A 127.0.0.1"), 1111 | newA("dom.skydns.test. IN A 127.0.0.2"), 1112 | }, 1113 | }, 1114 | { 1115 | // One has group, the other has not... Include the non-group always. 1116 | Qname: "dom2.skydns.test.", Qtype: dns.TypeA, 1117 | Answer: []dns.RR{ 1118 | newA("dom2.skydns.test. IN A 127.0.0.1"), 1119 | newA("dom2.skydns.test. IN A 127.0.0.2"), 1120 | }, 1121 | }, 1122 | { 1123 | // The groups differ. 1124 | Qname: "dom1.skydns.test.", Qtype: dns.TypeA, 1125 | Answer: []dns.RR{ 1126 | newA("dom1.skydns.test. IN A 127.0.0.1"), 1127 | }, 1128 | }, 1129 | } 1130 | 1131 | func newA(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } 1132 | func newAAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } 1133 | func newCNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } 1134 | func newSRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } 1135 | func newSOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } 1136 | func newNS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } 1137 | func newDNSKEY(rr string) *dns.DNSKEY { r, _ := dns.NewRR(rr); return r.(*dns.DNSKEY) } 1138 | func newRRSIG(rr string) *dns.RRSIG { r, _ := dns.NewRR(rr); return r.(*dns.RRSIG) } 1139 | func newNSEC3(rr string) *dns.NSEC3 { r, _ := dns.NewRR(rr); return r.(*dns.NSEC3) } 1140 | func newPTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } 1141 | func newTXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } 1142 | func newMX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } 1143 | 1144 | func TestDedup(t *testing.T) { 1145 | m := new(dns.Msg) 1146 | m.Answer = []dns.RR{ 1147 | newA("svc.ns.kubernetes.local. IN A 3.3.3.3"), 1148 | newA("svc.ns.kubernetes.local. IN A 2.2.2.2"), 1149 | newA("svc.ns.kubernetes.local. IN A 3.3.3.3"), 1150 | newA("svc.ns.kubernetes.local. IN A 2.2.2.2"), 1151 | newA("svc.ns.kubernetes.local. IN A 1.1.1.1"), 1152 | newA("svc.ns.kubernetes.local. IN A 1.1.1.1"), 1153 | } 1154 | s := &server{} 1155 | m = s.dedup(m) 1156 | sort.Sort(rrSet(m.Answer)) 1157 | if len(m.Answer) != 3 { 1158 | t.Fatalf("failing dedup: should have collapsed it to 3 records") 1159 | } 1160 | if dns.Field(m.Answer[0], 1) != "1.1.1.1" || dns.Field(m.Answer[1], 1) != "2.2.2.2" || 1161 | dns.Field(m.Answer[2], 1) != "3.3.3.3" { 1162 | t.Fatalf("failing dedup: %s", m) 1163 | } 1164 | } 1165 | 1166 | func TestTargetStripAdditional(t *testing.T) { 1167 | s := newTestServer(t, false) 1168 | defer s.Stop() 1169 | 1170 | c := new(dns.Client) 1171 | m := new(dns.Msg) 1172 | 1173 | pre := "bliep." 1174 | expected := "blaat.skydns.test." 1175 | serv := &msg.Service{ 1176 | Host: "199.43.132.53", Key: pre + expected, TargetStrip: 1, Text: "Text", 1177 | } 1178 | addService(t, s, serv.Key, 0, serv) 1179 | defer delService(t, s, serv.Key) 1180 | 1181 | m.SetQuestion(pre+expected, dns.TypeSRV) 1182 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 1183 | if err != nil { 1184 | t.Fatal(err) 1185 | } 1186 | t.Logf("%s", resp.String()) 1187 | if resp.Extra[0].Header().Name != expected { 1188 | t.Fatalf("expected %s, got %s for SRV with v4", expected, resp.Extra[0].Header().Name) 1189 | } 1190 | 1191 | serv = &msg.Service{ 1192 | Host: "2001::1", Key: pre + expected, TargetStrip: 1, Text: "Text", 1193 | } 1194 | delService(t, s, serv.Key) 1195 | // previous defer still stands 1196 | addService(t, s, serv.Key, 0, serv) 1197 | 1198 | m.SetQuestion(pre+expected, dns.TypeSRV) 1199 | resp, _, err = c.Exchange(m, "127.0.0.1:"+StrPort) 1200 | if err != nil { 1201 | t.Fatal(err) 1202 | } 1203 | t.Logf("%s", resp.String()) 1204 | if resp.Extra[0].Header().Name != expected { 1205 | t.Fatalf("expected %s, got %s for SRV with v6", expected, resp.Extra[0].Header().Name) 1206 | } 1207 | } 1208 | 1209 | func TestMsgOverflow(t *testing.T) { 1210 | if testing.Short() { 1211 | t.Skip("skipping test in short mode.") 1212 | } 1213 | 1214 | s := newTestServer(t, false) 1215 | defer s.Stop() 1216 | 1217 | c := new(dns.Client) 1218 | m := new(dns.Msg) 1219 | 1220 | for i := 0; i < 2000; i++ { 1221 | is := strconv.Itoa(i) 1222 | m := &msg.Service{ 1223 | Host: "2001::" + is, Key: "machine" + is + ".machines.skydns.test.", 1224 | } 1225 | addService(t, s, m.Key, 0, m) 1226 | defer delService(t, s, m.Key) 1227 | } 1228 | m.SetQuestion("machines.skydns.test.", dns.TypeSRV) 1229 | resp, _, err := c.Exchange(m, "127.0.0.1:"+StrPort) 1230 | if err != nil { 1231 | // Unpack can fail, and it should (i.e. msg too large) 1232 | t.Logf("%s", err) 1233 | return 1234 | } 1235 | t.Logf("%s", resp) 1236 | 1237 | if resp.Rcode != dns.RcodeSuccess { 1238 | t.Fatalf("expecting server failure, got %d", resp.Rcode) 1239 | } 1240 | } 1241 | 1242 | func BenchmarkDNSSingleCache(b *testing.B) { 1243 | b.StopTimer() 1244 | t := new(testing.T) 1245 | s := newTestServerDNSSEC(t, true) 1246 | defer s.Stop() 1247 | 1248 | serv := services[0] 1249 | addService(t, s, serv.Key, 0, serv) 1250 | defer delService(t, s, serv.Key) 1251 | 1252 | c := new(dns.Client) 1253 | tc := dnsTestCases[0] 1254 | m := new(dns.Msg) 1255 | m.SetQuestion(tc.Qname, tc.Qtype) 1256 | 1257 | b.StartTimer() 1258 | for i := 0; i < b.N; i++ { 1259 | c.Exchange(m, "127.0.0.1:"+StrPort) 1260 | } 1261 | } 1262 | 1263 | func BenchmarkDNSWildcardCache(b *testing.B) { 1264 | b.StopTimer() 1265 | t := new(testing.T) 1266 | s := newTestServerDNSSEC(t, true) 1267 | defer s.Stop() 1268 | 1269 | for _, serv := range services { 1270 | m := &msg.Service{Host: serv.Host, Port: serv.Port} 1271 | addService(t, s, serv.Key, 0, m) 1272 | defer delService(t, s, serv.Key) 1273 | } 1274 | 1275 | c := new(dns.Client) 1276 | tc := dnsTestCases[8] // Wildcard Test 1277 | m := new(dns.Msg) 1278 | m.SetQuestion(tc.Qname, tc.Qtype) 1279 | 1280 | b.StartTimer() 1281 | for i := 0; i < b.N; i++ { 1282 | c.Exchange(m, "127.0.0.1:"+StrPort) 1283 | } 1284 | } 1285 | 1286 | func BenchmarkDNSSECSingleCache(b *testing.B) { 1287 | b.StopTimer() 1288 | t := new(testing.T) 1289 | s := newTestServerDNSSEC(t, true) 1290 | defer s.Stop() 1291 | 1292 | serv := services[0] 1293 | addService(t, s, serv.Key, 0, serv) 1294 | defer delService(t, s, serv.Key) 1295 | 1296 | c := new(dns.Client) 1297 | tc := dnsTestCases[0] 1298 | m := new(dns.Msg) 1299 | m.SetQuestion(tc.Qname, tc.Qtype) 1300 | m.SetEdns0(4096, true) 1301 | 1302 | b.StartTimer() 1303 | for i := 0; i < b.N; i++ { 1304 | c.Exchange(m, "127.0.0.1:"+StrPort) 1305 | } 1306 | } 1307 | 1308 | func BenchmarkDNSSingleNoCache(b *testing.B) { 1309 | b.StopTimer() 1310 | t := new(testing.T) 1311 | s := newTestServerDNSSEC(t, false) 1312 | defer s.Stop() 1313 | 1314 | serv := services[0] 1315 | addService(t, s, serv.Key, 0, serv) 1316 | defer delService(t, s, serv.Key) 1317 | 1318 | c := new(dns.Client) 1319 | tc := dnsTestCases[0] 1320 | m := new(dns.Msg) 1321 | m.SetQuestion(tc.Qname, tc.Qtype) 1322 | 1323 | b.StartTimer() 1324 | for i := 0; i < b.N; i++ { 1325 | c.Exchange(m, "127.0.0.1:"+StrPort) 1326 | } 1327 | } 1328 | 1329 | func BenchmarkDNSWildcardNoCache(b *testing.B) { 1330 | b.StopTimer() 1331 | t := new(testing.T) 1332 | s := newTestServerDNSSEC(t, false) 1333 | defer s.Stop() 1334 | 1335 | for _, serv := range services { 1336 | m := &msg.Service{Host: serv.Host, Port: serv.Port} 1337 | addService(t, s, serv.Key, 0, m) 1338 | defer delService(t, s, serv.Key) 1339 | } 1340 | 1341 | c := new(dns.Client) 1342 | tc := dnsTestCases[8] // Wildcard Test 1343 | m := new(dns.Msg) 1344 | m.SetQuestion(tc.Qname, tc.Qtype) 1345 | 1346 | b.StartTimer() 1347 | for i := 0; i < b.N; i++ { 1348 | c.Exchange(m, "127.0.0.1:"+StrPort) 1349 | } 1350 | } 1351 | 1352 | func BenchmarkDNSSECSingleNoCache(b *testing.B) { 1353 | b.StopTimer() 1354 | t := new(testing.T) 1355 | s := newTestServerDNSSEC(t, false) 1356 | defer s.Stop() 1357 | 1358 | serv := services[0] 1359 | addService(t, s, serv.Key, 0, serv) 1360 | defer delService(t, s, serv.Key) 1361 | 1362 | c := new(dns.Client) 1363 | tc := dnsTestCases[0] 1364 | m := new(dns.Msg) 1365 | m.SetQuestion(tc.Qname, tc.Qtype) 1366 | m.SetEdns0(4096, true) 1367 | 1368 | b.StartTimer() 1369 | for i := 0; i < b.N; i++ { 1370 | c.Exchange(m, "127.0.0.1:"+StrPort) 1371 | } 1372 | } 1373 | --------------------------------------------------------------------------------