Repository: coredns/coredns (go)

Issue:
when no response is written to client, prometheus plugin reports it as NOERROR response
**What happened**:
when no response is written from upstream plugins (for example due to timeout in forward plugin) the prometheus plugin reports it as NOERROR (successful) response

```
curl http://localhost:9153/metrics | grep coredns_dns_responses_total

coredns_dns_responses_total{rcode="NOERROR",server="dns://:53",zone="."} 2
```

**What you expected to happen**:
I would expect that it will be reported as SERVFAIL as it is returned to client as SERVFAIL
```
coredns_dns_responses_total{rcode="SERVFAIL",server="dns://:53",zone="."} 2
```

**How to reproduce it (as minimally and precisely as possible)**:
run CoreDNS with Corefile (the invalid IP in forward plugin is there on purpose to better simulate errors)
```
.:53 {
    forward . 9.8.7.6
    prometheus []:9153
}
```

run dig
```
dig @127.0.0.1 9gag.com
```

get metrics and look at the coredns_dns_responses_total metric
```
curl http://localhost:9153/metrics | grep coredns_dns_responses_total

# HELP coredns_dns_responses_total Counter of response status codes.
# TYPE coredns_dns_responses_total counter
coredns_dns_responses_total{rcode="NOERROR",server="dns://:53",zone="."} 2
```

**Anything else we need to know?**:

**Environment**:

- the version of CoreDNS: 1.8.4 (latest)
- Corefile:
```
.:53 {
    forward . 9.8.7.6
    prometheus []:9153
}
```
- logs, if applicable:
- OS (e.g: `cat /etc/os-release`):
- Others:


Pull request:
when no response is written, fallback to status of next plugin in prometheus plugin
Signed-off-by: Ondrej Benkovsky <ondrej.benkovsky@wandera.com>

close #4726

### 1. Why is this pull request needed and what does it do?
when no response is written from up the chain of plugins, the default value of dnstest.Recorder for rcode (0) is used as rcode reported to the `coredns_dns_responses_total` metric, which is misleading and wrong. This PR changes the behaviour that when no response is written, the return status of the next plugin is used.

### 2. Which issues (if any) are related?
#4726

### 3. Which documentation changes (if any) need to be made?
I think no changes are needed

### 4. Does this introduce a backward incompatible change or deprecation?
I don't think so, it fixes the behaviour


