7ee9f096d1
The simplest fix is to wrap such errors in dnsdecoder.go. Fixes https://github.com/ooni/probe/issues/2317.
190 lines
4.8 KiB
Go
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{}
|