forked from coredns/coredns
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add local plugin See: coredns#4260 Signed-off-by: Miek Gieben <miek@miek.nl> * stickler bot Signed-off-by: Miek Gieben <miek@miek.nl> * See Also Signed-off-by: Miek Gieben <miek@miek.nl>
- Loading branch information
Showing
9 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ var Directives = []string{ | |
"errors", | ||
"log", | ||
"dnstap", | ||
"local", | ||
"dns64", | ||
"acl", | ||
"any", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
.\" Generated by Mmark Markdown Processer - mmark.miek.nl | ||
.TH "COREDNS-LOCAL" 7 "November 2020" "CoreDNS" "CoreDNS Plugins" | ||
|
||
.SH "NAME" | ||
.PP | ||
\fIlocal\fP - respond to local names. | ||
|
||
.SH "DESCRIPTION" | ||
.PP | ||
\fIlocal\fP will respond with a basic reply to a "local request". Local request are defined to be | ||
names in the following zones: localhost, 0.in-addr.arpa, 127.in-addr.arpa and 255.in-addr.arpa \fIand\fP | ||
any query asking for \fB\fClocalhost.<domain>\fR. When seeing the latter a metric counter is increased and | ||
if \fIdebug\fP is enabled a debug log is emitted. | ||
|
||
.PP | ||
With \fIlocal\fP enabled any query falling under these zones will get a reply. The prevents the query | ||
from "escaping" to the internet and putting strain on external infrastructure. | ||
|
||
.PP | ||
The zones are mostly empty, only \fB\fClocalhost.\fR address records (A and AAAA) are defined and a | ||
\fB\fC1.0.0.127.in-addr.arpa.\fR reverse (PTR) record. | ||
|
||
.SH "SYNTAX" | ||
.PP | ||
.RS | ||
|
||
.nf | ||
local | ||
|
||
.fi | ||
.RE | ||
|
||
.SH "METRICS" | ||
.PP | ||
If monitoring is enabled (via the \fIprometheus\fP plugin) then the following metric is exported: | ||
|
||
.IP \(bu 4 | ||
\fB\fCcoredns_local_localhost_requests_total{}\fR - a counter of the number of \fB\fClocalhost.<domain>\fR | ||
requests CoreDNS has seen. Note this does \fInot\fP count \fB\fClocalhost.\fR queries. | ||
|
||
|
||
.PP | ||
Note that this metric \fIdoes not\fP have a \fB\fCserver\fR label, because it's more interesting to find the | ||
client(s) performing these queries than to see which server handled it. You'll need to inspect the | ||
debug log to get the client IP address. | ||
|
||
.SH "EXAMPLES" | ||
.PP | ||
.RS | ||
|
||
.nf | ||
\&. { | ||
local | ||
} | ||
|
||
.fi | ||
.RE | ||
|
||
.SH "BUGS" | ||
.PP | ||
Only the \fB\fCin-addr.arpa.\fR reverse zone is implemented, \fB\fCip6.arpa.\fR queries are not intercepted. | ||
|
||
.SH "ALSO SEE" | ||
.PP | ||
BIND9's configuration in Debian comes with these zones preconfigured. See the \fIdebug\fP plugin for | ||
enabling debug logging. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ prometheus:metrics | |
errors:errors | ||
log:log | ||
dnstap:dnstap | ||
local:local | ||
dns64:dns64 | ||
acl:acl | ||
any:any | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# local | ||
|
||
## Name | ||
|
||
*local* - respond to local names. | ||
|
||
## Description | ||
|
||
*local* will respond with a basic reply to a "local request". Local request are defined to be | ||
names in the following zones: localhost, 0.in-addr.arpa, 127.in-addr.arpa and 255.in-addr.arpa *and* | ||
any query asking for `localhost.<domain>`. When seeing the latter a metric counter is increased and | ||
if *debug* is enabled a debug log is emitted. | ||
|
||
With *local* enabled any query falling under these zones will get a reply. The prevents the query | ||
from "escaping" to the internet and putting strain on external infrastructure. | ||
|
||
The zones are mostly empty, only `localhost.` address records (A and AAAA) are defined and a | ||
`1.0.0.127.in-addr.arpa.` reverse (PTR) record. | ||
|
||
## Syntax | ||
|
||
~~~ txt | ||
local | ||
~~~ | ||
|
||
## Metrics | ||
|
||
If monitoring is enabled (via the *prometheus* plugin) then the following metric is exported: | ||
|
||
* `coredns_local_localhost_requests_total{}` - a counter of the number of `localhost.<domain>` | ||
requests CoreDNS has seen. Note this does *not* count `localhost.` queries. | ||
|
||
Note that this metric *does not* have a `server` label, because it's more interesting to find the | ||
client(s) performing these queries than to see which server handled it. You'll need to inspect the | ||
debug log to get the client IP address. | ||
|
||
## Examples | ||
|
||
~~~ corefile | ||
. { | ||
local | ||
} | ||
~~~ | ||
|
||
## Bugs | ||
|
||
Only the `in-addr.arpa.` reverse zone is implemented, `ip6.arpa.` queries are not intercepted. | ||
|
||
## See Also | ||
|
||
BIND9's configuration in Debian comes with these zones preconfigured. See the *debug* plugin for | ||
enabling debug logging. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package local | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"strings" | ||
|
||
"github.com/coredns/coredns/plugin" | ||
clog "github.com/coredns/coredns/plugin/pkg/log" | ||
"github.com/coredns/coredns/request" | ||
|
||
"github.com/miekg/dns" | ||
) | ||
|
||
var log = clog.NewWithPlugin("local") | ||
|
||
// Local is a plugin that returns standard replies for local queries. | ||
type Local struct { | ||
Next plugin.Handler | ||
} | ||
|
||
var zones = []string{"localhost.", "0.in-addr.arpa.", "127.in-addr.arpa.", "255.in-addr.arpa."} | ||
|
||
func soaFromOrigin(origin string) []dns.RR { | ||
hdr := dns.RR_Header{Name: origin, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeSOA} | ||
return []dns.RR{&dns.SOA{Hdr: hdr, Ns: "localhost.", Mbox: "root.localhost.", Serial: 1, Refresh: 0, Retry: 0, Expire: 0, Minttl: ttl}} | ||
} | ||
|
||
func nsFromOrigin(origin string) []dns.RR { | ||
hdr := dns.RR_Header{Name: origin, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeNS} | ||
return []dns.RR{&dns.NS{Hdr: hdr, Ns: "localhost."}} | ||
} | ||
|
||
// ServeDNS implements the plugin.Handler interface. | ||
func (l Local) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { | ||
state := request.Request{W: w, Req: r} | ||
qname := state.QName() | ||
|
||
lc := len("localhost.") | ||
if len(state.Name()) > lc && strings.HasPrefix(state.Name(), "localhost.") { | ||
// we have multiple labels, but the first one is localhost, intercept this and return 127.0.0.1 or ::1 | ||
log.Debugf("Intercepting localhost query for %q %s, from %s", state.Name(), state.Type(), state.IP()) | ||
LocalhostCount.Inc() | ||
reply := doLocalhost(state) | ||
w.WriteMsg(reply) | ||
return 0, nil | ||
} | ||
|
||
zone := plugin.Zones(zones).Matches(qname) | ||
if zone == "" { | ||
return plugin.NextOrFailure(l.Name(), l.Next, ctx, w, r) | ||
} | ||
|
||
m := new(dns.Msg) | ||
m.SetReply(r) | ||
zone = qname[len(qname)-len(zone):] | ||
|
||
switch q := state.Name(); q { | ||
case "localhost.", "0.in-addr.arpa.", "127.in-addr.arpa.", "255.in-addr.arpa.": | ||
switch state.QType() { | ||
case dns.TypeA: | ||
if q != "localhost." { | ||
// nodata | ||
m.Ns = soaFromOrigin(qname) | ||
break | ||
} | ||
|
||
hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeA} | ||
m.Answer = []dns.RR{&dns.A{Hdr: hdr, A: net.ParseIP("127.0.0.1").To4()}} | ||
case dns.TypeAAAA: | ||
if q != "localhost." { | ||
// nodata | ||
m.Ns = soaFromOrigin(qname) | ||
break | ||
} | ||
|
||
hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeAAAA} | ||
m.Answer = []dns.RR{&dns.AAAA{Hdr: hdr, AAAA: net.ParseIP("::1")}} | ||
case dns.TypeSOA: | ||
m.Answer = soaFromOrigin(qname) | ||
case dns.TypeNS: | ||
m.Answer = nsFromOrigin(qname) | ||
default: | ||
// nodata | ||
m.Ns = soaFromOrigin(qname) | ||
} | ||
case "1.0.0.127.in-addr.arpa.": | ||
switch state.QType() { | ||
case dns.TypePTR: | ||
hdr := dns.RR_Header{Name: qname, Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypePTR} | ||
m.Answer = []dns.RR{&dns.PTR{Hdr: hdr, Ptr: "localhost."}} | ||
default: | ||
// nodata | ||
m.Ns = soaFromOrigin(zone) | ||
} | ||
} | ||
|
||
if len(m.Answer) == 0 && len(m.Ns) == 0 { | ||
m.Ns = soaFromOrigin(zone) | ||
m.Rcode = dns.RcodeNameError | ||
} | ||
|
||
w.WriteMsg(m) | ||
return 0, nil | ||
} | ||
|
||
// Name implements the plugin.Handler interface. | ||
func (l Local) Name() string { return "local" } | ||
|
||
func doLocalhost(state request.Request) *dns.Msg { | ||
m := new(dns.Msg) | ||
m.SetReply(state.Req) | ||
switch state.QType() { | ||
case dns.TypeA: | ||
hdr := dns.RR_Header{Name: state.QName(), Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeA} | ||
m.Answer = []dns.RR{&dns.A{Hdr: hdr, A: net.ParseIP("127.0.0.1").To4()}} | ||
case dns.TypeAAAA: | ||
hdr := dns.RR_Header{Name: state.QName(), Ttl: ttl, Class: dns.ClassINET, Rrtype: dns.TypeAAAA} | ||
m.Answer = []dns.RR{&dns.AAAA{Hdr: hdr, AAAA: net.ParseIP("::1")}} | ||
default: | ||
// nodata | ||
m.Ns = soaFromOrigin(state.QName()) | ||
} | ||
return m | ||
} | ||
|
||
const ttl = 604800 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package local | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/coredns/coredns/plugin/pkg/dnstest" | ||
"github.com/coredns/coredns/plugin/test" | ||
|
||
"github.com/miekg/dns" | ||
) | ||
|
||
var testcases = []struct { | ||
question string | ||
qtype uint16 | ||
rcode int | ||
answer dns.RR | ||
ns dns.RR | ||
}{ | ||
{"localhost.", dns.TypeA, dns.RcodeSuccess, test.A("localhost. IN A 127.0.0.1"), nil}, | ||
{"localHOst.", dns.TypeA, dns.RcodeSuccess, test.A("localHOst. IN A 127.0.0.1"), nil}, | ||
{"localhost.", dns.TypeAAAA, dns.RcodeSuccess, test.AAAA("localhost. IN AAAA ::1"), nil}, | ||
{"localhost.", dns.TypeNS, dns.RcodeSuccess, test.NS("localhost. IN NS localhost."), nil}, | ||
{"localhost.", dns.TypeSOA, dns.RcodeSuccess, test.SOA("localhost. IN SOA root.localhost. localhost. 1 0 0 0 0"), nil}, | ||
{"127.in-addr.arpa.", dns.TypeA, dns.RcodeSuccess, nil, test.SOA("127.in-addr.arpa. IN SOA root.localhost. localhost. 1 0 0 0 0")}, | ||
{"localhost.", dns.TypeMX, dns.RcodeSuccess, nil, test.SOA("localhost. IN SOA root.localhost. localhost. 1 0 0 0 0")}, | ||
{"a.localhost.", dns.TypeA, dns.RcodeNameError, nil, test.SOA("localhost. IN SOA root.localhost. localhost. 1 0 0 0 0")}, | ||
{"1.0.0.127.in-addr.arpa.", dns.TypePTR, dns.RcodeSuccess, test.PTR("1.0.0.127.in-addr.arpa. IN PTR localhost."), nil}, | ||
{"1.0.0.127.in-addr.arpa.", dns.TypeMX, dns.RcodeSuccess, nil, test.SOA("127.in-addr.arpa. IN SOA root.localhost. localhost. 1 0 0 0 0")}, | ||
{"2.0.0.127.in-addr.arpa.", dns.TypePTR, dns.RcodeNameError, nil, test.SOA("127.in-addr.arpa. IN SOA root.localhost. localhost. 1 0 0 0 0")}, | ||
{"localhost.example.net.", dns.TypeA, dns.RcodeSuccess, test.A("localhost.example.net. IN A 127.0.0.1"), nil}, | ||
{"localhost.example.net.", dns.TypeAAAA, dns.RcodeSuccess, test.AAAA("localhost.example.net IN AAAA ::1"), nil}, | ||
{"localhost.example.net.", dns.TypeSOA, dns.RcodeSuccess, nil, test.SOA("localhost.example.net. IN SOA root.localhost.example.net. localhost.example.net. 1 0 0 0 0")}, | ||
} | ||
|
||
func TestLocal(t *testing.T) { | ||
req := new(dns.Msg) | ||
l := &Local{} | ||
|
||
for i, tc := range testcases { | ||
req.SetQuestion(tc.question, tc.qtype) | ||
rec := dnstest.NewRecorder(&test.ResponseWriter{}) | ||
_, err := l.ServeDNS(context.TODO(), rec, req) | ||
|
||
if err != nil { | ||
t.Errorf("Test %d, expected no error, but got %q", i, err) | ||
continue | ||
} | ||
if rec.Msg.Rcode != tc.rcode { | ||
t.Errorf("Test %d, expected rcode %d, got %d", i, tc.rcode, rec.Msg.Rcode) | ||
} | ||
if tc.answer == nil && len(rec.Msg.Answer) > 0 { | ||
t.Errorf("Test %d, expected no answer RR, got %s", i, rec.Msg.Answer[0]) | ||
continue | ||
} | ||
if tc.ns == nil && len(rec.Msg.Ns) > 0 { | ||
t.Errorf("Test %d, expected no authority RR, got %s", i, rec.Msg.Ns[0]) | ||
continue | ||
} | ||
if tc.answer != nil { | ||
if x := tc.answer.Header().Rrtype; x != rec.Msg.Answer[0].Header().Rrtype { | ||
t.Errorf("Test %d, expected RR type %d in answer, got %d", i, x, rec.Msg.Answer[0].Header().Rrtype) | ||
} | ||
if x := tc.answer.Header().Name; x != rec.Msg.Answer[0].Header().Name { | ||
t.Errorf("Test %d, expected RR name %q in answer, got %q", i, x, rec.Msg.Answer[0].Header().Name) | ||
} | ||
} | ||
if tc.ns != nil { | ||
if x := tc.ns.Header().Rrtype; x != rec.Msg.Ns[0].Header().Rrtype { | ||
t.Errorf("Test %d, expected RR type %d in authority, got %d", i, x, rec.Msg.Ns[0].Header().Rrtype) | ||
} | ||
if x := tc.ns.Header().Name; x != rec.Msg.Ns[0].Header().Name { | ||
t.Errorf("Test %d, expected RR name %q in authority, got %q", i, x, rec.Msg.Ns[0].Header().Name) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package local | ||
|
||
import ( | ||
"github.com/coredns/coredns/plugin" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promauto" | ||
) | ||
|
||
var ( | ||
// LocalhostCount report the number of times we've seen a localhost.<domain> query. | ||
LocalhostCount = promauto.NewCounter(prometheus.CounterOpts{ | ||
Namespace: plugin.Namespace, | ||
Subsystem: "local", | ||
Name: "localhost_requests_total", | ||
Help: "Counter of localhost.<domain> requests.", | ||
}) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package local | ||
|
||
import ( | ||
"github.com/coredns/caddy" | ||
"github.com/coredns/coredns/core/dnsserver" | ||
"github.com/coredns/coredns/plugin" | ||
) | ||
|
||
func init() { plugin.Register("local", setup) } | ||
|
||
func setup(c *caddy.Controller) error { | ||
l := Local{} | ||
|
||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { | ||
l.Next = next | ||
return l | ||
}) | ||
|
||
return nil | ||
} |