Added health-check method
This commit is contained in:
parent
d6f560ecaf
commit
a6022fc198
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -67,4 +67,4 @@ func (p *UpstreamPlugin) finalizer() {
|
||||||
log.Printf("Error while closing the upstream: %s", err)
|
log.Printf("Error while closing the upstream: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue