diff --git a/libnetwork/resolver_test.go b/libnetwork/resolver_test.go index 507826af5e..f5deef9972 100644 --- a/libnetwork/resolver_test.go +++ b/libnetwork/resolver_test.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "errors" "net" - "runtime" "syscall" "testing" "time" @@ -16,7 +15,6 @@ import ( "github.com/sirupsen/logrus" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" - "gotest.tools/v3/skip" ) // a simple/null address type that will be used to fake a local address for unit testing @@ -95,102 +93,6 @@ func checkDNSRRType(t *testing.T, actual, expected uint16) { } } -func TestDNSIPQuery(t *testing.T) { - skip.If(t, runtime.GOOS == "windows", "test only works on linux") - - defer netnsutils.SetupTestOSContext(t)() - c, err := New() - if err != nil { - t.Fatal(err) - } - defer c.Stop() - - n, err := c.NewNetwork("bridge", "dtnet1", "", nil) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n.Delete(); err != nil { - t.Fatal(err) - } - }() - - ep, err := n.CreateEndpoint("testep") - if err != nil { - t.Fatal(err) - } - - sb, err := c.NewSandbox("c1") - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := sb.Delete(); err != nil { - t.Fatal(err) - } - }() - - // we need the endpoint only to populate ep_list for the sandbox as part of resolve_name - // it is not set as a target for name resolution and does not serve any other purpose - err = ep.Join(sb) - if err != nil { - t.Fatal(err) - } - - // add service records which are used to resolve names. These are the real targets for the DNS querries - n.addSvcRecords("ep1", "name1", "svc1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test") - - w := new(tstwriter) - // the unit tests right now will focus on non-proxyed DNS requests - r := NewResolver(resolverIPSandbox, false, sb) - - // test name1's IP is resolved correctly with the default A type query - // Also make sure DNS lookups are case insensitive - names := []string{"name1", "NaMe1"} - for _, name := range names { - q := new(dns.Msg) - q.SetQuestion(name, dns.TypeA) - r.serveDNS(w, q) - resp := w.GetResponse() - checkNonNullResponse(t, resp) - t.Log("Response: ", resp.String()) - checkDNSResponseCode(t, resp, dns.RcodeSuccess) - checkDNSAnswersCount(t, resp, 1) - checkDNSRRType(t, resp.Answer[0].Header().Rrtype, dns.TypeA) - if answer, ok := resp.Answer[0].(*dns.A); ok { - if !answer.A.Equal(net.ParseIP("192.168.0.1")) { - t.Fatalf("IP response in Answer %v does not match 192.168.0.1", answer.A) - } - } else { - t.Fatal("Answer of type A not found") - } - w.ClearResponse() - } - - // test MX query with name1 results in Success response with 0 answer records - q := new(dns.Msg) - q.SetQuestion("name1", dns.TypeMX) - r.serveDNS(w, q) - resp := w.GetResponse() - checkNonNullResponse(t, resp) - t.Log("Response: ", resp.String()) - checkDNSResponseCode(t, resp, dns.RcodeSuccess) - checkDNSAnswersCount(t, resp, 0) - w.ClearResponse() - - // test MX query with non existent name results in ServFail response with 0 answer records - // since this is a unit test env, we disable proxying DNS above which results in ServFail rather than NXDOMAIN - q = new(dns.Msg) - q.SetQuestion("nonexistent", dns.TypeMX) - r.serveDNS(w, q) - resp = w.GetResponse() - checkNonNullResponse(t, resp) - t.Log("Response: ", resp.String()) - checkDNSResponseCode(t, resp, dns.RcodeServerFailure) - w.ClearResponse() -} - func newDNSHandlerServFailOnce(requests *int) func(w dns.ResponseWriter, r *dns.Msg) { return func(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) @@ -234,79 +136,6 @@ func waitForLocalDNSServer(t *testing.T) { } } -func TestDNSProxyServFail(t *testing.T) { - skip.If(t, runtime.GOOS == "windows", "test only works on linux") - - osctx := netnsutils.SetupTestOSContextEx(t) - defer osctx.Cleanup(t) - - c, err := New() - if err != nil { - t.Fatal(err) - } - defer c.Stop() - - n, err := c.NewNetwork("bridge", "dtnet2", "", nil) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := n.Delete(); err != nil { - t.Fatal(err) - } - }() - - sb, err := c.NewSandbox("c1") - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := sb.Delete(); err != nil { - t.Fatal(err) - } - }() - - var nRequests int - // initialize a local DNS server and configure it to fail the first query - dns.HandleFunc(".", newDNSHandlerServFailOnce(&nRequests)) - // use TCP for predictable results. Connection tests (to figure out DNS server initialization) don't work with UDP - server := &dns.Server{Addr: "127.0.0.1:53", Net: "tcp"} - srvErrCh := make(chan error, 1) - osctx.Go(t, func() { - srvErrCh <- server.ListenAndServe() - }) - defer func() { - server.Shutdown() //nolint:errcheck - if err := <-srvErrCh; err != nil { - t.Error(err) - } - }() - - waitForLocalDNSServer(t) - t.Log("DNS Server can be reached") - - w := new(tstwriter) - r := NewResolver(resolverIPSandbox, true, sb) - q := new(dns.Msg) - q.SetQuestion("name1.", dns.TypeA) - - var localDNSEntries []extDNSEntry - extTestDNSEntry := extDNSEntry{IPStr: "127.0.0.1", HostLoopback: true} - - // configure two external DNS entries and point both to local DNS server thread - localDNSEntries = append(localDNSEntries, extTestDNSEntry) - localDNSEntries = append(localDNSEntries, extTestDNSEntry) - - // this should generate two requests: the first will fail leading to a retry - r.SetExtServers(localDNSEntries) - r.serveDNS(w, q) - if nRequests != 2 { - t.Fatalf("Expected 2 DNS querries. Found: %d", nRequests) - } - 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) diff --git a/libnetwork/resolver_unix_test.go b/libnetwork/resolver_unix_test.go new file mode 100644 index 0000000000..5493c16cca --- /dev/null +++ b/libnetwork/resolver_unix_test.go @@ -0,0 +1,178 @@ +//go:build !windows + +package libnetwork + +import ( + "net" + "testing" + + "github.com/docker/docker/internal/testutils/netnsutils" + "github.com/miekg/dns" +) + +// test only works on linux +func TestDNSIPQuery(t *testing.T) { + defer netnsutils.SetupTestOSContext(t)() + c, err := New() + if err != nil { + t.Fatal(err) + } + defer c.Stop() + + n, err := c.NewNetwork("bridge", "dtnet1", "", nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep, err := n.CreateEndpoint("testep") + if err != nil { + t.Fatal(err) + } + + sb, err := c.NewSandbox("c1") + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := sb.Delete(); err != nil { + t.Fatal(err) + } + }() + + // we need the endpoint only to populate ep_list for the sandbox as part of resolve_name + // it is not set as a target for name resolution and does not serve any other purpose + err = ep.Join(sb) + if err != nil { + t.Fatal(err) + } + + // add service records which are used to resolve names. These are the real targets for the DNS querries + n.addSvcRecords("ep1", "name1", "svc1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test") + + w := new(tstwriter) + // the unit tests right now will focus on non-proxyed DNS requests + r := NewResolver(resolverIPSandbox, false, sb) + + // test name1's IP is resolved correctly with the default A type query + // Also make sure DNS lookups are case insensitive + names := []string{"name1", "NaMe1"} + for _, name := range names { + q := new(dns.Msg) + q.SetQuestion(name, dns.TypeA) + r.serveDNS(w, q) + resp := w.GetResponse() + checkNonNullResponse(t, resp) + t.Log("Response: ", resp.String()) + checkDNSResponseCode(t, resp, dns.RcodeSuccess) + checkDNSAnswersCount(t, resp, 1) + checkDNSRRType(t, resp.Answer[0].Header().Rrtype, dns.TypeA) + if answer, ok := resp.Answer[0].(*dns.A); ok { + if !answer.A.Equal(net.ParseIP("192.168.0.1")) { + t.Fatalf("IP response in Answer %v does not match 192.168.0.1", answer.A) + } + } else { + t.Fatal("Answer of type A not found") + } + w.ClearResponse() + } + + // test MX query with name1 results in Success response with 0 answer records + q := new(dns.Msg) + q.SetQuestion("name1", dns.TypeMX) + r.serveDNS(w, q) + resp := w.GetResponse() + checkNonNullResponse(t, resp) + t.Log("Response: ", resp.String()) + checkDNSResponseCode(t, resp, dns.RcodeSuccess) + checkDNSAnswersCount(t, resp, 0) + w.ClearResponse() + + // test MX query with non existent name results in ServFail response with 0 answer records + // since this is a unit test env, we disable proxying DNS above which results in ServFail rather than NXDOMAIN + q = new(dns.Msg) + q.SetQuestion("nonexistent", dns.TypeMX) + r.serveDNS(w, q) + resp = w.GetResponse() + checkNonNullResponse(t, resp) + t.Log("Response: ", resp.String()) + checkDNSResponseCode(t, resp, dns.RcodeServerFailure) + w.ClearResponse() +} + +// test only works on linux +func TestDNSProxyServFail(t *testing.T) { + osctx := netnsutils.SetupTestOSContextEx(t) + defer osctx.Cleanup(t) + + c, err := New() + if err != nil { + t.Fatal(err) + } + defer c.Stop() + + n, err := c.NewNetwork("bridge", "dtnet2", "", nil) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + sb, err := c.NewSandbox("c1") + if err != nil { + t.Fatal(err) + } + + defer func() { + if err := sb.Delete(); err != nil { + t.Fatal(err) + } + }() + + var nRequests int + // initialize a local DNS server and configure it to fail the first query + dns.HandleFunc(".", newDNSHandlerServFailOnce(&nRequests)) + // use TCP for predictable results. Connection tests (to figure out DNS server initialization) don't work with UDP + server := &dns.Server{Addr: "127.0.0.1:53", Net: "tcp"} + srvErrCh := make(chan error, 1) + osctx.Go(t, func() { + srvErrCh <- server.ListenAndServe() + }) + defer func() { + server.Shutdown() //nolint:errcheck + if err := <-srvErrCh; err != nil { + t.Error(err) + } + }() + + waitForLocalDNSServer(t) + t.Log("DNS Server can be reached") + + w := new(tstwriter) + r := NewResolver(resolverIPSandbox, true, sb) + q := new(dns.Msg) + q.SetQuestion("name1.", dns.TypeA) + + var localDNSEntries []extDNSEntry + extTestDNSEntry := extDNSEntry{IPStr: "127.0.0.1", HostLoopback: true} + + // configure two external DNS entries and point both to local DNS server thread + localDNSEntries = append(localDNSEntries, extTestDNSEntry) + localDNSEntries = append(localDNSEntries, extTestDNSEntry) + + // this should generate two requests: the first will fail leading to a retry + r.SetExtServers(localDNSEntries) + r.serveDNS(w, q) + if nRequests != 2 { + t.Fatalf("Expected 2 DNS querries. Found: %d", nRequests) + } + t.Logf("Expected number of DNS requests generated") +}