refactor: DNSTransport I/Os DNS messages (#760)
This diff refactors the DNSTransport model to receive in input a DNSQuery and return in output a DNSResponse. The design of DNSQuery and DNSResponse takes into account the use case of a transport using getaddrinfo, meaning that we don't need to serialize and deserialize messages when using getaddrinfo. The current codebase does not use a getaddrinfo transport, but I wrote one such a transport in the Websteps Winter 2021 prototype (https://github.com/bassosimone/websteps-illustrated/). The design conversation that lead to producing this diff is https://github.com/ooni/probe/issues/2099
This commit is contained in:
@@ -1,36 +1,20 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"net"
|
||||
//
|
||||
// Mocks for model.DNSDecoder
|
||||
//
|
||||
|
||||
"github.com/miekg/dns"
|
||||
import (
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
// DNSDecoder allows mocking dnsx.DNSDecoder.
|
||||
// DNSDecoder allows mocking model.DNSDecoder.
|
||||
type DNSDecoder struct {
|
||||
MockDecodeLookupHost func(qtype uint16, reply []byte, queryID uint16) ([]string, error)
|
||||
MockDecodeHTTPS func(reply []byte, queryID uint16) (*model.HTTPSSvc, error)
|
||||
MockDecodeNS func(reply []byte, queryID uint16) ([]*net.NS, error)
|
||||
MockDecodeReply func(reply []byte) (*dns.Msg, error)
|
||||
MockDecodeResponse func(data []byte, query model.DNSQuery) (model.DNSResponse, error)
|
||||
}
|
||||
|
||||
// DecodeLookupHost calls MockDecodeLookupHost.
|
||||
func (e *DNSDecoder) DecodeLookupHost(qtype uint16, reply []byte, queryID uint16) ([]string, error) {
|
||||
return e.MockDecodeLookupHost(qtype, reply, queryID)
|
||||
}
|
||||
var _ model.DNSDecoder = &DNSDecoder{}
|
||||
|
||||
// DecodeHTTPS calls MockDecodeHTTPS.
|
||||
func (e *DNSDecoder) DecodeHTTPS(reply []byte, queryID uint16) (*model.HTTPSSvc, error) {
|
||||
return e.MockDecodeHTTPS(reply, queryID)
|
||||
}
|
||||
|
||||
// DecodeNS calls MockDecodeNS.
|
||||
func (e *DNSDecoder) DecodeNS(reply []byte, queryID uint16) ([]*net.NS, error) {
|
||||
return e.MockDecodeNS(reply, queryID)
|
||||
}
|
||||
|
||||
// DecodeReply calls MockDecodeReply.
|
||||
func (e *DNSDecoder) DecodeReply(reply []byte) (*dns.Msg, error) {
|
||||
return e.MockDecodeReply(reply)
|
||||
func (e *DNSDecoder) DecodeResponse(data []byte, query model.DNSQuery) (model.DNSResponse, error) {
|
||||
return e.MockDecodeResponse(data, query)
|
||||
}
|
||||
|
||||
@@ -2,70 +2,20 @@ package mocks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
func TestDNSDecoder(t *testing.T) {
|
||||
t.Run("DecodeLookupHost", func(t *testing.T) {
|
||||
t.Run("DecodeResponse", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
e := &DNSDecoder{
|
||||
MockDecodeLookupHost: func(qtype uint16, reply []byte, queryID uint16) ([]string, error) {
|
||||
MockDecodeResponse: func(reply []byte, query model.DNSQuery) (model.DNSResponse, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := e.DecodeLookupHost(dns.TypeA, make([]byte, 17), dns.Id())
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeHTTPS", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
e := &DNSDecoder{
|
||||
MockDecodeHTTPS: func(reply []byte, queryID uint16) (*model.HTTPSSvc, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := e.DecodeHTTPS(make([]byte, 17), dns.Id())
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeNS", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
e := &DNSDecoder{
|
||||
MockDecodeNS: func(reply []byte, queryID uint16) ([]*net.NS, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := e.DecodeNS(make([]byte, 17), dns.Id())
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeReply", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
e := &DNSDecoder{
|
||||
MockDecodeReply: func(reply []byte) (*dns.Msg, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := e.DecodeReply(make([]byte, 17))
|
||||
out, err := e.DecodeResponse(make([]byte, 17), &DNSQuery{})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package mocks
|
||||
|
||||
// DNSEncoder allows mocking dnsx.DNSEncoder.
|
||||
//
|
||||
// Mocks for model.DNSEncoder.
|
||||
//
|
||||
|
||||
import "github.com/ooni/probe-cli/v3/internal/model"
|
||||
|
||||
// DNSEncoder allows mocking model.DNSEncoder.
|
||||
type DNSEncoder struct {
|
||||
MockEncode func(domain string, qtype uint16, padding bool) ([]byte, uint16, error)
|
||||
MockEncode func(domain string, qtype uint16, padding bool) model.DNSQuery
|
||||
}
|
||||
|
||||
var _ model.DNSEncoder = &DNSEncoder{}
|
||||
|
||||
// Encode calls MockEncode.
|
||||
func (e *DNSEncoder) Encode(domain string, qtype uint16, padding bool) ([]byte, uint16, error) {
|
||||
func (e *DNSEncoder) Encode(domain string, qtype uint16, padding bool) model.DNSQuery {
|
||||
return e.MockEncode(domain, qtype, padding)
|
||||
}
|
||||
|
||||
@@ -5,24 +5,46 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
func TestDNSEncoder(t *testing.T) {
|
||||
t.Run("Encode", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
queryID := dns.Id()
|
||||
e := &DNSEncoder{
|
||||
MockEncode: func(domain string, qtype uint16, padding bool) ([]byte, uint16, error) {
|
||||
return nil, 0, expected
|
||||
MockEncode: func(domain string, qtype uint16, padding bool) model.DNSQuery {
|
||||
return &DNSQuery{
|
||||
MockDomain: func() string {
|
||||
return dns.Fqdn(domain) // do what an implementation MUST do
|
||||
},
|
||||
MockType: func() uint16 {
|
||||
return qtype
|
||||
},
|
||||
MockBytes: func() ([]byte, error) {
|
||||
return nil, expected
|
||||
},
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
out, queryID, err := e.Encode("dns.google", dns.TypeA, true)
|
||||
query := e.Encode("dns.google", dns.TypeA, true)
|
||||
if query.Domain() != "dns.google." {
|
||||
t.Fatal("invalid domain")
|
||||
}
|
||||
if query.Type() != dns.TypeA {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
out, err := query.Bytes()
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
if queryID != 0 {
|
||||
if query.ID() != queryID {
|
||||
t.Fatal("unexpected queryID")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package mocks
|
||||
|
||||
//
|
||||
// Mocks for model.DNSQuery.
|
||||
//
|
||||
|
||||
import "github.com/ooni/probe-cli/v3/internal/model"
|
||||
|
||||
// DNSQuery allocks mocking model.DNSQuery.
|
||||
type DNSQuery struct {
|
||||
MockDomain func() string
|
||||
MockType func() uint16
|
||||
MockBytes func() ([]byte, error)
|
||||
MockID func() uint16
|
||||
}
|
||||
|
||||
func (q *DNSQuery) Domain() string {
|
||||
return q.MockDomain()
|
||||
}
|
||||
|
||||
func (q *DNSQuery) Type() uint16 {
|
||||
return q.MockType()
|
||||
}
|
||||
|
||||
func (q *DNSQuery) Bytes() ([]byte, error) {
|
||||
return q.MockBytes()
|
||||
}
|
||||
|
||||
func (q *DNSQuery) ID() uint16 {
|
||||
return q.MockID()
|
||||
}
|
||||
|
||||
var _ model.DNSQuery = &DNSQuery{}
|
||||
@@ -0,0 +1,62 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestDNSQuery(t *testing.T) {
|
||||
t.Run("Domain", func(t *testing.T) {
|
||||
expected := "dns.google."
|
||||
q := &DNSQuery{
|
||||
MockDomain: func() string {
|
||||
return expected
|
||||
},
|
||||
}
|
||||
if q.Domain() != expected {
|
||||
t.Fatal("invalid domain")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Type", func(t *testing.T) {
|
||||
expected := dns.TypeAAAA
|
||||
q := &DNSQuery{
|
||||
MockType: func() uint16 {
|
||||
return expected
|
||||
},
|
||||
}
|
||||
if q.Type() != expected {
|
||||
t.Fatal("invalid type")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Bytes", func(t *testing.T) {
|
||||
expected := []byte{0xde, 0xea, 0xad, 0xbe, 0xef}
|
||||
q := &DNSQuery{
|
||||
MockBytes: func() ([]byte, error) {
|
||||
return expected, nil
|
||||
},
|
||||
}
|
||||
out, err := q.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(expected, out) {
|
||||
t.Fatal("invalid bytes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID", func(t *testing.T) {
|
||||
expected := dns.Id()
|
||||
q := &DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return expected
|
||||
},
|
||||
}
|
||||
if q.ID() != expected {
|
||||
t.Fatal("invalid id")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package mocks
|
||||
|
||||
//
|
||||
// Mocks for model.DNSResponse
|
||||
//
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
// DNSResponse allows mocking model.DNSResponse.
|
||||
type DNSResponse struct {
|
||||
MockQuery func() model.DNSQuery
|
||||
MockBytes func() []byte
|
||||
MockRcode func() int
|
||||
MockDecodeHTTPS func() (*model.HTTPSSvc, error)
|
||||
MockDecodeLookupHost func() ([]string, error)
|
||||
MockDecodeNS func() ([]*net.NS, error)
|
||||
}
|
||||
|
||||
var _ model.DNSResponse = &DNSResponse{}
|
||||
|
||||
func (r *DNSResponse) Query() model.DNSQuery {
|
||||
return r.MockQuery()
|
||||
}
|
||||
|
||||
func (r *DNSResponse) Bytes() []byte {
|
||||
return r.MockBytes()
|
||||
}
|
||||
|
||||
func (r *DNSResponse) Rcode() int {
|
||||
return r.MockRcode()
|
||||
}
|
||||
|
||||
func (r *DNSResponse) DecodeHTTPS() (*model.HTTPSSvc, error) {
|
||||
return r.MockDecodeHTTPS()
|
||||
}
|
||||
|
||||
func (r *DNSResponse) DecodeLookupHost() ([]string, error) {
|
||||
return r.MockDecodeLookupHost()
|
||||
}
|
||||
|
||||
func (r *DNSResponse) DecodeNS() ([]*net.NS, error) {
|
||||
return r.MockDecodeNS()
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
func TestDNSResponse(t *testing.T) {
|
||||
t.Run("Query", func(t *testing.T) {
|
||||
qid := dns.Id()
|
||||
query := &DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return qid
|
||||
},
|
||||
}
|
||||
resp := &DNSResponse{
|
||||
MockQuery: func() model.DNSQuery {
|
||||
return query
|
||||
},
|
||||
}
|
||||
out := resp.Query()
|
||||
if out.ID() != query.ID() {
|
||||
t.Fatal("invalid query")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Bytes", func(t *testing.T) {
|
||||
expected := []byte{0xde, 0xea, 0xad, 0xbe, 0xef}
|
||||
resp := &DNSResponse{
|
||||
MockBytes: func() []byte {
|
||||
return expected
|
||||
},
|
||||
}
|
||||
out := resp.Bytes()
|
||||
if !bytes.Equal(expected, out) {
|
||||
t.Fatal("invalid bytes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Rcode", func(t *testing.T) {
|
||||
expected := dns.RcodeBadAlg
|
||||
resp := &DNSResponse{
|
||||
MockRcode: func() int {
|
||||
return expected
|
||||
},
|
||||
}
|
||||
out := resp.Rcode()
|
||||
if out != expected {
|
||||
t.Fatal("invalid rcode")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeLookupHost", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
r := &DNSResponse{
|
||||
MockDecodeLookupHost: func() ([]string, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := r.DecodeLookupHost()
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeHTTPS", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
r := &DNSResponse{
|
||||
MockDecodeHTTPS: func() (*model.HTTPSSvc, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := r.DecodeHTTPS()
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecodeNS", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
r := &DNSResponse{
|
||||
MockDecodeNS: func() ([]*net.NS, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
out, err := r.DecodeNS()
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("unexpected out")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package mocks
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
// DNSTransport allows mocking dnsx.DNSTransport.
|
||||
type DNSTransport struct {
|
||||
MockRoundTrip func(ctx context.Context, query []byte) ([]byte, error)
|
||||
MockRoundTrip func(ctx context.Context, query model.DNSQuery) (model.DNSResponse, error)
|
||||
|
||||
MockRequiresPadding func() bool
|
||||
|
||||
@@ -15,8 +19,10 @@ type DNSTransport struct {
|
||||
MockCloseIdleConnections func()
|
||||
}
|
||||
|
||||
var _ model.DNSTransport = &DNSTransport{}
|
||||
|
||||
// RoundTrip calls MockRoundTrip.
|
||||
func (txp *DNSTransport) RoundTrip(ctx context.Context, query []byte) ([]byte, error) {
|
||||
func (txp *DNSTransport) RoundTrip(ctx context.Context, query model.DNSQuery) (model.DNSResponse, error) {
|
||||
return txp.MockRoundTrip(ctx, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/atomicx"
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
)
|
||||
|
||||
func TestDNSTransport(t *testing.T) {
|
||||
t.Run("RoundTrip", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
txp := &DNSTransport{
|
||||
MockRoundTrip: func(ctx context.Context, query []byte) ([]byte, error) {
|
||||
MockRoundTrip: func(ctx context.Context, query model.DNSQuery) (model.DNSResponse, error) {
|
||||
return nil, expected
|
||||
},
|
||||
}
|
||||
resp, err := txp.RoundTrip(context.Background(), make([]byte, 16))
|
||||
resp, err := txp.RoundTrip(context.Background(), &DNSQuery{})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("not the error we expected", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user