analyzer: make http3/quic handling more reliable
This commit is contained in:
@@ -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,18 +71,19 @@ 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 {
|
||||||
tokenLen, err := quicvarint.Read(b)
|
return nil, ErrNotInitialPacket
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
tokenLen, err := quicvarint.Read(b)
|
||||||
}
|
if err != nil {
|
||||||
if tokenLen > uint64(b.Len()) {
|
return nil, err
|
||||||
return nil, io.EOF
|
}
|
||||||
}
|
if tokenLen > uint64(b.Len()) {
|
||||||
h.Token = make([]byte, tokenLen)
|
return nil, io.EOF
|
||||||
if _, err := io.ReadFull(b, h.Token); err != nil {
|
}
|
||||||
return nil, err
|
h.Token = make([]byte, tokenLen)
|
||||||
}
|
if _, err := io.ReadFull(b, h.Token); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pl, err := quicvarint.Read(b)
|
pl, err := quicvarint.Read(b)
|
||||||
|
|||||||
@@ -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]
|
||||||
if err != nil {
|
pnMaxGuesses := []int64{0, 1, 2, 3, 4, 8, 16}
|
||||||
return nil, err
|
var lastErr error
|
||||||
|
for _, pnMax := range pnMaxGuesses {
|
||||||
|
packetCopy := append([]byte(nil), packetView...)
|
||||||
|
unProtectedPayload, err := pp.UnProtect(packetCopy, offset, pnMax)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload))
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return frs, nil
|
||||||
}
|
}
|
||||||
frs, err := extractCryptoFrames(bytes.NewReader(unProtectedPayload))
|
if lastErr != nil {
|
||||||
if err != nil {
|
return nil, lastErr
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return frs, nil
|
return nil, errors.New("unable to decrypt initial packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
Reference in New Issue
Block a user