diff --git a/plugin/backend_lookup.go b/plugin/backend_lookup.go index 096cf806b..9e5c9eeec 100644 --- a/plugin/backend_lookup.go +++ b/plugin/backend_lookup.go @@ -372,6 +372,8 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques // ... and reset state.Req.Question[0].Name = old + seen := map[string]bool{} + for _, serv := range services { what, ip := serv.HostType() switch what { @@ -380,8 +382,13 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques case dns.TypeA, dns.TypeAAAA: serv.Host = msg.Domain(serv.Key) - records = append(records, serv.NewNS(state.QName())) extra = append(extra, newAddress(serv, serv.Host, ip, what)) + ns := serv.NewNS(state.QName()) + if _, ok := seen[ns.Ns]; ok { + continue + } + seen[ns.Ns] = true + records = append(records, ns) } } return records, extra, nil diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go index e7b36917e..68176d78c 100644 --- a/plugin/kubernetes/kubernetes.go +++ b/plugin/kubernetes/kubernetes.go @@ -43,11 +43,10 @@ type Kubernetes struct { Fall fall.F ttl uint32 opts dnsControlOpts - - primaryZoneIndex int - interfaceAddrsFunc func() net.IP - autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. - TransferTo []string + primaryZoneIndex int + localIPs []net.IP + autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. + TransferTo []string } // New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other @@ -56,7 +55,6 @@ func New(zones []string) *Kubernetes { k := new(Kubernetes) k.Zones = zones k.Namespaces = make(map[string]struct{}) - k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("127.0.0.1") } k.podMode = podModeDisabled k.ttl = defaultTTL diff --git a/plugin/kubernetes/kubernetes_apex_test.go b/plugin/kubernetes/kubernetes_apex_test.go index 9a91ea2e2..5d2f4079b 100644 --- a/plugin/kubernetes/kubernetes_apex_test.go +++ b/plugin/kubernetes/kubernetes_apex_test.go @@ -2,6 +2,7 @@ package kubernetes import ( "context" + "net" "testing" "github.com/coredns/coredns/plugin/pkg/dnstest" @@ -63,6 +64,7 @@ func TestServeDNSApex(t *testing.T) { k := New([]string{"cluster.local."}) k.APIConn = &APIConnServeTest{} k.Next = test.NextHandler(dns.RcodeSuccess, nil) + k.localIPs = []net.IP{net.ParseIP("127.0.0.1")} ctx := context.TODO() for i, tc := range kubeApexCases { @@ -85,7 +87,7 @@ func TestServeDNSApex(t *testing.T) { } if err := test.SortAndCheck(resp, tc); err != nil { - t.Error(err) + t.Errorf("Test %d: %v", i, err) } } } diff --git a/plugin/kubernetes/kubernetes_test.go b/plugin/kubernetes/kubernetes_test.go index 4fd8e22a6..b259869a8 100644 --- a/plugin/kubernetes/kubernetes_test.go +++ b/plugin/kubernetes/kubernetes_test.go @@ -310,20 +310,28 @@ func TestServicesAuthority(t *testing.T) { key string } type svcTest struct { - interfaceAddrs func() net.IP - qname string - qtype uint16 - answer *svcAns + localIPs []net.IP + qname string + qtype uint16 + answer []svcAns } tests := []svcTest{ - {interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA, answer: &svcAns{host: "127.0.0.1", key: "/" + coredns + "/test/interwebs/dns/ns"}}, - {interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA}, - {interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA}, - {interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA, answer: &svcAns{host: "::1", key: "/" + coredns + "/test/interwebs/dns/ns"}}, + {localIPs: []net.IP{net.ParseIP("1.2.3.4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "1.2.3.4", key: "/" + coredns + "/test/interwebs/dns/ns"}}}, + {localIPs: []net.IP{net.ParseIP("1.2.3.4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA}, + {localIPs: []net.IP{net.ParseIP("1:2::3:4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA}, + {localIPs: []net.IP{net.ParseIP("1:2::3:4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA, answer: []svcAns{{host: "1:2::3:4", key: "/" + coredns + "/test/interwebs/dns/ns"}}}, + { + localIPs: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("1:2::3:4")}, + qname: "ns.dns.interwebs.test.", + qtype: dns.TypeNS, answer: []svcAns{ + {host: "1.2.3.4", key: "/" + coredns + "/test/interwebs/dns/ns"}, + {host: "1:2::3:4", key: "/" + coredns + "/test/interwebs/dns/ns"}, + }, + }, } for i, test := range tests { - k.interfaceAddrsFunc = test.interfaceAddrs + k.localIPs = test.localIPs state := request.Request{ Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}}, @@ -334,7 +342,7 @@ func TestServicesAuthority(t *testing.T) { t.Errorf("Test %d: got error '%v'", i, e) continue } - if test.answer != nil && len(svcs) != 1 { + if test.answer != nil && len(svcs) != len(test.answer) { t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs)) continue } @@ -347,11 +355,13 @@ func TestServicesAuthority(t *testing.T) { continue } - if test.answer.host != svcs[0].Host { - t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host) - } - if test.answer.key != svcs[0].Key { - t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key) + for i, answer := range test.answer { + if answer.host != svcs[i].Host { + t.Errorf("Test %d, expected host '%v', got '%v'", i, answer.host, svcs[i].Host) + } + if answer.key != svcs[i].Key { + t.Errorf("Test %d, expected key '%v', got '%v'", i, answer.key, svcs[i].Key) + } } } } diff --git a/plugin/kubernetes/local.go b/plugin/kubernetes/local.go index 199af6f0d..d09255061 100644 --- a/plugin/kubernetes/local.go +++ b/plugin/kubernetes/local.go @@ -2,41 +2,54 @@ package kubernetes import ( "net" + + "github.com/caddyserver/caddy" + "github.com/coredns/coredns/core/dnsserver" ) -func localPodIP() net.IP { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil +// boundIPs returns the list of non-loopback IPs that CoreDNS is bound to +func boundIPs(c *caddy.Controller) (ips []net.IP) { + conf := dnsserver.GetConfig(c) + hosts := conf.ListenHosts + if hosts == nil || hosts[0] == "" { + hosts = nil + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil + } + for _, addr := range addrs { + hosts = append(hosts, addr.String()) + } } - - for _, addr := range addrs { - ip, _, _ := net.ParseCIDR(addr.String()) + for _, host := range hosts { + ip, _, _ := net.ParseCIDR(host) ip4 := ip.To4() if ip4 != nil && !ip4.IsLoopback() { - return ip4 + ips = append(ips, ip4) + continue } ip6 := ip.To16() if ip6 != nil && !ip6.IsLoopback() { - return ip6 + ips = append(ips, ip6) } } - return nil + return ips } // LocalNodeName is exclusively used in federation plugin, will be deprecated later. func (k *Kubernetes) LocalNodeName() string { - localIP := k.interfaceAddrsFunc() - if localIP == nil { + if len(k.localIPs) == 0 { return "" } - // Find endpoint matching localIP - for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) { - for _, eps := range ep.Subsets { - for _, addr := range eps.Addresses { - if localIP.Equal(net.ParseIP(addr.IP)) { - return addr.NodeName + // Find fist endpoint matching any localIP + for _, localIP := range k.localIPs { + for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) { + for _, eps := range ep.Subsets { + for _, addr := range eps.Addresses { + if localIP.Equal(net.ParseIP(addr.IP)) { + return addr.NodeName + } } } } diff --git a/plugin/kubernetes/ns.go b/plugin/kubernetes/ns.go index 4774e176f..f21890cc9 100644 --- a/plugin/kubernetes/ns.go +++ b/plugin/kubernetes/ns.go @@ -21,15 +21,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { svcIPs []net.IP ) - // Find the CoreDNS Endpoint - localIP := k.interfaceAddrsFunc() - endpoints := k.APIConn.EpIndexReverse(localIP.String()) + // Find the CoreDNS Endpoints + for _, localIP := range k.localIPs { + endpoints := k.APIConn.EpIndexReverse(localIP.String()) - // If the CoreDNS Endpoint is not found, use the locally bound IP address - if len(endpoints) == 0 { - svcNames = []string{defaultNSName + zone} - svcIPs = []net.IP{localIP} - } else { // Collect IPs for all Services of the Endpoints for _, endpoint := range endpoints { svcs := k.APIConn.SvcIndex(object.ServiceKey(endpoint.Name, endpoint.Namespace)) @@ -59,6 +54,16 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { } } + // If no local IPs matched any endpoints, use the localIPs directly + if len(svcIPs) == 0 { + svcIPs = make([]net.IP, len(k.localIPs)) + svcNames = make([]string, len(k.localIPs)) + for i, localIP := range k.localIPs { + svcNames[i] = defaultNSName + zone + svcIPs[i] = localIP + } + } + // Create an RR slice of collected IPs var rrs []dns.RR rrs = make([]dns.RR, len(svcIPs)) diff --git a/plugin/kubernetes/ns_test.go b/plugin/kubernetes/ns_test.go index af057c254..5f9786a74 100644 --- a/plugin/kubernetes/ns_test.go +++ b/plugin/kubernetes/ns_test.go @@ -111,7 +111,7 @@ func TestNsAddrs(t *testing.T) { k := New([]string{"inter.webs.test."}) k.APIConn = &APIConnTest{} - k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("10.244.0.20") } + k.localIPs = []net.IP{net.ParseIP("10.244.0.20")} cdrs := k.nsAddrs(false, k.Zones[0]) diff --git a/plugin/kubernetes/setup.go b/plugin/kubernetes/setup.go index eb33936fa..1309139d7 100644 --- a/plugin/kubernetes/setup.go +++ b/plugin/kubernetes/setup.go @@ -61,6 +61,12 @@ func setup(c *caddy.Controller) error { return k }) + // get locally bound addresses + c.OnStartup(func() error { + k.localIPs = boundIPs(c) + return nil + }) + return nil } @@ -113,7 +119,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { func ParseStanza(c *caddy.Controller) (*Kubernetes, error) { k8s := New([]string{""}) - k8s.interfaceAddrsFunc = localPodIP k8s.autoPathSearch = searchFromResolvConf() opts := dnsControlOpts{