diff --git a/libnetwork/resolver.go b/libnetwork/resolver.go index 41546ac541..6684d46f4b 100644 --- a/libnetwork/resolver.go +++ b/libnetwork/resolver.go @@ -73,6 +73,7 @@ const ( type extDNSEntry struct { IPStr string + port uint16 // for testing HostLoopback bool } @@ -451,7 +452,11 @@ func (r *resolver) ServeDNS(w dns.ResponseWriter, query *dns.Msg) { break } extConnect := func() { - addr := fmt.Sprintf("%s:%d", extDNS.IPStr, 53) + port := extDNS.port + if port == 0 { + port = 53 + } + addr := fmt.Sprintf("%s:%d", extDNS.IPStr, port) extConn, err = net.DialTimeout(proto, addr, extIOTimeout) } diff --git a/libnetwork/resolver_test.go b/libnetwork/resolver_test.go index e5bd477f9c..05bb9b1017 100644 --- a/libnetwork/resolver_test.go +++ b/libnetwork/resolver_test.go @@ -1,6 +1,8 @@ package libnetwork import ( + "encoding/hex" + "errors" "net" "runtime" "syscall" @@ -10,6 +12,7 @@ import ( "github.com/docker/docker/libnetwork/testutils" "github.com/miekg/dns" "github.com/sirupsen/logrus" + "gotest.tools/v3/assert" "gotest.tools/v3/skip" ) @@ -23,7 +26,8 @@ func (a *tstaddr) String() string { return "127.0.0.1" } // a simple writer that implements dns.ResponseWriter for unit testing purposes type tstwriter struct { - msg *dns.Msg + localAddr net.Addr + msg *dns.Msg } func (w *tstwriter) WriteMsg(m *dns.Msg) (err error) { @@ -33,7 +37,12 @@ func (w *tstwriter) WriteMsg(m *dns.Msg) (err error) { func (w *tstwriter) Write(m []byte) (int, error) { return 0, nil } -func (w *tstwriter) LocalAddr() net.Addr { return new(tstaddr) } +func (w *tstwriter) LocalAddr() net.Addr { + if w.localAddr != nil { + return w.localAddr + } + return new(tstaddr) +} func (w *tstwriter) RemoteAddr() net.Addr { return new(tstaddr) } @@ -285,3 +294,117 @@ func TestDNSProxyServFail(t *testing.T) { } t.Logf("Expected number of DNS requests generated") } + +// 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{}).(*resolver) + 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. + oldLevel, oldOut := logrus.StandardLogger().Level, logrus.StandardLogger().Out + defer func() { + logrus.StandardLogger().SetLevel(oldLevel) + logrus.StandardLogger().SetOutput(oldOut) + }() + logrus.StandardLogger().SetLevel(logrus.DebugLevel) + logrus.SetOutput(tlogWriter{t}) + + w := &tstwriter{localAddr: srv.LocalAddr()} + 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) +} + +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(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) {}