Skip to content
Snippets Groups Projects
svcb.go 25.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • package dns
    
    import (
    	"bytes"
    	"encoding/binary"
    	"errors"
    
    	"net"
    	"sort"
    	"strconv"
    	"strings"
    )
    
    
    // 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.
    
    const (
    
    	SVCB_MANDATORY SVCBKey = iota
    	SVCB_ALPN
    	SVCB_NO_DEFAULT_ALPN
    	SVCB_PORT
    	SVCB_IPV4HINT
    	SVCB_ECHCONFIG
    	SVCB_IPV6HINT
    	SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
    
    	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:       "ech",
    
    	SVCB_IPV6HINT:        "ipv6hint",
    
    	SVCB_DOHPATH:         "dohpath",
    
    }
    
    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()
    	}
    
    
    	// "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
    	// ignore any SvcParams that are present."
    	// However, we don't check rr.Priority == 0 && len(xs) > 0 here
    	// It is the responsibility of the user of the library to check this.
    	// This is to encourage the fixing of the source of this error.
    
    
    	rr.Value = xs
    	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_DOHPATH:
    		return new(SVCBDoHPath)
    
    	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-08).
    //
    // NOTE: The HTTPS/SVCB RFCs are in the draft stage.
    // The API, including constants and types related to SVCBKeyValues, may
    // change in future versions in accordance with the latest drafts.
    
    type SVCB struct {
    	Hdr      RR_Header
    
    	Priority uint16         // If zero, Value must be empty or discarded by the user of this library
    
    	Target   string         `dns:"domain-name"`
    
    	Value    []SVCBKeyValue `dns:"pairs"`
    
    }
    
    // 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.
    
    //
    // NOTE: The HTTPS/SVCB RFCs are in the draft stage.
    // The API, including constants and types related to SVCBKeyValues, may
    // change in future versions in accordance with the latest drafts.
    
    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. If ignored, the whole RRSet must be ignored.
    // "port" and "no-default-alpn" are mandatory by default if present,
    // so they shouldn't be included here.
    //
    // It is incumbent upon the user of this library to reject the RRSet if
    // or avoid constructing such an RRSet that:
    // - "mandatory" is included as one of the keys of mandatory
    // - no key is listed multiple times in mandatory
    // - all keys listed in mandatory are present
    // - escape sequences are not used in mandatory
    // - mandatory, when present, lists at least one key
    //
    
    // 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{dns.SVCB_ALPN}
    
    //	s.Value = append(s.Value, e)
    
    //	t := new(dns.SVCBAlpn)
    //	t.Alpn = []string{"xmpp-client"}
    //	s.Value = append(s.Value, t)
    
    type SVCBMandatory struct {
    
    	Code []SVCBKey
    
    }
    
    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.
    
    // The user of this library must ensure that at least one protocol is listed when alpn is present.
    // 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(h.Value, e)
    
    type SVCBAlpn struct {
    	Alpn []string
    }
    
    
    func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
    
    func (s *SVCBAlpn) String() string {
    	// An ALPN value is a comma-separated list of values, each of which can be
    	// an arbitrary binary value. In order to allow parsing, the comma and
    	// backslash characters are themselves excaped.
    	//
    	// However, this escaping is done in addition to the normal escaping which
    	// happens in zone files, meaning that these values must be
    	// double-escaped. This looks terrible, so if you see a never-ending
    	// sequence of backslash in a zone file this may be why.
    	//
    	// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
    	var str strings.Builder
    	for i, alpn := range s.Alpn {
    		// 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
    		str.Grow(4*len(alpn) + 1)
    		if i > 0 {
    			str.WriteByte(',')
    		}
    		for j := 0; j < len(alpn); j++ {
    			e := alpn[j]
    			if ' ' > e || e > '~' {
    				str.WriteString(escapeByte(e))
    				continue
    			}
    			switch e {
    			// We escape a few characters which may confuse humans or parsers.
    			case '"', ';', ' ':
    				str.WriteByte('\\')
    				str.WriteByte(e)
    			// The comma and backslash characters themselves must be
    			// doubly-escaped. We use `\\` for the first backslash and
    			// the escaped numeric value for the other value. We especially
    			// don't want a comma in the output.
    			case ',':
    				str.WriteString(`\\\044`)
    			case '\\':
    				str.WriteString(`\\\092`)
    			default:
    				str.WriteByte(e)
    			}
    		}
    	}
    	return str.String()
    }
    
    
    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 {
    
    			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 {
    
    	if len(b) == 0 {
    		s.Alpn = []string{}
    		return nil
    	}
    
    	alpn := []string{}
    	a := []byte{}
    	for p := 0; p < len(b); {
    		c, q := nextByte(b, p)
    		if q == 0 {
    			return errors.New("dns: svcbalpn: unterminated escape")
    		}
    		p += q
    		// If we find a comma, we have finished reading an alpn.
    		if c == ',' {
    			if len(a) == 0 {
    				return errors.New("dns: svcbalpn: empty protocol identifier")
    			}
    			alpn = append(alpn, string(a))
    			a = []byte{}
    			continue
    		}
    		// If it's a backslash, we need to handle a comma-separated list.
    		if c == '\\' {
    			dc, dq := nextByte(b, p)
    			if dq == 0 {
    				return errors.New("dns: svcbalpn: unterminated escape decoding comma-separated list")
    			}
    			if dc != '\\' && dc != ',' {
    				return errors.New("dns: svcbalpn: bad escaped character decoding comma-separated list")
    			}
    			p += dq
    			c = dc
    		}
    		a = append(a, c)
    	}
    	// Add the final alpn.
    	if len(a) == 0 {
    		return errors.New("dns: svcbalpn: last protocol identifier empty")
    	}
    	s.Alpn = append(alpn, string(a))
    
    	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.
    
    // Should be used in conjunction with alpn.
    
    // Basic use pattern for creating a no-default-alpn option:
    //
    //	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
    
    //	t := new(dns.SVCBAlpn)
    //	t.Alpn = []string{"xmpp-client"}
    //	s.Value = append(s.Value, t)
    
    //	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 {
    
    		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 {
    
    	hint := make([]net.IP, len(s.Hint))
    	for i, ip := range s.Hint {
    		hint[i] = copyIP(ip)
    	}
    
    
    	return &SVCBIPv4Hint{
    
    	}
    }
    
    // SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
    
    // Basic use pattern for creating an ech 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 // Specifically ECHConfigList including the redundant length prefix
    
    }
    
    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: svcbech: bad base64 ech")
    
    	}
    	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 {
    	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")
    		}
    
    		if ip.To4() != nil {
    			return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
    		}
    
    		dst[i] = ip
    	}
    	s.Hint = dst
    	return nil
    }
    
    func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
    
    	hint := make([]net.IP, len(s.Hint))
    	for i, ip := range s.Hint {
    		hint[i] = copyIP(ip)
    	}
    
    
    	return &SVCBIPv6Hint{
    
    // 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).
    //
    // A basic example of using the dohpath option together with the alpn
    // option to indicate support for DNS over HTTPS on a certain path:
    //
    //	s := new(dns.SVCB)
    //	s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
    //	e := new(dns.SVCBAlpn)
    //	e.Alpn = []string{"h2", "h3"}
    //	p := new(dns.SVCBDoHPath)
    //	p.Template = "/dns-query{?dns}"
    //	s.Value = append(s.Value, e, p)
    //
    // 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) String() string        { return svcbParamToStr([]byte(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) parse(b string) error {
    	template, err := svcbParseParam(b)
    	if err != nil {
    		return fmt.Errorf("dns: svcbdohpath: %w", err)
    	}
    	s.Template = string(template)
    	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:
    //
    //	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) String() string        { return svcbParamToStr(s.Data) }
    
    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) parse(b string) error {
    
    	data, err := svcbParseParam(b)
    	if err != nil {
    		return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
    
    	}
    	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
    }
    
    
    // svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
    func svcbParamToStr(s []byte) string {
    	var str strings.Builder
    	str.Grow(4 * len(s))
    	for _, e := range s {
    		if ' ' <= e && e <= '~' {
    			switch e {
    			case '"', ';', ' ', '\\':
    				str.WriteByte('\\')
    				str.WriteByte(e)
    			default:
    				str.WriteByte(e)
    			}
    		} else {
    			str.WriteString(escapeByte(e))
    		}
    	}
    	return str.String()
    }
    
    // svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
    func svcbParseParam(b string) ([]byte, 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 nil, errors.New("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 nil, errors.New("bad escaped octet")
    		} else {
    			data = append(data, b[i+1])
    			i += 2
    		}
    	}
    	return data, nil
    }