package resolver import ( "errors" "testing" "time" "github.com/miekg/dns" ) // TestAnswerForAnotherQuestionIsRejected pins the check the DNS library does // not make: it matches the message ID and nothing else, so a reply with the // right ID for a different question would otherwise be cached under the name // that was asked. func TestAnswerForAnotherQuestionIsRejected(t *testing.T) { spoof := newFakeUpstream(t, fakeOpt{wrongQuestion: true}) p := NewPool(poolCfg(udpServer(spoof.addr))) for i := 0; i < 5; i++ { // more than the breaker threshold resp, _, err := p.Exchange(testCtx(t), query("www.example.test"), nil, time.Second) if !errors.Is(err, errQuestionMismatch) || resp != nil { t.Fatalf("attempt %d: resp=%v err=%v, want the mismatch error", i, resp, err) } } // A forged reply must not be able to take the upstream out of rotation. if up := p.All()[0]; up.BreakerOpen() { t.Fatal("spoofed replies opened the circuit breaker") } } func TestAnswersQuestion(t *testing.T) { q := query("WWW.Example.Test") reply := func(name string, qtype uint16, rcode int, drop bool) *dns.Msg { m := new(dns.Msg) m.SetReply(q) m.Question[0].Name, m.Question[0].Qtype = name, qtype m.Rcode = rcode if drop { m.Question = nil } return m } for name, tc := range map[string]struct { resp *dns.Msg want bool }{ "same": {reply("www.example.test.", dns.TypeA, 0, false), true}, "case differs": {reply("WWW.EXAMPLE.TEST.", dns.TypeA, 0, false), true}, "other name": {reply("evil.example.", dns.TypeA, 0, false), false}, "other type": {reply("www.example.test.", dns.TypeAAAA, 0, false), false}, "formerr, no question": {reply("", 0, dns.RcodeFormatError, true), true}, "refused, no question": {reply("", 0, dns.RcodeRefused, true), true}, "success, no question": {reply("", 0, dns.RcodeSuccess, true), false}, "nxdomain, no question": {reply("", 0, dns.RcodeNameError, true), false}, "nil": {nil, false}, } { if got := answersQuestion(q, tc.resp); got != tc.want { t.Errorf("%s: got %v, want %v", name, got, tc.want) } } }