From 8a162bbc18d8e16f018d1633e63297efa5db14ea Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 11 Apr 2022 15:57:54 +0300 Subject: [PATCH 1/3] Add SVCB dohpath key The parameter is being added in [its own IETF draft][1] and also being used in the [IETF draft about Descovery 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 --- parse_test.go | 2 ++ svcb.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++----- svcb_test.go | 1 + 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/parse_test.go b/parse_test.go index 444ac1e5a..07d254bd6 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.go b/svcb.go index ff5e01086..22aec63a6 100644 --- a/svcb.go +++ b/svcb.go @@ -13,7 +13,8 @@ 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 draft-ietf-dnsop-svcb-https-08 Section 14.3.2 +// and draft-ietf-add-svcb-dns-02 Section 9. const ( SVCB_MANDATORY SVCBKey = iota SVCB_ALPN @@ -22,11 +23,13 @@ const ( SVCB_IPV4HINT SVCB_ECHCONFIG SVCB_IPV6HINT + SVCB_DOHPATH svcb_RESERVED SVCBKey = 65535 ) -var svcbKeyToStringMap = map[SVCBKey]string{ +// SVCBKeyToString is a map of strings for each SVCB key. +var SVCBKeyToString = map[SVCBKey]string{ SVCB_MANDATORY: "mandatory", SVCB_ALPN: "alpn", SVCB_NO_DEFAULT_ALPN: "no-default-alpn", @@ -34,11 +37,14 @@ var svcbKeyToStringMap = map[SVCBKey]string{ SVCB_IPV4HINT: "ipv4hint", SVCB_ECHCONFIG: "ech", SVCB_IPV6HINT: "ipv6hint", + SVCB_DOHPATH: "dohpath", } -var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap) +// StringToSVCBKey is the reverse of SVCBKeyToString, needed for string +// parsing. +var StringToSVCBKey = reverseSVCBKey(SVCBKeyToString) -func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey { +func reverseSVCBKey(m map[SVCBKey]string) map[string]SVCBKey { n := make(map[string]SVCBKey, len(m)) for u, s := range m { n[s] = u @@ -50,7 +56,7 @@ func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey { // Returns an empty string for reserved keys. // Accepts unassigned keys as well as experimental/private keys. func (key SVCBKey) String() string { - if x := svcbKeyToStringMap[key]; x != "" { + if x := SVCBKeyToString[key]; x != "" { return x } if key == svcb_RESERVED { @@ -67,12 +73,12 @@ func svcbStringToKey(s string) SVCBKey { a, err := strconv.ParseUint(s[3:], 10, 16) // no leading zeros // key shouldn't be registered - if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" { + if err != nil || a == 65535 || s[3] == '0' || SVCBKeyToString[SVCBKey(a)] != "" { return svcb_RESERVED } return SVCBKey(a) } - if key, ok := svcbStringToKeyMap[s]; ok { + if key, ok := StringToSVCBKey[s]; ok { return key } return svcb_RESERVED @@ -196,6 +202,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 +677,62 @@ 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). +// +// Basic use pattern for using the dohpath option together with an alpn +// option: +// +// 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 only validates the length of Template. +// It 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) len() int { return len(s.Template) } + +func (s *SVCBDoHPath) pack() ([]byte, error) { + if len(s.Template) > 255 { + return nil, errors.New("dns: svcbdohpath: template too long") + } + return []byte(s.Template), nil +} + +func (s *SVCBDoHPath) unpack(b []byte) error { + s.Template = string(b) + return nil +} + +func (s *SVCBDoHPath) String() string { + return s.Template +} + +func (s *SVCBDoHPath) parse(b string) error { + if len(b) > 255 { + return errors.New("dns: svcbdohpath: template too long") + } + 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: diff --git a/svcb_test.go b/svcb_test.go index 254949954..0f940e7e5 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`, ``}, From 826b058a36d161c6923893f8510c57ec839dcab6 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 12 Apr 2022 17:22:55 +0300 Subject: [PATCH 2/3] Fix template length, docs; reverse some changes --- svcb.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/svcb.go b/svcb.go index 22aec63a6..7b6b68ce4 100644 --- a/svcb.go +++ b/svcb.go @@ -13,8 +13,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 -// and draft-ietf-add-svcb-dns-02 Section 9. +// Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2. const ( SVCB_MANDATORY SVCBKey = iota SVCB_ALPN @@ -23,13 +22,12 @@ const ( SVCB_IPV4HINT SVCB_ECHCONFIG SVCB_IPV6HINT - SVCB_DOHPATH + SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9 svcb_RESERVED SVCBKey = 65535 ) -// SVCBKeyToString is a map of strings for each SVCB key. -var SVCBKeyToString = map[SVCBKey]string{ +var svcbKeyToStringMap = map[SVCBKey]string{ SVCB_MANDATORY: "mandatory", SVCB_ALPN: "alpn", SVCB_NO_DEFAULT_ALPN: "no-default-alpn", @@ -40,11 +38,9 @@ var SVCBKeyToString = map[SVCBKey]string{ SVCB_DOHPATH: "dohpath", } -// StringToSVCBKey is the reverse of SVCBKeyToString, needed for string -// parsing. -var StringToSVCBKey = reverseSVCBKey(SVCBKeyToString) +var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap) -func reverseSVCBKey(m map[SVCBKey]string) map[string]SVCBKey { +func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey { n := make(map[string]SVCBKey, len(m)) for u, s := range m { n[s] = u @@ -56,7 +52,7 @@ func reverseSVCBKey(m map[SVCBKey]string) map[string]SVCBKey { // Returns an empty string for reserved keys. // Accepts unassigned keys as well as experimental/private keys. func (key SVCBKey) String() string { - if x := SVCBKeyToString[key]; x != "" { + if x := svcbKeyToStringMap[key]; x != "" { return x } if key == svcb_RESERVED { @@ -73,12 +69,12 @@ func svcbStringToKey(s string) SVCBKey { a, err := strconv.ParseUint(s[3:], 10, 16) // no leading zeros // key shouldn't be registered - if err != nil || a == 65535 || s[3] == '0' || SVCBKeyToString[SVCBKey(a)] != "" { + if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" { return svcb_RESERVED } return SVCBKey(a) } - if key, ok := StringToSVCBKey[s]; ok { + if key, ok := svcbStringToKeyMap[s]; ok { return key } return svcb_RESERVED @@ -704,7 +700,7 @@ func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH } func (s *SVCBDoHPath) len() int { return len(s.Template) } func (s *SVCBDoHPath) pack() ([]byte, error) { - if len(s.Template) > 255 { + if len(s.Template) > 65535 { return nil, errors.New("dns: svcbdohpath: template too long") } return []byte(s.Template), nil @@ -720,7 +716,7 @@ func (s *SVCBDoHPath) String() string { } func (s *SVCBDoHPath) parse(b string) error { - if len(b) > 255 { + if len(b) > 65535 { return errors.New("dns: svcbdohpath: template too long") } s.Template = b From 15bc19049c90199a4d295b465a9c8044e2010e28 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 12 Apr 2022 18:36:23 +0300 Subject: [PATCH 3/3] Remove incorrect validations; improve docs --- svcb.go | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/svcb.go b/svcb.go index 7b6b68ce4..68075e4b5 100644 --- a/svcb.go +++ b/svcb.go @@ -679,8 +679,8 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue { // 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). // -// Basic use pattern for using the dohpath option together with an alpn -// option: +// 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} @@ -690,35 +690,23 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue { // p.Template = "/dns-query{?dns}" // s.Value = append(s.Value, e, p) // -// The parsing currently only validates the length of Template. -// It doesn't validate that Template is a valid RFC 6570 URI template. +// 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) len() int { return len(s.Template) } - -func (s *SVCBDoHPath) pack() ([]byte, error) { - if len(s.Template) > 65535 { - return nil, errors.New("dns: svcbdohpath: template too long") - } - return []byte(s.Template), nil -} +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) String() string { - return s.Template -} - func (s *SVCBDoHPath) parse(b string) error { - if len(b) > 65535 { - return errors.New("dns: svcbdohpath: template too long") - } s.Template = b return nil }