diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 5a8beee8..b1303621 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -888,6 +888,7 @@ Response:
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
+ "dnssec_enabled": true | false
"disable_ipv6": true | false,
}
@@ -905,6 +906,7 @@ Request:
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"edns_cs_enabled": true | false,
+ "dnssec_enabled": true | false
"disable_ipv6": true | false,
}
@@ -1254,6 +1256,7 @@ Response:
}
...
],
+ "answer_dnssec": true,
"client":"127.0.0.1",
"elapsedMs":"0.098403",
"filterId":1,
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 5791f6b4..a4f2293e 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -479,5 +479,7 @@
"install_static_configure": "We have detected that a dynamic IP address is used — <0>{{ip}}0>. Do you want to use it as your static address?",
"confirm_static_ip": "AdGuard Home will configure {{ip}} to be your static IP address. Do you want to proceed?",
"list_updated": "{{count}} list updated",
- "list_updated_plural": "{{count}} lists updated"
-}
\ No newline at end of file
+ "list_updated_plural": "{{count}} lists updated",
+ "dnssec_enable": "Enable DNSSEC",
+ "dnssec_enable_desc": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required)"
+}
diff --git a/client/src/components/Settings/Dns/Config/Form.js b/client/src/components/Settings/Dns/Config/Form.js
index 580f09d4..1db400e8 100644
--- a/client/src/components/Settings/Dns/Config/Form.js
+++ b/client/src/components/Settings/Dns/Config/Form.js
@@ -65,6 +65,18 @@ let Form = ({
/>
+
{
blocking_ipv4,
blocking_ipv6,
edns_cs_enabled,
+ dnssec_enabled,
disable_ipv6,
processingSetConfig,
} = dnsConfig;
@@ -35,6 +36,7 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
blocking_ipv6,
edns_cs_enabled,
disable_ipv6,
+ dnssec_enabled,
}}
onSubmit={handleFormSubmit}
processing={processingSetConfig}
diff --git a/client/src/reducers/dnsConfig.js b/client/src/reducers/dnsConfig.js
index 87f59d36..ad45e631 100644
--- a/client/src/reducers/dnsConfig.js
+++ b/client/src/reducers/dnsConfig.js
@@ -45,6 +45,7 @@ const dnsConfig = handleActions(
blocking_ipv6: DEFAULT_BLOCKING_IPV6,
edns_cs_enabled: false,
disable_ipv6: false,
+ dnssec_enabled: false,
},
);
diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go
index 64949ce1..646f61ff 100644
--- a/dnsforward/dnsforward.go
+++ b/dnsforward/dnsforward.go
@@ -136,6 +136,8 @@ type FilteringConfig struct {
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
+ EnableDNSSEC bool `yaml:"enable_dnssec"` // Set DNSSEC flag in outcoming DNS request
+
// Respond with an empty answer to all AAAA requests
AAAADisabled bool `yaml:"aaaa_disabled"`
@@ -512,6 +514,7 @@ type dnsContext struct {
err error // error returned from the module
protectionEnabled bool // filtering is enabled, dnsfilter object is ready
responseFromUpstream bool // response is received from upstream servers
+ origReqDNSSEC bool // DNSSEC flag in the original request from user
}
const (
@@ -588,6 +591,18 @@ func processUpstream(ctx *dnsContext) int {
}
}
+ if s.conf.EnableDNSSEC {
+ opt := d.Req.IsEdns0()
+ if opt == nil {
+ log.Debug("DNS: Adding OPT record with DNSSEC flag")
+ d.Req.SetEdns0(4096, true)
+ } else if !opt.Do() {
+ opt.SetDo(true)
+ } else {
+ ctx.origReqDNSSEC = true
+ }
+ }
+
// request was not filtered so let it be processed further
err := s.dnsProxy.Resolve(d)
if err != nil {
@@ -599,6 +614,50 @@ func processUpstream(ctx *dnsContext) int {
return resultDone
}
+// Process DNSSEC after response from upstream server
+func processDNSSECAfterResponse(ctx *dnsContext) int {
+ d := ctx.proxyCtx
+
+ if !ctx.responseFromUpstream || // don't process response if it's not from upstream servers
+ !ctx.srv.conf.EnableDNSSEC {
+ return resultDone
+ }
+
+ optResp := d.Res.IsEdns0()
+ if !ctx.origReqDNSSEC && optResp != nil && optResp.Do() {
+ return resultDone
+ }
+
+ // Remove RRSIG records from response
+ // because there is no DO flag in the original request from client,
+ // but we have EnableDNSSEC set, so we have set DO flag ourselves,
+ // and now we have to clean up the DNS records our client didn't ask for.
+
+ answers := []dns.RR{}
+ for _, a := range d.Res.Answer {
+ switch a.(type) {
+ case *dns.RRSIG:
+ log.Debug("Removing RRSIG record from response: %v", a)
+ default:
+ answers = append(answers, a)
+ }
+ }
+ d.Res.Answer = answers
+
+ answers = []dns.RR{}
+ for _, a := range d.Res.Ns {
+ switch a.(type) {
+ case *dns.RRSIG:
+ log.Debug("Removing RRSIG record from response: %v", a)
+ default:
+ answers = append(answers, a)
+ }
+ }
+ d.Res.Ns = answers
+
+ return resultDone
+}
+
// Apply filtering logic after we have received response from upstream servers
func processFilteringAfterResponse(ctx *dnsContext) int {
s := ctx.srv
@@ -606,10 +665,6 @@ func processFilteringAfterResponse(ctx *dnsContext) int {
res := ctx.result
var err error
- if !ctx.responseFromUpstream {
- return resultDone // don't process response if it's not from upstream servers
- }
-
if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 {
d.Req.Question[0] = ctx.origQuestion
d.Res.Question[0] = ctx.origQuestion
@@ -688,14 +743,19 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
processInitial,
processFilteringBeforeRequest,
processUpstream,
+ processDNSSECAfterResponse,
processFilteringAfterResponse,
processQueryLogsAndStats,
}
for _, process := range mods {
r := process(ctx)
switch r {
+ case resultDone:
+ // continue: call the next filter
+
case resultFinish:
return nil
+
case resultError:
return ctx.err
}
diff --git a/dnsforward/dnsforward_http.go b/dnsforward/dnsforward_http.go
index 111fefbd..414b728b 100644
--- a/dnsforward/dnsforward_http.go
+++ b/dnsforward/dnsforward_http.go
@@ -28,6 +28,7 @@ type dnsConfigJSON struct {
BlockingIPv4 string `json:"blocking_ipv4"`
BlockingIPv6 string `json:"blocking_ipv6"`
EDNSCSEnabled bool `json:"edns_cs_enabled"`
+ DNSSECEnabled bool `json:"dnssec_enabled"`
DisableIPv6 bool `json:"disable_ipv6"`
}
@@ -40,6 +41,7 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
resp.BlockingIPv6 = s.conf.BlockingIPv6
resp.RateLimit = s.conf.Ratelimit
resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet
+ resp.DNSSECEnabled = s.conf.EnableDNSSEC
resp.DisableIPv6 = s.conf.AAAADisabled
s.RUnlock()
@@ -119,6 +121,10 @@ func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
restart = true
}
+ if js.Exists("dnssec_enabled") {
+ s.conf.EnableDNSSEC = req.DNSSECEnabled
+ }
+
if js.Exists("disable_ipv6") {
s.conf.AAAADisabled = req.DisableIPv6
}
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index d2ee5a8b..e597786f 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1110,6 +1110,8 @@ definitions:
type: "string"
edns_cs_enabled:
type: "boolean"
+ dnssec_enabled:
+ type: "boolean"
UpstreamsConfig:
type: "object"
@@ -1517,6 +1519,8 @@ definitions:
description: "Answer from upstream server (optional)"
items:
$ref: "#/definitions/DnsAnswer"
+ answer_dnssec:
+ type: "boolean"
client:
type: "string"
example: "192.168.0.1"
diff --git a/querylog/qlog.go b/querylog/qlog.go
index 55020612..21d5a1a6 100644
--- a/querylog/qlog.go
+++ b/querylog/qlog.go
@@ -286,7 +286,15 @@ func logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
if msg != nil {
jsonEntry["status"] = dns.RcodeToString[msg.Rcode]
+
+ opt := msg.IsEdns0()
+ dnssecOk := false
+ if opt != nil {
+ dnssecOk = opt.Do()
+ }
+ jsonEntry["answer_dnssec"] = dnssecOk
}
+
if len(entry.Result.Rule) > 0 {
jsonEntry["rule"] = entry.Result.Rule
jsonEntry["filterId"] = entry.Result.FilterID