Files
ExploreDNS/internal/traverse/response.go

257 lines
5.6 KiB
Go

package traverse
import (
"net"
"github.com/hits/ExploreDNS/internal/dns"
miekgdns "github.com/miekg/dns"
)
type ResponseType int
const (
RespReferral ResponseType = iota
RespAnswer
RespCNAMEFollow
RespNODATA
RespNXDOMAIN
RespSERVFAIL
RespREFUSED
RespNOTIMPL
RespCNAMELoop
RespError
// RespNSResolutionFailed indicates that the traversal could not resolve the
// IP address of an in-bailiwick nameserver. The domain may still be
// reachable in practice (e.g. via glue records held by the registry), but
// the iterative traversal could not complete that path.
RespNSResolutionFailed
)
func (rt ResponseType) String() string {
switch rt {
case RespReferral:
return "referral"
case RespAnswer:
return "answer"
case RespCNAMEFollow:
return "cname_follow"
case RespNODATA:
return "nodata"
case RespNXDOMAIN:
return "nxdomain"
case RespSERVFAIL:
return "servfail"
case RespREFUSED:
return "refused"
case RespNOTIMPL:
return "notimp"
case RespCNAMELoop:
return "cname_loop"
case RespError:
return "error"
case RespNSResolutionFailed:
return "ns_error"
default:
return "unknown"
}
}
type Response struct {
Referral *Referral
Server net.IP
Cache *InfoCache
Decoded *dns.DecodedResponse
Type ResponseType
ErrorMessage string
}
func NewResponse(ref *Referral, server net.IP, cache *InfoCache) *Response {
return &Response{
Referral: ref,
Server: server,
Cache: cache,
}
}
func (r *Response) Process(msg *miekgdns.Msg) *Response {
if msg == nil {
r.Type = RespError
r.ErrorMessage = "nil DNS response"
return r
}
r.Decoded = dns.DecodeResponse(msg)
if r.Decoded == nil {
r.Type = RespError
r.ErrorMessage = "failed to decode DNS response"
return r
}
// Synthesize CNAME from DNAME when the server didn't include a synthesized CNAME record.
if len(r.Decoded.CNAMEChain) == 0 && r.Referral != nil && len(r.Decoded.DNAMEMappings) > 0 {
for _, dm := range r.Decoded.DNAMEMappings {
synthesized := dns.SynthesizeCNAMEFromDNAME(r.Referral.Name, dm.Owner, dm.Target)
if synthesized != "" {
r.Decoded.CNAMEChain = append(r.Decoded.CNAMEChain, synthesized)
break
}
}
}
r.Type = r.classify()
return r
}
func (r *Response) classify() ResponseType {
switch r.Decoded.Classification {
case dns.ResponseNXDOMAIN:
return RespNXDOMAIN
case dns.ResponseSERVFAIL:
return RespSERVFAIL
case dns.ResponseREFUSED:
return RespREFUSED
case dns.ResponseNOTIMPL:
return RespNOTIMPL
case dns.ResponseAnswer:
if len(r.Decoded.CNAMEChain) > 0 && !r.hasFinalAnswer() {
return RespCNAMEFollow
}
return RespAnswer
case dns.ResponseReferral:
return RespReferral
case dns.ResponseNODATA:
return RespNODATA
default:
return RespError
}
}
func (r *Response) hasFinalAnswer() bool {
for _, rr := range r.Decoded.Answers {
switch rr.(type) {
case *miekgdns.CNAME, *miekgdns.DNAME, *miekgdns.RRSIG:
// CNAME and DNAME are redirect records, not final answers.
// RRSIG is a DNSSEC signature record — it covers the CNAME/DNAME
// but is not itself the answer to the original question type.
continue
}
return true
}
return false
}
func (r *Response) ChildReferrals() []*Referral {
if r.Type != RespReferral {
return nil
}
if r.Referral == nil {
return nil
}
var nameservers []string
for _, rr := range r.Decoded.Authority {
if ns, ok := rr.(*miekgdns.NS); ok {
if r.Referral.InBailiwick(ns.Ns) {
nameservers = append(nameservers, ns.Ns)
}
}
}
if len(nameservers) == 0 {
for _, rr := range r.Decoded.Authority {
if ns, ok := rr.(*miekgdns.NS); ok {
nameservers = append(nameservers, ns.Ns)
}
}
}
r.storeAuthority(nameservers)
prob := r.childProb(len(nameservers))
var children []*Referral
for _, ns := range nameservers {
child := NewReferral(
r.Referral.Name,
r.Referral.Qtype,
ns,
r.Referral.Depth+1,
prob,
r.Referral,
)
r.resolveGlue(child)
children = append(children, child)
}
return children
}
func (r *Response) CNAMEFollowReferral() *Referral {
if r.Type != RespCNAMEFollow || len(r.Decoded.CNAMEChain) == 0 {
return nil
}
target := r.Decoded.CNAMEChain[len(r.Decoded.CNAMEChain)-1]
follow := NewReferral(
target,
r.Referral.Qtype,
r.Referral.Bailiwick,
r.Referral.Depth+1,
r.Referral.Prob,
r.Referral,
)
if len(r.Referral.Addresses) > 0 {
follow.Addresses = make([]net.IP, len(r.Referral.Addresses))
copy(follow.Addresses, r.Referral.Addresses)
follow.State = StateResolved
}
return follow
}
func (r *Response) storeAuthority(nameservers []string) {
if r.Cache == nil {
return
}
zone := r.Referral.Name
r.Cache.StoreNS(zone, nameservers)
}
func (r *Response) resolveGlue(child *Referral) {
if r.Cache == nil {
return
}
nsName := child.Bailiwick
for _, rr := range r.Decoded.Additional {
switch v := rr.(type) {
case *miekgdns.A:
if normalize(v.Header().Name) == normalize(nsName) {
child.Addresses = append(child.Addresses, v.A)
}
case *miekgdns.AAAA:
if normalize(v.Header().Name) == normalize(nsName) {
child.Addresses = append(child.Addresses, v.AAAA)
}
}
}
if child.HasAddresses() {
child.State = StateResolved
}
r.Cache.StoreGlue(nsName, child.Addresses)
}
func (r *Response) IsTerminal() bool {
switch r.Type {
case RespAnswer, RespNODATA, RespNXDOMAIN, RespSERVFAIL, RespREFUSED, RespNOTIMPL, RespCNAMELoop, RespError, RespNSResolutionFailed:
return true
default:
return false
}
}
func (r *Response) childProb(n int) float64 {
if n <= 0 {
return 0
}
if r.Referral == nil {
return 1.0 / float64(n)
}
return r.Referral.Prob / float64(n)
}