Source file src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package dnsmessage
     6  
     7  import (
     8  	"slices"
     9  )
    10  
    11  // An SVCBResource is an SVCB Resource record.
    12  type SVCBResource struct {
    13  	Priority uint16
    14  	Target   Name
    15  	Params   []SVCParam // Must be in strict increasing order by Key.
    16  }
    17  
    18  func (r *SVCBResource) realType() Type {
    19  	return TypeSVCB
    20  }
    21  
    22  // GoString implements fmt.GoStringer.GoString.
    23  func (r *SVCBResource) GoString() string {
    24  	b := []byte("dnsmessage.SVCBResource{" +
    25  		"Priority: " + printUint16(r.Priority) + ", " +
    26  		"Target: " + r.Target.GoString() + ", " +
    27  		"Params: []dnsmessage.SVCParam{")
    28  	if len(r.Params) > 0 {
    29  		b = append(b, r.Params[0].GoString()...)
    30  		for _, p := range r.Params[1:] {
    31  			b = append(b, ", "+p.GoString()...)
    32  		}
    33  	}
    34  	b = append(b, "}}"...)
    35  	return string(b)
    36  }
    37  
    38  // An HTTPSResource is an HTTPS Resource record.
    39  // It has the same format as the SVCB record.
    40  type HTTPSResource struct {
    41  	// Alias for SVCB resource record.
    42  	SVCBResource
    43  }
    44  
    45  func (r *HTTPSResource) realType() Type {
    46  	return TypeHTTPS
    47  }
    48  
    49  // GoString implements fmt.GoStringer.GoString.
    50  func (r *HTTPSResource) GoString() string {
    51  	return "dnsmessage.HTTPSResource{SVCBResource: " + r.SVCBResource.GoString() + "}"
    52  }
    53  
    54  // GetParam returns a parameter value by key.
    55  func (r *SVCBResource) GetParam(key SVCParamKey) (value []byte, ok bool) {
    56  	for i := range r.Params {
    57  		if r.Params[i].Key == key {
    58  			return r.Params[i].Value, true
    59  		}
    60  		if r.Params[i].Key > key {
    61  			break
    62  		}
    63  	}
    64  	return nil, false
    65  }
    66  
    67  // SetParam sets a parameter value by key.
    68  // The Params list is kept sorted by key.
    69  func (r *SVCBResource) SetParam(key SVCParamKey, value []byte) {
    70  	i := 0
    71  	for i < len(r.Params) {
    72  		if r.Params[i].Key >= key {
    73  			break
    74  		}
    75  		i++
    76  	}
    77  
    78  	if i < len(r.Params) && r.Params[i].Key == key {
    79  		r.Params[i].Value = value
    80  		return
    81  	}
    82  
    83  	r.Params = slices.Insert(r.Params, i, SVCParam{Key: key, Value: value})
    84  }
    85  
    86  // DeleteParam deletes a parameter by key.
    87  // It returns true if the parameter was present.
    88  func (r *SVCBResource) DeleteParam(key SVCParamKey) bool {
    89  	for i := range r.Params {
    90  		if r.Params[i].Key == key {
    91  			r.Params = slices.Delete(r.Params, i, i+1)
    92  			return true
    93  		}
    94  		if r.Params[i].Key > key {
    95  			break
    96  		}
    97  	}
    98  	return false
    99  }
   100  
   101  // A SVCParam is a service parameter.
   102  type SVCParam struct {
   103  	Key   SVCParamKey
   104  	Value []byte
   105  }
   106  
   107  // GoString implements fmt.GoStringer.GoString.
   108  func (p SVCParam) GoString() string {
   109  	return "dnsmessage.SVCParam{" +
   110  		"Key: " + p.Key.GoString() + ", " +
   111  		"Value: []byte{" + printByteSlice(p.Value) + "}}"
   112  }
   113  
   114  // A SVCParamKey is a key for a service parameter.
   115  type SVCParamKey uint16
   116  
   117  // Values defined at https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys.
   118  const (
   119  	SVCParamMandatory          SVCParamKey = 0
   120  	SVCParamALPN               SVCParamKey = 1
   121  	SVCParamNoDefaultALPN      SVCParamKey = 2
   122  	SVCParamPort               SVCParamKey = 3
   123  	SVCParamIPv4Hint           SVCParamKey = 4
   124  	SVCParamECH                SVCParamKey = 5
   125  	SVCParamIPv6Hint           SVCParamKey = 6
   126  	SVCParamDOHPath            SVCParamKey = 7
   127  	SVCParamOHTTP              SVCParamKey = 8
   128  	SVCParamTLSSupportedGroups SVCParamKey = 9
   129  )
   130  
   131  var svcParamKeyNames = map[SVCParamKey]string{
   132  	SVCParamMandatory:          "Mandatory",
   133  	SVCParamALPN:               "ALPN",
   134  	SVCParamNoDefaultALPN:      "NoDefaultALPN",
   135  	SVCParamPort:               "Port",
   136  	SVCParamIPv4Hint:           "IPv4Hint",
   137  	SVCParamECH:                "ECH",
   138  	SVCParamIPv6Hint:           "IPv6Hint",
   139  	SVCParamDOHPath:            "DOHPath",
   140  	SVCParamOHTTP:              "OHTTP",
   141  	SVCParamTLSSupportedGroups: "TLSSupportedGroups",
   142  }
   143  
   144  // String implements fmt.Stringer.String.
   145  func (k SVCParamKey) String() string {
   146  	if n, ok := svcParamKeyNames[k]; ok {
   147  		return n
   148  	}
   149  	return printUint16(uint16(k))
   150  }
   151  
   152  // GoString implements fmt.GoStringer.GoString.
   153  func (k SVCParamKey) GoString() string {
   154  	if n, ok := svcParamKeyNames[k]; ok {
   155  		return "dnsmessage.SVCParam" + n
   156  	}
   157  	return printUint16(uint16(k))
   158  }
   159  
   160  func (r *SVCBResource) pack(msg []byte, _ map[string]uint16, _ int) ([]byte, error) {
   161  	oldMsg := msg
   162  	msg = packUint16(msg, r.Priority)
   163  	// https://datatracker.ietf.org/doc/html/rfc3597#section-4 prohibits name
   164  	// compression for RR types that are not "well-known".
   165  	// https://datatracker.ietf.org/doc/html/rfc9460#section-2.2 explicitly states that
   166  	// compression of the Target is prohibited, following RFC 3597.
   167  	msg, err := r.Target.pack(msg, nil, 0)
   168  	if err != nil {
   169  		return oldMsg, &nestedError{"SVCBResource.Target", err}
   170  	}
   171  	var previousKey SVCParamKey
   172  	for i, param := range r.Params {
   173  		if i > 0 && param.Key <= previousKey {
   174  			return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder}
   175  		}
   176  		if len(param.Value) > (1<<16)-1 {
   177  			return oldMsg, &nestedError{"SVCBResource.Params", errTooLongSVCBValue}
   178  		}
   179  		msg = packUint16(msg, uint16(param.Key))
   180  		msg = packUint16(msg, uint16(len(param.Value)))
   181  		msg = append(msg, param.Value...)
   182  	}
   183  	return msg, nil
   184  }
   185  
   186  func unpackSVCBResource(msg []byte, off int, length uint16) (SVCBResource, error) {
   187  	// Wire format reference: https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2.
   188  	r := SVCBResource{}
   189  	paramsOff := off
   190  	bodyEnd := off + int(length)
   191  
   192  	var err error
   193  	if r.Priority, paramsOff, err = unpackUint16(msg, paramsOff); err != nil {
   194  		return SVCBResource{}, &nestedError{"Priority", err}
   195  	}
   196  
   197  	if paramsOff, err = r.Target.unpack(msg, paramsOff); err != nil {
   198  		return SVCBResource{}, &nestedError{"Target", err}
   199  	}
   200  
   201  	// Two-pass parsing to avoid allocations.
   202  	// First, count the number of params.
   203  	n := 0
   204  	var totalValueLen uint16
   205  	off = paramsOff
   206  	var previousKey uint16
   207  	for off < bodyEnd {
   208  		var key, len uint16
   209  		if key, off, err = unpackUint16(msg, off); err != nil {
   210  			return SVCBResource{}, &nestedError{"Params key", err}
   211  		}
   212  		if n > 0 && key <= previousKey {
   213  			// As per https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2, clients MUST
   214  			// consider the RR malformed if the SvcParamKeys are not in strictly increasing numeric order
   215  			return SVCBResource{}, &nestedError{"Params", errParamOutOfOrder}
   216  		}
   217  		if len, off, err = unpackUint16(msg, off); err != nil {
   218  			return SVCBResource{}, &nestedError{"Params value length", err}
   219  		}
   220  		if off+int(len) > bodyEnd {
   221  			return SVCBResource{}, errResourceLen
   222  		}
   223  		totalValueLen += len
   224  		off += int(len)
   225  		n++
   226  	}
   227  	if off != bodyEnd {
   228  		return SVCBResource{}, errResourceLen
   229  	}
   230  
   231  	// Second, fill in the params.
   232  	r.Params = make([]SVCParam, n)
   233  	// valuesBuf is used to hold all param values to reduce allocations.
   234  	// Each param's Value slice will point into this buffer.
   235  	valuesBuf := make([]byte, totalValueLen)
   236  	off = paramsOff
   237  	for i := 0; i < n; i++ {
   238  		p := &r.Params[i]
   239  		var key, len uint16
   240  		if key, off, err = unpackUint16(msg, off); err != nil {
   241  			return SVCBResource{}, &nestedError{"param key", err}
   242  		}
   243  		p.Key = SVCParamKey(key)
   244  		if len, off, err = unpackUint16(msg, off); err != nil {
   245  			return SVCBResource{}, &nestedError{"param length", err}
   246  		}
   247  		if copy(valuesBuf, msg[off:off+int(len)]) != int(len) {
   248  			return SVCBResource{}, &nestedError{"param value", errCalcLen}
   249  		}
   250  		p.Value = valuesBuf[:len:len]
   251  		valuesBuf = valuesBuf[len:]
   252  		off += int(len)
   253  	}
   254  
   255  	return r, nil
   256  }
   257  
   258  // genericSVCBResource parses a single Resource Record compatible with SVCB.
   259  func (p *Parser) genericSVCBResource(svcbType Type) (SVCBResource, error) {
   260  	if !p.resHeaderValid || p.resHeaderType != svcbType {
   261  		return SVCBResource{}, ErrNotStarted
   262  	}
   263  	r, err := unpackSVCBResource(p.msg, p.off, p.resHeaderLength)
   264  	if err != nil {
   265  		return SVCBResource{}, err
   266  	}
   267  	p.off += int(p.resHeaderLength)
   268  	p.resHeaderValid = false
   269  	p.index++
   270  	return r, nil
   271  }
   272  
   273  // SVCBResource parses a single SVCBResource.
   274  //
   275  // One of the XXXHeader methods must have been called before calling this
   276  // method.
   277  func (p *Parser) SVCBResource() (SVCBResource, error) {
   278  	return p.genericSVCBResource(TypeSVCB)
   279  }
   280  
   281  // HTTPSResource parses a single HTTPSResource.
   282  //
   283  // One of the XXXHeader methods must have been called before calling this
   284  // method.
   285  func (p *Parser) HTTPSResource() (HTTPSResource, error) {
   286  	svcb, err := p.genericSVCBResource(TypeHTTPS)
   287  	if err != nil {
   288  		return HTTPSResource{}, err
   289  	}
   290  	return HTTPSResource{svcb}, nil
   291  }
   292  
   293  // genericSVCBResource is the generic implementation for adding SVCB-like resources.
   294  func (b *Builder) genericSVCBResource(h ResourceHeader, r SVCBResource) error {
   295  	if err := b.checkResourceSection(); err != nil {
   296  		return err
   297  	}
   298  	msg, lenOff, err := h.pack(b.msg, b.compression, b.start)
   299  	if err != nil {
   300  		return &nestedError{"ResourceHeader", err}
   301  	}
   302  	preLen := len(msg)
   303  	if msg, err = r.pack(msg, b.compression, b.start); err != nil {
   304  		return &nestedError{"ResourceBody", err}
   305  	}
   306  	if err := h.fixLen(msg, lenOff, preLen); err != nil {
   307  		return err
   308  	}
   309  	if err := b.incrementSectionCount(); err != nil {
   310  		return err
   311  	}
   312  	b.msg = msg
   313  	return nil
   314  }
   315  
   316  // SVCBResource adds a single SVCBResource.
   317  func (b *Builder) SVCBResource(h ResourceHeader, r SVCBResource) error {
   318  	h.Type = r.realType()
   319  	return b.genericSVCBResource(h, r)
   320  }
   321  
   322  // HTTPSResource adds a single HTTPSResource.
   323  func (b *Builder) HTTPSResource(h ResourceHeader, r HTTPSResource) error {
   324  	h.Type = r.realType()
   325  	return b.genericSVCBResource(h, r.SVCBResource)
   326  }
   327  

View as plain text