Sniffing: Support QUICv2 (#6695)

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
This commit is contained in:
Kosta
2026-08-30 12:15:25 +00:00
committed by GitHub
co-authored by 风扇滑翔翼
parent c1958dba04
commit d9c54026c5
3 changed files with 98 additions and 82 deletions
-8
View File
@@ -1,18 +1,10 @@
package quic package quic
import ( import (
"crypto"
"crypto/cipher" "crypto/cipher"
_ "crypto/tls" _ "crypto/tls"
_ "unsafe" _ "unsafe"
) )
type CipherSuiteTLS13 struct {
ID uint16
KeyLen int
AEAD func(key, fixedNonce []byte) cipher.AEAD
Hash crypto.Hash
}
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13 //go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
+89 -74
View File
@@ -3,7 +3,6 @@ package quic
import ( import (
"crypto" "crypto"
"crypto/aes" "crypto/aes"
"crypto/tls"
"encoding/binary" "encoding/binary"
"io" "io"
@@ -28,22 +27,43 @@ func (s SniffHeader) Domain() string {
return s.domain return s.domain
} }
const ( var (
versionDraft29 uint32 = 0xff00001d errNotQUIC = errors.New("not quic")
version1 uint32 = 0x1 errNotQUICInitial = errors.New("not initial packet")
) )
type quicVersionSpec struct {
ver uint32
typeInitial byte
initialSalt []byte
labelPrefix string
}
var ( var (
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99} quicDraft29 = quicVersionSpec{
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a} ver: 0xff00001d,
initialSuite = &CipherSuiteTLS13{ typeInitial: 0b00,
ID: tls.TLS_AES_128_GCM_SHA256, initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
KeyLen: 16, labelPrefix: "quic",
AEAD: AEADAESGCMTLS13, }
Hash: crypto.SHA256, quicV1 = quicVersionSpec{
ver: 0x1,
typeInitial: 0b00,
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
labelPrefix: "quic",
}
quicV2 = quicVersionSpec{
ver: 0x6b3343cf,
typeInitial: 0b01,
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
labelPrefix: "quicv2",
}
quicVersionSpecMap = map[uint32]*quicVersionSpec{
quicDraft29.ver: &quicDraft29,
quicV1.ver: &quicV1,
quicV2.ver: &quicV2,
} }
errNotQuic = errors.New("not quic")
errNotQuicInitial = errors.New("not initial packet")
) )
func SniffQUIC(b []byte) (*SniffHeader, error) { func SniffQUIC(b []byte) (*SniffHeader, error) {
@@ -63,60 +83,61 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
buffer := buf.FromBytes(b) buffer := buf.FromBytes(b)
typeByte, err := buffer.ReadByte() typeByte, err := buffer.ReadByte()
if err != nil { if err != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
isLongHeader := typeByte&0x80 > 0 isLongHeader := typeByte&0x80 > 0
if !isLongHeader || typeByte&0x40 == 0 { if !isLongHeader || typeByte&0x40 == 0 {
return nil, errNotQuicInitial return nil, errNotQUICInitial
} }
vb, err := buffer.ReadBytes(4) vb, err := buffer.ReadBytes(4)
if err != nil { if err != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
versionNumber := binary.BigEndian.Uint32(vb) versionNumber := binary.BigEndian.Uint32(vb)
if versionNumber != 0 && typeByte&0x40 == 0 { var s *quicVersionSpec
return nil, errNotQuic if v, ok := quicVersionSpecMap[versionNumber]; ok {
} else if versionNumber != versionDraft29 && versionNumber != version1 { s = v
return nil, errNotQuic } else {
return nil, errNotQUIC
} }
packetType := (typeByte & 0x30) >> 4
isQuicInitial := packetType == 0x0
var destConnID []byte var destConnID []byte
if l, err := buffer.ReadByte(); err != nil { if l, err := buffer.ReadByte(); err != nil {
return nil, errNotQuic return nil, errNotQUIC
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil { } else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
if l, err := buffer.ReadByte(); err != nil { if l, err := buffer.ReadByte(); err != nil {
return nil, errNotQuic return nil, errNotQUIC
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil { } else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2 packetType := (typeByte & 0x30) >> 4
tokenLen, err := readShortQuicVarint(buffer) isQUICInitial := packetType == s.typeInitial
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
tokenLen, err := readShortQUICVarint(buffer)
if err != nil || tokenLen > int32(len(b)) { if err != nil || tokenLen > int32(len(b)) {
return nil, errNotQuic return nil, errNotQUIC
} }
if _, err = buffer.ReadBytes(tokenLen); err != nil { if _, err = buffer.ReadBytes(tokenLen); err != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
} }
packetLen, err := readShortQuicVarint(buffer) packetLen, err := readShortQUICVarint(buffer)
if err != nil { if err != nil {
return nil, errNotQuic return nil, errNotQUIC
} }
// packetLen is impossible to be shorter than this // packetLen is impossible to be shorter than this
if packetLen < 4 { if packetLen < 4 {
return nil, errNotQuic return nil, errNotQUIC
} }
hdrLen := len(b) - int(buffer.Len()) hdrLen := len(b) - int(buffer.Len())
@@ -125,26 +146,22 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
} }
restPayload := b[hdrLen+int(packetLen):] restPayload := b[hdrLen+int(packetLen):]
if !isQuicInitial { // Skip this packet if it's not initial packet if !isQUICInitial { // Skip this packet if it's not initial packet
b = restPayload b = restPayload
continue continue
} }
var salt []byte salt := s.initialSalt
if versionNumber == version1 { label := s.labelPrefix
salt = quicSalt
} else {
salt = quicSaltOld
}
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt) initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size()) secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen) hpKey := hkdfExpandLabel(secret, label+" hp", 16)
block, err := aes.NewCipher(hpKey) block, err := aes.NewCipher(hpKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(b) < hdrLen+4+block.BlockSize() { if len(b) < hdrLen+4+block.BlockSize() {
return nil, errNotQuic return nil, errNotQUIC
} }
cache.Clear() cache.Clear()
mask := cache.Extend(int32(block.BlockSize())) mask := cache.Extend(int32(block.BlockSize()))
@@ -155,8 +172,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
b[hdrLen+i] ^= mask[i+1] b[hdrLen+i] ^= mask[i+1]
} }
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16) key := hkdfExpandLabel(secret, label+" key", 16)
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) iv := hkdfExpandLabel(secret, label+" iv", 12)
cipher := AEADAESGCMTLS13(key, iv) cipher := AEADAESGCMTLS13(key, iv)
nonce := cache.Extend(int32(cipher.NonceSize())) nonce := cache.Extend(int32(cipher.NonceSize()))
@@ -181,44 +198,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
case 0x00: // PADDING frame case 0x00: // PADDING frame
case 0x01: // PING frame case 0x01: // PING frame
case 0x02, 0x03: // ACK frame case 0x02, 0x03: // ACK frame
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
if err != nil { if err != nil {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
} }
if frameType == 0x03 { if frameType == 0x03 {
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
} }
case 0x06: // CRYPTO frame, we will use this frame case 0x06: // CRYPTO frame, we will use this frame
offset, err := readShortQuicVarint(buffer) // Field: Offset offset, err := readShortQUICVarint(buffer) // Field: Offset
if err != nil { if err != nil {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
length, err := readShortQuicVarint(buffer) // Field: Length length, err := readShortQUICVarint(buffer) // Field: Length
if err != nil || length > buffer.Len() { if err != nil || length > buffer.Len() {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
@@ -234,13 +251,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
if err != nil { if err != nil {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
@@ -250,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
default: default:
// Only above frame types are permitted in initial packet. // Only above frame types are permitted in initial packet.
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
return nil, errNotQuicInitial return nil, errNotQUICInitial
} }
} }
@@ -268,35 +285,33 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
return nil, protocol.ErrProtoNeedMoreData return nil, protocol.ErrProtoNeedMoreData
} }
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { func hkdfExpandLabel(secret []byte, label string, length int) []byte {
b := make([]byte, 3, 3+6+len(label)+1+len(context)) b := make([]byte, 0, 2+1+6+len(label)+1)
binary.BigEndian.PutUint16(b, uint16(length)) b = binary.BigEndian.AppendUint16(b, uint16(length))
b[2] = uint8(6 + len(label)) b = append(b, byte(6+len(label)))
b = append(b, []byte("tls13 ")...) b = append(b, "tls13 "...)
b = append(b, []byte(label)...) b = append(b, label...)
b = b[:3+6+len(label)+1] b = append(b, 0) // context
b[3+6+len(label)] = uint8(len(context))
b = append(b, context...)
out := make([]byte, length) out := make([]byte, length)
n, err := hkdf.Expand(hash.New, secret, b).Read(out) n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
if err != nil || n != length { if err != nil || n != length {
panic("quic: HKDF-Expand-Label invocation failed unexpectedly") panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
} }
return out return out
} }
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields. // readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
// we only handle QUIC Initial so these numbers should not exceed 65535 // we only handle QUIC Initial so these numbers should not exceed 65535
// returns int32 to reduce type conversion // returns int32 to reduce type conversion
func readShortQuicVarint(reader io.ByteReader) (int32, error) { func readShortQUICVarint(reader io.ByteReader) (int32, error) {
v, err := quicvarint.Read(reader) v, err := quicvarint.Read(reader)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if v > 65535 { if v > 65535 {
// not used( // not used(
return 0, errNotQuicInitial return 0, errNotQUICInitial
} }
return int32(v), nil return int32(v), nil
} }
File diff suppressed because one or more lines are too long