fix(dnssec): fix DNSSEC by adding TCP fallback to DoUDP

This commit is contained in:
2026-07-12 00:30:05 +01:00
parent e847b94474
commit c330fa6289
7 changed files with 84 additions and 30 deletions
+2 -2
View File
@@ -17,5 +17,5 @@
**/tls-key-log.txt
/results
/results.bak
/results_merged
dns_results*
/out
/results-dnssec
+5 -1
View File
@@ -15,7 +15,11 @@ LOCAL_IP := 192.168.100.2
.PHONY: all general dnssec clean clean-general clean-dnssec
all: general dnssec
all: get_files general dnssec
get_files:
rsync -a --progress afonso@afonso-pi:~/sdns-proxy/results/ $(GEN_IN)
rsync -a --progress afonso@afonso-pi:~/sdns-proxy/results/ $(DNS_IN)
# ----- general workload: results/ -> out/general/ -----
general:
+1 -1
View File
@@ -60,7 +60,7 @@ func New(upstream string, opts Options) (DNSClient, error) {
}
// With DNSSEC, wrap the base client with a validating client.
return NewValidating(baseClient, opts), nil
return NewDNSSECClient(baseClient, opts), nil
}
func createClientFromURL(parsedURL *url.URL, opts Options) (DNSClient, error) {
@@ -2,46 +2,51 @@ package client
import (
"fmt"
"net"
"github.com/afonsofrancof/sdns-proxy/common/dnssec"
"github.com/afonsofrancof/sdns-proxy/common/logger"
"github.com/afonsofrancof/sdns-proxy/common/protocols/doudp"
"github.com/miekg/dns"
)
type ValidatingDNSClient struct {
type DNSSECClient struct {
client DNSClient
validator *dnssec.Validator
options Options
validator *dnssec.Validator
}
func NewValidating(base DNSClient, opts Options) DNSClient {
func NewDNSSECClient(base DNSClient, opts Options) DNSClient {
logger.Debug("Wrapping base client with DNSSEC validator (AuthoritativeDNSSEC: %v)", opts.AuthoritativeDNSSEC)
var validator *dnssec.Validator
if opts.AuthoritativeDNSSEC {
validator = dnssec.NewAuthoritativeValidator()
validator = dnssec.NewAuthoritativeValidator(func(server string) (dnssec.Exchanger, error) {
host, port, err := net.SplitHostPort(server)
if err != nil {
host, port = server, "53"
}
return doudp.New(doudp.Config{
HostAndPort: net.JoinHostPort(host, port),
DNSSEC: true,
})
})
} else {
validator = dnssec.NewValidator(func(m *dns.Msg) (*dns.Msg, error) { _, r, e := base.Query(m); return r, e })
}
return &ValidatingDNSClient{
client: base,
validator: validator,
options: opts,
}
return &DNSSECClient{client: base, validator: validator, options: opts}
}
func (v *ValidatingDNSClient) LastValidation() dnssec.ValidationStats {
func (v *DNSSECClient) LastValidation() dnssec.ValidationStats {
if v.validator == nil {
return dnssec.ValidationStats{}
}
return v.validator.TakeStats()
}
func (v *ValidatingDNSClient) Query(msg *dns.Msg) (*dns.Msg, *dns.Msg, error) {
func (v *DNSSECClient) Query(msg *dns.Msg) (*dns.Msg, *dns.Msg, error) {
if len(msg.Question) > 0 {
question := msg.Question[0]
logger.Debug("ValidatingDNSClient query: %s %s (DNSSEC: %v, AuthoritativeDNSSEC: %v, ValidateOnly: %v, StrictValidation: %v)",
logger.Debug("DNSSECClient query: %s %s (DNSSEC: %v, AuthoritativeDNSSEC: %v, ValidateOnly: %v, StrictValidation: %v)",
question.Name, dns.TypeToString[question.Qtype], v.options.DNSSEC, v.options.AuthoritativeDNSSEC, v.options.ValidateOnly, v.options.StrictValidation)
}
@@ -90,8 +95,8 @@ func (v *ValidatingDNSClient) Query(msg *dns.Msg) (*dns.Msg, *dns.Msg, error) {
return sent, response, nil
}
func (v *ValidatingDNSClient) Close() {
logger.Debug("Closing ValidatingDNSClient")
func (v *DNSSECClient) Close() {
logger.Debug("Closing DNSSECClient")
if v.client != nil {
v.client.Close()
}
+8 -2
View File
@@ -5,6 +5,12 @@ import (
"github.com/miekg/dns"
)
type Exchanger interface {
Query(msg *dns.Msg) (sent, resp *dns.Msg, err error)
Close()
}
type ExchangeFactory func(server string) (Exchanger, error)
type ValidationStats struct {
Queries int
BytesSent int
@@ -28,8 +34,8 @@ func NewValidator(send SendFunc) *Validator {
return &Validator{walker: newTrustWalker(send)}
}
func NewAuthoritativeValidator() *Validator {
return &Validator{walker: newIterativeWalker()}
func NewAuthoritativeValidator(f ExchangeFactory) *Validator {
return &Validator{walker: newIterativeWalker(f)}
}
func (v *Validator) ValidateResponse(msg *dns.Msg, qname string, qtype uint16) error {
+13 -8
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"net"
"strings"
"time"
"github.com/afonsofrancof/sdns-proxy/common/logger"
"github.com/miekg/dns"
@@ -12,15 +11,15 @@ import (
// iterativeWalker validates top-down.
type iterativeWalker struct {
client *dns.Client
st ValidationStats
ipCache map[string]string
newClient ExchangeFactory
st ValidationStats
ipCache map[string]string
}
func newIterativeWalker() *iterativeWalker {
func newIterativeWalker(f ExchangeFactory) *iterativeWalker {
return &iterativeWalker{
client: &dns.Client{Timeout: 5 * time.Second},
ipCache: make(map[string]string),
newClient: f,
ipCache: make(map[string]string),
}
}
@@ -38,7 +37,13 @@ func (w *iterativeWalker) exchange(server, name string, qtype uint16) (*dns.Msg,
}
w.st.Queries++
resp, _, err := w.client.Exchange(m, server)
cl, err := w.newClient(server)
if err != nil {
return nil, err
}
defer cl.Close()
_, resp, err := cl.Query(m)
if err == nil && resp != nil {
if b, perr := resp.Pack(); perr == nil {
w.st.BytesReceived += len(b)
+34
View File
@@ -115,9 +115,43 @@ func (c *Client) Query(msg *dns.Msg) (*dns.Msg, *dns.Msg, error) {
return msg, nil, fmt.Errorf("doudp: failed to unpack DNS response: %w", err)
}
// RFC 1123 / 7766: a truncated UDP answer must be retried over TCP.
if response.Truncated {
logger.Debug("DoUDP response from %s truncated (TC set), retrying over TCP", c.hostAndPort)
tcpResp, terr := c.queryTCP(msg)
if terr != nil {
return msg, nil, fmt.Errorf("doudp: TCP fallback failed: %w", terr)
}
response = tcpResp
}
if len(response.Answer) > 0 {
logger.Debug("DoUDP response from %s: %d answers", c.hostAndPort, len(response.Answer))
}
return msg, response, nil
}
func (c *Client) queryTCP(msg *dns.Msg) (*dns.Msg, error) {
conn, err := net.DialTimeout("tcp", c.hostAndPort, c.config.WriteTimeout)
if err != nil {
return nil, fmt.Errorf("dial tcp: %w", err)
}
defer conn.Close()
co := &dns.Conn{Conn: conn}
if err := co.SetWriteDeadline(time.Now().Add(c.config.WriteTimeout)); err != nil {
return nil, fmt.Errorf("set write deadline: %w", err)
}
if err := co.WriteMsg(msg); err != nil {
return nil, fmt.Errorf("write: %w", err)
}
if err := co.SetReadDeadline(time.Now().Add(c.config.ReadTimeout)); err != nil {
return nil, fmt.Errorf("set read deadline: %w", err)
}
resp, err := co.ReadMsg()
if err != nil {
return nil, fmt.Errorf("read: %w", err)
}
return resp, nil
}