feat(netxlite): implements NS queries (#734)

This diff has been extracted from eb0bf38957.

See https://github.com/ooni/probe/issues/2096.

While there, skip the broken tests caused by issue
https://github.com/ooni/probe/issues/2098.
This commit is contained in:
Simone Basso 2022-05-16 10:46:53 +02:00 committed by GitHub
commit ce052b665e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 859 additions and 75 deletions

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"net"
"strings"
"sync"
"testing"
@ -166,6 +167,17 @@ func TestResolverSystem(t *testing.T) {
t.Fatal("expected nil result")
}
})
t.Run("LookupNS", func(t *testing.T) {
r := &resolverSystem{}
ns, err := r.LookupNS(context.Background(), "x.org")
if !errors.Is(err, ErrNoDNSTransport) {
t.Fatal("not the error we expected")
}
if ns != nil {
t.Fatal("expected nil result")
}
})
}
func TestResolverLogger(t *testing.T) {
@ -312,6 +324,94 @@ func TestResolverLogger(t *testing.T) {
})
})
t.Run("CloseIdleConnections", func(t *testing.T) {
var called bool
child := &mocks.Resolver{
MockCloseIdleConnections: func() {
called = true
},
}
reso := &resolverLogger{
Resolver: child,
Logger: model.DiscardLogger,
}
reso.CloseIdleConnections()
if !called {
t.Fatal("not called")
}
})
t.Run("LookupNS", func(t *testing.T) {
t.Run("with success", func(t *testing.T) {
var count int
lo := &mocks.Logger{
MockDebugf: func(format string, v ...interface{}) {
count++
},
}
expected := []*net.NS{{
Host: "ns1.zdns.google.",
}}
r := &resolverLogger{
Logger: lo,
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return expected, nil
},
MockNetwork: func() string {
return "system"
},
MockAddress: func() string {
return ""
},
},
}
ns, err := r.LookupNS(context.Background(), "dns.google")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expected, ns); diff != "" {
t.Fatal(diff)
}
if count != 2 {
t.Fatal("unexpected count")
}
})
t.Run("with failure", func(t *testing.T) {
var count int
lo := &mocks.Logger{
MockDebugf: func(format string, v ...interface{}) {
count++
},
}
expected := errors.New("mocked error")
r := &resolverLogger{
Logger: lo,
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, expected
},
MockNetwork: func() string {
return "system"
},
MockAddress: func() string {
return ""
},
},
}
ns, err := r.LookupNS(context.Background(), "dns.google")
if !errors.Is(err, expected) {
t.Fatal("not the error we expected", err)
}
if ns != nil {
t.Fatal("expected nil addr here")
}
if count != 2 {
t.Fatal("unexpected count")
}
})
})
}
func TestResolverIDNA(t *testing.T) {
@ -424,6 +524,63 @@ func TestResolverIDNA(t *testing.T) {
t.Fatal("invalid address")
}
})
t.Run("CloseIdleConnections", func(t *testing.T) {
var called bool
child := &mocks.Resolver{
MockCloseIdleConnections: func() {
called = true
},
}
reso := &resolverIDNA{child}
reso.CloseIdleConnections()
if !called {
t.Fatal("not called")
}
})
t.Run("LookupNS", func(t *testing.T) {
t.Run("with valid IDNA in input", func(t *testing.T) {
expected := []*net.NS{{
Host: "ns1.zdns.google.",
}}
r := &resolverIDNA{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
if domain != "xn--d1acpjx3f.xn--p1ai" {
return nil, errors.New("passed invalid domain")
}
return expected, nil
},
},
}
ctx := context.Background()
ns, err := r.LookupNS(ctx, "яндекс.рф")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expected, ns); diff != "" {
t.Fatal(diff)
}
})
t.Run("with invalid punycode", func(t *testing.T) {
r := &resolverIDNA{Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, errors.New("should not happen")
},
}}
// See https://www.farsightsecurity.com/blog/txt-record/punycode-20180711/
ctx := context.Background()
ns, err := r.LookupNS(ctx, "xn--0000h")
if err == nil || !strings.HasPrefix(err.Error(), "idna: invalid label") {
t.Fatal("not the error we expected")
}
if ns != nil {
t.Fatal("expected no response here")
}
})
})
}
func TestResolverShortCircuitIPAddr(t *testing.T) {
@ -520,6 +677,100 @@ func TestResolverShortCircuitIPAddr(t *testing.T) {
}
})
})
t.Run("LookupNS", func(t *testing.T) {
t.Run("with IPv4 addr", func(t *testing.T) {
r := &resolverShortCircuitIPAddr{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, errors.New("mocked error")
},
},
}
ctx := context.Background()
ns, err := r.LookupNS(ctx, "8.8.8.8")
if !errors.Is(err, ErrDNSIPAddress) {
t.Fatal("unexpected error", err)
}
if len(ns) > 0 {
t.Fatal("invalid result")
}
})
t.Run("with IPv6 addr", func(t *testing.T) {
r := &resolverShortCircuitIPAddr{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, errors.New("mocked error")
},
},
}
ctx := context.Background()
ns, err := r.LookupNS(ctx, "::1")
if !errors.Is(err, ErrDNSIPAddress) {
t.Fatal("unexpected error", err)
}
if len(ns) > 0 {
t.Fatal("invalid result")
}
})
t.Run("with domain", func(t *testing.T) {
r := &resolverShortCircuitIPAddr{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, errors.New("mocked error")
},
},
}
ctx := context.Background()
ns, err := r.LookupNS(ctx, "dns.google")
if err == nil || err.Error() != "mocked error" {
t.Fatal("not the error we expected", err)
}
if len(ns) > 0 {
t.Fatal("invalid result")
}
})
})
t.Run("Network", func(t *testing.T) {
child := &mocks.Resolver{
MockNetwork: func() string {
return "x"
},
}
reso := &resolverShortCircuitIPAddr{child}
if reso.Network() != "x" {
t.Fatal("invalid result")
}
})
t.Run("Address", func(t *testing.T) {
child := &mocks.Resolver{
MockAddress: func() string {
return "x"
},
}
reso := &resolverShortCircuitIPAddr{child}
if reso.Address() != "x" {
t.Fatal("invalid result")
}
})
t.Run("CloseIdleConnections", func(t *testing.T) {
var called bool
child := &mocks.Resolver{
MockCloseIdleConnections: func() {
called = true
},
}
reso := &resolverShortCircuitIPAddr{child}
reso.CloseIdleConnections()
if !called {
t.Fatal("not called")
}
})
}
func TestIsIPv6(t *testing.T) {
@ -592,6 +843,18 @@ func TestNullResolver(t *testing.T) {
}
r.CloseIdleConnections() // for coverage
})
t.Run("LookupNS", func(t *testing.T) {
r := &nullResolver{}
ctx := context.Background()
ns, err := r.LookupNS(ctx, "dns.google")
if !errors.Is(err, ErrNoResolver) {
t.Fatal("unexpected error", err)
}
if len(ns) > 0 {
t.Fatal("unexpected result")
}
})
}
func TestResolverErrWrapper(t *testing.T) {
@ -719,4 +982,46 @@ func TestResolverErrWrapper(t *testing.T) {
}
})
})
t.Run("LookupNS", func(t *testing.T) {
t.Run("on success", func(t *testing.T) {
expected := []*net.NS{{
Host: "antani.local.",
}}
reso := &resolverErrWrapper{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return expected, nil
},
},
}
ctx := context.Background()
ns, err := reso.LookupNS(ctx, "antani.local")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expected, ns); diff != "" {
t.Fatal(diff)
}
})
t.Run("on failure", func(t *testing.T) {
expected := io.EOF
reso := &resolverErrWrapper{
Resolver: &mocks.Resolver{
MockLookupNS: func(ctx context.Context, domain string) ([]*net.NS, error) {
return nil, expected
},
},
}
ctx := context.Background()
ns, err := reso.LookupNS(ctx, "")
if err == nil || err.Error() != FailureEOFError {
t.Fatal("unexpected err", err)
}
if len(ns) > 0 {
t.Fatal("unexpected addrs")
}
})
})
}