svcb.go raw

   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