ooni-probe-cli/internal/netxlite/dnsdecoder.go
Simone Basso 7c45f7b88c
fix(netxlite): ensure we only accept DNS responses (#735)
Previously, the DNS decoder did not check whether it was parsing
a DNS query or a DNS response, which was wrong.

As a side note, it seems I am using "reply" in the codebase instead
of "response". The latter seems correct DNS terminology.

This diff has been extracted from 9249d14f80

See https://github.com/ooni/probe/issues/2096.
2022-05-16 11:17:30 +02:00

150 lines
3.7 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{}
// ErrDNSReplyWithWrongQueryID indicates we have got a DNS reply with the wrong queryID.
var ErrDNSReplyWithWrongQueryID = errors.New(FailureDNSReplyWithWrongQueryID)
// ErrDNSIsQuery indicates that we were passed a DNS query.
var ErrDNSIsQuery = errors.New("ooresolver: expected response but received query")
// DecodeReply implements model.DNSDecoder.DecodeReply
func (d *DNSDecoderMiekg) DecodeReply(data []byte) (*dns.Msg, error) {
reply := &dns.Msg{}
if err := reply.Unpack(data); err != nil {
return nil, err
}
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 {
return nil, ErrDNSReplyWithWrongQueryID
}
// TODO(bassosimone): map more errors to net.DNSError names
// TODO(bassosimone): add support for lame referral.
switch reply.Rcode {
case dns.RcodeSuccess:
return reply, nil
case dns.RcodeNameError:
return nil, ErrOODNSNoSuchHost
case dns.RcodeRefused:
return nil, ErrOODNSRefused
case dns.RcodeServerFailure:
return nil, ErrOODNSServfail
default:
return nil, ErrOODNSMisbehaving
}
}
func (d *DNSDecoderMiekg) DecodeHTTPS(data []byte, queryID uint16) (*model.HTTPSSvc, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
return nil, err
}
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 reply.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, ErrOODNSNoAnswer
}
return out, nil
}
func (d *DNSDecoderMiekg) DecodeLookupHost(qtype uint16, data []byte, queryID uint16) ([]string, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
return nil, err
}
var addrs []string
for _, answer := range reply.Answer {
switch qtype {
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, ErrOODNSNoAnswer
}
return addrs, nil
}
func (d *DNSDecoderMiekg) DecodeNS(data []byte, queryID uint16) ([]*net.NS, error) {
reply, err := d.decodeSuccessfulReply(data, queryID)
if err != nil {
return nil, err
}
out := []*net.NS{}
for _, answer := range reply.Answer {
switch avalue := answer.(type) {
case *dns.NS:
out = append(out, &net.NS{Host: avalue.Ns})
}
}
if len(out) < 1 {
return nil, ErrOODNSNoAnswer
}
return out, nil
}
var _ model.DNSDecoder = &DNSDecoderMiekg{}