| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package resolver
- import (
- "fmt"
- "net"
- "net/netip"
- "strings"
- "testing"
- "github.com/miekg/dns"
- )
- // This file exists to isolate the test infrastructure
- // that depends on github.com/miekg/dns
- // from the rest, which only depends on dnsmessage.
- // resolveToIP returns a handler function which responds
- // to queries of type A it receives with an A record containing ipv4,
- // to queries of type AAAA with an AAAA record containing ipv6,
- // to queries of type NS with an NS record containing name.
- func resolveToIP(ipv4, ipv6 netip.Addr, ns string) dns.HandlerFunc {
- return func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(req)
- if len(req.Question) != 1 {
- panic("not a single-question request")
- }
- question := req.Question[0]
- var ans dns.RR
- switch question.Qtype {
- case dns.TypeA:
- ans = &dns.A{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeA,
- Class: dns.ClassINET,
- },
- A: ipv4.AsSlice(),
- }
- case dns.TypeAAAA:
- ans = &dns.AAAA{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeAAAA,
- Class: dns.ClassINET,
- },
- AAAA: ipv6.AsSlice(),
- }
- case dns.TypeNS:
- ans = &dns.NS{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeNS,
- Class: dns.ClassINET,
- },
- Ns: ns,
- }
- }
- m.Answer = append(m.Answer, ans)
- w.WriteMsg(m)
- }
- }
- // resolveToIPLowercase returns a handler function which canonicalizes responses
- // by lowercasing the question and answer names, and responds
- // to queries of type A it receives with an A record containing ipv4,
- // to queries of type AAAA with an AAAA record containing ipv6,
- // to queries of type NS with an NS record containing name.
- func resolveToIPLowercase(ipv4, ipv6 netip.Addr, ns string) dns.HandlerFunc {
- return func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(req)
- if len(req.Question) != 1 {
- panic("not a single-question request")
- }
- m.Question[0].Name = strings.ToLower(m.Question[0].Name)
- question := req.Question[0]
- var ans dns.RR
- switch question.Qtype {
- case dns.TypeA:
- ans = &dns.A{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeA,
- Class: dns.ClassINET,
- },
- A: ipv4.AsSlice(),
- }
- case dns.TypeAAAA:
- ans = &dns.AAAA{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeAAAA,
- Class: dns.ClassINET,
- },
- AAAA: ipv6.AsSlice(),
- }
- case dns.TypeNS:
- ans = &dns.NS{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeNS,
- Class: dns.ClassINET,
- },
- Ns: ns,
- }
- }
- m.Answer = append(m.Answer, ans)
- w.WriteMsg(m)
- }
- }
- // resolveToTXT returns a handler function which responds to queries of type TXT
- // it receives with the strings in txts.
- func resolveToTXT(txts []string, ednsMaxSize uint16) dns.HandlerFunc {
- return func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(req)
- if len(req.Question) != 1 {
- panic("not a single-question request")
- }
- question := req.Question[0]
- if question.Qtype != dns.TypeTXT {
- w.WriteMsg(m)
- return
- }
- ans := &dns.TXT{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeTXT,
- Class: dns.ClassINET,
- },
- Txt: txts,
- }
- m.Answer = append(m.Answer, ans)
- queryInfo := &dns.TXT{
- Hdr: dns.RR_Header{
- Name: "query-info.test.",
- Rrtype: dns.TypeTXT,
- Class: dns.ClassINET,
- },
- }
- if edns := req.IsEdns0(); edns == nil {
- queryInfo.Txt = []string{"EDNS=false"}
- } else {
- queryInfo.Txt = []string{"EDNS=true", fmt.Sprintf("maxSize=%v", edns.UDPSize())}
- }
- m.Extra = append(m.Extra, queryInfo)
- if ednsMaxSize > 0 {
- m.SetEdns0(ednsMaxSize, false)
- }
- if err := w.WriteMsg(m); err != nil {
- panic(err)
- }
- }
- }
- var resolveToNXDOMAIN = dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetRcode(req, dns.RcodeNameError)
- w.WriteMsg(m)
- })
- // weirdoGoCNAMEHandler returns a DNS handler that satisfies
- // Go's weird Resolver.LookupCNAME (read its godoc carefully!).
- //
- // This doesn't even return a CNAME record, because that's not
- // what Go looks for.
- func weirdoGoCNAMEHandler(target string) dns.HandlerFunc {
- return func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(req)
- question := req.Question[0]
- switch question.Qtype {
- case dns.TypeA:
- m.Answer = append(m.Answer, &dns.CNAME{
- Hdr: dns.RR_Header{
- Name: target,
- Rrtype: dns.TypeCNAME,
- Class: dns.ClassINET,
- Ttl: 600,
- },
- Target: target,
- })
- case dns.TypeAAAA:
- m.Answer = append(m.Answer, &dns.AAAA{
- Hdr: dns.RR_Header{
- Name: target,
- Rrtype: dns.TypeAAAA,
- Class: dns.ClassINET,
- Ttl: 600,
- },
- AAAA: net.ParseIP("1::2"),
- })
- }
- w.WriteMsg(m)
- }
- }
- // dnsHandler returns a handler that replies with the answers/options
- // provided.
- //
- // Types supported: netip.Addr.
- func dnsHandler(answers ...any) dns.HandlerFunc {
- return func(w dns.ResponseWriter, req *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(req)
- if len(req.Question) != 1 {
- panic("not a single-question request")
- }
- m.RecursionAvailable = true // to stop net package's errLameReferral on empty replies
- question := req.Question[0]
- for _, a := range answers {
- switch a := a.(type) {
- default:
- panic(fmt.Sprintf("unsupported dnsHandler arg %T", a))
- case netip.Addr:
- ip := a
- if ip.Is4() {
- m.Answer = append(m.Answer, &dns.A{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeA,
- Class: dns.ClassINET,
- },
- A: ip.AsSlice(),
- })
- } else if ip.Is6() {
- m.Answer = append(m.Answer, &dns.AAAA{
- Hdr: dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeAAAA,
- Class: dns.ClassINET,
- },
- AAAA: ip.AsSlice(),
- })
- }
- case dns.PTR:
- ptr := a
- ptr.Hdr = dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypePTR,
- Class: dns.ClassINET,
- }
- m.Answer = append(m.Answer, &ptr)
- case dns.CNAME:
- c := a
- c.Hdr = dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeCNAME,
- Class: dns.ClassINET,
- Ttl: 600,
- }
- m.Answer = append(m.Answer, &c)
- case dns.TXT:
- txt := a
- txt.Hdr = dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeTXT,
- Class: dns.ClassINET,
- }
- m.Answer = append(m.Answer, &txt)
- case dns.SRV:
- srv := a
- srv.Hdr = dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeSRV,
- Class: dns.ClassINET,
- }
- m.Answer = append(m.Answer, &srv)
- case dns.NS:
- rr := a
- rr.Hdr = dns.RR_Header{
- Name: question.Name,
- Rrtype: dns.TypeNS,
- Class: dns.ClassINET,
- }
- m.Answer = append(m.Answer, &rr)
- }
- }
- w.WriteMsg(m)
- }
- }
- func serveDNS(tb testing.TB, addr string, records ...any) *dns.Server {
- if len(records)%2 != 0 {
- panic("must have an even number of record values")
- }
- mux := dns.NewServeMux()
- for i := 0; i < len(records); i += 2 {
- name := records[i].(string)
- handler := records[i+1].(dns.Handler)
- mux.Handle(name, handler)
- }
- waitch := make(chan struct{})
- server := &dns.Server{
- Addr: addr,
- Net: "udp",
- Handler: mux,
- NotifyStartedFunc: func() { close(waitch) },
- ReusePort: true,
- }
- go func() {
- err := server.ListenAndServe()
- if err != nil {
- panic(fmt.Sprintf("ListenAndServe(%q): %v", addr, err))
- }
- }()
- <-waitch
- return server
- }
|