From 2758a756dd32197fdc20e0094ea9a22227649da4 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 24 May 2018 14:30:01 +0100 Subject: [PATCH] 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 --- core/plugin/zplugin.go | 2 +- coremain/run.go | 7 +- plugin.cfg | 2 +- plugin/deprecated/setup.go | 2 +- plugin/proxy/README.md | 49 +------- plugin/proxy/dnstap_test.go | 1 - plugin/proxy/google.go | 219 --------------------------------- plugin/proxy/google_rr.go | 89 -------------- plugin/proxy/google_test.go | 5 - plugin/proxy/proxy.go | 8 -- plugin/proxy/upstream.go | 7 +- plugin/reverse/OWNERS | 2 - plugin/reverse/README.md | 96 --------------- plugin/reverse/network.go | 87 ------------- plugin/reverse/network_test.go | 135 -------------------- plugin/reverse/reverse.go | 108 ---------------- plugin/reverse/reverse_test.go | 70 ----------- plugin/reverse/setup.go | 154 ----------------------- plugin/reverse/setup_test.go | 195 ----------------------------- test/reverse_test.go | 77 ------------ 20 files changed, 9 insertions(+), 1306 deletions(-) delete mode 100644 plugin/proxy/google.go delete mode 100644 plugin/proxy/google_rr.go delete mode 100644 plugin/proxy/google_test.go delete mode 100644 plugin/reverse/OWNERS delete mode 100644 plugin/reverse/README.md delete mode 100644 plugin/reverse/network.go delete mode 100644 plugin/reverse/network_test.go delete mode 100644 plugin/reverse/reverse.go delete mode 100644 plugin/reverse/reverse_test.go delete mode 100644 plugin/reverse/setup.go delete mode 100644 plugin/reverse/setup_test.go diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go index d22904fbd..d1c0aaa73 100644 --- a/core/plugin/zplugin.go +++ b/core/plugin/zplugin.go @@ -10,6 +10,7 @@ import ( _ "github.com/coredns/coredns/plugin/cache" _ "github.com/coredns/coredns/plugin/chaos" _ "github.com/coredns/coredns/plugin/debug" + _ "github.com/coredns/coredns/plugin/deprecated" _ "github.com/coredns/coredns/plugin/dnssec" _ "github.com/coredns/coredns/plugin/dnstap" _ "github.com/coredns/coredns/plugin/erratic" @@ -28,7 +29,6 @@ import ( _ "github.com/coredns/coredns/plugin/pprof" _ "github.com/coredns/coredns/plugin/proxy" _ "github.com/coredns/coredns/plugin/reload" - _ "github.com/coredns/coredns/plugin/reverse" _ "github.com/coredns/coredns/plugin/rewrite" _ "github.com/coredns/coredns/plugin/root" _ "github.com/coredns/coredns/plugin/route53" diff --git a/coremain/run.go b/coremain/run.go index f8f4651e6..a948a4c9d 100644 --- a/coremain/run.go +++ b/coremain/run.go @@ -29,7 +29,7 @@ func init() { flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file") flag.BoolVar(&version, "version", false, "Show version") 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.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader)) @@ -62,10 +62,7 @@ func Run() { mustLogFatal(fmt.Errorf("extra command line arguments: %s", flag.Args())) } - // Set up process log before anything bad happens - if logfile { - log.SetOutput(os.Stdout) - } + log.SetOutput(os.Stdout) log.SetFlags(log.LstdFlags) if version { diff --git a/plugin.cfg b/plugin.cfg index 4f19395f5..c152684b7 100644 --- a/plugin.cfg +++ b/plugin.cfg @@ -38,7 +38,7 @@ cache:cache rewrite:rewrite dnssec:dnssec autopath:autopath -reverse:reverse +reverse:deprecated template:template hosts:hosts route53:route53 diff --git a/plugin/deprecated/setup.go b/plugin/deprecated/setup.go index cf415c1ae..566e266ec 100644 --- a/plugin/deprecated/setup.go +++ b/plugin/deprecated/setup.go @@ -20,7 +20,7 @@ import ( ) // 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 { c.Next() diff --git a/plugin/proxy/README.md b/plugin/proxy/README.md index 7341d31a6..f9dc3493b 100644 --- a/plugin/proxy/README.md +++ b/plugin/proxy/README.md @@ -31,7 +31,7 @@ proxy FROM TO... { health_check PATH:PORT [DURATION] except IGNORED_NAMES... 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 a failsafe.) * `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 - using this **TO** will be ignored. The `grpc` option will talk to a server that has implemented + old DNS. The `grpc` option will talk to a server that has implemented the [DnsService](https://github.com/coredns/coredns/blob/master/pb/dns.proto). ## Policies @@ -73,10 +72,6 @@ available. This is to preeempt the case where the healthchecking (as a mechanism ## 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` : 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. @@ -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 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 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 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 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. @@ -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 for some background. diff --git a/plugin/proxy/dnstap_test.go b/plugin/proxy/dnstap_test.go index 08cbd98a7..b07b081bc 100644 --- a/plugin/proxy/dnstap_test.go +++ b/plugin/proxy/dnstap_test.go @@ -47,7 +47,6 @@ func TestDnstap(t *testing.T) { tapq.SocketProto = tap.SocketProtocol_TCP tapr.SocketProto = tap.SocketProtocol_TCP 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) { diff --git a/plugin/proxy/google.go b/plugin/proxy/google.go deleted file mode 100644 index 0dbca6383..000000000 --- a/plugin/proxy/google.go +++ /dev/null @@ -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." -) diff --git a/plugin/proxy/google_rr.go b/plugin/proxy/google_rr.go deleted file mode 100644 index 3b9233b7b..000000000 --- a/plugin/proxy/google_rr.go +++ /dev/null @@ -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 -} diff --git a/plugin/proxy/google_test.go b/plugin/proxy/google_test.go deleted file mode 100644 index 1ce591664..000000000 --- a/plugin/proxy/google_test.go +++ /dev/null @@ -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. diff --git a/plugin/proxy/proxy.go b/plugin/proxy/proxy.go index 5aeaa2a54..2a8e35096 100644 --- a/plugin/proxy/proxy.go +++ b/plugin/proxy/proxy.go @@ -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 if timeout == 0 { timeout = defaultFailTimeout diff --git a/plugin/proxy/upstream.go b/plugin/proxy/upstream.go index 421163b83..be3e0bdad 100644 --- a/plugin/proxy/upstream.go +++ b/plugin/proxy/upstream.go @@ -165,12 +165,7 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { u.ex = newDNSEx() } case "https_google": - boot := []string{"8.8.8.8:53", "8.8.4.4:53"} - if len(encArgs) > 2 && encArgs[1] == "bootstrap" { - boot = encArgs[2:] - } - - u.ex = newGoogle("", boot) // "" for default in google.go + // allow the config, but make noop case "grpc": if len(encArgs) == 2 && encArgs[1] == "insecure" { u.ex = newGrpcClient(nil, u) diff --git a/plugin/reverse/OWNERS b/plugin/reverse/OWNERS deleted file mode 100644 index 2563b9f76..000000000 --- a/plugin/reverse/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -reviewers: -approvers: diff --git a/plugin/reverse/README.md b/plugin/reverse/README.md deleted file mode 100644 index 7b4c0670f..000000000 --- a/plugin/reverse/README.md +++ /dev/null @@ -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 - } -} -~~~ diff --git a/plugin/reverse/network.go b/plugin/reverse/network.go deleted file mode 100644 index 80d533382..000000000 --- a/plugin/reverse/network.go +++ /dev/null @@ -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 -} diff --git a/plugin/reverse/network_test.go b/plugin/reverse/network_test.go deleted file mode 100644 index a826707e5..000000000 --- a/plugin/reverse/network_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/plugin/reverse/reverse.go b/plugin/reverse/reverse.go deleted file mode 100644 index 273e20551..000000000 --- a/plugin/reverse/reverse.go +++ /dev/null @@ -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" } diff --git a/plugin/reverse/reverse_test.go b/plugin/reverse/reverse_test.go deleted file mode 100644 index 6d07ace29..000000000 --- a/plugin/reverse/reverse_test.go +++ /dev/null @@ -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) - } - } - } -} diff --git a/plugin/reverse/setup.go b/plugin/reverse/setup.go deleted file mode 100644 index 1033a0de4..000000000 --- a/plugin/reverse/setup.go +++ /dev/null @@ -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 -} diff --git a/plugin/reverse/setup_test.go b/plugin/reverse/setup_test.go deleted file mode 100644 index 3c88bc8e1..000000000 --- a/plugin/reverse/setup_test.go +++ /dev/null @@ -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) - } - } - } -} diff --git a/test/reverse_test.go b/test/reverse_test.go index 184a5fe1f..4ff74c737 100644 --- a/test/reverse_test.go +++ b/test/reverse_test.go @@ -12,83 +12,6 @@ import ( "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) { corefile := `10.0.0.0/24:0 { whoami