-
Notifications
You must be signed in to change notification settings - Fork 5.7k
QUIC sniffer: Full support for handling multiple initial packets #4642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
73fea54
01df584
2c29525
59274f6
a64f263
80c6af7
e623076
2ad92b4
f42cd2f
ac6ba0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,7 @@ | ||
| package protocol // import "github.com/xtls/xray-core/common/protocol" | ||
|
|
||
| import ( | ||
| "errors" | ||
| ) | ||
|
|
||
| var ErrProtoNeedMoreData = errors.New("protocol matches, but need more data to complete sniffing") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,8 +11,8 @@ import ( | |
| "github.com/quic-go/quic-go/quicvarint" | ||
| "github.com/xtls/xray-core/common" | ||
| "github.com/xtls/xray-core/common/buf" | ||
| "github.com/xtls/xray-core/common/bytespool" | ||
| "github.com/xtls/xray-core/common/errors" | ||
| "github.com/xtls/xray-core/common/protocol" | ||
| ptls "github.com/xtls/xray-core/common/protocol/tls" | ||
| "golang.org/x/crypto/hkdf" | ||
| ) | ||
|
|
@@ -48,6 +48,9 @@ var ( | |
| ) | ||
|
|
||
| func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | ||
| if len(b) == 0 { | ||
| return nil, common.ErrNoClue | ||
| } | ||
| // In extremely rare cases, this sniffer may cause slice error | ||
| // and we set recover() here to prevent crash. | ||
| // TODO: Thoroughly fix this panic | ||
|
|
@@ -61,8 +64,11 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
|
|
||
| // Crypto data separated across packets | ||
| cryptoLen := 0 | ||
| cryptoData := bytespool.Alloc(int32(len(b))) | ||
| defer bytespool.Free(cryptoData) | ||
| cryptoDataBuf := buf.NewWithSize(32767) | ||
| cryptoDataBuf.Wipe() | ||
| defer cryptoDataBuf.Release() | ||
|
j2rong4cn marked this conversation as resolved.
Outdated
|
||
| cache := buf.New() | ||
| defer cache.Release() | ||
|
|
||
| // Parse QUIC packets | ||
| for len(b) > 0 { | ||
|
|
@@ -105,13 +111,15 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| return nil, errNotQuic | ||
| } | ||
|
|
||
| tokenLen, err := quicvarint.Read(buffer) | ||
| if err != nil || tokenLen > uint64(len(b)) { | ||
| return nil, errNotQuic | ||
| } | ||
| if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2 | ||
| tokenLen, err := quicvarint.Read(buffer) | ||
| if err != nil || tokenLen > uint64(len(b)) { | ||
| return nil, errNotQuic | ||
| } | ||
|
|
||
| if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil { | ||
| return nil, errNotQuic | ||
| if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil { | ||
| return nil, errNotQuic | ||
| } | ||
| } | ||
|
|
||
| packetLen, err := quicvarint.Read(buffer) | ||
|
|
@@ -130,9 +138,6 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| continue | ||
| } | ||
|
|
||
| origPNBytes := make([]byte, 4) | ||
| copy(origPNBytes, b[hdrLen:hdrLen+4]) | ||
|
|
||
| var salt []byte | ||
| if versionNumber == version1 { | ||
| salt = quicSalt | ||
|
|
@@ -147,44 +152,34 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| return nil, err | ||
| } | ||
|
|
||
| cache := buf.New() | ||
| defer cache.Release() | ||
|
|
||
| cache.Clear() | ||
| mask := cache.Extend(int32(block.BlockSize())) | ||
| block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16]) | ||
| b[0] ^= mask[0] & 0xf | ||
| for i := range b[hdrLen : hdrLen+4] { | ||
| packetNumberLength := int(b[0]&0x3 + 1) | ||
| for i := range packetNumberLength { | ||
| b[hdrLen+i] ^= mask[i+1] | ||
| } | ||
| packetNumberLength := b[0]&0x3 + 1 | ||
| if packetNumberLength != 1 { | ||
| return nil, errNotQuicInitial | ||
| } | ||
| var packetNumber uint32 | ||
| { | ||
| n, err := buffer.ReadByte() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| packetNumber = uint32(n) | ||
| } | ||
|
|
||
| extHdrLen := hdrLen + int(packetNumberLength) | ||
| copy(b[extHdrLen:hdrLen+4], origPNBytes[packetNumberLength:]) | ||
| data := b[extHdrLen : int(packetLen)+hdrLen] | ||
|
|
||
| key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16) | ||
| iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) | ||
| cipher := AEADAESGCMTLS13(key, iv) | ||
|
|
||
| nonce := cache.Extend(int32(cipher.NonceSize())) | ||
|
j2rong4cn marked this conversation as resolved.
|
||
| binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber)) | ||
| _, err = buffer.Read(nonce[len(nonce)-packetNumberLength:]) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RPRX 因为这里把数据读取到nonce末尾
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 原本的代码就有这个潜在bug
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个 buffer.Read 没问题吧,上面 cache.Extend 可能有问题 |
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| extHdrLen := hdrLen + packetNumberLength | ||
| data := b[extHdrLen : int(packetLen)+hdrLen] | ||
| decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| buffer = buf.FromBytes(decrypted) | ||
| for i := 0; !buffer.IsEmpty(); i++ { | ||
| frameType := byte(0x0) // Default to PADDING frame | ||
| for !buffer.IsEmpty() { | ||
| frameType, _ := buffer.ReadByte() | ||
| for frameType == 0x0 && !buffer.IsEmpty() { | ||
| frameType, _ = buffer.ReadByte() | ||
| } | ||
|
|
@@ -235,14 +230,14 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| } | ||
| if cryptoLen < int(offset+length) { | ||
| cryptoLen = int(offset + length) | ||
| if len(cryptoData) < cryptoLen { | ||
| newCryptoData := bytespool.Alloc(int32(cryptoLen)) | ||
| copy(newCryptoData, cryptoData) | ||
| bytespool.Free(cryptoData) | ||
| cryptoData = newCryptoData | ||
| if cryptoDataBuf.Cap() < int32(cryptoLen) { | ||
| return nil, io.ErrShortBuffer | ||
| } | ||
| if cryptoDataBuf.Len() != int32(cryptoLen) { | ||
| cryptoDataBuf.Extend(int32(cryptoLen) - cryptoDataBuf.Len()) | ||
| } | ||
| } | ||
| if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data | ||
| if _, err := buffer.Read(cryptoDataBuf.BytesRange(int32(offset), int32(offset+length))); err != nil { // Field: Crypto Data | ||
| return nil, io.ErrUnexpectedEOF | ||
| } | ||
| case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet | ||
|
|
@@ -267,7 +262,7 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| } | ||
|
|
||
| tlsHdr := &ptls.SniffHeader{} | ||
| err = ptls.ReadClientHello(cryptoData[:cryptoLen], tlsHdr) | ||
| err = ptls.ReadClientHello(cryptoDataBuf.BytesRange(0, int32(cryptoLen)), tlsHdr) | ||
| if err != nil { | ||
| // The crypto data may have not been fully recovered in current packets, | ||
| // So we continue to sniff rest packets. | ||
|
|
@@ -276,7 +271,8 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { | |
| } | ||
| return &SniffHeader{domain: tlsHdr.Domain()}, nil | ||
| } | ||
| return nil, common.ErrNoClue | ||
| // All payload is parsed as valid QUIC packets, but we need more packets for crypto data to read client hello. | ||
| return nil, protocol.ErrProtoNeedMoreData | ||
| } | ||
|
|
||
| func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.