From 8054f31440f07fc4afca7a182c1bc869fd62d23a Mon Sep 17 00:00:00 2001
From: steffsas <steffen.sassalla@outlook.de>
Date: Sat, 18 May 2024 14:21:33 +0200
Subject: [PATCH] Add RFC 9540 oblivious services via service binding records

---
 parse_test.go | 11 +++++++++++
 svcb.go       | 50 ++++++++++++++++++++++++++++++++++++++++++++++----
 svcb_test.go  |  5 +++++
 3 files changed, 62 insertions(+), 4 deletions(-)

diff --git a/parse_test.go b/parse_test.go
index da94cc38e4..f0d68a44ec 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -1609,7 +1609,18 @@ func TestParseSVCB(t *testing.T) {
 		// 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}"`,
 		`_dns.example.net. SVCB 1 example.net. alpn=h2 dohpath=/dns\045query{\?dns}`: `_dns.example.net.	3600	IN	SVCB	1 example.net. alpn="h2" dohpath="/dns-query{?dns}"`,
+		// From RFC9461 Section 7 (https://datatracker.ietf.org/doc/html/rfc9461#section-7)
+		`_dns.simple.example. 7200 IN SVCB 1 simple.example. alpn=dot`:                                 `_dns.simple.example.	7200	IN	SVCB	1 simple.example. alpn="dot"`,
+		`_dns.doh.example. 7200 IN SVCB 1 doh.example. alpn=h2 dohpath=/dns-query{?dns}`:               `_dns.doh.example.	7200	IN	SVCB	1 doh.example. alpn="h2" dohpath="/dns-query{?dns}"`,
+		`_dns.resolver.example.  7200 IN SVCB 1 resolver.example. alpn=dot,doq,h2,h3 dohpath=/q{?dns}`: `_dns.resolver.example.	7200	IN	SVCB	1 resolver.example. alpn="dot,doq,h2,h3" dohpath="/q{?dns}"`,
+		`_dns.resolver.example.  7200 IN SVCB 2 resolver.example. alpn=dot port=8530`:                  `_dns.resolver.example.	7200	IN	SVCB	2 resolver.example. alpn="dot" port="8530"`,
+		// From RFC 9540 Section 4.2.1 (https://www.rfc-editor.org/rfc/rfc9540.html#name-the-ohttp-svcparamkey)
+		`_dns.resolver.arpa  7200  IN SVCB 1 doh.example.net alpn=h2 dohpath=/dns-query{?dns} ohttp`: `_dns.resolver.arpa.	7200	IN	SVCB	1 doh.example.net. alpn="h2" dohpath="/dns-query{?dns}" ohttp=""`,
+		// From RFC 9540 Section 4.1 (HTTPS RR) (https://www.rfc-editor.org/rfc/rfc9540.html#name-use-in-https-service-rrs)
+		`svc.example.com. 7200  IN HTTPS 1 . alpn=h2 ohttp`:         `svc.example.com.	7200	IN	HTTPS	1 . alpn="h2" ohttp=""`,
+		`svc.example.com. 7200  IN HTTPS 1 . mandatory=ohttp ohttp`: `svc.example.com.	7200	IN	HTTPS	1 . mandatory="ohttp" ohttp=""`,
 	}
+
 	for s, o := range svcbs {
 		rr, err := NewRR(s)
 		if err != nil {
diff --git a/svcb.go b/svcb.go
index c1a740b684..310c7d11f5 100644
--- a/svcb.go
+++ b/svcb.go
@@ -14,7 +14,7 @@ import (
 // SVCBKey is the type of the keys used in the SVCB RR.
 type SVCBKey uint16
 
-// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
+// Keys defined in rfc9460
 const (
 	SVCB_MANDATORY SVCBKey = iota
 	SVCB_ALPN
@@ -23,7 +23,8 @@ const (
 	SVCB_IPV4HINT
 	SVCB_ECHCONFIG
 	SVCB_IPV6HINT
-	SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
+	SVCB_DOHPATH // rfc9461 Section 5
+	SVCB_OHTTP   // rfc9540 Section 8
 
 	svcb_RESERVED SVCBKey = 65535
 )
@@ -37,6 +38,7 @@ var svcbKeyToStringMap = map[SVCBKey]string{
 	SVCB_ECHCONFIG:       "ech",
 	SVCB_IPV6HINT:        "ipv6hint",
 	SVCB_DOHPATH:         "dohpath",
+	SVCB_OHTTP:           "ohttp",
 }
 
 var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
@@ -201,6 +203,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
 		return new(SVCBIPv6Hint)
 	case SVCB_DOHPATH:
 		return new(SVCBDoHPath)
+	case SVCB_OHTTP:
+		return new(SVCBOhttp)
 	case svcb_RESERVED:
 		return nil
 	default:
@@ -771,8 +775,8 @@ 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).
+// See RFC 9461 (https://datatracker.ietf.org/doc/html/rfc9461)
+// and RFC 9462 (https://datatracker.ietf.org/doc/html/rfc9462).
 //
 // A basic example of using the dohpath option together with the alpn
 // option to indicate support for DNS over HTTPS on a certain path:
@@ -816,6 +820,44 @@ func (s *SVCBDoHPath) copy() SVCBKeyValue {
 	}
 }
 
+// The "ohttp" SvcParamKey is used to indicate that a service described in a SVCB RR
+// can be accessed as a target using an associated gateway.
+// Both the presentation and wire-format values for the "ohttp" parameter MUST be empty.
+//
+// See RFC 9460 (https://datatracker.ietf.org/doc/html/rfc9460/)
+// and RFC 9230 (https://datatracker.ietf.org/doc/html/rfc9230/)
+//
+// 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.SVCBOhttp)
+//	s.Value = append(s.Value, e, p)
+type SVCBOhttp struct{}
+
+func (*SVCBOhttp) Key() SVCBKey          { return SVCB_OHTTP }
+func (*SVCBOhttp) copy() SVCBKeyValue    { return &SVCBOhttp{} }
+func (*SVCBOhttp) pack() ([]byte, error) { return []byte{}, nil }
+func (*SVCBOhttp) String() string        { return "" }
+func (*SVCBOhttp) len() int              { return 0 }
+
+func (*SVCBOhttp) unpack(b []byte) error {
+	if len(b) != 0 {
+		return errors.New("dns: svcbotthp: svcbotthp must have no value")
+	}
+	return nil
+}
+
+func (*SVCBOhttp) parse(b string) error {
+	if b != "" {
+		return errors.New("dns: svcbotthp: svcbotthp must have no value")
+	}
+	return nil
+}
+
 // 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:
diff --git a/svcb_test.go b/svcb_test.go
index 63a40102c7..a96a344b6d 100644
--- a/svcb_test.go
+++ b/svcb_test.go
@@ -24,6 +24,7 @@ func TestSVCB(t *testing.T) {
 		{`key65002`, ``},
 		{`key65003`, `=\"\"`},
 		{`key65004`, `\254\ \ \030\000`},
+		{`ohttp`, ``},
 	}
 
 	for _, o := range svcbs {
@@ -86,6 +87,10 @@ func TestDecodeBadSVCB(t *testing.T) {
 			key:  SVCB_IPV6HINT,
 			data: []byte{0, 0, 0},
 		},
+		{
+			key:  SVCB_OHTTP,
+			data: []byte{0},
+		},
 	}
 	for _, o := range svcbs {
 		err := makeSVCBKeyValue(SVCBKey(o.key)).unpack(o.data)