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"
|
|
|
|
|
"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{
|
plugin/proxy: decrease health timeouts (#1107)
Turn down the timeouts and numbers a bit:
FailTimeout 10s -> 5s
Future 60s -> 12s
TryDuration 60s -> 16s
The timeout for decrementing the fails in a host: 10s -> 2s
And the biggest change: don't set fails when the error is Timeout(),
meaning we loop for a bit and may try the same server again, but we
don't mark our upstream as bad, see comments in proxy.go. Testing this
with "ANY isc.org" and "MX miek.nl" we see:
~~~
::1 - [24/Sep/2017:08:06:17 +0100] "ANY IN isc.org. udp 37 false 4096" SERVFAIL qr,rd 37 10.001621221s
24/Sep/2017:08:06:17 +0100 [ERROR 0 isc.org. ANY] unreachable backend: read udp 192.168.1.148:37420->8.8.8.8:53: i/o timeout
::1 - [24/Sep/2017:08:06:17 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 35.957284ms
127.0.0.1 - [24/Sep/2017:08:06:18 +0100] "ANY IN isc.org. udp 37 false 4096" SERVFAIL qr,rd 37 10.002051726s
24/Sep/2017:08:06:18 +0100 [ERROR 0 isc.org. ANY] unreachable backend: read udp 192.168.1.148:54901->8.8.8.8:53: i/o timeout
::1 - [24/Sep/2017:08:06:19 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 56.848416ms
127.0.0.1 - [24/Sep/2017:08:06:21 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 48.118349ms
::1 - [24/Sep/2017:08:06:21 +0100] "MX IN miek.nl. udp 37 false 4096" NOERROR qr,rd,ra,ad 170 1.055172915s
~~~
So the ANY isc.org queries show up twice, because we retry internally -
this is I think WAI.
The `miek.nl MX` queries are just processed normally as no backend is
marked as unreachable.
May fix #1035 #486
2017-09-24 20:05:36 +01:00
|
|
|
FailTimeout: 5 * time.Second,
|
2017-08-09 09:21:33 -07:00
|
|
|
MaxFails: 3,
|
|
|
|
|
},
|
2017-02-07 21:03:17 +00:00
|
|
|
ex: old.ex,
|
|
|
|
|
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-10-15 19:38:39 +02:00
|
|
|
for i, host := range hosts {
|
2017-08-09 09:21:33 -07:00
|
|
|
uh := &healthcheck.UpstreamHost{
|
2017-10-15 19:38:39 +02:00
|
|
|
Name: host,
|
2017-02-06 19:32:48 +00:00
|
|
|
Conns: 0,
|
|
|
|
|
Fails: 0,
|
|
|
|
|
FailTimeout: upstream.FailTimeout,
|
2017-09-24 19:37:43 +01:00
|
|
|
CheckDown: checkDownFunc(upstream),
|
2017-02-06 19:32:48 +00:00
|
|
|
}
|
|
|
|
|
upstream.Hosts[i] = uh
|
|
|
|
|
}
|
|
|
|
|
return upstream
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// Default endpoint for this service.
|
|
|
|
|
ghost = "dns.google.com."
|
|
|
|
|
)
|