package main import ( "fmt" "log" "math/rand" "net" "strings" "sync" "github.com/miekg/dns" ) var ( dnsStringToType sync.Map dnsExtraQtype = make(map[uint16]bool) dnsTtlRange uint32 ) func setupDns() { for _, v := range []uint16{ dns.TypePTR, dns.TypeMX, dns.TypeTXT, dns.TypeSRV, dns.TypeLOC, dns.TypeHINFO, dns.TypeSPF, dns.TypeSIG, dns.TypeKEY, dns.TypeKX, dns.TypeDS, dns.TypeRRSIG, dns.TypeNSEC, dns.TypeDNSKEY, dns.TypeNSEC3, dns.TypeNSEC3PARAM, dns.TypeTLSA, dns.TypeCDS, dns.TypeCDNSKEY, dns.TypeZONEMD, dns.TypeTKEY, dns.TypeTSIG, dns.TypeCAA, dns.TypeSVCB, dns.TypeHTTPS, dns.TypeURI, } { dnsExtraQtype[v] = true } // warmup for _, v := range []string{"SOA", "NS", "A", "AAAA", "ANY", "PTR", "CNAME"} { dnsQtypeStringToValue(v) } } func dnsIsAllowedExtraQtype(qtype uint16) bool { _, found := dnsExtraQtype[qtype] return found } func dnsQtypeStringToValue(qtype string) uint16 { x, found := dnsStringToType.Load(qtype) if found { return x.(uint16) } for k, v := range dns.TypeToString { if v == qtype { dnsStringToType.Store(qtype, k) return k } } log.Printf("qtype %#v is not known or found", qtype) return dns.TypeNone } func dnsCustomResolve(qname string, qtype uint16) (*dns.Msg, error) { qtype_s, known := dns.TypeToString[qtype] if !known { log.Printf("qtype is not known (%v) for %v", qtype, qname) return nil, fmt.Errorf("qtype is not known: %v", qtype) } c := new(dns.Client) c.Net = cfgResolverProto c.Dialer = &net.Dialer{ Timeout: cfgResolverTimeout, } req := new(dns.Msg) req.Id = dns.Id() req.RecursionDesired = true req.Question = make([]dns.Question, 1) req.Question[0] = dns.Question{Name: qname, Qtype: qtype, Qclass: dns.ClassINET} resp, rtt, err := c.Exchange(req, cfgResolverEndpoint) if err != nil { log.Printf("resolving %v/%v (rtt %v) with error: %v", qname, qtype_s, rtt, err) return nil, err } log.Printf("resolved %v/%v (rtt %v)", qname, qtype_s, rtt) return resp, nil } func dnsClipTtl(ttl uint32) uint32 { if ttl < cfgTtlMin { return cfgTtlMin } if ttl > cfgTtlMax { return cfgTtlMax } return ttl } func randUint32(v uint32) uint32 { if v < 2 { return rand.Uint32() } return rand.Uint32() % v } func dnsFuzzClipTtl() uint32 { return dnsClipTtl(cfgTtlMin + 1 + randUint32(dnsTtlRange*2) - dnsTtlRange) } type PowerDnsAnswer struct { Qname string `json:"qname"` Qtype string `json:"qtype"` Ttl uint32 `json:"ttl"` Content string `json:"content"` } func dnsRrToPowerDnsAnswer(rr dns.RR) (PowerDnsAnswer, error) { qname := rr.Header().Name rrtype := rr.Header().Rrtype qtype_s, ok := dns.TypeToString[rrtype] if !ok { return PowerDnsAnswer{}, fmt.Errorf("record %v: unknown rrtype: %v", qname, rrtype) } cont, ok := strings.CutPrefix(rr.String(), rr.Header().String()) if !ok { return PowerDnsAnswer{}, fmt.Errorf("record %v/%v: unable to produce content", qname, qtype_s) } return PowerDnsAnswer{ Qname: qname, Qtype: qtype_s, Ttl: dnsClipTtl(rr.Header().Ttl), Content: strings.TrimSpace(cont), }, nil } func dnsToPowerDnsAnswer(resp *dns.Msg) ([]PowerDnsAnswer, error) { result := make([]PowerDnsAnswer, 0, len(resp.Answer)+len(resp.Extra)) for i := range resp.Answer { r, err := dnsRrToPowerDnsAnswer(resp.Answer[i]) if err != nil { log.Printf("%v", err) continue } result = append(result, r) } for i := range resp.Extra { r, err := dnsRrToPowerDnsAnswer(resp.Extra[i]) if err != nil { log.Printf("%v", err) continue } result = append(result, r) } return result, nil }