svcb.go raw
1 package dns
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "net"
9 "sort"
10 "strconv"
11 "strings"
12 )
13
14 // SVCBKey is the type of the keys used in the SVCB RR.
15 type SVCBKey uint16
16
17 // Keys defined in rfc9460
18 const (
19 SVCB_MANDATORY SVCBKey = iota
20 SVCB_ALPN
21 SVCB_NO_DEFAULT_ALPN
22 SVCB_PORT
23 SVCB_IPV4HINT
24 SVCB_ECHCONFIG
25 SVCB_IPV6HINT
26 SVCB_DOHPATH // rfc9461 Section 5
27 SVCB_OHTTP // rfc9540 Section 8
28
29 svcb_RESERVED SVCBKey = 65535
30 )
31
32 var svcbKeyToStringMap = map[SVCBKey]string{
33 SVCB_MANDATORY: "mandatory",
34 SVCB_ALPN: "alpn",
35 SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
36 SVCB_PORT: "port",
37 SVCB_IPV4HINT: "ipv4hint",
38 SVCB_ECHCONFIG: "ech",
39 SVCB_IPV6HINT: "ipv6hint",
40 SVCB_DOHPATH: "dohpath",
41 SVCB_OHTTP: "ohttp",
42 }
43
44 var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
45
46 func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey {
47 n := make(map[string]SVCBKey, len(m))
48 for u, s := range m {
49 n[s] = u
50 }
51 return n
52 }
53
54 // String takes the numerical code of an SVCB key and returns its name.
55 // Returns an empty string for reserved keys.
56 // Accepts unassigned keys as well as experimental/private keys.
57 func (key SVCBKey) String() string {
58 if x := svcbKeyToStringMap[key]; x != "" {
59 return x
60 }
61 if key == svcb_RESERVED {
62 return ""
63 }
64 return "key" + strconv.FormatUint(uint64(key), 10)
65 }
66
67 // svcbStringToKey returns the numerical code of an SVCB key.
68 // Returns svcb_RESERVED for reserved/invalid keys.
69 // Accepts unassigned keys as well as experimental/private keys.
70 func svcbStringToKey(s string) SVCBKey {
71 if strings.HasPrefix(s, "key") {
72 a, err := strconv.ParseUint(s[3:], 10, 16)
73 // no leading zeros
74 // key shouldn't be registered
75 if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" {
76 return svcb_RESERVED
77 }
78 return SVCBKey(a)
79 }
80 if key, ok := svcbStringToKeyMap[s]; ok {
81 return key
82 }
83 return svcb_RESERVED
84 }
85
86 func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
87 l, _ := c.Next()
88 i, e := strconv.ParseUint(l.token, 10, 16)
89 if e != nil || l.err {
90 return &ParseError{file: l.token, err: "bad SVCB priority", lex: l}
91 }
92 rr.Priority = uint16(i)
93
94 c.Next() // zBlank
95 l, _ = c.Next() // zString
96 rr.Target = l.token
97
98 name, nameOk := toAbsoluteName(l.token, o)
99 if l.err || !nameOk {
100 return &ParseError{file: l.token, err: "bad SVCB Target", lex: l}
101 }
102 rr.Target = name
103
104 // Values (if any)
105 l, _ = c.Next()
106 var xs []SVCBKeyValue
107 // Helps require whitespace between pairs.
108 // Prevents key1000="a"key1001=...
109 canHaveNextKey := true
110 for l.value != zNewline && l.value != zEOF {
111 switch l.value {
112 case zString:
113 if !canHaveNextKey {
114 // The key we can now read was probably meant to be
115 // a part of the last value.
116 return &ParseError{file: l.token, err: "bad SVCB value quotation", lex: l}
117 }
118
119 // In key=value pairs, value does not have to be quoted unless value
120 // contains whitespace. And keys don't need to have values.
121 // Similarly, keys with an equality signs after them don't need values.
122 // l.token includes at least up to the first equality sign.
123 idx := strings.IndexByte(l.token, '=')
124 var key, value string
125 if idx < 0 {
126 // Key with no value and no equality sign
127 key = l.token
128 } else if idx == 0 {
129 return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
130 } else {
131 key, value = l.token[:idx], l.token[idx+1:]
132
133 if value == "" {
134 // We have a key and an equality sign. Maybe we have nothing
135 // after "=" or we have a double quote.
136 l, _ = c.Next()
137 if l.value == zQuote {
138 // Only needed when value ends with double quotes.
139 // Any value starting with zQuote ends with it.
140 canHaveNextKey = false
141
142 l, _ = c.Next()
143 switch l.value {
144 case zString:
145 // We have a value in double quotes.
146 value = l.token
147 l, _ = c.Next()
148 if l.value != zQuote {
149 return &ParseError{file: l.token, err: "SVCB unterminated value", lex: l}
150 }
151 case zQuote:
152 // There's nothing in double quotes.
153 default:
154 return &ParseError{file: l.token, err: "bad SVCB value", lex: l}
155 }
156 }
157 }
158 }
159 kv := makeSVCBKeyValue(svcbStringToKey(key))
160 if kv == nil {
161 return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
162 }
163 if err := kv.parse(value); err != nil {
164 return &ParseError{file: l.token, wrappedErr: err, lex: l}
165 }
166 xs = append(xs, kv)
167 case zQuote:
168 return &ParseError{file: l.token, err: "SVCB key can't contain double quotes", lex: l}
169 case zBlank:
170 canHaveNextKey = true
171 default:
172 return &ParseError{file: l.token, err: "bad SVCB values", lex: l}
173 }
174 l, _ = c.Next()
175 }
176
177 // "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
178 // ignore any SvcParams that are present."
179 // However, we don't check rr.Priority == 0 && len(xs) > 0 here
180 // It is the responsibility of the user of the library to check this.
181 // This is to encourage the fixing of the source of this error.
182
183 rr.Value = xs
184 return nil
185 }
186
187 // makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.
188 func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
189 switch key {
190 case SVCB_MANDATORY:
191 return new(SVCBMandatory)
192 case SVCB_ALPN:
193 return new(SVCBAlpn)
194 case SVCB_NO_DEFAULT_ALPN:
195 return new(SVCBNoDefaultAlpn)
196 case SVCB_PORT:
197 return new(SVCBPort)
198 case SVCB_IPV4HINT:
199 return new(SVCBIPv4Hint)
200 case SVCB_ECHCONFIG:
201 return new(SVCBECHConfig)
202 case SVCB_IPV6HINT:
203 return new(SVCBIPv6Hint)
204 case SVCB_DOHPATH:
205 return new(SVCBDoHPath)
206 case SVCB_OHTTP:
207 return new(SVCBOhttp)
208 case svcb_RESERVED:
209 return nil
210 default:
211 e := new(SVCBLocal)
212 e.KeyCode = key
213 return e
214 }
215 }
216
217 // SVCB RR. See RFC 9460.
218 type SVCB struct {
219 Hdr RR_Header
220 Priority uint16 // If zero, Value must be empty or discarded by the user of this library
221 Target string `dns:"domain-name"`
222 Value []SVCBKeyValue `dns:"pairs"`
223 }
224
225 // HTTPS RR. See RFC 9460. Everything valid for SVCB applies to HTTPS as well.
226 // Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
227 type HTTPS struct {
228 SVCB
229 }
230
231 func (rr *HTTPS) String() string {
232 return rr.SVCB.String()
233 }
234
235 func (rr *HTTPS) parse(c *zlexer, o string) *ParseError {
236 return rr.SVCB.parse(c, o)
237 }
238
239 // SVCBKeyValue defines a key=value pair for the SVCB RR type.
240 // An SVCB RR can have multiple SVCBKeyValues appended to it.
241 type SVCBKeyValue interface {
242 Key() SVCBKey // Key returns the numerical key code.
243 pack() ([]byte, error) // pack returns the encoded value.
244 unpack([]byte) error // unpack sets the value.
245 String() string // String returns the string representation of the value.
246 parse(string) error // parse sets the value to the given string representation of the value.
247 copy() SVCBKeyValue // copy returns a deep-copy of the pair.
248 len() int // len returns the length of value in the wire format.
249 }
250
251 // SVCBMandatory pair adds to required keys that must be interpreted for the RR
252 // to be functional. If ignored, the whole RRSet must be ignored.
253 // "port" and "no-default-alpn" are mandatory by default if present,
254 // so they shouldn't be included here.
255 //
256 // It is incumbent upon the user of this library to reject the RRSet if
257 // or avoid constructing such an RRSet that:
258 // - "mandatory" is included as one of the keys of mandatory
259 // - no key is listed multiple times in mandatory
260 // - all keys listed in mandatory are present
261 // - escape sequences are not used in mandatory
262 // - mandatory, when present, lists at least one key
263 //
264 // Basic use pattern for creating a mandatory option:
265 //
266 // s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
267 // e := new(dns.SVCBMandatory)
268 // e.Code = []uint16{dns.SVCB_ALPN}
269 // s.Value = append(s.Value, e)
270 // t := new(dns.SVCBAlpn)
271 // t.Alpn = []string{"xmpp-client"}
272 // s.Value = append(s.Value, t)
273 type SVCBMandatory struct {
274 Code []SVCBKey
275 }
276
277 func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
278
279 func (s *SVCBMandatory) String() string {
280 str := make([]string, len(s.Code))
281 for i, e := range s.Code {
282 str[i] = e.String()
283 }
284 return strings.Join(str, ",")
285 }
286
287 func (s *SVCBMandatory) pack() ([]byte, error) {
288 codes := cloneSlice(s.Code)
289 sort.Slice(codes, func(i, j int) bool {
290 return codes[i] < codes[j]
291 })
292 b := make([]byte, 2*len(codes))
293 for i, e := range codes {
294 binary.BigEndian.PutUint16(b[2*i:], uint16(e))
295 }
296 return b, nil
297 }
298
299 func (s *SVCBMandatory) unpack(b []byte) error {
300 if len(b)%2 != 0 {
301 return errors.New("bad svcbmandatory: value length is not a multiple of 2")
302 }
303 codes := make([]SVCBKey, 0, len(b)/2)
304 for i := 0; i < len(b); i += 2 {
305 // We assume strictly increasing order.
306 codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:])))
307 }
308 s.Code = codes
309 return nil
310 }
311
312 func (s *SVCBMandatory) parse(b string) error {
313 codes := make([]SVCBKey, 0, strings.Count(b, ",")+1)
314 for len(b) > 0 {
315 var key string
316 key, b, _ = strings.Cut(b, ",")
317 codes = append(codes, svcbStringToKey(key))
318 }
319 s.Code = codes
320 return nil
321 }
322
323 func (s *SVCBMandatory) len() int {
324 return 2 * len(s.Code)
325 }
326
327 func (s *SVCBMandatory) copy() SVCBKeyValue {
328 return &SVCBMandatory{cloneSlice(s.Code)}
329 }
330
331 // SVCBAlpn pair is used to list supported connection protocols.
332 // The user of this library must ensure that at least one protocol is listed when alpn is present.
333 // Protocol IDs can be found at:
334 // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
335 // Basic use pattern for creating an alpn option:
336 //
337 // h := new(dns.HTTPS)
338 // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
339 // e := new(dns.SVCBAlpn)
340 // e.Alpn = []string{"h2", "http/1.1"}
341 // h.Value = append(h.Value, e)
342 type SVCBAlpn struct {
343 Alpn []string
344 }
345
346 func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
347
348 func (s *SVCBAlpn) String() string {
349 // An ALPN value is a comma-separated list of values, each of which can be
350 // an arbitrary binary value. In order to allow parsing, the comma and
351 // backslash characters are themselves escaped.
352 //
353 // However, this escaping is done in addition to the normal escaping which
354 // happens in zone files, meaning that these values must be
355 // double-escaped. This looks terrible, so if you see a never-ending
356 // sequence of backslash in a zone file this may be why.
357 //
358 // https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
359 var str strings.Builder
360 for i, alpn := range s.Alpn {
361 // 4*len(alpn) is the worst case where we escape every character in the alpn as \123, plus 1 byte for the ',' separating the alpn from others
362 str.Grow(4*len(alpn) + 1)
363 if i > 0 {
364 str.WriteByte(',')
365 }
366 for j := 0; j < len(alpn); j++ {
367 e := alpn[j]
368 if ' ' > e || e > '~' {
369 str.WriteString(escapeByte(e))
370 continue
371 }
372 switch e {
373 // We escape a few characters which may confuse humans or parsers.
374 case '"', ';', ' ':
375 str.WriteByte('\\')
376 str.WriteByte(e)
377 // The comma and backslash characters themselves must be
378 // doubly-escaped. We use `\\` for the first backslash and
379 // the escaped numeric value for the other value. We especially
380 // don't want a comma in the output.
381 case ',':
382 str.WriteString(`\\\044`)
383 case '\\':
384 str.WriteString(`\\\092`)
385 default:
386 str.WriteByte(e)
387 }
388 }
389 }
390 return str.String()
391 }
392
393 func (s *SVCBAlpn) pack() ([]byte, error) {
394 // Liberally estimate the size of an alpn as 10 octets
395 b := make([]byte, 0, 10*len(s.Alpn))
396 for _, e := range s.Alpn {
397 if e == "" {
398 return nil, errors.New("bad svcbalpn: empty alpn-id")
399 }
400 if len(e) > 255 {
401 return nil, errors.New("bad svcbalpn: alpn-id too long")
402 }
403 b = append(b, byte(len(e)))
404 b = append(b, e...)
405 }
406 return b, nil
407 }
408
409 func (s *SVCBAlpn) unpack(b []byte) error {
410 // Estimate the size of the smallest alpn as 4 bytes
411 alpn := make([]string, 0, len(b)/4)
412 for i := 0; i < len(b); {
413 length := int(b[i])
414 i++
415 if i+length > len(b) {
416 return errors.New("bad svcbalpn: alpn array overflowing")
417 }
418 alpn = append(alpn, string(b[i:i+length]))
419 i += length
420 }
421 s.Alpn = alpn
422 return nil
423 }
424
425 func (s *SVCBAlpn) parse(b string) error {
426 if len(b) == 0 {
427 s.Alpn = []string{}
428 return nil
429 }
430
431 alpn := []string{}
432 a := []byte{}
433 for p := 0; p < len(b); {
434 c, q := nextByte(b, p)
435 if q == 0 {
436 return errors.New("bad svcbalpn: unterminated escape")
437 }
438 p += q
439 // If we find a comma, we have finished reading an alpn.
440 if c == ',' {
441 if len(a) == 0 {
442 return errors.New("bad svcbalpn: empty protocol identifier")
443 }
444 alpn = append(alpn, string(a))
445 a = []byte{}
446 continue
447 }
448 // If it's a backslash, we need to handle a comma-separated list.
449 if c == '\\' {
450 dc, dq := nextByte(b, p)
451 if dq == 0 {
452 return errors.New("bad svcbalpn: unterminated escape decoding comma-separated list")
453 }
454 if dc != '\\' && dc != ',' {
455 return errors.New("bad svcbalpn: bad escaped character decoding comma-separated list")
456 }
457 p += dq
458 c = dc
459 }
460 a = append(a, c)
461 }
462 // Add the final alpn.
463 if len(a) == 0 {
464 return errors.New("bad svcbalpn: last protocol identifier empty")
465 }
466 s.Alpn = append(alpn, string(a))
467 return nil
468 }
469
470 func (s *SVCBAlpn) len() int {
471 var l int
472 for _, e := range s.Alpn {
473 l += 1 + len(e)
474 }
475 return l
476 }
477
478 func (s *SVCBAlpn) copy() SVCBKeyValue {
479 return &SVCBAlpn{cloneSlice(s.Alpn)}
480 }
481
482 // SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
483 // Should be used in conjunction with alpn.
484 // Basic use pattern for creating a no-default-alpn option:
485 //
486 // s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
487 // t := new(dns.SVCBAlpn)
488 // t.Alpn = []string{"xmpp-client"}
489 // s.Value = append(s.Value, t)
490 // e := new(dns.SVCBNoDefaultAlpn)
491 // s.Value = append(s.Value, e)
492 type SVCBNoDefaultAlpn struct{}
493
494 func (*SVCBNoDefaultAlpn) Key() SVCBKey { return SVCB_NO_DEFAULT_ALPN }
495 func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue { return &SVCBNoDefaultAlpn{} }
496 func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil }
497 func (*SVCBNoDefaultAlpn) String() string { return "" }
498 func (*SVCBNoDefaultAlpn) len() int { return 0 }
499
500 func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
501 if len(b) != 0 {
502 return errors.New("bad svcbnodefaultalpn: no-default-alpn must have no value")
503 }
504 return nil
505 }
506
507 func (*SVCBNoDefaultAlpn) parse(b string) error {
508 if b != "" {
509 return errors.New("bad svcbnodefaultalpn: no-default-alpn must have no value")
510 }
511 return nil
512 }
513
514 // SVCBPort pair defines the port for connection.
515 // Basic use pattern for creating a port option:
516 //
517 // s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
518 // e := new(dns.SVCBPort)
519 // e.Port = 80
520 // s.Value = append(s.Value, e)
521 type SVCBPort struct {
522 Port uint16
523 }
524
525 func (*SVCBPort) Key() SVCBKey { return SVCB_PORT }
526 func (*SVCBPort) len() int { return 2 }
527 func (s *SVCBPort) String() string { return strconv.FormatUint(uint64(s.Port), 10) }
528 func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} }
529
530 func (s *SVCBPort) unpack(b []byte) error {
531 if len(b) != 2 {
532 return errors.New("bad svcbport: port length is not exactly 2 octets")
533 }
534 s.Port = binary.BigEndian.Uint16(b)
535 return nil
536 }
537
538 func (s *SVCBPort) pack() ([]byte, error) {
539 b := make([]byte, 2)
540 binary.BigEndian.PutUint16(b, s.Port)
541 return b, nil
542 }
543
544 func (s *SVCBPort) parse(b string) error {
545 port, err := strconv.ParseUint(b, 10, 16)
546 if err != nil {
547 return errors.New("bad svcbport: port out of range")
548 }
549 s.Port = uint16(port)
550 return nil
551 }
552
553 // SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections
554 // if A and AAAA record responses for SVCB's Target domain haven't been received.
555 // In that case, optionally, A and AAAA requests can be made, after which the connection
556 // to the hinted IP address may be terminated and a new connection may be opened.
557 // Basic use pattern for creating an ipv4hint option:
558 //
559 // h := new(dns.HTTPS)
560 // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
561 // e := new(dns.SVCBIPv4Hint)
562 // e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
563 //
564 // Or
565 //
566 // e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
567 // h.Value = append(h.Value, e)
568 type SVCBIPv4Hint struct {
569 Hint []net.IP
570 }
571
572 func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT }
573 func (s *SVCBIPv4Hint) len() int { return 4 * len(s.Hint) }
574
575 func (s *SVCBIPv4Hint) pack() ([]byte, error) {
576 b := make([]byte, 0, 4*len(s.Hint))
577 for _, e := range s.Hint {
578 x := e.To4()
579 if x == nil {
580 return nil, errors.New("bad svcbipv4hint: expected ipv4, hint is ipv6")
581 }
582 b = append(b, x...)
583 }
584 return b, nil
585 }
586
587 func (s *SVCBIPv4Hint) unpack(b []byte) error {
588 if len(b) == 0 || len(b)%4 != 0 {
589 return errors.New("bad svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
590 }
591 b = cloneSlice(b)
592 x := make([]net.IP, 0, len(b)/4)
593 for i := 0; i < len(b); i += 4 {
594 x = append(x, net.IP(b[i:i+4]))
595 }
596 s.Hint = x
597 return nil
598 }
599
600 func (s *SVCBIPv4Hint) String() string {
601 str := make([]string, len(s.Hint))
602 for i, e := range s.Hint {
603 x := e.To4()
604 if x == nil {
605 return "<nil>"
606 }
607 str[i] = x.String()
608 }
609 return strings.Join(str, ",")
610 }
611
612 func (s *SVCBIPv4Hint) parse(b string) error {
613 if b == "" {
614 return errors.New("bad svcbipv4hint: empty hint")
615 }
616 if strings.Contains(b, ":") {
617 return errors.New("bad svcbipv4hint: expected ipv4, got ipv6")
618 }
619
620 hint := make([]net.IP, 0, strings.Count(b, ",")+1)
621 for len(b) > 0 {
622 var e string
623 e, b, _ = strings.Cut(b, ",")
624 ip := net.ParseIP(e).To4()
625 if ip == nil {
626 return errors.New("bad svcbipv4hint: bad ip")
627 }
628 hint = append(hint, ip)
629 }
630 s.Hint = hint
631 return nil
632 }
633
634 func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
635 hint := make([]net.IP, len(s.Hint))
636 for i, ip := range s.Hint {
637 hint[i] = cloneSlice(ip)
638 }
639 return &SVCBIPv4Hint{Hint: hint}
640 }
641
642 // SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
643 // Basic use pattern for creating an ech option:
644 //
645 // h := new(dns.HTTPS)
646 // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
647 // e := new(dns.SVCBECHConfig)
648 // e.ECH = []byte{0xfe, 0x08, ...}
649 // h.Value = append(h.Value, e)
650 type SVCBECHConfig struct {
651 ECH []byte // Specifically ECHConfigList including the redundant length prefix
652 }
653
654 func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
655 func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
656 func (s *SVCBECHConfig) len() int { return len(s.ECH) }
657
658 func (s *SVCBECHConfig) pack() ([]byte, error) {
659 return cloneSlice(s.ECH), nil
660 }
661
662 func (s *SVCBECHConfig) copy() SVCBKeyValue {
663 return &SVCBECHConfig{cloneSlice(s.ECH)}
664 }
665
666 func (s *SVCBECHConfig) unpack(b []byte) error {
667 s.ECH = cloneSlice(b)
668 return nil
669 }
670
671 func (s *SVCBECHConfig) parse(b string) error {
672 x, err := fromBase64([]byte(b))
673 if err != nil {
674 return errors.New("bad svcbech: bad base64 ech")
675 }
676 s.ECH = x
677 return nil
678 }
679
680 // SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections
681 // if A and AAAA record responses for SVCB's Target domain haven't been received.
682 // In that case, optionally, A and AAAA requests can be made, after which the
683 // connection to the hinted IP address may be terminated and a new connection may be opened.
684 // Basic use pattern for creating an ipv6hint option:
685 //
686 // h := new(dns.HTTPS)
687 // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
688 // e := new(dns.SVCBIPv6Hint)
689 // e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
690 // h.Value = append(h.Value, e)
691 type SVCBIPv6Hint struct {
692 Hint []net.IP
693 }
694
695 func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT }
696 func (s *SVCBIPv6Hint) len() int { return 16 * len(s.Hint) }
697
698 func (s *SVCBIPv6Hint) pack() ([]byte, error) {
699 b := make([]byte, 0, 16*len(s.Hint))
700 for _, e := range s.Hint {
701 if len(e) != net.IPv6len || e.To4() != nil {
702 return nil, errors.New("bad svcbipv6hint: expected ipv6, hint is ipv4")
703 }
704 b = append(b, e...)
705 }
706 return b, nil
707 }
708
709 func (s *SVCBIPv6Hint) unpack(b []byte) error {
710 if len(b) == 0 || len(b)%16 != 0 {
711 return errors.New("bas svcbipv6hint: ipv6 address byte array length not a multiple of 16")
712 }
713 b = cloneSlice(b)
714 x := make([]net.IP, 0, len(b)/16)
715 for i := 0; i < len(b); i += 16 {
716 ip := net.IP(b[i : i+16])
717 if ip.To4() != nil {
718 return errors.New("bad svcbipv6hint: expected ipv6, got ipv4")
719 }
720 x = append(x, ip)
721 }
722 s.Hint = x
723 return nil
724 }
725
726 func (s *SVCBIPv6Hint) String() string {
727 str := make([]string, len(s.Hint))
728 for i, e := range s.Hint {
729 if x := e.To4(); x != nil {
730 return "<nil>"
731 }
732 str[i] = e.String()
733 }
734 return strings.Join(str, ",")
735 }
736
737 func (s *SVCBIPv6Hint) parse(b string) error {
738 if b == "" {
739 return errors.New("bad svcbipv6hint: empty hint")
740 }
741
742 hint := make([]net.IP, 0, strings.Count(b, ",")+1)
743 for len(b) > 0 {
744 var e string
745 e, b, _ = strings.Cut(b, ",")
746 ip := net.ParseIP(e)
747 if ip == nil {
748 return errors.New("bad svcbipv6hint: bad ip")
749 }
750 if ip.To4() != nil {
751 return errors.New("bad svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
752 }
753 hint = append(hint, ip)
754 }
755 s.Hint = hint
756 return nil
757 }
758
759 func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
760 hint := make([]net.IP, len(s.Hint))
761 for i, ip := range s.Hint {
762 hint[i] = cloneSlice(ip)
763 }
764 return &SVCBIPv6Hint{Hint: hint}
765 }
766
767 // SVCBDoHPath pair is used to indicate the URI template that the
768 // clients may use to construct a DNS over HTTPS URI.
769 //
770 // See RFC 9461 (https://datatracker.ietf.org/doc/html/rfc9461)
771 // and RFC 9462 (https://datatracker.ietf.org/doc/html/rfc9462).
772 //
773 // A basic example of using the dohpath option together with the alpn
774 // option to indicate support for DNS over HTTPS on a certain path:
775 //
776 // s := new(dns.SVCB)
777 // s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
778 // e := new(dns.SVCBAlpn)
779 // e.Alpn = []string{"h2", "h3"}
780 // p := new(dns.SVCBDoHPath)
781 // p.Template = "/dns-query{?dns}"
782 // s.Value = append(s.Value, e, p)
783 //
784 // The parsing currently doesn't validate that Template is a valid
785 // RFC 6570 URI template.
786 type SVCBDoHPath struct {
787 Template string
788 }
789
790 func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
791 func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
792 func (s *SVCBDoHPath) len() int { return len(s.Template) }
793 func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
794
795 func (s *SVCBDoHPath) unpack(b []byte) error {
796 s.Template = string(b)
797 return nil
798 }
799
800 func (s *SVCBDoHPath) parse(b string) error {
801 template, err := svcbParseParam(b)
802 if err != nil {
803 return fmt.Errorf("bad svcbdohpath: %w", err)
804 }
805 s.Template = string(template)
806 return nil
807 }
808
809 func (s *SVCBDoHPath) copy() SVCBKeyValue {
810 return &SVCBDoHPath{
811 Template: s.Template,
812 }
813 }
814
815 // The "ohttp" SvcParamKey is used to indicate that a service described in a SVCB RR
816 // can be accessed as a target using an associated gateway.
817 // Both the presentation and wire-format values for the "ohttp" parameter MUST be empty.
818 //
819 // See RFC 9460 (https://datatracker.ietf.org/doc/html/rfc9460/)
820 // and RFC 9230 (https://datatracker.ietf.org/doc/html/rfc9230/)
821 //
822 // A basic example of using the dohpath option together with the alpn
823 // option to indicate support for DNS over HTTPS on a certain path:
824 //
825 // s := new(dns.SVCB)
826 // s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
827 // e := new(dns.SVCBAlpn)
828 // e.Alpn = []string{"h2", "h3"}
829 // p := new(dns.SVCBOhttp)
830 // s.Value = append(s.Value, e, p)
831 type SVCBOhttp struct{}
832
833 func (*SVCBOhttp) Key() SVCBKey { return SVCB_OHTTP }
834 func (*SVCBOhttp) copy() SVCBKeyValue { return &SVCBOhttp{} }
835 func (*SVCBOhttp) pack() ([]byte, error) { return []byte{}, nil }
836 func (*SVCBOhttp) String() string { return "" }
837 func (*SVCBOhttp) len() int { return 0 }
838
839 func (*SVCBOhttp) unpack(b []byte) error {
840 if len(b) != 0 {
841 return errors.New("bad svcbotthp: svcbotthp must have no value")
842 }
843 return nil
844 }
845
846 func (*SVCBOhttp) parse(b string) error {
847 if b != "" {
848 return errors.New("bad svcbotthp: svcbotthp must have no value")
849 }
850 return nil
851 }
852
853 // SVCBLocal pair is intended for experimental/private use. The key is recommended
854 // to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
855 // Basic use pattern for creating a keyNNNNN option:
856 //
857 // h := new(dns.HTTPS)
858 // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
859 // e := new(dns.SVCBLocal)
860 // e.KeyCode = 65400
861 // e.Data = []byte("abc")
862 // h.Value = append(h.Value, e)
863 type SVCBLocal struct {
864 KeyCode SVCBKey // Never 65535 or any assigned keys.
865 Data []byte // All byte sequences are allowed.
866 }
867
868 func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
869 func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
870 func (s *SVCBLocal) pack() ([]byte, error) { return cloneSlice(s.Data), nil }
871 func (s *SVCBLocal) len() int { return len(s.Data) }
872
873 func (s *SVCBLocal) unpack(b []byte) error {
874 s.Data = cloneSlice(b)
875 return nil
876 }
877
878 func (s *SVCBLocal) parse(b string) error {
879 data, err := svcbParseParam(b)
880 if err != nil {
881 return fmt.Errorf("bad svcblocal: svcb private/experimental key %w", err)
882 }
883 s.Data = data
884 return nil
885 }
886
887 func (s *SVCBLocal) copy() SVCBKeyValue {
888 return &SVCBLocal{s.KeyCode, cloneSlice(s.Data)}
889 }
890
891 func (rr *SVCB) String() string {
892 s := rr.Hdr.String() +
893 strconv.Itoa(int(rr.Priority)) + " " +
894 sprintName(rr.Target)
895 for _, e := range rr.Value {
896 s += " " + e.Key().String() + "=\"" + e.String() + "\""
897 }
898 return s
899 }
900
901 // areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
902 // copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
903 func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
904 a = cloneSlice(a)
905 b = cloneSlice(b)
906 sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
907 sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
908 for i, e := range a {
909 if e.Key() != b[i].Key() {
910 return false
911 }
912 b1, err1 := e.pack()
913 b2, err2 := b[i].pack()
914 if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) {
915 return false
916 }
917 }
918 return true
919 }
920
921 // svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
922 func svcbParamToStr(s []byte) string {
923 var str strings.Builder
924 str.Grow(4 * len(s))
925 for _, e := range s {
926 if ' ' <= e && e <= '~' {
927 switch e {
928 case '"', ';', ' ', '\\':
929 str.WriteByte('\\')
930 str.WriteByte(e)
931 default:
932 str.WriteByte(e)
933 }
934 } else {
935 str.WriteString(escapeByte(e))
936 }
937 }
938 return str.String()
939 }
940
941 // svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
942 func svcbParseParam(b string) ([]byte, error) {
943 data := make([]byte, 0, len(b))
944 for i := 0; i < len(b); {
945 if b[i] != '\\' {
946 data = append(data, b[i])
947 i++
948 continue
949 }
950 if i+1 == len(b) {
951 return nil, errors.New("escape unterminated")
952 }
953 if isDigit(b[i+1]) {
954 if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
955 a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
956 if err == nil {
957 i += 4
958 data = append(data, byte(a))
959 continue
960 }
961 }
962 return nil, errors.New("bad escaped octet")
963 } else {
964 data = append(data, b[i+1])
965 i += 2
966 }
967 }
968 return data, nil
969 }
970