Skip to content

Commit

Permalink
Added support for following CNAMEs
Browse files Browse the repository at this point in the history
  • Loading branch information
nsmithuk committed Oct 27, 2024
1 parent 4373a09 commit 303bc7e
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 43 deletions.
45 changes: 45 additions & 0 deletions cname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package resolver

import (
"context"
"fmt"
"github.com/miekg/dns"
)

func cname(ctx context.Context, qmsg *dns.Msg, r *Response, exchanger exchanger) error {
cnames := extractRecords[*dns.CNAME](r.Msg.Answer)
Debug(fmt.Sprintf("resolved %s to %d cnames", qmsg.Question[0].Name, len(cnames)))

for _, c := range cnames {
target := dns.CanonicalName(c.Target)

if recordsOfNameAndTypeExist(r.Msg.Answer, target, qmsg.Question[0].Qtype) || recordsOfNameAndTypeExist(r.Msg.Answer, target, dns.TypeCNAME) {
// Skip over the answer already contains a record for the target.
continue
}

qmsgCNAME := new(dns.Msg)
qmsgCNAME.SetQuestion(target, qmsg.Question[0].Qtype)

if isSetDO(qmsg) {
qmsgCNAME.SetEdns0(4096, true)
}

rmsgCNAME := exchanger.exchange(ctx, qmsgCNAME)

if rmsgCNAME.Error() {
return rmsgCNAME.Err
}
if rmsgCNAME.Empty() {
return fmt.Errorf("unable to follow cname [%s]", c.Target)
}

r.Msg.Answer = append(r.Msg.Answer, rmsgCNAME.Msg.Answer...)
r.Msg.Ns = append(r.Msg.Ns, rmsgCNAME.Msg.Ns...)
r.Msg.Extra = append(r.Msg.Extra, rmsgCNAME.Msg.Extra...)

r.Auth = r.Auth.Combine(rmsgCNAME.Auth)
}

return nil
}
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const (
DefaultLazyEnrichment = false

DefaultSuppressBogusResponseSections = true

DefaultRemoveAuthoritySectionForPositiveAnswers = true
)

