Skip to content

Commit

Permalink
Follow CNAME and DNAME records (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
mreiger authored May 16, 2023
1 parent 970b715 commit 9363939
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 17 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,5 @@ droptailer-6d556bd988-4g8gp droptailer 2020-06-17 13:23:51 +0000 UTC {"ACTION":"
```

You can forward the droptailer logs to any log aggregation infrastructure you have in place.

If enabled in the firewall spec, accepted connections can be logged in addition to dropped connections.
8 changes: 7 additions & 1 deletion pkg/dns/dns_proxy_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dns
import (
"fmt"
"net"
"strings"
"time"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -109,7 +110,12 @@ func getUpdateCacheFunc(log logr.Logger, cache *DNSCache) func(lookupTime time.T
return func(lookupTime time.Time, response *dnsgo.Msg) {
if response.Response && response.Rcode == dnsgo.RcodeSuccess {
scopedLog := log.WithValues(reqIdLogField, response.Id)
if err := cache.Update(lookupTime, response); err != nil {
var qname string
if response.Question != nil && len(response.Question) > 0 {
qname = strings.ToLower(response.Question[0].Name)
}
log.V(4).Info("DEBUG dnsproxyhandler function getUpdateCacheFunc updating DNS cache", "queried name", qname, "dns response", response)
if _, err := cache.Update(lookupTime, qname, response); err != nil {
scopedLog.Error(err, "failed to update DNS cache")
}
}
Expand Down
82 changes: 66 additions & 16 deletions pkg/dns/dnscache.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const (
// Versions specifically for nftables rendering purposes
IPv4 IPVersion = "ipv4_addr"
IPv6 IPVersion = "ipv6_addr"

// How many DNS redirections (CNAME/DNAME) are followed, to break up redirection loops.
maxDNSRedirects = 10
)

// RenderIPSet stores set info for rendering
Expand Down Expand Up @@ -218,7 +221,7 @@ func (c *DNSCache) getSetNameForFQDN(fqdn string) (result []firewallv1.IPSet) {

if !found {
c.RUnlock()
if err := c.loadDataFromDNSServer(fqdn); err != nil {
if err := c.loadDataFromDNSServer([]string{fqdn}); err != nil {
c.log.Error(err, "failed to load data for FQDN")
return nil
}
Expand All @@ -241,18 +244,28 @@ func (c *DNSCache) getSetNameForFQDN(fqdn string) (result []firewallv1.IPSet) {
return
}

func (c *DNSCache) loadDataFromDNSServer(fqdn string) error {
func (c *DNSCache) loadDataFromDNSServer(fqdns []string) error {
c.log.V(4).Info("DEBUG dnscache loadDataFromDNSServer function called", "fqdns", fqdns)
if len(fqdns) == 0 {
return fmt.Errorf("no fqdn given")
}
if len(fqdns) > maxDNSRedirects+1 {
return fmt.Errorf("too many hops, fqdn chain: %s", strings.Join(fqdns, ","))
}
qname := fqdns[len(fqdns)-1]
cl := new(dnsgo.Client)
for _, t := range []uint16{dnsgo.TypeA, dnsgo.TypeAAAA} {
m := new(dnsgo.Msg)
m.Id = dnsgo.Id()
m.SetQuestion(fqdn, t)
m.SetQuestion(qname, t)
c.log.V(4).Info("DEBUG dnscache loadDataFromDNSServer function querying DNS", "message", m)
in, _, err := cl.Exchange(m, c.dnsServerAddr)
if err != nil {
return fmt.Errorf("failed to get DNS data about fqdn %s: %w", fqdn, err)
return fmt.Errorf("failed to get DNS data about fqdn %s: %w", fqdns[0], err)
}
if err = c.Update(time.Now(), in); err != nil {
return fmt.Errorf("failed to update DNS data for fqdn %s: %w", fqdn, err)
c.log.V(4).Info("DEBUG dnscache loadDataFromDNSServer function calling Update function", "answer", in, "fqdns", fqdns)
if _, err = c.Update(time.Now(), qname, in, fqdns); err != nil {
return fmt.Errorf("failed to update DNS data for fqdn %s: %w", fqdns[0], err)
}
}

Expand Down Expand Up @@ -283,16 +296,31 @@ func (c *DNSCache) getSetNameForRegex(regex string) (sets []firewallv1.IPSet) {
// Update DNS cache.
// It expects that there was only one question to DNS(majority of cases).
// So it picks first qname and skips all others(if there is).
func (c *DNSCache) Update(lookupTime time.Time, msg *dnsgo.Msg) error {
qname := strings.ToLower(msg.Question[0].Name)
func (c *DNSCache) Update(lookupTime time.Time, qname string, msg *dnsgo.Msg, fqdnsfield ...[]string) (bool, error) {
c.log.V(4).Info("DEBUG dnscache Update function called", "Message", msg, "fqdnsfield", fqdnsfield)

fqdns := []string{}
if len(fqdnsfield) == 0 {
fqdns = append(fqdns, qname)
c.log.V(4).Info("DEBUG dnscache Update function not called with fqdnsfield parameter", "fqdns", fqdns)
} else {
fqdns = fqdnsfield[0]
c.log.V(4).Info("DEBUG dnscache Update function called with fqdnsfield parameter", "fqdns", fqdns)
}
if len(fqdns) > maxDNSRedirects+1 {
return true, fmt.Errorf("too many hops, fqdn chain: %s", strings.Join(fqdns, ","))
}

ipv4 := []net.IP{}
ipv6 := []net.IP{}
minIPv4TTL := uint32(math.MaxUint32)
minIPv6TTL := uint32(math.MaxUint32)
found := false

for _, ans := range msg.Answer {
c.log.V(4).Info("DEBUG dnscache Update function", "considering DNS answer", ans)
if strings.ToLower(ans.Header().Name) != qname {
c.log.V(4).Info("DEBUG dnscache Update function name does not match our query, continuing", "name", strings.ToLower(ans.Header().Name), "qname", qname)
continue
}

Expand All @@ -302,28 +330,50 @@ func (c *DNSCache) Update(lookupTime time.Time, msg *dnsgo.Msg) error {
if minIPv4TTL > rr.Hdr.Ttl {
minIPv4TTL = rr.Hdr.Ttl
}
found = true
c.log.V(4).Info("DEBUG dnscache Update function A record found", "IPs", ipv4)
case *dnsgo.AAAA:
ipv6 = append(ipv6, rr.AAAA)
if minIPv6TTL > rr.Hdr.Ttl {
minIPv6TTL = rr.Hdr.Ttl
}
found = true
c.log.V(4).Info("DEBUG dnscache Update function AAAA record found", "IPs", ipv6)
case *dnsgo.CNAME:
c.log.V(4).Info("DEBUG dnscache Update function CNAME record found. Looking for resolution in same DNS reply", "CNAME", rr.Target, "fqdns slice", append(fqdns, rr.Target))
stop, err := c.Update(lookupTime, rr.Target, msg, append(fqdns, rr.Target))
if err != nil {
return found, fmt.Errorf("error while trying to resolve CNAME %s within the same DNS reply: %w", rr.Target, err)
}
if stop {
return true, nil
}
c.log.V(4).Info("DEBUG dnscache Update function CNAME record found, could not resolve in same DNS reply. Performing DNS lookup", "CNAME", rr.Target, "fqdns slice", append(fqdns, rr.Target))
err = c.loadDataFromDNSServer(append(fqdns, rr.Target))
if err != nil {
return true, fmt.Errorf("could not look up address for CNAME %s: %w", rr.Target, err)
}
return true, nil
default:
continue
}
}

if c.ipv4Enabled && len(ipv4) > 0 {
if err := c.updateIPEntry(qname, ipv4, lookupTime.Add(time.Duration(minIPv4TTL)), nftables.TypeIPAddr); err != nil {
return fmt.Errorf("failed to update IPv4 addresses: %w", err)
for _, fqdn := range fqdns {
c.log.V(4).Info("DEBUG dnscache Update function Updating DNS cache for", "fqdn", fqdn, "ipv4", ipv4, "ipv6", ipv6)
if c.ipv4Enabled && len(ipv4) > 0 {
if err := c.updateIPEntry(fqdn, ipv4, lookupTime.Add(time.Duration(minIPv4TTL)), nftables.TypeIPAddr); err != nil {
return false, fmt.Errorf("failed to update IPv4 addresses: %w", err)
}
}
}
if c.ipv6Enabled && len(ipv6) > 0 {
if err := c.updateIPEntry(qname, ipv6, lookupTime.Add(time.Duration(minIPv6TTL)), nftables.TypeIP6Addr); err != nil {
return fmt.Errorf("failed to update IPv6 addresses: %w", err)
if c.ipv6Enabled && len(ipv6) > 0 {
if err := c.updateIPEntry(fqdn, ipv6, lookupTime.Add(time.Duration(minIPv6TTL)), nftables.TypeIP6Addr); err != nil {
return false, fmt.Errorf("failed to update IPv6 addresses: %w", err)
}
}
}

return nil
return found, nil
}

func (c *DNSCache) updateIPEntry(qname string, ips []net.IP, expirationTime time.Time, dtype nftables.SetDatatype) error {
Expand Down

0 comments on commit 9363939

Please sign in to comment.