middleware/proxy: absorb httpproxy (#481)
* middleware/proxy: absorb httpproxy Move the httproxy into proxy. This adds and Exchanger interface which is used to exchange the messages with the upstream. The https_google upstream will re-resolve itself and update the upstream hosts used every 300s. * Remove and add TODO
This commit is contained in:
parent
77f957d443
commit
123a76c91e
21 changed files with 466 additions and 827 deletions
|
@ -19,7 +19,6 @@ import (
|
||||||
_ "github.com/miekg/coredns/middleware/etcd"
|
_ "github.com/miekg/coredns/middleware/etcd"
|
||||||
_ "github.com/miekg/coredns/middleware/file"
|
_ "github.com/miekg/coredns/middleware/file"
|
||||||
_ "github.com/miekg/coredns/middleware/health"
|
_ "github.com/miekg/coredns/middleware/health"
|
||||||
_ "github.com/miekg/coredns/middleware/httpproxy"
|
|
||||||
_ "github.com/miekg/coredns/middleware/kubernetes"
|
_ "github.com/miekg/coredns/middleware/kubernetes"
|
||||||
_ "github.com/miekg/coredns/middleware/loadbalance"
|
_ "github.com/miekg/coredns/middleware/loadbalance"
|
||||||
_ "github.com/miekg/coredns/middleware/log"
|
_ "github.com/miekg/coredns/middleware/log"
|
||||||
|
|
|
@ -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"
|
|
||||||
~~~
|
|
|
@ -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"
|
|
||||||
)
|
|
|
@ -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"
|
|
|
@ -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" }
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -13,8 +13,8 @@ In its most basic form, a simple reverse proxy uses this syntax:
|
||||||
proxy FROM TO
|
proxy FROM TO
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* **FROM** is the base domain to match for the request to be proxied
|
* **FROM** is the base domain to match for the request to be proxied.
|
||||||
* **TO** is the destination endpoint to proxy to
|
* **TO** is the destination endpoint to proxy to.
|
||||||
|
|
||||||
However, advanced features including load balancing can be utilized with an expanded syntax:
|
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]
|
health_check PATH:PORT [DURATION]
|
||||||
except IGNORED_NAMES...
|
except IGNORED_NAMES...
|
||||||
spray
|
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.
|
* `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.)
|
* `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
|
* `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
|
## Policies
|
||||||
|
|
||||||
|
@ -53,17 +54,43 @@ available. This is to preeempt the case where the healthchecking (as a mechanism
|
||||||
|
|
||||||
## Upstream Protocols
|
## Upstream Protocols
|
||||||
|
|
||||||
Currently supported are `dns` (i.e., standard DNS over UDP) and `https_google`. Note that with
|
Currently `protocol` supports `dns` (i.e., standard DNS over UDP/TCP) and `https_google` (JSON
|
||||||
`https_google` the entire transport is encrypted. Only *you* and *Google* can see your DNS activity.
|
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
|
## 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:
|
||||||
|
|
||||||
* 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
|
Where `proto` is the protocol used (`dns`, or `https_google`) and `from` is **FROM** specified in
|
||||||
in the configuration.
|
the config.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -111,3 +138,19 @@ proxy . /etc/resolv.conf {
|
||||||
except miek.nl example.org
|
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
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
|
@ -12,23 +12,20 @@ import (
|
||||||
|
|
||||||
type dnsEx struct {
|
type dnsEx struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Address string // address/name of this upstream
|
group *singleflight.Group
|
||||||
|
|
||||||
group *singleflight.Group
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDNSEx(address string) *dnsEx {
|
func newDNSEx() *dnsEx {
|
||||||
return &dnsEx{Address: address, group: new(singleflight.Group), Timeout: defaultTimeout * time.Second}
|
return &dnsEx{group: new(singleflight.Group), Timeout: defaultTimeout * time.Second}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dnsEx) OnStartup() error { return nil }
|
func (g *dnsEx) Protocol() string { return "dns" }
|
||||||
func (d *dnsEx) OnShutdown() error { return nil }
|
func (d *dnsEx) OnShutdown(p *Proxy) error { return nil }
|
||||||
func (d *dnsEx) SetUpstream(u Upstream) error { return nil }
|
func (d *dnsEx) OnStartup(p *Proxy) error { return nil }
|
||||||
func (d *dnsEx) Protocol() protocol { return dnsProto }
|
|
||||||
|
|
||||||
// Exchange implements the Exchanger interface.
|
// Exchange implements the Exchanger interface.
|
||||||
func (d *dnsEx) Exchange(state request.Request) (*dns.Msg, error) {
|
func (d *dnsEx) Exchange(addr string, state request.Request) (*dns.Msg, error) {
|
||||||
co, err := net.DialTimeout(state.Proto(), d.Address, d.Timeout)
|
co, err := net.DialTimeout(state.Proto(), addr, d.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -101,5 +98,3 @@ func exchange(m *dns.Msg, co net.Conn) (dns.Msg, error) {
|
||||||
}
|
}
|
||||||
return *r, err
|
return *r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const dnsProto protocol = "dns"
|
|
||||||
|
|
|
@ -8,11 +8,9 @@ import (
|
||||||
// Exchanger is an interface that specifies a type implementing a DNS resolver that
|
// Exchanger is an interface that specifies a type implementing a DNS resolver that
|
||||||
// can use whatever transport it likes.
|
// can use whatever transport it likes.
|
||||||
type Exchanger interface {
|
type Exchanger interface {
|
||||||
Exchange(request.Request) (*dns.Msg, error)
|
Exchange(addr string, state request.Request) (*dns.Msg, error)
|
||||||
SetUpstream(Upstream) error // (Re)set the upstream
|
Protocol() string
|
||||||
OnStartup() error
|
|
||||||
OnShutdown() error
|
|
||||||
Protocol() protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
type protocol string
|
OnStartup(*Proxy) error
|
||||||
|
OnShutdown(*Proxy) error
|
||||||
|
}
|
||||||
|
|
241
middleware/proxy/google.go
Normal file
241
middleware/proxy/google.go
Normal file
|
@ -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."
|
||||||
|
)
|
90
middleware/proxy/google_rr.go
Normal file
90
middleware/proxy/google_rr.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package httpproxy
|
package proxy
|
||||||
|
|
||||||
// TODO(miek):
|
// TODO(miek):
|
||||||
// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT
|
// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT
|
|
@ -13,7 +13,6 @@ import (
|
||||||
|
|
||||||
// NewLookup create a new proxy with the hosts in host and a Random policy.
|
// NewLookup create a new proxy with the hosts in host and a Random policy.
|
||||||
func NewLookup(hosts []string) Proxy {
|
func NewLookup(hosts []string) Proxy {
|
||||||
// TODO(miek): maybe add optional protocol parameter?
|
|
||||||
p := Proxy{Next: nil}
|
p := Proxy{Next: nil}
|
||||||
|
|
||||||
upstream := &staticUpstream{
|
upstream := &staticUpstream{
|
||||||
|
@ -22,7 +21,8 @@ func NewLookup(hosts []string) Proxy {
|
||||||
Policy: &Random{},
|
Policy: &Random{},
|
||||||
Spray: nil,
|
Spray: nil,
|
||||||
FailTimeout: 10 * time.Second,
|
FailTimeout: 10 * time.Second,
|
||||||
MaxFails: 3,
|
MaxFails: 3, // TODO(miek): disable error checking for simple lookups?
|
||||||
|
ex: newDNSEx(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, host := range hosts {
|
for i, host := range hosts {
|
||||||
|
@ -31,7 +31,6 @@ func NewLookup(hosts []string) Proxy {
|
||||||
Conns: 0,
|
Conns: 0,
|
||||||
Fails: 0,
|
Fails: 0,
|
||||||
FailTimeout: upstream.FailTimeout,
|
FailTimeout: upstream.FailTimeout,
|
||||||
Exchanger: newDNSEx(host),
|
|
||||||
|
|
||||||
Unhealthy: false,
|
Unhealthy: false,
|
||||||
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
||||||
|
@ -50,7 +49,7 @@ func NewLookup(hosts []string) Proxy {
|
||||||
}
|
}
|
||||||
upstream.Hosts[i] = uh
|
upstream.Hosts[i] = uh
|
||||||
}
|
}
|
||||||
p.Upstreams = []Upstream{upstream}
|
p.Upstreams = &[]Upstream{upstream}
|
||||||
return p
|
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) {
|
func (p Proxy) lookup(state request.Request) (*dns.Msg, error) {
|
||||||
for _, upstream := range p.Upstreams {
|
for _, upstream := range *p.Upstreams {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Since Select() should give us "up" hosts, keep retrying
|
// 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)
|
atomic.AddInt64(&host.Conns, 1)
|
||||||
|
|
||||||
reply, backendErr := host.Exchange(state)
|
reply, backendErr := upstream.Exchanger().Exchange(host.Name, state)
|
||||||
|
|
||||||
atomic.AddInt64(&host.Conns, -1)
|
atomic.AddInt64(&host.Conns, -1)
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@ var (
|
||||||
Name: "request_duration_milliseconds",
|
Name: "request_duration_milliseconds",
|
||||||
Buckets: append(prometheus.DefBuckets, []float64{50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 10000}...),
|
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.",
|
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.
|
// OnStartupMetrics sets up the metrics on startup. This is done for all proxy protocols.
|
||||||
func OnStartup() error {
|
func OnStartupMetrics() error {
|
||||||
metricsOnce.Do(func() {
|
metricsOnce.Do(func() {
|
||||||
prometheus.MustRegister(RequestDuration)
|
prometheus.MustRegister(RequestDuration)
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,8 +21,12 @@ var (
|
||||||
|
|
||||||
// Proxy represents a middleware instance that can proxy requests to another (DNS) server.
|
// Proxy represents a middleware instance that can proxy requests to another (DNS) server.
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
Upstreams []Upstream
|
|
||||||
|
// 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
|
// Upstream manages a pool of proxy upstream hosts. Select should return a
|
||||||
|
@ -34,8 +38,8 @@ type Upstream interface {
|
||||||
Select() *UpstreamHost
|
Select() *UpstreamHost
|
||||||
// Checks if subpdomain is not an ignored.
|
// Checks if subpdomain is not an ignored.
|
||||||
IsAllowedPath(string) bool
|
IsAllowedPath(string) bool
|
||||||
// Options returns the options set for this upstream
|
// Exchanger returns the exchanger to be used for this upstream.
|
||||||
Options() Options
|
Exchanger() Exchanger
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamHostDownFunc can be used to customize how Down behaves.
|
// UpstreamHostDownFunc can be used to customize how Down behaves.
|
||||||
|
@ -50,7 +54,6 @@ type UpstreamHost struct {
|
||||||
Unhealthy bool
|
Unhealthy bool
|
||||||
CheckDown UpstreamHostDownFunc
|
CheckDown UpstreamHostDownFunc
|
||||||
WithoutPathPrefix string
|
WithoutPathPrefix string
|
||||||
Exchanger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down checks whether the upstream host is down or not.
|
// Down checks whether the upstream host is down or not.
|
||||||
|
@ -70,11 +73,12 @@ func (uh *UpstreamHost) Down() bool {
|
||||||
var tryDuration = 60 * time.Second
|
var tryDuration = 60 * time.Second
|
||||||
|
|
||||||
// ServeDNS satisfies the middleware.Handler interface.
|
// ServeDNS satisfies the middleware.Handler interface.
|
||||||
|
|
||||||
func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
var span, child ot.Span
|
var span, child ot.Span
|
||||||
span = ot.SpanFromContext(ctx)
|
span = ot.SpanFromContext(ctx)
|
||||||
state := request.Request{W: w, Req: r}
|
state := request.Request{W: w, Req: r}
|
||||||
for _, upstream := range p.Upstreams {
|
for _, upstream := range *p.Upstreams {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Since Select() should give us "up" hosts, keep retrying
|
// 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()
|
host := upstream.Select()
|
||||||
if host == nil {
|
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
|
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)
|
atomic.AddInt64(&host.Conns, 1)
|
||||||
|
|
||||||
reply, backendErr := host.Exchange(state)
|
reply, backendErr := upstream.Exchanger().Exchange(host.Name, state)
|
||||||
|
|
||||||
atomic.AddInt64(&host.Conns, -1)
|
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 {
|
if backendErr == nil {
|
||||||
w.WriteMsg(reply)
|
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
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
@ -121,7 +125,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
}(host, timeout)
|
}(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
|
return dns.RcodeServerFailure, errUnreachable
|
||||||
}
|
}
|
||||||
|
|
21
middleware/proxy/response.go
Normal file
21
middleware/proxy/response.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -19,11 +19,24 @@ func setup(c *caddy.Controller) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return middleware.Error("proxy", err)
|
return middleware.Error("proxy", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
P := &Proxy{}
|
||||||
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,7 @@ type staticUpstream struct {
|
||||||
}
|
}
|
||||||
WithoutPathPrefix string
|
WithoutPathPrefix string
|
||||||
IgnoredSubDomains []string
|
IgnoredSubDomains []string
|
||||||
options Options
|
ex Exchanger
|
||||||
Protocol protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options ...
|
|
||||||
type Options struct {
|
|
||||||
Ecs []*net.IPNet // EDNS0 CLIENT SUBNET address (v4/v6) to add in CIDR notaton.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStaticUpstreams parses the configuration input and sets up
|
// NewStaticUpstreams parses the configuration input and sets up
|
||||||
|
@ -58,7 +52,7 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
|
||||||
Spray: nil,
|
Spray: nil,
|
||||||
FailTimeout: 10 * time.Second,
|
FailTimeout: 10 * time.Second,
|
||||||
MaxFails: 1,
|
MaxFails: 1,
|
||||||
Protocol: dnsProto,
|
ex: newDNSEx(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Args(&upstream.from) {
|
if !c.Args(&upstream.from) {
|
||||||
|
@ -89,7 +83,6 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
|
||||||
Fails: 0,
|
Fails: 0,
|
||||||
FailTimeout: upstream.FailTimeout,
|
FailTimeout: upstream.FailTimeout,
|
||||||
Unhealthy: false,
|
Unhealthy: false,
|
||||||
Exchanger: newDNSEx(host),
|
|
||||||
|
|
||||||
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
||||||
return func(uh *UpstreamHost) bool {
|
return func(uh *UpstreamHost) bool {
|
||||||
|
@ -106,14 +99,6 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
|
||||||
}(upstream),
|
}(upstream),
|
||||||
WithoutPathPrefix: upstream.WithoutPathPrefix,
|
WithoutPathPrefix: upstream.WithoutPathPrefix,
|
||||||
}
|
}
|
||||||
switch upstream.Protocol {
|
|
||||||
// case https_google:
|
|
||||||
|
|
||||||
case dnsProto:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
// Already done in the initialization above.
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream.Hosts[i] = uh
|
upstream.Hosts[i] = uh
|
||||||
}
|
}
|
||||||
|
@ -135,10 +120,6 @@ func (u *staticUpstream) From() string {
|
||||||
return u.from
|
return u.from
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) Options() Options {
|
|
||||||
return u.options
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "policy":
|
case "policy":
|
||||||
|
@ -208,9 +189,14 @@ func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error {
|
||||||
}
|
}
|
||||||
switch encArgs[0] {
|
switch encArgs[0] {
|
||||||
case "dns":
|
case "dns":
|
||||||
u.Protocol = dnsProto
|
u.ex = newDNSEx()
|
||||||
case "https_google":
|
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:
|
default:
|
||||||
return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0])
|
return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0])
|
||||||
}
|
}
|
||||||
|
@ -305,3 +291,5 @@ func (u *staticUpstream) IsAllowedPath(name string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *staticUpstream) Exchanger() Exchanger { return u.ex }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue