Repository: miekg/dns
Pull request #1359 (2022-04-11 13:08:15)

Add SVCB dohpath key
The parameter is being added in [its own IETF draft][1] and also being used in
the [IETF draft about Discovery of Designated Resolvers][2].

Additionally, the mappings of the numeric key values to strings are exported,
under names consistent with the already existing exported mappings, to make it
easier for the clients of the module to validate and print SVCB keys.

Testing was done by sending SVCB queries for the "_dns.resolver.arpa" domain to
OpenDNS's 146.112.41.2 server.

[1]: https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02
[2]: https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06.html

ainar-g: @miekg, thanks for the review, ptal!

Patch:
diff --git a/svcb.go b/svcb.go
index ff5e0108..68075e4b 100644
--- a/svcb.go
+++ b/svcb.go
@@ -22,6 +22,7 @@ const (
 	SVCB_IPV4HINT
 	SVCB_ECHCONFIG
 	SVCB_IPV6HINT
+	SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
 
 	svcb_RESERVED SVCBKey = 65535
 )
@@ -34,6 +35,7 @@ var svcbKeyToStringMap = map[SVCBKey]string{
 	SVCB_IPV4HINT:        "ipv4hint",
 	SVCB_ECHCONFIG:       "ech",
 	SVCB_IPV6HINT:        "ipv6hint",
+	SVCB_DOHPATH:         "dohpath",
 }
 
 var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
@@ -196,6 +198,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
 		return new(SVCBECHConfig)
 	case SVCB_IPV6HINT:
 		return new(SVCBIPv6Hint)
+	case SVCB_DOHPATH:
+		return new(SVCBDoHPath)
 	case svcb_RESERVED:
 		return nil
 	default:
@@ -669,6 +673,50 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
 	}
 }
 
+// SVCBDoHPath pair is used to indicate the URI template that the
+// clients may use to construct a DNS over HTTPS URI.
+//
+// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
+// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
+//
+// A basic example of using the dohpath option together with the alpn
+// option to indicate support for DNS over HTTPS on a certain path:
+//
+//	s := new(dns.SVCB)
+//	s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
+//	e := new(dns.SVCBAlpn)
+//	e.Alpn = []string{"h2", "h3"}
+//	p := new(dns.SVCBDoHPath)
+//	p.Template = "/dns-query{?dns}"
+//	s.Value = append(s.Value, e, p)
+//
+// The parsing currently doesn't validate that Template is a valid
+// RFC 6570 URI template.
+type SVCBDoHPath struct {
+	Template string
+}
+
+func (*SVCBDoHPath) Key() SVCBKey            { return SVCB_DOHPATH }
+func (s *SVCBDoHPath) String() string        { return s.Template }
+func (s *SVCBDoHPath) len() int              { return len(s.Template) }
+func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
+
+func (s *SVCBDoHPath) unpack(b []byte) error {
+	s.Template = string(b)
+	return nil
+}
+
+func (s *SVCBDoHPath) parse(b string) error {
+	s.Template = b
+	return nil
+}
+
+func (s *SVCBDoHPath) copy() SVCBKeyValue {
+	return &SVCBDoHPath{
+		Template: s.Template,
+	}
+}
+
 // SVCBLocal pair is intended for experimental/private use. The key is recommended
 // to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
 // Basic use pattern for creating a keyNNNNN option:


Test patch:
diff --git a/parse_test.go b/parse_test.go
index 444ac1e5..07d254bd 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -1652,6 +1652,8 @@ func TestParseSVCB(t *testing.T) {
 		`example.com.   SVCB   16 foo.example.org. alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1`: `example.com.	3600	IN	SVCB	16 foo.example.org. alpn="h2,h3-19" mandatory="ipv4hint,alpn" ipv4hint="192.0.2.1"`,
 		`example.com.   SVCB   16 foo.example.org. alpn="f\\\\oo\\,bar,h2"`: `example.com.	3600	IN	SVCB	16 foo.example.org. alpn="f\\\\oo\\,bar,h2"`,
 		`example.com.   SVCB   16 foo.example.org. alpn=f\\\092oo\092,bar,h2`: `example.com.	3600	IN	SVCB	16 foo.example.org. alpn="f\\\092oo\092,bar,h2"`,
+		// From draft-ietf-add-ddr-06
+		`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns-query{?dns}`: `_dns.example.net.	3600	IN	SVCB	1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
 	}
 	for s, o := range svcbs {
 		rr, err := NewRR(s)
diff --git a/svcb_test.go b/svcb_test.go
index 25494995..0f940e7e 100644
--- a/svcb_test.go
+++ b/svcb_test.go
@@ -18,6 +18,7 @@ func TestSVCB(t *testing.T) {
 		{`no-default-alpn`, ``},
 		{`ipv6hint`, `1::4:4:4:4,1::3:3:3:3`},
 		{`ech`, `YUdWc2JHOD0=`},
+		{`dohpath`, `/dns-query{?dns}`},
 		{`key65000`, `4\ 3`},
 		{`key65001`, `\"\ `},
 		{`key65002`, ``},