func (s *DNSSuite) TestRecursor(c *C) { udpAddr, tcpAddr, cleanup := startUpstreamTestServer(c) defer cleanup() withResolvers := func(resolvers []string, net string, f func(string)) { srv := s.newServer(c, resolvers) defer func() { c.Assert(srv.Close(), IsNil) }() addr := srv.UDPAddr if net == "tcp" { addr = srv.TCPAddr } f(addr) } for net, upstreamAddr := range map[string]string{"tcp": tcpAddr, "udp": udpAddr} { c.Log(net) client := &dns.Client{ ReadTimeout: 10 * time.Second, Net: net, } msg := &dns.Msg{} msg.SetQuestion("long-compressed-response.", dns.TypeA) // Valid request withResolvers([]string{upstreamAddr}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeSuccess) c.Assert(len(res.Answer) > 0, Equals, true) }) // Failing recursor fallback withResolvers([]string{"127.1.1.1:55", upstreamAddr}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeSuccess) c.Assert(len(res.Answer) > 0, Equals, true) }) // All failing withResolvers([]string{"127.1.1.1:55"}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeServerFailure) }) } }
func (s *DNSSuite) TestRecursor(c *C) { s.srv.Close() s.srv = nil withResolvers := func(resolvers []string, net string, f func(string)) { srv := s.newServer(c, resolvers) defer func() { c.Assert(srv.Close(), IsNil) }() addr := srv.UDPAddr if net == "tcp" { addr = srv.TCPAddr } f(addr) } for _, net := range []string{"tcp", "udp"} { c.Log(net) client := &dns.Client{ ReadTimeout: 10 * time.Second, Net: net, } msg := &dns.Msg{} msg.SetQuestion("google.com.", dns.TypeA) // Valid request withResolvers([]string{"8.8.8.8:53"}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeSuccess) c.Assert(len(res.Answer) > 0, Equals, true) }) // Failing recursor fallback withResolvers([]string{"127.1.1.1:55", "8.8.8.8:53"}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeSuccess) c.Assert(len(res.Answer) > 0, Equals, true) }) // All failing withResolvers([]string{"127.1.1.1:55"}, net, func(addr string) { res, _, err := client.Exchange(msg, addr) c.Assert(err, IsNil) c.Assert(res.Rcode, Equals, dns.RcodeServerFailure) }) } }
func (s *DNSSuite) TestServiceLookup(c *C) { type test struct { name string domain string qs map[uint16][]testAddr data []*discoverd.Instance net string } simpleData := make([]*discoverd.Instance, 3) simpleAddrs := make([]testAddr, 3) simpleData[0], simpleAddrs[0] = fakeStaticInstance("tcp", "192.168.0.1", 80) simpleData[1], simpleAddrs[1] = fakeStaticInstance("tcp", "192.168.0.2", 81) simpleData[2], simpleAddrs[2] = fakeStaticInstance("tcp", "192.168.0.3", 82) mixedData := make([]*discoverd.Instance, 3) mixedAddrs := make([]testAddr, 3) copy(mixedData, simpleData) copy(mixedAddrs, simpleAddrs) mixedData[0], mixedAddrs[0] = fakeStaticInstance("udp", "192.168.0.5", 99) filteredAddrs := mixedAddrs[:1] v6v4Data := make([]*discoverd.Instance, 3) v6v4Addrs := make([]testAddr, 3) copy(v6v4Data, simpleData) copy(v6v4Addrs, simpleAddrs) v6v4Data[0], v6v4Addrs[0] = fakeStaticInstance("tcp", "fe80::bae8:56ff:fe46:243c", 22) longData := make([]*discoverd.Instance, 5) longAddrs := make([]testAddr, 5) copy(longData, simpleData) copy(longAddrs, simpleAddrs) longData[3], longAddrs[3] = fakeStaticInstance("tcp", "192.168.0.4", 83) longData[4], longAddrs[4] = fakeStaticInstance("tcp", "192.168.0.5", 84) dupeData := make([]*discoverd.Instance, 3) dupeAddrs := make([]testAddr, 3) copy(dupeData, simpleData) copy(dupeAddrs, simpleAddrs) dupeData[1], dupeAddrs[1] = fakeStaticInstance("tcp", "192.168.0.1", 85) emptyAll := map[uint16][]testAddr{ dns.TypeA: nil, dns.TypeAAAA: nil, dns.TypeANY: nil, dns.TypeSRV: nil, dns.TypeSOA: nil, dns.TypeTXT: nil, } simpleQs := map[uint16][]testAddr{ dns.TypeA: simpleAddrs, dns.TypeAAAA: nil, dns.TypeANY: simpleAddrs, dns.TypeSRV: simpleAddrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } udpFiltered := map[uint16][]testAddr{ dns.TypeA: filteredAddrs, dns.TypeAAAA: nil, dns.TypeANY: filteredAddrs, dns.TypeSRV: filteredAddrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } mixedQs := map[uint16][]testAddr{ dns.TypeA: mixedAddrs, dns.TypeAAAA: nil, dns.TypeANY: mixedAddrs, dns.TypeSRV: mixedAddrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } v6v4Qs := map[uint16][]testAddr{ dns.TypeA: v6v4Addrs[1:], dns.TypeAAAA: v6v4Addrs[:1], dns.TypeANY: v6v4Addrs, dns.TypeSRV: v6v4Addrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } instanceQs := map[uint16][]testAddr{ dns.TypeA: simpleAddrs[:1], dns.TypeAAAA: nil, dns.TypeANY: simpleAddrs[:1], dns.TypeSRV: simpleAddrs[:1], dns.TypeSOA: nil, dns.TypeTXT: nil, } v6instanceQs := map[uint16][]testAddr{ dns.TypeA: nil, dns.TypeAAAA: v6v4Addrs[:1], dns.TypeANY: v6v4Addrs[:1], dns.TypeSRV: v6v4Addrs[:1], dns.TypeSOA: nil, dns.TypeTXT: nil, } longQs := map[uint16][]testAddr{ dns.TypeA: longAddrs, dns.TypeAAAA: nil, dns.TypeANY: longAddrs, dns.TypeSRV: longAddrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } dupeQs := map[uint16][]testAddr{ dns.TypeA: dupeAddrs[1:], dns.TypeAAAA: nil, dns.TypeANY: dupeAddrs[1:], dns.TypeSRV: dupeAddrs, dns.TypeSOA: nil, dns.TypeTXT: nil, } leaderQs := map[uint16][]testAddr{ dns.TypeA: simpleAddrs[:1], dns.TypeAAAA: nil, dns.TypeANY: simpleAddrs[:1], dns.TypeSRV: simpleAddrs[:1], dns.TypeSOA: nil, dns.TypeTXT: nil, } v6LeaderQs := map[uint16][]testAddr{ dns.TypeA: nil, dns.TypeAAAA: v6v4Addrs[:1], dns.TypeANY: v6v4Addrs[:1], dns.TypeSRV: v6v4Addrs[:1], dns.TypeSOA: nil, dns.TypeTXT: nil, } tests := []test{ { name: "non-existent service NXDOMAIN", domain: "b.discoverd.", qs: emptyAll, }, { name: "non-existent service RFC2782 NXDOMAIN", domain: "_b._tcp.discoverd.", qs: emptyAll, }, { name: "non-existent instance with existing service NXDOMAIN", domain: "foo.a._i.discoverd.", qs: emptyAll, }, { name: "non-existent instance with non-existent service NXDOMAIN", domain: "foo.b._i.discoverd.", qs: emptyAll, }, { name: "bad domain NXDOMAIN", domain: "asdf.b.discoverd.", qs: emptyAll, }, { name: "leader NXDOMAIN", domain: "leader.b.discoverd.", qs: emptyAll, }, { name: "leader with non-existent service NXDOMAIN", domain: "leader.b.discoverd.", qs: emptyAll, }, { name: "empty service", domain: "a.discoverd.", qs: emptyAll, }, { name: "empty 2782", domain: "_a._tcp.discoverd.", qs: emptyAll, }, { name: "2782 no proto matches", domain: "_a._foo.discoverd.", data: simpleData, qs: emptyAll, }, { name: "2782 full proto matches", domain: "_a._tcp.discoverd.", data: simpleData, qs: simpleQs, }, { name: "2782 some proto matches", domain: "_a._udp.discoverd.", data: mixedData, qs: udpFiltered, }, { name: "service mixed protocols", domain: "a.discoverd.", data: mixedData, qs: mixedQs, }, { name: "service mixed v4/v6", domain: "a.discoverd.", data: v6v4Data, qs: v6v4Qs, }, { name: "2782 mixed v4/v6", domain: "_a._tcp.discoverd.", data: v6v4Data, qs: v6v4Qs, }, { name: "instance", domain: fmt.Sprintf("%s.a._i.discoverd.", simpleData[0].ID), data: simpleData, qs: instanceQs, }, { name: "v6 instance", domain: fmt.Sprintf("%s.a._i.discoverd.", v6v4Data[0].ID), data: v6v4Data, qs: v6instanceQs, }, { name: "service udp limit", domain: "a.discoverd.", data: longData, qs: longQs, }, { name: "2782 udp limit", domain: "_a._tcp.discoverd.", data: longData, qs: longQs, }, { name: "service duplicate IPs", domain: "a.discoverd.", data: dupeData, qs: dupeQs, }, { name: "2782 duplicate IPs", domain: "_a._tcp.discoverd.", data: dupeData, qs: dupeQs, }, { name: "leader", domain: "leader.a.discoverd.", data: simpleData, qs: leaderQs, }, { name: "v6 leader", domain: "leader.a.discoverd.", data: v6v4Data[:1], qs: v6LeaderQs, }, } // Run all of the tests with TCP as well as UDP for i, t := range tests { tests[i].net = "udp" t.net = "tcp" tests = append(tests, t) } // Run all of the tests again to test case sensitivity for _, t := range tests { t.domain = strings.ToUpper(t.domain) tests = append(tests, t) } for _, t := range tests { func() { srv := s.newServer(c, []string{"8.8.8.8", "8.8.4.4"}) defer srv.Close() srv.SetStore(&DNSServerStore{ InstancesFn: func(service string) ([]*discoverd.Instance, error) { if service == "a" { if len(t.data) == 0 { return []*discoverd.Instance{}, nil } else { return t.data, nil } } return nil, nil }, ServiceLeaderFn: func(service string) (*discoverd.Instance, error) { if service == "a" && len(t.data) > 0 { return t.data[0], nil } return nil, nil }, }) client := &dns.Client{Net: t.net} for q, addrs := range t.qs { c.Logf("+ %s: %s - %s - %s", t.domain, t.net, t.name, dns.TypeToString[q]) // exchange the question req := &dns.Msg{} req.SetQuestion(t.domain, q) addr := srv.UDPAddr if t.net == "tcp" { addr = srv.TCPAddr } res, _, err := client.Exchange(req, addr) c.Assert(err, IsNil) if strings.Contains(t.name, "NXDOMAIN") { // if this is a nxdomain test we just need to ensure we got an nxdomain c.Assert(res.Rcode, Equals, dns.RcodeNameError) c.Assert(res.Extra, HasLen, 0) c.Assert(res.Answer, HasLen, 0) assertSOA(c, res.Ns) continue } // UDP responses only include up to three responses truncated := t.net == "udp" && len(addrs) > 3 c.Assert(res.Rcode, Equals, dns.RcodeSuccess) switch { case q == dns.TypeANY: if truncated { // three SRV records plus three A/AAAA records c.Assert(res.Answer, HasLen, 6) } else { // SRV + A/AAAA records c.Assert(res.Answer, HasLen, len(addrs)*2) } case q == dns.TypeSOA: // the only response to a SOA question should be an SOA answer assertSOA(c, res.Answer) continue case len(addrs) == 0: // empty responses include an SOA answer in the authority section assertSOA(c, res.Ns) c.Assert(res.Answer, HasLen, 0) default: if truncated { c.Assert(res.Answer, HasLen, 3) } else { c.Assert(res.Answer, HasLen, len(addrs)) } } // build a list of all A/AAAA and SRV records received ips := make(map[string]struct{}, len(addrs)) srv := make(map[string]struct{}, len(addrs)) for _, rr := range res.Answer { switch v := rr.(type) { case *dns.A: ips[v.A.String()] = struct{}{} c.Assert(v.A.To4(), NotNil) c.Assert(v.Hdr.Name, Equals, t.domain) c.Assert(v.Hdr.Rrtype, Equals, dns.TypeA) case *dns.AAAA: ips[v.AAAA.String()] = struct{}{} c.Assert(v.AAAA.To4(), IsNil) c.Assert(v.Hdr.Name, Equals, t.domain) c.Assert(v.Hdr.Rrtype, Equals, dns.TypeAAAA) case *dns.SRV: srv[fmt.Sprintf("%s:%d", v.Target, v.Port)] = struct{}{} c.Assert(v.Hdr.Name, Equals, t.domain) c.Assert(v.Hdr.Rrtype, Equals, dns.TypeSRV) c.Assert(v.Weight, Equals, uint16(1)) c.Assert(v.Priority, Equals, uint16(1)) default: c.Fatalf("unexpected record in answer %#v", v) } } // ensure that we got the expected A/AAAA records if q == dns.TypeANY || q == dns.TypeA || q == dns.TypeAAAA { if truncated { c.Assert(ips, HasLen, 3) } else { c.Assert(ips, HasLen, len(addrs)) } var found int for _, addr := range addrs { if _, ok := ips[addr.IP.String()]; ok { found++ } } if truncated { c.Assert(found, Equals, 3) } else { c.Assert(found, Equals, len(addrs)) } } else { c.Assert(ips, HasLen, 0) } // ensure that we got the expected SRV records if q == dns.TypeANY || q == dns.TypeSRV { if truncated { c.Assert(srv, HasLen, 3) } else { c.Assert(srv, HasLen, len(addrs)) } if !strings.Contains(t.name, "duplicate") { var found int for _, addr := range addrs { key := fmt.Sprintf("%s.a._i.discoverd.:%d", addr.ID, addr.Port) if strings.Contains(t.name, "instance") || strings.Contains(t.name, "leader") { key = fmt.Sprintf("%s:%d", t.domain, addr.Port) } if _, ok := srv[key]; ok { found++ } } if truncated { c.Assert(found, Equals, 3) } else { c.Assert(found, Equals, len(addrs)) } } } else { c.Assert(srv, HasLen, 0) } // responses to SRV questions over TCP get instance records in // the extra section so that another lookup is not needed to get // the IP addresses if t.net == "tcp" && q == dns.TypeSRV { c.Assert(res.Extra, HasLen, len(addrs)) extraIPs := make(map[string]string, len(res.Extra)) for _, rr := range res.Extra { switch v := rr.(type) { case *dns.A: c.Assert(v.Hdr.Rrtype, Equals, dns.TypeA) id := strings.TrimSuffix(strings.ToLower(v.Hdr.Name), ".a._i.discoverd.") extraIPs[id] = v.A.String() case *dns.AAAA: c.Assert(v.Hdr.Rrtype, Equals, dns.TypeAAAA) id := strings.TrimSuffix(strings.ToLower(v.Hdr.Name), ".a._i.discoverd.") extraIPs[id] = v.AAAA.String() default: c.Fatalf("unexpected record in extra %#v", v) } } for _, addr := range addrs { if strings.Contains(t.name, "leader") { c.Assert(extraIPs[strings.ToLower(t.domain)], Equals, addr.IP.String()) } else { c.Assert(extraIPs[addr.ID], Equals, addr.IP.String()) } } } else { c.Assert(res.Extra, HasLen, 0) } } }() } }