| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | package proxy
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							| 
									
										
										
										
											2017-02-11 16:56:04 +00:00
										 |  |  | 	"context"
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	"crypto/tls"
 | 
					
						
							|  |  |  | 	"encoding/json"
 | 
					
						
							|  |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"io/ioutil"
 | 
					
						
							|  |  |  | 	"log"
 | 
					
						
							|  |  |  | 	"net"
 | 
					
						
							|  |  |  | 	"net/http"
 | 
					
						
							|  |  |  | 	"net/url"
 | 
					
						
							|  |  |  | 	"sync/atomic"
 | 
					
						
							|  |  |  | 	"time"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/healthcheck"
 | 
					
						
							| 
									
										
										
										
											2017-02-21 22:51:47 -08:00
										 |  |  | 	"github.com/coredns/coredns/request"
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"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)}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-11 16:56:04 +00:00
										 |  |  | func (g *google) Exchange(ctx context.Context, addr string, state request.Request) (*dns.Msg, error) {
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	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
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-12 10:52:43 +01:00
										 |  |  | 		m, err := toMsg(gm)
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 		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
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | func (g *google) Transport() string { return "tcp" }
 | 
					
						
							|  |  |  | func (g *google) Protocol() string  { return "https_google" }
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 	if len(*p.Upstreams) == 0 {
 | 
					
						
							|  |  |  | 		return fmt.Errorf("no upstreams defined")
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 	oldUpstream := (*p.Upstreams)[0]
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 	log.Printf("[INFO] Bootstrapping A records %q", g.endpoint)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err)
 | 
					
						
							|  |  |  | 	} else {
 | 
					
						
							|  |  |  | 		addrs, err1 := extractAnswer(new)
 | 
					
						
							|  |  |  | 		if err1 != nil {
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 			log.Printf("[WARNING] Failed to bootstrap A records %q: %s", g.endpoint, err1)
 | 
					
						
							|  |  |  | 		} else {
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 			up := newUpstream(addrs, oldUpstream.(*staticUpstream))
 | 
					
						
							|  |  |  | 			p.Upstreams = &[]Upstream{up}
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			log.Printf("[INFO] Bootstrapping A records %q found: %v", g.endpoint, addrs)
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() {
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 		tick := time.NewTicker(120 * time.Second)
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		for {
 | 
					
						
							|  |  |  | 			select {
 | 
					
						
							|  |  |  | 			case <-tick.C:
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 				log.Printf("[INFO] Resolving A records %q", g.endpoint)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 				new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA)
 | 
					
						
							|  |  |  | 				if err != nil {
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 					log.Printf("[WARNING] Failed to resolve A records %q: %s", g.endpoint, err)
 | 
					
						
							|  |  |  | 					continue
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-02 18:43:52 +02:00
										 |  |  | 				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)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 			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.
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | func newUpstream(hosts []string, old *staticUpstream) Upstream {
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	upstream := &staticUpstream{
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 		from: old.from,
 | 
					
						
							|  |  |  | 		HealthCheck: healthcheck.HealthCheck{
 | 
					
						
							|  |  |  | 			FailTimeout: 10 * time.Second,
 | 
					
						
							|  |  |  | 			MaxFails:    3,
 | 
					
						
							|  |  |  | 			Future:      60 * time.Second,
 | 
					
						
							|  |  |  | 		},
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 		ex:                old.ex,
 | 
					
						
							|  |  |  | 		WithoutPathPrefix: old.WithoutPathPrefix,
 | 
					
						
							|  |  |  | 		IgnoredSubDomains: old.IgnoredSubDomains,
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 	upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts))
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	for i, h := range hosts {
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 		uh := &healthcheck.UpstreamHost{
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 			Name:        h,
 | 
					
						
							|  |  |  | 			Conns:       0,
 | 
					
						
							|  |  |  | 			Fails:       0,
 | 
					
						
							|  |  |  | 			FailTimeout: upstream.FailTimeout,
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 			CheckDown: func(upstream *staticUpstream) healthcheck.UpstreamHostDownFunc {
 | 
					
						
							|  |  |  | 				return func(uh *healthcheck.UpstreamHost) bool {
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					down := false
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 					uh.CheckMu.Lock()
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 					until := uh.OkUntil
 | 
					
						
							| 
									
										
										
										
											2017-08-09 09:21:33 -07:00
										 |  |  | 					uh.CheckMu.Unlock()
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					if !until.IsZero() && time.Now().After(until) {
 | 
					
						
							|  |  |  | 						down = true
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 					}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					fails := atomic.LoadInt32(&uh.Fails)
 | 
					
						
							|  |  |  | 					if fails >= upstream.MaxFails && upstream.MaxFails != 0 {
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 						down = true
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 					}
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 					return down
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 				}
 | 
					
						
							|  |  |  | 			}(upstream),
 | 
					
						
							|  |  |  | 			WithoutPathPrefix: upstream.WithoutPathPrefix,
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2017-06-30 10:13:45 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 		upstream.Hosts[i] = uh
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return upstream
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const (
 | 
					
						
							|  |  |  | 	// Default endpoint for this service.
 | 
					
						
							|  |  |  | 	ghost = "dns.google.com."
 | 
					
						
							|  |  |  | )
 |