From 69a740714fb7e049a92f7d49449b91a334479472 Mon Sep 17 00:00:00 2001
From: Simon Zolin <s.zolin@adguard.com>
Date: Fri, 2 Oct 2020 12:51:55 +0300
Subject: [PATCH] * DNS: set default blocking mode to Null IP for A/AAAA, empty
 response for others

Close #1914

Squashed commit of the following:

commit cb127a9a409b2f228848fb838535f3db6e9d32a9
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Oct 2 12:37:11 2020 +0300

    return empty response if not A/AAAA qtype

commit 175a736d7d69619022db92a9250c382ad7fc9996
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Oct 2 12:21:51 2020 +0300

    fix

commit 03aab89d2da00ede3aad6eb5a5bb2d545444a186
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Oct 2 12:18:11 2020 +0300

    fix tests

commit 4225d511df910aae2df4651231c01a8a13bb937f
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Oct 2 12:02:11 2020 +0300

    * DNS: set default blocking mode to Null IP for A/AAAA, NXDOMAIN for others
---
 client/src/__locales/en.json  |  2 +-
 dnsforward/dnsforward_test.go | 11 +++++++----
 dnsforward/handle_dns.go      |  3 ++-
 dnsforward/msg.go             | 29 +++++++++++++++++++----------
 4 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 26e7df08..678dd39f 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -257,7 +257,7 @@
     "rate_limit_desc": "The number of requests per second that a single client is allowed to make (setting it to 0 means unlimited)",
     "blocking_ipv4_desc": "IP address to be returned for a blocked A request",
     "blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
-    "blocking_mode_default": "Default: Respond with REFUSED when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule",
+    "blocking_mode_default": "Default: Respond with zero IP address (0.0.0.0 for A; :: for AAAA) when blocked by Adblock-style rule; respond with the IP address specified in the rule when blocked by /etc/hosts-style rule",
     "blocking_mode_refused": "REFUSED: Respond with REFUSED code",
     "blocking_mode_nxdomain": "NXDOMAIN: Respond with NXDOMAIN code",
     "blocking_mode_null_ip": "Null IP: Respond with zero IP address (0.0.0.0 for A; :: for AAAA)",
diff --git a/dnsforward/dnsforward_test.go b/dnsforward/dnsforward_test.go
index 80336055..fbffc5c4 100644
--- a/dnsforward/dnsforward_test.go
+++ b/dnsforward/dnsforward_test.go
@@ -267,7 +267,7 @@ func TestBlockedRequest(t *testing.T) {
 	addr := s.dnsProxy.Addr(proxy.ProtoUDP)
 
 	//
-	// Default blocking - REFUSED
+	// Default blocking - NULL IP
 	//
 	req := dns.Msg{}
 	req.Id = dns.Id()
@@ -280,7 +280,8 @@ func TestBlockedRequest(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Couldn't talk to server %s: %s", addr, err)
 	}
-	assert.Equal(t, dns.RcodeRefused, reply.Rcode)
+	assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
+	assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0")))
 
 	err = s.Stop()
 	if err != nil {
@@ -442,7 +443,8 @@ func TestBlockCNAME(t *testing.T) {
 	req := createTestMessage("badhost.")
 	reply, err := dns.Exchange(req, addr.String())
 	assert.Nil(t, err, nil)
-	assert.Equal(t, dns.RcodeRefused, reply.Rcode)
+	assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
+	assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0")))
 
 	// 'whitelist.example.org' has a canonical name 'null.example.org' which is blocked by filters
 	//   but 'whitelist.example.org' is in a whitelist:
@@ -457,7 +459,8 @@ func TestBlockCNAME(t *testing.T) {
 	req = createTestMessage("example.org.")
 	reply, err = dns.Exchange(req, addr.String())
 	assert.Nil(t, err)
-	assert.Equal(t, dns.RcodeRefused, reply.Rcode)
+	assert.Equal(t, dns.RcodeSuccess, reply.Rcode)
+	assert.True(t, reply.Answer[0].(*dns.A).A.Equal(net.ParseIP("0.0.0.0")))
 
 	_ = s.Stop()
 }
diff --git a/dnsforward/handle_dns.go b/dnsforward/handle_dns.go
index f864865b..d770250c 100644
--- a/dnsforward/handle_dns.go
+++ b/dnsforward/handle_dns.go
@@ -86,9 +86,10 @@ func processInitial(ctx *dnsContext) int {
 	}
 
 	// disable Mozilla DoH
+	// https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet
 	if (d.Req.Question[0].Qtype == dns.TypeA || d.Req.Question[0].Qtype == dns.TypeAAAA) &&
 		d.Req.Question[0].Name == "use-application-dns.net." {
-		d.Res = s.makeResponseREFUSED(d.Req)
+		d.Res = s.genNXDomain(d.Req)
 		return resultFinish
 	}
 
diff --git a/dnsforward/msg.go b/dnsforward/msg.go
index fc4ecb51..80997ed6 100644
--- a/dnsforward/msg.go
+++ b/dnsforward/msg.go
@@ -24,7 +24,10 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
 	m := d.Req
 
 	if m.Question[0].Qtype != dns.TypeA && m.Question[0].Qtype != dns.TypeAAAA {
-		return s.makeResponseREFUSED(m)
+		if s.conf.BlockingMode == "null_ip" {
+			return s.makeResponse(m)
+		}
+		return s.genNXDomain(m)
 	}
 
 	switch result.Reason {
@@ -42,13 +45,7 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
 
 		if s.conf.BlockingMode == "null_ip" {
 			// it means that we should return 0.0.0.0 or :: for any blocked request
-
-			switch m.Question[0].Qtype {
-			case dns.TypeA:
-				return s.genARecord(m, []byte{0, 0, 0, 0})
-			case dns.TypeAAAA:
-				return s.genAAAARecord(m, net.IPv6zero)
-			}
+			return s.makeResponseNullIP(m)
 
 		} else if s.conf.BlockingMode == "custom_ip" {
 			// means that we should return custom IP for any blocked request
@@ -73,11 +70,12 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
 
 		// Default blocking mode
 		// If there's an IP specified in the rule, return it
-		// If there is no IP, return REFUSED
+		// For host-type rules, return null IP
 		if result.IP != nil {
 			return s.genResponseWithIP(m, result.IP)
 		}
-		return s.makeResponseREFUSED(m)
+
+		return s.makeResponseNullIP(m)
 	}
 }
 
@@ -138,6 +136,17 @@ func (s *Server) genResponseWithIP(req *dns.Msg, ip net.IP) *dns.Msg {
 	return resp
 }
 
+// Respond with 0.0.0.0 for A, :: for AAAA, empty response for other types
+func (s *Server) makeResponseNullIP(req *dns.Msg) *dns.Msg {
+	if req.Question[0].Qtype == dns.TypeA {
+		return s.genARecord(req, []byte{0, 0, 0, 0})
+	} else if req.Question[0].Qtype == dns.TypeAAAA {
+		return s.genAAAARecord(req, net.IPv6zero)
+	}
+
+	return s.makeResponse(req)
+}
+
 func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg {
 
 	ip := net.ParseIP(newAddr)