Skip to content
Snippets Groups Projects
svcb.go 20 KiB
Newer Older
  • Learn to ignore specific revisions
  • package dns
    
    import (
    	"bytes"
    	"encoding/binary"
    	"errors"
    	"net"
    	"sort"
    	"strconv"
    	"strings"
    )
    
    type SVCBKey uint16
    
    // Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2.
    const (
    	SVCB_MANDATORY       SVCBKey = 0
    	SVCB_ALPN            SVCBKey = 1
    	SVCB_NO_DEFAULT_ALPN SVCBKey = 2
    	SVCB_PORT            SVCBKey = 3
    	SVCB_IPV4HINT        SVCBKey = 4
    	SVCB_ECHCONFIG       SVCBKey = 5
    	SVCB_IPV6HINT        SVCBKey = 6
    	svcb_RESERVED        SVCBKey = 65535
    )
    
    var svcbKeyToStringMap = map[SVCBKey]string{
    	SVCB_MANDATORY:       "mandatory",
    	SVCB_ALPN:            "alpn",
    	SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
    	SVCB_PORT:            "port",
    	SVCB_IPV4HINT:        "ipv4hint",
    	SVCB_ECHCONFIG:       "echconfig",
    	SVCB_IPV6HINT:        "ipv6hint",
    }
    
    var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
    
    func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey {
    	n := make(map[string]SVCBKey, len(m))
    	for u, s := range m {
    		n[s] = u
    	}
    	return n
    }
    
    // String takes the numerical code of an SVCB key and returns its name.
    // 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 != "" {
    		return x
    	}
    	if key == svcb_RESERVED {
    		return ""
    	}
    	return "key" + strconv.FormatUint(uint64(key), 10)
    }
    
    // svcbStringToKey returns the numerical code of an SVCB key.
    // Returns svcb_RESERVED for reserved/invalid keys.
    // Accepts unassigned keys as well as experimental/private keys.
    func svcbStringToKey(s string) SVCBKey {
    	if strings.HasPrefix(s, "key") {
    		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)] != "" {
    			return svcb_RESERVED
    		}
    		return SVCBKey(a)
    	}
    	if key, ok := svcbStringToKeyMap[s]; ok {
    		return key
    	}
    	return svcb_RESERVED
    }
    
    func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
    	l, _ := c.Next()
    	i, e := strconv.ParseUint(l.token, 10, 16)
    	if e != nil || l.err {
    		return &ParseError{l.token, "bad SVCB priority", l}
    	}
    	rr.Priority = uint16(i)
    
    	c.Next()        // zBlank
    	l, _ = c.Next() // zString
    	rr.Target = l.token
    
    	name, nameOk := toAbsoluteName(l.token, o)
    	if l.err || !nameOk {
    		return &ParseError{l.token, "bad SVCB Target", l}
    	}
    	rr.Target = name
    
    	// Values (if any)
    	l, _ = c.Next()
    	var xs []SVCBKeyValue
    	// Helps require whitespace between pairs.
    	// Prevents key1000="a"key1001=...
    	canHaveNextKey := true
    	for l.value != zNewline && l.value != zEOF {
    		switch l.value {
    		case zString:
    			if !canHaveNextKey {
    				// The key we can now read was probably meant to be
    				// a part of the last value.
    				return &ParseError{l.token, "bad SVCB value quotation", l}
    			}
    
    			// In key=value pairs, value does not have to be quoted unless value
    			// contains whitespace. And keys don't need to have values.
    			// Similarly, keys with an equality signs after them don't need values.
    			// l.token includes at least up to the first equality sign.
    			idx := strings.IndexByte(l.token, '=')
    			var key, value string
    			if idx < 0 {
    				// Key with no value and no equality sign
    				key = l.token
    			} else if idx == 0 {
    				return &ParseError{l.token, "bad SVCB key", l}
    			} else {
    				key, value = l.token[:idx], l.token[idx+1:]
    
    				if value == "" {
    					// We have a key and an equality sign. Maybe we have nothing
    					// after "=" or we have a double quote.
    					l, _ = c.Next()
    					if l.value == zQuote {
    						// Only needed when value ends with double quotes.
    						// Any value starting with zQuote ends with it.
    						canHaveNextKey = false
    
    						l, _ = c.Next()
    						switch l.value {
    						case zString:
    							// We have a value in double quotes.
    							value = l.token
    							l, _ = c.Next()
    							if l.value != zQuote {
    								return &ParseError{l.token, "SVCB unterminated value", l}
    							}
    						case zQuote:
    							// There's nothing in double quotes.
    						default:
    							return &ParseError{l.token, "bad SVCB value", l}
    						}
    					}
    				}
    			}
    			kv := makeSVCBKeyValue(svcbStringToKey(key))
    			if kv == nil {
    				return &ParseError{l.token, "bad SVCB key", l}
    			}
    			if err := kv.parse(value); err != nil {
    				return &ParseError{l.token, err.Error(), l}
    			}
    			xs = append(xs, kv)
    		case zQuote:
    			return &ParseError{l.token, "SVCB key can't contain double quotes", l}
    		case zBlank:
    			canHaveNextKey = true
    		default:
    			return &ParseError{l.token, "bad SVCB values", l}
    		}
    		l, _ = c.Next()
    	}
    	rr.Value = xs
    	if rr.Priority == 0 && len(xs) > 0 {
    		return &ParseError{l.token, "SVCB aliasform can't have values", l}
    	}
    	return nil
    }
    
    // makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.
    func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
    	switch key {
    	case SVCB_MANDATORY:
    		return new(SVCBMandatory)
    	case SVCB_ALPN:
    		return new(SVCBAlpn)
    	case SVCB_NO_DEFAULT_ALPN:
    		return new(SVCBNoDefaultAlpn)
    	case SVCB_PORT:
    		return new(SVCBPort)
    	case SVCB_IPV4HINT:
    		return new(SVCBIPv4Hint)
    	case SVCB_ECHCONFIG:
    		return new(SVCBECHConfig)
    	case SVCB_IPV6HINT:
    		return new(SVCBIPv6Hint)
    	case svcb_RESERVED:
    		return nil
    	default:
    		e := new(SVCBLocal)
    		e.KeyCode = key
    		return e
    	}
    }
    
    // SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01).
    type SVCB struct {
    	Hdr      RR_Header
    	Priority uint16
    	Target   string         `dns:"domain-name"`
    
    	Value    []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is zero.
    
    208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
    }
    
    // HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
    // Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
    type HTTPS struct {
    	SVCB
    }
    
    func (rr *HTTPS) String() string {
    	return rr.SVCB.String()
    }
    
    func (rr *HTTPS) parse(c *zlexer, o string) *ParseError {
    	return rr.SVCB.parse(c, o)
    }
    
    // SVCBKeyValue defines a key=value pair for the SVCB RR type.
    // An SVCB RR can have multiple SVCBKeyValues appended to it.
    type SVCBKeyValue interface {
    	Key() SVCBKey          // Key returns the numerical key code.
    	pack() ([]byte, error) // pack returns the encoded value.
    	unpack([]byte) error   // unpack sets the value.
    	String() string        // String returns the string representation of the value.
    	parse(string) error    // parse sets the value to the given string representation of the value.
    	copy() SVCBKeyValue    // copy returns a deep-copy of the pair.
    	len() int              // len returns the length of value in the wire format.
    }
    
    // SVCBMandatory pair adds to required keys that must be interpreted for the RR
    // to be functional.
    // Basic use pattern for creating a mandatory option:
    //
    //	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
    //	e := new(dns.SVCBMandatory)
    //	e.Code = []uint16{65403}
    //	s.Value = append(s.Value, e)
    type SVCBMandatory struct {
    	Code []SVCBKey // Must not include mandatory
    }
    
    func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
    
    func (s *SVCBMandatory) String() string {
    	str := make([]string, len(s.Code))
    	for i, e := range s.Code {
    		str[i] = e.String()
    	}
    	return strings.Join(str, ",")
    }
    
    func (s *SVCBMandatory) pack() ([]byte, error) {
    	codes := append([]SVCBKey(nil), s.Code...)
    	sort.Slice(codes, func(i, j int) bool {
    		return codes[i] < codes[j]
    	})
    	b := make([]byte, 2*len(codes))
    	for i, e := range codes {
    		binary.BigEndian.PutUint16(b[2*i:], uint16(e))
    	}
    	return b, nil
    }
    
    func (s *SVCBMandatory) unpack(b []byte) error {
    	if len(b)%2 != 0 {
    		return errors.New("dns: svcbmandatory: value length is not a multiple of 2")
    	}
    	codes := make([]SVCBKey, 0, len(b)/2)
    	for i := 0; i < len(b); i += 2 {
    		// We assume strictly increasing order.
    		codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:])))
    	}
    	s.Code = codes
    	return nil
    }
    
    func (s *SVCBMandatory) parse(b string) error {
    	str := strings.Split(b, ",")
    	codes := make([]SVCBKey, 0, len(str))
    	for _, e := range str {
    		codes = append(codes, svcbStringToKey(e))
    	}
    	s.Code = codes
    	return nil
    }
    
    func (s *SVCBMandatory) len() int {
    	return 2 * len(s.Code)
    }
    
    func (s *SVCBMandatory) copy() SVCBKeyValue {
    	return &SVCBMandatory{
    		append([]SVCBKey(nil), s.Code...),
    	}
    }
    
    // SVCBAlpn pair is used to list supported connection protocols.
    // Protocol ids can be found at:
    // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
    // Basic use pattern for creating an alpn option:
    //
    //	h := new(dns.HTTPS)
    //	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
    //	e := new(dns.SVCBAlpn)
    //	e.Alpn = []string{"h2", "http/1.1"}
    //	h.Value = append(o.Value, e)
    type SVCBAlpn struct {
    	Alpn []string
    }
    
    func (*SVCBAlpn) Key() SVCBKey     { return SVCB_ALPN }
    func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
    
    func (s *SVCBAlpn) pack() ([]byte, error) {
    	// Liberally estimate the size of an alpn as 10 octets
    	b := make([]byte, 0, 10*len(s.Alpn))
    	for _, e := range s.Alpn {
    		if len(e) == 0 {
    			return nil, errors.New("dns: svcbalpn: empty alpn-id")
    		}
    		if len(e) > 255 {
    			return nil, errors.New("dns: svcbalpn: alpn-id too long")
    		}
    		b = append(b, byte(len(e)))
    		b = append(b, e...)
    	}
    	return b, nil
    }
    
    func (s *SVCBAlpn) unpack(b []byte) error {
    	// Estimate the size of the smallest alpn as 4 bytes
    	alpn := make([]string, 0, len(b)/4)
    	for i := 0; i < len(b); {
    		length := int(b[i])
    		i++
    		if i+length > len(b) {
    			return errors.New("dns: svcbalpn: alpn array overflowing")
    		}
    		alpn = append(alpn, string(b[i:i+length]))
    		i += length
    	}
    	s.Alpn = alpn
    	return nil
    }
    
    func (s *SVCBAlpn) parse(b string) error {
    	s.Alpn = strings.Split(b, ",")
    	return nil
    }
    
    func (s *SVCBAlpn) len() int {
    	var l int
    	for _, e := range s.Alpn {
    		l += 1 + len(e)
    	}
    	return l
    }
    
    func (s *SVCBAlpn) copy() SVCBKeyValue {
    	return &SVCBAlpn{
    		append([]string(nil), s.Alpn...),
    	}
    }
    
    // SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
    // Basic use pattern for creating a no-default-alpn option:
    //
    //	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
    //	e := new(dns.SVCBNoDefaultAlpn)
    //	s.Value = append(s.Value, e)
    type SVCBNoDefaultAlpn struct{}
    
    func (*SVCBNoDefaultAlpn) Key() SVCBKey          { return SVCB_NO_DEFAULT_ALPN }
    func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue    { return &SVCBNoDefaultAlpn{} }
    func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil }
    func (*SVCBNoDefaultAlpn) String() string        { return "" }
    func (*SVCBNoDefaultAlpn) len() int              { return 0 }
    
    func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
    	if len(b) != 0 {
    		return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
    	}
    	return nil
    }
    
    func (*SVCBNoDefaultAlpn) parse(b string) error {
    	if len(b) != 0 {
    		return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
    	}
    	return nil
    }
    
    // SVCBPort pair defines the port for connection.
    // Basic use pattern for creating a port option:
    //
    //	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
    //	e := new(dns.SVCBPort)
    //	e.Port = 80
    //	s.Value = append(s.Value, e)
    type SVCBPort struct {
    	Port uint16
    }
    
    func (*SVCBPort) Key() SVCBKey         { return SVCB_PORT }
    func (*SVCBPort) len() int             { return 2 }
    func (s *SVCBPort) String() string     { return strconv.FormatUint(uint64(s.Port), 10) }
    func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} }
    
    func (s *SVCBPort) unpack(b []byte) error {
    	if len(b) != 2 {
    		return errors.New("dns: svcbport: port length is not exactly 2 octets")
    	}
    	s.Port = binary.BigEndian.Uint16(b)
    	return nil
    }
    
    func (s *SVCBPort) pack() ([]byte, error) {
    	b := make([]byte, 2)
    	binary.BigEndian.PutUint16(b, s.Port)
    	return b, nil
    }
    
    func (s *SVCBPort) parse(b string) error {
    	port, err := strconv.ParseUint(b, 10, 16)
    	if err != nil {
    		return errors.New("dns: svcbport: port out of range")
    	}
    	s.Port = uint16(port)
    	return nil
    }
    
    // SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections
    // if A and AAAA record responses for SVCB's Target domain haven't been received.
    // In that case, optionally, A and AAAA requests can be made, after which the connection
    // to the hinted IP address may be terminated and a new connection may be opened.
    // Basic use pattern for creating an ipv4hint option:
    //
    //	h := new(dns.HTTPS)
    //	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
    //	e := new(dns.SVCBIPv4Hint)
    //	e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
    //
    //  Or
    //
    //	e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
    //	h.Value = append(h.Value, e)
    type SVCBIPv4Hint struct {
    	Hint []net.IP
    }
    
    func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT }
    func (s *SVCBIPv4Hint) len() int   { return 4 * len(s.Hint) }
    
    func (s *SVCBIPv4Hint) pack() ([]byte, error) {
    	b := make([]byte, 0, 4*len(s.Hint))
    	for _, e := range s.Hint {
    		x := e.To4()
    		if x == nil {
    			return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6")
    		}
    		b = append(b, x...)
    	}
    	return b, nil
    }
    
    func (s *SVCBIPv4Hint) unpack(b []byte) error {
    	if len(b) == 0 || len(b)%4 != 0 {
    		return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
    	}
    	x := make([]net.IP, 0, len(b)/4)
    	for i := 0; i < len(b); i += 4 {
    		x = append(x, net.IP(b[i:i+4]))
    	}
    	s.Hint = x
    	return nil
    }
    
    func (s *SVCBIPv4Hint) String() string {
    	str := make([]string, len(s.Hint))
    	for i, e := range s.Hint {
    		x := e.To4()
    		if x == nil {
    			return "<nil>"
    		}
    		str[i] = x.String()
    	}
    	return strings.Join(str, ",")
    }
    
    func (s *SVCBIPv4Hint) parse(b string) error {
    	if strings.Contains(b, ":") {
    		return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
    	}
    	str := strings.Split(b, ",")
    	dst := make([]net.IP, len(str))
    	for i, e := range str {
    		ip := net.ParseIP(e).To4()
    		if ip == nil {
    			return errors.New("dns: svcbipv4hint: bad ip")
    		}
    		dst[i] = ip
    	}
    	s.Hint = dst
    	return nil
    }
    
    func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
    	return &SVCBIPv4Hint{
    		append([]net.IP(nil), s.Hint...),
    	}
    }
    
    // SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
    // Basic use pattern for creating an echconfig option:
    //
    //	h := new(dns.HTTPS)
    //	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
    //	e := new(dns.SVCBECHConfig)
    //	e.ECH = []byte{0xfe, 0x08, ...}
    //	h.Value = append(h.Value, e)
    type SVCBECHConfig struct {
    	ECH []byte
    }
    
    func (*SVCBECHConfig) Key() SVCBKey     { return SVCB_ECHCONFIG }
    func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
    func (s *SVCBECHConfig) len() int       { return len(s.ECH) }
    
    func (s *SVCBECHConfig) pack() ([]byte, error) {
    	return append([]byte(nil), s.ECH...), nil
    }
    
    func (s *SVCBECHConfig) copy() SVCBKeyValue {
    	return &SVCBECHConfig{
    		append([]byte(nil), s.ECH...),
    	}
    }
    
    func (s *SVCBECHConfig) unpack(b []byte) error {
    	s.ECH = append([]byte(nil), b...)
    	return nil
    }
    func (s *SVCBECHConfig) parse(b string) error {
    	x, err := fromBase64([]byte(b))
    	if err != nil {
    		return errors.New("dns: svcbechconfig: bad base64 echconfig")
    	}
    	s.ECH = x
    	return nil
    }
    
    // SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections
    // if A and AAAA record responses for SVCB's Target domain haven't been received.
    // In that case, optionally, A and AAAA requests can be made, after which the
    // connection to the hinted IP address may be terminated and a new connection may be opened.
    // Basic use pattern for creating an ipv6hint option:
    //
    //	h := new(dns.HTTPS)
    //	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
    //	e := new(dns.SVCBIPv6Hint)
    //	e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
    //	h.Value = append(h.Value, e)
    type SVCBIPv6Hint struct {
    	Hint []net.IP
    }
    
    func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT }
    func (s *SVCBIPv6Hint) len() int   { return 16 * len(s.Hint) }
    
    func (s *SVCBIPv6Hint) pack() ([]byte, error) {
    	b := make([]byte, 0, 16*len(s.Hint))
    	for _, e := range s.Hint {
    		if len(e) != net.IPv6len || e.To4() != nil {
    			return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4")
    		}
    		b = append(b, e...)
    	}
    	return b, nil
    }
    
    func (s *SVCBIPv6Hint) unpack(b []byte) error {
    	if len(b) == 0 || len(b)%16 != 0 {
    		return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16")
    	}
    	x := make([]net.IP, 0, len(b)/16)
    	for i := 0; i < len(b); i += 16 {
    		ip := net.IP(b[i : i+16])
    		if ip.To4() != nil {
    			return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
    		}
    		x = append(x, ip)
    	}
    	s.Hint = x
    	return nil
    }
    
    func (s *SVCBIPv6Hint) String() string {
    	str := make([]string, len(s.Hint))
    	for i, e := range s.Hint {
    		if x := e.To4(); x != nil {
    			return "<nil>"
    		}
    		str[i] = e.String()
    	}
    	return strings.Join(str, ",")
    }
    
    func (s *SVCBIPv6Hint) parse(b string) error {
    	if strings.Contains(b, ".") {
    		return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
    	}
    	str := strings.Split(b, ",")
    	dst := make([]net.IP, len(str))
    	for i, e := range str {
    		ip := net.ParseIP(e)
    		if ip == nil {
    			return errors.New("dns: svcbipv6hint: bad ip")
    		}
    		dst[i] = ip
    	}
    	s.Hint = dst
    	return nil
    }
    
    func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
    	return &SVCBIPv6Hint{
    		append([]net.IP(nil), s.Hint...),
    	}
    }
    
    // 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:
    //
    //	h := new(dns.HTTPS)
    //	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
    //	e := new(dns.SVCBLocal)
    //	e.KeyCode = 65400
    //	e.Data = []byte("abc")
    //	h.Value = append(h.Value, e)
    type SVCBLocal struct {
    	KeyCode SVCBKey // Never 65535 or any assigned keys.
    	Data    []byte  // All byte sequences are allowed.
    }
    
    func (s *SVCBLocal) Key() SVCBKey          { return s.KeyCode }
    func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
    func (s *SVCBLocal) len() int              { return len(s.Data) }
    
    func (s *SVCBLocal) unpack(b []byte) error {
    	s.Data = append([]byte(nil), b...)
    	return nil
    }
    
    func (s *SVCBLocal) String() string {
    	var str strings.Builder
    	str.Grow(4 * len(s.Data))
    	for _, e := range s.Data {
    		if ' ' <= e && e <= '~' {
    			switch e {
    			case '"', ';', ' ', '\\':
    				str.WriteByte('\\')
    				str.WriteByte(e)
    			default:
    				str.WriteByte(e)
    			}
    		} else {
    			str.WriteString(escapeByte(e))
    		}
    	}
    	return str.String()
    }
    
    func (s *SVCBLocal) parse(b string) error {
    	data := make([]byte, 0, len(b))
    	for i := 0; i < len(b); {
    		if b[i] != '\\' {
    			data = append(data, b[i])
    			i++
    			continue
    		}
    		if i+1 == len(b) {
    			return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated")
    		}
    		if isDigit(b[i+1]) {
    			if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
    				a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
    				if err == nil {
    					i += 4
    					data = append(data, byte(a))
    					continue
    				}
    			}
    			return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
    		} else {
    			data = append(data, b[i+1])
    			i += 2
    		}
    	}
    	s.Data = data
    	return nil
    }
    
    func (s *SVCBLocal) copy() SVCBKeyValue {
    	return &SVCBLocal{s.KeyCode,
    		append([]byte(nil), s.Data...),
    	}
    }
    
    func (rr *SVCB) String() string {
    	s := rr.Hdr.String() +
    		strconv.Itoa(int(rr.Priority)) + " " +
    		sprintName(rr.Target)
    	for _, e := range rr.Value {
    		s += " " + e.Key().String() + "=\"" + e.String() + "\""
    	}
    	return s
    }
    
    // areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
    // copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
    func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
    	a = append([]SVCBKeyValue(nil), a...)
    	b = append([]SVCBKeyValue(nil), b...)
    	sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
    	sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
    	for i, e := range a {
    		if e.Key() != b[i].Key() {
    			return false
    		}
    		b1, err1 := e.pack()
    		b2, err2 := b[i].pack()
    		if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) {
    			return false
    		}
    	}
    	return true
    }