analyzer: make http3/quic handling more reliable
Some checks failed
Quality check / Static analysis (push) Has been cancelled
Quality check / Tests (push) Has been cancelled

This commit is contained in:
2026-02-11 15:49:32 +05:30
parent c3fe0ea16f
commit 43cb4755d0
4 changed files with 59 additions and 22 deletions

View File

@@ -9,6 +9,8 @@ import (
"github.com/quic-go/quic-go/quicvarint"
)
var ErrNotInitialPacket = errors.New("not initial packet")
// The Header represents a QUIC header.
type Header struct {
Type uint8
@@ -36,6 +38,9 @@ func parseLongHeader(b *bytes.Reader) (*Header, error) {
if err != nil {
return nil, err
}
if !isLongHeader(typeByte) {
return nil, ErrNotInitialPacket
}
h := &Header{}
ver, err := beUint32(b)
if err != nil {
@@ -66,7 +71,9 @@ func parseLongHeader(b *bytes.Reader) (*Header, error) {
if h.Version == V2 {
initialPacketType = 0b01
}
if (typeByte >> 4 & 0b11) == initialPacketType {
if (typeByte>>4)&0b11 != initialPacketType {
return nil, ErrNotInitialPacket
}
tokenLen, err := quicvarint.Read(b)
if err != nil {
return nil, err
@@ -78,7 +85,6 @@ func parseLongHeader(b *bytes.Reader) (*Header, error) {
if _, err := io.ReadFull(b, h.Token); err != nil {
return nil, err
}
}
pl, err := quicvarint.Read(b)
if err != nil {

View File

@@ -51,16 +51,28 @@ func ReadCryptoFrames(packet []byte) ([]CryptoFrame, error) {
if int64(len(packet)) < offset+hdr.Length {
return nil, fmt.Errorf("packet is too short: %d < %d", len(packet), offset+hdr.Length)
}
unProtectedPayload, err := pp.UnProtect(packet[:offset+hdr.Length], offset, 2)
packetView := packet[:offset+hdr.Length]
pnMaxGuesses := []int64{0, 1, 2, 3, 4, 8, 16}
var lastErr error
for _, pnMax := range pnMaxGuesses {
packetCopy := append([]byte(nil), packetView...)
unProtectedPayload, err := pp.UnProtect(packetCopy, offset, pnMax)
if err != nil {
return nil, err
lastErr = err
continue
}
frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload))
if err != nil {
return nil, err
lastErr = err
continue
}
return frs, nil
}
if lastErr != nil {
return nil, lastErr
}
return nil, errors.New("unable to decrypt initial packet")
}
const (
paddingFrameType = 0x00

View File

@@ -55,3 +55,14 @@ func TestExtractCryptoFrames_UnknownAfterCrypto(t *testing.T) {
t.Fatalf("frame0 = %+v, want offset=0 data=abc", frames[0])
}
}
func TestReadCryptoFrames_NonInitialHeader(t *testing.T) {
// Short header packet marker should be rejected as non-initial.
_, err := ReadCryptoFrames([]byte{0x40, 0x01, 0x02, 0x03, 0x04})
if err == nil {
t.Fatal("ReadCryptoFrames() error = nil, want non-nil")
}
if err.Error() != ErrNotInitialPacket.Error() {
t.Fatalf("ReadCryptoFrames() error = %v, want %v", err, ErrNotInitialPacket)
}
}

View File

@@ -1,6 +1,7 @@
package udp
import (
"errors"
"sort"
"git.difuse.io/Difuse/Mellaris/analyzer"
@@ -50,7 +51,14 @@ func (s *quicStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done b
const minDataSize = 41
frs, err := quic.ReadCryptoFrames(data)
if err != nil || len(frs) == 0 {
if err != nil {
if errors.Is(err, quic.ErrNotInitialPacket) {
return nil, false
}
s.invalidCount++
return nil, s.invalidCount >= quicInvalidCountThreshold
}
if len(frs) == 0 {
s.invalidCount++
return nil, s.invalidCount >= quicInvalidCountThreshold
}
@@ -64,8 +72,8 @@ func (s *quicStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done b
}
if pl[0] != internal.TypeClientHello {
s.invalidCount++
return nil, s.invalidCount >= quicInvalidCountThreshold
// Not a ClientHello (e.g. server-direction CRYPTO); ignore.
return nil, false
}
chLen := int(pl[1])<<16 | int(pl[2])<<8 | int(pl[3])