123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- package libnetwork
- import (
- "context"
- "encoding/hex"
- "errors"
- "net"
- "syscall"
- "testing"
- "time"
- "github.com/containerd/log"
- "github.com/docker/docker/internal/testutils/netnsutils"
- "github.com/miekg/dns"
- "github.com/sirupsen/logrus"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- )
- // a simple/null address type that will be used to fake a local address for unit testing
- type tstaddr struct {
- network string
- }
- func (a *tstaddr) Network() string {
- if a.network != "" {
- return a.network
- }
- return "tcp"
- }
- func (a *tstaddr) String() string { return "(fake)" }
- // a simple writer that implements dns.ResponseWriter for unit testing purposes
- type tstwriter struct {
- network string
- msg *dns.Msg
- }
- func (w *tstwriter) WriteMsg(m *dns.Msg) (err error) {
- w.msg = m
- return nil
- }
- func (w *tstwriter) Write(m []byte) (int, error) { return 0, nil }
- func (w *tstwriter) LocalAddr() net.Addr {
- return &tstaddr{network: w.network}
- }
- func (w *tstwriter) RemoteAddr() net.Addr {
- return &tstaddr{network: w.network}
- }
- func (w *tstwriter) TsigStatus() error { return nil }
- func (w *tstwriter) TsigTimersOnly(b bool) {}
- func (w *tstwriter) Hijack() {}
- func (w *tstwriter) Close() error { return nil }
- func (w *tstwriter) GetResponse() *dns.Msg { return w.msg }
- func (w *tstwriter) ClearResponse() { w.msg = nil }
- func checkNonNullResponse(t *testing.T, m *dns.Msg) {
- t.Helper()
- if m == nil {
- t.Fatal("Null DNS response found. Non Null response msg expected.")
- }
- }
- func checkDNSAnswersCount(t *testing.T, m *dns.Msg, expected int) {
- t.Helper()
- answers := len(m.Answer)
- if answers != expected {
- t.Fatalf("Expected number of answers in response: %d. Found: %d", expected, answers)
- }
- }
- func checkDNSResponseCode(t *testing.T, m *dns.Msg, expected int) {
- t.Helper()
- if m.MsgHdr.Rcode != expected {
- t.Fatalf("Expected DNS response code: %d. Found: %d", expected, m.MsgHdr.Rcode)
- }
- }
- func checkDNSRRType(t *testing.T, actual, expected uint16) {
- t.Helper()
- if actual != expected {
- t.Fatalf("Expected DNS Rrtype: %d. Found: %d", expected, actual)
- }
- }
- func newDNSHandlerServFailOnce(requests *int) func(w dns.ResponseWriter, r *dns.Msg) {
- return func(w dns.ResponseWriter, r *dns.Msg) {
- m := new(dns.Msg)
- m.SetReply(r)
- m.Compress = false
- if *requests == 0 {
- m.SetRcode(r, dns.RcodeServerFailure)
- }
- *requests = *requests + 1
- if err := w.WriteMsg(m); err != nil {
- log.G(context.TODO()).WithError(err).Error("Error writing dns response")
- }
- }
- }
- func waitForLocalDNSServer(t *testing.T) {
- retries := 0
- maxRetries := 10
- for retries < maxRetries {
- t.Log("Try connecting to DNS server ...")
- // this test and retry mechanism only works for TCP. With UDP there is no
- // connection and the test becomes inaccurate leading to unpredictable results
- tconn, err := net.DialTimeout("tcp", "127.0.0.1:53", 10*time.Second)
- retries = retries + 1
- if err != nil {
- if oerr, ok := err.(*net.OpError); ok {
- // server is probably initializing
- if oerr.Err == syscall.ECONNREFUSED {
- continue
- }
- } else {
- // something is wrong: we should stop for analysis
- t.Fatal(err)
- }
- }
- if tconn != nil {
- tconn.Close()
- break
- }
- }
- }
- // Packet 24 extracted from
- // https://gist.github.com/vojtad/3bac63b8c91b1ec50e8d8b36047317fa/raw/7d75eb3d3448381bf252ae55ea5123a132c46658/host.pcap
- // (https://github.com/moby/moby/issues/44575)
- // which is a non-compliant DNS reply > 512B (w/o EDNS(0)) to the query
- //
- // s3.amazonaws.com. IN A
- const oversizedDNSReplyMsg = "\xf5\x11\x81\x80\x00\x01\x00\x20\x00\x00\x00\x00\x02\x73\x33\x09" +
- "\x61\x6d\x61\x7a\x6f\x6e\x61\x77\x73\x03\x63\x6f\x6d\x00\x00\x01" +
- "\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\x11\x9e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x4c\x66\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\xda\x10\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\x01\x3e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\x88\x68\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\x66\x9e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\x5f\x28\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x8e\x4e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x36\xe7" +
- "\x84\xf0\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x34\xd8" +
- "\x92\x45\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x8f\xa6\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x36\xe7" +
- "\xc0\xd0\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\xfe\x28\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\xaa\x3d\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x4e\x56\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd9" +
- "\xea\xb0\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x6d\xed\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x04\x00\x04\x34\xd8" +
- "\x28\x00\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x34\xd9" +
- "\xe9\x78\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x34\xd9" +
- "\x6e\x9e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x34\xd9" +
- "\x45\x86\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x34\xd8" +
- "\x30\x38\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x36\xe7" +
- "\xc6\xa8\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x03\x05" +
- "\x01\x9d\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd9" +
- "\xa8\xe8\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd9" +
- "\x64\xa6\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd8" +
- "\x3c\x48\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd8" +
- "\x35\x20\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd9" +
- "\x54\xf6\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd9" +
- "\x5d\x36\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x34\xd9" +
- "\x30\x36\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x05\x00\x04\x36\xe7" +
- "\x83\x90"
- // Regression test for https://github.com/moby/moby/issues/44575
- func TestOversizedDNSReply(t *testing.T) {
- srv, err := net.ListenPacket("udp", "127.0.0.1:0")
- assert.NilError(t, err)
- defer srv.Close()
- go func() {
- buf := make([]byte, 65536)
- for {
- n, src, err := srv.ReadFrom(buf)
- if errors.Is(err, net.ErrClosed) {
- return
- }
- t.Logf("[<-%v]\n%s", src, hex.Dump(buf[:n]))
- if n < 2 {
- continue
- }
- resp := []byte(oversizedDNSReplyMsg)
- resp[0], resp[1] = buf[0], buf[1] // Copy query ID into response.
- _, err = srv.WriteTo(resp, src)
- if errors.Is(err, net.ErrClosed) {
- return
- }
- if err != nil {
- t.Log(err)
- }
- }
- }()
- srvAddr := srv.LocalAddr().(*net.UDPAddr)
- rsv := NewResolver("", true, noopDNSBackend{})
- // The resolver logs lots of valuable info at level debug. Redirect it
- // to t.Log() so the log spew is emitted only if the test fails.
- rsv.logger = testLogger(t)
- rsv.SetExtServers([]extDNSEntry{
- {IPStr: srvAddr.IP.String(), port: uint16(srvAddr.Port), HostLoopback: true},
- })
- w := &tstwriter{network: srvAddr.Network()}
- q := new(dns.Msg).SetQuestion("s3.amazonaws.com.", dns.TypeA)
- rsv.serveDNS(w, q)
- resp := w.GetResponse()
- checkNonNullResponse(t, resp)
- t.Log("Response: ", resp.String())
- checkDNSResponseCode(t, resp, dns.RcodeSuccess)
- assert.Assert(t, len(resp.Answer) >= 1)
- checkDNSRRType(t, resp.Answer[0].Header().Rrtype, dns.TypeA)
- }
- func testLogger(t *testing.T) *logrus.Entry {
- logger := logrus.New()
- logger.SetLevel(logrus.DebugLevel)
- logger.SetOutput(tlogWriter{t})
- return logrus.NewEntry(logger)
- }
- type tlogWriter struct{ t *testing.T }
- func (w tlogWriter) Write(p []byte) (n int, err error) {
- w.t.Logf("%s", p)
- return len(p), nil
- }
- type noopDNSBackend struct{ DNSBackend }
- func (noopDNSBackend) ResolveName(_ context.Context, name string, iplen int) ([]net.IP, bool) {
- return nil, false
- }
- func (noopDNSBackend) ExecFunc(f func()) error { f(); return nil }
- func (noopDNSBackend) NdotsSet() bool { return false }
- func (noopDNSBackend) HandleQueryResp(name string, ip net.IP) {}
- func TestReplySERVFAIL(t *testing.T) {
- cases := []struct {
- name string
- q *dns.Msg
- proxyDNS bool
- }{
- {
- name: "InternalError",
- q: new(dns.Msg).SetQuestion("_sip._tcp.example.com.", dns.TypeSRV),
- },
- {
- name: "ProxyDNS=false",
- q: new(dns.Msg).SetQuestion("example.com.", dns.TypeA),
- },
- {
- name: "ProxyDNS=true", // No extDNS servers configured -> no answer from any upstream
- q: new(dns.Msg).SetQuestion("example.com.", dns.TypeA),
- proxyDNS: true,
- },
- }
- for _, tt := range cases {
- t.Run(tt.name, func(t *testing.T) {
- rsv := NewResolver("", tt.proxyDNS, badSRVDNSBackend{})
- rsv.logger = testLogger(t)
- w := &tstwriter{}
- rsv.serveDNS(w, tt.q)
- resp := w.GetResponse()
- checkNonNullResponse(t, resp)
- t.Log("Response: ", resp.String())
- checkDNSResponseCode(t, resp, dns.RcodeServerFailure)
- })
- }
- }
- type badSRVDNSBackend struct{ noopDNSBackend }
- func (badSRVDNSBackend) ResolveService(_ context.Context, _ string) ([]*net.SRV, []net.IP) {
- return []*net.SRV{nil, nil, nil}, nil // Mismatched slice lengths
- }
- func TestProxyNXDOMAIN(t *testing.T) {
- mockSOA, err := dns.NewRR(". 86367 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2023051800 1800 900 604800 86400\n")
- assert.NilError(t, err)
- assert.Assert(t, mockSOA != nil)
- serveStarted := make(chan struct{})
- srv := &dns.Server{
- Net: "udp",
- Addr: "127.0.0.1:0",
- Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
- msg := new(dns.Msg).SetRcode(r, dns.RcodeNameError)
- msg.Ns = append(msg.Ns, dns.Copy(mockSOA))
- w.WriteMsg(msg)
- }),
- NotifyStartedFunc: func() { close(serveStarted) },
- }
- serveDone := make(chan error, 1)
- go func() {
- defer close(serveDone)
- serveDone <- srv.ListenAndServe()
- }()
- select {
- case err := <-serveDone:
- t.Fatal(err)
- case <-serveStarted:
- }
- defer func() {
- if err := srv.Shutdown(); err != nil {
- t.Error(err)
- }
- <-serveDone
- }()
- // This test, by virtue of running a server and client in different
- // not-locked-to-thread goroutines, happens to be a good canary for
- // whether we are leaking unlocked OS threads set to the wrong network
- // namespace. Make a best-effort attempt to detect that situation so we
- // are not left chasing ghosts next time.
- netnsutils.AssertSocketSameNetNS(t, srv.PacketConn.(*net.UDPConn))
- srvAddr := srv.PacketConn.LocalAddr().(*net.UDPAddr)
- rsv := NewResolver("", true, noopDNSBackend{})
- rsv.SetExtServers([]extDNSEntry{
- {IPStr: srvAddr.IP.String(), port: uint16(srvAddr.Port), HostLoopback: true},
- })
- // The resolver logs lots of valuable info at level debug. Redirect it
- // to t.Log() so the log spew is emitted only if the test fails.
- rsv.logger = testLogger(t)
- w := &tstwriter{network: srvAddr.Network()}
- q := new(dns.Msg).SetQuestion("example.net.", dns.TypeA)
- rsv.serveDNS(w, q)
- resp := w.GetResponse()
- checkNonNullResponse(t, resp)
- t.Log("Response:\n" + resp.String())
- checkDNSResponseCode(t, resp, dns.RcodeNameError)
- assert.Assert(t, is.Len(resp.Answer, 0))
- assert.Assert(t, is.Len(resp.Ns, 1))
- assert.Equal(t, resp.Ns[0].String(), mockSOA.String())
- }
|