middleware/proxy: multiple enhancements (#145)

Add port 53 in the proxy host if not specified.
Check if the host is actually an IP address (v4 or v6)
Remove the http headers and other TODOs
This commit is contained in:
Miek Gieben 2016-04-30 15:54:41 +01:00
parent 14b84ce02b
commit e635b4e773
7 changed files with 86 additions and 62 deletions

View file

@ -24,7 +24,7 @@ proxy from to... {
max_fails integer
health_check path:port [duration]
except ignored_names...
tcp
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

View file

@ -4,7 +4,6 @@ package proxy
// style as the proxy.
import (
"net/http"
"sync/atomic"
"time"
@ -18,7 +17,6 @@ func New(hosts []string) Proxy {
upstream := &staticUpstream{
from: "",
proxyHeaders: make(http.Header),
Hosts: make([]*UpstreamHost, len(hosts)),
Policy: &Random{},
FailTimeout: 10 * time.Second,
@ -32,7 +30,6 @@ func New(hosts []string) Proxy {
Fails: 0,
FailTimeout: upstream.FailTimeout,
Unhealthy: false,
ExtraHeaders: upstream.proxyHeaders, // TODO(miek): fixer the fix
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
return func(uh *UpstreamHost) bool {
if uh.Unhealthy {

View file

@ -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)

View file

@ -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

View file

@ -10,6 +10,7 @@ import (
type ReverseProxy struct {
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)
}

View file

@ -1,6 +1,7 @@
package proxy
import (
"fmt"
"io"
"io/ioutil"
"net"
@ -20,8 +21,6 @@ var (
type staticUpstream struct {
from string
// TODO(miek): allows use to added headers
proxyHeaders http.Header // TODO(miek): kill these
Hosts HostPool
Policy Policy
@ -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
@ -43,7 +46,6 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
for c.Next() {
upstream := &staticUpstream{
from: "",
proxyHeaders: make(http.Header),
Hosts: nil,
Policy: &Random{},
FailTimeout: 10 * time.Second,
@ -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,
Name: defaultHostPort(host),
Conns: 0,
Fails: 0,
FailTimeout: upstream.FailTimeout,
Unhealthy: false,
ExtraHeaders: upstream.proxyHeaders,
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")
}

View file

@ -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"