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

View File

@@ -51,15 +51,27 @@ func ReadCryptoFrames(packet []byte) ([]CryptoFrame, error) {
if int64(len(packet)) < offset+hdr.Length { if int64(len(packet)) < offset+hdr.Length {
return nil, fmt.Errorf("packet is too short: %d < %d", 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 { if err != nil {
return nil, err lastErr = err
continue
} }
frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload)) frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload))
if err != nil { if err != nil {
return nil, err lastErr = err
continue
} }
return frs, nil return frs, nil
}
if lastErr != nil {
return nil, lastErr
}
return nil, errors.New("unable to decrypt initial packet")
} }
const ( const (

View File

@@ -55,3 +55,14 @@ func TestExtractCryptoFrames_UnknownAfterCrypto(t *testing.T) {
t.Fatalf("frame0 = %+v, want offset=0 data=abc", frames[0]) 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 package udp
import ( import (
"errors"
"sort" "sort"
"git.difuse.io/Difuse/Mellaris/analyzer" "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 const minDataSize = 41
frs, err := quic.ReadCryptoFrames(data) 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++ s.invalidCount++
return nil, s.invalidCount >= quicInvalidCountThreshold 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 { if pl[0] != internal.TypeClientHello {
s.invalidCount++ // Not a ClientHello (e.g. server-direction CRYPTO); ignore.
return nil, s.invalidCount >= quicInvalidCountThreshold return nil, false
} }
chLen := int(pl[1])<<16 | int(pl[2])<<8 | int(pl[3]) chLen := int(pl[1])<<16 | int(pl[2])<<8 | int(pl[3])