mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-16 19:00:05 +07:00
feat: add sniffing suite and dial_mode option (#16)
This commit is contained in:
183
component/sniffing/quic.go
Normal file
183
component/sniffing/quic.go
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) since 2023, v2rayA Organization <team@v2raya.org>
|
||||
*/
|
||||
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mzz2017/softwind/pool"
|
||||
"github.com/v2rayA/dae/component/sniffing/internal/quicutils"
|
||||
)
|
||||
|
||||
const (
|
||||
QuicFlag_PacketNumberLength = iota
|
||||
QuicFlag_PacketNumberLength1
|
||||
QuicFlag_Reserved
|
||||
QuicFlag_Reserved1
|
||||
QuicFlag_LongPacketType
|
||||
QuicFlag_LongPacketType1
|
||||
QuicFlag_FixedBit
|
||||
QuicFlag_HeaderForm
|
||||
)
|
||||
const (
|
||||
QuicFlag_HeaderForm_LongHeader = 1
|
||||
QuicFlag_LongPacketType_Initial = 0
|
||||
)
|
||||
|
||||
var (
|
||||
QuicReassemble = QuicReassemblePolicy_ReassembleCryptoToBytesFromPool
|
||||
)
|
||||
|
||||
type QuicReassemblePolicy int
|
||||
|
||||
const (
|
||||
QuicReassemblePolicy_ReassembleCryptoToBytesFromPool QuicReassemblePolicy = iota
|
||||
QuicReassemblePolicy_LinearLocator
|
||||
QuicReassemblePolicy_Slow
|
||||
)
|
||||
|
||||
func (s *Sniffer) SniffQuic() (d string, err error) {
|
||||
nextBlock := s.buf
|
||||
isQuic := false
|
||||
for {
|
||||
d, nextBlock, err = sniffQuicBlock(nextBlock)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
// If block is not a quic block, return it.
|
||||
if errors.Is(err, NotApplicableError) {
|
||||
// But if we have found quic block before, correct it.
|
||||
if isQuic {
|
||||
return "", NotFoundError
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
// Error is not NotApplicableError, should be quic block.
|
||||
isQuic = true
|
||||
if len(nextBlock) == 0 {
|
||||
return "", NotFoundError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
// QUIC: A UDP-Based Multiplexed and Secure Transport
|
||||
// https://datatracker.ietf.org/doc/html/rfc9000#name-initial-packet
|
||||
const dstConnIdPos = 6
|
||||
boundary := dstConnIdPos
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
// Check flag.
|
||||
// Long header: 4 bits masked
|
||||
// High 4 bits are not protected, so we can access QuicFlag_HeaderForm and QuicFlag_LongPacketType without decryption.
|
||||
protectedFlag := buf[0]
|
||||
if ((protectedFlag >> QuicFlag_HeaderForm) & 0b11) != QuicFlag_HeaderForm_LongHeader {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
if ((protectedFlag >> QuicFlag_LongPacketType) & 0b11) != QuicFlag_LongPacketType_Initial {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
|
||||
// Skip version.
|
||||
|
||||
destConnIdLength := int(buf[boundary-1])
|
||||
boundary += destConnIdLength + 1 // +1 because next field has 1B length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
destConnId := buf[dstConnIdPos : dstConnIdPos+destConnIdLength]
|
||||
|
||||
srcConnIdLength := int(buf[boundary-1])
|
||||
boundary += srcConnIdLength + quicutils.MaxVarintLen64 // The next fields may have quic.MaxVarintLen64 bytes length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
tokenLength, n, err := quicutils.BigEndianUvarint(buf[boundary-quicutils.MaxVarintLen64:])
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
boundary = boundary - quicutils.MaxVarintLen64 + n // Correct boundary.
|
||||
boundary += int(tokenLength) + quicutils.MaxVarintLen64 // Next fields may have quic.MaxVarintLen64 bytes length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc
|
||||
length, n, err := quicutils.BigEndianUvarint(buf[boundary-quicutils.MaxVarintLen64:])
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
boundary = boundary - quicutils.MaxVarintLen64 + n // Correct boundary.
|
||||
blockEnd := boundary + int(length)
|
||||
if len(buf) < blockEnd {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
boundary += quicutils.MaxPacketNumberLength
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
header := buf[:boundary]
|
||||
// Decrypt protected Packets.
|
||||
// https://datatracker.ietf.org/doc/html/rfc9000#packet-protected
|
||||
|
||||
// This function will modify the packet in place, thus we should save the first byte and MaxPacketNumberLength
|
||||
// and recover it later.
|
||||
firstByte := header[0]
|
||||
rawPacketNumber := pool.Get(quicutils.MaxPacketNumberLength)
|
||||
copy(rawPacketNumber, header[boundary-quicutils.MaxPacketNumberLength:])
|
||||
defer pool.Put(rawPacketNumber)
|
||||
defer func() {
|
||||
header[0] = firstByte
|
||||
copy(header[boundary-quicutils.MaxPacketNumberLength:], rawPacketNumber)
|
||||
}()
|
||||
plaintext, err := quicutils.DecryptQuicFromPool_(header, blockEnd, destConnId)
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
}
|
||||
defer pool.Put(plaintext)
|
||||
// Now, we confirm it is exact a quic frame.
|
||||
// After here, we should not return NotApplicableError.
|
||||
// And we should return nextFrame.
|
||||
if d, err = extractSniFromQuicPayload(plaintext); err != nil {
|
||||
return "", buf[blockEnd:], NotFoundError
|
||||
}
|
||||
return d, buf[blockEnd:], nil
|
||||
}
|
||||
|
||||
func extractSniFromQuicPayload(payload []byte) (sni string, err error) {
|
||||
// One payload may have multiple frames.
|
||||
// Reassemble Crypto frames.
|
||||
|
||||
// Choose locator.
|
||||
var locator quicutils.Locator
|
||||
switch QuicReassemble {
|
||||
case QuicReassemblePolicy_LinearLocator:
|
||||
relocation, err := quicutils.NewCryptoFrameRelocation(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
locator = quicutils.NewLinearLocator(relocation)
|
||||
case QuicReassemblePolicy_Slow:
|
||||
relocation, err := quicutils.NewCryptoFrameRelocation(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := relocation.BytesFromPool()
|
||||
defer pool.Put(b)
|
||||
locator = quicutils.BuiltinBytesLocator(b)
|
||||
case QuicReassemblePolicy_ReassembleCryptoToBytesFromPool:
|
||||
b, err := quicutils.ReassembleCryptoToBytesFromPool(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer pool.Put(b)
|
||||
locator = quicutils.BuiltinBytesLocator(b)
|
||||
}
|
||||
sni, err = extractSniFromTls(locator)
|
||||
if err == nil {
|
||||
return sni, nil
|
||||
}
|
||||
return "", NotFoundError
|
||||
}
|
Reference in New Issue
Block a user