Skip to content

Commit

Permalink
quic: transport parameter encoding and decoding
Browse files Browse the repository at this point in the history
Transport parameters are passed in the extension_data field of the
quic_transport_parameters TLS extension.

RFC 9000, Section 18.
RFC 9001, Section 8.2.

For golang/go#58547

Change-Id: I294ab6cdef19256f5db02dc269e8b417b1d5e54b
Reviewed-on: https://go-review.googlesource.com/c/net/+/510575
Auto-Submit: Damien Neil <[email protected]>
Reviewed-by: Jonathan Amsterdam <[email protected]>
Run-TryBot: Damien Neil <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
neild authored and gopherbot committed Jul 18, 2023
1 parent 0adcadf commit 8db2ead
Show file tree
Hide file tree
Showing 2 changed files with 651 additions and 0 deletions.
277 changes: 277 additions & 0 deletions internal/quic/transport_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.21

package quic

import (
"encoding/binary"
"net/netip"
"time"
)

// transportParameters transferred in the quic_transport_parameters TLS extension.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2
type transportParameters struct {
originalDstConnID []byte
maxIdleTimeout time.Duration
statelessResetToken []byte
maxUDPPayloadSize int64
initialMaxData int64
initialMaxStreamDataBidiLocal int64
initialMaxStreamDataBidiRemote int64
initialMaxStreamDataUni int64
initialMaxStreamsBidi int64
initialMaxStreamsUni int64
ackDelayExponent uint8
maxAckDelay time.Duration
disableActiveMigration bool
preferredAddrV4 netip.AddrPort
preferredAddrV6 netip.AddrPort
preferredAddrConnID []byte
preferredAddrResetToken []byte
activeConnIDLimit int64
initialSrcConnID []byte
retrySrcConnID []byte
}

const (
defaultParamMaxUDPPayloadSize = 65527
defaultParamAckDelayExponent = 3
defaultParamMaxAckDelayMilliseconds = 25
defaultParamActiveConnIDLimit = 2
)

// defaultTransportParameters is initialized to the RFC 9000 default values.
func defaultTransportParameters() transportParameters {
return transportParameters{
maxUDPPayloadSize: defaultParamMaxUDPPayloadSize,
ackDelayExponent: defaultParamAckDelayExponent,
maxAckDelay: defaultParamMaxAckDelayMilliseconds * time.Millisecond,
activeConnIDLimit: defaultParamActiveConnIDLimit,
}
}

const (
paramOriginalDestinationConnectionID = 0x00
paramMaxIdleTimeout = 0x01
paramStatelessResetToken = 0x02
paramMaxUDPPayloadSize = 0x03
paramInitialMaxData = 0x04
paramInitialMaxStreamDataBidiLocal = 0x05
paramInitialMaxStreamDataBidiRemote = 0x06
paramInitialMaxStreamDataUni = 0x07
paramInitialMaxStreamsBidi = 0x08
paramInitialMaxStreamsUni = 0x09
paramAckDelayExponent = 0x0a
paramMaxAckDelay = 0x0b
paramDisableActiveMigration = 0x0c
paramPreferredAddress = 0x0d
paramActiveConnectionIDLimit = 0x0e
paramInitialSourceConnectionID = 0x0f
paramRetrySourceConnectionID = 0x10
)

