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:
parent
7a0a156aec
commit
01a513a496
35 changed files with 1694 additions and 1039 deletions
|
|
@ -1,5 +1,9 @@
|
|||
package model
|
||||
|
||||
//
|
||||
// Network extensions
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
|
|
@ -9,74 +13,81 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
//
|
||||
// Network extensions
|
||||
//
|
||||
// DNSResponse is a parsed DNS response ready for further processing.
|
||||
type DNSResponse interface {
|
||||
// Query is the query associated with this response.
|
||||
Query() DNSQuery
|
||||
|
||||
// The DNSDecoder decodes DNS replies.
|
||||
// Bytes returns the bytes from which we parsed the query.
|
||||
Bytes() []byte
|
||||
|
||||
// Rcode returns the response's Rcode.
|
||||
Rcode() int
|
||||
|
||||
// DecodeHTTPS returns information gathered from all the HTTPS
|
||||
// records found inside of this response.
|
||||
DecodeHTTPS() (*HTTPSSvc, error)
|
||||
|
||||
// DecodeLookupHost returns the addresses in the response matching
|
||||
// the original query type (one of A and AAAA).
|
||||
DecodeLookupHost() ([]string, error)
|
||||
|
||||
// DecodeNS returns all the NS entries in this response.
|
||||
DecodeNS() ([]*net.NS, error)
|
||||
}
|
||||
|
||||
// The DNSDecoder decodes DNS responses.
|
||||
type DNSDecoder interface {
|
||||
// DecodeLookupHost decodes an A or AAAA reply.
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// - qtype is the query type (e.g., dns.TypeAAAA)
|
||||
//
|
||||
// - data contains the reply bytes read from a DNSTransport
|
||||
//
|
||||
// - queryID is the original query ID
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// - on success, a list of IP addrs inside the reply and a nil error
|
||||
//
|
||||
// - on failure, a nil list and an error.
|
||||
//
|
||||
// Note that this function will return an error if there is no
|
||||
// IP address inside of the reply.
|
||||
DecodeLookupHost(qtype uint16, data []byte, queryID uint16) ([]string, error)
|
||||
|
||||
// DecodeHTTPS is like DecodeLookupHost but decodes an HTTPS reply.
|
||||
//
|
||||
// The argument is the reply as read by the DNSTransport.
|
||||
//
|
||||
// On success, this function returns an HTTPSSvc structure and
|
||||
// a nil error. On failure, the HTTPSSvc pointer is nil and
|
||||
// the error points to the error that occurred.
|
||||
//
|
||||
// This function will return an error if the HTTPS reply does not
|
||||
// contain at least a valid ALPN entry. It will not return
|
||||
// an error, though, when there are no IPv4/IPv6 hints in the reply.
|
||||
DecodeHTTPS(data []byte, queryID uint16) (*HTTPSSvc, error)
|
||||
|
||||
// DecodeNS is like DecodeHTTPS but for NS queries.
|
||||
DecodeNS(data []byte, queryID uint16) ([]*net.NS, error)
|
||||
|
||||
// DecodeReply decodes a DNS reply message.
|
||||
// DecodeResponse decodes a DNS response message.
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// - data is the raw reply
|
||||
//
|
||||
// This function fails if we cannot parse data as a DNS
|
||||
// message or the message is not a reply.
|
||||
// message or the message is not a response.
|
||||
//
|
||||
// If you use this function, remember that:
|
||||
// Regarding the returned response, remember that the Rcode
|
||||
// MAY still be nonzero (this method does not treat a nonzero
|
||||
// Rcode as an error when parsing the response).
|
||||
DecodeResponse(data []byte, query DNSQuery) (DNSResponse, error)
|
||||
}
|
||||
|
||||
// DNSQuery is an encoded DNS query ready to be sent using a DNSTransport.
|
||||
type DNSQuery interface {
|
||||
// Domain is the domain we're querying for.
|
||||
Domain() string
|
||||
|
||||
// Type is the query type.
|
||||
Type() uint16
|
||||
|
||||
// Bytes serializes the query to bytes. This function may fail if we're not
|
||||
// able to correctly encode the domain into a query message.
|
||||
//
|
||||
// 1. the Rcode MAY be nonzero;
|
||||
//
|
||||
// 2. the replyID MAY NOT match the original query ID.
|
||||
//
|
||||
// That is, this is a very basic parsing method.
|
||||
DecodeReply(data []byte) (*dns.Msg, error)
|
||||
// The value returned by this function WILL be memoized after the first call,
|
||||
// so you SHOULD create a new DNSQuery if you need to retry a query.
|
||||
Bytes() ([]byte, error)
|
||||
|
||||
// ID returns the query ID.
|
||||
ID() uint16
|
||||
}
|
||||
|
||||
// The DNSEncoder encodes DNS queries to bytes
|
||||
type DNSEncoder interface {
|
||||
// Encode transforms its arguments into a serialized DNS query.
|
||||
//
|
||||
// Every time you call Encode, you get a new DNSQuery value
|
||||
// using a query ID selected at random.
|
||||
//
|
||||
// Serialization to bytes is lazy to acommodate DNS transports that
|
||||
// do not need to serialize and send bytes, e.g., getaddrinfo.
|
||||
//
|
||||
// You serialize to bytes using DNSQuery.Bytes. This operation MAY fail
|
||||
// if the domain name cannot be packed into a DNS message (e.g., it is
|
||||
// too long to fit into the message).
|
||||
//
|
||||
// Arguments:
|
||||
//
|
||||
// - domain is the domain for the query (e.g., x.org);
|
||||
|
|
@ -85,16 +96,15 @@ type DNSEncoder interface {
|
|||
//
|
||||
// - padding is whether to add padding to the query.
|
||||
//
|
||||
// On success, this function returns a valid byte array, the queryID, and
|
||||
// a nil error. On failure, we have a non-nil error, a nil arrary and a zero
|
||||
// query ID.
|
||||
Encode(domain string, qtype uint16, padding bool) ([]byte, uint16, error)
|
||||
// This function will transform the domain into an FQDN is it's not
|
||||
// already expressed in the FQDN format.
|
||||
Encode(domain string, qtype uint16, padding bool) DNSQuery
|
||||
}
|
||||
|
||||
// DNSTransport represents an abstract DNS transport.
|
||||
type DNSTransport interface {
|
||||
// RoundTrip sends a DNS query and receives the reply.
|
||||
RoundTrip(ctx context.Context, query []byte) (reply []byte, err error)
|
||||
RoundTrip(ctx context.Context, query DNSQuery) (DNSResponse, error)
|
||||
|
||||
// RequiresPadding returns whether this transport needs padding.
|
||||
RequiresPadding() bool
|
||||
|
|
|
|||
Loading…
Reference in a new issue