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:
parent
14b84ce02b
commit
e635b4e773
7 changed files with 86 additions and 62 deletions
|
@ -19,12 +19,12 @@ However, advanced features including load balancing can be utilized with an expa
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
proxy from to... {
|
proxy from to... {
|
||||||
policy random | least_conn | round_robin
|
policy random | least_conn | round_robin
|
||||||
fail_timeout duration
|
fail_timeout duration
|
||||||
max_fails integer
|
max_fails integer
|
||||||
health_check path:port [duration]
|
health_check path:port [duration]
|
||||||
except ignored_names...
|
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.
|
* `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").
|
* `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.
|
* `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
|
* `ecs` add EDNS0 client submit metadata to the outgoing query. This can be optionally be followed
|
||||||
query. TODO(miek): implement.
|
by an IPv4 and/or IPv6 address. If none is specified the server's addresses are used.
|
||||||
|
|
||||||
## Policies
|
## Policies
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ package proxy
|
||||||
// style as the proxy.
|
// style as the proxy.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,22 +16,20 @@ func New(hosts []string) Proxy {
|
||||||
p := Proxy{Next: nil, Client: Clients()}
|
p := Proxy{Next: nil, Client: Clients()}
|
||||||
|
|
||||||
upstream := &staticUpstream{
|
upstream := &staticUpstream{
|
||||||
from: "",
|
from: "",
|
||||||
proxyHeaders: make(http.Header),
|
Hosts: make([]*UpstreamHost, len(hosts)),
|
||||||
Hosts: make([]*UpstreamHost, len(hosts)),
|
Policy: &Random{},
|
||||||
Policy: &Random{},
|
FailTimeout: 10 * time.Second,
|
||||||
FailTimeout: 10 * time.Second,
|
MaxFails: 1,
|
||||||
MaxFails: 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, host := range hosts {
|
for i, host := range hosts {
|
||||||
uh := &UpstreamHost{
|
uh := &UpstreamHost{
|
||||||
Name: host,
|
Name: host,
|
||||||
Conns: 0,
|
Conns: 0,
|
||||||
Fails: 0,
|
Fails: 0,
|
||||||
FailTimeout: upstream.FailTimeout,
|
FailTimeout: upstream.FailTimeout,
|
||||||
Unhealthy: false,
|
Unhealthy: false,
|
||||||
ExtraHeaders: upstream.proxyHeaders, // TODO(miek): fixer the fix
|
|
||||||
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
||||||
return func(uh *UpstreamHost) bool {
|
return func(uh *UpstreamHost) bool {
|
||||||
if uh.Unhealthy {
|
if uh.Unhealthy {
|
||||||
|
|
|
@ -3,14 +3,13 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/miekg/coredns/middleware"
|
"github.com/miekg/coredns/middleware"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errUnreachable = errors.New("unreachable backend")
|
var errUnreachable = errors.New("unreachable backend")
|
||||||
|
@ -36,6 +35,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
|
||||||
|
Options() Options
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpstreamHostDownFunc can be used to customize how Down behaves.
|
// UpstreamHostDownFunc can be used to customize how Down behaves.
|
||||||
|
@ -48,7 +49,6 @@ type UpstreamHost struct {
|
||||||
Fails int32
|
Fails int32
|
||||||
FailTimeout time.Duration
|
FailTimeout time.Duration
|
||||||
Unhealthy bool
|
Unhealthy bool
|
||||||
ExtraHeaders http.Header
|
|
||||||
CheckDown UpstreamHostDownFunc
|
CheckDown UpstreamHostDownFunc
|
||||||
WithoutPathPrefix string
|
WithoutPathPrefix string
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ 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) {
|
||||||
for _, upstream := range p.Upstreams {
|
for _, upstream := range p.Upstreams {
|
||||||
// allowed bla bla bla TODO(miek): fix full proxy spec from caddy
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Since Select() should give us "up" hosts, keep retrying
|
// 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 {
|
if host == nil {
|
||||||
return dns.RcodeServerFailure, errUnreachable
|
return dns.RcodeServerFailure, errUnreachable
|
||||||
}
|
}
|
||||||
// TODO(miek): PORT!
|
reverseproxy := ReverseProxy{Host: host.Name, Client: p.Client, Options: upstream.Options()}
|
||||||
reverseproxy := ReverseProxy{Host: host.Name, Client: p.Client}
|
|
||||||
|
|
||||||
atomic.AddInt64(&host.Conns, 1)
|
atomic.AddInt64(&host.Conns, 1)
|
||||||
backendErr := reverseproxy.ServeDNS(w, r, nil)
|
backendErr := reverseproxy.ServeDNS(w, r, nil)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package proxy
|
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() {
|
func init() {
|
||||||
tryDuration = 50 * time.Millisecond // prevent tests from hanging
|
tryDuration = 50 * time.Millisecond // prevent tests from hanging
|
||||||
|
|
|
@ -8,8 +8,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReverseProxy struct {
|
type ReverseProxy struct {
|
||||||
Host string
|
Host string
|
||||||
Client Client
|
Client Client
|
||||||
|
Options Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ReverseProxy) ServeDNS(w dns.ResponseWriter, r *dns.Msg, extra []dns.RR) error {
|
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
|
reply *dns.Msg
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
state := middleware.State{W: w, Req: r}
|
|
||||||
|
|
||||||
// We forward the original request, no need to fiddle with EDNS0 opt sizes.
|
switch {
|
||||||
if state.Proto() == "tcp" {
|
case middleware.Proto(w) == "tcp":
|
||||||
reply, err = middleware.Exchange(p.Client.TCP, r, p.Host)
|
reply, err = middleware.Exchange(p.Client.TCP, r, p.Host)
|
||||||
} else {
|
default:
|
||||||
reply, err = middleware.Exchange(p.Client.UDP, r, p.Host)
|
reply, err = middleware.Exchange(p.Client.UDP, r, p.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
@ -19,11 +20,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type staticUpstream struct {
|
type staticUpstream struct {
|
||||||
from string
|
from string
|
||||||
// TODO(miek): allows use to added headers
|
Hosts HostPool
|
||||||
proxyHeaders http.Header // TODO(miek): kill these
|
Policy Policy
|
||||||
Hosts HostPool
|
|
||||||
Policy Policy
|
|
||||||
|
|
||||||
FailTimeout time.Duration
|
FailTimeout time.Duration
|
||||||
MaxFails int32
|
MaxFails int32
|
||||||
|
@ -34,6 +33,10 @@ type staticUpstream struct {
|
||||||
}
|
}
|
||||||
WithoutPathPrefix string
|
WithoutPathPrefix string
|
||||||
IgnoredSubDomains []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
|
// NewStaticUpstreams parses the configuration input and sets up
|
||||||
|
@ -42,12 +45,11 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
|
||||||
var upstreams []Upstream
|
var upstreams []Upstream
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
upstream := &staticUpstream{
|
upstream := &staticUpstream{
|
||||||
from: "",
|
from: "",
|
||||||
proxyHeaders: make(http.Header),
|
Hosts: nil,
|
||||||
Hosts: nil,
|
Policy: &Random{},
|
||||||
Policy: &Random{},
|
FailTimeout: 10 * time.Second,
|
||||||
FailTimeout: 10 * time.Second,
|
MaxFails: 1,
|
||||||
MaxFails: 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Args(&upstream.from) {
|
if !c.Args(&upstream.from) {
|
||||||
|
@ -57,6 +59,15 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
|
||||||
if len(to) == 0 {
|
if len(to) == 0 {
|
||||||
return upstreams, c.ArgErr()
|
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() {
|
for c.NextBlock() {
|
||||||
if err := parseBlock(&c, upstream); err != nil {
|
if err := parseBlock(&c, upstream); err != nil {
|
||||||
|
@ -67,12 +78,11 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
|
||||||
upstream.Hosts = make([]*UpstreamHost, len(to))
|
upstream.Hosts = make([]*UpstreamHost, len(to))
|
||||||
for i, host := range to {
|
for i, host := range to {
|
||||||
uh := &UpstreamHost{
|
uh := &UpstreamHost{
|
||||||
Name: host,
|
Name: defaultHostPort(host),
|
||||||
Conns: 0,
|
Conns: 0,
|
||||||
Fails: 0,
|
Fails: 0,
|
||||||
FailTimeout: upstream.FailTimeout,
|
FailTimeout: upstream.FailTimeout,
|
||||||
Unhealthy: false,
|
Unhealthy: false,
|
||||||
ExtraHeaders: upstream.proxyHeaders,
|
|
||||||
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
||||||
return func(uh *UpstreamHost) bool {
|
return func(uh *UpstreamHost) bool {
|
||||||
if uh.Unhealthy {
|
if uh.Unhealthy {
|
||||||
|
@ -107,6 +117,10 @@ func (u *staticUpstream) From() string {
|
||||||
return u.from
|
return u.from
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *staticUpstream) Options() Options {
|
||||||
|
return u.options
|
||||||
|
}
|
||||||
|
|
||||||
func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
|
func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "policy":
|
case "policy":
|
||||||
|
@ -153,12 +167,6 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
|
||||||
}
|
}
|
||||||
u.HealthCheck.Interval = dur
|
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":
|
case "without":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return c.ArgErr()
|
return c.ArgErr()
|
||||||
|
@ -173,6 +181,12 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
|
||||||
ignoredDomains[i] = strings.ToLower(dns.Fqdn(ignoredDomains[i]))
|
ignoredDomains[i] = strings.ToLower(dns.Fqdn(ignoredDomains[i]))
|
||||||
}
|
}
|
||||||
u.IgnoredSubDomains = ignoredDomains
|
u.IgnoredSubDomains = ignoredDomains
|
||||||
|
case "ecs":
|
||||||
|
ips := c.RemainingArgs()
|
||||||
|
if len(ips) > 0 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return c.Errf("unknown property '%s'", c.Val())
|
return c.Errf("unknown property '%s'", c.Val())
|
||||||
}
|
}
|
||||||
|
@ -247,3 +261,11 @@ func (u *staticUpstream) IsAllowedPath(name string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultHostPort(s string) string {
|
||||||
|
_, _, e := net.SplitHostPort(s)
|
||||||
|
if e == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(s, "53")
|
||||||
|
}
|
||||||
|
|
|
@ -53,13 +53,15 @@ func (s *State) RemoteAddr() string {
|
||||||
return s.W.RemoteAddr().String()
|
return s.W.RemoteAddr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proto gets the protocol used as the transport. This
|
// Proto gets the protocol used as the transport. This will be udp or tcp.
|
||||||
// will be udp or tcp.
|
func (s *State) Proto() string { return Proto(s.W) }
|
||||||
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 Proto(w dns.ResponseWriter) string {
|
||||||
|
if _, ok := w.RemoteAddr().(*net.UDPAddr); ok {
|
||||||
return "udp"
|
return "udp"
|
||||||
}
|
}
|
||||||
if _, ok := s.W.RemoteAddr().(*net.TCPAddr); ok {
|
if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
return "tcp"
|
return "tcp"
|
||||||
}
|
}
|
||||||
return "udp"
|
return "udp"
|
||||||
|
|
Loading…
Add table
Reference in a new issue