package main import ( "log" "net" "sync" "time" nft "github.com/google/nftables" "github.com/miekg/dns" ) const ( dnsCnameWalkLimit = 16 nftTimeoutSkew = time.Second * 1 ) type DnsAnswer struct { Qname string Qtype uint16 Ttl uint32 AddrLen uint32 Addr net.IP } func dnsRemap(qname string, qtype uint16, resp *dns.Msg) ([]PowerDnsAnswer, error) { r_main := make([]dns.RR, 0) r_extra := make([]dns.RR, 0) answ := make([]dns.RR, 0, len(resp.Answer)+len(resp.Extra)) answ = append(answ, resp.Answer...) answ = append(answ, resp.Extra...) last_qname := qname needle := qname name_seen := make(map[string]bool) name_seen[needle] = true for range dnsCnameWalkLimit { i_cnames := make([]int, 0, len(answ)) i_addrs := make([]int, 0, len(answ)) for i := range answ { switch answ[i].Header().Rrtype { case dns.TypeCNAME: i_cnames = append(i_cnames, i) case dns.TypeA, dns.TypeAAAA: i_addrs = append(i_addrs, i) } } if len(i_cnames) > 0 { seen := false cname_dive := true for { if !cname_dive { break } cname_dive = false for _, i := range i_cnames { if answ[i].Header().Name != needle { continue } cname := answ[i].(*dns.CNAME) needle = cname.Target _, seen = name_seen[needle] if seen { // CNAME loop? log.Printf("CNAME loop: %v -> %v", qname, needle) return []PowerDnsAnswer{}, nil } name_seen[needle] = true cname_dive = true break } } } found := false for _, i := range i_addrs { if answ[i].Header().Name != needle { continue } found = true r_main = append(r_main, answ[i]) } if found { for i := range answ { if !dnsIsAllowedExtraQtype(answ[i].Header().Rrtype) { continue } // if answ[i].Header().Name != needle { // continue // } r_extra = append(r_extra, answ[i]) } break } // trim answ = answ[:0] if (needle == last_qname) && (qtype == dns.TypeANY) { answ = dnsRemapAnyFallback(needle) continue } last_qname = needle resp, err := dnsCustomResolve(needle, qtype) if err != nil { break } if resp == nil { break } if len(resp.Answer) != 0 { for i := range resp.Answer { answ = append(answ, dns.Copy(resp.Answer[i])) } for i := range resp.Extra { answ = append(answ, dns.Copy(resp.Extra[i])) } resp = nil continue } if qtype != dns.TypeANY { break } answ = dnsRemapAnyFallback(needle) } if len(r_main) == 0 { if qname == needle { log.Printf("not resolved fully %v/%v", qname, dns.TypeToString[qtype]) } else { log.Printf("not resolved fully %v/%v (stuck at %v)", qname, dns.TypeToString[qtype], needle) } return []PowerDnsAnswer{}, nil } interim := make([]DnsAnswer, 0, len(r_main)) for i := range r_main { t := r_main[i].Header().Rrtype r := DnsAnswer{ Qname: qname, Qtype: t, Ttl: r_main[i].Header().Ttl, } switch t { case dns.TypeA: r.AddrLen = net.IPv4len r.Addr = make([]byte, net.IPv4len) copy(r.Addr, r_main[i].(*dns.A).A) case dns.TypeAAAA: r.AddrLen = net.IPv6len r.Addr = make([]byte, net.IPv6len) copy(r.Addr, r_main[i].(*dns.AAAA).AAAA) } interim = append(interim, r) } // unify/adjust TTL var ttl uint32 if cfgTtlFuzzy { ttl = dnsFuzzClipTtl() } else { ttl = cfgTtlMax for i := range interim { if ttl > interim[i].Ttl { ttl = interim[i].Ttl } } ttl = dnsClipTtl(ttl) } unix_start := time.Unix(0, 0) nft_ipv4 := make([]nft.SetElement, 0, len(interim)) nft_ipv6 := make([]nft.SetElement, 0, len(interim)) // remap addresses in answers and prepare nftables maps for i := range interim { addrlen := interim[i].AddrLen var srcAddr net.IP = make([]byte, addrlen) copy(srcAddr, interim[i].Addr) var cidr *net.IPNet = nil switch addrlen { case net.IPv4len: if cfgCidrV4 != nil { cidr = cfgCidrV4 } case net.IPv6len: if cfgCidrV6 != nil { cidr = cfgCidrV6 } } if cidr == nil { // no need to remap or add to nftables continue } dstAddr, nft_ttl := addrMapGet(srcAddr, cidr, ttl) // HACK: replace addr copy(interim[i].Addr, dstAddr) if !cfgWithNft { continue } elem := nft.SetElement{ Key: []byte(dstAddr), Val: []byte(srcAddr), // Timeout: time.Duration(nft_ttl), Timeout: time.Unix(int64(nft_ttl), 0).Add(nftTimeoutSkew).Sub(unix_start).Round(time.Millisecond), } switch addrlen { case net.IPv4len: nft_ipv4 = append(nft_ipv4, elem) case net.IPv6len: nft_ipv6 = append(nft_ipv6, elem) } } // perform nftables assignment if (len(nft_ipv4) > 0) && (cfgNftMapV4 != "") { nftDoWithMap(cfgNftTable, cfgNftTableFamily, cfgNftMapV4, func(c *nft.Conn, t *nft.Table, m *nft.Set) error { _ = c.SetDeleteElements(m, nft_ipv4) return nil }) nftDoWithMap(cfgNftTable, cfgNftTableFamily, cfgNftMapV4, func(c *nft.Conn, t *nft.Table, m *nft.Set) error { return c.SetAddElements(m, nft_ipv4) }) } if (len(nft_ipv6) > 0) && (cfgNftMapV6 != "") { nftDoWithMap(cfgNftTable, cfgNftTableFamily, cfgNftMapV6, func(c *nft.Conn, t *nft.Table, m *nft.Set) error { _ = c.SetDeleteElements(m, nft_ipv6) return nil }) nftDoWithMap(cfgNftTable, cfgNftTableFamily, cfgNftMapV6, func(c *nft.Conn, t *nft.Table, m *nft.Set) error { return c.SetAddElements(m, nft_ipv6) }) } result := make([]PowerDnsAnswer, 0) for i := range interim { r := PowerDnsAnswer{ Qname: interim[i].Qname, Qtype: dns.TypeToString[interim[i].Qtype], Ttl: ttl, Content: interim[i].Addr.String(), } result = append(result, r) } // extra records (if any) for i := range r_extra { r, err := dnsRrToPowerDnsAnswer(r_extra[i]) if err != nil { log.Printf("%v", err) continue } result = append(result, r) } // adjust results for i := range result { if result[i].Qname == needle { result[i].Qname = qname } } return result, nil } func dnsRemapAnyFallback(qname string) []dns.RR { var wg sync.WaitGroup var r_a, r_aaaa []dns.RR wg.Add(1) go func() { defer wg.Done() resp, err := dnsCustomResolve(qname, dns.TypeA) if err != nil { return } if resp == nil { return } if len(resp.Answer) == 0 { return } r_a = make([]dns.RR, 0, len(resp.Answer)) for i := range resp.Answer { r_a = append(r_a, dns.Copy(resp.Answer[i])) } for i := range resp.Extra { r_a = append(r_a, dns.Copy(resp.Extra[i])) } }() wg.Add(1) go func() { defer wg.Done() resp, err := dnsCustomResolve(qname, dns.TypeAAAA) if err != nil { return } if resp == nil { return } if len(resp.Answer) == 0 { return } r_aaaa = make([]dns.RR, 0, len(resp.Answer)) for i := range resp.Answer { r_aaaa = append(r_aaaa, dns.Copy(resp.Answer[i])) } for i := range resp.Extra { r_aaaa = append(r_aaaa, dns.Copy(resp.Extra[i])) } }() wg.Wait() answ := make([]dns.RR, 0, len(r_a)+len(r_aaaa)) // TODO: very naive (no unique record checks) if r_a != nil { answ = append(answ, r_a...) } if r_aaaa != nil { answ = append(answ, r_aaaa...) } return answ }