| 
									
										
										
										
											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-02-21 22:51:47 -08:00
										 |  |  | 	"github.com/coredns/coredns/middleware/pkg/debug" | 
					
						
							|  |  |  | 	"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())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	optDebug := false | 
					
						
							|  |  |  | 	if bug := debug.IsDebug(state.Name()); bug != "" { | 
					
						
							|  |  |  | 		optDebug = true | 
					
						
							|  |  |  | 		v.Set("name", bug) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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, debug, err := toMsg(gm) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if optDebug { | 
					
						
							|  |  |  | 			// reset question | 
					
						
							|  |  |  | 			m.Question[0].Name = state.QName() | 
					
						
							|  |  |  | 			// prepend debug RR to the additional section | 
					
						
							|  |  |  | 			m.Extra = append([]dns.RR{debug}, m.Extra...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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) 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} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	new, err := g.bootstrapProxy.Lookup(state, g.endpoint, dns.TypeA) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 	var oldUpstream Upstream | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// ignore errors here, as we want to keep on trying. | 
					
						
							|  |  |  | 	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, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 		if len(*p.Upstreams) > 0 { | 
					
						
							|  |  |  | 			oldUpstream = (*p.Upstreams)[0] | 
					
						
							|  |  |  | 			up := newUpstream(addrs, oldUpstream.(*staticUpstream)) | 
					
						
							|  |  |  | 			p.Upstreams = &[]Upstream{up} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			log.Printf("[WARNING] Failed to bootstrap upstreams %q", g.endpoint) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		tick := time.NewTicker(300 * time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-tick.C: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				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, err) | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-07 21:03:17 +00:00
										 |  |  | 					// TODO(miek): can this actually happen? | 
					
						
							|  |  |  | 					if oldUpstream != nil { | 
					
						
							|  |  |  | 						up := newUpstream(addrs, oldUpstream.(*staticUpstream)) | 
					
						
							|  |  |  | 						p.Upstreams = &[]Upstream{up} | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											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-02-07 21:03:17 +00:00
										 |  |  | 		from:              old.from, | 
					
						
							|  |  |  | 		Hosts:             nil, | 
					
						
							|  |  |  | 		Policy:            &Random{}, | 
					
						
							|  |  |  | 		Spray:             nil, | 
					
						
							|  |  |  | 		FailTimeout:       10 * time.Second, | 
					
						
							|  |  |  | 		MaxFails:          3, | 
					
						
							|  |  |  | 		ex:                old.ex, | 
					
						
							|  |  |  | 		WithoutPathPrefix: old.WithoutPathPrefix, | 
					
						
							|  |  |  | 		IgnoredSubDomains: old.IgnoredSubDomains, | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	upstream.Hosts = make([]*UpstreamHost, len(hosts)) | 
					
						
							|  |  |  | 	for i, h := range hosts { | 
					
						
							|  |  |  | 		uh := &UpstreamHost{ | 
					
						
							|  |  |  | 			Name:        h, | 
					
						
							|  |  |  | 			Conns:       0, | 
					
						
							|  |  |  | 			Fails:       0, | 
					
						
							|  |  |  | 			FailTimeout: upstream.FailTimeout, | 
					
						
							| 
									
										
										
										
											2017-04-24 20:37:43 +01:00
										 |  |  | 			Unhealthy:   false, | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc { | 
					
						
							|  |  |  | 				return func(uh *UpstreamHost) bool { | 
					
						
							| 
									
										
										
										
											2017-04-24 20:37:43 +01:00
										 |  |  | 					if uh.Unhealthy { | 
					
						
							| 
									
										
										
										
											2017-02-06 19:32:48 +00:00
										 |  |  | 						return true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					fails := atomic.LoadInt32(&uh.Fails) | 
					
						
							|  |  |  | 					if fails >= upstream.MaxFails && upstream.MaxFails != 0 { | 
					
						
							|  |  |  | 						return true | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return false | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}(upstream), | 
					
						
							|  |  |  | 			WithoutPathPrefix: upstream.WithoutPathPrefix, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		upstream.Hosts[i] = uh | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return upstream | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// Default endpoint for this service. | 
					
						
							|  |  |  | 	ghost = "dns.google.com." | 
					
						
							|  |  |  | ) |