package output import ( "fmt" "sort" "strings" "github.com/hits/ExploreDNS/internal/dns" "github.com/hits/ExploreDNS/internal/traverse" miekgdns "github.com/miekg/dns" ) type summaryEntry struct { Type string Prob float64 } type answerEntry struct { RData string Prob float64 RRs []string } type SummaryStats struct { ByType map[string]float64 Answers []answerEntry } func ComputeSummary(results []traverse.TraversalResult) *SummaryStats { stats := &SummaryStats{ ByType: make(map[string]float64), } for _, result := range results { if result.Response == nil || !result.Response.IsTerminal() { continue } if result.Referral == nil { continue } prob := result.Referral.Prob respType := result.Response.Type.String() switch result.Response.Type { case traverse.RespAnswer: key, rrs := answerKey(result.Response) if key == "" { stats.ByType[respType] += prob continue } found := false for i := range stats.Answers { if stats.Answers[i].RData == key { stats.Answers[i].Prob += prob found = true break } } if !found { stats.Answers = append(stats.Answers, answerEntry{ RData: key, Prob: prob, RRs: rrs, }) } default: stats.ByType[respType] += prob } } sort.Slice(stats.Answers, func(i, j int) bool { return stats.Answers[i].RData < stats.Answers[j].RData }) return stats } func answerKey(resp *traverse.Response) (string, []string) { if resp == nil || resp.Decoded == nil { return "", nil } var rdatas []string var formatted []string for _, rr := range resp.Decoded.Answers { if _, ok := rr.(*miekgdns.CNAME); ok { continue } rdata := rrDataString(rr) if rdata == "" { continue } rdatas = append(rdatas, rdata) formatted = append(formatted, dns.FormatRecord(rr)) } if len(rdatas) == 0 { return "", nil } sort.Strings(rdatas) return strings.Join(rdatas, " / "), formatted } func rrDataString(rr miekgdns.RR) string { switch v := rr.(type) { case *miekgdns.A: return v.A.String() case *miekgdns.AAAA: return v.AAAA.String() case *miekgdns.CNAME: return v.Target case *miekgdns.NS: return v.Ns case *miekgdns.MX: return fmt.Sprintf("%d %s", v.Preference, v.Mx) case *miekgdns.TXT: return strings.Join(v.Txt, " ") default: return rr.String() } } func formatProbability(prob float64) string { text := fmt.Sprintf("%.1f%%", prob*100) text = strings.Replace(text, ".0%", "%", 1) return fmt.Sprintf("%5s", text) } func summaryTypeLabel(respType string) string { switch respType { case "nodata": return "found no such record" case "nxdomain": return "name does not exist" case "servfail": return "resulted in SERVFAIL" case "error": return "resulted in an error" case "referral": return "resulted in a referral" default: return respType } } func trimDomain(name string) string { return strings.TrimSuffix(name, ".") } func collectServers(results []traverse.TraversalResult) map[string][]string { servers := make(map[string][]string) for _, result := range results { if result.Response == nil || result.Response.Server == nil { continue } name := serverName(result) ip := result.Response.Server.String() if containsString(servers[name], ip) { continue } servers[name] = append(servers[name], ip) } return servers } func serverName(result traverse.TraversalResult) string { if result.Referral != nil && result.Referral.Bailiwick != "" && result.Referral.Bailiwick != "." { return trimDomain(result.Referral.Bailiwick) } if result.Referral != nil && result.Referral.NSName != "" { return result.Referral.NSName } if result.Response != nil && result.Response.Server != nil { return result.Response.Server.String() } return "unknown" } func containsString(items []string, target string) bool { for _, item := range items { if item == target { return true } } return false }