fix(dnssec): fix DNSSEC by adding TCP fallback to DoUDP
This commit is contained in:
+2
-2
@@ -17,5 +17,5 @@
|
|||||||
**/tls-key-log.txt
|
**/tls-key-log.txt
|
||||||
/results
|
/results
|
||||||
/results.bak
|
/results.bak
|
||||||
/results_merged
|
/out
|
||||||
dns_results*
|
/results-dnssec
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ LOCAL_IP := 192.168.100.2
|
|||||||
|
|
||||||
.PHONY: all general dnssec clean clean-general clean-dnssec
|
.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 workload: results/ -> out/general/ -----
|
||||||
general:
|
general:
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ func New(upstream string, opts Options) (DNSClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// With DNSSEC, wrap the base client with a validating client.
|
// 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) {
|
func createClientFromURL(parsedURL *url.URL, opts Options) (DNSClient, error) {
|
||||||
|
|||||||
@@ -2,46 +2,51 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/afonsofrancof/sdns-proxy/common/dnssec"
|
"github.com/afonsofrancof/sdns-proxy/common/dnssec"
|
||||||
"github.com/afonsofrancof/sdns-proxy/common/logger"
|
"github.com/afonsofrancof/sdns-proxy/common/logger"
|
||||||
|
"github.com/afonsofrancof/sdns-proxy/common/protocols/doudp"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValidatingDNSClient struct {
|
type DNSSECClient struct {
|
||||||
client DNSClient
|
client DNSClient
|
||||||
validator *dnssec.Validator
|
|
||||||
options Options
|
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)
|
logger.Debug("Wrapping base client with DNSSEC validator (AuthoritativeDNSSEC: %v)", opts.AuthoritativeDNSSEC)
|
||||||
|
|
||||||
var validator *dnssec.Validator
|
var validator *dnssec.Validator
|
||||||
if opts.AuthoritativeDNSSEC {
|
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 {
|
} else {
|
||||||
validator = dnssec.NewValidator(func(m *dns.Msg) (*dns.Msg, error) { _, r, e := base.Query(m); return r, e })
|
validator = dnssec.NewValidator(func(m *dns.Msg) (*dns.Msg, error) { _, r, e := base.Query(m); return r, e })
|
||||||
}
|
}
|
||||||
|
return &DNSSECClient{client: base, validator: validator, options: opts}
|
||||||
return &ValidatingDNSClient{
|
|
||||||
client: base,
|
|
||||||
validator: validator,
|
|
||||||
options: opts,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ValidatingDNSClient) LastValidation() dnssec.ValidationStats {
|
func (v *DNSSECClient) LastValidation() dnssec.ValidationStats {
|
||||||
if v.validator == nil {
|
if v.validator == nil {
|
||||||
return dnssec.ValidationStats{}
|
return dnssec.ValidationStats{}
|
||||||
}
|
}
|
||||||
return v.validator.TakeStats()
|
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 {
|
if len(msg.Question) > 0 {
|
||||||
question := 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)
|
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
|
return sent, response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ValidatingDNSClient) Close() {
|
func (v *DNSSECClient) Close() {
|
||||||
logger.Debug("Closing ValidatingDNSClient")
|
logger.Debug("Closing DNSSECClient")
|
||||||
if v.client != nil {
|
if v.client != nil {
|
||||||
v.client.Close()
|
v.client.Close()
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,12 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"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 {
|
type ValidationStats struct {
|
||||||
Queries int
|
Queries int
|
||||||
BytesSent int
|
BytesSent int
|
||||||
@@ -28,8 +34,8 @@ func NewValidator(send SendFunc) *Validator {
|
|||||||
return &Validator{walker: newTrustWalker(send)}
|
return &Validator{walker: newTrustWalker(send)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthoritativeValidator() *Validator {
|
func NewAuthoritativeValidator(f ExchangeFactory) *Validator {
|
||||||
return &Validator{walker: newIterativeWalker()}
|
return &Validator{walker: newIterativeWalker(f)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) ValidateResponse(msg *dns.Msg, qname string, qtype uint16) error {
|
func (v *Validator) ValidateResponse(msg *dns.Msg, qname string, qtype uint16) error {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/afonsofrancof/sdns-proxy/common/logger"
|
"github.com/afonsofrancof/sdns-proxy/common/logger"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -12,15 +11,15 @@ import (
|
|||||||
|
|
||||||
// iterativeWalker validates top-down.
|
// iterativeWalker validates top-down.
|
||||||
type iterativeWalker struct {
|
type iterativeWalker struct {
|
||||||
client *dns.Client
|
newClient ExchangeFactory
|
||||||
st ValidationStats
|
st ValidationStats
|
||||||
ipCache map[string]string
|
ipCache map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIterativeWalker() *iterativeWalker {
|
func newIterativeWalker(f ExchangeFactory) *iterativeWalker {
|
||||||
return &iterativeWalker{
|
return &iterativeWalker{
|
||||||
client: &dns.Client{Timeout: 5 * time.Second},
|
newClient: f,
|
||||||
ipCache: make(map[string]string),
|
ipCache: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +37,13 @@ func (w *iterativeWalker) exchange(server, name string, qtype uint16) (*dns.Msg,
|
|||||||
}
|
}
|
||||||
w.st.Queries++
|
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 err == nil && resp != nil {
|
||||||
if b, perr := resp.Pack(); perr == nil {
|
if b, perr := resp.Pack(); perr == nil {
|
||||||
w.st.BytesReceived += len(b)
|
w.st.BytesReceived += len(b)
|
||||||
|
|||||||
@@ -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)
|
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 {
|
if len(response.Answer) > 0 {
|
||||||
logger.Debug("DoUDP response from %s: %d answers", c.hostAndPort, len(response.Answer))
|
logger.Debug("DoUDP response from %s: %d answers", c.hostAndPort, len(response.Answer))
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg, response, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user