var (
Expand All @@ -36,6 +38,10 @@ var (
// be suppressed if a response is Bogus. The default and recommended value is true which
// aligns the resolver with https://datatracker.ietf.org/doc/html/rfc4035#section-5.5
SuppressBogusResponseSections = DefaultSuppressBogusResponseSections

// RemoveAuthoritySectionForPositiveAnswers indicates if the Authority section should be returned when it's deemed
// that it's record have no material impact on the result. e.g. it only contains nameserver records.
RemoveAuthoritySectionForPositiveAnswers = DefaultRemoveAuthoritySectionForPositiveAnswers
)

//---
Expand Down
69 changes: 69 additions & 0 deletions dnssec/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,46 @@ const (
Bogus
)

func (r AuthenticationResult) String() string {
switch r {
default:
fallthrough
case Unknown:
return "Unknown"
case Insecure:
return "Insecure"
case Secure:
return "Secure"
case Bogus:
return "Bogus"
}
}

// Combine determines the overall AuthenticationResult when merging two authenticated results,
// such as when a result is based on multiple DNS requests (e.g., following a CNAME chain).
func (r AuthenticationResult) Combine(r2 AuthenticationResult) AuthenticationResult {
// If either result is Bogus, the overall result should be Bogus.
if r == Bogus || r2 == Bogus {
return Bogus
}
// If either result is Unknown, the overall result should be Unknown.
if r == Unknown || r2 == Unknown {
return Unknown
}
// If either result is Insecure, the overall result should be Insecure.
if r == Insecure || r2 == Insecure {
return Insecure
}
// Only return Secure if both results are independently Secure.
if r == Secure && r2 == Secure {
return Secure
}
// Default to Bogus if none of the conditions are met.
return Bogus
}

//---

type DenialOfExistenceState uint8

const (
Expand All @@ -26,6 +66,35 @@ const (
Nsec3Wildcard
)

func (d DenialOfExistenceState) String() string {
switch d {
default:
fallthrough
case NotFound:
return "NotFound"
case NsecMissingDS:
return "NsecMissingDS"
case NsecNoData:
return "NsecNoData"
case NsecNxDomain:
return "NsecNxDomain"
case NsecWildcard:
return "NsecWildcard"
case Nsec3MissingDS:
return "Nsec3MissingDS"
case Nsec3NoData:
return "Nsec3NoData"
case Nsec3NxDomain:
return "Nsec3NxDomain"
case Nsec3OptOut:
return "Nsec3OptOut"
case Nsec3Wildcard:
return "Nsec3Wildcard"
}
}

//---

type section bool

const (
Expand Down
67 changes: 67 additions & 0 deletions dnssec/const_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package dnssec

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAuthenticationResult_String(t *testing.T) {
tests := []struct {
result AuthenticationResult
expected string
}{
{Unknown, "Unknown"},
{Insecure, "Insecure"},
{Secure, "Secure"},
{Bogus, "Bogus"},
}

for _, test := range tests {
assert.Equal(t, test.expected, test.result.String())
}
}

func TestAuthenticationResult_Combine(t *testing.T) {
tests := []struct {
r1, r2 AuthenticationResult
expected AuthenticationResult
}{
{Secure, Secure, Secure},
{Secure, Insecure, Insecure},
{Secure, Unknown, Unknown},
{Secure, Bogus, Bogus},
{Insecure, Insecure, Insecure},
{Insecure, Unknown, Unknown},
{Insecure, Bogus, Bogus},
{Unknown, Unknown, Unknown},
{Unknown, Bogus, Bogus},
{Bogus, Bogus, Bogus},
}

for _, test := range tests {
assert.Equal(t, test.expected, test.r1.Combine(test.r2))
}
}

func TestDenialOfExistenceState_String(t *testing.T) {
tests := []struct {
state DenialOfExistenceState
expected string
}{
{NotFound, "NotFound"},
{NsecMissingDS, "NsecMissingDS"},
{NsecNoData, "NsecNoData"},
{NsecNxDomain, "NsecNxDomain"},
{NsecWildcard, "NsecWildcard"},
{Nsec3MissingDS, "Nsec3MissingDS"},
{Nsec3NoData, "Nsec3NoData"},
{Nsec3NxDomain, "Nsec3NxDomain"},
{Nsec3OptOut, "Nsec3OptOut"},
{Nsec3Wildcard, "Nsec3Wildcard"},
}

for _, test := range tests {
assert.Equal(t, test.expected, test.state.String())
}
}
84 changes: 64 additions & 20 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,61 @@ import (
)

var dnsRecordTypes = map[uint16]string{
1: "A",
2: "NS",
5: "CNAME",
6: "SOA",
12: "PTR",
15: "MX",
16: "TXT",
28: "AAAA",
33: "SRV",
35: "NAPTR",
36: "KX",
37: "CERT",
39: "DNAME",
43: "DS",
46: "RRSIG",
47: "NSEC",
48: "DNSKEY",
50: "NSEC3",
51: "NSEC3PARAM",
257: "CAA",
1: "A",
2: "NS",
5: "CNAME",
6: "SOA",
12: "PTR",
15: "MX",
16: "TXT",
17: "RP",
18: "AFSDB",
24: "SIG", // Predecessor to RRSIG, included for completeness
25: "KEY", // Predecessor to DNSKEY
28: "AAAA",
29: "LOC",
33: "SRV",
35: "NAPTR",
36: "KX",
37: "CERT",
39: "DNAME",
41: "OPT", // Pseudo-record for EDNS (Extended DNS)
43: "DS",
44: "SSHFP", // SSH Public Key Fingerprint
45: "IPSECKEY", // IPsec Key
46: "RRSIG",
47: "NSEC",
48: "DNSKEY",
49: "DHCID", // DHCP Identifier
50: "NSEC3",
51: "NSEC3PARAM",
52: "TLSA",
53: "SMIMEA", // S/MIME certificate association
55: "HIP", // Host Identity Protocol
57: "NINFO", // (Experimental, rarely used)
59: "CDS", // Child DS, related to DNSSEC delegation
60: "CDNSKEY", // Child DNSKEY, related to DNSSEC delegation
61: "OPENPGPKEY", // OpenPGP public key
62: "CSYNC", // Child-To-Parent Synchronization
63: "ZONEMD", // Zone Message Digest
64: "SVCB", // Service Binding
65: "HTTPS", // HTTPS-specific Service Binding
99: "SPF", // Sender Policy Framework, typically TXT now
100: "UINFO", // User Information
101: "UID", // User ID
102: "GID", // Group ID
103: "UNSPEC", // Unspecified Information
108: "EUI48", // Extended Unique Identifier (48-bit)
109: "EUI64", // Extended Unique Identifier (64-bit)
249: "TKEY", // Transaction Key, for DNS security
250: "TSIG", // Transaction Signature, for DNS security
251: "IXFR", // Incremental Zone Transfer
252: "AXFR", // Full Zone Transfer
255: "ANY", // Query for all record types
256: "URI", // URI record
257: "CAA",
32768: "TA", // Trust Anchor, experimental
32769: "DLV", // DNSSEC Lookaside Validation, obsolete
}

func TypeToString(rrtype uint16) string {
Expand Down Expand Up @@ -100,3 +135,12 @@ func recordsOfTypeExist(rr []dns.RR, t uint16) bool {
}
return false
}

func recordsOfNameAndTypeExist(rr []dns.RR, name string, t uint16) bool {
for _, record := range rr {
if record.Header().Rrtype == t && record.Header().Name == name {
return true
}
}
return false
}
2 changes: 2 additions & 0 deletions ipv6.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func IPv6Available() bool {
func UpdateIPv6Availability() {
defer ipv6Answered.Store(true)

// TODO: This needs to be better. I need to make sure I can actually talk to the server.

// Tries:
// k.root-servers.net
// e.root-servers.net.
Expand Down
20 changes: 12 additions & 8 deletions pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,22 +257,26 @@ func (pool *nameserverPool) exchange(ctx context.Context, m *dns.Msg) *Response
var response *Response

if pool.hasIPv6() && IPv6Available() {
server := pool.getIPv6()
response = server.exchange(ctx, m)
if server := pool.getIPv6(); server != nil {
response = server.exchange(ctx, m)
}
} else {
server := pool.getIPv4()
response = server.exchange(ctx, m)
if server := pool.getIPv4(); server != nil {
response = server.exchange(ctx, m)
}
}

if response.Empty() || response.Error() || response.truncated() {
// If there was an issue, we give it one more try.
// If we have more than one nameserver, this will try a different one.
if pool.hasIPv4() {
server := pool.getIPv4()
response = server.exchange(ctx, m)
if server := pool.getIPv4(); server != nil {
response = server.exchange(ctx, m)
}
} else if pool.hasIPv6() {
server := pool.getIPv6()
response = server.exchange(ctx, m)
if server := pool.getIPv6(); server != nil {
response = server.exchange(ctx, m)
}
}
}

Expand Down
Loading

0 comments on commit 303bc7e

Please sign in to comment.