aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/coredns.go1
-rw-r--r--middleware/httpproxy/README.md71
-rw-r--r--middleware/httpproxy/google.go313
-rw-r--r--middleware/httpproxy/metrics.go32
-rw-r--r--middleware/httpproxy/proxy.go45
-rw-r--r--middleware/httpproxy/setup.go96
-rw-r--r--middleware/httpproxy/setup_test.go70
-rw-r--r--middleware/httpproxy/tls.go33
-rw-r--r--middleware/httpproxy/upstream.go92
-rw-r--r--middleware/proxy/README.md61
-rw-r--r--middleware/proxy/dns.go21
-rw-r--r--middleware/proxy/exchanger.go12
-rw-r--r--middleware/proxy/google.go241
-rw-r--r--middleware/proxy/google_rr.go90
-rw-r--r--middleware/proxy/google_test.go (renamed from middleware/httpproxy/google_test.go)2
-rw-r--r--middleware/proxy/lookup.go11
-rw-r--r--middleware/proxy/metrics.go6
-rw-r--r--middleware/proxy/proxy.go24
-rw-r--r--middleware/proxy/response.go21
-rw-r--r--middleware/proxy/setup.go17
-rw-r--r--middleware/proxy/upstream.go34
21 files changed, 466 insertions, 827 deletions
diff --git a/core/coredns.go b/core/coredns.go
index 5cd5c88b9..d7043c804 100644
--- a/core/coredns.go
+++ b/core/coredns.go
@@ -19,7 +19,6 @@ import (
_ "github.com/miekg/coredns/middleware/etcd"
_ "github.com/miekg/coredns/middleware/file"
_ "github.com/miekg/coredns/middleware/health"
- _ "github.com/miekg/coredns/middleware/httpproxy"
_ "github.com/miekg/coredns/middleware/kubernetes"
_ "github.com/miekg/coredns/middleware/loadbalance"
_ "github.com/miekg/coredns/middleware/log"
diff --git a/middleware/httpproxy/README.md b/middleware/httpproxy/README.md
deleted file mode 100644
index f0bf58903..000000000
--- a/middleware/httpproxy/README.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# httpproxy
-
-*httpproxy* proxies DNS request to a proxy using HTTPS (or HTTP/2 - not implemented). Usually this
- involves sending a JSON payload over this transport and translating the response back to DNS. The
- current supported backend is Google, using the URL: https://dns.google.com .
-
-## Syntax
-
-In its most basic form, a simple http proxy uses this syntax:
-
-~~~
-httpproxy FROM TO
-~~~
-
-* **FROM** is the base domain to match for the request to be proxied.
-* **TO** is the destination endpoint to proxy to, accepted values here are `dns.google.com`.
-
-For changing the defaults you can use the expanded syntax:
-
-~~~
-proxy FROM TO {
- upstream ADDRESS...
-}
-~~~
-
-* `upstream` defines upstream resolvers to be used (re-)resolve `dns.google.com` (or other names in the
- future) every 30 seconds. When not specified the combo 8.8.8.8, 8.8.4.4 is used.
-
-## Metrics
-
-If monitoring is enabled (via the *prometheus* directive) then the following metric is exported:
-
-* coredns_httpproxy_request_count_total{zone, proto, family}
-
-## Examples
-
-Proxy all requests within example.org to Google's dns.google.com.
-
-~~~
-proxy example.org dns.google.com
-~~~
-
-Proxy everything, and re-lookup `dns.google.com` every 30 seconds using the resolvers specified
-in /etc/resolv.conf.
-
-~~~
-proxy . dns.google.com {
- upstream /etc/resolv.conf
-}
-~~~
-
-## Debug queries
-
-Debug queries are enabled by default and currently there is no way to turn them off. When CoreDNS
-receives a debug queries (i.e. the name is prefixed with `o-o.debug.` a TXT record with Comment from
-`dns.google.com` is added. Note this is not always set, but sometimes you'll see:
-
-`dig @localhost -p 1053 mx o-o.debug.example.org`:
-
-~~~ txt
-;; OPT PSEUDOSECTION:
-; EDNS: version: 0, flags:; udp: 4096
-;; QUESTION SECTION:
-;o-o.debug.example.org. IN MX
-
-;; AUTHORITY SECTION:
-example.org. 1799 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016110711 7200 3600 1209600 3600
-
-;; ADDITIONAL SECTION:
-. 0 CH TXT "Response from 199.43.133.53"
-~~~
diff --git a/middleware/httpproxy/google.go b/middleware/httpproxy/google.go
deleted file mode 100644
index 68186c232..000000000
--- a/middleware/httpproxy/google.go
+++ /dev/null
@@ -1,313 +0,0 @@
-package httpproxy
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "log"
- "net"
- "net/http"
- "net/url"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/miekg/coredns/middleware/pkg/debug"
- "github.com/miekg/coredns/middleware/proxy"
- "github.com/miekg/coredns/request"
-
- "github.com/miekg/dns"
-)
-
-// immediate retries until this duration ends or we get a nil host.
-var tryDuration = 60 * time.Second
-
-type google struct {
- client *http.Client
- upstream *simpleUpstream
- addr *simpleUpstream
- quit chan bool
- sync.RWMutex
-}
-
-func newGoogle() *google { return &google{client: newClient(ghost), quit: make(chan bool)} }
-
-func (g *google) Exchange(state request.Request) (*dns.Msg, error) {
- v := url.Values{}
-
- v.Set("name", state.Name())
- v.Set("type", fmt.Sprintf("%d", state.QType()))
-
- optDebug := false
- if bug := debug.IsDebug(state.Name()); bug != "" {
- optDebug = true
- v.Set("name", bug)
- }
-
- start := time.Now()
-
- for time.Now().Sub(start) < tryDuration {
-
- g.RLock()
- addr := g.addr.Select()
- g.RUnlock()
-
- if addr == nil {
- return nil, fmt.Errorf("no healthy upstream http hosts")
- }
-
- atomic.AddInt64(&addr.Conns, 1)
-
- buf, backendErr := g.do(addr.Name, v.Encode())
-
- atomic.AddInt64(&addr.Conns, -1)
-
- if backendErr == nil {
- gm := new(googleMsg)
- if err := json.Unmarshal(buf, gm); err != nil {
- return nil, err
- }
-
- m, debug, err := toMsg(gm)
- if err != nil {
- return nil, err
- }
-
- if optDebug {
- // reset question
- m.Question[0].Name = state.QName()
- // prepend debug RR to the additional section
- m.Extra = append([]dns.RR{debug}, m.Extra...)
-
- }
-
- m.Id = state.Req.Id
- return m, nil
- }
-
- log.Printf("[WARNING] Failed to connect to HTTPS backend %q: %s", ghost, backendErr)
-
- timeout := addr.FailTimeout
- if timeout == 0 {
- timeout = 5 * time.Second
- }
- atomic.AddInt32(&addr.Fails, 1)
- go func(host *proxy.UpstreamHost, timeout time.Duration) {
- time.Sleep(timeout)
- atomic.AddInt32(&host.Fails, -1)
- }(addr, timeout)
- }
-
- return nil, errUnreachable
-}
-
-// OnStartup looks up the IP address for "ghost" every 30 seconds.
-func (g *google) OnStartup() error {
- r := new(dns.Msg)
- r.SetQuestion(dns.Fqdn(ghost), dns.TypeA)
- new, err := g.lookup(r)
- if err != nil {
- return err
- }
-
- up, _ := newSimpleUpstream(new)
- g.Lock()
- g.addr = up
- g.Unlock()
-
- go func() {
- tick := time.NewTicker(30 * time.Second)
-
- for {
- select {
- case <-tick.C:
-
- r.SetQuestion(dns.Fqdn(ghost), dns.TypeA)
- new, err := g.lookup(r)
- if err != nil {
- log.Printf("[WARNING] Failed to lookup A records %q: %s", ghost, err)
- continue
- }
-
- up, _ := newSimpleUpstream(new)
- g.Lock()
- g.addr = up
- g.Unlock()
- case <-g.quit:
- return
- }
- }
- }()
-
- return nil
-}
-
-func (g *google) OnShutdown() error {
- g.quit <- true
- return nil
-}
-
-func (g *google) SetUpstream(u *simpleUpstream) error {
- g.upstream = u
- return nil
-}
-
-func (g *google) lookup(r *dns.Msg) ([]string, error) {
- c := new(dns.Client)
- start := time.Now()
-
- for time.Now().Sub(start) < tryDuration {
- host := g.upstream.Select()
- if host == nil {
- return nil, fmt.Errorf("no healthy upstream hosts")
- }
-
- atomic.AddInt64(&host.Conns, 1)
-
- m, _, backendErr := c.Exchange(r, host.Name)
-
- atomic.AddInt64(&host.Conns, -1)
-
- if backendErr == nil {
- 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")
- }
-
- timeout := host.FailTimeout
- if timeout == 0 {
- timeout = 7 * time.Second
- }
- atomic.AddInt32(&host.Fails, 1)
- go func(host *proxy.UpstreamHost, timeout time.Duration) {
- time.Sleep(timeout)
- atomic.AddInt32(&host.Fails, -1)
- }(host, timeout)
- }
- return nil, fmt.Errorf("no healthy upstream hosts")
-}
-
-func (g *google) do(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 = ghost
-
- 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
-}
-
-// toMsg converts a googleMsg into the dns message. The returned RR is the comment disquised as a TXT
-// record.
-func toMsg(g *googleMsg) (*dns.Msg, dns.RR, 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, nil, err
- }
- }
- for i := 0; i < len(m.Ns); i++ {
- m.Ns[i], err = toRR(g.Authority[i])
- if err != nil {
- return nil, nil, err
- }
- }
- for i := 0; i < len(m.Extra); i++ {
- m.Extra[i], err = toRR(g.Additional[i])
- if err != nil {
- return nil, nil, err
- }
- }
-
- txt, _ := dns.NewRR(". 0 CH TXT " + g.Comment)
- return m, txt, nil
-}
-
-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
-}
-
-const (
- ghost = "dns.google.com"
-)
diff --git a/middleware/httpproxy/metrics.go b/middleware/httpproxy/metrics.go
deleted file mode 100644
index c3822e523..000000000
--- a/middleware/httpproxy/metrics.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package httpproxy
-
-import (
- "sync"
-
- "github.com/miekg/coredns/middleware"
-
- "github.com/prometheus/client_golang/prometheus"
-)
-
-// Metrics the httpproxy middleware exports.
-var (
- RequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
- Namespace: middleware.Namespace,
- Subsystem: subsystem,
- Name: "request_duration_milliseconds",
- Buckets: append(prometheus.DefBuckets, []float64{50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000}...),
- Help: "Histogram of the time (in milliseconds) each request took.",
- }, []string{"zone"})
-)
-
-// OnStartupMetrics sets up the metrics on startup.
-func OnStartupMetrics() error {
- metricsOnce.Do(func() {
- prometheus.MustRegister(RequestDuration)
- })
- return nil
-}
-
-var metricsOnce sync.Once
-
-const subsystem = "httpproxy"
diff --git a/middleware/httpproxy/proxy.go b/middleware/httpproxy/proxy.go
deleted file mode 100644
index 3ef638a8f..000000000
--- a/middleware/httpproxy/proxy.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Package httpproxy is middleware that proxies requests to a HTTPs server doing DNS.
-package httpproxy
-
-import (
- "errors"
- "time"
-
- "github.com/miekg/coredns/middleware"
- "github.com/miekg/coredns/request"
-
- "github.com/miekg/dns"
- "golang.org/x/net/context"
-)
-
-var errUnreachable = errors.New("unreachable backend")
-
-// Proxy represents a middleware instance that can proxy requests to HTTPS servers.
-type Proxy struct {
- from string
- e Exchanger
-
- Next middleware.Handler
-}
-
-// ServeDNS satisfies the middleware.Handler interface.
-func (p *Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
- start := time.Now()
- state := request.Request{W: w, Req: r}
-
- reply, backendErr := p.e.Exchange(state)
-
- if backendErr == nil && reply != nil {
- state.SizeAndDo(reply)
-
- w.WriteMsg(reply)
- RequestDuration.WithLabelValues(p.from).Observe(float64(time.Since(start) / time.Millisecond))
- return 0, nil
- }
- RequestDuration.WithLabelValues(p.from).Observe(float64(time.Since(start) / time.Millisecond))
-
- return dns.RcodeServerFailure, errUnreachable
-}
-
-// Name implements the Handler interface.
-func (p Proxy) Name() string { return "httpproxy" }
diff --git a/middleware/httpproxy/setup.go b/middleware/httpproxy/setup.go
deleted file mode 100644
index 01094c908..000000000
--- a/middleware/httpproxy/setup.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package httpproxy
-
-import (
- "fmt"
-
- "github.com/miekg/coredns/core/dnsserver"
- "github.com/miekg/coredns/middleware"
-
- "github.com/mholt/caddy"
- "github.com/mholt/caddy/caddyfile"
-)
-
-func init() {
- caddy.RegisterPlugin("httpproxy", caddy.Plugin{
- ServerType: "dns",
- Action: setup,
- })
-}
-
-func setup(c *caddy.Controller) error {
- p, err := httpproxyParse(c)
- if err != nil {
- return middleware.Error("httpproxy", err)
- }
-
- dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
- p.Next = next
- return p
- })
-
- c.OnStartup(func() error {
- OnStartupMetrics()
- e := p.e.OnStartup()
- if e != nil {
- return middleware.Error("httpproxy", e)
- }
- return nil
- })
- c.OnShutdown(func() error {
- e := p.e.OnShutdown()
- if e != nil {
- return middleware.Error("httpproxy", e)
- }
- return nil
- })
-
- return nil
-}
-
-func httpproxyParse(c *caddy.Controller) (*Proxy, error) {
- var p = &Proxy{}
-
- for c.Next() {
- if !c.Args(&p.from) {
- return p, c.ArgErr()
- }
- to := c.RemainingArgs()
- if len(to) != 1 {
- return p, c.ArgErr()
- }
- switch to[0] {
- case "dns.google.com":
- p.e = newGoogle()
- u, _ := newSimpleUpstream([]string{"8.8.8.8:53", "8.8.4.4:53"})
- p.e.SetUpstream(u)
- default:
- return p, fmt.Errorf("unknown http proxy %q", to[0])
- }
-
- for c.NextBlock() {
- if err := parseBlock(&c.Dispenser, p); err != nil {
- return p, err
- }
- }
- }
-
- return p, nil
-}
-
-func parseBlock(c *caddyfile.Dispenser, p *Proxy) error {
- switch c.Val() {
- case "upstream":
- upstreams := c.RemainingArgs()
- if len(upstreams) == 0 {
- return c.ArgErr()
- }
- u, err := newSimpleUpstream(upstreams)
- if err != nil {
- return err
- }
- p.e.SetUpstream(u)
- default:
- return c.Errf("unknown property '%s'", c.Val())
- }
- return nil
-}
diff --git a/middleware/httpproxy/setup_test.go b/middleware/httpproxy/setup_test.go
deleted file mode 100644
index 82db40aff..000000000
--- a/middleware/httpproxy/setup_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package httpproxy
-
-import (
- "io/ioutil"
- "log"
- "os"
- "strings"
- "testing"
-
- "github.com/mholt/caddy"
-)
-
-func TestSetupHttpproxy(t *testing.T) {
- log.SetOutput(ioutil.Discard)
-
- tests := []struct {
- input string
- shouldErr bool
- expectedFrom string // expected from.
- expectedErrContent string // substring from the expected error. Empty for positive cases.
- }{
- // ok
- {
- `httpproxy . dns.google.com`, false, "", "",
- },
- {
- `httpproxy . dns.google.com {
- upstream 8.8.8.8:53
- }`, false, "", "",
- },
- {
- `httpproxy . dns.google.com {
- upstream resolv.conf
- }`, false, "", "",
- },
- // fail
- {
- `httpproxy`, true, "", "Wrong argument count or unexpected line ending after",
- },
- {
- `httpproxy . wns.google.com`, true, "", "unknown http proxy",
- },
- }
-
- // Write fake resolv.conf for test
- err := ioutil.WriteFile("resolv.conf", []byte("nameserver 127.0.0.1\n"), 0600)
- if err != nil {
- t.Fatalf("Failed to write test resolv.conf")
- }
- defer os.Remove("resolv.conf")
-
- for i, test := range tests {
- c := caddy.NewTestController("dns", test.input)
- _, err := httpproxyParse(c)
-
- if test.shouldErr && err == nil {
- t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
- }
-
- if err != nil {
- if !test.shouldErr {
- t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
- }
-
- if !strings.Contains(err.Error(), test.expectedErrContent) {
- t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
- }
- }
- }
-}
diff --git a/middleware/httpproxy/tls.go b/middleware/httpproxy/tls.go
deleted file mode 100644
index 2c05a0331..000000000
--- a/middleware/httpproxy/tls.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package httpproxy
-
-import (
- "crypto/tls"
- "net/http"
- "time"
-
- "github.com/miekg/coredns/request"
- "github.com/miekg/dns"
-)
-
-// Exchanger is an interface that specifies a type implementing a DNS resolver that
-// uses a HTTPS server.
-type Exchanger interface {
- Exchange(request.Request) (*dns.Msg, error)
-
- SetUpstream(*simpleUpstream) error
- OnStartup() error
- OnShutdown() error
-}
-
-func newClient(sni string) *http.Client {
- tls := &tls.Config{ServerName: sni}
-
- c := &http.Client{
- Timeout: time.Second * timeOut,
- Transport: &http.Transport{TLSClientConfig: tls},
- }
-
- return c
-}
-
-const timeOut = 5
diff --git a/middleware/httpproxy/upstream.go b/middleware/httpproxy/upstream.go
deleted file mode 100644
index 3342cce6f..000000000
--- a/middleware/httpproxy/upstream.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package httpproxy
-
-import (
- "sync/atomic"
- "time"
-
- "github.com/miekg/coredns/middleware/pkg/dnsutil"
- "github.com/miekg/coredns/middleware/proxy"
-)
-
-type simpleUpstream struct {
- from string
- Hosts proxy.HostPool
- Policy proxy.Policy
-
- FailTimeout time.Duration
- MaxFails int32
-}
-
-// newSimpleUpstream return a new simpleUpstream initialized with the addresses.
-func newSimpleUpstream(hosts []string) (*simpleUpstream, error) {
- upstream := &simpleUpstream{
- Hosts: nil,
- Policy: &proxy.Random{},
- FailTimeout: 3 * time.Second,
- MaxFails: 3,
- }
-
- toHosts, err := dnsutil.ParseHostPortOrFile(hosts...)
- if err != nil {
- return upstream, err
- }
-
- upstream.Hosts = make([]*proxy.UpstreamHost, len(toHosts))
- for i, host := range toHosts {
- uh := &proxy.UpstreamHost{
- Name: host,
- Conns: 0,
- Fails: 0,
- FailTimeout: upstream.FailTimeout,
- Unhealthy: false,
-
- CheckDown: func(upstream *simpleUpstream) proxy.UpstreamHostDownFunc {
- return func(uh *proxy.UpstreamHost) bool {
- if uh.Unhealthy {
- return true
- }
-
- fails := atomic.LoadInt32(&uh.Fails)
- if fails >= upstream.MaxFails && upstream.MaxFails != 0 {
- return true
- }
- return false
- }
- }(upstream),
- }
- upstream.Hosts[i] = uh
- }
- return upstream, nil
-}
-
-func (u *simpleUpstream) From() string { return u.from }
-func (u *simpleUpstream) Options() proxy.Options { return proxy.Options{} }
-func (u *simpleUpstream) IsAllowedPath(name string) bool { return true }
-
-func (u *simpleUpstream) Select() *proxy.UpstreamHost {
- pool := u.Hosts
- if len(pool) == 1 {
- if pool[0].Down() {
- return nil
- }
- return pool[0]
- }
- allDown := true
- for _, host := range pool {
- if !host.Down() {
- allDown = false
- break
- }
- }
- if allDown {
- return nil
- }
-
- if u.Policy == nil {
- h := (&proxy.Random{}).Select(pool)
- return h
- }
-
- h := u.Policy.Select(pool)
- return h
-}
diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md
index 9ca0e4638..2e6d84c2b 100644
--- a/middleware/proxy/README.md
+++ b/middleware/proxy/README.md
@@ -13,8 +13,8 @@ In its most basic form, a simple reverse proxy uses this syntax:
proxy FROM TO
~~~
-* **FROM** is the base domain to match for the request to be proxied
-* **TO** is the destination endpoint to proxy to
+* **FROM** is the base domain to match for the request to be proxied.
+* **TO** is the destination endpoint to proxy to.
However, advanced features including load balancing can be utilized with an expanded syntax:
@@ -26,7 +26,7 @@ proxy FROM TO... {
health_check PATH:PORT [DURATION]
except IGNORED_NAMES...
spray
- protocol [dns|https_google]
+ protocol [dns|https_google [bootstrap ADDRESS...]]
}
~~~
@@ -39,7 +39,8 @@ proxy FROM TO... {
* `ignored_names...` is a space-separated list of paths to exclude from proxying. Requests that match any of these paths will be passed through.
* `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.
+ `https_google` uses `https://dns.google.com` and speaks a JSON DNS dialect. Note when using this
+ **TO** must be `dns.google.com`.
## Policies
@@ -53,17 +54,43 @@ available. This is to preeempt the case where the healthchecking (as a mechanism
## Upstream Protocols
-Currently supported are `dns` (i.e., standard DNS over UDP) and `https_google`. Note that with
-`https_google` the entire transport is encrypted. Only *you* and *Google* can see your DNS activity.
+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`: no options can be given at the moment.
+* `https_google`: bootstrap **ADDRESS...** is used to (re-)resolve `dns.google.com` to an address to
+ connect to. 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`.
+
+ Debug queries are enabled by default and currently there is no way to turn them off. When CoreDNS
+ receives a debug query (i.e. the name is prefixed with `o-o.debug.`) a TXT record with Comment
+ from `dns.google.com` is added. Note this is not always set, but sometimes you'll see:
+
+ `dig @localhost -p 1053 mx o-o.debug.example.org`:
+
+~~~ txt
+;; OPT PSEUDOSECTION:
+; EDNS: version: 0, flags:; udp: 4096
+;; QUESTION SECTION:
+;o-o.debug.example.org. IN MX
+
+;; AUTHORITY SECTION:
+example.org. 1799 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2016110711 7200 3600 1209600 3600
+
+;; ADDITIONAL SECTION:
+. 0 CH TXT "Response from 199.43.133.53"
+~~~
## Metrics
If monitoring is enabled (via the *prometheus* directive) then the following metric is exported:
-* coredns_proxy_request_duration_milliseconds{zone}
+* coredns_proxy_request_count_total{proto, from}
-The metric shows the duration for a proxied request, the `zone` label is the **FROM** as specified
-in the configuration.
+Where `proto` is the protocol used (`dns`, or `https_google`) and `from` is **FROM** specified in
+the config.
## Examples
@@ -111,3 +138,19 @@ proxy . /etc/resolv.conf {
except miek.nl example.org
}
~~~
+
+Proxy all requests within example.org to Google's dns.google.com.
+
+~~~
+proxy example.org 1.2.3.4:53 {
+ protocol https_google
+}
+~~~
+
+Proxy everything, and re-lookup `dns.google.com` every 300 seconds using 8.8.8.8:53.
+
+~~~
+proxy . 1.2.3.4:53 {
+ protocol https_google bootstrap 8.8.8.8:53
+}
+~~~
diff --git a/middleware/proxy/dns.go b/middleware/proxy/dns.go
index 51633c268..4fde48e40 100644
--- a/middleware/proxy/dns.go
+++ b/middleware/proxy/dns.go
@@ -12,23 +12,20 @@ import (
type dnsEx struct {
Timeout time.Duration
- Address string // address/name of this upstream
-
- group *singleflight.Group
+ group *singleflight.Group
}
-func newDNSEx(address string) *dnsEx {
- return &dnsEx{Address: address, group: new(singleflight.Group), Timeout: defaultTimeout * time.Second}
+func newDNSEx() *dnsEx {
+ return &dnsEx{group: new(singleflight.Group), Timeout: defaultTimeout * time.Second}
}
-func (d *dnsEx) OnStartup() error { return nil }
-func (d *dnsEx) OnShutdown() error { return nil }
-func (d *dnsEx) SetUpstream(u Upstream) error { return nil }
-func (d *dnsEx) Protocol() protocol { return dnsProto }
+func (g *dnsEx) Protocol() string { return "dns" }
+func (d *dnsEx) OnShutdown(p *Proxy) error { return nil }
+func (d *dnsEx) OnStartup(p *Proxy) error { return nil }
// Exchange implements the Exchanger interface.
-func (d *dnsEx) Exchange(state request.Request) (*dns.Msg, error) {
- co, err := net.DialTimeout(state.Proto(), d.Address, d.Timeout)
+func (d *dnsEx) Exchange(addr string, state request.Request) (*dns.Msg, error) {
+ co, err := net.DialTimeout(state.Proto(), addr, d.Timeout)
if err != nil {
return nil, err
}
@@ -101,5 +98,3 @@ func exchange(m *dns.Msg, co net.Conn) (dns.Msg, error) {
}
return *r, err
}
-
-const dnsProto protocol = "dns"
diff --git a/middleware/proxy/exchanger.go b/middleware/proxy/exchanger.go
index 29974a289..78c80b8b6 100644
--- a/middleware/proxy/exchanger.go
+++ b/middleware/proxy/exchanger.go
@@ -8,11 +8,9 @@ import (
// Exchanger is an interface that specifies a type implementing a DNS resolver that
// can use whatever transport it likes.
type Exchanger interface {
- Exchange(request.Request) (*dns.Msg, error)
- SetUpstream(Upstream) error // (Re)set the upstream
- OnStartup() error
- OnShutdown() error
- Protocol() protocol
-}
+ Exchange(addr string, state request.Request) (*dns.Msg, error)
+ Protocol() string
-type protocol string
+ OnStartup(*Proxy) error
+ OnShutdown(*Proxy) error
+}
diff --git a/middleware/proxy/google.go b/middleware/proxy/google.go
new file mode 100644
index 000000000..b2a3b45f8
--- /dev/null
+++ b/middleware/proxy/google.go
@@ -0,0 +1,241 @@
+package proxy
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "sync/atomic"
+ "time"
+
+ "github.com/miekg/coredns/middleware/pkg/debug"
+ "github.com/miekg/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 {
+ 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(addr string, state request.Request) (*dns.Msg, error) {
+ v := url.Values{}
+
+ v.Set("name", state.Name())
+ v.Set("type", fmt.Sprintf("%d", state.QType()))
+
+ optDebug := false
+ if bug := debug.IsDebug(state.Name()); bug != "" {
+ optDebug = true
+ v.Set("name", bug)
+ }
+
+ 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, debug, err := toMsg(gm)
+ if err != nil {
+ return nil, err
+ }
+
+ if optDebug {
+ // reset question
+ m.Question[0].Name = state.QName()
+ // prepend debug RR to the additional section
+ m.Extra = append([]dns.RR{debug}, m.Extra...)
+
+ }
+
+ m.Id = state.Req.Id
+ return m, nil
+ }
+
+ log.Printf("[WARNING] 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) 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}
+
+ new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
+
+ oldUpstream := *p.Upstreams
+ oldFrom := ""
+ var oldEx Exchanger
+ if len(oldUpstream) > 0 {
+ oldFrom = oldUpstream[0].From()
+ oldEx = oldUpstream[0].Exchanger()
+ }
+
+ // ignore errors here, as we want to keep on trying.
+ if err != nil {
+ log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err)
+ } else {
+ addrs, err1 := extractAnswer(new)
+ if err1 != nil {
+ log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err)
+ }
+
+ up := newUpstream(addrs, oldFrom, oldEx)
+ p.Upstreams = &[]Upstream{up}
+ }
+
+ go func() {
+ tick := time.NewTicker(300 * time.Second)
+
+ for {
+ select {
+ case <-tick.C:
+
+ new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
+ if err != nil {
+ log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err)
+ } else {
+ addrs, err1 := extractAnswer(new)
+ if err1 != nil {
+ log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err)
+ continue
+ }
+
+ up := newUpstream(addrs, oldFrom, oldEx)
+ p.Upstreams = &[]Upstream{up}
+ }
+
+ 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, from string, ex Exchanger) Upstream {
+ upstream := &staticUpstream{
+ from: from,
+ Hosts: nil,
+ Policy: &Random{},
+ Spray: nil,
+ FailTimeout: 10 * time.Second,
+ MaxFails: 3,
+ ex: ex,
+ }
+
+ upstream.Hosts = make([]*UpstreamHost, len(hosts))
+ for i, h := range hosts {
+ uh := &UpstreamHost{
+ Name: h,
+ Conns: 0,
+ Fails: 0,
+ FailTimeout: upstream.FailTimeout,
+ Unhealthy: false,
+
+ CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
+ return func(uh *UpstreamHost) bool {
+ if uh.Unhealthy {
+ return true
+ }
+
+ fails := atomic.LoadInt32(&uh.Fails)
+ if fails >= upstream.MaxFails && upstream.MaxFails != 0 {
+ return true
+ }
+ return false
+ }
+ }(upstream),
+ WithoutPathPrefix: upstream.WithoutPathPrefix,
+ }
+ upstream.Hosts[i] = uh
+ }
+ return upstream
+}
+
+const (
+ // Default endpoint for this service.
+ ghost = "dns.google.com."
+)
diff --git a/middleware/proxy/google_rr.go b/middleware/proxy/google_rr.go
new file mode 100644
index 000000000..8c7e82bc2
--- /dev/null
+++ b/middleware/proxy/google_rr.go
@@ -0,0 +1,90 @@
+package proxy
+
+import (
+ "fmt"
+
+ "github.com/miekg/dns"
+)
+
+// toMsg converts a googleMsg into the dns message. The returned RR is the comment disquised as a TXT record.
+func toMsg(g *googleMsg) (*dns.Msg, dns.RR, 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, nil, err
+ }
+ }
+ for i := 0; i < len(m.Ns); i++ {
+ m.Ns[i], err = toRR(g.Authority[i])
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+ for i := 0; i < len(m.Extra); i++ {
+ m.Extra[i], err = toRR(g.Additional[i])
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ txt, _ := dns.NewRR(". 0 CH TXT " + g.Comment)
+ return m, txt, 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/middleware/httpproxy/google_test.go b/middleware/proxy/google_test.go
index bd435a6ff..1ce591664 100644
--- a/middleware/httpproxy/google_test.go
+++ b/middleware/proxy/google_test.go
@@ -1,4 +1,4 @@
-package httpproxy
+package proxy
// TODO(miek):
// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT
diff --git a/middleware/proxy/lookup.go b/middleware/proxy/lookup.go
index 51cdb54d8..23c086bbf 100644
--- a/middleware/proxy/lookup.go
+++ b/middleware/proxy/lookup.go
@@ -13,7 +13,6 @@ import (
// NewLookup create a new proxy with the hosts in host and a Random policy.
func NewLookup(hosts []string) Proxy {
- // TODO(miek): maybe add optional protocol parameter?
p := Proxy{Next: nil}
upstream := &staticUpstream{
@@ -22,7 +21,8 @@ func NewLookup(hosts []string) Proxy {
Policy: &Random{},
Spray: nil,
FailTimeout: 10 * time.Second,
- MaxFails: 3,
+ MaxFails: 3, // TODO(miek): disable error checking for simple lookups?
+ ex: newDNSEx(),
}
for i, host := range hosts {
@@ -31,7 +31,6 @@ func NewLookup(hosts []string) Proxy {
Conns: 0,
Fails: 0,
FailTimeout: upstream.FailTimeout,
- Exchanger: newDNSEx(host),
Unhealthy: false,
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
@@ -50,7 +49,7 @@ func NewLookup(hosts []string) Proxy {
}
upstream.Hosts[i] = uh
}
- p.Upstreams = []Upstream{upstream}
+ p.Upstreams = &[]Upstream{upstream}
return p
}
@@ -72,7 +71,7 @@ func (p Proxy) Forward(state request.Request) (*dns.Msg, error) {
}
func (p Proxy) lookup(state request.Request) (*dns.Msg, error) {
- for _, upstream := range p.Upstreams {
+ for _, upstream := range *p.Upstreams {
start := time.Now()
// Since Select() should give us "up" hosts, keep retrying
@@ -88,7 +87,7 @@ func (p Proxy) lookup(state request.Request) (*dns.Msg, error) {
atomic.AddInt64(&host.Conns, 1)
- reply, backendErr := host.Exchange(state)
+ reply, backendErr := upstream.Exchanger().Exchange(host.Name, state)
atomic.AddInt64(&host.Conns, -1)
diff --git a/middleware/proxy/metrics.go b/middleware/proxy/metrics.go
index 77d268bd1..485b594c7 100644
--- a/middleware/proxy/metrics.go
+++ b/middleware/proxy/metrics.go
@@ -16,11 +16,11 @@ var (
Name: "request_duration_milliseconds",
Buckets: append(prometheus.DefBuckets, []float64{50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000}...),
Help: "Histogram of the time (in milliseconds) each request took.",
- }, []string{"zone"})
+ }, []string{"proto", "from"})
)
-// OnStartup sets up the metrics on startup. This is done for all proxy protocols.
-func OnStartup() error {
+// OnStartupMetrics sets up the metrics on startup. This is done for all proxy protocols.
+func OnStartupMetrics() error {
metricsOnce.Do(func() {
prometheus.MustRegister(RequestDuration)
})
diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go
index 037c05376..3f870661c 100644
--- a/middleware/proxy/proxy.go
+++ b/middleware/proxy/proxy.go
@@ -21,8 +21,12 @@ var (
// Proxy represents a middleware instance that can proxy requests to another (DNS) server.
type Proxy struct {
- Next middleware.Handler
- Upstreams []Upstream
+ Next middleware.Handler
+
+ // Upstreams is a pointer to a slice, so we can update the upstream (used for Google)
+ // midway.
+
+ Upstreams *[]Upstream
}
// Upstream manages a pool of proxy upstream hosts. Select should return a
@@ -34,8 +38,8 @@ type Upstream interface {
Select() *UpstreamHost
// Checks if subpdomain is not an ignored.
IsAllowedPath(string) bool
- // Options returns the options set for this upstream
- Options() Options
+ // Exchanger returns the exchanger to be used for this upstream.
+ Exchanger() Exchanger
}
// UpstreamHostDownFunc can be used to customize how Down behaves.
@@ -50,7 +54,6 @@ type UpstreamHost struct {
Unhealthy bool
CheckDown UpstreamHostDownFunc
WithoutPathPrefix string
- Exchanger
}
// Down checks whether the upstream host is down or not.
@@ -70,11 +73,12 @@ func (uh *UpstreamHost) Down() bool {
var tryDuration = 60 * time.Second
// ServeDNS satisfies the middleware.Handler interface.
+
func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
var span, child ot.Span
span = ot.SpanFromContext(ctx)
state := request.Request{W: w, Req: r}
- for _, upstream := range p.Upstreams {
+ for _, upstream := range *p.Upstreams {
start := time.Now()
// Since Select() should give us "up" hosts, keep retrying
@@ -83,7 +87,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
host := upstream.Select()
if host == nil {
- RequestDuration.WithLabelValues(state.Proto(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
+ RequestDuration.WithLabelValues(upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return dns.RcodeServerFailure, errUnreachable
}
@@ -95,7 +99,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
atomic.AddInt64(&host.Conns, 1)
- reply, backendErr := host.Exchange(state)
+ reply, backendErr := upstream.Exchanger().Exchange(host.Name, state)
atomic.AddInt64(&host.Conns, -1)
@@ -106,7 +110,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
if backendErr == nil {
w.WriteMsg(reply)
- RequestDuration.WithLabelValues(state.Proto(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
+ RequestDuration.WithLabelValues(upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return 0, nil
}
@@ -121,7 +125,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
}(host, timeout)
}
- RequestDuration.WithLabelValues(state.Proto(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
+ RequestDuration.WithLabelValues(upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond))
return dns.RcodeServerFailure, errUnreachable
}
diff --git a/middleware/proxy/response.go b/middleware/proxy/response.go
new file mode 100644
index 000000000..2ad553c41
--- /dev/null
+++ b/middleware/proxy/response.go
@@ -0,0 +1,21 @@
+package proxy
+
+import (
+ "net"
+
+ "github.com/miekg/dns"
+)
+
+type fakeBootWriter struct {
+ dns.ResponseWriter
+}
+
+func (w *fakeBootWriter) LocalAddr() net.Addr {
+ local := net.ParseIP("127.0.0.1")
+ return &net.UDPAddr{IP: local, Port: 53} // Port is not used here
+}
+
+func (w *fakeBootWriter) RemoteAddr() net.Addr {
+ remote := net.ParseIP("8.8.8.8")
+ return &net.UDPAddr{IP: remote, Port: 53} // Port is not used here
+}
diff --git a/middleware/proxy/setup.go b/middleware/proxy/setup.go
index 2356ab962..538ecb67a 100644
--- a/middleware/proxy/setup.go
+++ b/middleware/proxy/setup.go
@@ -19,11 +19,24 @@ func setup(c *caddy.Controller) error {
if err != nil {
return middleware.Error("proxy", err)
}
+
+ P := &Proxy{}
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
- return Proxy{Next: next, Upstreams: upstreams}
+ P.Next = next
+ P.Upstreams = &upstreams
+ return P
})
- c.OnStartup(OnStartup)
+ c.OnStartup(OnStartupMetrics)
+
+ for _, u := range upstreams {
+ c.OnStartup(func() error {
+ return u.Exchanger().OnStartup(P)
+ })
+ c.OnShutdown(func() error {
+ return u.Exchanger().OnShutdown(P)
+ })
+ }
return nil
}
diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go
index e6a19ca58..b02c08a42 100644
--- a/middleware/proxy/upstream.go
+++ b/middleware/proxy/upstream.go
@@ -37,13 +37,7 @@ type staticUpstream struct {
}
WithoutPathPrefix string
IgnoredSubDomains []string
- options Options
- Protocol protocol
-}
-
-// Options ...
-type Options struct {
- Ecs []*net.IPNet // EDNS0 CLIENT SUBNET address (v4/v6) to add in CIDR notaton.
+ ex Exchanger
}
// NewStaticUpstreams parses the configuration input and sets up
@@ -58,7 +52,7 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
Spray: nil,
FailTimeout: 10 * time.Second,
MaxFails: 1,
- Protocol: dnsProto,
+ ex: newDNSEx(),
}
if !c.Args(&upstream.from) {
@@ -89,7 +83,6 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
Fails: 0,
FailTimeout: upstream.FailTimeout,
Unhealthy: false,
- Exchanger: newDNSEx(host),
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
return func(uh *UpstreamHost) bool {
@@ -106,14 +99,6 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
}(upstream),
WithoutPathPrefix: upstream.WithoutPathPrefix,
}
- switch upstream.Protocol {
- // case https_google:
-
- case dnsProto:
- fallthrough
- default:
- // Already done in the initialization above.
- }
upstream.Hosts[i] = uh
}
@@ -135,10 +120,6 @@ func (u *staticUpstream) From() string {
return u.from
}
-func (u *staticUpstream) Options() Options {
- return u.options
-}
-
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
switch c.Val() {
case "policy":
@@ -208,9 +189,14 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
}
switch encArgs[0] {
case "dns":
- u.Protocol = dnsProto
+ u.ex = newDNSEx()
case "https_google":
- // Nothing yet.
+ 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
default:
return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0])
}
@@ -305,3 +291,5 @@ func (u *staticUpstream) IsAllowedPath(name string) bool {
}
return true
}
+
+func (u *staticUpstream) Exchanger() Exchanger { return u.ex }