tsdns_server_test.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package resolver
  4. import (
  5. "fmt"
  6. "net"
  7. "net/netip"
  8. "strings"
  9. "testing"
  10. "github.com/miekg/dns"
  11. )
  12. // This file exists to isolate the test infrastructure
  13. // that depends on github.com/miekg/dns
  14. // from the rest, which only depends on dnsmessage.
  15. // resolveToIP returns a handler function which responds
  16. // to queries of type A it receives with an A record containing ipv4,
  17. // to queries of type AAAA with an AAAA record containing ipv6,
  18. // to queries of type NS with an NS record containing name.
  19. func resolveToIP(ipv4, ipv6 netip.Addr, ns string) dns.HandlerFunc {
  20. return func(w dns.ResponseWriter, req *dns.Msg) {
  21. m := new(dns.Msg)
  22. m.SetReply(req)
  23. if len(req.Question) != 1 {
  24. panic("not a single-question request")
  25. }
  26. question := req.Question[0]
  27. var ans dns.RR
  28. switch question.Qtype {
  29. case dns.TypeA:
  30. ans = &dns.A{
  31. Hdr: dns.RR_Header{
  32. Name: question.Name,
  33. Rrtype: dns.TypeA,
  34. Class: dns.ClassINET,
  35. },
  36. A: ipv4.AsSlice(),
  37. }
  38. case dns.TypeAAAA:
  39. ans = &dns.AAAA{
  40. Hdr: dns.RR_Header{
  41. Name: question.Name,
  42. Rrtype: dns.TypeAAAA,
  43. Class: dns.ClassINET,
  44. },
  45. AAAA: ipv6.AsSlice(),
  46. }
  47. case dns.TypeNS:
  48. ans = &dns.NS{
  49. Hdr: dns.RR_Header{
  50. Name: question.Name,
  51. Rrtype: dns.TypeNS,
  52. Class: dns.ClassINET,
  53. },
  54. Ns: ns,
  55. }
  56. }
  57. m.Answer = append(m.Answer, ans)
  58. w.WriteMsg(m)
  59. }
  60. }
  61. // resolveToIPLowercase returns a handler function which canonicalizes responses
  62. // by lowercasing the question and answer names, and responds
  63. // to queries of type A it receives with an A record containing ipv4,
  64. // to queries of type AAAA with an AAAA record containing ipv6,
  65. // to queries of type NS with an NS record containing name.
  66. func resolveToIPLowercase(ipv4, ipv6 netip.Addr, ns string) dns.HandlerFunc {
  67. return func(w dns.ResponseWriter, req *dns.Msg) {
  68. m := new(dns.Msg)
  69. m.SetReply(req)
  70. if len(req.Question) != 1 {
  71. panic("not a single-question request")
  72. }
  73. m.Question[0].Name = strings.ToLower(m.Question[0].Name)
  74. question := req.Question[0]
  75. var ans dns.RR
  76. switch question.Qtype {
  77. case dns.TypeA:
  78. ans = &dns.A{
  79. Hdr: dns.RR_Header{
  80. Name: question.Name,
  81. Rrtype: dns.TypeA,
  82. Class: dns.ClassINET,
  83. },
  84. A: ipv4.AsSlice(),
  85. }
  86. case dns.TypeAAAA:
  87. ans = &dns.AAAA{
  88. Hdr: dns.RR_Header{
  89. Name: question.Name,
  90. Rrtype: dns.TypeAAAA,
  91. Class: dns.ClassINET,
  92. },
  93. AAAA: ipv6.AsSlice(),
  94. }
  95. case dns.TypeNS:
  96. ans = &dns.NS{
  97. Hdr: dns.RR_Header{
  98. Name: question.Name,
  99. Rrtype: dns.TypeNS,
  100. Class: dns.ClassINET,
  101. },
  102. Ns: ns,
  103. }
  104. }
  105. m.Answer = append(m.Answer, ans)
  106. w.WriteMsg(m)
  107. }
  108. }
  109. // resolveToTXT returns a handler function which responds to queries of type TXT
  110. // it receives with the strings in txts.
  111. func resolveToTXT(txts []string, ednsMaxSize uint16) dns.HandlerFunc {
  112. return func(w dns.ResponseWriter, req *dns.Msg) {
  113. m := new(dns.Msg)
  114. m.SetReply(req)
  115. if len(req.Question) != 1 {
  116. panic("not a single-question request")
  117. }
  118. question := req.Question[0]
  119. if question.Qtype != dns.TypeTXT {
  120. w.WriteMsg(m)
  121. return
  122. }
  123. ans := &dns.TXT{
  124. Hdr: dns.RR_Header{
  125. Name: question.Name,
  126. Rrtype: dns.TypeTXT,
  127. Class: dns.ClassINET,
  128. },
  129. Txt: txts,
  130. }
  131. m.Answer = append(m.Answer, ans)
  132. queryInfo := &dns.TXT{
  133. Hdr: dns.RR_Header{
  134. Name: "query-info.test.",
  135. Rrtype: dns.TypeTXT,
  136. Class: dns.ClassINET,
  137. },
  138. }
  139. if edns := req.IsEdns0(); edns == nil {
  140. queryInfo.Txt = []string{"EDNS=false"}
  141. } else {
  142. queryInfo.Txt = []string{"EDNS=true", fmt.Sprintf("maxSize=%v", edns.UDPSize())}
  143. }
  144. m.Extra = append(m.Extra, queryInfo)
  145. if ednsMaxSize > 0 {
  146. m.SetEdns0(ednsMaxSize, false)
  147. }
  148. if err := w.WriteMsg(m); err != nil {
  149. panic(err)
  150. }
  151. }
  152. }
  153. var resolveToNXDOMAIN = dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
  154. m := new(dns.Msg)
  155. m.SetRcode(req, dns.RcodeNameError)
  156. w.WriteMsg(m)
  157. })
  158. // weirdoGoCNAMEHandler returns a DNS handler that satisfies
  159. // Go's weird Resolver.LookupCNAME (read its godoc carefully!).
  160. //
  161. // This doesn't even return a CNAME record, because that's not
  162. // what Go looks for.
  163. func weirdoGoCNAMEHandler(target string) dns.HandlerFunc {
  164. return func(w dns.ResponseWriter, req *dns.Msg) {
  165. m := new(dns.Msg)
  166. m.SetReply(req)
  167. question := req.Question[0]
  168. switch question.Qtype {
  169. case dns.TypeA:
  170. m.Answer = append(m.Answer, &dns.CNAME{
  171. Hdr: dns.RR_Header{
  172. Name: target,
  173. Rrtype: dns.TypeCNAME,
  174. Class: dns.ClassINET,
  175. Ttl: 600,
  176. },
  177. Target: target,
  178. })
  179. case dns.TypeAAAA:
  180. m.Answer = append(m.Answer, &dns.AAAA{
  181. Hdr: dns.RR_Header{
  182. Name: target,
  183. Rrtype: dns.TypeAAAA,
  184. Class: dns.ClassINET,
  185. Ttl: 600,
  186. },
  187. AAAA: net.ParseIP("1::2"),
  188. })
  189. }
  190. w.WriteMsg(m)
  191. }
  192. }
  193. // dnsHandler returns a handler that replies with the answers/options
  194. // provided.
  195. //
  196. // Types supported: netip.Addr.
  197. func dnsHandler(answers ...any) dns.HandlerFunc {
  198. return func(w dns.ResponseWriter, req *dns.Msg) {
  199. m := new(dns.Msg)
  200. m.SetReply(req)
  201. if len(req.Question) != 1 {
  202. panic("not a single-question request")
  203. }
  204. m.RecursionAvailable = true // to stop net package's errLameReferral on empty replies
  205. question := req.Question[0]
  206. for _, a := range answers {
  207. switch a := a.(type) {
  208. default:
  209. panic(fmt.Sprintf("unsupported dnsHandler arg %T", a))
  210. case netip.Addr:
  211. ip := a
  212. if ip.Is4() {
  213. m.Answer = append(m.Answer, &dns.A{
  214. Hdr: dns.RR_Header{
  215. Name: question.Name,
  216. Rrtype: dns.TypeA,
  217. Class: dns.ClassINET,
  218. },
  219. A: ip.AsSlice(),
  220. })
  221. } else if ip.Is6() {
  222. m.Answer = append(m.Answer, &dns.AAAA{
  223. Hdr: dns.RR_Header{
  224. Name: question.Name,
  225. Rrtype: dns.TypeAAAA,
  226. Class: dns.ClassINET,
  227. },
  228. AAAA: ip.AsSlice(),
  229. })
  230. }
  231. case dns.PTR:
  232. ptr := a
  233. ptr.Hdr = dns.RR_Header{
  234. Name: question.Name,
  235. Rrtype: dns.TypePTR,
  236. Class: dns.ClassINET,
  237. }
  238. m.Answer = append(m.Answer, &ptr)
  239. case dns.CNAME:
  240. c := a
  241. c.Hdr = dns.RR_Header{
  242. Name: question.Name,
  243. Rrtype: dns.TypeCNAME,
  244. Class: dns.ClassINET,
  245. Ttl: 600,
  246. }
  247. m.Answer = append(m.Answer, &c)
  248. case dns.TXT:
  249. txt := a
  250. txt.Hdr = dns.RR_Header{
  251. Name: question.Name,
  252. Rrtype: dns.TypeTXT,
  253. Class: dns.ClassINET,
  254. }
  255. m.Answer = append(m.Answer, &txt)
  256. case dns.SRV:
  257. srv := a
  258. srv.Hdr = dns.RR_Header{
  259. Name: question.Name,
  260. Rrtype: dns.TypeSRV,
  261. Class: dns.ClassINET,
  262. }
  263. m.Answer = append(m.Answer, &srv)
  264. case dns.NS:
  265. rr := a
  266. rr.Hdr = dns.RR_Header{
  267. Name: question.Name,
  268. Rrtype: dns.TypeNS,
  269. Class: dns.ClassINET,
  270. }
  271. m.Answer = append(m.Answer, &rr)
  272. }
  273. }
  274. w.WriteMsg(m)
  275. }
  276. }
  277. func serveDNS(tb testing.TB, addr string, records ...any) *dns.Server {
  278. if len(records)%2 != 0 {
  279. panic("must have an even number of record values")
  280. }
  281. mux := dns.NewServeMux()
  282. for i := 0; i < len(records); i += 2 {
  283. name := records[i].(string)
  284. handler := records[i+1].(dns.Handler)
  285. mux.Handle(name, handler)
  286. }
  287. waitch := make(chan struct{})
  288. server := &dns.Server{
  289. Addr: addr,
  290. Net: "udp",
  291. Handler: mux,
  292. NotifyStartedFunc: func() { close(waitch) },
  293. ReusePort: true,
  294. }
  295. go func() {
  296. err := server.ListenAndServe()
  297. if err != nil {
  298. panic(fmt.Sprintf("ListenAndServe(%q): %v", addr, err))
  299. }
  300. }()
  301. <-waitch
  302. return server
  303. }