func marshalTransportParameters(p transportParameters) []byte {
var b []byte
if v := p.originalDstConnID; v != nil {
b = appendVarint(b, paramOriginalDestinationConnectionID)
b = appendVarintBytes(b, v)
}
if v := uint64(p.maxIdleTimeout / time.Millisecond); v != 0 {
b = appendVarint(b, paramMaxIdleTimeout)
b = appendVarint(b, uint64(sizeVarint(v)))
b = appendVarint(b, uint64(v))
}
if v := p.statelessResetToken; v != nil {
b = appendVarint(b, paramStatelessResetToken)
b = appendVarintBytes(b, v)
}
if v := p.maxUDPPayloadSize; v != defaultParamMaxUDPPayloadSize {
b = appendVarint(b, paramMaxUDPPayloadSize)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxData; v != 0 {
b = appendVarint(b, paramInitialMaxData)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataBidiLocal; v != 0 {
b = appendVarint(b, paramInitialMaxStreamDataBidiLocal)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataBidiRemote; v != 0 {
b = appendVarint(b, paramInitialMaxStreamDataBidiRemote)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxStreamDataUni; v != 0 {
b = appendVarint(b, paramInitialMaxStreamDataUni)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxStreamsBidi; v != 0 {
b = appendVarint(b, paramInitialMaxStreamsBidi)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialMaxStreamsUni; v != 0 {
b = appendVarint(b, paramInitialMaxStreamsUni)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.ackDelayExponent; v != defaultParamAckDelayExponent {
b = appendVarint(b, paramAckDelayExponent)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := uint64(p.maxAckDelay / time.Millisecond); v != defaultParamMaxAckDelayMilliseconds {
b = appendVarint(b, paramMaxAckDelay)
b = appendVarint(b, uint64(sizeVarint(v)))
b = appendVarint(b, v)
}
if p.disableActiveMigration {
b = appendVarint(b, paramDisableActiveMigration)
b = append(b, 0) // 0-length value
}
if p.preferredAddrConnID != nil {
b = append(b, paramPreferredAddress)
b = appendVarint(b, uint64(4+2+16+2+1+len(p.preferredAddrConnID)+16))
b = append(b, p.preferredAddrV4.Addr().AsSlice()...) // 4 bytes
b = binary.BigEndian.AppendUint16(b, p.preferredAddrV4.Port()) // 2 bytes
b = append(b, p.preferredAddrV6.Addr().AsSlice()...) // 16 bytes
b = binary.BigEndian.AppendUint16(b, p.preferredAddrV6.Port()) // 2 bytes
b = appendUint8Bytes(b, p.preferredAddrConnID) // 1 byte + len(conn_id)
b = append(b, p.preferredAddrResetToken...) // 16 bytes
}
if v := p.activeConnIDLimit; v != defaultParamActiveConnIDLimit {
b = appendVarint(b, paramActiveConnectionIDLimit)
b = appendVarint(b, uint64(sizeVarint(uint64(v))))
b = appendVarint(b, uint64(v))
}
if v := p.initialSrcConnID; v != nil {
b = appendVarint(b, paramInitialSourceConnectionID)
b = appendVarintBytes(b, v)
}
if v := p.retrySrcConnID; v != nil {
b = appendVarint(b, paramRetrySourceConnectionID)
b = appendVarintBytes(b, v)
}
return b
}

func unmarshalTransportParams(params []byte) (transportParameters, error) {
p := defaultTransportParameters()
for len(params) > 0 {
id, n := consumeVarint(params)
if n < 0 {
return p, localTransportError(errTransportParameter)
}
params = params[n:]
val, n := consumeVarintBytes(params)
if n < 0 {
return p, localTransportError(errTransportParameter)
}
params = params[n:]
n = 0
switch id {
case paramOriginalDestinationConnectionID:
p.originalDstConnID = val
n = len(val)
case paramMaxIdleTimeout:
var v uint64
v, n = consumeVarint(val)
// If this is unreasonably large, consider it as no timeout to avoid
// time.Duration overflows.
if v > 1<<32 {
v = 0
}
p.maxIdleTimeout = time.Duration(v) * time.Millisecond
case paramStatelessResetToken:
if len(val) != 16 {
return p, localTransportError(errTransportParameter)
}
p.statelessResetToken = val
n = 16
case paramMaxUDPPayloadSize:
p.maxUDPPayloadSize, n = consumeVarintInt64(val)
if p.maxUDPPayloadSize < 1200 {
return p, localTransportError(errTransportParameter)
}
case paramInitialMaxData:
p.initialMaxData, n = consumeVarintInt64(val)
case paramInitialMaxStreamDataBidiLocal:
p.initialMaxStreamDataBidiLocal, n = consumeVarintInt64(val)
case paramInitialMaxStreamDataBidiRemote:
p.initialMaxStreamDataBidiRemote, n = consumeVarintInt64(val)
case paramInitialMaxStreamDataUni:
p.initialMaxStreamDataUni, n = consumeVarintInt64(val)
case paramInitialMaxStreamsBidi:
p.initialMaxStreamsBidi, n = consumeVarintInt64(val)
case paramInitialMaxStreamsUni:
p.initialMaxStreamsUni, n = consumeVarintInt64(val)
case paramAckDelayExponent:
var v uint64
v, n = consumeVarint(val)
if v > 20 {
return p, localTransportError(errTransportParameter)
}
p.ackDelayExponent = uint8(v)
case paramMaxAckDelay:
var v uint64
v, n = consumeVarint(val)
if v >= 1<<14 {
return p, localTransportError(errTransportParameter)
}
p.maxAckDelay = time.Duration(v) * time.Millisecond
case paramDisableActiveMigration:
p.disableActiveMigration = true
case paramPreferredAddress:
if len(val) < 4+2+16+2+1 {
return p, localTransportError(errTransportParameter)
}
p.preferredAddrV4 = netip.AddrPortFrom(
netip.AddrFrom4(*(*[4]byte)(val[:4])),
binary.BigEndian.Uint16(val[4:][:2]),
)
val = val[4+2:]
p.preferredAddrV6 = netip.AddrPortFrom(
netip.AddrFrom16(*(*[16]byte)(val[:16])),
binary.BigEndian.Uint16(val[16:][:2]),
)
val = val[16+2:]
var nn int
p.preferredAddrConnID, nn = consumeUint8Bytes(val)
if nn < 0 {
return p, localTransportError(errTransportParameter)
}
val = val[nn:]
if len(val) != 16 {
return p, localTransportError(errTransportParameter)
}
p.preferredAddrResetToken = val
val = nil
case paramActiveConnectionIDLimit:
p.activeConnIDLimit, n = consumeVarintInt64(val)
if p.activeConnIDLimit < 2 {
return p, localTransportError(errTransportParameter)
}
case paramInitialSourceConnectionID:
p.initialSrcConnID = val
n = len(val)
case paramRetrySourceConnectionID:
p.retrySrcConnID = val
n = len(val)
default:
n = len(val)
}
if n != len(val) {
return p, localTransportError(errTransportParameter)
}
}
return p, nil
}
Loading

0 comments on commit 8db2ead

Please sign in to comment.