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:
Simone Basso 2022-05-25 17:03:58 +02:00 committed by GitHub
commit 01a513a496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1694 additions and 1039 deletions

View file

@ -15,14 +15,16 @@ import (
// DNSDecoderMiekg uses github.com/miekg/dns to implement the Decoder.
type DNSDecoderMiekg struct{}
// ErrDNSReplyWithWrongQueryID indicates we have got a DNS reply with the wrong queryID.
var ErrDNSReplyWithWrongQueryID = errors.New(FailureDNSReplyWithWrongQueryID)
var (
// ErrDNSReplyWithWrongQueryID indicates we have got a DNS reply with the wrong queryID.
ErrDNSReplyWithWrongQueryID = errors.New(FailureDNSReplyWithWrongQueryID)
// ErrDNSIsQuery indicates that we were passed a DNS query.
var ErrDNSIsQuery = errors.New("ooresolver: expected response but received query")
// ErrDNSIsQuery indicates that we were passed a DNS query.
ErrDNSIsQuery = errors.New("ooresolver: expected response but received query")
)
// DecodeReply implements model.DNSDecoder.DecodeReply
func (d *DNSDecoderMiekg) DecodeReply(data []byte) (*dns.Msg, error) {
// DecodeResponse implements model.DNSDecoder.DecodeResponse.
func (d *DNSDecoderMiekg) DecodeResponse(data []byte, query model.DNSQuery) (model.DNSResponse, error) {
reply := &dns.Msg{}
if err := reply.Unpack(data); err != nil {
return nil, err
@ -30,46 +32,64 @@ func (d *DNSDecoderMiekg) DecodeReply(data []byte) (*dns.Msg, error) {
if !reply.Response {
return nil, ErrDNSIsQuery
}
return reply, nil
}
// decodeSuccessfulReply decodes the bytes in data as a successful reply for the
// given queryID. This function returns an error if:
//
// 1. we cannot decode data
//
// 2. the decoded message is not a reply
//
// 3. the query ID does not match
//
// 4. the Rcode is not zero.
func (d *DNSDecoderMiekg) decodeSuccessfulReply(data []byte, queryID uint16) (*dns.Msg, error) {
reply, err := d.DecodeReply(data)
if err != nil {
return nil, err
}
if reply.Id != queryID {
if reply.Id != query.ID() {
return nil, ErrDNSReplyWithWrongQueryID
}
resp := &dnsResponse{
bytes: data,
msg: reply,
query: query,
}
return resp, nil
}
// dnsResponse implements model.DNSResponse.
type dnsResponse struct {
// bytes contains the response bytes.
bytes []byte
// msg contains the message.
msg *dns.Msg
// query is the original query.
query model.DNSQuery
}
// Query implements model.DNSResponse.Query.
func (r *dnsResponse) Query() model.DNSQuery {
return r.query
}
// Bytes implements model.DNSResponse.Bytes.
func (r *dnsResponse) Bytes() []byte {
return r.bytes
}
// Rcode implements model.DNSResponse.Rcode.
func (r *dnsResponse) Rcode() int {
return r.msg.Rcode
}
func (r *dnsResponse) rcodeToError() error {
// TODO(bassosimone): map more errors to net.DNSError names
// TODO(bassosimone): add support for lame referral.
switch reply.Rcode {
switch r.msg.Rcode {
case dns.RcodeSuccess:
return reply, nil
return nil
case dns.RcodeNameError:
return nil, ErrOODNSNoSuchHost
return ErrOODNSNoSuchHost
case dns.RcodeRefused:
return nil, ErrOODNSRefused
return ErrOODNSRefused
case dns.RcodeServerFailure:
return nil, ErrOODNSServfail
return ErrOODNSServfail
default:
return nil, ErrOODNSMisbehaving
return ErrOODNSMisbehaving
}
}
func (d *DNSDecoderMiekg) DecodeHTTPS(data []byte, queryID uint16) (*model.HTTPSSvc, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
// DecodeHTTPS implements model.DNSResponse.DecodeHTTPS.
func (r *dnsResponse) DecodeHTTPS() (*model.HTTPSSvc, error) {
if err := r.rcodeToError(); err != nil {
return nil, err
}
out := &model.HTTPSSvc{
@ -77,7 +97,7 @@ func (d *DNSDecoderMiekg) DecodeHTTPS(data []byte, queryID uint16) (*model.HTTPS
IPv4: []string{}, // ensure it's not nil
IPv6: []string{}, // ensure it's not nil
}
for _, answer := range reply.Answer {
for _, answer := range r.msg.Answer {
switch avalue := answer.(type) {
case *dns.HTTPS:
for _, v := range avalue.Value {
@ -102,14 +122,14 @@ func (d *DNSDecoderMiekg) DecodeHTTPS(data []byte, queryID uint16) (*model.HTTPS
return out, nil
}
func (d *DNSDecoderMiekg) DecodeLookupHost(qtype uint16, data []byte, queryID uint16) ([]string, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
// DecodeLookupHost implements model.DNSResponse.DecodeLookupHost.
func (r *dnsResponse) DecodeLookupHost() ([]string, error) {
if err := r.rcodeToError(); err != nil {
return nil, err
}
var addrs []string
for _, answer := range reply.Answer {
switch qtype {
for _, answer := range r.msg.Answer {
switch r.Query().Type() {
case dns.TypeA:
if rra, ok := answer.(*dns.A); ok {
ip := rra.A
@ -128,13 +148,13 @@ func (d *DNSDecoderMiekg) DecodeLookupHost(qtype uint16, data []byte, queryID ui
return addrs, nil
}
func (d *DNSDecoderMiekg) DecodeNS(data []byte, queryID uint16) ([]*net.NS, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
// DecodeNS implements model.DNSResponse.DecodeNS.
func (r *dnsResponse) DecodeNS() ([]*net.NS, error) {
if err := r.rcodeToError(); err != nil {
return nil, err
}
out := []*net.NS{}
for _, answer := range reply.Answer {
for _, answer := range r.msg.Answer {
switch avalue := answer.(type) {
case *dns.NS:
out = append(out, &net.NS{Host: avalue.Ns})
@ -147,3 +167,4 @@ func (d *DNSDecoderMiekg) DecodeNS(data []byte, queryID uint16) ([]*net.NS, erro
}
var _ model.DNSDecoder = &DNSDecoderMiekg{}
var _ model.DNSResponse = &dnsResponse{}