2023-03-24 12:55:51 +00:00
|
|
|
package proxy
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-10 02:38:22 +02:00
|
|
|
"crypto/tls"
|
|
|
|
|
"net"
|
2026-06-15 02:54:05 +02:00
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
2023-03-24 12:55:51 +00:00
|
|
|
"sync/atomic"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
2026-06-15 02:54:05 +02:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/doh"
|
2023-03-24 12:55:51 +00:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
|
|
|
|
|
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestHealth(t *testing.T) {
|
|
|
|
|
i := uint32(0)
|
|
|
|
|
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
|
if r.Question[0].Name == "." && r.RecursionDesired == true {
|
|
|
|
|
atomic.AddUint32(&i, 1)
|
|
|
|
|
}
|
|
|
|
|
ret := new(dns.Msg)
|
|
|
|
|
ret.SetReply(r)
|
|
|
|
|
w.WriteMsg(ret)
|
|
|
|
|
})
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
2023-11-09 06:30:32 +10:30
|
|
|
hc := NewHealthChecker("TestHealth", transport.DNS, true, ".")
|
2023-03-24 12:55:51 +00:00
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
p := NewProxy("TestHealth", s.Addr, transport.DNS)
|
2023-03-24 12:55:51 +00:00
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("check failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
|
i1 := atomic.LoadUint32(&i)
|
|
|
|
|
if i1 != 1 {
|
|
|
|
|
t.Errorf("Expected number of health checks with RecursionDesired==true to be %d, got %d", 1, i1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHealthTCP(t *testing.T) {
|
|
|
|
|
i := uint32(0)
|
|
|
|
|
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
|
if r.Question[0].Name == "." && r.RecursionDesired == true {
|
|
|
|
|
atomic.AddUint32(&i, 1)
|
|
|
|
|
}
|
|
|
|
|
ret := new(dns.Msg)
|
|
|
|
|
ret.SetReply(r)
|
|
|
|
|
w.WriteMsg(ret)
|
|
|
|
|
})
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
2023-11-09 06:30:32 +10:30
|
|
|
hc := NewHealthChecker("TestHealthTCP", transport.DNS, true, ".")
|
2023-03-24 12:55:51 +00:00
|
|
|
hc.SetTCPTransport()
|
|
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
p := NewProxy("TestHealthTCP", s.Addr, transport.DNS)
|
2023-03-24 12:55:51 +00:00
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("check failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
|
i1 := atomic.LoadUint32(&i)
|
|
|
|
|
if i1 != 1 {
|
|
|
|
|
t.Errorf("Expected number of health checks with RecursionDesired==true to be %d, got %d", 1, i1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 02:54:05 +02:00
|
|
|
func TestHealthHTTPS(t *testing.T) {
|
|
|
|
|
i := uint32(0)
|
|
|
|
|
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
msg, err := doh.RequestToMsg(r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if msg.Question[0].Name == "." && msg.RecursionDesired == true {
|
|
|
|
|
atomic.AddUint32(&i, 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret := new(dns.Msg)
|
|
|
|
|
ret.SetReply(msg)
|
|
|
|
|
|
|
|
|
|
buf, err := ret.Pack()
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", doh.MimeType)
|
|
|
|
|
w.Write(buf)
|
|
|
|
|
}))
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
|
|
hc := NewHealthChecker("TestHealthHTTPS", transport.HTTPS, true, ".")
|
|
|
|
|
hc.SetTLSConfig(s.Client().Transport.(*http.Transport).TLSClientConfig)
|
2026-07-07 20:00:40 -07:00
|
|
|
hc.SetReadTimeout(1 * time.Second)
|
|
|
|
|
hc.SetWriteTimeout(1 * time.Second)
|
2026-06-15 02:54:05 +02:00
|
|
|
|
|
|
|
|
p := NewProxy("TestHealthHTTPS", s.URL, transport.HTTPS)
|
|
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("check failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
|
i1 := atomic.LoadUint32(&i)
|
|
|
|
|
if i1 != 1 {
|
|
|
|
|
t.Errorf("Expected number of health checks with RecursionDesired==true to be %d, got %d", 1, i1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-24 12:55:51 +00:00
|
|
|
func TestHealthNoRecursion(t *testing.T) {
|
|
|
|
|
i := uint32(0)
|
|
|
|
|
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
|
if r.Question[0].Name == "." && r.RecursionDesired == false {
|
|
|
|
|
atomic.AddUint32(&i, 1)
|
|
|
|
|
}
|
|
|
|
|
ret := new(dns.Msg)
|
|
|
|
|
ret.SetReply(r)
|
|
|
|
|
w.WriteMsg(ret)
|
|
|
|
|
})
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
2023-11-09 06:30:32 +10:30
|
|
|
hc := NewHealthChecker("TestHealthNoRecursion", transport.DNS, false, ".")
|
2023-03-24 12:55:51 +00:00
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
p := NewProxy("TestHealthNoRecursion", s.Addr, transport.DNS)
|
2023-03-24 12:55:51 +00:00
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("check failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
|
i1 := atomic.LoadUint32(&i)
|
|
|
|
|
if i1 != 1 {
|
|
|
|
|
t.Errorf("Expected number of health checks with RecursionDesired==false to be %d, got %d", 1, i1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHealthTimeout(t *testing.T) {
|
2026-03-30 03:02:20 +03:00
|
|
|
s := dnstest.NewServer(func(_w dns.ResponseWriter, _r *dns.Msg) {
|
2023-03-24 12:55:51 +00:00
|
|
|
// timeout
|
|
|
|
|
})
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
2023-11-09 06:30:32 +10:30
|
|
|
hc := NewHealthChecker("TestHealthTimeout", transport.DNS, false, ".")
|
2023-03-24 12:55:51 +00:00
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
p := NewProxy("TestHealthTimeout", s.Addr, transport.DNS)
|
2023-03-24 12:55:51 +00:00
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Errorf("expected error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHealthDomain(t *testing.T) {
|
|
|
|
|
hcDomain := "example.org."
|
|
|
|
|
|
|
|
|
|
i := uint32(0)
|
|
|
|
|
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
|
|
|
|
if r.Question[0].Name == hcDomain && r.RecursionDesired == true {
|
|
|
|
|
atomic.AddUint32(&i, 1)
|
|
|
|
|
}
|
|
|
|
|
ret := new(dns.Msg)
|
|
|
|
|
ret.SetReply(r)
|
|
|
|
|
w.WriteMsg(ret)
|
|
|
|
|
})
|
|
|
|
|
defer s.Close()
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
hc := NewHealthChecker("TestHealthDomain", transport.DNS, true, hcDomain)
|
2023-03-24 12:55:51 +00:00
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
|
2023-07-04 15:35:55 +01:00
|
|
|
p := NewProxy("TestHealthDomain", s.Addr, transport.DNS)
|
2023-03-24 12:55:51 +00:00
|
|
|
p.readTimeout = 10 * time.Millisecond
|
|
|
|
|
err := hc.Check(p)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("check failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(12 * time.Millisecond)
|
|
|
|
|
i1 := atomic.LoadUint32(&i)
|
|
|
|
|
if i1 != 1 {
|
|
|
|
|
t.Errorf("Expected number of health checks with Domain==%s to be %d, got %d", hcDomain, 1, i1)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-10 02:38:22 +02:00
|
|
|
|
|
|
|
|
func TestHealthLocalAddress(t *testing.T) {
|
|
|
|
|
hc := NewHealthChecker("TestHealthLocalAddress", transport.DNS, true, ".")
|
|
|
|
|
hc.SetReadTimeout(10 * time.Millisecond)
|
|
|
|
|
hc.SetWriteTimeout(10 * time.Millisecond)
|
|
|
|
|
currLocalAddress := hc.GetLocalAddress()
|
|
|
|
|
if currLocalAddress != nil {
|
|
|
|
|
t.Errorf("Expected local address to be nil, got %s", currLocalAddress.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hc.SetLocalAddress(net.ParseIP("127.0.0.1"))
|
|
|
|
|
dnsClient := hc.(*dnsHc).c
|
|
|
|
|
if dnsClient.Dialer.LocalAddr.String() != "127.0.0.1:0" {
|
|
|
|
|
t.Errorf("Expected local address to be 127.0.0.1:0, got %s", dnsClient.Dialer.LocalAddr.String())
|
|
|
|
|
}
|
plugin/pkg/proxy: set IP_RECVERR on upstream UDP sockets (#8628)
Without IP_RECVERR, Linux passes only some ICMP errors, such as port
unreachable, to a connected UDP socket. It drops net and host unreachable
(net/ipv4/udp.c, __udp4_lib_err), so a query to an upstream behind an
unreachable route waits out the read timeout before forward tries the
next upstream, and health checks wait out theirs before marking it down.
Set IP_RECVERR (IPV6_RECVERR for IPv6) on the UDP sockets for upstream
queries and health checks, so the read fails when the ICMP error arrives.
TCP sockets are left alone, and nothing changes outside Linux. The health
check client gets an explicit 2s dial timeout, since a Dialer replaces
dns.Client's default one. Because the Dialer now exists from the start,
setDialer no longer creates a bare one when source_address is set, which
also lost the dial timeout before.
Fixes #8621
Signed-off-by: MohibShaikh <mohibuddin9@gmail.com>
2026-10-13 12:45:06 +05:00
|
|
|
// A Dialer replaces dns.Client's own dial timeout, so it must carry one.
|
|
|
|
|
if dnsClient.Dialer.Timeout != 2*time.Second {
|
|
|
|
|
t.Errorf("Expected dial timeout to be 2s, got %s", dnsClient.Dialer.Timeout)
|
|
|
|
|
}
|
2026-07-10 02:38:22 +02:00
|
|
|
|
|
|
|
|
// check type of underlying transport
|
|
|
|
|
_, ok := dnsClient.Dialer.LocalAddr.(*net.UDPAddr)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Error("Expected local address to be udp")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// set TCP transport
|
|
|
|
|
hc.SetTCPTransport()
|
|
|
|
|
|
|
|
|
|
// check update of underlying transport
|
|
|
|
|
_, ok = dnsClient.Dialer.LocalAddr.(*net.TCPAddr)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Error("Expected local address to be tcp")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tlsConfig := new(tls.Config)
|
|
|
|
|
// set TLS transport
|
|
|
|
|
hc.SetTLSConfig(tlsConfig)
|
|
|
|
|
|
|
|
|
|
// check update of underlying transport
|
|
|
|
|
_, ok = dnsClient.Dialer.LocalAddr.(*net.TCPAddr)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Error("Expected local address to be tcp")
|
|
|
|
|
}
|
|
|
|
|
}
|