CI / test (pull_request) Waiting to run
- gofmt main.go to fix tab indentation in main() body - stats.go ComputeSummary: return nil when Answers and ByType are empty - dns/types.go: remove duplicate ParseQueryType (dead code) - formatter.go: log hook errors to stderr when Debug > 0; add Debug to Config - text.go referralID: remove redundant depth==0 branch - text.go writeServers: remove version lookup placeholder string Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai>
219 lines
4.1 KiB
Go
219 lines
4.1 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
|
|
RespError
|
|
)
|
|
|
|
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 RespError:
|
|
return "error"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type Response struct {
|
|
Referral *Referral
|
|
Server net.IP
|
|
Cache *InfoCache
|
|
Decoded *dns.DecodedResponse
|
|
Type ResponseType
|
|
}
|
|
|
|
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
|
|
return r
|
|
}
|
|
|
|
r.Decoded = dns.DecodeResponse(msg)
|
|
if r.Decoded == nil {
|
|
r.Type = RespError
|
|
return r
|
|
}
|
|
|
|
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.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 {
|
|
if _, ok := rr.(*miekgdns.CNAME); ok {
|
|
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, RespError:
|
|
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)
|
|
}
|