Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: TRR2 resolver #846

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
testing: add test for simple resolver
  • Loading branch information
decfox committed Aug 4, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 8c594c20eb13953830cfde51902a81b796f25de2
206 changes: 204 additions & 2 deletions internal/measurexlite/dns_test.go
Original file line number Diff line number Diff line change
@@ -12,8 +12,8 @@ import (
"github.com/ooni/probe-cli/v3/internal/testingx"
)

func TestNewUnwrappedParallelResolver(t *testing.T) {
t.Run("NewUnwrappedParallelResolver creates an UnwrappedParallelResolver with Trace", func(t *testing.T) {
func TestNewParallelResolver(t *testing.T) {
t.Run("NewParallelResolverTrace creates an ParallelResolver with Trace", func(t *testing.T) {
underlying := &mocks.Resolver{}
zeroTime := time.Now()
trace := NewTrace(0, zeroTime)
@@ -221,6 +221,208 @@ func TestNewUnwrappedParallelResolver(t *testing.T) {
})
}

func TestNewSimpleResolver(t *testing.T) {
t.Run("NewSimpleResolverTrace creates a SimpleResolver with Trace", func(t *testing.T) {
underlying := &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
return []string{}, nil
},
}
zeroTime := time.Now()
trace := NewTrace(0, zeroTime)
trace.NewSimpleResolverFn = func() model.SimpleResolver {
return underlying
}
resolver := trace.newSimpleResolverTrace(func() model.SimpleResolver {
return nil
})
resolvert := resolver.(*simpleResolverTrace)
if resolvert.r != underlying {
t.Fatal("invalid simple resolver")
}
if resolvert.tx != trace {
t.Fatal("invalid trace")
}
})

t.Run("Trace-aware resolver forwards underlying functions", func(t *testing.T) {
zeroTime := time.Now()
trace := NewTrace(0, zeroTime)
newMockResolver := func() model.SimpleResolver {
return &mocks.Resolver{
MockNetwork: func() string {
return "udp"
},
}
}
resolver := trace.newSimpleResolver(newMockResolver)

t.Run("Network is correctly forwarded", func(t *testing.T) {
got := resolver.Network()
if got != "udp" {
t.Fatal("Network not called")
}
})
})

t.Run("LookupHost saves into trace", func(t *testing.T) {
zeroTime := time.Now()
td := testingx.NewTimeDeterministic(zeroTime)
trace := NewTrace(0, zeroTime)
trace.TimeNowFn = td.Now
txp := &mocks.DNSTransport{
MockRoundTrip: func(ctx context.Context, query model.DNSQuery) (model.DNSResponse, error) {
response := &mocks.DNSResponse{
MockDecodeLookupHost: func() ([]string, error) {
if query.Type() != dns.TypeA {
return []string{"fe80::a00:20ff:feb9:4c54"}, nil
}
return []string{"1.1.1.1"}, nil
},
}
return response, nil
},
MockRequiresPadding: func() bool {
return true
},
MockNetwork: func() string {
return ""
},
MockAddress: func() string {
return "dns.google"
},
}
newSimpleResolver := func() model.SimpleResolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
reso := netxlite.NewUnwrappedParallelResolver(txp)
return reso.LookupHost(ctx, domain)
},
}
}
resolver := trace.newSimpleResolverTrace(newSimpleResolver)
ctx := context.Background()
addrs, err := resolver.LookupHost(ctx, "example.com")
if err != nil {
t.Fatal("unexpected err", err)
}
if len(addrs) != 2 {
t.Fatal("unexpected array output", addrs)
}
if addrs[0] != "1.1.1.1" && addrs[1] != "1.1.1.1" {
t.Fatal("unexpected array output", addrs)
}
if addrs[0] != "fe80::a00:20ff:feb9:4c54" && addrs[1] != "fe80::a00:20ff:feb9:4c54" {
t.Fatal("unexpected array output", addrs)
}

t.Run("DNSLookups QueryType A", func(t *testing.T) {
events := trace.DNSLookupsFromRoundTrip(dns.TypeA)
if len(events) != 1 {
t.Fatal("expected to see single DNSLookup event")
}
lookup := events[0]
answers := lookup.Answers
if lookup.Failure != nil {
t.Fatal("unexpected err", *(lookup.Failure))
}
if lookup.ResolverAddress != "dns.google" {
t.Fatal("unexpected address field")
}
if len(answers) != 1 {
t.Fatal("expected 1 DNS answer, got", len(answers))
}
if answers[0].AnswerType != "A" || answers[0].IPv4 != "1.1.1.1" {
t.Fatal("unexpected DNS answer", answers)
}
})

t.Run("DNSLookups QueryType AAAA", func(t *testing.T) {
events := trace.DNSLookupsFromRoundTrip(dns.TypeAAAA)
if len(events) != 1 {
t.Fatal("expected to see single DNSLookup event")
}
lookup := events[0]
answers := lookup.Answers
if lookup.Failure != nil {
t.Fatal("unexpected err", *(lookup.Failure))
}
if lookup.ResolverAddress != "dns.google" {
t.Fatal("unexpected address field")
}
if len(answers) != 1 {
t.Fatal("expected 1 DNS answer, got", len(answers))
}
if answers[0].AnswerType != "AAAA" || answers[0].IPv6 != "fe80::a00:20ff:feb9:4c54" {
t.Fatal("unexpected DNS answer", answers)
}
})
})

t.Run("LookupHost discards events when buffers are full", func(t *testing.T) {
zeroTime := time.Now()
td := testingx.NewTimeDeterministic(zeroTime)
trace := NewTrace(0, zeroTime)
trace.DNSLookup = map[uint16]chan *model.ArchivalDNSLookupResult{
dns.TypeA: make(chan *model.ArchivalDNSLookupResult), // no buffer
dns.TypeAAAA: make(chan *model.ArchivalDNSLookupResult), // no buffer
}
trace.TimeNowFn = td.Now
txp := &mocks.DNSTransport{
MockRoundTrip: func(ctx context.Context, query model.DNSQuery) (model.DNSResponse, error) {
response := &mocks.DNSResponse{
MockDecodeLookupHost: func() ([]string, error) {
if query.Type() != dns.TypeA {
return []string{"fe80::a00:20ff:feb9:4c54"}, nil
}
return []string{"1.1.1.1"}, nil
},
}
return response, nil
},
MockRequiresPadding: func() bool {
return true
},
MockNetwork: func() string {
return ""
},
MockAddress: func() string {
return "dns.google"
},
}
newSimpleResolver := func() model.SimpleResolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
reso := netxlite.NewUnwrappedParallelResolver(txp)
return reso.LookupHost(ctx, domain)
},
}
}
resolver := trace.newSimpleResolverTrace(newSimpleResolver)
ctx := context.Background()
addrs, err := resolver.LookupHost(ctx, "example.com")
if err != nil {
t.Fatal("unexpected err", err)
}
if len(addrs) != 2 {
t.Fatal("unexpected array output", addrs)
}

t.Run("DNSLookups QueryType A", func(t *testing.T) {
events := trace.DNSLookupsFromRoundTrip(dns.TypeA)
if len(events) != 0 {
t.Fatal("expected to see no DNSLookup")
}
})
t.Run("DNSLookups QueryType AAAA", func(t *testing.T) {
events := trace.DNSLookupsFromRoundTrip(dns.TypeAAAA)
if len(events) != 0 {
t.Fatal("expected to see no DNSLookup")
}
})
})
}

func TestAnswersFromAddrs(t *testing.T) {
tests := []struct {
name string
57 changes: 57 additions & 0 deletions internal/measurexlite/trace_test.go
Original file line number Diff line number Diff line change
@@ -52,6 +52,12 @@ func TestNewTrace(t *testing.T) {
}
})

t.Run("NewSimpleResolverFn is nil", func(t *testing.T) {
if trace.NewSimpleResolverFn != nil {
t.Fatal("expected nil NewSimpleResolverFn")
}
})

t.Run("NewDialerWithoutResolverFn is nil", func(t *testing.T) {
if trace.NewDialerWithoutResolverFn != nil {
t.Fatal("expected nil NewDialerWithoutResolverFn")
@@ -189,6 +195,57 @@ func TestTrace(t *testing.T) {
})
})

t.Run("NewSimpleResolverFn works as intended", func(t *testing.T) {
t.Run("when not nil", func(t *testing.T) {
mockedErr := errors.New("mocked")
tx := &Trace{
NewSimpleResolverFn: func() model.SimpleResolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
return []string{}, mockedErr
},
}
},
}
resolver := tx.newSimpleResolver(func() model.SimpleResolver {
return nil
})
ctx := context.Background()
addrs, err := resolver.LookupHost(ctx, "example.com")
if !errors.Is(err, mockedErr) {
t.Fatal("unexpected err", err)
}
if len(addrs) != 0 {
t.Fatal("expected array of size 0")
}
})

t.Run("when nil", func(t *testing.T) {
tx := &Trace{
NewSimpleResolverFn: nil,
}
newResolver := func() model.SimpleResolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
return []string{"1.1.1.1"}, nil
},
}
}
resolver := tx.newSimpleResolver(newResolver)
ctx := context.Background()
addrs, err := resolver.LookupHost(ctx, "example.com")
if err != nil {
t.Fatal("unexpected err", err)
}
if len(addrs) != 1 {
t.Fatal("expected array of size 1")
}
if addrs[0] != "1.1.1.1" {
t.Fatal("unexpected array output", addrs)
}
})
})

t.Run("NewDialerWithoutResolverFn works as intended", func(t *testing.T) {
t.Run("when not nil", func(t *testing.T) {
mockedErr := errors.New("mocked")
Loading