diff --git a/middleware/etcd/etcd.go b/middleware/etcd/etcd.go index d1e6bfadf..195bc6f33 100644 --- a/middleware/etcd/etcd.go +++ b/middleware/etcd/etcd.go @@ -17,7 +17,7 @@ import ( type Etcd struct { Next middleware.Handler Zones []string - Proxy proxy.Proxy + Proxy proxy.Proxy // Proxy for looking up names during the resolution process Client etcdc.KeysAPI Ctx context.Context Inflight *singleflight.Group diff --git a/middleware/etcd/etcd.md b/middleware/etcd/etcd.md index 1986b5ca3..1e91dc337 100644 --- a/middleware/etcd/etcd.md +++ b/middleware/etcd/etcd.md @@ -4,6 +4,9 @@ a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26) like [SkyDNS](https//github.com/skynetservices/skydns). +The etcd middleware makes extensive use of the proxy middleware to forward and query +other servers in the network. + ## Syntax ~~~ diff --git a/middleware/etcd/lookup.go b/middleware/etcd/lookup.go index 0c57e3632..dd975a2ad 100644 --- a/middleware/etcd/lookup.go +++ b/middleware/etcd/lookup.go @@ -28,7 +28,6 @@ func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) ( // x CNAME x is a direct loop, don't add those continue } - println("TRYING TO ADD CNAME", len(previousRecords)) newRecord := serv.NewCNAME(state.QName(), serv.Host) if len(previousRecords) > 7 { @@ -54,7 +53,6 @@ func (e Etcd) A(zone string, state middleware.State, previousRecords []dns.RR) ( target := newRecord.Target if dns.IsSubDomain(zone, target) { // We should already have found it - println("DIDN'T FOUND IT") continue } m1, e1 := e.Proxy.Lookup(state, target, state.QType()) diff --git a/middleware/etcd/lookup_test.go b/middleware/etcd/lookup_test.go index ead0d1925..8dff78fe6 100644 --- a/middleware/etcd/lookup_test.go +++ b/middleware/etcd/lookup_test.go @@ -7,128 +7,10 @@ package etcd // names. import ( - "encoding/json" - "sort" - "testing" - "time" - - "github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware/etcd/msg" - "github.com/miekg/coredns/middleware/etcd/singleflight" - "github.com/miekg/coredns/middleware/proxy" "github.com/miekg/dns" - - etcdc "github.com/coreos/etcd/client" - "golang.org/x/net/context" ) -var ( - etc Etcd - client etcdc.KeysAPI - ctx context.Context -) - -type Section int - -const ( - Answer Section = iota - Ns - Extra -) - -func init() { - ctx = context.TODO() - - etcdCfg := etcdc.Config{ - Endpoints: []string{"http://localhost:2379"}, - } - cli, _ := etcdc.New(etcdCfg) - etc = Etcd{ - Proxy: proxy.New([]string{"8.8.8.8:53"}), - PathPrefix: "skydns", - Ctx: context.Background(), - Inflight: &singleflight.Group{}, - Zones: []string{"skydns.test."}, - Client: etcdc.NewKeysAPI(cli), - } -} - -func set(t *testing.T, e Etcd, k string, ttl time.Duration, m *msg.Service) { - b, err := json.Marshal(m) - if err != nil { - t.Fatal(err) - } - path, _ := e.PathWithWildcard(k) - e.Client.Set(ctx, path, string(b), &etcdc.SetOptions{TTL: ttl}) -} - -func delete(t *testing.T, e Etcd, k string) { - path, _ := e.PathWithWildcard(k) - e.Client.Delete(ctx, path, &etcdc.DeleteOptions{Recursive: false}) -} - -type rrSet []dns.RR - -func (p rrSet) Len() int { return len(p) } -func (p rrSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p rrSet) Less(i, j int) bool { return p[i].String() < p[j].String() } - -func TestLookup(t *testing.T) { - for _, serv := range services { - set(t, etc, serv.Key, 0, serv) - defer delete(t, etc, serv.Key) - } - for _, tc := range dnsTestCases { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) - - rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) - _, err := etc.ServeDNS(ctx, rec, m) - if err != nil { - t.Errorf("expected no error, got %v\n", err) - return - } - resp := rec.Reply() - - sort.Sort(rrSet(resp.Answer)) - sort.Sort(rrSet(resp.Ns)) - sort.Sort(rrSet(resp.Extra)) - - t.Logf("%v\n", resp) - - if resp.Rcode != tc.Rcode { - t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) - continue - } - - if len(resp.Answer) != len(tc.Answer) { - t.Errorf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) - continue - } - if len(resp.Ns) != len(tc.Ns) { - t.Errorf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) - continue - } - if len(resp.Extra) != len(tc.Extra) { - t.Errorf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) - continue - } - - checkSection(t, tc, Answer, resp.Answer) - checkSection(t, tc, Ns, resp.Ns) - checkSection(t, tc, Extra, resp.Extra) - } -} - -type dnsTestCase struct { - Qname string - Qtype uint16 - Rcode int - Answer []dns.RR - Ns []dns.RR - Extra []dns.RR -} - // Note the key is encoded as DNS name, while in "reality" it is a etcd path. var services = []*msg.Service{ {Host: "server1", Port: 8080, Key: "a.server1.dev.region1.skydns.test."}, @@ -136,12 +18,18 @@ var services = []*msg.Service{ {Host: "10.0.0.2", Port: 8080, Key: "b.server1.prod.region1.skydns.test."}, {Host: "::1", Port: 8080, Key: "b.server6.prod.region1.skydns.test."}, - // CNAME dedup Test + // CNAME dedup {Host: "www.miek.nl", Key: "a.miek.nl.skydns.test."}, {Host: "www.miek.nl", Key: "b.miek.nl.skydns.test."}, // Unresolvable internal name {Host: "unresolvable.skydns.test", Key: "cname.prod.region1.skydns.test."}, + // priority + {Host: "server1", Priority: 333, Port: 8080, Key: "priority.skydns.test."}, + // Subdomain + {Host: "server1", Port: 0, Key: "a.sub.region1.skydns.test."}, + {Host: "server2", Port: 80, Key: "b.sub.region1.skydns.test."}, + {Host: "10.0.0.1", Port: 8080, Key: "c.sub.region1.skydns.test."}, } var dnsTestCases = []dnsTestCase{ @@ -182,6 +70,21 @@ var dnsTestCases = []dnsTestCase{ newA("server1.prod.region1.skydns.test. 300 A 10.0.0.2"), }, }, + // Priority Test + { + Qname: "priority.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{newSRV("priority.skydns.test. 300 SRV 333 100 8080 server1.")}, + }, + // Subdomain Test + { + Qname: "sub.region1.skydns.test.", Qtype: dns.TypeSRV, + Answer: []dns.RR{ + newSRV("sub.region1.skydns.test. 300 IN SRV 10 33 0 server1."), + newSRV("sub.region1.skydns.test. 300 IN SRV 10 33 80 server2."), + newSRV("sub.region1.skydns.test. 300 IN SRV 10 33 8080 c.sub.region1.skydns.test."), + }, + Extra: []dns.RR{newA("c.sub.region1.skydns.test. 300 IN A 10.0.0.1")}, + }, // Multi SRV with the same target, should be dedupped. { Qname: "*.miek.nl.skydns.test.", Qtype: dns.TypeSRV, @@ -223,29 +126,6 @@ var dnsTestCases = []dnsTestCase{ Answer: []dns.RR{}, Ns: []dns.RR{newSOA("skydns.test. 60 SOA ns.dns.skydns.test. hostmaster.skydns.test. 1407441600 28800 7200 604800 60")}, }, - // Priority Test - { - Qname: "region6.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{newSRV("region6.skydns.test. 300 SRV 333 100 80 server4.")}, - }, - // Subdomain Test - { - Qname: "region1.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - newSRV("region1.skydns.test. 300 SRV 10 33 0 104.server1.dev.region1.skydns.test."), - newSRV("region1.skydns.test. 300 SRV 10 33 80 server2"), - newSRV("region1.skydns.test. 300 SRV 10 33 8080 server1.")}, - Extra: []dns.RR{newA("104.server1.dev.region1.skydns.test. 300 A 10.0.0.1")}, - }, - // Subdomain Weight Test - { - Qname: "region5.skydns.test.", Qtype: dns.TypeSRV, - Answer: []dns.RR{ - newSRV("region5.skydns.test. 300 SRV 10 22 0 server2."), - newSRV("region5.skydns.test. 300 SRV 10 36 0 server1."), - newSRV("region5.skydns.test. 300 SRV 10 41 0 server3."), - newSRV("region5.skydns.test. 300 SRV 30 100 0 server4.")}, - }, // Wildcard Test { Qname: "*.region1.skydns.test.", Qtype: dns.TypeSRV, @@ -299,99 +179,3 @@ var dnsTestCases = []dnsTestCase{ }, */ } - -func newA(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } -func newAAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } -func newCNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } -func newSRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } -func newSOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } -func newNS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } -func newPTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } -func newTXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } -func newMX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } - -func checkSection(t *testing.T, tc dnsTestCase, sect Section, rr []dns.RR) { - section := []dns.RR{} - switch sect { - case 0: - section = tc.Answer - case 1: - section = tc.Ns - case 2: - section = tc.Extra - } - - for i, a := range rr { - if a.Header().Name != section[i].Header().Name { - t.Errorf("answer %d should have a Header Name of %q, but has %q", i, section[i].Header().Name, a.Header().Name) - continue - } - // 303 signals: don't care what the ttl is. - if section[i].Header().Ttl != 303 && a.Header().Ttl != section[i].Header().Ttl { - t.Errorf("Answer %d should have a Header TTL of %d, but has %d", i, section[i].Header().Ttl, a.Header().Ttl) - continue - } - if a.Header().Rrtype != section[i].Header().Rrtype { - t.Errorf("answer %d should have a header rr type of %d, but has %d", i, section[i].Header().Rrtype, a.Header().Rrtype) - continue - } - - switch x := a.(type) { - case *dns.SRV: - if x.Priority != section[i].(*dns.SRV).Priority { - t.Errorf("answer %d should have a Priority of %d, but has %d", i, section[i].(*dns.SRV).Priority, x.Priority) - } - if x.Weight != section[i].(*dns.SRV).Weight { - t.Errorf("answer %d should have a Weight of %d, but has %d", i, section[i].(*dns.SRV).Weight, x.Weight) - } - if x.Port != section[i].(*dns.SRV).Port { - t.Errorf("answer %d should have a Port of %d, but has %d", i, section[i].(*dns.SRV).Port, x.Port) - } - if x.Target != section[i].(*dns.SRV).Target { - t.Errorf("answer %d should have a Target of %q, but has %q", i, section[i].(*dns.SRV).Target, x.Target) - } - case *dns.A: - if x.A.String() != section[i].(*dns.A).A.String() { - t.Errorf("answer %d should have a Address of %q, but has %q", i, section[i].(*dns.A).A.String(), x.A.String()) - } - case *dns.AAAA: - if x.AAAA.String() != section[i].(*dns.AAAA).AAAA.String() { - t.Errorf("answer %d should have a Address of %q, but has %q", i, section[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) - } - case *dns.TXT: - for j, txt := range x.Txt { - if txt != section[i].(*dns.TXT).Txt[j] { - t.Errorf("answer %d should have a Txt of %q, but has %q", i, section[i].(*dns.TXT).Txt[j], txt) - } - } - case *dns.SOA: - tt := section[i].(*dns.SOA) - if x.Ns != tt.Ns { - t.Errorf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) - } - case *dns.PTR: - tt := section[i].(*dns.PTR) - if x.Ptr != tt.Ptr { - t.Errorf("PTR ptr should be %q, but is %q", x.Ptr, tt.Ptr) - } - case *dns.CNAME: - tt := section[i].(*dns.CNAME) - if x.Target != tt.Target { - t.Errorf("CNAME target should be %q, but is %q", x.Target, tt.Target) - } - case *dns.MX: - tt := section[i].(*dns.MX) - if x.Mx != tt.Mx { - t.Errorf("MX Mx should be %q, but is %q", x.Mx, tt.Mx) - } - if x.Preference != tt.Preference { - t.Errorf("MX Preference should be %q, but is %q", x.Preference, tt.Preference) - } - case *dns.NS: - tt := section[i].(*dns.NS) - if x.Ns != tt.Ns { - t.Errorf("NS nameserver should be %q, but is %q", x.Ns, tt.Ns) - } - } - } -} diff --git a/middleware/etcd/setup_test.go b/middleware/etcd/setup_test.go new file mode 100644 index 000000000..21a215f6f --- /dev/null +++ b/middleware/etcd/setup_test.go @@ -0,0 +1,247 @@ +package etcd + +// etcd needs to be running on http://127.0.0.1:2379 +// *and* needs connectivity to the internet for remotely resolving +// names. + +import ( + "encoding/json" + "sort" + "testing" + "time" + + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/etcd/msg" + "github.com/miekg/coredns/middleware/etcd/singleflight" + "github.com/miekg/coredns/middleware/proxy" + "github.com/miekg/dns" + + etcdc "github.com/coreos/etcd/client" + "golang.org/x/net/context" +) + +var ( + etc Etcd + client etcdc.KeysAPI + ctx context.Context +) + +type Section int + +const ( + Answer Section = iota + Ns + Extra +) + +func init() { + ctx = context.TODO() + + etcdCfg := etcdc.Config{ + Endpoints: []string{"http://localhost:2379"}, + } + cli, _ := etcdc.New(etcdCfg) + etc = Etcd{ + Proxy: proxy.New([]string{"8.8.8.8:53"}), + PathPrefix: "skydns", + Ctx: context.Background(), + Inflight: &singleflight.Group{}, + Zones: []string{"skydns.test."}, + Client: etcdc.NewKeysAPI(cli), + } +} + +func set(t *testing.T, e Etcd, k string, ttl time.Duration, m *msg.Service) { + b, err := json.Marshal(m) + if err != nil { + t.Fatal(err) + } + path, _ := e.PathWithWildcard(k) + e.Client.Set(ctx, path, string(b), &etcdc.SetOptions{TTL: ttl}) +} + +func delete(t *testing.T, e Etcd, k string) { + path, _ := e.PathWithWildcard(k) + e.Client.Delete(ctx, path, &etcdc.DeleteOptions{Recursive: false}) +} + +type rrSet []dns.RR + +func (p rrSet) Len() int { return len(p) } +func (p rrSet) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p rrSet) Less(i, j int) bool { return p[i].String() < p[j].String() } + +func TestLookup(t *testing.T) { + for _, serv := range services { + set(t, etc, serv.Key, 0, serv) + defer delete(t, etc, serv.Key) + } + for _, tc := range dnsTestCases { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(tc.Qname), tc.Qtype) + + rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{}) + _, err := etc.ServeDNS(ctx, rec, m) + if err != nil { + t.Errorf("expected no error, got %v\n", err) + return + } + resp := rec.Reply() + + sort.Sort(rrSet(resp.Answer)) + sort.Sort(rrSet(resp.Ns)) + sort.Sort(rrSet(resp.Extra)) + + if resp.Rcode != tc.Rcode { + t.Errorf("rcode is %q, expected %q", dns.RcodeToString[resp.Rcode], dns.RcodeToString[tc.Rcode]) + t.Logf("%v\n", resp) + continue + } + + if len(resp.Answer) != len(tc.Answer) { + t.Errorf("answer for %q contained %d results, %d expected", tc.Qname, len(resp.Answer), len(tc.Answer)) + t.Logf("%v\n", resp) + continue + } + if len(resp.Ns) != len(tc.Ns) { + t.Errorf("authority for %q contained %d results, %d expected", tc.Qname, len(resp.Ns), len(tc.Ns)) + t.Logf("%v\n", resp) + continue + } + if len(resp.Extra) != len(tc.Extra) { + t.Errorf("additional for %q contained %d results, %d expected", tc.Qname, len(resp.Extra), len(tc.Extra)) + t.Logf("%v\n", resp) + continue + } + + if !checkSection(t, tc, Answer, resp.Answer) { + t.Logf("%v\n", resp) + } + if !checkSection(t, tc, Ns, resp.Ns) { + t.Logf("%v\n", resp) + + } + if !checkSection(t, tc, Extra, resp.Extra) { + t.Logf("%v\n", resp) + } + } +} + +type dnsTestCase struct { + Qname string + Qtype uint16 + Rcode int + Answer []dns.RR + Ns []dns.RR + Extra []dns.RR +} + +func newA(rr string) *dns.A { r, _ := dns.NewRR(rr); return r.(*dns.A) } +func newAAAA(rr string) *dns.AAAA { r, _ := dns.NewRR(rr); return r.(*dns.AAAA) } +func newCNAME(rr string) *dns.CNAME { r, _ := dns.NewRR(rr); return r.(*dns.CNAME) } +func newSRV(rr string) *dns.SRV { r, _ := dns.NewRR(rr); return r.(*dns.SRV) } +func newSOA(rr string) *dns.SOA { r, _ := dns.NewRR(rr); return r.(*dns.SOA) } +func newNS(rr string) *dns.NS { r, _ := dns.NewRR(rr); return r.(*dns.NS) } +func newPTR(rr string) *dns.PTR { r, _ := dns.NewRR(rr); return r.(*dns.PTR) } +func newTXT(rr string) *dns.TXT { r, _ := dns.NewRR(rr); return r.(*dns.TXT) } +func newMX(rr string) *dns.MX { r, _ := dns.NewRR(rr); return r.(*dns.MX) } + +func checkSection(t *testing.T, tc dnsTestCase, sect Section, rr []dns.RR) bool { + section := []dns.RR{} + switch sect { + case 0: + section = tc.Answer + case 1: + section = tc.Ns + case 2: + section = tc.Extra + } + + for i, a := range rr { + if a.Header().Name != section[i].Header().Name { + t.Errorf("answer %d should have a Header Name of %q, but has %q", i, section[i].Header().Name, a.Header().Name) + return false + } + // 303 signals: don't care what the ttl is. + if section[i].Header().Ttl != 303 && a.Header().Ttl != section[i].Header().Ttl { + t.Errorf("Answer %d should have a Header TTL of %d, but has %d", i, section[i].Header().Ttl, a.Header().Ttl) + return false + } + if a.Header().Rrtype != section[i].Header().Rrtype { + t.Errorf("answer %d should have a header rr type of %d, but has %d", i, section[i].Header().Rrtype, a.Header().Rrtype) + return false + } + + switch x := a.(type) { + case *dns.SRV: + if x.Priority != section[i].(*dns.SRV).Priority { + t.Errorf("answer %d should have a Priority of %d, but has %d", i, section[i].(*dns.SRV).Priority, x.Priority) + return false + } + if x.Weight != section[i].(*dns.SRV).Weight { + t.Errorf("answer %d should have a Weight of %d, but has %d", i, section[i].(*dns.SRV).Weight, x.Weight) + return false + } + if x.Port != section[i].(*dns.SRV).Port { + t.Errorf("answer %d should have a Port of %d, but has %d", i, section[i].(*dns.SRV).Port, x.Port) + return false + } + if x.Target != section[i].(*dns.SRV).Target { + t.Errorf("answer %d should have a Target of %q, but has %q", i, section[i].(*dns.SRV).Target, x.Target) + return false + } + case *dns.A: + if x.A.String() != section[i].(*dns.A).A.String() { + t.Errorf("answer %d should have a Address of %q, but has %q", i, section[i].(*dns.A).A.String(), x.A.String()) + return false + } + case *dns.AAAA: + if x.AAAA.String() != section[i].(*dns.AAAA).AAAA.String() { + t.Errorf("answer %d should have a Address of %q, but has %q", i, section[i].(*dns.AAAA).AAAA.String(), x.AAAA.String()) + return false + } + case *dns.TXT: + for j, txt := range x.Txt { + if txt != section[i].(*dns.TXT).Txt[j] { + t.Errorf("answer %d should have a Txt of %q, but has %q", i, section[i].(*dns.TXT).Txt[j], txt) + return false + } + } + case *dns.SOA: + tt := section[i].(*dns.SOA) + if x.Ns != tt.Ns { + t.Errorf("SOA nameserver should be %q, but is %q", x.Ns, tt.Ns) + return false + } + case *dns.PTR: + tt := section[i].(*dns.PTR) + if x.Ptr != tt.Ptr { + t.Errorf("PTR ptr should be %q, but is %q", x.Ptr, tt.Ptr) + return false + } + case *dns.CNAME: + tt := section[i].(*dns.CNAME) + if x.Target != tt.Target { + t.Errorf("CNAME target should be %q, but is %q", x.Target, tt.Target) + return false + } + case *dns.MX: + tt := section[i].(*dns.MX) + if x.Mx != tt.Mx { + t.Errorf("MX Mx should be %q, but is %q", x.Mx, tt.Mx) + return false + } + if x.Preference != tt.Preference { + t.Errorf("MX Preference should be %q, but is %q", x.Preference, tt.Preference) + return false + } + case *dns.NS: + tt := section[i].(*dns.NS) + if x.Ns != tt.Ns { + t.Errorf("NS nameserver should be %q, but is %q", x.Ns, tt.Ns) + return false + } + } + } + return true +} diff --git a/middleware/etcd/stub.go b/middleware/etcd/stub.go new file mode 100644 index 000000000..aae44df12 --- /dev/null +++ b/middleware/etcd/stub.go @@ -0,0 +1,107 @@ +package etcd + +import ( + "net" + "strconv" + "strings" + + "github.com/miekg/dns" +) + +// hasStubEdns0 checks if the message is carrying our special +// edns0 zero option. +func hasStubEdns0(m *dns.Msg) bool { + option := m.IsEdns0() + if option == nil { + return false + } + for _, o := range option.Option { + if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 && + o.(*dns.EDNS0_LOCAL).Data[0] == 1 { + return true + } + } + return false +} + +// addStubEdns0 adds our special option to the message's OPT record. +func addStubEdns0(m *dns.Msg) *dns.Msg { + option := m.IsEdns0() + // Add a custom EDNS0 option to the packet, so we can detect loops + // when 2 stubs are forwarding to each other. + if option != nil { + option.Option = append(option.Option, &dns.EDNS0_LOCAL{ednsStubCode, []byte{1}}) + } else { + m.Extra = append(m.Extra, ednsStub) + } + return m +} + +// Look in .../dns/stub//xx for msg.Services. Loop through them +// extract and add them as forwarders (ip:port-combos) for +// the stub zones. Only numeric (i.e. IP address) hosts are used. +// TODO(miek): makes this Startup Function. +func (e Etcd) UpdateStubZones(zone string) error { + stubmap := make(map[string][]string) + + services, err := e.Records("stub.dns."+zone, false) + if err != nil { + return err + } + for _, serv := range services { + if serv.Port == 0 { + serv.Port = 53 + } + ip := net.ParseIP(serv.Host) + if ip == nil { + //logf("stub zone non-address %s seen for: %s", serv.Key, serv.Host) + continue + } + + domain := e.Domain(serv.Key) + labels := dns.SplitDomainName(domain) + + // If the remaining name equals any of the zones we have, we ignore it. + for _, z := range e.Zones { + // Chop of left most label, because that is used as the nameserver place holder + // and drop the right most labels that belong to zone. + domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(z)], ".")) + if domain == z { + continue + } + stubmap[domain] = append(stubmap[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port))) + } + } + + // TODO(miek): add to etcd structure and startup with a StartFunction + // e.stub = &stubmap + // stubmap contains proxy is best way forward... I think. + // TODO(miek): setup a proxy that forward to these + // StubProxy type? + return nil +} + +func ServeDNSStubForward(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { + if !hasStubEdns0(req) { + return nil + } + req = addStubEdns0(req) + // proxy woxy + return nil +} + +// ednsStub is the EDNS0 record we add to stub queries. Queries which have this record are +// not forwarded again. +var ednsStub = func() *dns.OPT { + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + + e := new(dns.EDNS0_LOCAL) + e.Code = ednsStubCode + e.Data = []byte{1} + o.Option = append(o.Option, e) + return o +}() + +const ednsStubCode = dns.EDNS0LOCALSTART + 10