mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			314 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package httpproxy
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/miekg/coredns/middleware/pkg/debug"
 | |
| 	"github.com/miekg/coredns/middleware/proxy"
 | |
| 	"github.com/miekg/coredns/request"
 | |
| 
 | |
| 	"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)} }
 | |
| 
 | |
| func (g *google) Exchange(state request.Request) (*dns.Msg, error) {
 | |
| 	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)
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 			}
 | |
| 
 | |
| 			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", ghost, backendErr)
 | |
| 
 | |
| 		timeout := addr.FailTimeout
 | |
| 		if timeout == 0 {
 | |
| 			timeout = 5 * time.Second
 | |
| 		}
 | |
| 		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
 | |
| 	}
 | |
| 
 | |
| 	up, _ := newSimpleUpstream(new)
 | |
| 	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
 | |
| 				}
 | |
| 
 | |
| 				up, _ := newSimpleUpstream(new)
 | |
| 				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 {
 | |
| 			timeout = 7 * time.Second
 | |
| 		}
 | |
| 		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
 | |
| }
 | |
| 
 | |
| // 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) {
 | |
| 	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, nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	for i := 0; i < len(m.Ns); i++ {
 | |
| 		m.Ns[i], err = toRR(g.Authority[i])
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	for i := 0; i < len(m.Extra); i++ {
 | |
| 		m.Extra[i], err = toRR(g.Additional[i])
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	txt, _ := dns.NewRR(". 0 CH TXT " + g.Comment)
 | |
| 	return m, txt, nil
 | |
| }
 | |
| 
 | |
| 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"
 | |
| )
 |