diff options
Diffstat (limited to 'middleware/proxy')
-rw-r--r-- | middleware/proxy/README.md | 175 | ||||
-rw-r--r-- | middleware/proxy/dns.go | 106 | ||||
-rw-r--r-- | middleware/proxy/dnstap_test.go | 57 | ||||
-rw-r--r-- | middleware/proxy/exchanger.go | 22 | ||||
-rw-r--r-- | middleware/proxy/google.go | 244 | ||||
-rw-r--r-- | middleware/proxy/google_rr.go | 89 | ||||
-rw-r--r-- | middleware/proxy/google_test.go | 5 | ||||
-rw-r--r-- | middleware/proxy/grpc.go | 96 | ||||
-rw-r--r-- | middleware/proxy/grpc_test.go | 71 | ||||
-rw-r--r-- | middleware/proxy/lookup.go | 132 | ||||
-rw-r--r-- | middleware/proxy/metrics.go | 30 | ||||
-rw-r--r-- | middleware/proxy/proxy.go | 195 | ||||
-rw-r--r-- | middleware/proxy/proxy_test.go | 87 | ||||
-rw-r--r-- | middleware/proxy/response.go | 21 | ||||
-rw-r--r-- | middleware/proxy/setup.go | 46 | ||||
-rw-r--r-- | middleware/proxy/upstream.go | 234 | ||||
-rw-r--r-- | middleware/proxy/upstream_test.go | 324 |
17 files changed, 0 insertions, 1934 deletions
diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md deleted file mode 100644 index 17a43f68e..000000000 --- a/middleware/proxy/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# proxy - -*proxy* facilitates both a basic reverse proxy and a robust load balancer. - -The proxy has support for multiple backends. The load balancing features include multiple policies, -health checks, and failovers. If all hosts fail their health check the proxy middleware will fail -back to randomly selecting a target and sending packets to it. - -## Syntax - -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. - -However, advanced features including load balancing can be utilized with an expanded syntax: - -~~~ -proxy FROM TO... { - policy random|least_conn|round_robin - fail_timeout DURATION - max_fails INTEGER - health_check PATH:PORT [DURATION] - except IGNORED_NAMES... - spray - protocol [dns [force_tcp]|https_google [bootstrap ADDRESS...]|grpc [insecure|CACERT|KEY CERT|KEY CERT CACERT]] -} -~~~ - -* **FROM** is the name to match for the request to be proxied. -* **TO** is the destination endpoint to proxy to. At least one is required, but multiple may be - specified. **TO** may be an IP:Port pair, or may reference a file in resolv.conf format -* `policy` is the load balancing policy to use; applies only with multiple backends. May be one of - random, least_conn, or round_robin. Default is random. -* `fail_timeout` specifies how long to consider a backend as down after it has failed. While it is - down, requests will not be routed to that backend. A backend is "down" if CoreDNS fails to - communicate with it. The default value is 10 seconds ("10s"). -* `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 marked healthy for double the healthcheck duration. If it doesn't, - it is marked as unhealthy and no requests are routed to it. If this option is not provided then - health checks are disabled. The default duration is 30 seconds ("30s"). -* **IGNORED_NAMES** in `except` is a space-separated list of domains to exclude from proxying. - Requests that match none of these names 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. Note when - using this **TO** will be ignored. The `grpc` option will talk to a server that has implemented - the [DnsService](https://github.com/coredns/coredns/pb/dns.proto). - An out-of-tree middleware that implements the server side of this can be found at - [here](https://github.com/infobloxopen/coredns-grpc). - -## Policies - -There are three load-balancing policies available: -* `random` (default) - Randomly select a backend -* `least_conn` - Select the backend with the fewest active connections -* `round_robin` - Select the backend in round-robin fashion - -All polices implement randomly spraying packets to backend hosts when *no healthy* hosts are -available. This is to preeempt the case where the healthchecking (as a mechanism) fails. - -## Upstream Protocols - -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`: uses the standard DNS exchange. You can pass `force_tcp` to make sure that the proxied connection is performed - over TCP, regardless of the inbound request's protocol. -* `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. -* `grpc`: options are used to control how the TLS connection is made to the gRPC server. - * None - No client authentication is used, and the system CAs are used to verify the server certificate. - * `insecure` - TLS is not used, the connection is made in plaintext (not good in production). - * **CACERT** - No client authentication is used, and the file **CACERT** is used to verify the server certificate. - * **KEY** **CERT** - Client authentication is used with the specified key/cert pair. The server - certificate is verified with the system CAs. - * **KEY** **CERT** **CACERT** - Client authentication is used with the specified key/cert pair. The - server certificate is verified using the **CACERT** file. - - An out-of-tree middleware that implements the server side of this can be found at - [here](https://github.com/infobloxopen/coredns-grpc). - -## Metrics - -If monitoring is enabled (via the *prometheus* directive) then the following metric is exported: - -* coredns_proxy_request_count_total{proto, proxy_proto, from} - -Where `proxy_proto` is the protocol used (`dns`, `grpc`, or `https_google`) and `from` is **FROM** -specified in the config, `proto` is the protocol used by the incoming query ("tcp" or "udp"). - -## Examples - -Proxy all requests within example.org. to a backend system: - -~~~ -proxy example.org 127.0.0.1:9005 -~~~ - -Load-balance all requests between three backends (using random policy): - -~~~ -proxy . 10.0.0.10:53 10.0.0.11:1053 10.0.0.12 -~~~ - -Same as above, but round-robin style: - -~~~ -proxy . 10.0.0.10:53 10.0.0.11:1053 10.0.0.12 { - policy round_robin -} -~~~ - -With health checks and proxy headers to pass hostname, IP, and scheme upstream: - -~~~ -proxy . 10.0.0.11:53 10.0.0.11:53 10.0.0.12:53 { - policy round_robin - health_check /health:8080 -} -~~~ - -Proxy everything except requests to miek.nl or example.org - -~~~ -proxy . 10.0.0.10:1234 { - except miek.nl example.org -} -~~~ - -Proxy everything except example.org using the host resolv.conf nameservers: - -~~~ -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 with HTTPS to `dns.google.com`, except `example.org`. Then have another proxy in -another stanza that uses plain DNS to resolve names under `example.org`. - -~~~ -. { - proxy . 1.2.3.4:53 { - except example.org - protocol https_google - } -} - -example.org { - proxy . 8.8.8.8:53 -} -~~~ diff --git a/middleware/proxy/dns.go b/middleware/proxy/dns.go deleted file mode 100644 index 4d8038422..000000000 --- a/middleware/proxy/dns.go +++ /dev/null @@ -1,106 +0,0 @@ -package proxy - -import ( - "context" - "net" - "time" - - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -type dnsEx struct { - Timeout time.Duration - Options -} - -// Options define the options understood by dns.Exchange. -type Options struct { - ForceTCP bool // If true use TCP for upstream no matter what -} - -func newDNSEx() *dnsEx { - return newDNSExWithOption(Options{}) -} - -func newDNSExWithOption(opt Options) *dnsEx { - return &dnsEx{Timeout: defaultTimeout * time.Second, Options: opt} -} - -func (d *dnsEx) Transport() string { - if d.Options.ForceTCP { - return "tcp" - } - - // The protocol will be determined by `state.Proto()` during Exchange. - return "" -} -func (d *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(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - proto := state.Proto() - if d.Options.ForceTCP { - proto = "tcp" - } - co, err := net.DialTimeout(proto, addr, d.Timeout) - if err != nil { - return nil, err - } - - reply, _, err := d.ExchangeConn(state.Req, co) - - co.Close() - - if reply != nil && reply.Truncated { - // Suppress proxy error for truncated responses - err = nil - } - - if err != nil { - return nil, err - } - // Make sure it fits in the DNS response. - reply, _ = state.Scrub(reply) - reply.Compress = true - reply.Id = state.Req.Id - - return reply, nil -} - -func (d *dnsEx) ExchangeConn(m *dns.Msg, co net.Conn) (*dns.Msg, time.Duration, error) { - start := time.Now() - r, err := exchange(m, co) - rtt := time.Since(start) - - return r, rtt, err -} - -func exchange(m *dns.Msg, co net.Conn) (*dns.Msg, error) { - opt := m.IsEdns0() - - udpsize := uint16(dns.MinMsgSize) - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= dns.MinMsgSize { - udpsize = opt.UDPSize() - } - - dnsco := &dns.Conn{Conn: co, UDPSize: udpsize} - - writeDeadline := time.Now().Add(defaultTimeout) - dnsco.SetWriteDeadline(writeDeadline) - dnsco.WriteMsg(m) - - readDeadline := time.Now().Add(defaultTimeout) - co.SetReadDeadline(readDeadline) - r, err := dnsco.ReadMsg() - - dnsco.Close() - if r == nil { - return nil, err - } - return r, err -} diff --git a/middleware/proxy/dnstap_test.go b/middleware/proxy/dnstap_test.go deleted file mode 100644 index b3c31c207..000000000 --- a/middleware/proxy/dnstap_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package proxy - -import ( - "testing" - - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/dnstap/test" - mwtest "github.com/coredns/coredns/middleware/test" - "github.com/coredns/coredns/request" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" - "golang.org/x/net/context" -) - -func testCase(t *testing.T, ex Exchanger, q, r *dns.Msg, datq, datr *msg.Data) { - tapq := datq.ToOutsideQuery(tap.Message_FORWARDER_QUERY) - tapr := datr.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE) - ctx := test.Context{} - err := toDnstap(&ctx, "10.240.0.1:40212", ex, - request.Request{W: &mwtest.ResponseWriter{}, Req: q}, r, 0, 0) - if err != nil { - t.Fatal(err) - } - if len(ctx.Trap) != 2 { - t.Fatalf("messages: %d", len(ctx.Trap)) - } - if !test.MsgEqual(ctx.Trap[0], tapq) { - t.Errorf("want: %v\nhave: %v", tapq, ctx.Trap[0]) - } - if !test.MsgEqual(ctx.Trap[1], tapr) { - t.Errorf("want: %v\nhave: %v", tapr, ctx.Trap[1]) - } -} - -func TestDnstap(t *testing.T) { - q := mwtest.Case{Qname: "example.org", Qtype: dns.TypeA}.Msg() - r := mwtest.Case{ - Qname: "example.org.", Qtype: dns.TypeA, - Answer: []dns.RR{ - mwtest.A("example.org. 3600 IN A 10.0.0.1"), - }, - }.Msg() - tapq, tapr := test.TestingData(), test.TestingData() - testCase(t, newDNSEx(), q, r, tapq, tapr) - tapq.SocketProto = tap.SocketProtocol_TCP - tapr.SocketProto = tap.SocketProtocol_TCP - testCase(t, newDNSExWithOption(Options{ForceTCP: true}), q, r, tapq, tapr) - testCase(t, newGoogle("", []string{"8.8.8.8:53", "8.8.4.4:53"}), q, r, tapq, tapr) -} - -func TestNoDnstap(t *testing.T) { - err := toDnstap(context.TODO(), "", nil, request.Request{}, nil, 0, 0) - if err != nil { - t.Fatal(err) - } -} diff --git a/middleware/proxy/exchanger.go b/middleware/proxy/exchanger.go deleted file mode 100644 index b98a687e7..000000000 --- a/middleware/proxy/exchanger.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxy - -import ( - "context" - - "github.com/coredns/coredns/request" - "github.com/miekg/dns" -) - -// Exchanger is an interface that specifies a type implementing a DNS resolver that -// can use whatever transport it likes. -type Exchanger interface { - Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) - Protocol() string - - // Transport returns the only transport protocol used by this Exchanger or "". - // If the return value is "", Exchange must use `state.Proto()`. - Transport() string - - OnStartup(*Proxy) error - OnShutdown(*Proxy) error -} diff --git a/middleware/proxy/google.go b/middleware/proxy/google.go deleted file mode 100644 index b7e605fcb..000000000 --- a/middleware/proxy/google.go +++ /dev/null @@ -1,244 +0,0 @@ -package proxy - -import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net" - "net/http" - "net/url" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/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(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - v := url.Values{} - - v.Set("name", state.Name()) - v.Set("type", fmt.Sprintf("%d", state.QType())) - - 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, err := toMsg(gm) - if err != nil { - return nil, err - } - - 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) Transport() string { return "tcp" } -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} - - if len(*p.Upstreams) == 0 { - return fmt.Errorf("no upstreams defined") - } - - oldUpstream := (*p.Upstreams)[0] - - log.Printf("[INFO] Bootstrapping A records %q", g.endpoint) - - 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, err1) - } else { - - up := newUpstream(addrs, oldUpstream.(*staticUpstream)) - p.Upstreams = &[]Upstream{up} - - log.Printf("[INFO] Bootstrapping A records %q found: %v", g.endpoint, addrs) - } - } - - go func() { - tick := time.NewTicker(120 * time.Second) - - for { - select { - case <-tick.C: - - log.Printf("[INFO] Resolving A records %q", g.endpoint) - - new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA) - if err != nil { - log.Printf("[WARNING] Failed to resolve A records %q: %s", g.endpoint, err) - continue - } - - addrs, err1 := extractAnswer(new) - if err1 != nil { - log.Printf("[WARNING] Failed to resolve A records %q: %s", g.endpoint, err1) - continue - } - - up := newUpstream(addrs, oldUpstream.(*staticUpstream)) - p.Upstreams = &[]Upstream{up} - - log.Printf("[INFO] Resolving A records %q found: %v", g.endpoint, addrs) - - 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, old *staticUpstream) Upstream { - upstream := &staticUpstream{ - from: old.from, - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 3, - Future: 60 * time.Second, - }, - ex: old.ex, - WithoutPathPrefix: old.WithoutPathPrefix, - IgnoredSubDomains: old.IgnoredSubDomains, - } - - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts)) - for i, h := range hosts { - uh := &healthcheck.UpstreamHost{ - Name: h, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(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 deleted file mode 100644 index 3b9233b7b..000000000 --- a/middleware/proxy/google_rr.go +++ /dev/null @@ -1,89 +0,0 @@ -package proxy - -import ( - "fmt" - - "github.com/miekg/dns" -) - -// toMsg converts a googleMsg into the dns message. -func toMsg(g *googleMsg) (*dns.Msg, 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, err - } - } - for i := 0; i < len(m.Ns); i++ { - m.Ns[i], err = toRR(g.Authority[i]) - if err != nil { - return nil, err - } - } - for i := 0; i < len(m.Extra); i++ { - m.Extra[i], err = toRR(g.Additional[i]) - if err != nil { - return nil, err - } - } - - return m, 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/proxy/google_test.go b/middleware/proxy/google_test.go deleted file mode 100644 index 1ce591664..000000000 --- a/middleware/proxy/google_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package proxy - -// TODO(miek): -// Test cert failures - put those in SERVFAIL messages, but attach error code in TXT -// Test connecting to a a bad host. diff --git a/middleware/proxy/grpc.go b/middleware/proxy/grpc.go deleted file mode 100644 index 8aabf0eb0..000000000 --- a/middleware/proxy/grpc.go +++ /dev/null @@ -1,96 +0,0 @@ -package proxy - -import ( - "context" - "crypto/tls" - "log" - - "github.com/coredns/coredns/middleware/pkg/trace" - "github.com/coredns/coredns/pb" - "github.com/coredns/coredns/request" - - "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" - "github.com/miekg/dns" - opentracing "github.com/opentracing/opentracing-go" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -type grpcClient struct { - dialOpts []grpc.DialOption - clients map[string]pb.DnsServiceClient - conns []*grpc.ClientConn - upstream *staticUpstream -} - -func newGrpcClient(tls *tls.Config, u *staticUpstream) *grpcClient { - g := &grpcClient{upstream: u} - - if tls == nil { - g.dialOpts = append(g.dialOpts, grpc.WithInsecure()) - } else { - g.dialOpts = append(g.dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tls))) - } - g.clients = map[string]pb.DnsServiceClient{} - - return g -} - -func (g *grpcClient) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) { - msg, err := state.Req.Pack() - if err != nil { - return nil, err - } - - reply, err := g.clients[addr].Query(ctx, &pb.DnsPacket{Msg: msg}) - if err != nil { - return nil, err - } - d := new(dns.Msg) - err = d.Unpack(reply.Msg) - if err != nil { - return nil, err - } - return d, nil -} - -func (g *grpcClient) Transport() string { return "tcp" } - -func (g *grpcClient) Protocol() string { return "grpc" } - -func (g *grpcClient) OnShutdown(p *Proxy) error { - g.clients = map[string]pb.DnsServiceClient{} - for i, conn := range g.conns { - err := conn.Close() - if err != nil { - log.Printf("[WARNING] Error closing connection %d: %s\n", i, err) - } - } - g.conns = []*grpc.ClientConn{} - return nil -} - -func (g *grpcClient) OnStartup(p *Proxy) error { - dialOpts := g.dialOpts - if p.Trace != nil { - if t, ok := p.Trace.(trace.Trace); ok { - onlyIfParent := func(parentSpanCtx opentracing.SpanContext, method string, req, resp interface{}) bool { - return parentSpanCtx != nil - } - intercept := otgrpc.OpenTracingClientInterceptor(t.Tracer(), otgrpc.IncludingSpans(onlyIfParent)) - dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(intercept)) - } else { - log.Printf("[WARNING] Wrong type for trace middleware reference: %s", p.Trace) - } - } - for _, host := range g.upstream.Hosts { - conn, err := grpc.Dial(host.Name, dialOpts...) - if err != nil { - log.Printf("[WARNING] Skipping gRPC host '%s' due to Dial error: %s\n", host.Name, err) - } else { - g.clients[host.Name] = pb.NewDnsServiceClient(conn) - g.conns = append(g.conns, conn) - } - } - return nil -} diff --git a/middleware/proxy/grpc_test.go b/middleware/proxy/grpc_test.go deleted file mode 100644 index dcde7cc0e..000000000 --- a/middleware/proxy/grpc_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package proxy - -import ( - "testing" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - - "google.golang.org/grpc/grpclog" -) - -func pool() []*healthcheck.UpstreamHost { - return []*healthcheck.UpstreamHost{ - { - Name: "localhost:10053", - }, - { - Name: "localhost:10054", - }, - } -} - -func TestStartupShutdown(t *testing.T) { - grpclog.SetLogger(discard{}) - - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - Hosts: pool(), - FailTimeout: 10 * time.Second, - Future: 60 * time.Second, - MaxFails: 1, - }, - } - g := newGrpcClient(nil, upstream) - upstream.ex = g - - p := &Proxy{} - p.Upstreams = &[]Upstream{upstream} - - err := g.OnStartup(p) - if err != nil { - t.Errorf("Error starting grpc client exchanger: %s", err) - return - } - if len(g.clients) != len(pool()) { - t.Errorf("Expected %d grpc clients but found %d", len(pool()), len(g.clients)) - } - - err = g.OnShutdown(p) - if err != nil { - t.Errorf("Error stopping grpc client exchanger: %s", err) - return - } - if len(g.clients) != 0 { - t.Errorf("Shutdown didn't remove clients, found %d", len(g.clients)) - } - if len(g.conns) != 0 { - t.Errorf("Shutdown didn't remove conns, found %d", len(g.conns)) - } -} - -// discard is a Logger that outputs nothing. -type discard struct{} - -func (d discard) Fatal(args ...interface{}) {} -func (d discard) Fatalf(format string, args ...interface{}) {} -func (d discard) Fatalln(args ...interface{}) {} -func (d discard) Print(args ...interface{}) {} -func (d discard) Printf(format string, args ...interface{}) {} -func (d discard) Println(args ...interface{}) {} diff --git a/middleware/proxy/lookup.go b/middleware/proxy/lookup.go deleted file mode 100644 index 1963e7dbd..000000000 --- a/middleware/proxy/lookup.go +++ /dev/null @@ -1,132 +0,0 @@ -package proxy - -// functions other middleware might want to use to do lookup in the same style as the proxy. - -import ( - "context" - "fmt" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/request" - - "github.com/miekg/dns" -) - -// NewLookup create a new proxy with the hosts in host and a Random policy. -func NewLookup(hosts []string) Proxy { return NewLookupWithOption(hosts, Options{}) } - -// NewLookupWithOption process creates a simple round robin forward with potentially forced proto for upstream. -func NewLookupWithOption(hosts []string, opts Options) Proxy { - p := Proxy{Next: nil} - - // TODO(miek): this needs to be unified with upstream.go's NewStaticUpstreams, caddy uses NewHost - // we should copy/make something similar. - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 3, // TODO(miek): disable error checking for simple lookups? - Future: 60 * time.Second, - }, - ex: newDNSExWithOption(opts), - } - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts)) - - for i, host := range hosts { - uh := &healthcheck.UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(upstream), - WithoutPathPrefix: upstream.WithoutPathPrefix, - } - - upstream.Hosts[i] = uh - } - p.Upstreams = &[]Upstream{upstream} - return p -} - -// Lookup will use name and type to forge a new message and will send that upstream. It will -// set any EDNS0 options correctly so that downstream will be able to process the reply. -func (p Proxy) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) { - req := new(dns.Msg) - req.SetQuestion(name, typ) - state.SizeAndDo(req) - - state2 := request.Request{W: state.W, Req: req} - - return p.lookup(state2) -} - -// Forward forward the request in state as-is. Unlike Lookup that adds EDNS0 suffix to the message. -func (p Proxy) Forward(state request.Request) (*dns.Msg, error) { - return p.lookup(state) -} - -func (p Proxy) lookup(state request.Request) (*dns.Msg, error) { - upstream := p.match(state) - if upstream == nil { - return nil, errInvalidDomain - } - for { - start := time.Now() - reply := new(dns.Msg) - var backendErr error - - // Since Select() should give us "up" hosts, keep retrying - // hosts until timeout (or until we get a nil host). - for time.Since(start) < tryDuration { - host := upstream.Select() - if host == nil { - return nil, fmt.Errorf("%s: %s", errUnreachable, "no upstream host") - } - - // duplicated from proxy.go, but with a twist, we don't write the - // reply back to the client, we return it and there is no monitoring. - - atomic.AddInt64(&host.Conns, 1) - - reply, backendErr = upstream.Exchanger().Exchange(context.TODO(), host.Name, state) - - atomic.AddInt64(&host.Conns, -1) - - if backendErr == nil { - return reply, nil - } - timeout := host.FailTimeout - if timeout == 0 { - timeout = 10 * time.Second - } - atomic.AddInt32(&host.Fails, 1) - go func(host *healthcheck.UpstreamHost, timeout time.Duration) { - time.Sleep(timeout) - atomic.AddInt32(&host.Fails, -1) - }(host, timeout) - } - return nil, fmt.Errorf("%s: %s", errUnreachable, backendErr) - } -} diff --git a/middleware/proxy/metrics.go b/middleware/proxy/metrics.go deleted file mode 100644 index e9bb48d6f..000000000 --- a/middleware/proxy/metrics.go +++ /dev/null @@ -1,30 +0,0 @@ -package proxy - -import ( - "sync" - - "github.com/coredns/coredns/middleware" - - "github.com/prometheus/client_golang/prometheus" -) - -// Metrics the proxy middleware exports. -var ( - RequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Namespace: middleware.Namespace, - Subsystem: "proxy", - 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{"proto", "proxy_proto", "from"}) -) - -// OnStartupMetrics sets up the metrics on startup. This is done for all proxy protocols. -func OnStartupMetrics() error { - metricsOnce.Do(func() { - prometheus.MustRegister(RequestDuration) - }) - return nil -} - -var metricsOnce sync.Once diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go deleted file mode 100644 index 2a3c8002d..000000000 --- a/middleware/proxy/proxy.go +++ /dev/null @@ -1,195 +0,0 @@ -// Package proxy is middleware that proxies requests. -package proxy - -import ( - "errors" - "fmt" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/dnstap" - "github.com/coredns/coredns/middleware/dnstap/msg" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/request" - - tap "github.com/dnstap/golang-dnstap" - "github.com/miekg/dns" - ot "github.com/opentracing/opentracing-go" - "golang.org/x/net/context" -) - -var ( - errUnreachable = errors.New("unreachable backend") - errInvalidProtocol = errors.New("invalid protocol") - errInvalidDomain = errors.New("invalid path for proxy") -) - -// Proxy represents a middleware instance that can proxy requests to another (DNS) server. -type Proxy struct { - Next middleware.Handler - - // Upstreams is a pointer to a slice, so we can update the upstream (used for Google) - // midway. - - Upstreams *[]Upstream - - // Trace is the Trace middleware, if it is installed - // This is used by the grpc exchanger to trace through the grpc calls - Trace middleware.Handler -} - -// Upstream manages a pool of proxy upstream hosts. Select should return a -// suitable upstream host, or nil if no such hosts are available. -type Upstream interface { - // The domain name this upstream host should be routed on. - From() string - // Selects an upstream host to be routed to. - Select() *healthcheck.UpstreamHost - // Checks if subpdomain is not an ignored. - IsAllowedDomain(string) bool - // Exchanger returns the exchanger to be used for this upstream. - Exchanger() Exchanger - // Stops the upstream from proxying requests to shutdown goroutines cleanly. - Stop() error -} - -// tryDuration is how long to try upstream hosts; failures result in -// immediate retries until this duration ends or we get a nil host. -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} - - upstream := p.match(state) - if upstream == nil { - return middleware.NextOrFailure(p.Name(), p.Next, ctx, w, r) - } - - for { - start := time.Now() - reply := new(dns.Msg) - var backendErr error - - // Since Select() should give us "up" hosts, keep retrying - // hosts until timeout (or until we get a nil host). - for time.Since(start) < tryDuration { - host := upstream.Select() - if host == nil { - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return dns.RcodeServerFailure, fmt.Errorf("%s: %s", errUnreachable, "no upstream host") - } - - if span != nil { - child = span.Tracer().StartSpan("exchange", ot.ChildOf(span.Context())) - ctx = ot.ContextWithSpan(ctx, child) - } - - atomic.AddInt64(&host.Conns, 1) - queryEpoch := msg.Epoch() - - reply, backendErr = upstream.Exchanger().Exchange(ctx, host.Name, state) - - respEpoch := msg.Epoch() - atomic.AddInt64(&host.Conns, -1) - - if child != nil { - child.Finish() - } - - taperr := toDnstap(ctx, host.Name, upstream.Exchanger(), state, reply, queryEpoch, respEpoch) - - if backendErr == nil { - w.WriteMsg(reply) - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return 0, taperr - } - - timeout := host.FailTimeout - if timeout == 0 { - timeout = 10 * time.Second - } - atomic.AddInt32(&host.Fails, 1) - go func(host *healthcheck.UpstreamHost, timeout time.Duration) { - time.Sleep(timeout) - atomic.AddInt32(&host.Fails, -1) - }(host, timeout) - } - - RequestDuration.WithLabelValues(state.Proto(), upstream.Exchanger().Protocol(), upstream.From()).Observe(float64(time.Since(start) / time.Millisecond)) - - return dns.RcodeServerFailure, fmt.Errorf("%s: %s", errUnreachable, backendErr) - } -} - -func (p Proxy) match(state request.Request) (u Upstream) { - if p.Upstreams == nil { - return nil - } - - longestMatch := 0 - for _, upstream := range *p.Upstreams { - from := upstream.From() - - if !middleware.Name(from).Matches(state.Name()) || !upstream.IsAllowedDomain(state.Name()) { - continue - } - - if lf := len(from); lf > longestMatch { - longestMatch = lf - u = upstream - } - } - return u - -} - -// Name implements the Handler interface. -func (p Proxy) Name() string { return "proxy" } - -// defaultTimeout is the default networking timeout for DNS requests. -const defaultTimeout = 5 * time.Second - -func toDnstap(ctx context.Context, host string, ex Exchanger, state request.Request, reply *dns.Msg, queryEpoch, respEpoch uint64) (err error) { - if tapper := dnstap.TapperFromContext(ctx); tapper != nil { - // Query - b := tapper.TapBuilder() - b.TimeSec = queryEpoch - if err = b.HostPort(host); err != nil { - return - } - t := ex.Transport() - if t == "" { - t = state.Proto() - } - if t == "tcp" { - b.SocketProto = tap.SocketProtocol_TCP - } else { - b.SocketProto = tap.SocketProtocol_UDP - } - if err = b.Msg(state.Req); err != nil { - return - } - err = tapper.TapMessage(b.ToOutsideQuery(tap.Message_FORWARDER_QUERY)) - if err != nil { - return - } - - // Response - if reply != nil { - b.TimeSec = respEpoch - if err = b.Msg(reply); err != nil { - return - } - err = tapper.TapMessage(b.ToOutsideResponse(tap.Message_FORWARDER_RESPONSE)) - } - } - return -} diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go deleted file mode 100644 index b0cb9c3cb..000000000 --- a/middleware/proxy/proxy_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package proxy - -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/mholt/caddy/caddyfile" -) - -func TestStop(t *testing.T) { - config := "proxy . %s {\n health_check /healthcheck:%s %dms \n}" - tests := []struct { - name string - intervalInMilliseconds int - numHealthcheckIntervals int - }{ - { - "No Healthchecks After Stop - 5ms, 1 intervals", - 5, - 1, - }, - { - "No Healthchecks After Stop - 5ms, 2 intervals", - 5, - 2, - }, - { - "No Healthchecks After Stop - 5ms, 3 intervals", - 5, - 3, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - - // Set up proxy. - var counter int64 - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r.Body.Close() - atomic.AddInt64(&counter, 1) - })) - - defer backend.Close() - - port := backend.URL[17:] // Remove all crap up to the port - back := backend.URL[7:] // Remove http:// - c := caddyfile.NewDispenser("Testfile", strings.NewReader(fmt.Sprintf(config, back, port, test.intervalInMilliseconds))) - upstreams, err := NewStaticUpstreams(&c) - if err != nil { - t.Error("Expected no error. Got:", err.Error()) - } - - // Give some time for healthchecks to hit the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - for _, upstream := range upstreams { - if err := upstream.Stop(); err != nil { - t.Error("Expected no error stopping upstream. Got: ", err.Error()) - } - } - - counterValueAfterShutdown := atomic.LoadInt64(&counter) - - // Give some time to see if healthchecks are still hitting the server. - time.Sleep(time.Duration(test.intervalInMilliseconds*test.numHealthcheckIntervals) * time.Millisecond) - - if counterValueAfterShutdown == 0 { - t.Error("Expected healthchecks to hit test server. Got no healthchecks.") - } - - // health checks are in a go routine now, so one may well occur after we shutdown, - // but we only ever expect one more - counterValueAfterWaiting := atomic.LoadInt64(&counter) - if counterValueAfterWaiting > (counterValueAfterShutdown + 1) { - t.Errorf("Expected no more healthchecks after shutdown. Got: %d healthchecks after shutdown", counterValueAfterWaiting-counterValueAfterShutdown) - } - - }) - - } -} diff --git a/middleware/proxy/response.go b/middleware/proxy/response.go deleted file mode 100644 index 2ad553c41..000000000 --- a/middleware/proxy/response.go +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index d55065734..000000000 --- a/middleware/proxy/setup.go +++ /dev/null @@ -1,46 +0,0 @@ -package proxy - -import ( - "github.com/coredns/coredns/core/dnsserver" - "github.com/coredns/coredns/middleware" - - "github.com/mholt/caddy" -) - -func init() { - caddy.RegisterPlugin("proxy", caddy.Plugin{ - ServerType: "dns", - Action: setup, - }) -} - -func setup(c *caddy.Controller) error { - upstreams, err := NewStaticUpstreams(&c.Dispenser) - if err != nil { - return middleware.Error("proxy", err) - } - - t := dnsserver.GetConfig(c).Handler("trace") - P := &Proxy{Trace: t} - dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { - P.Next = next - P.Upstreams = &upstreams - return P - }) - - c.OnStartup(OnStartupMetrics) - - for i := range upstreams { - u := upstreams[i] - c.OnStartup(func() error { - return u.Exchanger().OnStartup(P) - }) - c.OnShutdown(func() error { - return u.Exchanger().OnShutdown(P) - }) - // Register shutdown handlers. - c.OnShutdown(u.Stop) - } - - return nil -} diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go deleted file mode 100644 index 677b8e2fc..000000000 --- a/middleware/proxy/upstream.go +++ /dev/null @@ -1,234 +0,0 @@ -package proxy - -import ( - "fmt" - "net" - "strconv" - "sync/atomic" - "time" - - "github.com/coredns/coredns/middleware" - "github.com/coredns/coredns/middleware/pkg/dnsutil" - "github.com/coredns/coredns/middleware/pkg/healthcheck" - "github.com/coredns/coredns/middleware/pkg/tls" - "github.com/mholt/caddy/caddyfile" - "github.com/miekg/dns" -) - -type staticUpstream struct { - from string - - healthcheck.HealthCheck - - WithoutPathPrefix string - IgnoredSubDomains []string - ex Exchanger -} - -// NewStaticUpstreams parses the configuration input and sets up -// static upstreams for the proxy middleware. -func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) { - var upstreams []Upstream - for c.Next() { - upstream := &staticUpstream{ - from: ".", - HealthCheck: healthcheck.HealthCheck{ - FailTimeout: 10 * time.Second, - MaxFails: 1, - Future: 60 * time.Second, - }, - ex: newDNSEx(), - } - - if !c.Args(&upstream.from) { - return upstreams, c.ArgErr() - } - upstream.from = middleware.Host(upstream.from).Normalize() - - to := c.RemainingArgs() - if len(to) == 0 { - return upstreams, c.ArgErr() - } - - // process the host list, substituting in any nameservers in files - toHosts, err := dnsutil.ParseHostPortOrFile(to...) - if err != nil { - return upstreams, err - } - - for c.NextBlock() { - if err := parseBlock(c, upstream); err != nil { - return upstreams, err - } - } - - upstream.Hosts = make([]*healthcheck.UpstreamHost, len(toHosts)) - for i, host := range toHosts { - uh := &healthcheck.UpstreamHost{ - Name: host, - Conns: 0, - Fails: 0, - FailTimeout: upstream.FailTimeout, - - CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc { - return func(uh *healthcheck.UpstreamHost) bool { - - down := false - - uh.CheckMu.Lock() - until := uh.OkUntil - uh.CheckMu.Unlock() - - if !until.IsZero() && time.Now().After(until) { - down = true - } - - fails := atomic.LoadInt32(&uh.Fails) - if fails >= upstream.MaxFails && upstream.MaxFails != 0 { - down = true - } - return down - } - }(upstream), - WithoutPathPrefix: upstream.WithoutPathPrefix, - } - - upstream.Hosts[i] = uh - } - upstream.Start() - - upstreams = append(upstreams, upstream) - } - return upstreams, nil -} - -func (u *staticUpstream) From() string { - return u.from -} - -func parseBlock(c *caddyfile.Dispenser, u *staticUpstream) error { - switch c.Val() { - case "policy": - if !c.NextArg() { - return c.ArgErr() - } - policyCreateFunc, ok := healthcheck.SupportedPolicies[c.Val()] - if !ok { - return c.ArgErr() - } - u.Policy = policyCreateFunc() - case "fail_timeout": - if !c.NextArg() { - return c.ArgErr() - } - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.FailTimeout = dur - case "max_fails": - if !c.NextArg() { - return c.ArgErr() - } - n, err := strconv.Atoi(c.Val()) - if err != nil { - return err - } - u.MaxFails = int32(n) - case "health_check": - if !c.NextArg() { - return c.ArgErr() - } - var err error - u.HealthCheck.Path, u.HealthCheck.Port, err = net.SplitHostPort(c.Val()) - if err != nil { - return err - } - u.HealthCheck.Interval = 30 * time.Second - if c.NextArg() { - dur, err := time.ParseDuration(c.Val()) - if err != nil { - return err - } - u.HealthCheck.Interval = dur - u.Future = 2 * dur - - // set a minimum of 3 seconds - if u.Future < (3 * time.Second) { - u.Future = 3 * time.Second - } - } - case "without": - if !c.NextArg() { - return c.ArgErr() - } - u.WithoutPathPrefix = c.Val() - case "except": - ignoredDomains := c.RemainingArgs() - if len(ignoredDomains) == 0 { - return c.ArgErr() - } - for i := 0; i < len(ignoredDomains); i++ { - ignoredDomains[i] = middleware.Host(ignoredDomains[i]).Normalize() - } - u.IgnoredSubDomains = ignoredDomains - case "spray": - u.Spray = &healthcheck.Spray{} - case "protocol": - encArgs := c.RemainingArgs() - if len(encArgs) == 0 { - return c.ArgErr() - } - switch encArgs[0] { - case "dns": - if len(encArgs) > 1 { - if encArgs[1] == "force_tcp" { - opts := Options{ForceTCP: true} - u.ex = newDNSExWithOption(opts) - } else { - return fmt.Errorf("only force_tcp allowed as parameter to dns") - } - } else { - u.ex = newDNSEx() - } - case "https_google": - 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 - case "grpc": - if len(encArgs) == 2 && encArgs[1] == "insecure" { - u.ex = newGrpcClient(nil, u) - return nil - } - tls, err := tls.NewTLSConfigFromArgs(encArgs[1:]...) - if err != nil { - return err - } - u.ex = newGrpcClient(tls, u) - default: - return fmt.Errorf("%s: %s", errInvalidProtocol, encArgs[0]) - } - - default: - return c.Errf("unknown property '%s'", c.Val()) - } - return nil -} - -func (u *staticUpstream) IsAllowedDomain(name string) bool { - if dns.Name(name) == dns.Name(u.From()) { - return true - } - - for _, ignoredSubDomain := range u.IgnoredSubDomains { - if middleware.Name(ignoredSubDomain).Matches(name) { - return false - } - } - return true -} - -func (u *staticUpstream) Exchanger() Exchanger { return u.ex } diff --git a/middleware/proxy/upstream_test.go b/middleware/proxy/upstream_test.go deleted file mode 100644 index 3ee225c2d..000000000 --- a/middleware/proxy/upstream_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package proxy - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/coredns/coredns/middleware/test" - - "github.com/mholt/caddy" -) - -func TestAllowedDomain(t *testing.T) { - upstream := &staticUpstream{ - from: "miek.nl.", - IgnoredSubDomains: []string{"download.miek.nl.", "static.miek.nl."}, // closing dot mandatory - } - tests := []struct { - name string - expected bool - }{ - {"miek.nl.", true}, - {"download.miek.nl.", false}, - {"static.miek.nl.", false}, - {"blaat.miek.nl.", true}, - } - - for i, test := range tests { - isAllowed := upstream.IsAllowedDomain(test.name) - if test.expected != isAllowed { - t.Errorf("Test %d: expected %v found %v for %s", i+1, test.expected, isAllowed, test.name) - } - } -} - -func TestProxyParse(t *testing.T) { - rmFunc, cert, key, ca := getPEMFiles(t) - defer rmFunc() - - grpc1 := "proxy . 8.8.8.8:53 {\n protocol grpc " + ca + "\n}" - grpc2 := "proxy . 8.8.8.8:53 {\n protocol grpc " + cert + " " + key + "\n}" - grpc3 := "proxy . 8.8.8.8:53 {\n protocol grpc " + cert + " " + key + " " + ca + "\n}" - grpc4 := "proxy . 8.8.8.8:53 {\n protocol grpc " + key + "\n}" - - tests := []struct { - inputUpstreams string - shouldErr bool - }{ - { - `proxy . 8.8.8.8:53`, - false, - }, - { - `proxy 10.0.0.0/24 8.8.8.8:53`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - policy round_robin -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout 5s -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - max_fails 10 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - health_check /health:8080 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - without without -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - except miek.nl example.org 10.0.0.0/24 -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - spray -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - error_option -}`, - true, - }, - { - ` -proxy . some_bogus_filename`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc insecure -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns force_tcp -}`, - false, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol grpc a b c d -}`, - true, - }, - { - grpc1, - false, - }, - { - grpc2, - false, - }, - { - grpc3, - false, - }, - { - grpc4, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol foobar -}`, - true, - }, - { - `proxy`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol foobar -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - policy -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - fail_timeout junky -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - health_check -}`, - true, - }, - { - ` -proxy . 8.8.8.8:53 { - protocol dns force -}`, - true, - }, - } - for i, test := range tests { - c := caddy.NewTestController("dns", test.inputUpstreams) - _, err := NewStaticUpstreams(&c.Dispenser) - if (err != nil) != test.shouldErr { - t.Errorf("Test %d expected no error, got %v for %s", i+1, err, test.inputUpstreams) - } - } -} - -func TestResolvParse(t *testing.T) { - tests := []struct { - inputUpstreams string - filedata string - shouldErr bool - expected []string - }{ - { - ` -proxy . FILE -`, - ` -nameserver 1.2.3.4 -nameserver 4.3.2.1 -`, - false, - []string{"1.2.3.4:53", "4.3.2.1:53"}, - }, - { - ` -proxy example.com 1.1.1.1:5000 -proxy . FILE -proxy example.org 2.2.2.2:1234 -`, - ` -nameserver 1.2.3.4 -`, - false, - []string{"1.1.1.1:5000", "1.2.3.4:53", "2.2.2.2:1234"}, - }, - { - ` -proxy example.com 1.1.1.1:5000 -proxy . FILE -proxy example.org 2.2.2.2:1234 -`, - ` -junky resolve.conf -`, - false, - []string{"1.1.1.1:5000", "2.2.2.2:1234"}, - }, - } - for i, tc := range tests { - - path, rm, err := test.TempFile(".", tc.filedata) - if err != nil { - t.Fatalf("Test %d could not creat temp file %v", i, err) - } - defer rm() - - config := strings.Replace(tc.inputUpstreams, "FILE", path, -1) - c := caddy.NewTestController("dns", config) - upstreams, err := NewStaticUpstreams(&c.Dispenser) - if (err != nil) != tc.shouldErr { - t.Errorf("Test %d expected no error, got %v", i+1, err) - } - var hosts []string - for _, u := range upstreams { - for _, h := range u.(*staticUpstream).Hosts { - hosts = append(hosts, h.Name) - } - } - if !tc.shouldErr { - if len(hosts) != len(tc.expected) { - t.Errorf("Test %d expected %d hosts got %d", i+1, len(tc.expected), len(upstreams)) - } else { - ok := true - for i, v := range tc.expected { - if v != hosts[i] { - ok = false - } - } - if !ok { - t.Errorf("Test %d expected %v got %v", i+1, tc.expected, upstreams) - } - } - } - } -} - -func getPEMFiles(t *testing.T) (rmFunc func(), cert, key, ca string) { - tempDir, rmFunc, err := test.WritePEMFiles("") - if err != nil { - t.Fatalf("Could not write PEM files: %s", err) - } - - cert = filepath.Join(tempDir, "cert.pem") - key = filepath.Join(tempDir, "key.pem") - ca = filepath.Join(tempDir, "ca.pem") - - return -} |