Patch:
diff --git a/go.mod b/go.mod
index 140699734..c06b644a7 100644
--- a/go.mod
+++ b/go.mod
@@ -5,11 +5,11 @@ go 1.16
 require (
 	github.com/Azure/azure-sdk-for-go v53.3.0+incompatible
 	github.com/Azure/go-autorest/autorest v0.11.19
-	github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
+	github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
 	github.com/Azure/go-autorest/autorest/to v0.2.0 // indirect
 	github.com/Microsoft/go-winio v0.5.0 // indirect
 	github.com/apparentlymart/go-cidr v1.1.0
-	github.com/aws/aws-sdk-go v1.38.68
+	github.com/aws/aws-sdk-go v1.39.0
 	github.com/coredns/caddy v1.1.0
 	github.com/dnstap/golang-dnstap v0.4.0
 	github.com/farsightsec/golang-framestream v0.3.0
@@ -29,10 +29,10 @@ require (
 	github.com/prometheus/common v0.29.0
 	go.etcd.io/etcd/api/v3 v3.5.0
 	go.etcd.io/etcd/client/v3 v3.5.0
-	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
+	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
 	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
-	google.golang.org/api v0.49.0
-	google.golang.org/grpc v1.38.0
+	google.golang.org/api v0.50.0
+	google.golang.org/grpc v1.39.0
 	gopkg.in/DataDog/dd-trace-go.v1 v1.31.1
 	k8s.io/api v0.21.2
 	k8s.io/apimachinery v0.21.2
diff --git a/go.sum b/go.sum
index 7e619076c..b314174b1 100644
--- a/go.sum
+++ b/go.sum
@@ -51,8 +51,8 @@ github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyC
 github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
 github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
 github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 h1:8DQB8yl7aLQuP+nuR5e2RO6454OvFlSTXXaNHshc16s=
-github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=
+github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
 github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
 github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
@@ -89,8 +89,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
 github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
 github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/aws/aws-sdk-go v1.38.68 h1:aOG8geU4SohNp659eKBHRBgbqSrZ6jNZlfimIuJAwL8=
-github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
+github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -105,6 +105,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
 github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -134,6 +135,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -415,6 +417,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
@@ -429,10 +432,9 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -523,8 +525,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 h1:x622Z2o4hgCr/4CiKWc51jHVKaWdtVpBNmEI8wI9Qns=
-golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -665,6 +667,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -693,8 +696,8 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
-google.golang.org/api v0.49.0 h1:gjIBDxlTG7vnzMmEnYwTnvLTF8Rjzo+ETCgEX1YZ/fY=
-google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18=
+google.golang.org/api v0.50.0 h1:LX7NFCFYOHzr7WHaYiRUpeipZe9o5L8T+2F4Z798VDw=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -747,8 +750,8 @@ google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQ
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
-google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -772,8 +775,9 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
diff --git a/plugin/cache/cache.go b/plugin/cache/cache.go
index 5673cc948..54e5e4db3 100644
--- a/plugin/cache/cache.go
+++ b/plugin/cache/cache.go
@@ -181,6 +181,10 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
 	res.Ns = filterRRSlice(res.Ns, ttl, w.do, false)
 	res.Extra = filterRRSlice(res.Extra, ttl, w.do, false)
 
+	if !w.do {
+		res.AuthenticatedData = false // unset AD bit if client is not OK with DNSSEC
+	}
+
 	return w.ResponseWriter.WriteMsg(res)
 }
 
diff --git a/plugin/errors/README.md b/plugin/errors/README.md
index 61ed582cb..4a8ff3ce8 100644
--- a/plugin/errors/README.md
+++ b/plugin/errors/README.md
@@ -22,12 +22,12 @@ Extra knobs are available with an expanded syntax:
 
 ~~~
 errors {
-	consolidate DURATION REGEXP
+	consolidate DURATION REGEXP [LEVEL]
 }
 ~~~
 
-Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output, e.g.
-
+Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output with
+log level, which is configurable by optional option **LEVEL**. Supported options for **LEVEL** option are `warn`,`error`,`info` and `debug`.
 ~~~
 2 errors like '^read udp .* i/o timeout$' occurred in last 30s
 ~~~
@@ -53,7 +53,7 @@ Use the *forward* to resolve queries via 8.8.8.8 and print consolidated error me
 . {
     forward . 8.8.8.8
     errors {
-        consolidate 5m ".* i/o timeout$"
+        consolidate 5m ".* i/o timeout$" warn
         consolidate 30s "^Failed to .+"
     }
 }
diff --git a/plugin/errors/errors.go b/plugin/errors/errors.go
index 9565f11ab..c045f6992 100644
--- a/plugin/errors/errors.go
+++ b/plugin/errors/errors.go
@@ -18,10 +18,11 @@ import (
 var log = clog.NewWithPlugin("errors")
 
 type pattern struct {
-	ptimer  unsafe.Pointer
-	count   uint32
-	period  time.Duration
-	pattern *regexp.Regexp
+	ptimer      unsafe.Pointer
+	count       uint32
+	period      time.Duration
+	pattern     *regexp.Regexp
+	logCallback func(format string, v ...interface{})
 }
 
 func (p *pattern) timer() *time.Timer {
@@ -46,7 +47,7 @@ func newErrorHandler() *errorHandler {
 func (h *errorHandler) logPattern(i int) {
 	cnt := atomic.SwapUint32(&h.patterns[i].count, 0)
 	if cnt > 0 {
-		log.Errorf("%d errors like '%s' occurred in last %s",
+		h.patterns[i].logCallback("%d errors like '%s' occurred in last %s",
 			cnt, h.patterns[i].pattern.String(), h.patterns[i].period)
 	}
 }
diff --git a/plugin/errors/setup.go b/plugin/errors/setup.go
index ef279dadf..9fc5a3595 100644
--- a/plugin/errors/setup.go
+++ b/plugin/errors/setup.go
@@ -66,7 +66,7 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
 	}
 
 	args := c.RemainingArgs()
-	if len(args) != 2 {
+	if len(args) < 2 || len(args) > 3 {
 		return c.ArgErr()
 	}
 	p, err := time.ParseDuration(args[0])
@@ -77,7 +77,30 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
 	if err != nil {
 		return c.Err(err.Error())
 	}
-	h.patterns = append(h.patterns, &pattern{period: p, pattern: re})
+	lc, err := parseLogLevel(c, args)
+	if err != nil {
+		return err
+	}
+	h.patterns = append(h.patterns, &pattern{period: p, pattern: re, logCallback: lc})
 
 	return nil
 }
+
+func parseLogLevel(c *caddy.Controller, args []string) (func(format string, v ...interface{}), error) {
+	if len(args) != 3 {
+		return log.Errorf, nil
+	}
+
+	switch args[2] {
+	case "warn":
+		return log.Warningf, nil
+	case "error":
+		return log.Errorf, nil
+	case "info":
+		return log.Infof, nil
+	case "debug":
+		return log.Debugf, nil
+	default:
+		return nil, c.Err("unknown log level argument in consolidate")
+	}
+}
diff --git a/plugin/metrics/handler.go b/plugin/metrics/handler.go
index 0dbc053da..f26f90598 100644
--- a/plugin/metrics/handler.go
+++ b/plugin/metrics/handler.go
@@ -26,7 +26,14 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
 	rw := dnstest.NewRecorder(w)
 	status, err := plugin.NextOrFailure(m.Name(), m.Next, ctx, rw, r)
 
-	vars.Report(WithServer(ctx), state, zone, rcode.ToString(rw.Rcode), rw.Len, rw.Start)
+	rc := rw.Rcode
+	if !plugin.ClientWrite(status) {
+		// when no response was written, fallback to status returned from next plugin as this status
+		// is actually used as rcode of DNS response
+		// see https://github.com/coredns/coredns/blob/master/core/dnsserver/server.go#L318
+		rc = status
+	}
+	vars.Report(WithServer(ctx), state, zone, rcode.ToString(rc), rw.Len, rw.Start)
 
 	return status, err
 }
diff --git a/plugin/pkg/replacer/replacer.go b/plugin/pkg/replacer/replacer.go
index 81261ca79..845f11bc3 100644
--- a/plugin/pkg/replacer/replacer.go
+++ b/plugin/pkg/replacer/replacer.go
@@ -86,7 +86,7 @@ func appendValue(b []byte, state request.Request, rr *dnstest.Recorder, label st
 		return strconv.AppendInt(b, int64(state.Size()), 10)
 	// Recorded replacements.
 	case "{rcode}":
-		if rr == nil {
+		if rr == nil || rr.Msg == nil {
 			return append(b, EmptyValue...)
 		}
 		if rcode := dns.RcodeToString[rr.Rcode]; rcode != "" {
diff --git a/plugin/transfer/README.md b/plugin/transfer/README.md
index 8924a1e79..a7c6dca7f 100644
--- a/plugin/transfer/README.md
+++ b/plugin/transfer/README.md
@@ -31,9 +31,29 @@ transfer [ZONE...] {
     `transfer.Transferer`.
 
  *  `to` **ADDRESS...** The hosts *transfer* will transfer to. Use `*` to permit transfers to all
-    addresses. **ADDRESS** must be denoted in CIDR notation (e.g., 127.0.0.1/32) or just as plain
-    addresses. `to` may be specified multiple times.
+    addresses. Zone change notifications are sent to all **ADDRESS** that are an IP address or
+    an IP address and port e.g. `1.2.3.4`, `12:34::56`, `1.2.3.4:5300`, `[12:34::56]:5300`.
+    `to` may be specified multiple times.
+
+You can use the _acl_ plugin to further restrict hosts permitted to receive a zone transfer.
+See example below.
 
 ## Examples
 
-See the specific plugins using this plugin for examples on it's usage.
+Use in conjuction with the _acl_ plugin to restrict access to subnet 10.1.0.0/16.
+
+```
+...
+  acl {
+    allow type AXFR net 10.1.0.0/16
+    allow type IXFR net 10.1.0.0/16
+    block type AXFR net *
+    block type IXFR net *
+  }
+  transfer {
+    to *
+  }
+...
+```
+
+Each plugin that can use _transfer_ includes an example of use in their respective documentation.
\ No newline at end of file


Test patch:
diff --git a/plugin/cache/dnssec_test.go b/plugin/cache/dnssec_test.go
index 446718c9f..a746387bf 100644
--- a/plugin/cache/dnssec_test.go
+++ b/plugin/cache/dnssec_test.go
@@ -23,7 +23,8 @@ func TestResponseWithDNSSEC(t *testing.T) {
 		},
 		{
 			Qname: "invent.example.org.", Qtype: dns.TypeA,
-			Do: true,
+			Do:                true,
+			AuthenticatedData: true,
 			Answer: []dns.RR{
 				test.CNAME("invent.example.org.		1781	IN	CNAME	leptone.example.org."),
 				test.RRSIG("invent.example.org.		1781	IN	RRSIG	CNAME 8 3 1800 20201012085750 20200912082613 57411 example.org. ijSv5FmsNjFviBcOFwQgqjt073lttxTTNqkno6oMa3DD3kC+"),
@@ -40,6 +41,9 @@ func TestResponseWithDNSSEC(t *testing.T) {
 		m := tc.Msg()
 		rec := dnstest.NewRecorder(&test.ResponseWriter{})
 		c.ServeDNS(context.TODO(), rec, m)
+		if tc.AuthenticatedData != rec.Msg.AuthenticatedData {
+			t.Errorf("Test %d, expected AuthenticatedData=%v", i, tc.AuthenticatedData)
+		}
 		if err := test.Section(tc, test.Answer, rec.Msg.Answer); err != nil {
 			t.Errorf("Test %d, expected no error, got %s", i, err)
 		}
@@ -64,6 +68,7 @@ func dnssecHandler() plugin.Handler {
 		m := new(dns.Msg)
 		m.SetQuestion("example.org.", dns.TypeA)
 
+		m.AuthenticatedData = true
 		m.Answer = make([]dns.RR, 4)
 		m.Answer[0] = test.CNAME("invent.example.org.		1781	IN	CNAME	leptone.example.org.")
 		m.Answer[1] = test.RRSIG("invent.example.org.		1781	IN	RRSIG	CNAME 8 3 1800 20201012085750 20200912082613 57411 example.org. ijSv5FmsNjFviBcOFwQgqjt073lttxTTNqkno6oMa3DD3kC+")
diff --git a/plugin/errors/errors_test.go b/plugin/errors/errors_test.go
index 9563b7323..ba9a9b571 100644
--- a/plugin/errors/errors_test.go
+++ b/plugin/errors/errors_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/coredns/coredns/plugin"
 	"github.com/coredns/coredns/plugin/pkg/dnstest"
+	clog "github.com/coredns/coredns/plugin/pkg/log"
 	"github.com/coredns/coredns/plugin/test"
 
 	"github.com/miekg/dns"
@@ -71,21 +72,56 @@ func TestErrors(t *testing.T) {
 }
 
 func TestLogPattern(t *testing.T) {
-	buf := bytes.Buffer{}
-	golog.SetOutput(&buf)
-
-	h := &errorHandler{
-		patterns: []*pattern{{
-			count:   4,
-			period:  2 * time.Second,
-			pattern: regexp.MustCompile("^error.*!$"),
-		}},
+	type args struct {
+		logCallback func(format string, v ...interface{})
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "error log",
+			args: args{logCallback: log.Errorf},
+			want: "[ERROR] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
+		},
+		{
+			name: "warn log",
+			args: args{logCallback: log.Warningf},
+			want: "[WARNING] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
+		},
+		{
+			name: "info log",
+			args: args{logCallback: log.Infof},
+			want: "[INFO] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
+		},
+		{
+			name: "debug log",
+			args: args{logCallback: log.Debugf},
+			want: "[DEBUG] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
+		},
 	}
-	h.logPattern(0)
 
-	expLog := "4 errors like '^error.*!$' occurred in last 2s"
-	if log := buf.String(); !strings.Contains(log, expLog) {
-		t.Errorf("Expected log %q, but got %q", expLog, log)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			buf := bytes.Buffer{}
+			clog.D.Set()
+			golog.SetOutput(&buf)
+
+			h := &errorHandler{
+				patterns: []*pattern{{
+					count:       4,
+					period:      2 * time.Second,
+					pattern:     regexp.MustCompile("^error.*!$"),
+					logCallback: tt.args.logCallback,
+				}},
+			}
+			h.logPattern(0)
+
+			if log := buf.String(); !strings.Contains(log, tt.want) {
+				t.Errorf("Expected log %q, but got %q", tt.want, log)
+			}
+		})
 	}
 }
 
@@ -154,6 +190,7 @@ func TestStop(t *testing.T) {
 		patterns: []*pattern{{
 			period:  2 * time.Second,
 			pattern: regexp.MustCompile("^error.*!$"),
+			logCallback: log.Errorf,
 		}},
 	}
 
diff --git a/plugin/errors/setup_test.go b/plugin/errors/setup_test.go
index c61cf54c4..3bfb87c8d 100644
--- a/plugin/errors/setup_test.go
+++ b/plugin/errors/setup_test.go
@@ -1,9 +1,13 @@
 package errors
 
 import (
+	"bytes"
+	golog "log"
+	"strings"
 	"testing"
 
 	"github.com/coredns/caddy"
+	clog "github.com/coredns/coredns/plugin/pkg/log"
 )
 
 func TestErrorsParse(t *testing.T) {
@@ -67,3 +71,65 @@ func TestErrorsParse(t *testing.T) {
 		}
 	}
 }
+
+func TestProperLogCallbackIsSet(t *testing.T) {
+	tests := []struct {
+		name             string
+		inputErrorsRules string
+		wantLogLevel     string
+	}{
+		{
+			name: "warn is parsed properly",
+			inputErrorsRules: `errors {
+		        consolidate 1m .* warn
+		    }`,
+			wantLogLevel: "[WARNING]",
+		},
+		{
+			name: "error is parsed properly",
+			inputErrorsRules: `errors {
+		        consolidate 1m .* error
+		    }`,
+			wantLogLevel: "[ERROR]",
+		},
+		{
+			name: "info is parsed properly",
+			inputErrorsRules: `errors {
+		        consolidate 1m .* info
+		    }`,
+			wantLogLevel: "[INFO]",
+		},
+		{
+			name: "debug is parsed properly",
+			inputErrorsRules: `errors {
+		        consolidate 1m .* debug
+		    }`,
+			wantLogLevel: "[DEBUG]",
+		},
+		{
+			name: "default is error",
+			inputErrorsRules: `errors {
+		        consolidate 1m .*
+		    }`,
+			wantLogLevel: "[ERROR]",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			buf := bytes.Buffer{}
+			golog.SetOutput(&buf)
+			clog.D.Set()
+
+			c := caddy.NewTestController("dns", tt.inputErrorsRules)
+			h, _ := errorsParse(c)
+
+			l := h.patterns[0].logCallback
+			l("some error happened")
+
+			if log := buf.String(); !strings.Contains(log, tt.wantLogLevel) {
+				t.Errorf("Expected log %q, but got %q", tt.wantLogLevel, log)
+			}
+		})
+	}
+}
diff --git a/plugin/pkg/replacer/replacer_test.go b/plugin/pkg/replacer/replacer_test.go
index 3e0a50691..25dd37a5b 100644
--- a/plugin/pkg/replacer/replacer_test.go
+++ b/plugin/pkg/replacer/replacer_test.go
@@ -393,3 +393,52 @@ func TestMetadataMalformed(t *testing.T) {
 		}
 	}
 }
+
+func TestNoResponseWasWritten(t *testing.T) {
+	w := dnstest.NewRecorder(&test.ResponseWriter{})
+	r := new(dns.Msg)
+	r.SetQuestion("example.org.", dns.TypeHINFO)
+	r.Id = 1053
+	r.AuthenticatedData = true
+	r.CheckingDisabled = true
+	state := request.Request{W: w, Req: r}
+
+	replacer := New()
+	ctx := context.TODO()
+
+	// This couples the test very tightly to the code, but so be it.
+	expect := map[string]string{
+		"{type}":                    "HINFO",
+		"{name}":                    "example.org.",
+		"{class}":                   "IN",
+		"{proto}":                   "udp",
+		"{size}":                    "29",
+		"{remote}":                  "10.240.0.1",
+		"{port}":                    "40212",
+		"{local}":                   "127.0.0.1",
+		headerReplacer + "id}":      "1053",
+		headerReplacer + "opcode}":  "0",
+		headerReplacer + "do}":      "false",
+		headerReplacer + "bufsize}": "512",
+		"{rcode}":                   "-",
+		"{rsize}":                   "0",
+		"{duration}":                "0",
+		headerReplacer + "rflags}":  "-",
+	}
+	if len(expect) != len(labels) {
+		t.Fatalf("Expect %d labels, got %d", len(expect), len(labels))
+	}
+
+	for lbl := range labels {
+		repl := replacer.Replace(ctx, state, w, lbl)
+		if lbl == "{duration}" {
+			if repl[len(repl)-1] != 's' {
+				t.Errorf("Expected seconds, got %q", repl)
+			}
+			continue
+		}
+		if repl != expect[lbl] {
+			t.Errorf("Expected value %q, got %q", expect[lbl], repl)
+		}
+	}
+}
diff --git a/plugin/test/helpers.go b/plugin/test/helpers.go
index 0c7e85f2a..cb7b0994b 100644
--- a/plugin/test/helpers.go
+++ b/plugin/test/helpers.go
@@ -29,14 +29,15 @@ func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() }
 // Case represents a test case that encapsulates various data from a query and response.
 // Note that is the TTL of a record is 303 we don't compare it with the TTL.
 type Case struct {
-	Qname  string
-	Qtype  uint16
-	Rcode  int
-	Do     bool
-	Answer []dns.RR
-	Ns     []dns.RR
-	Extra  []dns.RR
-	Error  error
+	Qname             string
+	Qtype             uint16
+	Rcode             int
+	Do                bool
+	AuthenticatedData bool
+	Answer            []dns.RR
+	Ns                []dns.RR
+	Extra             []dns.RR
+	Error             error
 }
 
 // Msg returns a *dns.Msg embedded in c.
diff --git a/plugin/trace/trace_test.go b/plugin/trace/trace_test.go
index 28c2e10bd..233f63207 100644
--- a/plugin/trace/trace_test.go
+++ b/plugin/trace/trace_test.go
@@ -45,7 +45,7 @@ func TestTrace(t *testing.T) {
 		rcode    int
 		question *dns.Msg
 		server   string
-		err    error
+		err      error
 	}{
 		{
 			name:     "NXDOMAIN",