ooni-probe-cli/internal/netxlite/dnsdecoder.go
Simone Basso 7ee9f096d1
fix(nextlite): wrap DNSDecoder errors (#962)
The simplest fix is to wrap such errors in dnsdecoder.go.

Fixes https://github.com/ooni/probe/issues/2317.
2022-09-14 13:47:20 +02:00

190 lines
4.8 KiB
Go

package netxlite
//
// Decode byte arrays to DNS messages
//
import (
"errors"
"net"
"github.com/miekg/dns"
"github.com/ooni/probe-cli/v3/internal/model"
)
// DNSDecoderMiekg uses github.com/miekg/dns to implement the Decoder.
type DNSDecoderMiekg struct{}
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.
ErrDNSIsQuery = errors.New("ooresolver: expected response but received query")
)
// dnsDecoderWrapError ensures we wrap the returned errors
func dnsDecoderWrapError(err error) error {
return MaybeNewErrWrapper(ClassifyResolverError, ResolveOperation, err)
}
// 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, dnsDecoderWrapError(err)
}
if !reply.Response {
return nil, dnsDecoderWrapError(ErrDNSIsQuery)
}
if reply.Id != query.ID() {
return nil, dnsDecoderWrapError(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 r.msg.Rcode {
case dns.RcodeSuccess:
return nil
case dns.RcodeNameError:
return dnsDecoderWrapError(ErrOODNSNoSuchHost)
case dns.RcodeRefused:
return dnsDecoderWrapError(ErrOODNSRefused)
case dns.RcodeServerFailure:
return dnsDecoderWrapError(ErrOODNSServfail)
default:
return dnsDecoderWrapError(ErrOODNSMisbehaving)
}
}
// DecodeHTTPS implements model.DNSResponse.DecodeHTTPS.
func (r *dnsResponse) DecodeHTTPS() (*model.HTTPSSvc, error) {
if err := r.rcodeToError(); err != nil {
return nil, err // error already wrapped
}
out := &model.HTTPSSvc{
ALPN: []string{}, // ensure it's not nil
IPv4: []string{}, // ensure it's not nil
IPv6: []string{}, // ensure it's not nil
}
for _, answer := range r.msg.Answer {
switch avalue := answer.(type) {
case *dns.HTTPS:
for _, v := range avalue.Value {
switch extv := v.(type) {
case *dns.SVCBAlpn:
out.ALPN = extv.Alpn
case *dns.SVCBIPv4Hint:
for _, ip := range extv.Hint {
out.IPv4 = append(out.IPv4, ip.String())
}
case *dns.SVCBIPv6Hint:
for _, ip := range extv.Hint {
out.IPv6 = append(out.IPv6, ip.String())
}
}
}
}
}
if len(out.IPv4) <= 0 && len(out.IPv6) <= 0 {
return nil, dnsDecoderWrapError(ErrOODNSNoAnswer)
}
return out, nil
}
// DecodeLookupHost implements model.DNSResponse.DecodeLookupHost.
func (r *dnsResponse) DecodeLookupHost() ([]string, error) {
if err := r.rcodeToError(); err != nil {
return nil, err // error already wrapped
}
var addrs []string
for _, answer := range r.msg.Answer {
switch r.Query().Type() {
case dns.TypeA:
if rra, ok := answer.(*dns.A); ok {
ip := rra.A
addrs = append(addrs, ip.String())
}
case dns.TypeAAAA:
if rra, ok := answer.(*dns.AAAA); ok {
ip := rra.AAAA
addrs = append(addrs, ip.String())
}
}
}
if len(addrs) <= 0 {
return nil, dnsDecoderWrapError(ErrOODNSNoAnswer)
}
return addrs, nil
}
// DecodeNS implements model.DNSResponse.DecodeNS.
func (r *dnsResponse) DecodeNS() ([]*net.NS, error) {
if err := r.rcodeToError(); err != nil {
return nil, err // error already wrapped
}
out := []*net.NS{}
for _, answer := range r.msg.Answer {
switch avalue := answer.(type) {
case *dns.NS:
out = append(out, &net.NS{Host: avalue.Ns})
}
}
if len(out) < 1 {
return nil, dnsDecoderWrapError(ErrOODNSNoAnswer)
}
return out, nil
}
// DecodeCNAME implements model.DNSResponse.DecodeCNAME.
func (r *dnsResponse) DecodeCNAME() (string, error) {
if err := r.rcodeToError(); err != nil {
return "", err // error already wrapped
}
for _, answer := range r.msg.Answer {
switch avalue := answer.(type) {
case *dns.CNAME:
return avalue.Target, nil
}
}
return "", dnsDecoderWrapError(ErrOODNSNoAnswer)
}
var _ model.DNSDecoder = &DNSDecoderMiekg{}
var _ model.DNSResponse = &dnsResponse{}