package dnsimport ()// SVCBKey is the type of the keys used in the SVCB RR.typeSVCBKeyuint16// Keys defined in rfc9460const (SVCB_MANDATORYSVCBKey = iotaSVCB_ALPNSVCB_NO_DEFAULT_ALPNSVCB_PORTSVCB_IPV4HINTSVCB_ECHCONFIGSVCB_IPV6HINTSVCB_DOHPATH// rfc9461 Section 5SVCB_OHTTP// rfc9540 Section 8 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",SVCB_OHTTP: "ohttp",}var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)func reverseSVCBKeyMap( map[SVCBKey]string) map[string]SVCBKey { := make(map[string]SVCBKey, len())for , := range { [] = }return}// 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 ( SVCBKey) () string {if := svcbKeyToStringMap[]; != "" {return }if == svcb_RESERVED {return"" }return"key" + strconv.FormatUint(uint64(), 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( string) SVCBKey {ifstrings.HasPrefix(, "key") { , := strconv.ParseUint([3:], 10, 16)// no leading zeros // key shouldn't be registeredif != nil || == 65535 || [3] == '0' || svcbKeyToStringMap[SVCBKey()] != "" {returnsvcb_RESERVED }returnSVCBKey() }if , := svcbStringToKeyMap[]; {return }returnsvcb_RESERVED}func ( *SVCB) ( *zlexer, string) *ParseError { , := .Next() , := strconv.ParseUint(.token, 10, 16)if != nil || .err {return &ParseError{file: .token, err: "bad SVCB priority", lex: } } .Priority = uint16() .Next() // zBlank , _ = .Next() // zString .Target = .token , := toAbsoluteName(.token, )if .err || ! {return &ParseError{file: .token, err: "bad SVCB Target", lex: } } .Target = // Values (if any) , _ = .Next()var []SVCBKeyValue// Helps require whitespace between pairs. // Prevents key1000="a"key1001=... := truefor .value != zNewline && .value != zEOF {switch .value {casezString:if ! {// The key we can now read was probably meant to be // a part of the last value.return &ParseError{file: .token, err: "bad SVCB value quotation", lex: } }// 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. := strings.IndexByte(.token, '=')var , stringif < 0 {// Key with no value and no equality sign = .token } elseif == 0 {return &ParseError{file: .token, err: "bad SVCB key", lex: } } else { , = .token[:], .token[+1:]if == "" {// We have a key and an equality sign. Maybe we have nothing // after "=" or we have a double quote. , _ = .Next()if .value == zQuote {// Only needed when value ends with double quotes. // Any value starting with zQuote ends with it. = false , _ = .Next()switch .value {casezString:// We have a value in double quotes. = .token , _ = .Next()if .value != zQuote {return &ParseError{file: .token, err: "SVCB unterminated value", lex: } }casezQuote:// There's nothing in double quotes.default:return &ParseError{file: .token, err: "bad SVCB value", lex: } } } } } := makeSVCBKeyValue(svcbStringToKey())if == nil {return &ParseError{file: .token, err: "bad SVCB key", lex: } }if := .parse(); != nil {return &ParseError{file: .token, wrappedErr: , lex: } } = append(, )casezQuote:return &ParseError{file: .token, err: "SVCB key can't contain double quotes", lex: }casezBlank: = truedefault:return &ParseError{file: .token, err: "bad SVCB values", lex: } } , _ = .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. .Value = returnnil}// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.func makeSVCBKeyValue( SVCBKey) SVCBKeyValue {switch {caseSVCB_MANDATORY:returnnew(SVCBMandatory)caseSVCB_ALPN:returnnew(SVCBAlpn)caseSVCB_NO_DEFAULT_ALPN:returnnew(SVCBNoDefaultAlpn)caseSVCB_PORT:returnnew(SVCBPort)caseSVCB_IPV4HINT:returnnew(SVCBIPv4Hint)caseSVCB_ECHCONFIG:returnnew(SVCBECHConfig)caseSVCB_IPV6HINT:returnnew(SVCBIPv6Hint)caseSVCB_DOHPATH:returnnew(SVCBDoHPath)caseSVCB_OHTTP:returnnew(SVCBOhttp)casesvcb_RESERVED:returnnildefault: := new(SVCBLocal) .KeyCode = return }}// SVCB RR. See RFC 9460.typeSVCBstruct { 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. See RFC 9460. Everything valid for SVCB applies to HTTPS as well.// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.typeHTTPSstruct {SVCB}func ( *HTTPS) () string {return .SVCB.String()}func ( *HTTPS) ( *zlexer, string) *ParseError {return .SVCB.parse(, )}// SVCBKeyValue defines a key=value pair for the SVCB RR type.// An SVCB RR can have multiple SVCBKeyValues appended to it.typeSVCBKeyValueinterface {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)typeSVCBMandatorystruct { Code []SVCBKey}func (*SVCBMandatory) () SVCBKey { returnSVCB_MANDATORY }func ( *SVCBMandatory) () string { := make([]string, len(.Code))for , := range .Code { [] = .String() }returnstrings.Join(, ",")}func ( *SVCBMandatory) () ([]byte, error) { := cloneSlice(.Code)sort.Slice(, func(, int) bool {return [] < [] }) := make([]byte, 2*len())for , := range {binary.BigEndian.PutUint16([2*:], uint16()) }return , nil}func ( *SVCBMandatory) ( []byte) error {iflen()%2 != 0 {returnerrors.New("dns: svcbmandatory: value length is not a multiple of 2") } := make([]SVCBKey, 0, len()/2)for := 0; < len(); += 2 {// We assume strictly increasing order. = append(, SVCBKey(binary.BigEndian.Uint16([:]))) } .Code = returnnil}func ( *SVCBMandatory) ( string) error { := make([]SVCBKey, 0, strings.Count(, ",")+1)forlen() > 0 {varstring , , _ = strings.Cut(, ",") = append(, svcbStringToKey()) } .Code = returnnil}func ( *SVCBMandatory) () int {return2 * len(.Code)}func ( *SVCBMandatory) () SVCBKeyValue {return &SVCBMandatory{cloneSlice(.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)typeSVCBAlpnstruct { Alpn []string}func (*SVCBAlpn) () SVCBKey { returnSVCB_ALPN }func ( *SVCBAlpn) () 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 escaped. // // 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.1varstrings.Builderfor , := range .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 .Grow(4*len() + 1)if > 0 { .WriteByte(',') }for := 0; < len(); ++ { := []if' ' > || > '~' { .WriteString(escapeByte())continue }switch {// We escape a few characters which may confuse humans or parsers.case'"', ';', ' ': .WriteByte('\\') .WriteByte()// 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',': .WriteString(`\\\044`)case'\\': .WriteString(`\\\092`)default: .WriteByte() } } }return .String()}func ( *SVCBAlpn) () ([]byte, error) {// Liberally estimate the size of an alpn as 10 octets := make([]byte, 0, 10*len(.Alpn))for , := range .Alpn {if == "" {returnnil, errors.New("dns: svcbalpn: empty alpn-id") }iflen() > 255 {returnnil, errors.New("dns: svcbalpn: alpn-id too long") } = append(, byte(len())) = append(, ...) }return , nil}func ( *SVCBAlpn) ( []byte) error {// Estimate the size of the smallest alpn as 4 bytes := make([]string, 0, len()/4)for := 0; < len(); { := int([]) ++if + > len() {returnerrors.New("dns: svcbalpn: alpn array overflowing") } = append(, string([:+])) += } .Alpn = returnnil}func ( *SVCBAlpn) ( string) error {iflen() == 0 { .Alpn = []string{}returnnil } := []string{} := []byte{}for := 0; < len(); { , := nextByte(, )if == 0 {returnerrors.New("dns: svcbalpn: unterminated escape") } += // If we find a comma, we have finished reading an alpn.if == ',' {iflen() == 0 {returnerrors.New("dns: svcbalpn: empty protocol identifier") } = append(, string()) = []byte{}continue }// If it's a backslash, we need to handle a comma-separated list.if == '\\' { , := nextByte(, )if == 0 {returnerrors.New("dns: svcbalpn: unterminated escape decoding comma-separated list") }if != '\\' && != ',' {returnerrors.New("dns: svcbalpn: bad escaped character decoding comma-separated list") } += = } = append(, ) }// Add the final alpn.iflen() == 0 {returnerrors.New("dns: svcbalpn: last protocol identifier empty") } .Alpn = append(, string())returnnil}func ( *SVCBAlpn) () int {varintfor , := range .Alpn { += 1 + len() }return}func ( *SVCBAlpn) () SVCBKeyValue {return &SVCBAlpn{cloneSlice(.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)typeSVCBNoDefaultAlpnstruct{}func (*SVCBNoDefaultAlpn) () SVCBKey { returnSVCB_NO_DEFAULT_ALPN }func (*SVCBNoDefaultAlpn) () SVCBKeyValue { return &SVCBNoDefaultAlpn{} }func (*SVCBNoDefaultAlpn) () ([]byte, error) { return []byte{}, nil }func (*SVCBNoDefaultAlpn) () string { return"" }func (*SVCBNoDefaultAlpn) () int { return0 }func (*SVCBNoDefaultAlpn) ( []byte) error {iflen() != 0 {returnerrors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value") }returnnil}func (*SVCBNoDefaultAlpn) ( string) error {if != "" {returnerrors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value") }returnnil}// 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)typeSVCBPortstruct { Port uint16}func (*SVCBPort) () SVCBKey { returnSVCB_PORT }func (*SVCBPort) () int { return2 }func ( *SVCBPort) () string { returnstrconv.FormatUint(uint64(.Port), 10) }func ( *SVCBPort) () SVCBKeyValue { return &SVCBPort{.Port} }func ( *SVCBPort) ( []byte) error {iflen() != 2 {returnerrors.New("dns: svcbport: port length is not exactly 2 octets") } .Port = binary.BigEndian.Uint16()returnnil}func ( *SVCBPort) () ([]byte, error) { := make([]byte, 2)binary.BigEndian.PutUint16(, .Port)return , nil}func ( *SVCBPort) ( string) error { , := strconv.ParseUint(, 10, 16)if != nil {returnerrors.New("dns: svcbport: port out of range") } .Port = uint16()returnnil}// 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)typeSVCBIPv4Hintstruct { Hint []net.IP}func (*SVCBIPv4Hint) () SVCBKey { returnSVCB_IPV4HINT }func ( *SVCBIPv4Hint) () int { return4 * len(.Hint) }func ( *SVCBIPv4Hint) () ([]byte, error) { := make([]byte, 0, 4*len(.Hint))for , := range .Hint { := .To4()if == nil {returnnil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6") } = append(, ...) }return , nil}func ( *SVCBIPv4Hint) ( []byte) error {iflen() == 0 || len()%4 != 0 {returnerrors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4") } = cloneSlice() := make([]net.IP, 0, len()/4)for := 0; < len(); += 4 { = append(, net.IP([:+4])) } .Hint = returnnil}func ( *SVCBIPv4Hint) () string { := make([]string, len(.Hint))for , := range .Hint { := .To4()if == nil {return"<nil>" } [] = .String() }returnstrings.Join(, ",")}func ( *SVCBIPv4Hint) ( string) error {if == "" {returnerrors.New("dns: svcbipv4hint: empty hint") }ifstrings.Contains(, ":") {returnerrors.New("dns: svcbipv4hint: expected ipv4, got ipv6") } := make([]net.IP, 0, strings.Count(, ",")+1)forlen() > 0 {varstring , , _ = strings.Cut(, ",") := net.ParseIP().To4()if == nil {returnerrors.New("dns: svcbipv4hint: bad ip") } = append(, ) } .Hint = returnnil}func ( *SVCBIPv4Hint) () SVCBKeyValue { := make([]net.IP, len(.Hint))for , := range .Hint { [] = cloneSlice() }return &SVCBIPv4Hint{Hint: }}// 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)typeSVCBECHConfigstruct { ECH []byte// Specifically ECHConfigList including the redundant length prefix}func (*SVCBECHConfig) () SVCBKey { returnSVCB_ECHCONFIG }func ( *SVCBECHConfig) () string { returntoBase64(.ECH) }func ( *SVCBECHConfig) () int { returnlen(.ECH) }func ( *SVCBECHConfig) () ([]byte, error) {returncloneSlice(.ECH), nil}func ( *SVCBECHConfig) () SVCBKeyValue {return &SVCBECHConfig{cloneSlice(.ECH)}}func ( *SVCBECHConfig) ( []byte) error { .ECH = cloneSlice()returnnil}func ( *SVCBECHConfig) ( string) error { , := fromBase64([]byte())if != nil {returnerrors.New("dns: svcbech: bad base64 ech") } .ECH = returnnil}// 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)typeSVCBIPv6Hintstruct { Hint []net.IP}func (*SVCBIPv6Hint) () SVCBKey { returnSVCB_IPV6HINT }func ( *SVCBIPv6Hint) () int { return16 * len(.Hint) }func ( *SVCBIPv6Hint) () ([]byte, error) { := make([]byte, 0, 16*len(.Hint))for , := range .Hint {iflen() != net.IPv6len || .To4() != nil {returnnil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4") } = append(, ...) }return , nil}func ( *SVCBIPv6Hint) ( []byte) error {iflen() == 0 || len()%16 != 0 {returnerrors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16") } = cloneSlice() := make([]net.IP, 0, len()/16)for := 0; < len(); += 16 { := net.IP([ : +16])if .To4() != nil {returnerrors.New("dns: svcbipv6hint: expected ipv6, got ipv4") } = append(, ) } .Hint = returnnil}func ( *SVCBIPv6Hint) () string { := make([]string, len(.Hint))for , := range .Hint {if := .To4(); != nil {return"<nil>" } [] = .String() }returnstrings.Join(, ",")}func ( *SVCBIPv6Hint) ( string) error {if == "" {returnerrors.New("dns: svcbipv6hint: empty hint") } := make([]net.IP, 0, strings.Count(, ",")+1)forlen() > 0 {varstring , , _ = strings.Cut(, ",") := net.ParseIP()if == nil {returnerrors.New("dns: svcbipv6hint: bad ip") }if .To4() != nil {returnerrors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6") } = append(, ) } .Hint = returnnil}func ( *SVCBIPv6Hint) () SVCBKeyValue { := make([]net.IP, len(.Hint))for , := range .Hint { [] = cloneSlice() }return &SVCBIPv6Hint{Hint: }}// SVCBDoHPath pair is used to indicate the URI template that the// clients may use to construct a DNS over HTTPS URI.//// See RFC 9461 (https://datatracker.ietf.org/doc/html/rfc9461)// and RFC 9462 (https://datatracker.ietf.org/doc/html/rfc9462).//// 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.typeSVCBDoHPathstruct { Template string}func (*SVCBDoHPath) () SVCBKey { returnSVCB_DOHPATH }func ( *SVCBDoHPath) () string { returnsvcbParamToStr([]byte(.Template)) }func ( *SVCBDoHPath) () int { returnlen(.Template) }func ( *SVCBDoHPath) () ([]byte, error) { return []byte(.Template), nil }func ( *SVCBDoHPath) ( []byte) error { .Template = string()returnnil}func ( *SVCBDoHPath) ( string) error { , := svcbParseParam()if != nil {returnfmt.Errorf("dns: svcbdohpath: %w", ) } .Template = string()returnnil}func ( *SVCBDoHPath) () SVCBKeyValue {return &SVCBDoHPath{Template: .Template, }}// The "ohttp" SvcParamKey is used to indicate that a service described in a SVCB RR// can be accessed as a target using an associated gateway.// Both the presentation and wire-format values for the "ohttp" parameter MUST be empty.//// See RFC 9460 (https://datatracker.ietf.org/doc/html/rfc9460/)// and RFC 9230 (https://datatracker.ietf.org/doc/html/rfc9230/)//// 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.SVCBOhttp)// s.Value = append(s.Value, e, p)typeSVCBOhttpstruct{}func (*SVCBOhttp) () SVCBKey { returnSVCB_OHTTP }func (*SVCBOhttp) () SVCBKeyValue { return &SVCBOhttp{} }func (*SVCBOhttp) () ([]byte, error) { return []byte{}, nil }func (*SVCBOhttp) () string { return"" }func (*SVCBOhttp) () int { return0 }func (*SVCBOhttp) ( []byte) error {iflen() != 0 {returnerrors.New("dns: svcbotthp: svcbotthp must have no value") }returnnil}func (*SVCBOhttp) ( string) error {if != "" {returnerrors.New("dns: svcbotthp: svcbotthp must have no value") }returnnil}// 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)typeSVCBLocalstruct { KeyCode SVCBKey// Never 65535 or any assigned keys. Data []byte// All byte sequences are allowed.}func ( *SVCBLocal) () SVCBKey { return .KeyCode }func ( *SVCBLocal) () string { returnsvcbParamToStr(.Data) }func ( *SVCBLocal) () ([]byte, error) { returncloneSlice(.Data), nil }func ( *SVCBLocal) () int { returnlen(.Data) }func ( *SVCBLocal) ( []byte) error { .Data = cloneSlice()returnnil}func ( *SVCBLocal) ( string) error { , := svcbParseParam()if != nil {returnfmt.Errorf("dns: svcblocal: svcb private/experimental key %w", ) } .Data = returnnil}func ( *SVCBLocal) () SVCBKeyValue {return &SVCBLocal{.KeyCode, cloneSlice(.Data)}}func ( *SVCB) () string { := .Hdr.String() +strconv.Itoa(int(.Priority)) + " " +sprintName(.Target)for , := range .Value { += " " + .Key().String() + "=\"" + .String() + "\"" }return}// 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( []SVCBKeyValue, []SVCBKeyValue) bool { = cloneSlice() = cloneSlice()sort.Slice(, func(, int) bool { return [].Key() < [].Key() })sort.Slice(, func(, int) bool { return [].Key() < [].Key() })for , := range {if .Key() != [].Key() {returnfalse } , := .pack() , := [].pack()if != nil || != nil || !bytes.Equal(, ) {returnfalse } }returntrue}// svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.func svcbParamToStr( []byte) string {varstrings.Builder .Grow(4 * len())for , := range {if' ' <= && <= '~' {switch {case'"', ';', ' ', '\\': .WriteByte('\\') .WriteByte()default: .WriteByte() } } else { .WriteString(escapeByte()) } }return .String()}// svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.func svcbParseParam( string) ([]byte, error) { := make([]byte, 0, len())for := 0; < len(); {if [] != '\\' { = append(, []) ++continue }if +1 == len() {returnnil, errors.New("escape unterminated") }ifisDigit([+1]) {if +3 < len() && isDigit([+2]) && isDigit([+3]) { , := strconv.ParseUint([+1:+4], 10, 8)if == nil { += 4 = append(, byte())continue } }returnnil, errors.New("bad escaped octet") } else { = append(, [+1]) += 2 } }return , nil}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.