Implement deprecation notice for 1.1.4 (#1833)
* Implement deprecation notice for 1.1.4 This still allows all the config to be parsed, but noops it: * -log; always set the log to stdout; no matter what. * https_google; removed from the proxy implementation. * reverse plugin: set to deprecated. * Whole of reverse can go * Remove test for deprecated plugin
This commit is contained in:
parent
b0fd575c65
commit
2758a756dd
20 changed files with 9 additions and 1306 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
_ "github.com/coredns/coredns/plugin/cache"
|
_ "github.com/coredns/coredns/plugin/cache"
|
||||||
_ "github.com/coredns/coredns/plugin/chaos"
|
_ "github.com/coredns/coredns/plugin/chaos"
|
||||||
_ "github.com/coredns/coredns/plugin/debug"
|
_ "github.com/coredns/coredns/plugin/debug"
|
||||||
|
_ "github.com/coredns/coredns/plugin/deprecated"
|
||||||
_ "github.com/coredns/coredns/plugin/dnssec"
|
_ "github.com/coredns/coredns/plugin/dnssec"
|
||||||
_ "github.com/coredns/coredns/plugin/dnstap"
|
_ "github.com/coredns/coredns/plugin/dnstap"
|
||||||
_ "github.com/coredns/coredns/plugin/erratic"
|
_ "github.com/coredns/coredns/plugin/erratic"
|
||||||
|
@ -28,7 +29,6 @@ import (
|
||||||
_ "github.com/coredns/coredns/plugin/pprof"
|
_ "github.com/coredns/coredns/plugin/pprof"
|
||||||
_ "github.com/coredns/coredns/plugin/proxy"
|
_ "github.com/coredns/coredns/plugin/proxy"
|
||||||
_ "github.com/coredns/coredns/plugin/reload"
|
_ "github.com/coredns/coredns/plugin/reload"
|
||||||
_ "github.com/coredns/coredns/plugin/reverse"
|
|
||||||
_ "github.com/coredns/coredns/plugin/rewrite"
|
_ "github.com/coredns/coredns/plugin/rewrite"
|
||||||
_ "github.com/coredns/coredns/plugin/root"
|
_ "github.com/coredns/coredns/plugin/root"
|
||||||
_ "github.com/coredns/coredns/plugin/route53"
|
_ "github.com/coredns/coredns/plugin/route53"
|
||||||
|
|
|
@ -29,7 +29,7 @@ func init() {
|
||||||
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
|
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
|
||||||
flag.BoolVar(&version, "version", false, "Show version")
|
flag.BoolVar(&version, "version", false, "Show version")
|
||||||
flag.BoolVar(&dnsserver.Quiet, "quiet", false, "Quiet mode (no initialization output)")
|
flag.BoolVar(&dnsserver.Quiet, "quiet", false, "Quiet mode (no initialization output)")
|
||||||
flag.BoolVar(&logfile, "log", false, "Log to standard output")
|
flag.BoolVar(&logfile, "log", false, "Log to standard output") // noop for 1.1.4; drop in 1.2.0.
|
||||||
|
|
||||||
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
|
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
|
||||||
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
|
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
|
||||||
|
@ -62,10 +62,7 @@ func Run() {
|
||||||
mustLogFatal(fmt.Errorf("extra command line arguments: %s", flag.Args()))
|
mustLogFatal(fmt.Errorf("extra command line arguments: %s", flag.Args()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up process log before anything bad happens
|
log.SetOutput(os.Stdout)
|
||||||
if logfile {
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
}
|
|
||||||
log.SetFlags(log.LstdFlags)
|
log.SetFlags(log.LstdFlags)
|
||||||
|
|
||||||
if version {
|
if version {
|
||||||
|
|
|
@ -38,7 +38,7 @@ cache:cache
|
||||||
rewrite:rewrite
|
rewrite:rewrite
|
||||||
dnssec:dnssec
|
dnssec:dnssec
|
||||||
autopath:autopath
|
autopath:autopath
|
||||||
reverse:reverse
|
reverse:deprecated
|
||||||
template:template
|
template:template
|
||||||
hosts:hosts
|
hosts:hosts
|
||||||
route53:route53
|
route53:route53
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// removed has the names of the plugins that need to error on startup.
|
// removed has the names of the plugins that need to error on startup.
|
||||||
var removed = []string{"startup", "shutdown"}
|
var removed = []string{"reverse"}
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
|
@ -31,7 +31,7 @@ proxy FROM TO... {
|
||||||
health_check PATH:PORT [DURATION]
|
health_check PATH:PORT [DURATION]
|
||||||
except IGNORED_NAMES...
|
except IGNORED_NAMES...
|
||||||
spray
|
spray
|
||||||
protocol [dns [force_tcp]|https_google [bootstrap ADDRESS...]|grpc [insecure|CACERT|KEY CERT|KEY CERT CACERT]]
|
protocol [dns [force_tcp]|grpc [insecure|CACERT|KEY CERT|KEY CERT CACERT]]
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -54,8 +54,7 @@ proxy FROM TO... {
|
||||||
* `spray` when all backends are unhealthy, randomly pick one to send the traffic to. (This is
|
* `spray` when all backends are unhealthy, randomly pick one to send the traffic to. (This is
|
||||||
a failsafe.)
|
a failsafe.)
|
||||||
* `protocol` specifies what protocol to use to speak to an upstream, `dns` (the default) is plain
|
* `protocol` specifies what protocol to use to speak to an upstream, `dns` (the default) is plain
|
||||||
old DNS, and `https_google` uses `https://dns.google.com` and speaks a JSON DNS dialect. Note when
|
old DNS. The `grpc` option will talk to a server that has implemented
|
||||||
using this **TO** will be ignored. The `grpc` option will talk to a server that has implemented
|
|
||||||
the [DnsService](https://github.com/coredns/coredns/blob/master/pb/dns.proto).
|
the [DnsService](https://github.com/coredns/coredns/blob/master/pb/dns.proto).
|
||||||
|
|
||||||
## Policies
|
## Policies
|
||||||
|
@ -73,10 +72,6 @@ available. This is to preeempt the case where the healthchecking (as a mechanism
|
||||||
|
|
||||||
## Upstream Protocols
|
## Upstream Protocols
|
||||||
|
|
||||||
Currently `protocol` supports `dns` (i.e., standard DNS over UDP/TCP) and `https_google` (JSON
|
|
||||||
payload over HTTPS). Note that with `https_google` the entire transport is encrypted. Only *you* and
|
|
||||||
*Google* can see your DNS activity.
|
|
||||||
|
|
||||||
`dns`
|
`dns`
|
||||||
: uses the standard DNS exchange. You can pass `force_tcp` to make sure that the proxied connection is performed
|
: uses the standard DNS exchange. You can pass `force_tcp` to make sure that the proxied connection is performed
|
||||||
over TCP, regardless of the inbound request's protocol.
|
over TCP, regardless of the inbound request's protocol.
|
||||||
|
@ -92,13 +87,6 @@ payload over HTTPS). Note that with `https_google` the entire transport is encry
|
||||||
* **KEY** **CERT** **CACERT** - Client authentication is used with the specified key/cert pair. The
|
* **KEY** **CERT** **CACERT** - Client authentication is used with the specified key/cert pair. The
|
||||||
server certificate is verified using the **CACERT** file.
|
server certificate is verified using the **CACERT** file.
|
||||||
|
|
||||||
`https_google`
|
|
||||||
: bootstrap **ADDRESS...** is used to (re-)resolve `dns.google.com`.
|
|
||||||
|
|
||||||
This happens every 300s. If not specified the default is used: 8.8.8.8:53/8.8.4.4:53.
|
|
||||||
Note that **TO** is *ignored* when `https_google` is used, as its upstream is defined as `dns.google.com`.
|
|
||||||
|
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
If monitoring is enabled (via the *prometheus* directive) then the following metric is exported:
|
If monitoring is enabled (via the *prometheus* directive) then the following metric is exported:
|
||||||
|
@ -108,7 +96,7 @@ If monitoring is enabled (via the *prometheus* directive) then the following met
|
||||||
* `coredns_proxy_request_count_total{server, proto, proto_proxy, family, to}` - query count per
|
* `coredns_proxy_request_count_total{server, proto, proto_proxy, family, to}` - query count per
|
||||||
upstream.
|
upstream.
|
||||||
|
|
||||||
Where `proxy_proto` is the protocol used (`dns`, `grpc`, or `https_google`) and `to` is **TO**
|
Where `proxy_proto` is the protocol used (`dns` or `grpc`) and `to` is **TO**
|
||||||
specified in the config, `proto` is the protocol used by the incoming query ("tcp" or "udp"), family
|
specified in the config, `proto` is the protocol used by the incoming query ("tcp" or "udp"), family
|
||||||
the transport family ("1" for IPv4, and "2" for IPv6). `Server` is the server responsible for the
|
the transport family ("1" for IPv4, and "2" for IPv6). `Server` is the server responsible for the
|
||||||
request (and metric). See the documention in the metrics plugin.
|
request (and metric). See the documention in the metrics plugin.
|
||||||
|
@ -169,34 +157,3 @@ Proxy everything except `example.org` using the host's `resolv.conf`'s nameserve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Proxy all requests within `example.org` to Google's `dns.google.com`.
|
|
||||||
|
|
||||||
~~~ corefile
|
|
||||||
. {
|
|
||||||
proxy example.org 1.2.3.4:53 {
|
|
||||||
protocol https_google
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Proxy everything with HTTPS to `dns.google.com`, except `example.org`. Then have another proxy in
|
|
||||||
another stanza that uses plain DNS to resolve names under `example.org`.
|
|
||||||
|
|
||||||
~~~ corefile
|
|
||||||
. {
|
|
||||||
proxy . 1.2.3.4:53 {
|
|
||||||
except example.org
|
|
||||||
protocol https_google
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
example.org {
|
|
||||||
proxy . 8.8.8.8:53
|
|
||||||
}
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Bugs
|
|
||||||
|
|
||||||
When using the `google_https` protocol the health checking will health check the wrong endpoint.
|
|
||||||
See <https://github.com/coredns/coredns/issues/1202> for some background.
|
|
||||||
|
|
|
@ -47,7 +47,6 @@ func TestDnstap(t *testing.T) {
|
||||||
tapq.SocketProto = tap.SocketProtocol_TCP
|
tapq.SocketProto = tap.SocketProtocol_TCP
|
||||||
tapr.SocketProto = tap.SocketProtocol_TCP
|
tapr.SocketProto = tap.SocketProtocol_TCP
|
||||||
testCase(t, newDNSExWithOption(Options{ForceTCP: true}), q, r, tapq, tapr)
|
testCase(t, newDNSExWithOption(Options{ForceTCP: true}), q, r, tapq, tapr)
|
||||||
testCase(t, newGoogle("", []string{"8.8.8.8:53", "8.8.4.4:53"}), q, r, tapq, tapr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoDnstap(t *testing.T) {
|
func TestNoDnstap(t *testing.T) {
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/healthcheck"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type google struct {
|
|
||||||
client *http.Client
|
|
||||||
|
|
||||||
endpoint string // Name to resolve via 'bootstrapProxy'
|
|
||||||
|
|
||||||
bootstrapProxy Proxy
|
|
||||||
quit chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGoogle(endpoint string, bootstrap []string) *google {
|
|
||||||
// TODO(miek): Deprecate after 1.1.3 (that would be 1.2.0)
|
|
||||||
log.Warning("https_google will be deprecated in the next release")
|
|
||||||
|
|
||||||
if endpoint == "" {
|
|
||||||
endpoint = ghost
|
|
||||||
}
|
|
||||||
tls := &tls.Config{ServerName: endpoint}
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: time.Second * defaultTimeout,
|
|
||||||
Transport: &http.Transport{TLSClientConfig: tls},
|
|
||||||
}
|
|
||||||
|
|
||||||
boot := NewLookup(bootstrap)
|
|
||||||
|
|
||||||
return &google{client: client, endpoint: dns.Fqdn(endpoint), bootstrapProxy: boot, quit: make(chan bool)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *google) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) {
|
|
||||||
v := url.Values{}
|
|
||||||
|
|
||||||
v.Set("name", state.Name())
|
|
||||||
v.Set("type", fmt.Sprintf("%d", state.QType()))
|
|
||||||
|
|
||||||
buf, backendErr := g.exchangeJSON(addr, v.Encode())
|
|
||||||
|
|
||||||
if backendErr == nil {
|
|
||||||
gm := new(googleMsg)
|
|
||||||
if err := json.Unmarshal(buf, gm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := toMsg(gm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Id = state.Req.Id
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warningf("Failed to connect to HTTPS backend %q: %s", g.endpoint, backendErr)
|
|
||||||
return nil, backendErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *google) exchangeJSON(addr, json string) ([]byte, error) {
|
|
||||||
url := "https://" + addr + "/resolve?" + json
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Host = g.endpoint // TODO(miek): works with the extra dot at the end?
|
|
||||||
|
|
||||||
resp, err := g.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("failed to get 200 status code, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *google) Transport() string { return "tcp" }
|
|
||||||
func (g *google) Protocol() string { return "https_google" }
|
|
||||||
|
|
||||||
func (g *google) OnShutdown(p *Proxy) error {
|
|
||||||
g.quit <- true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *google) OnStartup(p *Proxy) error {
|
|
||||||
// We fake a state because normally the proxy is called after we already got a incoming query.
|
|
||||||
// This is a non-edns0, udp request to g.endpoint.
|
|
||||||
req := new(dns.Msg)
|
|
||||||
req.SetQuestion(g.endpoint, dns.TypeA)
|
|
||||||
state := request.Request{W: new(fakeBootWriter), Req: req}
|
|
||||||
|
|
||||||
if len(*p.Upstreams) == 0 {
|
|
||||||
return fmt.Errorf("no upstreams defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUpstream := (*p.Upstreams)[0]
|
|
||||||
|
|
||||||
new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("Failed to bootstrap A records %q: %s", g.endpoint, err)
|
|
||||||
} else {
|
|
||||||
addrs, err1 := extractAnswer(new)
|
|
||||||
if err1 != nil {
|
|
||||||
log.Warningf("Failed to bootstrap A records %q: %s", g.endpoint, err1)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
up := newUpstream(addrs, oldUpstream.(*staticUpstream))
|
|
||||||
p.Upstreams = &[]Upstream{up}
|
|
||||||
|
|
||||||
log.Infof("Bootstrapping A records %q found: %v", g.endpoint, addrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
tick := time.NewTicker(120 * time.Second)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-tick.C:
|
|
||||||
|
|
||||||
log.Infof("Resolving A records %q", g.endpoint)
|
|
||||||
|
|
||||||
new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("Failed to resolve A records %q: %s", g.endpoint, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err1 := extractAnswer(new)
|
|
||||||
if err1 != nil {
|
|
||||||
log.Warningf("Failed to resolve A records %q: %s", g.endpoint, err1)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
up := newUpstream(addrs, oldUpstream.(*staticUpstream))
|
|
||||||
p.Upstreams = &[]Upstream{up}
|
|
||||||
|
|
||||||
log.Infof("Resolving A records %q found: %v", g.endpoint, addrs)
|
|
||||||
|
|
||||||
case <-g.quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractAnswer(m *dns.Msg) ([]string, error) {
|
|
||||||
if len(m.Answer) == 0 {
|
|
||||||
return nil, fmt.Errorf("no answer section in response")
|
|
||||||
}
|
|
||||||
ret := []string{}
|
|
||||||
for _, an := range m.Answer {
|
|
||||||
if a, ok := an.(*dns.A); ok {
|
|
||||||
ret = append(ret, net.JoinHostPort(a.A.String(), "443"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(ret) > 0 {
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("no address records in answer section")
|
|
||||||
}
|
|
||||||
|
|
||||||
// newUpstream returns an upstream initialized with hosts.
|
|
||||||
func newUpstream(hosts []string, old *staticUpstream) Upstream {
|
|
||||||
upstream := &staticUpstream{
|
|
||||||
from: old.from,
|
|
||||||
HealthCheck: healthcheck.HealthCheck{
|
|
||||||
FailTimeout: 5 * time.Second,
|
|
||||||
MaxFails: 3,
|
|
||||||
},
|
|
||||||
ex: old.ex,
|
|
||||||
IgnoredSubDomains: old.IgnoredSubDomains,
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts))
|
|
||||||
for i, host := range hosts {
|
|
||||||
uh := &healthcheck.UpstreamHost{
|
|
||||||
Name: host,
|
|
||||||
Conns: 0,
|
|
||||||
Fails: 0,
|
|
||||||
FailTimeout: upstream.FailTimeout,
|
|
||||||
CheckDown: checkDownFunc(upstream),
|
|
||||||
}
|
|
||||||
upstream.Hosts[i] = uh
|
|
||||||
}
|
|
||||||
return upstream
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Default endpoint for this service.
|
|
||||||
ghost = "dns.google.com."
|
|
||||||
)
|
|
|
@ -1,89 +0,0 @@
|
||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// toMsg converts a googleMsg into the dns message.
|
|
||||||
func toMsg(g *googleMsg) (*dns.Msg, error) {
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.Response = true
|
|
||||||
m.Rcode = g.Status
|
|
||||||
m.Truncated = g.TC
|
|
||||||
m.RecursionDesired = g.RD
|
|
||||||
m.RecursionAvailable = g.RA
|
|
||||||
m.AuthenticatedData = g.AD
|
|
||||||
m.CheckingDisabled = g.CD
|
|
||||||
|
|
||||||
m.Question = make([]dns.Question, 1)
|
|
||||||
m.Answer = make([]dns.RR, len(g.Answer))
|
|
||||||
m.Ns = make([]dns.RR, len(g.Authority))
|
|
||||||
m.Extra = make([]dns.RR, len(g.Additional))
|
|
||||||
|
|
||||||
m.Question[0] = dns.Question{Name: g.Question[0].Name, Qtype: g.Question[0].Type, Qclass: dns.ClassINET}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for i := 0; i < len(m.Answer); i++ {
|
|
||||||
m.Answer[i], err = toRR(g.Answer[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(m.Ns); i++ {
|
|
||||||
m.Ns[i], err = toRR(g.Authority[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < len(m.Extra); i++ {
|
|
||||||
m.Extra[i], err = toRR(g.Additional[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// toRR transforms a "google" RR to a dns.RR.
|
|
||||||
func toRR(g googleRR) (dns.RR, error) {
|
|
||||||
typ, ok := dns.TypeToString[g.Type]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to convert type %q", g.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
str := fmt.Sprintf("%s %d %s %s", g.Name, g.TTL, typ, g.Data)
|
|
||||||
rr, err := dns.NewRR(str)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse %q: %s", str, err)
|
|
||||||
}
|
|
||||||
return rr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// googleRR represents a dns.RR in another form.
|
|
||||||
type googleRR struct {
|
|
||||||
Name string
|
|
||||||
Type uint16
|
|
||||||
TTL uint32
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
// googleMsg is a JSON representation of the dns.Msg.
|
|
||||||
type googleMsg struct {
|
|
||||||
Status int
|
|
||||||
TC bool
|
|
||||||
RD bool
|
|
||||||
RA bool
|
|
||||||
AD bool
|
|
||||||
CD bool
|
|
||||||
Question []struct {
|
|
||||||
Name string
|
|
||||||
Type uint16
|
|
||||||
}
|
|
||||||
Answer []googleRR
|
|
||||||
Authority []googleRR
|
|
||||||
Additional []googleRR
|
|
||||||
Comment string
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package proxy
|
|
||||||
|
|
||||||
// TODO(miek):
|
|
||||||
// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT
|
|
||||||
// Test connecting to a a bad host.
|
|
|
@ -129,14 +129,6 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If protocol is https_google we do the health checks wrong, i.e. we're healthchecking the wrong
|
|
||||||
// endpoint, hence the health check code below should not be executed. See issue #1202.
|
|
||||||
// This is an ugly hack and the thing requires a rethink. Possibly in conjunction with moving
|
|
||||||
// to the *forward* plugin.
|
|
||||||
if upstream.Exchanger().Protocol() == "https_google" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := host.FailTimeout
|
timeout := host.FailTimeout
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = defaultFailTimeout
|
timeout = defaultFailTimeout
|
||||||
|
|
|
@ -165,12 +165,7 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
||||||
u.ex = newDNSEx()
|
u.ex = newDNSEx()
|
||||||
}
|
}
|
||||||
case "https_google":
|
case "https_google":
|
||||||
boot := []string{"8.8.8.8:53", "8.8.4.4:53"}
|
// allow the config, but make noop
|
||||||
if len(encArgs) > 2 && encArgs[1] == "bootstrap" {
|
|
||||||
boot = encArgs[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
u.ex = newGoogle("", boot) // "" for default in google.go
|
|
||||||
case "grpc":
|
case "grpc":
|
||||||
if len(encArgs) == 2 && encArgs[1] == "insecure" {
|
if len(encArgs) == 2 && encArgs[1] == "insecure" {
|
||||||
u.ex = newGrpcClient(nil, u)
|
u.ex = newGrpcClient(nil, u)
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
reviewers:
|
|
||||||
approvers:
|
|
|
@ -1,96 +0,0 @@
|
||||||
# reverse
|
|
||||||
|
|
||||||
## Name
|
|
||||||
|
|
||||||
*reverse* - allows for dynamic responses to PTR and the related A/AAAA requests.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
If a request matches a regular expression (see Template Syntax below) this plugin will generate a
|
|
||||||
response. This is only done for "address" records (PTR, A and AAAA).
|
|
||||||
|
|
||||||
## Syntax
|
|
||||||
|
|
||||||
~~~
|
|
||||||
reverse NETWORK... {
|
|
||||||
hostname TEMPLATE
|
|
||||||
[ttl TTL]
|
|
||||||
[fallthrough [ZONES...]]
|
|
||||||
[wildcard]
|
|
||||||
~~~
|
|
||||||
|
|
||||||
* **NETWORK** one or more CIDR formatted networks to respond on.
|
|
||||||
* `hostname` injects the IP and zone to a template for the hostname. Defaults to "ip-{IP}.{zone[1]}". See below for template.
|
|
||||||
* `ttl` defaults to 60
|
|
||||||
* `fallthrough` if zone matches and no record can be generated, pass request to the next plugin.
|
|
||||||
If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin
|
|
||||||
is authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only
|
|
||||||
queries for those zones will be subject to fallthrough.
|
|
||||||
* `wildcard` allows matches to catch all subdomains as well.
|
|
||||||
|
|
||||||
### Template Syntax
|
|
||||||
|
|
||||||
The template for the hostname is used for generating the PTR for a reverse lookup and matching the
|
|
||||||
forward lookup back to an IP.
|
|
||||||
|
|
||||||
#### `{ip}`
|
|
||||||
|
|
||||||
The `{ip}` symbol is **required** to make reverse work.
|
|
||||||
For IPv4 lookups the IP is directly extracted
|
|
||||||
With IPv6 lookups the ":" is removed, and any zero ranged are expanded, e.g.,
|
|
||||||
"ffff::ffff" results in "ffff000000000000000000000000ffff"
|
|
||||||
|
|
||||||
#### `{zone[i]}`
|
|
||||||
|
|
||||||
The `{zone[i]}` symbol is **optional** and can be replaced by a fixed (zone) string.
|
|
||||||
The zone will be matched by the zones listed in *this* configuration stanza.
|
|
||||||
`i` needs to be replaced with the index of the configured listener zones, starting with 1.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
~~~ corefile
|
|
||||||
arpa compute.internal {
|
|
||||||
# proxy unmatched requests
|
|
||||||
proxy . 8.8.8.8
|
|
||||||
|
|
||||||
# answer requests for IPs in this network
|
|
||||||
# PTR 1.0.32.10.in-addr.arpa. 3600 ip-10.0.32.1.compute.internal.
|
|
||||||
# A ip-10.0.32.1.compute.internal. 3600 10.0.32.1
|
|
||||||
# v6 is also possible
|
|
||||||
# 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.1.0.d.f.ip6.arpa. 3600 ip-fd010000000000000000000000000001.compute.internal.
|
|
||||||
# AAAA ip-fd010000000000000000000000000001.compute.internal. 3600 fd01::1
|
|
||||||
reverse 10.32.0.0/16 fd01::/16 {
|
|
||||||
# template of the ip injection to hostname, zone resolved to compute.internal.
|
|
||||||
hostname ip-{ip}.{zone[2]}
|
|
||||||
|
|
||||||
ttl 3600
|
|
||||||
|
|
||||||
# Forward unanswered or unmatched requests to proxy
|
|
||||||
# without this flag, requesting A/AAAA records on compute.internal. will end here.
|
|
||||||
fallthrough
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~~~
|
|
||||||
|
|
||||||
|
|
||||||
~~~ corefile
|
|
||||||
32.10.in-addr.arpa.arpa arpa.company.org {
|
|
||||||
|
|
||||||
reverse 10.32.0.0/16 {
|
|
||||||
# template of the ip injection to hostname, zone resolved to arpa.company.org.
|
|
||||||
hostname "ip-{ip}.v4.{zone[2]}"
|
|
||||||
|
|
||||||
ttl 3600
|
|
||||||
|
|
||||||
# fallthrough is not required, v4.arpa.company.org. will be only answered here
|
|
||||||
}
|
|
||||||
|
|
||||||
# cidr closer to the ip wins, so we can overwrite the "default"
|
|
||||||
reverse 10.32.2.0/24 {
|
|
||||||
# its also possible to set fix domain suffix
|
|
||||||
hostname ip-{ip}.fix.arpa.company.org.
|
|
||||||
|
|
||||||
ttl 3600
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~~~
|
|
|
@ -1,87 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type network struct {
|
|
||||||
IPnet *net.IPNet
|
|
||||||
Zone string // forward lookup zone
|
|
||||||
Template string
|
|
||||||
TTL uint32
|
|
||||||
RegexMatchIP *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: we might want to get rid of these regexes.
|
|
||||||
const hexDigit = "0123456789abcdef"
|
|
||||||
const templateNameIP = "{ip}"
|
|
||||||
const regexMatchV4 = "((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))"
|
|
||||||
const regexMatchV6 = "([0-9a-fA-F]{32})"
|
|
||||||
|
|
||||||
// hostnameToIP converts the hostname back to an ip, based on the template
|
|
||||||
// returns nil if there is no IP found.
|
|
||||||
func (network *network) hostnameToIP(rname string) net.IP {
|
|
||||||
var matchedIP net.IP
|
|
||||||
|
|
||||||
match := network.RegexMatchIP.FindStringSubmatch(rname)
|
|
||||||
if len(match) != 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if network.IPnet.IP.To4() != nil {
|
|
||||||
matchedIP = net.ParseIP(match[1])
|
|
||||||
} else {
|
|
||||||
// TODO: can probably just allocate a []byte and use that.
|
|
||||||
var buf bytes.Buffer
|
|
||||||
// convert back to an valid ipv6 string with colons
|
|
||||||
for i := 0; i < 8*4; i += 4 {
|
|
||||||
buf.WriteString(match[1][i : i+4])
|
|
||||||
if i < 28 {
|
|
||||||
buf.WriteString(":")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matchedIP = net.ParseIP(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// No valid ip or it does not belong to this network
|
|
||||||
if matchedIP == nil || !network.IPnet.Contains(matchedIP) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedIP
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipToHostname converts an IP to an DNS compatible hostname and injects it into the template.domain.
|
|
||||||
func (network *network) ipToHostname(ip net.IP) (name string) {
|
|
||||||
if ipv4 := ip.To4(); ipv4 != nil {
|
|
||||||
// replace . to -
|
|
||||||
name = ipv4.String()
|
|
||||||
} else {
|
|
||||||
// assume v6
|
|
||||||
// ensure zeros are present in string
|
|
||||||
buf := make([]byte, 0, len(ip)*4)
|
|
||||||
for i := 0; i < len(ip); i++ {
|
|
||||||
v := ip[i]
|
|
||||||
buf = append(buf, hexDigit[v>>4])
|
|
||||||
buf = append(buf, hexDigit[v&0xF])
|
|
||||||
}
|
|
||||||
name = string(buf)
|
|
||||||
}
|
|
||||||
// inject the converted ip into the fqdn template
|
|
||||||
return strings.Replace(network.Template, templateNameIP, name, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type networks []network
|
|
||||||
|
|
||||||
func (n networks) Len() int { return len(n) }
|
|
||||||
func (n networks) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
|
||||||
|
|
||||||
// cidr closer to the ip wins (by netmask)
|
|
||||||
func (n networks) Less(i, j int) bool {
|
|
||||||
isize, _ := n[i].IPnet.Mask.Size()
|
|
||||||
jsize, _ := n[j].IPnet.Mask.Size()
|
|
||||||
return isize > jsize
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test converting from hostname to IP and back again to hostname
|
|
||||||
func TestNetworkConversion(t *testing.T) {
|
|
||||||
|
|
||||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
|
||||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
|
||||||
|
|
||||||
regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$")
|
|
||||||
regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$")
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
network network
|
|
||||||
resultHost string
|
|
||||||
resultIP net.IP
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net4,
|
|
||||||
Template: "dns-{ip}.domain.internal.",
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
},
|
|
||||||
"dns-10.1.1.23.domain.internal.",
|
|
||||||
net.ParseIP("10.1.1.23"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net6,
|
|
||||||
Template: "dns-{ip}.domain.internal.",
|
|
||||||
RegexMatchIP: regexIP6,
|
|
||||||
},
|
|
||||||
"dns-fd01000000000000000000000000a32f.domain.internal.",
|
|
||||||
net.ParseIP("fd01::a32f"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
resultIP := test.network.hostnameToIP(test.resultHost)
|
|
||||||
if !reflect.DeepEqual(test.resultIP, resultIP) {
|
|
||||||
t.Fatalf("Test %d expected %v, got %v", i, test.resultIP, resultIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
resultHost := test.network.ipToHostname(test.resultIP)
|
|
||||||
if !reflect.DeepEqual(test.resultHost, resultHost) {
|
|
||||||
t.Fatalf("Test %d expected %v, got %v", i, test.resultHost, resultHost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetworkHostnameToIP(t *testing.T) {
|
|
||||||
|
|
||||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
|
||||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
|
||||||
|
|
||||||
regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$")
|
|
||||||
regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$")
|
|
||||||
|
|
||||||
// Test regex does NOT match
|
|
||||||
// All this test should return nil
|
|
||||||
testsNil := []struct {
|
|
||||||
network network
|
|
||||||
hostname string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net4,
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
},
|
|
||||||
// domain does not match
|
|
||||||
"dns-10.1.1.23.domain.internals.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net4,
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
},
|
|
||||||
// IP does match / contain in subnet
|
|
||||||
"dns-200.1.1.23.domain.internals.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net4,
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
},
|
|
||||||
// template does not match
|
|
||||||
"dns-10.1.1.23-x.domain.internal.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net4,
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
},
|
|
||||||
// template does not match
|
|
||||||
"IP-dns-10.1.1.23.domain.internal.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net6,
|
|
||||||
RegexMatchIP: regexIP6,
|
|
||||||
},
|
|
||||||
// template does not match
|
|
||||||
"dnx-fd01000000000000000000000000a32f.domain.internal.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net6,
|
|
||||||
RegexMatchIP: regexIP6,
|
|
||||||
},
|
|
||||||
// no valid v6 (missing one 0, only 31 chars)
|
|
||||||
"dns-fd0100000000000000000000000a32f.domain.internal.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
network{
|
|
||||||
IPnet: net6,
|
|
||||||
RegexMatchIP: regexIP6,
|
|
||||||
},
|
|
||||||
// IP does match / contain in subnet
|
|
||||||
"dns-ed01000000000000000000000000a32f.domain.internal.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range testsNil {
|
|
||||||
resultIP := test.network.hostnameToIP(test.hostname)
|
|
||||||
if resultIP != nil {
|
|
||||||
t.Fatalf("Test %d expected nil, got %v", i, resultIP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reverse provides dynamic reverse DNS and the related forward RR.
|
|
||||||
type Reverse struct {
|
|
||||||
Next plugin.Handler
|
|
||||||
Networks networks
|
|
||||||
|
|
||||||
Fall fall.F
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeDNS implements the plugin.Handler interface.
|
|
||||||
func (re Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
||||||
var rr dns.RR
|
|
||||||
|
|
||||||
state := request.Request{W: w, Req: r}
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetReply(r)
|
|
||||||
m.Authoritative, m.RecursionAvailable = true, true
|
|
||||||
|
|
||||||
switch state.QType() {
|
|
||||||
case dns.TypePTR:
|
|
||||||
address := dnsutil.ExtractAddressFromReverse(state.Name())
|
|
||||||
|
|
||||||
if address == "" {
|
|
||||||
// Not an reverse lookup, but can still be an pointer for an domain
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(address)
|
|
||||||
// loop through the configured networks
|
|
||||||
for _, n := range re.Networks {
|
|
||||||
if n.IPnet.Contains(ip) {
|
|
||||||
rr = &dns.PTR{
|
|
||||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: n.TTL},
|
|
||||||
Ptr: n.ipToHostname(ip),
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case dns.TypeA:
|
|
||||||
for _, n := range re.Networks {
|
|
||||||
if dns.IsSubDomain(n.Zone, state.Name()) {
|
|
||||||
|
|
||||||
// skip if requesting an v4 address and network is not v4
|
|
||||||
if n.IPnet.IP.To4() == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result := n.hostnameToIP(state.Name())
|
|
||||||
if result != nil {
|
|
||||||
rr = &dns.A{
|
|
||||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: n.TTL},
|
|
||||||
A: result,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case dns.TypeAAAA:
|
|
||||||
for _, n := range re.Networks {
|
|
||||||
if dns.IsSubDomain(n.Zone, state.Name()) {
|
|
||||||
|
|
||||||
// Do not use To16 which tries to make v4 in v6
|
|
||||||
if n.IPnet.IP.To4() != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result := n.hostnameToIP(state.Name())
|
|
||||||
if result != nil {
|
|
||||||
rr = &dns.AAAA{
|
|
||||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: n.TTL},
|
|
||||||
AAAA: result,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if rr != nil {
|
|
||||||
m.Answer = append(m.Answer, rr)
|
|
||||||
state.SizeAndDo(m)
|
|
||||||
w.WriteMsg(m)
|
|
||||||
return dns.RcodeSuccess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if re.Fall.Through(state.Name()) {
|
|
||||||
return plugin.NextOrFailure(re.Name(), re.Next, ctx, w, r)
|
|
||||||
}
|
|
||||||
return dns.RcodeServerFailure, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements the Handler interface.
|
|
||||||
func (re Reverse) Name() string { return "reverse" }
|
|
|
@ -1,70 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReverse(t *testing.T) {
|
|
||||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
|
||||||
regexIP4, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.example\\.org\\.$")
|
|
||||||
|
|
||||||
em := Reverse{
|
|
||||||
Networks: networks{network{
|
|
||||||
IPnet: net4,
|
|
||||||
Zone: "example.org.",
|
|
||||||
Template: "ip-{ip}.example.org.",
|
|
||||||
RegexMatchIP: regexIP4,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
next plugin.Handler
|
|
||||||
qname string
|
|
||||||
qtype uint16
|
|
||||||
expectedCode int
|
|
||||||
expectedReply string
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
next: test.NextHandler(dns.RcodeSuccess, nil),
|
|
||||||
qname: "test.ip-10.1.1.2.example.org.",
|
|
||||||
expectedCode: dns.RcodeSuccess,
|
|
||||||
expectedReply: "10.1.1.2",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
for i, tr := range tests {
|
|
||||||
req := new(dns.Msg)
|
|
||||||
|
|
||||||
tr.qtype = dns.TypeA
|
|
||||||
req.SetQuestion(tr.qname, tr.qtype)
|
|
||||||
|
|
||||||
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
||||||
code, err := em.ServeDNS(ctx, rec, req)
|
|
||||||
|
|
||||||
if err != tr.expectedErr {
|
|
||||||
t.Errorf("Test %d: Expected error %v, but got %v", i, tr.expectedErr, err)
|
|
||||||
}
|
|
||||||
if code != int(tr.expectedCode) {
|
|
||||||
t.Errorf("Test %d: Expected status code %d, but got %d", i, tr.expectedCode, code)
|
|
||||||
}
|
|
||||||
if tr.expectedReply != "" {
|
|
||||||
answer := rec.Msg.Answer[0].(*dns.A).A.String()
|
|
||||||
if answer != tr.expectedReply {
|
|
||||||
t.Errorf("Test %d: Expected answer %s, but got %s", i, tr.expectedReply, answer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
|
||||||
"github.com/coredns/coredns/plugin"
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = clog.NewWithPlugin("reverse")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
caddy.RegisterPlugin("reverse", caddy.Plugin{
|
|
||||||
ServerType: "dns",
|
|
||||||
Action: setup,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
|
||||||
networks, fall, err := reverseParse(c)
|
|
||||||
if err != nil {
|
|
||||||
return plugin.Error("reverse", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
|
||||||
return Reverse{Next: next, Networks: networks, Fall: fall}
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO(miek): Deprecate after 1.1.3 (that would be 1.2.0)
|
|
||||||
log.Warning("reverse will be deprecated in the next release")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func reverseParse(c *caddy.Controller) (nets networks, f fall.F, err error) {
|
|
||||||
zones := make([]string, len(c.ServerBlockKeys))
|
|
||||||
wildcard := false
|
|
||||||
|
|
||||||
// We copy from the serverblock, these contains Hosts.
|
|
||||||
for i, str := range c.ServerBlockKeys {
|
|
||||||
zones[i] = plugin.Host(str).Normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
var cidrs []*net.IPNet
|
|
||||||
|
|
||||||
// parse all networks
|
|
||||||
for _, cidr := range c.RemainingArgs() {
|
|
||||||
if cidr == "{" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_, ipnet, err := net.ParseCIDR(cidr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, f, c.Errf("network needs to be CIDR formatted: %q\n", cidr)
|
|
||||||
}
|
|
||||||
cidrs = append(cidrs, ipnet)
|
|
||||||
}
|
|
||||||
if len(cidrs) == 0 {
|
|
||||||
return nil, f, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// set defaults
|
|
||||||
var (
|
|
||||||
template = "ip-" + templateNameIP + ".{zone[1]}"
|
|
||||||
ttl = 60
|
|
||||||
)
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "hostname":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, f, c.ArgErr()
|
|
||||||
}
|
|
||||||
template = c.Val()
|
|
||||||
|
|
||||||
case "ttl":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return nil, f, c.ArgErr()
|
|
||||||
}
|
|
||||||
ttl, err = strconv.Atoi(c.Val())
|
|
||||||
if err != nil {
|
|
||||||
return nil, f, err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "wildcard":
|
|
||||||
wildcard = true
|
|
||||||
|
|
||||||
case "fallthrough":
|
|
||||||
f.SetZonesFromArgs(c.RemainingArgs())
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, f, c.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare template
|
|
||||||
// replace {zone[index]} by the listen zone/domain of this config block
|
|
||||||
for i, zone := range zones {
|
|
||||||
// TODO: we should be smarter about actually replacing this. This works, but silently allows "zone[-1]"
|
|
||||||
// for instance.
|
|
||||||
template = strings.Replace(template, "{zone["+strconv.Itoa(i+1)+"]}", zone, 1)
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(template, ".") {
|
|
||||||
template += "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract zone from template
|
|
||||||
templateZone := strings.SplitAfterN(template, ".", 2)
|
|
||||||
if len(templateZone) != 2 || templateZone[1] == "" {
|
|
||||||
return nil, f, c.Errf("cannot find domain in template '%v'", template)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create for each configured network in this stanza
|
|
||||||
for _, ipnet := range cidrs {
|
|
||||||
// precompile regex for hostname to ip matching
|
|
||||||
regexIP := regexMatchV4
|
|
||||||
if ipnet.IP.To4() == nil {
|
|
||||||
regexIP = regexMatchV6
|
|
||||||
}
|
|
||||||
prefix := "^"
|
|
||||||
if wildcard {
|
|
||||||
prefix += ".*"
|
|
||||||
}
|
|
||||||
regex, err := regexp.Compile(
|
|
||||||
prefix + strings.Replace( // inject ip regex into template
|
|
||||||
regexp.QuoteMeta(template), // escape dots
|
|
||||||
regexp.QuoteMeta(templateNameIP),
|
|
||||||
regexIP,
|
|
||||||
1) + "$")
|
|
||||||
if err != nil {
|
|
||||||
return nil, f, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nets = append(nets, network{
|
|
||||||
IPnet: ipnet,
|
|
||||||
Zone: templateZone[1],
|
|
||||||
Template: template,
|
|
||||||
RegexMatchIP: regex,
|
|
||||||
TTL: uint32(ttl),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by cidr
|
|
||||||
sort.Sort(nets)
|
|
||||||
return nets, f, nil
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
package reverse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetup(t *testing.T) {
|
|
||||||
|
|
||||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
|
||||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
|
||||||
|
|
||||||
regexIP4wildcard, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.domain\\.com\\.$")
|
|
||||||
regexIP6, _ := regexp.Compile("^ip-" + regexMatchV6 + "\\.domain\\.com\\.$")
|
|
||||||
regexIpv4dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-intern\\.dynamic\\.domain\\.com\\.$")
|
|
||||||
regexIpv6dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV6 + "-intern\\.dynamic\\.domain\\.com\\.$")
|
|
||||||
regexIpv4vpndynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-vpn\\.dynamic\\.domain\\.com\\.$")
|
|
||||||
|
|
||||||
serverBlockKeys := []string{"domain.com.:8053", "dynamic.domain.com.:8053"}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
inputFileRules string
|
|
||||||
shouldErr bool
|
|
||||||
networks networks
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
// with defaults
|
|
||||||
`reverse fd01::/64`,
|
|
||||||
false,
|
|
||||||
networks{network{
|
|
||||||
IPnet: net6,
|
|
||||||
Template: "ip-{ip}.domain.com.",
|
|
||||||
Zone: "domain.com.",
|
|
||||||
TTL: 60,
|
|
||||||
RegexMatchIP: regexIP6,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`reverse`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
//no cidr
|
|
||||||
`reverse 10.1.1.1`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
//no cidr
|
|
||||||
`reverse 10.1.1.0/16 fd00::`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// invalid key
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
notavailable
|
|
||||||
}`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// no domain suffix
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
hostname ip-{ip}.
|
|
||||||
}`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// hostname requires an second arg
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
hostname
|
|
||||||
}`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// template breaks regex compile
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
hostname ip-{[-x
|
|
||||||
}`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// ttl requires an (u)int
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
ttl string
|
|
||||||
}`,
|
|
||||||
true,
|
|
||||||
networks{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`reverse fd01::/64 {
|
|
||||||
hostname dynamic-{ip}-intern.{zone[2]}
|
|
||||||
ttl 50
|
|
||||||
}
|
|
||||||
reverse 10.1.1.0/24 {
|
|
||||||
hostname dynamic-{ip}-vpn.{zone[2]}
|
|
||||||
fallthrough
|
|
||||||
}`,
|
|
||||||
false,
|
|
||||||
networks{network{
|
|
||||||
IPnet: net6,
|
|
||||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
|
||||||
Zone: "dynamic.domain.com.",
|
|
||||||
TTL: 50,
|
|
||||||
RegexMatchIP: regexIpv6dynamic,
|
|
||||||
}, network{
|
|
||||||
IPnet: net4,
|
|
||||||
Template: "dynamic-{ip}-vpn.dynamic.domain.com.",
|
|
||||||
Zone: "dynamic.domain.com.",
|
|
||||||
TTL: 60,
|
|
||||||
RegexMatchIP: regexIpv4vpndynamic,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// multiple networks in one stanza
|
|
||||||
`reverse fd01::/64 10.1.1.0/24 {
|
|
||||||
hostname dynamic-{ip}-intern.{zone[2]}
|
|
||||||
ttl 50
|
|
||||||
fallthrough
|
|
||||||
}`,
|
|
||||||
false,
|
|
||||||
networks{network{
|
|
||||||
IPnet: net6,
|
|
||||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
|
||||||
Zone: "dynamic.domain.com.",
|
|
||||||
TTL: 50,
|
|
||||||
RegexMatchIP: regexIpv6dynamic,
|
|
||||||
}, network{
|
|
||||||
IPnet: net4,
|
|
||||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
|
||||||
Zone: "dynamic.domain.com.",
|
|
||||||
TTL: 50,
|
|
||||||
RegexMatchIP: regexIpv4dynamic,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// fix domain in template
|
|
||||||
`reverse fd01::/64 {
|
|
||||||
hostname dynamic-{ip}-intern.dynamic.domain.com
|
|
||||||
ttl 300
|
|
||||||
fallthrough
|
|
||||||
}`,
|
|
||||||
false,
|
|
||||||
networks{network{
|
|
||||||
IPnet: net6,
|
|
||||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
|
||||||
Zone: "dynamic.domain.com.",
|
|
||||||
TTL: 300,
|
|
||||||
RegexMatchIP: regexIpv6dynamic,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`reverse 10.1.1.0/24 {
|
|
||||||
hostname ip-{ip}.{zone[1]}
|
|
||||||
ttl 50
|
|
||||||
wildcard
|
|
||||||
fallthrough
|
|
||||||
}`,
|
|
||||||
false,
|
|
||||||
networks{network{
|
|
||||||
IPnet: net4,
|
|
||||||
Template: "ip-{ip}.domain.com.",
|
|
||||||
Zone: "domain.com.",
|
|
||||||
TTL: 50,
|
|
||||||
RegexMatchIP: regexIP4wildcard,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
c := caddy.NewTestController("dns", test.inputFileRules)
|
|
||||||
c.ServerBlockKeys = serverBlockKeys
|
|
||||||
networks, _, err := reverseParse(c)
|
|
||||||
|
|
||||||
if err == nil && test.shouldErr {
|
|
||||||
t.Fatalf("Test %d expected errors, but got no error", i)
|
|
||||||
} else if err != nil && !test.shouldErr {
|
|
||||||
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
|
|
||||||
}
|
|
||||||
for j, n := range networks {
|
|
||||||
reflect.DeepEqual(test.networks[j], n)
|
|
||||||
if !reflect.DeepEqual(test.networks[j], n) {
|
|
||||||
t.Fatalf("Test %d/%d expected %v, got %v", i, j, test.networks[j], n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,83 +12,6 @@ import (
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReverseFallthrough(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
name, rm, err := test.TempFile(".", exampleOrg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create zone: %s", err)
|
|
||||||
}
|
|
||||||
defer rm()
|
|
||||||
|
|
||||||
corefile := `arpa:0 example.org:0 {
|
|
||||||
reverse 10.32.0.0/16 {
|
|
||||||
hostname ip-{ip}.{zone[2]}
|
|
||||||
#fallthrough
|
|
||||||
}
|
|
||||||
file ` + name + ` example.org
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
i, udp, _, err := CoreDNSServerAndPorts(corefile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
|
||||||
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
|
||||||
}
|
|
||||||
// Reply should be SERVFAIL because of no fallthrough
|
|
||||||
if resp.Rcode != dns.RcodeServerFailure {
|
|
||||||
t.Fatalf("Expected SERVFAIL, but got: %d", resp.Rcode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the server.
|
|
||||||
i.Stop()
|
|
||||||
|
|
||||||
// And redo with fallthrough enabled
|
|
||||||
|
|
||||||
corefile = `arpa:0 example.org:0 {
|
|
||||||
reverse 10.32.0.0/16 {
|
|
||||||
hostname ip-{ip}.{zone[2]}
|
|
||||||
fallthrough
|
|
||||||
}
|
|
||||||
file ` + name + ` example.org
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
i, err = CoreDNSServer(corefile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
udp, _ = CoreDNSServerPorts(i, 0)
|
|
||||||
if udp == "" {
|
|
||||||
t.Fatalf("Could not get UDP listening port")
|
|
||||||
}
|
|
||||||
defer i.Stop()
|
|
||||||
|
|
||||||
p = proxy.NewLookup([]string{udp})
|
|
||||||
resp, err = p.Lookup(state, "example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resp.Answer) == 0 {
|
|
||||||
t.Error("Expected to at least one RR in the answer section, got none")
|
|
||||||
}
|
|
||||||
if resp.Answer[0].Header().Rrtype != dns.TypeA {
|
|
||||||
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
|
|
||||||
}
|
|
||||||
if resp.Answer[0].(*dns.A).A.String() != "127.0.0.1" {
|
|
||||||
t.Errorf("Expected 127.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReverseCorefile(t *testing.T) {
|
func TestReverseCorefile(t *testing.T) {
|
||||||
corefile := `10.0.0.0/24:0 {
|
corefile := `10.0.0.0/24:0 {
|
||||||
whoami
|
whoami
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue