| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | package httpproxy
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"encoding/json"
 | 
					
						
							|  |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"io/ioutil"
 | 
					
						
							|  |  |  | 	"log"
 | 
					
						
							|  |  |  | 	"net"
 | 
					
						
							|  |  |  | 	"net/http"
 | 
					
						
							|  |  |  | 	"net/url"
 | 
					
						
							|  |  |  | 	"sync"
 | 
					
						
							|  |  |  | 	"sync/atomic"
 | 
					
						
							|  |  |  | 	"time"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 	"github.com/miekg/coredns/middleware/pkg/debug"
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 	"github.com/miekg/coredns/middleware/proxy"
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 	"github.com/miekg/coredns/request"
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // immediate retries until this duration ends or we get a nil host.
 | 
					
						
							|  |  |  | var tryDuration = 60 * time.Second
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type google struct {
 | 
					
						
							|  |  |  | 	client   *http.Client
 | 
					
						
							|  |  |  | 	upstream *simpleUpstream
 | 
					
						
							|  |  |  | 	addr     *simpleUpstream
 | 
					
						
							|  |  |  | 	quit     chan bool
 | 
					
						
							|  |  |  | 	sync.RWMutex
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newGoogle() *google { return &google{client: newClient(ghost), quit: make(chan bool)} }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | func (g *google) Exchange(state request.Request) (*dns.Msg, error) {
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 	v := url.Values{}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 	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)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	start := time.Now()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for time.Now().Sub(start) < tryDuration {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		g.RLock()
 | 
					
						
							|  |  |  | 		addr := g.addr.Select()
 | 
					
						
							|  |  |  | 		g.RUnlock()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if addr == nil {
 | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("no healthy upstream http hosts")
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		atomic.AddInt64(&addr.Conns, 1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		buf, backendErr := g.do(addr.Name, v.Encode())
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		atomic.AddInt64(&addr.Conns, -1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if backendErr == nil {
 | 
					
						
							|  |  |  | 			gm := new(googleMsg)
 | 
					
						
							|  |  |  | 			if err := json.Unmarshal(buf, gm); err != nil {
 | 
					
						
							|  |  |  | 				return nil, err
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 			m, debug, err := toMsg(gm)
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 			if err != nil {
 | 
					
						
							|  |  |  | 				return nil, err
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 			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
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 			return m, nil
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		log.Printf("[WARNING] Failed to connect to HTTPS backend %q: %s", ghost, backendErr)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		timeout := addr.FailTimeout
 | 
					
						
							|  |  |  | 		if timeout == 0 {
 | 
					
						
							| 
									
										
										
										
											2016-11-27 15:24:50 +00:00
										 |  |  | 			timeout = 5 * time.Second
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 		atomic.AddInt32(&addr.Fails, 1)
 | 
					
						
							|  |  |  | 		go func(host *proxy.UpstreamHost, timeout time.Duration) {
 | 
					
						
							|  |  |  | 			time.Sleep(timeout)
 | 
					
						
							|  |  |  | 			atomic.AddInt32(&host.Fails, -1)
 | 
					
						
							|  |  |  | 		}(addr, timeout)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil, errUnreachable
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // OnStartup looks up the IP address for "ghost" every 30 seconds.
 | 
					
						
							|  |  |  | func (g *google) OnStartup() error {
 | 
					
						
							|  |  |  | 	r := new(dns.Msg)
 | 
					
						
							|  |  |  | 	r.SetQuestion(dns.Fqdn(ghost), dns.TypeA)
 | 
					
						
							|  |  |  | 	new, err := g.lookup(r)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-30 20:12:14 +00:00
										 |  |  | 	up, _ := newSimpleUpstream(new)
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 	g.Lock()
 | 
					
						
							|  |  |  | 	g.addr = up
 | 
					
						
							|  |  |  | 	g.Unlock()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() {
 | 
					
						
							|  |  |  | 		tick := time.NewTicker(30 * time.Second)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for {
 | 
					
						
							|  |  |  | 			select {
 | 
					
						
							|  |  |  | 			case <-tick.C:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				r.SetQuestion(dns.Fqdn(ghost), dns.TypeA)
 | 
					
						
							|  |  |  | 				new, err := g.lookup(r)
 | 
					
						
							|  |  |  | 				if err != nil {
 | 
					
						
							|  |  |  | 					log.Printf("[WARNING] Failed to lookup A records %q: %s", ghost, err)
 | 
					
						
							|  |  |  | 					continue
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-30 20:12:14 +00:00
										 |  |  | 				up, _ := newSimpleUpstream(new)
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 				g.Lock()
 | 
					
						
							|  |  |  | 				g.addr = up
 | 
					
						
							|  |  |  | 				g.Unlock()
 | 
					
						
							|  |  |  | 			case <-g.quit:
 | 
					
						
							|  |  |  | 				return
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (g *google) OnShutdown() error {
 | 
					
						
							|  |  |  | 	g.quit <- true
 | 
					
						
							|  |  |  | 	return nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (g *google) SetUpstream(u *simpleUpstream) error {
 | 
					
						
							|  |  |  | 	g.upstream = u
 | 
					
						
							|  |  |  | 	return nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (g *google) lookup(r *dns.Msg) ([]string, error) {
 | 
					
						
							|  |  |  | 	c := new(dns.Client)
 | 
					
						
							|  |  |  | 	start := time.Now()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for time.Now().Sub(start) < tryDuration {
 | 
					
						
							|  |  |  | 		host := g.upstream.Select()
 | 
					
						
							|  |  |  | 		if host == nil {
 | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("no healthy upstream hosts")
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		atomic.AddInt64(&host.Conns, 1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		m, _, backendErr := c.Exchange(r, host.Name)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		atomic.AddInt64(&host.Conns, -1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if backendErr == nil {
 | 
					
						
							|  |  |  | 			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")
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		timeout := host.FailTimeout
 | 
					
						
							|  |  |  | 		if timeout == 0 {
 | 
					
						
							| 
									
										
										
										
											2016-11-27 15:24:50 +00:00
										 |  |  | 			timeout = 7 * time.Second
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 		atomic.AddInt32(&host.Fails, 1)
 | 
					
						
							|  |  |  | 		go func(host *proxy.UpstreamHost, timeout time.Duration) {
 | 
					
						
							|  |  |  | 			time.Sleep(timeout)
 | 
					
						
							|  |  |  | 			atomic.AddInt32(&host.Fails, -1)
 | 
					
						
							|  |  |  | 		}(host, timeout)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return nil, fmt.Errorf("no healthy upstream hosts")
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (g *google) do(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 = ghost
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | // toMsg converts a googleMsg into the dns message. The returned RR is the comment disquised as a TXT
 | 
					
						
							|  |  |  | // record.
 | 
					
						
							|  |  |  | func toMsg(g *googleMsg) (*dns.Msg, dns.RR, error) {
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 	m := new(dns.Msg)
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 	m.Response = true
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 	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 {
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 			return nil, nil, err
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	for i := 0; i < len(m.Ns); i++ {
 | 
					
						
							|  |  |  | 		m.Ns[i], err = toRR(g.Authority[i])
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 			return nil, nil, err
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	for i := 0; i < len(m.Extra); i++ {
 | 
					
						
							|  |  |  | 		m.Extra[i], err = toRR(g.Additional[i])
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 			return nil, nil, err
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-29 09:54:57 +00:00
										 |  |  | 	txt, _ := dns.NewRR(". 0 CH TXT " + g.Comment)
 | 
					
						
							|  |  |  | 	return m, txt, nil
 | 
					
						
							| 
									
										
										
										
											2016-11-26 17:57:22 +00:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const (
 | 
					
						
							|  |  |  | 	ghost = "dns.google.com"
 | 
					
						
							|  |  |  | )
 |