diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md index c259659b7..40f83caa4 100644 --- a/middleware/proxy/README.md +++ b/middleware/proxy/README.md @@ -19,12 +19,12 @@ However, advanced features including load balancing can be utilized with an expa ~~~ proxy from to... { - policy random | least_conn | round_robin - fail_timeout duration - max_fails integer - health_check path:port [duration] - except ignored_names... - tcp + policy random | least_conn | round_robin + fail_timeout duration + max_fails integer + health_check path:port [duration] + except ignored_names... + ecs [v4 address/mask] [v6 address/mask] (TODO) } ~~~ @@ -35,8 +35,8 @@ proxy from to... { * `max_fails` is the number of failures within fail_timeout that are needed before considering a backend to be down. If 0, the backend will never be marked as down. Default is 1. * `health_check` will check path (on port) on each backend. If a backend returns a status code of 200-399, then that backend is healthy. If it doesn't, the backend is marked as unhealthy for duration and no requests are routed to it. If this option is not provided then health checks are disabled. The default duration is 10 seconds ("10s"). * `ignored_names...` is a space-separated list of paths to exclude from proxying. Requests that match any of these paths will be passed thru. -* `tcp` use TCP for all upstream queries, otherwise it depends on the transport of the incoming - query. TODO(miek): implement. +* `ecs` add EDNS0 client submit metadata to the outgoing query. This can be optionally be followed + by an IPv4 and/or IPv6 address. If none is specified the server's addresses are used. ## Policies diff --git a/middleware/proxy/lookup.go b/middleware/proxy/lookup.go index 92ce9ae2c..ac3da261d 100644 --- a/middleware/proxy/lookup.go +++ b/middleware/proxy/lookup.go @@ -4,7 +4,6 @@ package proxy // style as the proxy. import ( - "net/http" "sync/atomic" "time" @@ -17,22 +16,20 @@ func New(hosts []string) Proxy { p := Proxy{Next: nil, Client: Clients()} upstream := &staticUpstream{ - from: "", - proxyHeaders: make(http.Header), - Hosts: make([]*UpstreamHost, len(hosts)), - Policy: &Random{}, - FailTimeout: 10 * time.Second, - MaxFails: 1, + from: "", + Hosts: make([]*UpstreamHost, len(hosts)), + Policy: &Random{}, + FailTimeout: 10 * time.Second, + MaxFails: 1, } for i, host := range hosts { uh := &UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - Unhealthy: false, - ExtraHeaders: upstream.proxyHeaders, // TODO(miek): fixer the fix + Name: host, + Conns: 0, + Fails: 0, + FailTimeout: upstream.FailTimeout, + Unhealthy: false, CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc { return func(uh *UpstreamHost) bool { if uh.Unhealthy { diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index dfdfd082e..2b84ed28d 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -3,14 +3,13 @@ package proxy import ( "errors" - "net/http" "sync/atomic" "time" - "golang.org/x/net/context" - "github.com/miekg/coredns/middleware" + "github.com/miekg/dns" + "golang.org/x/net/context" ) var errUnreachable = errors.New("unreachable backend") @@ -36,6 +35,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 } // UpstreamHostDownFunc can be used to customize how Down behaves. @@ -48,7 +49,6 @@ type UpstreamHost struct { Fails int32 FailTimeout time.Duration Unhealthy bool - ExtraHeaders http.Header CheckDown UpstreamHostDownFunc WithoutPathPrefix string } @@ -71,7 +71,6 @@ 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) { for _, upstream := range p.Upstreams { - // allowed bla bla bla TODO(miek): fix full proxy spec from caddy start := time.Now() // Since Select() should give us "up" hosts, keep retrying @@ -81,8 +80,7 @@ func (p Proxy) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( if host == nil { return dns.RcodeServerFailure, errUnreachable } - // TODO(miek): PORT! - reverseproxy := ReverseProxy{Host: host.Name, Client: p.Client} + reverseproxy := ReverseProxy{Host: host.Name, Client: p.Client, Options: upstream.Options()} atomic.AddInt64(&host.Conns, 1) backendErr := reverseproxy.ServeDNS(w, r, nil) diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index e4b403254..afb001d9f 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -1,5 +1,10 @@ package proxy +// Also test these inputs: +//.:1053 { +//proxy . ::1 2001:4860:4860::8844 8.8.8.8:54 [2001:4860:4860::8845]:53 +//} + /* func init() { tryDuration = 50 * time.Millisecond // prevent tests from hanging diff --git a/middleware/proxy/reverseproxy.go b/middleware/proxy/reverseproxy.go index 83221ba78..3f36dac17 100644 --- a/middleware/proxy/reverseproxy.go +++ b/middleware/proxy/reverseproxy.go @@ -8,8 +8,9 @@ import ( ) type ReverseProxy struct { - Host string - Client Client + Host string + Client Client + Options Options } func (p ReverseProxy) ServeDNS(w dns.ResponseWriter, r *dns.Msg, extra []dns.RR) error { @@ -17,12 +18,11 @@ func (p ReverseProxy) ServeDNS(w dns.ResponseWriter, r *dns.Msg, extra []dns.RR) reply *dns.Msg err error ) - state := middleware.State{W: w, Req: r} - // We forward the original request, no need to fiddle with EDNS0 opt sizes. - if state.Proto() == "tcp" { + switch { + case middleware.Proto(w) == "tcp": reply, err = middleware.Exchange(p.Client.TCP, r, p.Host) - } else { + default: reply, err = middleware.Exchange(p.Client.UDP, r, p.Host) } diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go index 7f339c01f..66f07e418 100644 --- a/middleware/proxy/upstream.go +++ b/middleware/proxy/upstream.go @@ -1,6 +1,7 @@ package proxy import ( + "fmt" "io" "io/ioutil" "net" @@ -19,11 +20,9 @@ var ( ) type staticUpstream struct { - from string - // TODO(miek): allows use to added headers - proxyHeaders http.Header // TODO(miek): kill these - Hosts HostPool - Policy Policy + from string + Hosts HostPool + Policy Policy FailTimeout time.Duration MaxFails int32 @@ -34,6 +33,10 @@ type staticUpstream struct { } WithoutPathPrefix string IgnoredSubDomains []string + options 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 @@ -42,12 +45,11 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) { var upstreams []Upstream for c.Next() { upstream := &staticUpstream{ - from: "", - proxyHeaders: make(http.Header), - Hosts: nil, - Policy: &Random{}, - FailTimeout: 10 * time.Second, - MaxFails: 1, + from: "", + Hosts: nil, + Policy: &Random{}, + FailTimeout: 10 * time.Second, + MaxFails: 1, } if !c.Args(&upstream.from) { @@ -57,6 +59,15 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) { if len(to) == 0 { return upstreams, c.ArgErr() } + for _, host := range to { + h, _, err := net.SplitHostPort(host) + if err != nil { + h = host + } + if x := net.ParseIP(h); x == nil { + return upstreams, fmt.Errorf("not an IP address: `%s'", h) + } + } for c.NextBlock() { if err := parseBlock(&c, upstream); err != nil { @@ -67,12 +78,11 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) { upstream.Hosts = make([]*UpstreamHost, len(to)) for i, host := range to { uh := &UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - Unhealthy: false, - ExtraHeaders: upstream.proxyHeaders, + Name: defaultHostPort(host), + Conns: 0, + Fails: 0, + FailTimeout: upstream.FailTimeout, + Unhealthy: false, CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc { return func(uh *UpstreamHost) bool { if uh.Unhealthy { @@ -107,6 +117,10 @@ func (u *staticUpstream) From() string { return u.from } +func (u *staticUpstream) Options() Options { + return u.options +} + func parseBlock(c *parse.Dispenser, u *staticUpstream) error { switch c.Val() { case "policy": @@ -153,12 +167,6 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error { } u.HealthCheck.Interval = dur } - case "proxy_header": - var header, value string - if !c.Args(&header, &value) { - return c.ArgErr() - } - u.proxyHeaders.Add(header, value) case "without": if !c.NextArg() { return c.ArgErr() @@ -173,6 +181,12 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error { ignoredDomains[i] = strings.ToLower(dns.Fqdn(ignoredDomains[i])) } u.IgnoredSubDomains = ignoredDomains + case "ecs": + ips := c.RemainingArgs() + if len(ips) > 0 { + + } + default: return c.Errf("unknown property '%s'", c.Val()) } @@ -247,3 +261,11 @@ func (u *staticUpstream) IsAllowedPath(name string) bool { } return true } + +func defaultHostPort(s string) string { + _, _, e := net.SplitHostPort(s) + if e == nil { + return s + } + return net.JoinHostPort(s, "53") +} diff --git a/middleware/state.go b/middleware/state.go index 34e6cdfc3..1028ab2a3 100644 --- a/middleware/state.go +++ b/middleware/state.go @@ -53,13 +53,15 @@ func (s *State) RemoteAddr() string { return s.W.RemoteAddr().String() } -// Proto gets the protocol used as the transport. This -// will be udp or tcp. -func (s *State) Proto() string { - if _, ok := s.W.RemoteAddr().(*net.UDPAddr); ok { +// Proto gets the protocol used as the transport. This will be udp or tcp. +func (s *State) Proto() string { return Proto(s.W) } + +// Proto gets the protocol used as the transport. This will be udp or tcp. +func Proto(w dns.ResponseWriter) string { + if _, ok := w.RemoteAddr().(*net.UDPAddr); ok { return "udp" } - if _, ok := s.W.RemoteAddr().(*net.TCPAddr); ok { + if _, ok := w.RemoteAddr().(*net.TCPAddr); ok { return "tcp" } return "udp"