348 lines
9.0 KiB
Go
348 lines
9.0 KiB
Go
|
package archival
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/google/go-cmp/cmp"
|
||
|
"github.com/ooni/probe-cli/v3/internal/fakefill"
|
||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
||
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||
|
)
|
||
|
|
||
|
func TestSaverLookupHost(t *testing.T) {
|
||
|
// newResolver helps to create a new resolver.
|
||
|
newResolver := func(addrs []string, err error) model.Resolver {
|
||
|
return &mocks.Resolver{
|
||
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
||
|
return addrs, err
|
||
|
},
|
||
|
MockAddress: func() string {
|
||
|
return "8.8.8.8:53"
|
||
|
},
|
||
|
MockNetwork: func() string {
|
||
|
return "udp"
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
t.Run("on success", func(t *testing.T) {
|
||
|
const domain = "dns.google"
|
||
|
expectAddrs := []string{"8.8.8.8", "8.8.4.4"}
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSLookupValidator{
|
||
|
ExpectALPNs: nil,
|
||
|
ExpectAddrs: expectAddrs,
|
||
|
ExpectDomain: domain,
|
||
|
ExpectLookupType: "getaddrinfo",
|
||
|
ExpectFailure: nil,
|
||
|
ExpectResolverAddress: "8.8.8.8:53",
|
||
|
ExpectResolverNetwork: "udp",
|
||
|
Saver: saver,
|
||
|
}
|
||
|
reso := newResolver(expectAddrs, nil)
|
||
|
ctx := context.Background()
|
||
|
addrs, err := saver.LookupHost(ctx, reso, domain)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if diff := cmp.Diff(expectAddrs, addrs); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("on failure", func(t *testing.T) {
|
||
|
mockedError := netxlite.NewTopLevelGenericErrWrapper(io.EOF)
|
||
|
const domain = "dns.google"
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSLookupValidator{
|
||
|
ExpectALPNs: nil,
|
||
|
ExpectAddrs: nil,
|
||
|
ExpectDomain: domain,
|
||
|
ExpectLookupType: "getaddrinfo",
|
||
|
ExpectFailure: mockedError,
|
||
|
ExpectResolverAddress: "8.8.8.8:53",
|
||
|
ExpectResolverNetwork: "udp",
|
||
|
Saver: saver,
|
||
|
}
|
||
|
reso := newResolver(nil, mockedError)
|
||
|
ctx := context.Background()
|
||
|
addrs, err := saver.LookupHost(ctx, reso, domain)
|
||
|
if !errors.Is(err, mockedError) {
|
||
|
t.Fatal("invalid err", err)
|
||
|
}
|
||
|
if len(addrs) != 0 {
|
||
|
t.Fatal("invalid addrs")
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestSaverLookupHTTPS(t *testing.T) {
|
||
|
// newResolver helps to create a new resolver.
|
||
|
newResolver := func(alpns, ipv4, ipv6 []string, err error) model.Resolver {
|
||
|
return &mocks.Resolver{
|
||
|
MockLookupHTTPS: func(ctx context.Context, domain string) (*model.HTTPSSvc, error) {
|
||
|
if alpns == nil && ipv4 == nil && ipv6 == nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &model.HTTPSSvc{
|
||
|
ALPN: alpns,
|
||
|
IPv4: ipv4,
|
||
|
IPv6: ipv6,
|
||
|
}, err
|
||
|
},
|
||
|
MockAddress: func() string {
|
||
|
return "8.8.8.8:53"
|
||
|
},
|
||
|
MockNetwork: func() string {
|
||
|
return "udp"
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
t.Run("on success", func(t *testing.T) {
|
||
|
const domain = "dns.google"
|
||
|
expectALPN := []string{"h3", "h2", "http/1.1"}
|
||
|
expectA := []string{"8.8.8.8", "8.8.4.4"}
|
||
|
expectAAAA := []string{"2001:4860:4860::8844"}
|
||
|
expectAddrs := append(expectA, expectAAAA...)
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSLookupValidator{
|
||
|
ExpectALPNs: expectALPN,
|
||
|
ExpectAddrs: expectAddrs,
|
||
|
ExpectDomain: domain,
|
||
|
ExpectLookupType: "https",
|
||
|
ExpectFailure: nil,
|
||
|
ExpectResolverAddress: "8.8.8.8:53",
|
||
|
ExpectResolverNetwork: "udp",
|
||
|
Saver: saver,
|
||
|
}
|
||
|
reso := newResolver(expectALPN, expectA, expectAAAA, nil)
|
||
|
ctx := context.Background()
|
||
|
https, err := saver.LookupHTTPS(ctx, reso, domain)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if diff := cmp.Diff(expectALPN, https.ALPN); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
if diff := cmp.Diff(expectA, https.IPv4); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
if diff := cmp.Diff(expectAAAA, https.IPv6); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("on failure", func(t *testing.T) {
|
||
|
mockedError := netxlite.NewTopLevelGenericErrWrapper(io.EOF)
|
||
|
const domain = "dns.google"
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSLookupValidator{
|
||
|
ExpectALPNs: nil,
|
||
|
ExpectAddrs: nil,
|
||
|
ExpectDomain: domain,
|
||
|
ExpectLookupType: "https",
|
||
|
ExpectFailure: mockedError,
|
||
|
ExpectResolverAddress: "8.8.8.8:53",
|
||
|
ExpectResolverNetwork: "udp",
|
||
|
Saver: saver,
|
||
|
}
|
||
|
reso := newResolver(nil, nil, nil, mockedError)
|
||
|
ctx := context.Background()
|
||
|
https, err := saver.LookupHTTPS(ctx, reso, domain)
|
||
|
if !errors.Is(err, mockedError) {
|
||
|
t.Fatal("unexpected err", err)
|
||
|
}
|
||
|
if https != nil {
|
||
|
t.Fatal("expected nil https")
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type SingleDNSLookupValidator struct {
|
||
|
ExpectALPNs []string
|
||
|
ExpectAddrs []string
|
||
|
ExpectDomain string
|
||
|
ExpectLookupType string
|
||
|
ExpectFailure error
|
||
|
ExpectResolverAddress string
|
||
|
ExpectResolverNetwork string
|
||
|
Saver *Saver
|
||
|
}
|
||
|
|
||
|
func (v *SingleDNSLookupValidator) Validate() error {
|
||
|
trace := v.Saver.MoveOutTrace()
|
||
|
var entries []*DNSLookupEvent
|
||
|
switch v.ExpectLookupType {
|
||
|
case "getaddrinfo":
|
||
|
entries = trace.DNSLookupHost
|
||
|
case "https":
|
||
|
entries = trace.DNSLookupHTTPS
|
||
|
default:
|
||
|
return errors.New("invalid v.ExpectLookupType")
|
||
|
}
|
||
|
if len(entries) != 1 {
|
||
|
return errors.New("expected a single entry")
|
||
|
}
|
||
|
entry := entries[0]
|
||
|
if diff := cmp.Diff(v.ExpectALPNs, entry.ALPNs); diff != "" {
|
||
|
return errors.New(diff)
|
||
|
}
|
||
|
if diff := cmp.Diff(v.ExpectAddrs, entry.Addresses); diff != "" {
|
||
|
return errors.New(diff)
|
||
|
}
|
||
|
if v.ExpectDomain != entry.Domain {
|
||
|
return errors.New("invalid .Domain value")
|
||
|
}
|
||
|
if !errors.Is(entry.Failure, v.ExpectFailure) {
|
||
|
return errors.New("invalid .Failure value")
|
||
|
}
|
||
|
if !entry.Finished.After(entry.Started) {
|
||
|
return errors.New(".Finished is not after .Started")
|
||
|
}
|
||
|
if entry.ResolverAddress != v.ExpectResolverAddress {
|
||
|
return errors.New("invalid .ResolverAddress value")
|
||
|
}
|
||
|
if entry.ResolverNetwork != v.ExpectResolverNetwork {
|
||
|
return errors.New("invalid .ResolverNetwork value")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func TestSaverDNSRoundTrip(t *testing.T) {
|
||
|
// generateQueryAndReply generates a fake query and reply.
|
||
|
generateQueryAndReply := func() (query, reply []byte, err error) {
|
||
|
ff := &fakefill.Filler{}
|
||
|
ff.Fill(&query)
|
||
|
ff.Fill(&reply)
|
||
|
if len(query) < 1 || len(reply) < 1 {
|
||
|
return nil, nil, errors.New("did not generate query or reply")
|
||
|
}
|
||
|
return query, reply, nil
|
||
|
}
|
||
|
|
||
|
// newDNSTransport creates a suitable DNSTransport.
|
||
|
newDNSTransport := func(reply []byte, err error) model.DNSTransport {
|
||
|
return &mocks.DNSTransport{
|
||
|
MockRoundTrip: func(ctx context.Context, query []byte) ([]byte, error) {
|
||
|
return reply, err
|
||
|
},
|
||
|
MockNetwork: func() string {
|
||
|
return "udp"
|
||
|
},
|
||
|
MockAddress: func() string {
|
||
|
return "8.8.8.8:53"
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
t.Run("on success", func(t *testing.T) {
|
||
|
query, expectedReply, err := generateQueryAndReply()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSRoundTripValidator{
|
||
|
ExpectAddress: "8.8.8.8:53",
|
||
|
ExpectFailure: nil,
|
||
|
ExpectNetwork: "udp",
|
||
|
ExpectQuery: query,
|
||
|
ExpectReply: expectedReply,
|
||
|
Saver: saver,
|
||
|
}
|
||
|
ctx := context.Background()
|
||
|
txp := newDNSTransport(expectedReply, nil)
|
||
|
reply, err := saver.DNSRoundTrip(ctx, txp, query)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if diff := cmp.Diff(expectedReply, reply); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("on failure", func(t *testing.T) {
|
||
|
mockedError := netxlite.NewTopLevelGenericErrWrapper(io.EOF)
|
||
|
query, _, err := generateQueryAndReply()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
saver := NewSaver()
|
||
|
v := &SingleDNSRoundTripValidator{
|
||
|
ExpectAddress: "8.8.8.8:53",
|
||
|
ExpectFailure: mockedError,
|
||
|
ExpectNetwork: "udp",
|
||
|
ExpectQuery: query,
|
||
|
ExpectReply: nil,
|
||
|
Saver: saver,
|
||
|
}
|
||
|
ctx := context.Background()
|
||
|
txp := newDNSTransport(nil, mockedError)
|
||
|
reply, err := saver.DNSRoundTrip(ctx, txp, query)
|
||
|
if !errors.Is(err, mockedError) {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if len(reply) != 0 {
|
||
|
t.Fatal("unexpected reply")
|
||
|
}
|
||
|
if err := v.Validate(); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type SingleDNSRoundTripValidator struct {
|
||
|
ExpectAddress string
|
||
|
ExpectFailure error
|
||
|
ExpectNetwork string
|
||
|
ExpectQuery []byte
|
||
|
ExpectReply []byte
|
||
|
Saver *Saver
|
||
|
}
|
||
|
|
||
|
func (v *SingleDNSRoundTripValidator) Validate() error {
|
||
|
trace := v.Saver.MoveOutTrace()
|
||
|
if len(trace.DNSRoundTrip) != 1 {
|
||
|
return errors.New("expected a single entry")
|
||
|
}
|
||
|
entry := trace.DNSRoundTrip[0]
|
||
|
if v.ExpectAddress != entry.Address {
|
||
|
return errors.New("invalid .Address")
|
||
|
}
|
||
|
if !errors.Is(entry.Failure, v.ExpectFailure) {
|
||
|
return errors.New("invalid .Failure value")
|
||
|
}
|
||
|
if !entry.Finished.After(entry.Started) {
|
||
|
return errors.New(".Finished is not after .Started")
|
||
|
}
|
||
|
if v.ExpectNetwork != entry.Network {
|
||
|
return errors.New("invalid .Network value")
|
||
|
}
|
||
|
if diff := cmp.Diff(v.ExpectQuery, entry.Query); diff != "" {
|
||
|
return errors.New(diff)
|
||
|
}
|
||
|
if diff := cmp.Diff(v.ExpectReply, entry.Reply); diff != "" {
|
||
|
return errors.New(diff)
|
||
|
}
|
||
|
return nil
|
||
|
}
|