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