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{}