aboutsummaryrefslogtreecommitdiff
path: root/plugin/proxy/google.go
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2017-09-14 09:36:06 +0100
committerGravatar GitHub <noreply@github.com> 2017-09-14 09:36:06 +0100
commitd8714e64e400ef873c2adc4d929a07d7890727b9 (patch)
treec9fa4c157e6af12eb1517654f8d23ca5d5619513 /plugin/proxy/google.go
parentb984aa45595dc95253b91191afe7d3ee29e71b48 (diff)
downloadcoredns-d8714e64e400ef873c2adc4d929a07d7890727b9.tar.gz
coredns-d8714e64e400ef873c2adc4d929a07d7890727b9.tar.zst
coredns-d8714e64e400ef873c2adc4d929a07d7890727b9.zip
Remove the word middleware (#1067)
* Rename middleware to plugin first pass; mostly used 'sed', few spots where I manually changed text. This still builds a coredns binary. * fmt error * Rename AddMiddleware to AddPlugin * Readd AddMiddleware to remain backwards compat
Diffstat (limited to 'plugin/proxy/google.go')
-rw-r--r--plugin/proxy/google.go244
1 files changed, 244 insertions, 0 deletions
diff --git a/plugin/proxy/google.go b/plugin/proxy/google.go
new file mode 100644
index 000000000..ecc5e6dfd
--- /dev/null
+++ b/plugin/proxy/google.go
@@ -0,0 +1,244 @@
+package proxy
+
+import (
+ "context"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "sync/atomic"
+ "time"
+
+ "github.com/coredns/coredns/plugin/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."
+)