mirror of
https://github.com/coredns/coredns.git
synced 2026-04-05 11:45:33 -04:00
* plugin/file: expand SVCB/HTTPS record support Add proper SVCB (type 64) and HTTPS (type 65) handling: - Additional section processing: include A/AAAA glue for in-bailiwick SVCB/HTTPS targets, matching existing SRV/MX behavior - Target name normalization: lowercase SVCB/HTTPS Target on zone insert, consistent with CNAME/MX handling - Metrics: add TypeSVCB to monitored query types (TypeHTTPS was already present) - Test helpers: add SVCB()/HTTPS() constructors and Section comparison cases - Tests: basic queries with glue, AliasMode, wildcards, NoData, NXDOMAIN, target normalization, and DNS-AID private-use key (65400-65408) round-trip Signed-off-by: Ingmar <ivanglabbeek@infoblox.com> * plugin/file: simplify HTTPS target access via field promotion dns.HTTPS embeds dns.SVCB, so .Target is directly accessible without the redundant .SVCB. qualifier. Fixes gosimple S1027. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Ingmar <ivanglabbeek@infoblox.com> --------- Signed-off-by: Ingmar <ivanglabbeek@infoblox.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
225 lines
6.5 KiB
Go
225 lines
6.5 KiB
Go
package file
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
"github.com/coredns/coredns/plugin/test"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
var svcbAuth = []dns.RR{
|
|
test.NS("example.com. 1800 IN NS ns.example.com."),
|
|
}
|
|
|
|
var svcbTestCases = []test.Case{
|
|
{
|
|
// Basic SVCB query with additional section glue for in-bailiwick target
|
|
Qname: "_8443._foo.example.com.", Qtype: dns.TypeSVCB,
|
|
Answer: []dns.RR{
|
|
test.SVCB("_8443._foo.example.com. 1800 IN SVCB 1 svc-target.example.com. alpn=\"h2,h3\" port=\"8443\""),
|
|
},
|
|
Ns: svcbAuth,
|
|
Extra: []dns.RR{
|
|
test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
|
|
test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
|
|
},
|
|
},
|
|
{
|
|
// Basic HTTPS query with additional section glue for in-bailiwick target
|
|
Qname: "www.example.com.", Qtype: dns.TypeHTTPS,
|
|
Answer: []dns.RR{
|
|
test.HTTPS("www.example.com. 1800 IN HTTPS 1 svc-target.example.com. alpn=\"h2,h3\""),
|
|
},
|
|
Ns: svcbAuth,
|
|
Extra: []dns.RR{
|
|
test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
|
|
test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
|
|
},
|
|
},
|
|
{
|
|
// SVCB AliasMode (Priority=0) — glue still added for in-bailiwick target
|
|
Qname: "_alias._foo.example.com.", Qtype: dns.TypeSVCB,
|
|
Answer: []dns.RR{
|
|
test.SVCB("_alias._foo.example.com. 1800 IN SVCB 0 svc-target.example.com."),
|
|
},
|
|
Ns: svcbAuth,
|
|
Extra: []dns.RR{
|
|
test.A("svc-target.example.com. 1800 IN A 192.0.2.10"),
|
|
test.AAAA("svc-target.example.com. 1800 IN AAAA 2001:db8::10"),
|
|
},
|
|
},
|
|
{
|
|
// Wildcard SVCB expansion (no additional section — wildcards don't run additionalProcessing)
|
|
Qname: "_http._tcp.example.com.", Qtype: dns.TypeSVCB,
|
|
Answer: []dns.RR{
|
|
test.SVCB("_http._tcp.example.com. 1800 IN SVCB 1 svc-target.example.com. port=\"443\""),
|
|
},
|
|
Ns: svcbAuth,
|
|
},
|
|
{
|
|
// NoData: existing name, no SVCB record
|
|
Qname: "svc-target.example.com.", Qtype: dns.TypeSVCB,
|
|
Ns: []dns.RR{
|
|
test.SOA("example.com. 1800 IN SOA ns.example.com. admin.example.com. 2024010100 14400 3600 604800 14400"),
|
|
},
|
|
},
|
|
{
|
|
// NXDOMAIN
|
|
Qname: "nonexistent.example.com.", Qtype: dns.TypeSVCB,
|
|
Rcode: dns.RcodeNameError,
|
|
Ns: []dns.RR{
|
|
test.SOA("example.com. 1800 IN SOA ns.example.com. admin.example.com. 2024010100 14400 3600 604800 14400"),
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestLookupSVCB(t *testing.T) {
|
|
zone, err := Parse(strings.NewReader(dbSVCBExample), testSVCBOrigin, "stdin", 0)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error when reading zone, got %q", err)
|
|
}
|
|
|
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
|
|
ctx := context.TODO()
|
|
|
|
for _, tc := range svcbTestCases {
|
|
m := tc.Msg()
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
_, err := fm.ServeDNS(ctx, rec, m)
|
|
if err != nil {
|
|
t.Errorf("Expected no error for %q/%d, got %v", tc.Qname, tc.Qtype, err)
|
|
continue
|
|
}
|
|
|
|
resp := rec.Msg
|
|
if err := test.SortAndCheck(resp, tc); err != nil {
|
|
t.Errorf("Test %q/%d: %v", tc.Qname, tc.Qtype, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSVCBTargetNormalization(t *testing.T) {
|
|
// Zone with mixed-case SVCB target — should be lowercased on insert
|
|
const dbMixedCase = `
|
|
$TTL 30M
|
|
$ORIGIN example.com.
|
|
@ IN SOA ns.example.com. admin.example.com. ( 2024010100 14400 3600 604800 14400 )
|
|
IN NS ns.example.com.
|
|
ns IN A 192.0.2.1
|
|
svc IN A 192.0.2.10
|
|
_foo IN SVCB 1 SVC.Example.COM. alpn=h2
|
|
`
|
|
zone, err := Parse(strings.NewReader(dbMixedCase), testSVCBOrigin, "stdin", 0)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error when reading zone, got %q", err)
|
|
}
|
|
|
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
|
|
ctx := context.TODO()
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("_foo.example.com.", dns.TypeSVCB)
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
_, err = fm.ServeDNS(ctx, rec, m)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
resp := rec.Msg
|
|
if len(resp.Answer) != 1 {
|
|
t.Fatalf("Expected 1 answer, got %d", len(resp.Answer))
|
|
}
|
|
|
|
svcb, ok := resp.Answer[0].(*dns.SVCB)
|
|
if !ok {
|
|
t.Fatalf("Expected SVCB record, got %T", resp.Answer[0])
|
|
}
|
|
if svcb.Target != "svc.example.com." {
|
|
t.Errorf("Expected lowercased target %q, got %q", "svc.example.com.", svcb.Target)
|
|
}
|
|
|
|
// Verify additional section contains glue (target was normalized so lookup works)
|
|
if len(resp.Extra) != 1 {
|
|
t.Fatalf("Expected 1 extra record (A glue), got %d", len(resp.Extra))
|
|
}
|
|
}
|
|
|
|
func TestSVCBPrivateKeys(t *testing.T) {
|
|
// Test DNS-AID private-use SvcParamKeys (65400-65408) round-trip
|
|
zone, err := Parse(strings.NewReader(dbSVCBExample), testSVCBOrigin, "stdin", 0)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error when reading zone, got %q", err)
|
|
}
|
|
|
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{testSVCBOrigin: zone}, Names: []string{testSVCBOrigin}}}
|
|
ctx := context.TODO()
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion("_dnsaid.example.com.", dns.TypeHTTPS)
|
|
|
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
_, err = fm.ServeDNS(ctx, rec, m)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
resp := rec.Msg
|
|
if len(resp.Answer) != 1 {
|
|
t.Fatalf("Expected 1 answer, got %d", len(resp.Answer))
|
|
}
|
|
|
|
https, ok := resp.Answer[0].(*dns.HTTPS)
|
|
if !ok {
|
|
t.Fatalf("Expected HTTPS record, got %T", resp.Answer[0])
|
|
}
|
|
|
|
// Verify private-use keys survived the round-trip
|
|
rr := https.String()
|
|
for _, key := range []string{"key65400=", "key65401=", "key65406="} {
|
|
if !strings.Contains(rr, key) {
|
|
t.Errorf("Expected response to contain %s, got: %s", key, rr)
|
|
}
|
|
}
|
|
}
|
|
|
|
const testSVCBOrigin = "example.com."
|
|
|
|
const dbSVCBExample = `
|
|
$TTL 30M
|
|
$ORIGIN example.com.
|
|
@ IN SOA ns.example.com. admin.example.com. (
|
|
2024010100 ; serial
|
|
14400 ; refresh (4 hours)
|
|
3600 ; retry (1 hour)
|
|
604800 ; expire (1 week)
|
|
14400 ; minimum (4 hours)
|
|
)
|
|
IN NS ns.example.com.
|
|
|
|
; A/AAAA records for glue
|
|
ns IN A 192.0.2.1
|
|
svc-target IN A 192.0.2.10
|
|
IN AAAA 2001:db8::10
|
|
|
|
; SVCB ServiceMode with in-bailiwick target (tests additional section processing)
|
|
_8443._foo IN SVCB 1 svc-target.example.com. alpn=h2,h3 port=8443
|
|
|
|
; HTTPS ServiceMode with in-bailiwick target
|
|
www IN HTTPS 1 svc-target.example.com. alpn=h2,h3
|
|
|
|
; SVCB AliasMode (Priority=0)
|
|
_alias._foo IN SVCB 0 svc-target.example.com.
|
|
|
|
; Wildcard SVCB
|
|
*._tcp IN SVCB 1 svc-target.example.com. port=443
|
|
|
|
; DNS-AID private-use keys (65400-65408)
|
|
_dnsaid IN HTTPS 1 svc-target.example.com. alpn=h2 key65400="https://aid.example.com/cap" key65401="sha256:e3b0c44298fc" key65406="apphub-psc"
|
|
`
|