diff --git a/command/agent/dns.go b/command/agent/dns.go index afcc33cb9796..3effaef7fda8 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -1,6 +1,7 @@ package agent import ( + "encoding/hex" "fmt" "io" "log" @@ -357,6 +358,46 @@ PARSE: query := strings.Join(labels[:n-1], ".") d.preparedQueryLookup(network, datacenter, query, req, resp) + case "addr": + if n != 2 { + goto INVALID + } + + switch len(labels[0]) / 2 { + // IPv4 + case 4: + ip, err := hex.DecodeString(labels[0]) + if err != nil { + goto INVALID + } + + resp.Answer = append(resp.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: qName + d.domain, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: uint32(d.config.NodeTTL / time.Second), + }, + A: ip, + }) + // IPv6 + case 16: + ip, err := hex.DecodeString(labels[0]) + if err != nil { + goto INVALID + } + + resp.Answer = append(resp.Answer, &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: qName + d.domain, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: uint32(d.config.NodeTTL / time.Second), + }, + AAAA: ip, + }) + } + default: // Store the DC, and re-parse datacenter = labels[n-1] @@ -820,8 +861,32 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes // Add the extra record records := d.formatNodeRecord(node.Node, addr, srvRec.Target, dns.TypeANY, ttl) + if records != nil { - resp.Extra = append(resp.Extra, records...) + // Use the node address if it doesn't differ from the service address + if addr == node.Node.Address { + resp.Extra = append(resp.Extra, records...) + } else { + // If it differs from the service address, give a special response in the + // 'addr.consul' domain with the service IP encoded in it. We have to do + // this because we can't put an IP in the target field of an SRV record. + switch record := records[0].(type) { + // IPv4 + case *dns.A: + addr := hex.EncodeToString(record.A) + + // Take the last 8 chars (4 bytes) of the encoded address to avoid junk bytes + srvRec.Target = fmt.Sprintf("%s.addr.%s.%s", addr[len(addr)-(net.IPv4len*2):], dc, d.domain) + record.Hdr.Name = srvRec.Target + resp.Extra = append(resp.Extra, record) + + // IPv6 + case *dns.AAAA: + srvRec.Target = fmt.Sprintf("%s.addr.%s.%s", hex.EncodeToString(record.AAAA), dc, d.domain) + record.Hdr.Name = srvRec.Target + resp.Extra = append(resp.Extra, record) + } + } } } } diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index 09281dbfb2a1..109ccb381652 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -1039,7 +1039,7 @@ func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) { if srvRec.Port != 12345 { t.Fatalf("Bad: %#v", srvRec) } - if srvRec.Target != "foo.node.dc1.consul." { + if srvRec.Target != "7f000002.addr.dc1.consul." { t.Fatalf("Bad: %#v", srvRec) } if srvRec.Hdr.Ttl != 0 { @@ -1050,7 +1050,7 @@ func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) { if !ok { t.Fatalf("Bad: %#v", in.Extra[0]) } - if aRec.Hdr.Name != "foo.node.dc1.consul." { + if aRec.Hdr.Name != "7f000002.addr.dc1.consul." { t.Fatalf("Bad: %#v", in.Extra[0]) } if aRec.A.String() != "127.0.0.2" { @@ -1062,6 +1062,100 @@ func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) { } } +func TestDNS_ServiceLookup_ServiceAddressIPV6(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Address: "2607:20:4005:808::200e", + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "2607002040050808000000000000200e.addr.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "2607002040050808000000000000200e.addr.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.AAAA.String() != "2607:20:4005:808::200e" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + } +} + func TestDNS_ServiceLookup_WanAddress(t *testing.T) { dir1, srv1 := makeDNSServerConfig(t, func(c *Config) { @@ -1157,7 +1251,7 @@ func TestDNS_ServiceLookup_WanAddress(t *testing.T) { if !ok { t.Fatalf("Bad: %#v", in.Extra[0]) } - if aRec.Hdr.Name != "foo.node.dc2.consul." { + if aRec.Hdr.Name != "7f000002.addr.dc2.consul." { t.Fatalf("Bad: %#v", in.Extra[0]) } if aRec.A.String() != "127.0.0.2" { @@ -3193,7 +3287,7 @@ func TestDNS_PreparedQuery_Failover(t *testing.T) { if !ok { t.Fatalf("Bad: %#v", in.Answer[0]) } - if srv.Target != "foo.node.dc2.consul." { + if srv.Target != "7f000002.addr.dc2.consul." { t.Fatalf("Bad: %#v", in.Answer[0]) } @@ -3201,7 +3295,7 @@ func TestDNS_PreparedQuery_Failover(t *testing.T) { if !ok { t.Fatalf("Bad: %#v", in.Extra[0]) } - if a.Hdr.Name != "foo.node.dc2.consul." { + if a.Hdr.Name != "7f000002.addr.dc2.consul." { t.Fatalf("Bad: %#v", in.Extra[0]) } if a.A.String() != "127.0.0.2" { @@ -3399,6 +3493,85 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) { } } +func TestDNS_AddressLookup(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Look up the addresses + cases := map[string]string{ + "7f000001.addr.dc1.consul.": "127.0.0.1", + } + for question, answer := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.A.To4().String() != answer { + t.Fatalf("Bad: %#v", aRec) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + } +} + +func TestDNS_AddressLookupIPV6(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Look up the addresses + cases := map[string]string{ + "2607002040050808000000000000200e.addr.consul.": "2607:20:4005:808::200e", + "2607112040051808ffffffffffff200e.addr.consul.": "2607:1120:4005:1808:ffff:ffff:ffff:200e", + } + for question, answer := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + aaaaRec, ok := in.Answer[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.AAAA.To16().String() != answer { + t.Fatalf("Bad: %#v", aaaaRec) + } + if aaaaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + } +} + func TestDNS_NonExistingLookup(t *testing.T) { dir, srv := makeDNSServer(t) defer os.RemoveAll(dir)