Skip to content

Commit

Permalink
adds handling for RFC 9156
Browse files Browse the repository at this point in the history
  • Loading branch information
ncode committed Jan 23, 2025
1 parent f82f520 commit 097c768
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .changelog/22095.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
release-note:improvement
dns: qname minimization handling based on rfc9156
12 changes: 11 additions & 1 deletion agent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
"encoding/hex"
"errors"
"fmt"
agentdns "github.com/hashicorp/consul/agent/dns"
"math"
"net"
"regexp"
"strings"
"sync/atomic"
"time"

agentdns "github.com/hashicorp/consul/agent/dns"

"github.com/armon/go-metrics"
"github.com/armon/go-radix"
"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -801,6 +802,15 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, cfg *dnsRe
}

invalid := func() error {
// Handle potential QNAME minimization queries
// Resolvers using QNAME minimization will send queries with partial names
// RFC 9156: Allow queries for partial domain components when they look like QNAME minimization
// eg. "service.dc.consul" or "service.consul"
if len(queryParts) == 0 && (queryKind != "") {
// Return empty non-referral response (RFC 9156 Section 3)
resp.Answer = []dns.RR{}
return nil
}
d.logger.Warn("QName invalid", "qname", qName)
return errNameNotFound
}
Expand Down
106 changes: 103 additions & 3 deletions agent/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2231,9 +2231,6 @@ func TestDNS_InvalidQueries(t *testing.T) {
// of our query parser.
questions := []string{
"consul.",
"node.consul.",
"service.consul.",
"query.consul.",
"foo.node.dc1.extra.more.consul.",
"foo.service.dc1.extra.more.consul.",
"foo.query.dc1.extra.more.consul.",
Expand Down Expand Up @@ -3439,3 +3436,106 @@ func TestDNS_EffectiveDatacenter(t *testing.T) {
})
}
}

func TestDNS_QNAMEMinimization(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}

t.Parallel()

a := NewTestAgent(t, "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")

// Register a node with a service to ensure service lookups have a valid target
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "api",
Tags: []string{"a"},
Port: 5000,
},
}

var out struct{}
require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out))
}

// Test cases for QNAME minimization partial queries - these should return empty success
partialQueries := []struct {
name string
query string
queryType uint16
}{
{"service-component", "service.consul.", dns.TypeA},
{"service-component-any", "service.consul.", dns.TypeANY},
{"node-component", "node.consul.", dns.TypeA},
{"node-component-any", "node.consul.", dns.TypeANY},
{"addr-component", "addr.consul.", dns.TypeA},
{"addr-component-any", "addr.consul.", dns.TypeANY},
{"query-component", "query.consul.", dns.TypeA},
{"query-component-any", "query.consul.", dns.TypeANY},
{"service-txt-type", "service.consul.", dns.TypeTXT},
}

for _, tc := range partialQueries {
t.Run(tc.name, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(tc.query, tc.queryType)

c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
require.Equal(t, dns.RcodeSuccess, in.Rcode)
require.Empty(t, in.Answer)
})
}

// Test cases for actual service/node lookups - these should return real records
fullQueries := []struct {
name string
query string
queryType uint16
checkFn func(*testing.T, *dns.Msg)
}{
{
"service-lookup",
"api.service.consul.",
dns.TypeA,
func(t *testing.T, in *dns.Msg) {
require.Len(t, in.Answer, 1)
aRec, ok := in.Answer[0].(*dns.A)
require.True(t, ok)
require.Equal(t, net.ParseIP("127.0.0.1").String(), aRec.A.String())
},
},
{
"node-lookup",
"foo.node.consul.",
dns.TypeA,
func(t *testing.T, in *dns.Msg) {
require.Len(t, in.Answer, 1)
aRec, ok := in.Answer[0].(*dns.A)
require.True(t, ok)
require.Equal(t, net.ParseIP("127.0.0.1").String(), aRec.A.String())
},
},
}

for _, tc := range fullQueries {
t.Run(tc.name, func(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion(tc.query, tc.queryType)

c := new(dns.Client)
in, _, err := c.Exchange(m, a.DNSAddr())
require.NoError(t, err)
require.Equal(t, dns.RcodeSuccess, in.Rcode)
tc.checkFn(t, in)
})
}
}

0 comments on commit 097c768

Please sign in to comment.