Added health-check method

This commit is contained in:
Andrey Meshkov 2018-11-05 21:19:01 +03:00
parent d6f560ecaf
commit a6022fc198
4 changed files with 152 additions and 18 deletions

23
upstream/helpers.go Normal file
View File

@ -0,0 +1,23 @@
package upstream
import "github.com/miekg/dns"
// Performs a simple health-check of the specified upstream
func IsAlive(u Upstream) (bool, error) {
// Using ipv4only.arpa. domain as it is a part of DNS64 RFC and it should exist everywhere
ping := new(dns.Msg)
ping.SetQuestion("ipv4only.arpa.", dns.TypeA)
resp, err := u.Exchange(nil, ping)
// If we got a header, we're alright, basically only care about I/O errors 'n stuff.
if err != nil && resp != nil {
// Silly check, something sane came back.
if resp.Response || resp.Opcode == dns.OpcodeQuery {
err = nil
}
}
return err == nil, err
}

View File

@ -10,16 +10,17 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
"net/url" "net/url"
"time"
) )
const ( const (
dnsMessageContentType = "application/dns-message" dnsMessageContentType = "application/dns-message"
defaultKeepAlive = 30 * time.Second
) )
// TODO: Add bootstrap DNS resolver field
// HttpsUpstream is the upstream implementation for DNS-over-HTTPS // HttpsUpstream is the upstream implementation for DNS-over-HTTPS
type HttpsUpstream struct { type HttpsUpstream struct {
client *http.Client client *http.Client
@ -27,18 +28,39 @@ type HttpsUpstream struct {
} }
// NewHttpsUpstream creates a new DNS-over-HTTPS upstream from hostname // NewHttpsUpstream creates a new DNS-over-HTTPS upstream from hostname
func NewHttpsUpstream(endpoint string) (Upstream, error) { func NewHttpsUpstream(endpoint string, bootstrap string) (Upstream, error) {
u, err := url.Parse(endpoint) u, err := url.Parse(endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Initialize bootstrap resolver
bootstrapResolver := net.DefaultResolver
if bootstrap != "" {
bootstrapResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
conn, err := d.DialContext(ctx, network, bootstrap)
return conn, err
},
}
}
dialer := &net.Dialer{
Timeout: defaultTimeout,
KeepAlive: defaultKeepAlive,
DualStack: true,
Resolver: bootstrapResolver,
}
// Update TLS and HTTP client configuration // Update TLS and HTTP client configuration
tlsConfig := &tls.Config{ServerName: u.Hostname()} tlsConfig := &tls.Config{ServerName: u.Hostname()}
transport := &http.Transport{ transport := &http.Transport{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
DisableCompression: true, DisableCompression: true,
MaxIdleConns: 1, MaxIdleConns: 1,
DialContext: dialer.DialContext,
} }
http2.ConfigureTransport(transport) http2.ConfigureTransport(transport)

View File

@ -42,8 +42,8 @@ func (p *UpstreamPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *
var reply *dns.Msg var reply *dns.Msg
var backendErr error var backendErr error
// TODO: Change the way we call upstreams for i := range p.Upstreams {
for _, upstream := range p.Upstreams { upstream := p.Upstreams[i]
reply, backendErr = upstream.Exchange(ctx, r) reply, backendErr = upstream.Exchange(ctx, r)
if backendErr == nil { if backendErr == nil {
w.WriteMsg(reply) w.WriteMsg(reply)

View File

@ -6,27 +6,107 @@ import (
"testing" "testing"
) )
func TestDnsUpstream(t *testing.T) { func TestDnsUpstreamIsAlive(t *testing.T) {
u, err := NewDnsUpstream("8.8.8.8:53", "udp", "") var tests = []struct {
endpoint string
if err != nil { proto string
t.Errorf("cannot create a DNS upstream") }{
{"8.8.8.8:53", "udp"},
{"8.8.8.8:53", "tcp"},
{"1.1.1.1:53", "udp"},
} }
testUpstream(t, u) for _, test := range tests {
u, err := NewDnsUpstream(test.endpoint, test.proto, "")
if err != nil {
t.Errorf("cannot create a DNS upstream")
}
testUpstreamIsAlive(t, u)
}
}
func TestHttpsUpstreamIsAlive(t *testing.T) {
var tests = []struct {
url string
bootstrap string
}{
{"https://cloudflare-dns.com/dns-query", "8.8.8.8:53"},
{"https://dns.google.com/experimental", "8.8.8.8:53"},
{"https://doh.cleanbrowsing.org/doh/security-filter/", ""}, // TODO: status 201??
}
for _, test := range tests {
u, err := NewHttpsUpstream(test.url, test.bootstrap)
if err != nil {
t.Errorf("cannot create a DNS-over-HTTPS upstream")
}
testUpstreamIsAlive(t, u)
}
}
func TestDnsOverTlsIsAlive(t *testing.T) {
var tests = []struct {
endpoint string
tlsServerName string
}{
{"1.1.1.1:853", ""},
{"9.9.9.9:853", ""},
{"185.228.168.10:853", "security-filter-dns.cleanbrowsing.org"},
}
for _, test := range tests {
u, err := NewDnsUpstream(test.endpoint, "tcp-tls", test.tlsServerName)
if err != nil {
t.Errorf("cannot create a DNS-over-TLS upstream")
}
testUpstreamIsAlive(t, u)
}
}
func TestDnsUpstream(t *testing.T) {
var tests = []struct {
endpoint string
proto string
}{
{"8.8.8.8:53", "udp"},
{"8.8.8.8:53", "tcp"},
{"1.1.1.1:53", "udp"},
}
for _, test := range tests {
u, err := NewDnsUpstream(test.endpoint, test.proto, "")
if err != nil {
t.Errorf("cannot create a DNS upstream")
}
testUpstream(t, u)
}
} }
func TestHttpsUpstream(t *testing.T) { func TestHttpsUpstream(t *testing.T) {
testCases := []string{ var tests = []struct {
"https://cloudflare-dns.com/dns-query", url string
"https://dns.google.com/experimental", bootstrap string
"https://doh.cleanbrowsing.org/doh/security-filter/", }{
{"https://cloudflare-dns.com/dns-query", "8.8.8.8:53"},
{"https://dns.google.com/experimental", "8.8.8.8:53"},
{"https://doh.cleanbrowsing.org/doh/security-filter/", ""},
} }
for _, url := range testCases { for _, test := range tests {
u, err := NewHttpsUpstream(url) u, err := NewHttpsUpstream(test.url, test.bootstrap)
if err != nil { if err != nil {
t.Errorf("cannot create a DNS-over-HTTPS upstream") t.Errorf("cannot create a DNS-over-HTTPS upstream")
@ -58,6 +138,15 @@ func TestDnsOverTlsUpstream(t *testing.T) {
} }
} }
func testUpstreamIsAlive(t *testing.T, u Upstream) {
alive, err := IsAlive(u)
if !alive || err != nil {
t.Errorf("Upstream is not alive")
}
u.Close()
}
func testUpstream(t *testing.T, u Upstream) { func testUpstream(t *testing.T, u Upstream) {
var tests = []struct { var tests = []struct {