From 1b6caba3073391da1bfd6a90ebdaaea124dc1c00 Mon Sep 17 00:00:00 2001 From: Ian Bashford Date: Mon, 13 Dec 2021 13:00:13 +0000 Subject: [PATCH] allow ptr queries for cloaked domains (#1958) * allow ptr queries for cloaked domains * multi ips per PTR returned + cleanup * some string tidy up * enable config file switch * add cloaked ptr test * enable cloak ptrs in test scenario * fix reverse ipv6 ptr lookup * added ipv6 cloaked ptr test --- .ci/ci-test.sh | 2 + .ci/cloaking-rules.txt | 2 + .ci/test2-dnscrypt-proxy.toml | 1 + dnscrypt-proxy/common.go | 5 +++ dnscrypt-proxy/config.go | 3 ++ dnscrypt-proxy/example-cloaking-rules.txt | 7 ++++ dnscrypt-proxy/example-dnscrypt-proxy.toml | 3 ++ dnscrypt-proxy/plugin_cloak.go | 48 ++++++++++++++++++++-- dnscrypt-proxy/proxy.go | 1 + 9 files changed, 69 insertions(+), 3 deletions(-) diff --git a/.ci/ci-test.sh b/.ci/ci-test.sh index 6c907733..63410f00 100755 --- a/.ci/ci-test.sh +++ b/.ci/ci-test.sh @@ -70,6 +70,8 @@ t || dig -p${DNS_PORT} +short cloaked.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1 t || dig -p${DNS_PORT} +short www.cloaked2.com @127.0.0.1 | grep -Eq '1.1.1.1|1.0.0.1' || fail t || dig -p${DNS_PORT} +short www.dnscrypt-test @127.0.0.1 | grep -Fq '192.168.100.100' || fail t || dig -p${DNS_PORT} a.www.dnscrypt-test @127.0.0.1 | grep -Fq 'NXDOMAIN' || fail +t || dig -p${DNS_PORT} +short ptr 101.100.168.192.in-addr.arpa. @127.0.0.1 | grep -Eq 'www.dnscrypt-test.com' || fail +t || dig -p${DNS_PORT} +short ptr 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.d.f.ip6.arpa. @127.0.0.1 | grep -Eq 'ipv6.dnscrypt-test.com' || fail section t || dig -p${DNS_PORT} telemetry.example @127.0.0.1 | grep -Fq 'locally blocked' || fail diff --git a/.ci/cloaking-rules.txt b/.ci/cloaking-rules.txt index 50ba0cbb..946efa71 100644 --- a/.ci/cloaking-rules.txt +++ b/.ci/cloaking-rules.txt @@ -1,3 +1,5 @@ cloaked.* one.one.one.one *.cloaked2.* one.one.one.one # inline comment =www.dnscrypt-test 192.168.100.100 +=www.dnscrypt-test.com 192.168.100.101 +=ipv6.dnscrypt-test.com fd02::1 \ No newline at end of file diff --git a/.ci/test2-dnscrypt-proxy.toml b/.ci/test2-dnscrypt-proxy.toml index 2ccc3309..2489a244 100644 --- a/.ci/test2-dnscrypt-proxy.toml +++ b/.ci/test2-dnscrypt-proxy.toml @@ -10,6 +10,7 @@ block_unqualified = true block_undelegated = true forwarding_rules = 'forwarding-rules.txt' cloaking_rules = 'cloaking-rules.txt' +cloak_ptr = true cache = true [local_doh] diff --git a/dnscrypt-proxy/common.go b/dnscrypt-proxy/common.go index 29431651..b1ab7186 100644 --- a/dnscrypt-proxy/common.go +++ b/dnscrypt-proxy/common.go @@ -47,6 +47,11 @@ const ( InheritedDescriptorsBase = uintptr(50) ) +const ( + IPv4Arpa = "in-addr.arpa" + IPv6Arpa = "ip6.arpa" +) + func PrefixWithSize(packet []byte) ([]byte, error) { packetLen := len(packet) if packetLen > 0xffff { diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index 12f7db1e..cceecd61 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -98,6 +98,7 @@ type Config struct { RefusedCodeInResponses bool `toml:"refused_code_in_responses"` BlockedQueryResponse string `toml:"blocked_query_response"` QueryMeta []string `toml:"query_meta"` + CloakedPTR bool `toml:"cloak_ptr"` AnonymizedDNS AnonymizedDNSConfig `toml:"anonymized_dns"` DoHClientX509Auth DoHClientX509AuthConfig `toml:"doh_client_x509_auth"` DoHClientX509AuthLegacy DoHClientX509AuthConfig `toml:"tls_client_auth"` @@ -154,6 +155,7 @@ func newConfig() Config { AnonymizedDNS: AnonymizedDNSConfig{ DirectCertFallback: true, }, + CloakedPTR: false, } } @@ -484,6 +486,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error { proxy.cacheMaxTTL = config.CacheMaxTTL proxy.rejectTTL = config.RejectTTL proxy.cloakTTL = config.CloakTTL + proxy.cloakedPTR = config.CloakedPTR proxy.queryMeta = config.QueryMeta diff --git a/dnscrypt-proxy/example-cloaking-rules.txt b/dnscrypt-proxy/example-cloaking-rules.txt index 7f98c2e3..95de377c 100644 --- a/dnscrypt-proxy/example-cloaking-rules.txt +++ b/dnscrypt-proxy/example-cloaking-rules.txt @@ -35,3 +35,10 @@ localhost ::1 # ads.* 192.168.100.1 # ads.* 192.168.100.2 # ads.* ::1 + +# PTR records can be created by setting cloak_ptr in the main configuration file +# Entries with wild cards will not have PTR records created, but multiple +# names for the same IP are supported + +# example.com 192.168.100.1 +# my.example.com 192.168.100.1 diff --git a/dnscrypt-proxy/example-dnscrypt-proxy.toml b/dnscrypt-proxy/example-dnscrypt-proxy.toml index 4185710e..b4a1f9f3 100644 --- a/dnscrypt-proxy/example-dnscrypt-proxy.toml +++ b/dnscrypt-proxy/example-dnscrypt-proxy.toml @@ -352,6 +352,8 @@ reject_ttl = 10 ## Cloaking returns a predefined address for a specific name. ## In addition to acting as a HOSTS file, it can also return the IP address ## of a different name. It will also do CNAME flattening. +## If 'cloak_ptr' is set, then PTR (reverse lookups) are enabled +## for cloaking rules that do not contain wild cards. ## ## See the `example-cloaking-rules.txt` file for an example @@ -360,6 +362,7 @@ reject_ttl = 10 ## TTL used when serving entries in cloaking-rules.txt # cloak_ttl = 600 +# cloak_ptr = false diff --git a/dnscrypt-proxy/plugin_cloak.go b/dnscrypt-proxy/plugin_cloak.go index b2d4a425..6e7d59d8 100644 --- a/dnscrypt-proxy/plugin_cloak.go +++ b/dnscrypt-proxy/plugin_cloak.go @@ -19,12 +19,14 @@ type CloakedName struct { lastUpdate *time.Time lineNo int isIP bool + PTR []string } type PluginCloak struct { sync.RWMutex patternMatcher *PatternMatcher ttl uint32 + createPTR bool } func (plugin *PluginCloak) Name() string { @@ -42,6 +44,7 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error { return err } plugin.ttl = proxy.cloakTTL + plugin.createPTR = proxy.cloakedPTR plugin.patternMatcher = NewPatternMatcher() cloakedNames := make(map[string]*CloakedName) for lineNo, line := range strings.Split(string(bin), "\n") { @@ -67,7 +70,8 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error { if !found { cloakedName = &CloakedName{} } - if ip := net.ParseIP(target); ip != nil { + ip := net.ParseIP(target) + if ip != nil { if ipv4 := ip.To4(); ipv4 != nil { cloakedName.ipv4 = append((*cloakedName).ipv4, ipv4) } else if ipv6 := ip.To16(); ipv6 != nil { @@ -82,6 +86,28 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error { } cloakedName.lineNo = lineNo + 1 cloakedNames[line] = cloakedName + + if !plugin.createPTR || strings.Contains(line, "*") || !cloakedName.isIP == true { + continue + } + + var ptrLine string + if ipv4 := ip.To4(); ipv4 != nil { + reversed, _ := dns.ReverseAddr(ip.To4().String()) + ptrLine = strings.TrimSuffix(reversed, ".") + } else { + reversed, _ := dns.ReverseAddr(cloakedName.ipv6[0].To16().String()) + ptrLine = strings.TrimSuffix(reversed, ".") + } + ptrQueryLine := ptrEntryToQuery(ptrLine) + ptrCloakedName, found := cloakedNames[ptrQueryLine] + if !found { + ptrCloakedName = &CloakedName{} + } + ptrCloakedName.isIP = true + ptrCloakedName.PTR = append((*ptrCloakedName).PTR, ptrNameToFQDN(line)) + ptrCloakedName.lineNo = lineNo + 1 + cloakedNames[ptrQueryLine] = ptrCloakedName } for line, cloakedName := range cloakedNames { if err := plugin.patternMatcher.Add(line, cloakedName, cloakedName.lineNo); err != nil { @@ -91,6 +117,15 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error { return nil } +func ptrEntryToQuery(ptrEntry string) string { + return "=" + ptrEntry +} + +func ptrNameToFQDN(ptrLine string) string { + ptrLine = strings.TrimPrefix(ptrLine, "=") + return ptrLine + "." +} + func (plugin *PluginCloak) Drop() error { return nil } @@ -101,7 +136,7 @@ func (plugin *PluginCloak) Reload() error { func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error { question := msg.Question[0] - if question.Qclass != dns.ClassINET || (question.Qtype != dns.TypeA && question.Qtype != dns.TypeAAAA) { + if question.Qclass != dns.ClassINET || (question.Qtype != dns.TypeA && question.Qtype != dns.TypeAAAA && question.Qtype != dns.TypePTR) { return nil } now := time.Now() @@ -157,13 +192,20 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error rr.A = ip synth.Answer = append(synth.Answer, rr) } - } else { + } else if question.Qtype == dns.TypeAAAA { for _, ip := range cloakedName.ipv6 { rr := new(dns.AAAA) rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: ttl} rr.AAAA = ip synth.Answer = append(synth.Answer, rr) } + } else if question.Qtype == dns.TypePTR { + for _, ptr := range cloakedName.PTR { + rr := new(dns.PTR) + rr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl} + rr.Ptr = ptr + synth.Answer = append(synth.Answer, rr) + } } rand.Shuffle(len(synth.Answer), func(i, j int) { synth.Answer[i], synth.Answer[j] = synth.Answer[j], synth.Answer[i] }) pluginsState.synthResponse = synth diff --git a/dnscrypt-proxy/proxy.go b/dnscrypt-proxy/proxy.go index 8e9aa299..b0cebbd4 100644 --- a/dnscrypt-proxy/proxy.go +++ b/dnscrypt-proxy/proxy.go @@ -83,6 +83,7 @@ type Proxy struct { cacheMinTTL uint32 cacheNegMaxTTL uint32 cloakTTL uint32 + cloakedPTR bool cache bool pluginBlockIPv6 bool ephemeralKeys bool