mirror of
https://github.com/daeuniverse/dae.git
synced 2025-01-03 13:31:00 +07:00
refactor: allow to pass in multiple packets to a quic sniffer
This commit is contained in:
parent
d76919fc62
commit
2af52bb8bd
@ -17,10 +17,10 @@ type ConnSniffer struct {
|
||||
*Sniffer
|
||||
}
|
||||
|
||||
func NewConnSniffer(conn net.Conn, snifferBufSize int, dataWaitingTimeout time.Duration) *ConnSniffer {
|
||||
func NewConnSniffer(conn net.Conn, snifferBufSize int, timeout time.Duration) *ConnSniffer {
|
||||
s := &ConnSniffer{
|
||||
Conn: conn,
|
||||
Sniffer: NewStreamSniffer(conn, snifferBufSize, dataWaitingTimeout),
|
||||
Sniffer: NewStreamSniffer(conn, snifferBufSize, timeout),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -8,19 +8,20 @@ package sniffing
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
)
|
||||
|
||||
func (s *Sniffer) SniffHttp() (d string, err error) {
|
||||
// First byte should be printable.
|
||||
if len(s.buf) == 0 || !unicode.IsPrint(rune(s.buf[0])) {
|
||||
if s.buf.Len() == 0 || !unicode.IsPrint(rune(s.buf.Bytes()[0])) {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
// Search method.
|
||||
search := s.buf
|
||||
search := s.buf.Bytes()
|
||||
if len(search) > 12 {
|
||||
search = search[:12]
|
||||
}
|
||||
@ -35,7 +36,7 @@ func (s *Sniffer) SniffHttp() (d string, err error) {
|
||||
// Now we assume it is an HTTP packet. We should not return NotApplicableError after here.
|
||||
|
||||
// Search Host.
|
||||
scanner := bufio.NewScanner(bytes.NewReader(s.buf))
|
||||
scanner := bufio.NewScanner(bytes.NewReader(s.buf.Bytes()))
|
||||
// \r\n
|
||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
|
@ -10,10 +10,11 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -113,7 +114,7 @@ func (k *Keys) HeaderProtection_(sample []byte, longHeader bool, firstByte *byte
|
||||
return packetNumber, nil
|
||||
}
|
||||
|
||||
func (k *Keys) PayloadDecryptFromPool(ciphertext []byte, packetNumber []byte, header []byte) (plaintext []byte, err error) {
|
||||
func (k *Keys) PayloadDecrypt(ciphertext []byte, packetNumber []byte, header []byte) (plaintext []byte, err error) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets
|
||||
|
||||
aead, err := k.newAead(k.key)
|
||||
@ -125,15 +126,15 @@ func (k *Keys) PayloadDecryptFromPool(ciphertext []byte, packetNumber []byte, he
|
||||
for i := range packetNumber {
|
||||
k.iv[len(k.iv)-len(packetNumber)+i] ^= packetNumber[i]
|
||||
}
|
||||
plaintext = pool.Get(len(ciphertext) - aead.Overhead())
|
||||
plaintext = make([]byte, len(ciphertext)-aead.Overhead())
|
||||
plaintext, err = aead.Open(plaintext[:0], k.iv, ciphertext, header)
|
||||
if err != nil {
|
||||
pool.Put(plaintext)
|
||||
// Do nothing.
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func DecryptQuicFromPool_(header []byte, blockEnd int, destConnId []byte) (plaintext []byte, err error) {
|
||||
func DecryptQuic_(header []byte, blockEnd int, destConnId []byte) (plaintext []byte, err error) {
|
||||
_version := binary.BigEndian.Uint32(header[1:])
|
||||
version, err := ParseVersion(_version)
|
||||
if err != nil {
|
||||
@ -158,7 +159,7 @@ func DecryptQuicFromPool_(header []byte, blockEnd int, destConnId []byte) (plain
|
||||
header = header[:len(header)-MaxPacketNumberLength+len(packetNumber)] // Correct header
|
||||
payload := header[len(header):blockEnd] // Correct payload
|
||||
|
||||
plaintext, err = keys.PayloadDecryptFromPool(payload, packetNumber, header)
|
||||
plaintext, err = keys.PayloadDecrypt(payload, packetNumber, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func TestKeys_PayloadDecrypt_(t *testing.T) {
|
||||
}
|
||||
header = data[:len(header)-4+len(packetNumber)]
|
||||
payload := data[len(header):]
|
||||
plaintext, err := keys.PayloadDecryptFromPool(payload, packetNumber, header)
|
||||
plaintext, err := keys.PayloadDecrypt(payload, packetNumber, header)
|
||||
if err != nil {
|
||||
t.Fatal("PayloadDecryptFromPool:", err)
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -32,69 +30,50 @@ type CryptoFrameOffset struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type CryptoFrameRelocation struct {
|
||||
payload []byte
|
||||
o []*CryptoFrameOffset
|
||||
length int
|
||||
}
|
||||
|
||||
func NewCryptoFrameRelocation(plaintextPayload []byte) (cryptoRelocation *CryptoFrameRelocation, err error) {
|
||||
var frameSize int
|
||||
var offset *CryptoFrameOffset
|
||||
cryptoRelocation = &CryptoFrameRelocation{
|
||||
payload: plaintextPayload,
|
||||
o: nil,
|
||||
}
|
||||
|
||||
// Extract crypto frames.
|
||||
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset == nil {
|
||||
continue
|
||||
}
|
||||
cryptoRelocation.o = append(cryptoRelocation.o, offset)
|
||||
}
|
||||
|
||||
// Sort offsets by UpperAppOffset.
|
||||
sort.Slice(cryptoRelocation.o, func(i, j int) bool {
|
||||
return cryptoRelocation.o[i].UpperAppOffset < cryptoRelocation.o[j].UpperAppOffset
|
||||
})
|
||||
|
||||
// Store length.
|
||||
left := cryptoRelocation.o[0]
|
||||
right := cryptoRelocation.o[len(cryptoRelocation.o)-1]
|
||||
cryptoRelocation.length = right.UpperAppOffset + len(right.Data) - left.UpperAppOffset
|
||||
|
||||
return cryptoRelocation, nil
|
||||
}
|
||||
|
||||
func ReassembleCryptoToBytesFromPool(plaintextPayload []byte) (b []byte, err error) {
|
||||
func ReassembleCryptos(offsets []*CryptoFrameOffset, newPayload []byte) (newOffsets []*CryptoFrameOffset, err error) {
|
||||
oldLen := len(offsets)
|
||||
var frameSize int
|
||||
var offset *CryptoFrameOffset
|
||||
var boundary int
|
||||
b = pool.Get(len(plaintextPayload))
|
||||
// Extract crypto frames.
|
||||
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
||||
for iNextFrame := 0; iNextFrame < len(newPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(newPayload[iNextFrame:], iNextFrame)
|
||||
if err != nil {
|
||||
pool.Put(b)
|
||||
return nil, err
|
||||
}
|
||||
if offset == nil {
|
||||
continue
|
||||
}
|
||||
if offset.UpperAppOffset+len(offset.Data) >= len(b) {
|
||||
return nil, fmt.Errorf("offset.UpperAppOffset out of bound: %v:%v/%v", offset.UpperAppOffset, offset.UpperAppOffset+len(offset.Data), len(b))
|
||||
}
|
||||
copy(b[offset.UpperAppOffset:], offset.Data)
|
||||
offsets = append(offsets, offset)
|
||||
if offset.UpperAppOffset+len(offset.Data) > boundary {
|
||||
boundary = offset.UpperAppOffset + len(offset.Data)
|
||||
}
|
||||
}
|
||||
return b[:boundary], nil
|
||||
// Sort the new part.
|
||||
newPart := offsets[oldLen:]
|
||||
sort.Slice(newPart, func(i, j int) bool {
|
||||
return newPart[i].UpperAppOffset < newPart[j].UpperAppOffset
|
||||
})
|
||||
|
||||
// Insertion sort.
|
||||
for i := oldLen; i < len(offsets); i++ {
|
||||
item := offsets[i]
|
||||
j := i - 1
|
||||
for ; j >= 0; j-- {
|
||||
if item.UpperAppOffset < offsets[j].UpperAppOffset {
|
||||
offsets[j+1] = offsets[j]
|
||||
} else {
|
||||
if offsets[j+1] != item {
|
||||
offsets[j+1] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if j < 0 {
|
||||
offsets[0] = item
|
||||
}
|
||||
}
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
func ExtractCryptoFrameOffset(remainder []byte, transportOffset int) (offset *CryptoFrameOffset, frameSize int, err error) {
|
||||
@ -136,108 +115,16 @@ func ExtractCryptoFrameOffset(remainder []byte, transportOffset int) (offset *Cr
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) BinarySearch(iUpper int, leftOuter, rightOuter int) (iOuter int, iInner int, err error) {
|
||||
rightOuterInstance := r.o[rightOuter]
|
||||
if iUpper < r.o[leftOuter].UpperAppOffset || iUpper >= rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data) {
|
||||
return 0, 0, fmt.Errorf("%w: %v is not in [%v, %v)", OutOfRangeError, iUpper, r.o[leftOuter].UpperAppOffset, rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data))
|
||||
}
|
||||
for leftOuter < rightOuter {
|
||||
mid := leftOuter + ((rightOuter - leftOuter) >> 1)
|
||||
if iUpper < r.o[mid].UpperAppOffset {
|
||||
rightOuter = mid - 1
|
||||
} else if iUpper >= r.o[mid].UpperAppOffset {
|
||||
if iUpper < r.o[mid].UpperAppOffset+len(r.o[mid].Data) {
|
||||
return mid, iUpper - r.o[mid].UpperAppOffset, nil
|
||||
} else {
|
||||
leftOuter = mid + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return leftOuter, iUpper - r.o[leftOuter].UpperAppOffset, nil
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) BytesFromPool() []byte {
|
||||
if len(r.o) == 0 {
|
||||
return pool.Get(0)
|
||||
}
|
||||
right := r.o[len(r.o)-1]
|
||||
return r.copyBytesToPool(0, 0, len(r.o)-1, len(right.Data)-1, r.length)
|
||||
}
|
||||
|
||||
// RangeFromPool copy bytes from iUpperAppOffset to jUpperAppOffset.
|
||||
// It is not suggested to use it for large range and frequent copy.
|
||||
func (r *CryptoFrameRelocation) RangeFromPool(i, j int) []byte {
|
||||
if i > j {
|
||||
panic(fmt.Sprintf("i > j: %v > %v", i, j))
|
||||
}
|
||||
// We find bytes including i and j, so we should sub j with 1.
|
||||
j--
|
||||
|
||||
// Find i.
|
||||
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Check if j and i is in the same outer or adjacent outers.
|
||||
// It is very common because we usually have small access range.
|
||||
var jOuter, jInner int
|
||||
if iInner+j-i < len(r.o[iOuter].Data) {
|
||||
jOuter = iOuter
|
||||
jInner = iInner + j - i
|
||||
} else if iOuter+1 < len(r.o) && j < r.o[iOuter+1].UpperAppOffset+len(r.o[iOuter+1].Data) {
|
||||
jOuter = iOuter + 1
|
||||
jInner = (j - i) + (len(r.o[iOuter].Data) - iInner)
|
||||
} else {
|
||||
// We have searched iOuter and iOuter+1
|
||||
jOuter, jInner, err = r.BinarySearch(j, iOuter+2, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return r.copyBytesToPool(iOuter, iInner, jOuter, jInner, j-i+1)
|
||||
}
|
||||
|
||||
// copyBytesToPool copy bytes including i and j.
|
||||
func (r *CryptoFrameRelocation) copyBytesToPool(iOuter, iInner, jOuter, jInner, size int) []byte {
|
||||
b := pool.Get(size)
|
||||
// io := r.o[iOuter]
|
||||
k := 0
|
||||
for {
|
||||
// Most accesses are small range accesses.
|
||||
base := r.o[iOuter].Data
|
||||
if iOuter == jOuter {
|
||||
k += copy(b[k:], base[iInner:jInner+1])
|
||||
if k != size {
|
||||
panic("unmatched size")
|
||||
}
|
||||
return b
|
||||
} else {
|
||||
k += copy(b[k:], base[iInner:])
|
||||
if iInner != 0 {
|
||||
iInner = 0
|
||||
}
|
||||
iOuter++
|
||||
}
|
||||
}
|
||||
}
|
||||
func (r *CryptoFrameRelocation) At(i int) byte {
|
||||
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r.o[iOuter].Data[iInner]
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) Len() int {
|
||||
return r.length
|
||||
}
|
||||
var (
|
||||
ErrMissingCrypto = fmt.Errorf("missing crypto frame")
|
||||
)
|
||||
|
||||
type Locator interface {
|
||||
Range(i, j int) []byte
|
||||
Slice(i, j int) Locator
|
||||
At(i int) byte
|
||||
Range(i, j int) ([]byte, error)
|
||||
Slice(i, j int) (Locator, error)
|
||||
At(i int) (byte, error)
|
||||
Len() int
|
||||
Bytes() ([]byte, error)
|
||||
}
|
||||
|
||||
// LinearLocator only searches forward and have no boundary check.
|
||||
@ -248,95 +135,130 @@ type LinearLocator struct {
|
||||
baseEnd int
|
||||
baseStart int
|
||||
baseData []byte
|
||||
cfr *CryptoFrameRelocation
|
||||
o []*CryptoFrameOffset
|
||||
}
|
||||
|
||||
func NewLinearLocator(cfr *CryptoFrameRelocation) (linearLocator *LinearLocator) {
|
||||
func NewLinearLocator(o []*CryptoFrameOffset) *LinearLocator {
|
||||
if len(o) == 0 {
|
||||
return &LinearLocator{}
|
||||
}
|
||||
return &LinearLocator{
|
||||
left: 0,
|
||||
length: cfr.length,
|
||||
length: o[len(o)-1].UpperAppOffset + len(o[len(o)-1].Data),
|
||||
iOuter: 0,
|
||||
baseData: cfr.o[0].Data,
|
||||
baseStart: cfr.o[0].UpperAppOffset,
|
||||
baseEnd: cfr.o[0].UpperAppOffset + len(cfr.o[0].Data),
|
||||
cfr: cfr,
|
||||
baseData: o[0].Data,
|
||||
baseStart: o[0].UpperAppOffset,
|
||||
baseEnd: o[0].UpperAppOffset + len(o[0].Data),
|
||||
o: o,
|
||||
}
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) relocate(i int) {
|
||||
func (l *LinearLocator) relocate(i int) error {
|
||||
// Relocate ll.iOuter.
|
||||
for i >= ll.baseEnd {
|
||||
ll.iOuter++
|
||||
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
||||
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
||||
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
||||
for i >= l.baseEnd {
|
||||
if l.iOuter+1 >= len(l.o) {
|
||||
return ErrMissingCrypto
|
||||
}
|
||||
l.iOuter++
|
||||
l.baseData = l.o[l.iOuter].Data
|
||||
l.baseStart = l.o[l.iOuter].UpperAppOffset
|
||||
l.baseEnd = l.baseStart + len(l.baseData)
|
||||
}
|
||||
if i < l.baseStart {
|
||||
return ErrMissingCrypto
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Range(i, j int) []byte {
|
||||
func (l *LinearLocator) Range(i, j int) ([]byte, error) {
|
||||
if i == j {
|
||||
return []byte{}
|
||||
return []byte{}, nil
|
||||
}
|
||||
if len(l.o) == 0 {
|
||||
return nil, ErrMissingCrypto
|
||||
}
|
||||
size := j - i
|
||||
|
||||
// We find bytes including i and j, so we should sub j with 1.
|
||||
i += ll.left
|
||||
j += ll.left - 1
|
||||
ll.relocate(i)
|
||||
i += l.left
|
||||
j += l.left - 1
|
||||
if err := l.relocate(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Linearly copy.
|
||||
|
||||
if j < ll.baseEnd {
|
||||
if j < l.baseEnd {
|
||||
// In the same block, no copy needed.
|
||||
return ll.baseData[i-ll.baseStart : j-ll.baseStart+1]
|
||||
return l.baseData[i-l.baseStart : j-l.baseStart+1], nil
|
||||
}
|
||||
|
||||
b := make([]byte, size)
|
||||
k := 0
|
||||
for j >= ll.baseEnd {
|
||||
n := copy(b[k:], ll.baseData[i-ll.baseStart:])
|
||||
for j >= l.baseEnd {
|
||||
n := copy(b[k:], l.baseData[i-l.baseStart:])
|
||||
k += n
|
||||
i += n
|
||||
ll.iOuter++
|
||||
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
||||
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
||||
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
||||
if l.iOuter+1 >= len(l.o) || l.o[l.iOuter].UpperAppOffset+len(l.o[l.iOuter+1].Data) != l.o[l.iOuter].UpperAppOffset {
|
||||
// Some crypto is missing.
|
||||
return nil, ErrMissingCrypto
|
||||
}
|
||||
l.iOuter++
|
||||
l.baseData = l.o[l.iOuter].Data
|
||||
l.baseStart = l.o[l.iOuter].UpperAppOffset
|
||||
l.baseEnd = l.baseStart + len(l.baseData)
|
||||
}
|
||||
copy(b[k:], ll.baseData[i-ll.baseStart:j-ll.baseStart+1])
|
||||
return b
|
||||
copy(b[k:], l.baseData[i-l.baseStart:j-l.baseStart+1])
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) At(i int) byte {
|
||||
i += ll.left
|
||||
func (l *LinearLocator) At(i int) (byte, error) {
|
||||
if len(l.o) == 0 {
|
||||
return 0, ErrMissingCrypto
|
||||
}
|
||||
i += l.left
|
||||
|
||||
ll.relocate(i)
|
||||
b := ll.baseData[i-ll.baseStart]
|
||||
return b
|
||||
if err := l.relocate(i); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b := l.baseData[i-l.baseStart]
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Slice(i, j int) Locator {
|
||||
func (l *LinearLocator) Slice(i, j int) (Locator, error) {
|
||||
// We do not care about right.
|
||||
newLL := *ll
|
||||
newLL := *l
|
||||
newLL.left += i
|
||||
newLL.length = j - i + 1
|
||||
return &newLL
|
||||
return &newLL, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Len() int {
|
||||
return ll.length
|
||||
func (l *LinearLocator) Bytes() ([]byte, error) {
|
||||
return l.Range(0, l.length)
|
||||
}
|
||||
|
||||
var _ Locator = &LinearLocator{}
|
||||
|
||||
func (l *LinearLocator) Len() int {
|
||||
return l.length
|
||||
}
|
||||
|
||||
type BuiltinBytesLocator []byte
|
||||
|
||||
func (l BuiltinBytesLocator) Range(i, j int) []byte {
|
||||
return l[i:j]
|
||||
func (l BuiltinBytesLocator) Range(i, j int) ([]byte, error) {
|
||||
return l[i:j], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) At(i int) byte {
|
||||
return l[i]
|
||||
func (l BuiltinBytesLocator) At(i int) (byte, error) {
|
||||
return l[i], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) Slice(i, j int) Locator {
|
||||
return l[i:j]
|
||||
func (l BuiltinBytesLocator) Slice(i, j int) (Locator, error) {
|
||||
return l[i:j], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
func (l BuiltinBytesLocator) Bytes() ([]byte, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
var _ Locator = BuiltinBytesLocator{}
|
||||
|
@ -28,10 +28,6 @@ const (
|
||||
QuicFlag_LongPacketType_Initial = 0
|
||||
)
|
||||
|
||||
var (
|
||||
QuicReassemble = QuicReassemblePolicy_ReassembleCryptoToBytesFromPool
|
||||
)
|
||||
|
||||
type QuicReassemblePolicy int
|
||||
|
||||
const (
|
||||
@ -41,50 +37,58 @@ const (
|
||||
)
|
||||
|
||||
func (s *Sniffer) SniffQuic() (d string, err error) {
|
||||
nextBlock := s.buf
|
||||
nextBlock := s.buf.Bytes()
|
||||
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 {
|
||||
s.quicCryptos, nextBlock, err = sniffQuicBlock(s.quicCryptos, nextBlock)
|
||||
if err != 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
|
||||
}
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
// ConnectionClose sniffed.
|
||||
return "", NotFoundError
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
// ConnectionClose sniffed.
|
||||
return "", NotFoundError
|
||||
}
|
||||
// Error is not NotApplicableError, should be quic block.
|
||||
// Should be quic block.
|
||||
isQuic = true
|
||||
if len(nextBlock) == 0 {
|
||||
return "", NotFoundError
|
||||
break
|
||||
}
|
||||
}
|
||||
// Is quic.
|
||||
s.buf.Reset()
|
||||
sni, err := extractSniFromTls(quicutils.NewLinearLocator(s.quicCryptos))
|
||||
if err != nil {
|
||||
s.needMore = true
|
||||
return "", NotFoundError
|
||||
}
|
||||
return sni, nil
|
||||
}
|
||||
|
||||
func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
func sniffQuicBlock(cryptos []*quicutils.CryptoFrameOffset, buf []byte) (new []*quicutils.CryptoFrameOffset, 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
|
||||
return nil, 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
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
if ((protectedFlag >> QuicFlag_LongPacketType) & 0b11) != QuicFlag_LongPacketType_Initial {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
|
||||
// Skip version.
|
||||
@ -92,37 +96,37 @@ func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
destConnIdLength := int(buf[boundary-1])
|
||||
boundary += destConnIdLength + 1 // +1 because next field has 1B length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, 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
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
tokenLength, n, err := quicutils.BigEndianUvarint(buf[boundary-quicutils.MaxVarintLen64:])
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, 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
|
||||
return nil, 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
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
boundary = boundary - quicutils.MaxVarintLen64 + n // Correct boundary.
|
||||
blockEnd := boundary + int(length)
|
||||
if len(buf) < blockEnd {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
boundary += quicutils.MaxPacketNumberLength
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, nil, NotApplicableError
|
||||
}
|
||||
header := buf[:boundary]
|
||||
// Decrypt protected Packets.
|
||||
@ -138,55 +142,18 @@ func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
copy(header[boundary-quicutils.MaxPacketNumberLength:], rawPacketNumber)
|
||||
pool.Put(rawPacketNumber)
|
||||
}()
|
||||
plaintext, err := quicutils.DecryptQuicFromPool_(header, blockEnd, destConnId)
|
||||
plaintext, err := quicutils.DecryptQuic_(header, blockEnd, destConnId)
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
return nil, 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 {
|
||||
if new, err = quicutils.ReassembleCryptos(cryptos, plaintext); err != nil {
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return "", buf[blockEnd:], NotFoundError
|
||||
return nil, 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
|
||||
return new, buf[blockEnd:], nil
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package sniffing
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -16,41 +17,10 @@ var QuicStream, _ = hex.DecodeString("c00000000108d60451e5cb0f7050000044bc9acdca
|
||||
|
||||
//var QuicStream, _ = hex.DecodeString("c6ff00001d100d5a802c52bfee4d71f3770529a5c6871415ea0d6ef29709e829432a18eb50f3af09c81c75004127f234d23fca9370573fd78cd781f4057ce9940111f0ad20e03e894b232013d76e268299644b036ac4557f03fead23ece9b788b3bcff3492b376861a188d5905e5e07cb156b57d7419e66235bedd44e5e774e1476d344eff64bdb1604aa9755a1fd08d4597a03a205e490f4223ddb32af2fc4023bc6784bcf6622ded2a49bbb976dec36e3712e0016272207f462b93b5a70dc66463131d2375bbfc38ece9215119b0b53676d05d470dcce52460f76d284d8f23846cbb38fcaa7e07fa1d6dec390e2876aea21bbd188dca3fe96dfc8c9f99237564e3db587b240279f46613ccc46c84e1b246cf1536be8275075fa4e63f0750df54f0cfbae986811cf3493c1d6ea63a836f387d1a3a02ac158b433ead3fc2035987f1f9c65c71c2d31803320f7a1a978a1aee3e1a50")
|
||||
|
||||
func BenchmarkLinearLocator(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_LinearLocator
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
b.Fatal(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuiltinSlow(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_Slow
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
b.Fatal(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReassembleCryptoToBytesFromPool(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_ReassembleCryptoToBytesFromPool
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
sniffer := NewPacketSniffer(QuicStream, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
46
component/sniffing/quic_test.go
Normal file
46
component/sniffing/quic_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing/internal/quicutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var QuicStream2_2, _ = hex.DecodeString("cc0000000108e8da6ed9f385c987000044d026f109c2764c22f0ea2656550ea03e832d0ed5113eff115f2a057f77655cf5bbbb69fc98f7f70a3f407e0d94f37960c5ba5bd95a2df75f6f25020c2f2f21ddf9db5266bb4293991d58efec945468a820c61b743ca4b73663c3adcda58dee75607c5465e255b58477069a928687789c18c2ccb53911a47d64b83d5b58398ee4fd58f4f88f78788d5594218730cab9db3bac2fbfb947f2cb4eafb5e2964fce361042c622dfa7130afaf0e9d391ffc3aba2f5ee2f5c4d0dfaae0d71db2b3d7fab6dbccbb63d7961ddab55711d5a1beacf00ce5a82030a2c79c4ea65a2762f3b8e5f8fec8f6963b1a42c0f8a8d863225b2d6e7a15e9758e43095459e3d7ff88dc276605452b10de95a8795fe9952eb0b1eb200465ca9b00f98e2c4ad6a2a2e2bff2e2430438241525e1d16d5423c2262134a97056b7e86d5eb7eb2ac546086a3b8d7a97bc2263fa9a8b46f4b7d31cad63762c17a653b89593434aecf7a5e8fc169cfb5aa4a47e78ee817e115feceb9b68b29da6e15c647b7528980fb7cdc7c9ca660871228d0367f030f658d19ddddefe55908a2ec4ef5f5d89ec5aebee33f88a116c2857f7d1a2fd98321f28468a93938da406a68e4e660f0668fe49118812d5264073f28a8aa800c5970ef3f6fb4f0e9e4e48510700a5465c92886c50f2c6af570075f29f6a80636171f73d91864583d2d199e39b18623ee0cb489b449838bd9f7cd67ccc3e38f1b5a3ce08814f979f94db45cdcfa39a475e3efc4847def8e8e4c707a88d2f486fc85e10910ab0f1bbeb40468af777ff2bb0e655f1a006cde0d2e2ae036dafe60f110e859543699e0c9aa47eefa53d792b3cbcfa11ea1d3b55d3629de0345517d47f4e4c801104b81710ad28cd8611e150a1fc32160cb784cfcfdd908052cd43969b27929013edd2b0f3cd914590a32b2f99d4fc88873838b6fa0ec1450adb95f395988998801e85319fa448925ba767e3191df2b5b0983990beb4127216c93291a94463b453a4972c9a974742b0b22c935f4235c350120b6cf8296fc6d3c2812f74a17acf334e3c34ff9988f980e0cfff737a8b1a03508f47d8bf3748fbb5bd5ad7f1f47120c3a33822612f3a614aae7fe536b73db814aa4aac4b685aa1e7357309cf921b931113624881ce764feeff3292d2d794c6fa76529f3da8e6327e8f28aafe8b675a80ae3f478c65f1bf8fd7f2b140fea130dfa55982f0b0fcd61b42c8b2ea27a2b8bb44511eb44c1416ac16698f0ddb739e3d773f2afdd35bcfed0ffd7966aa3e727f8f08d02cab8d034a7ae363e42c9089901ddee147c98a856df4e5dcfeeb2f72e9edb12da513f32d99e1c653f4503e9a7f7fee1f4724ce9d6d530485362d993cb3bc4faff683327a02aee6f004bd9f98a8a4841091d48f5cd27af46431c66e68007750be57361e293650a0ae9fc9fa82ddf4483663c9805dc6e4a9b43529c0b2267cc3c0fb9084378acbda4962150a73e0c1b5aef6e40538d2630d8dbc2b084f9a53079cc73484906b7ad4a5021f280baf276a01b0fcea57d5c4284364f4d795645fc7bd8bb7d00021af924b75829e8a936e153676a182803537a23c76fee7c881e8063751ca0f5a585481b9077e9593734f9997e78b79ba38f6e13a1b631106a2ceddafdf51110b8bf07ec9337024355088d0bb3de2d46a03d3e3e7362b8b815613e36d746e5a9992f8e62ad5257e5798bd49b1a62717f02151b75a18e051df1292191d4")
|
||||
var QuicStream2_1, _ = hex.DecodeString("ce0000000108e8da6ed9f385c987000044d0f34f94dcc26b99261ea264742abe4e552a146e16e89e4b7ef0ab3d6f3a34227b59742e4ba83a1e18cea494d2f67e469be4a7ff01334b151e9b7ca63b53735008eecc1f5c618419982292eca5731bb163ba81c1300e0bb99f2536d89ab0faf2dbd37ebfdb3d71f7343296a2190914bda556b8f9ccf5219964eb3cd373966fcfaca8a4735fb59fbaf69bbbdfc3a81b11570bb81fd3f5ef780fb7036e0666b997b0f4ed3305b68eafa1a99b3c8a6a2142ad9fe1e6b0a0eade6ace92b57416d4bf68fa2e9295bfc22757b0542ce91c8af3f547ef0ad385788db230a50158a0009fd95a7e8ee6e0dd11d6f9a906cbe8117e85bd507cdbd8f1a5a6cabf2617de7227d1ae8a8c6086b8ec325df90c0e16b37b4ed0ce617a00c7598a21924a19aec1b08c31b69430b23eefbe555ca2433431d28a4ffec548e463e8e6363b6b4fe9b8477c686c393571273c30b2e1785261faa0fd6f560c12418b27cd0491e013db5a8b3294e01a46a6e4c6b52e32756ab4be6f4ebc886c0c472d63f117ce30115182a97f1308c7f28989ce301cabced825154b0f4fa3bf4a55ce2f384ff11d9cbc0460d69db363664f92dc014bdb771b9b1e1ab6672c6da71c90aa514dcdc3a4ce45298bf9e5a395ebac3dff2a738c4b4690ee06fdab572a277addac7035d94afe794df05da75a56c79c37f42de1d727dc65e3060d9331e2fc82de2d7cef6cb9ae46f648b9930593975c35960b24deb770d5ee4332f8f57a05503399ca7bfdf7207f66a0f73d6b53269a944d5a3043b225adddfdd29d20ea8f500bb09ea3bb724083dd29ea8839e8192c4360ba3c5a6db0d695af5d357d6c4ed94aa28305033629201689764189774bbd4f0ae41b878b8f29a0fe0e124075ea08c5054871506a05be2f90e9ec0c2db48c0780580312e9ff4071054386e4206841f575f7ca06c228f7ee11e2333d08652b9b4f0b97f473a46a3d79c4f9a3416fb20fdbd88cacfa36f06fe1d73618195c6f0bf759a77c6a16b7e271c6cdb672ea53f6edfac860fcaf03313564abde1f66bca441d844d289a9e1025711c284f2c7c805353f2a89e9aeb52e3f452e879f0fafcdc0b48a0676afcf617a85037d991762664f6db64847eff2308447c4e8ea6688838bb7237a5fdfe0f1695afaa0bbb821b0004585adf151b029bd3458e28ba49dfc17eef1d2dd14ccda88d0848d4cd36d33cc5bab173c2448785ec1bdabc8873c904b95d7847d1b89857f2c7e078c6e2eb96029aa91c077e0efcf7b2ed2f30c7abc12189627793c7870dc0e70342cc27402ee1d6dec5ceea0ca06159002ea14a20c63b85689ed1840f404e46cb83d91c5e02f3ed938462364d3349f689310234083f7044e4b338ac54bed94530640d684c9688651b915d8c8895ef0f05f376292871b589751ac5b233e3d85572bb0c11bbbe91cc49a4ef0422f2676a2f3cc62bc88dbb7acf03cb5e847e976bfca6a90b9cee743ea77be5472ef162ff101c6873043df94c53c252840fd6a2662018f0897a06cd215997d6050917876500796fef718957212c773c39d1c7b839931af1e7dfae6e2c1d2251e78896521bb35b20057bad77df85aaed90288c17edb081398815e47239aeb77293a02a61a5125109fc3953593233fa83c17770a815fad7831c1b8647c6089ec621ee774a12a714def498d4335d0bb8a4a6a3dddead8ddb1176f58218477d55317df88cd2ca5a06b72679cf2ff7253ebd76a5ed3")
|
||||
|
||||
func dumpCryptos(t *testing.T, cryptos []*quicutils.CryptoFrameOffset) {
|
||||
var b strings.Builder
|
||||
for _, c := range cryptos {
|
||||
b.WriteString(fmt.Sprintf("Offset %v; length: %v:\n", c.UpperAppOffset, len(c.Data)))
|
||||
b.WriteString(fmt.Sprintf("Dump:\n%v\n", hex.Dump(c.Data)))
|
||||
}
|
||||
t.Log(b.String())
|
||||
}
|
||||
|
||||
func TestQuic(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
sniffer := NewPacketSniffer(QuicStream2_1, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
if sniffer.NeedMore() {
|
||||
sniffer.AppendData(QuicStream2_2)
|
||||
d, err = sniffer.SniffQuic()
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
dumpCryptos(t, sniffer.quicCryptos)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
t.Fatal("domain is empty")
|
||||
}
|
||||
t.Log(d)
|
||||
}
|
@ -6,49 +6,65 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing/internal/quicutils"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
)
|
||||
|
||||
type Sniffer struct {
|
||||
// Stream
|
||||
stream bool
|
||||
r io.Reader
|
||||
dataReady chan struct{}
|
||||
dataError error
|
||||
dataWaitingTimeout time.Duration
|
||||
stream bool
|
||||
r io.Reader
|
||||
dataReady chan struct{}
|
||||
dataError error
|
||||
|
||||
// Common
|
||||
buf []byte
|
||||
bufAt int
|
||||
readMu sync.Mutex
|
||||
buf *bytes.Buffer
|
||||
readMu sync.Mutex
|
||||
needMore bool
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
|
||||
// Quic
|
||||
quicCryptos []*quicutils.CryptoFrameOffset
|
||||
}
|
||||
|
||||
func NewStreamSniffer(r io.Reader, bufSize int, dataWaitingTimeout time.Duration) *Sniffer {
|
||||
func NewStreamSniffer(r io.Reader, bufSize int, timeout time.Duration) *Sniffer {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
s := &Sniffer{
|
||||
stream: true,
|
||||
r: r,
|
||||
buf: make([]byte, bufSize),
|
||||
dataReady: make(chan struct{}),
|
||||
dataWaitingTimeout: dataWaitingTimeout,
|
||||
stream: true,
|
||||
r: r,
|
||||
buf: pool.GetBuffer(),
|
||||
dataReady: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func NewPacketSniffer(data []byte) *Sniffer {
|
||||
func NewPacketSniffer(data []byte, timeout time.Duration) *Sniffer {
|
||||
buffer := pool.GetBuffer()
|
||||
buffer.Write(data)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
s := &Sniffer{
|
||||
stream: false,
|
||||
r: nil,
|
||||
buf: data,
|
||||
buf: buffer,
|
||||
dataReady: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type sniff func() (d string, err error)
|
||||
|
||||
func sniffGroup(sniffs []sniff) (d string, err error) {
|
||||
func sniffGroup(sniffs ...sniff) (d string, err error) {
|
||||
for _, sniffer := range sniffs {
|
||||
d, err = sniffer()
|
||||
if err == nil {
|
||||
@ -66,8 +82,7 @@ func (s *Sniffer) SniffTcp() (d string, err error) {
|
||||
defer s.readMu.Unlock()
|
||||
if s.stream {
|
||||
go func() {
|
||||
n, err := s.r.Read(s.buf)
|
||||
s.buf = s.buf[:n]
|
||||
_, err := s.buf.ReadFrom(s.r)
|
||||
if err != nil {
|
||||
s.dataError = err
|
||||
}
|
||||
@ -80,22 +95,22 @@ func (s *Sniffer) SniffTcp() (d string, err error) {
|
||||
if s.dataError != nil {
|
||||
return "", s.dataError
|
||||
}
|
||||
case <-time.After(s.dataWaitingTimeout):
|
||||
case <-s.ctx.Done():
|
||||
return "", NotApplicableError
|
||||
}
|
||||
} else {
|
||||
close(s.dataReady)
|
||||
}
|
||||
|
||||
if len(s.buf) == 0 {
|
||||
if s.buf.Len() == 0 {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
return sniffGroup([]sniff{
|
||||
return sniffGroup(
|
||||
// Most sniffable traffic is TLS, thus we sniff it first.
|
||||
s.SniffTls,
|
||||
s.SniffHttp,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Sniffer) SniffUdp() (d string, err error) {
|
||||
@ -105,13 +120,21 @@ func (s *Sniffer) SniffUdp() (d string, err error) {
|
||||
// Always ready.
|
||||
close(s.dataReady)
|
||||
|
||||
if len(s.buf) == 0 {
|
||||
if s.buf.Len() == 0 {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
return sniffGroup([]sniff{
|
||||
return sniffGroup(
|
||||
s.SniffQuic,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Sniffer) AppendData(data []byte) {
|
||||
s.buf.Write(data)
|
||||
}
|
||||
|
||||
func (s *Sniffer) NeedMore() bool {
|
||||
return s.needMore
|
||||
}
|
||||
|
||||
func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
@ -121,21 +144,13 @@ func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
defer s.readMu.Unlock()
|
||||
|
||||
if s.dataError != nil {
|
||||
if s.bufAt < len(s.buf) {
|
||||
n = copy(p, s.buf[s.bufAt:])
|
||||
s.bufAt += n
|
||||
}
|
||||
n, _ = s.buf.Read(p)
|
||||
return n, s.dataError
|
||||
}
|
||||
|
||||
if s.bufAt < len(s.buf) {
|
||||
if s.buf.Len() > 0 {
|
||||
// Read buf first.
|
||||
n = copy(p, s.buf[s.bufAt:])
|
||||
s.bufAt += n
|
||||
if s.bufAt >= len(s.buf) {
|
||||
s.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
return s.buf.Read(p)
|
||||
}
|
||||
if !s.stream {
|
||||
return 0, io.EOF
|
||||
@ -144,5 +159,11 @@ func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (s *Sniffer) Close() (err error) {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
default:
|
||||
s.cancel()
|
||||
pool.PutBuffer(s.buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -30,16 +30,16 @@ func (s *Sniffer) SniffTls() (d string, err error) {
|
||||
// The Transport Layer Security (TLS) Protocol Version 1.3
|
||||
// https://www.rfc-editor.org/rfc/rfc8446#page-27
|
||||
boundary := 5
|
||||
if len(s.buf) < boundary {
|
||||
if s.buf.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
if s.buf[0] != ContentType_HandShake || (!bytes.Equal(s.buf[1:3], Version_Tls1_0) && !bytes.Equal(s.buf[1:3], Version_Tls1_2)) {
|
||||
if s.buf.Bytes()[0] != ContentType_HandShake || (!bytes.Equal(s.buf.Bytes()[1:3], Version_Tls1_0) && !bytes.Equal(s.buf.Bytes()[1:3], Version_Tls1_2)) {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
length := int(binary.BigEndian.Uint16(s.buf[3:5]))
|
||||
search := s.buf[5:]
|
||||
length := int(binary.BigEndian.Uint16(s.buf.Bytes()[3:5]))
|
||||
search := s.buf.Bytes()[5:]
|
||||
if len(search) < length {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
@ -53,7 +53,10 @@ func extractSniFromTls(search quicutils.Locator) (sni string, err error) {
|
||||
}
|
||||
// Transport Layer Security (TLS) Extensions: Extension Definitions
|
||||
// https://www.rfc-editor.org/rfc/rfc6066#page-5
|
||||
b := search.Range(0, 6)
|
||||
b, err := search.Range(0, 6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b[0] != HandShakeType_Hello {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
@ -70,43 +73,62 @@ func extractSniFromTls(search quicutils.Locator) (sni string, err error) {
|
||||
|
||||
// Skip 32 bytes random.
|
||||
|
||||
sessionIdLength := search.At(boundary - 1)
|
||||
sessionIdLength, err := search.At(boundary - 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
boundary += int(sessionIdLength) + 2 // +2 because the next field has 2B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
b = search.Range(boundary-2, boundary)
|
||||
b, err = search.Range(boundary-2, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cipherSuiteLength := int(binary.BigEndian.Uint16(b))
|
||||
boundary += int(cipherSuiteLength) + 1 // +1 because the next field has 1B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
compressMethodsLength := search.At(boundary - 1)
|
||||
compressMethodsLength, err := search.At(boundary - 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
boundary += int(compressMethodsLength) + 2 // +2 because the next field has 2B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
|
||||
b = search.Range(boundary-2, boundary)
|
||||
b, err = search.Range(boundary-2, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
extensionsLength := int(binary.BigEndian.Uint16(b))
|
||||
boundary += extensionsLength + 0 // +0 because our search ends
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
// Search SNI
|
||||
return findSniExtension(search.Slice(boundary-extensionsLength, boundary))
|
||||
extensions, err := search.Slice(boundary-extensionsLength, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return findSniExtension(extensions)
|
||||
}
|
||||
|
||||
func findSniExtension(search quicutils.Locator) (string, error) {
|
||||
func findSniExtension(search quicutils.Locator) (d string, err error) {
|
||||
i := 0
|
||||
var b []byte
|
||||
for {
|
||||
if i+4 >= search.Len() {
|
||||
return "", NotFoundError
|
||||
}
|
||||
b = search.Range(i, i+4)
|
||||
b, err = search.Range(i, i+4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
typ := binary.BigEndian.Uint16(b)
|
||||
extLength := int(binary.BigEndian.Uint16(b[2:]))
|
||||
|
||||
@ -115,14 +137,20 @@ func findSniExtension(search quicutils.Locator) (string, error) {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
if typ == TlsExtension_ServerName {
|
||||
b = search.Range(i+4, i+6)
|
||||
b, err = search.Range(i+4, i+6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sniLen := int(binary.BigEndian.Uint16(b))
|
||||
if extLength < sniLen+2 {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
// Search HostName type SNI.
|
||||
for j, indicatorLen := i+6, 0; j+3 <= iNextField; j += indicatorLen {
|
||||
b = search.Range(j, j+3)
|
||||
b, err = search.Range(j, j+3)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
indicatorLen = int(binary.BigEndian.Uint16(b[1:]))
|
||||
if b[0] != TlsExtension_ServerNameType_HostName {
|
||||
continue
|
||||
@ -130,7 +158,10 @@ func findSniExtension(search quicutils.Locator) (string, error) {
|
||||
if j+3+indicatorLen > iNextField {
|
||||
return "", NotApplicableError
|
||||
}
|
||||
b = search.Range(j+3, j+3+indicatorLen)
|
||||
b, err = search.Range(j+3, j+3+indicatorLen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// An SNI value may not include a trailing dot.
|
||||
// https://tools.ietf.org/html/rfc6066#section-3
|
||||
// But we accept it here.
|
||||
|
@ -8,6 +8,7 @@ package sniffing
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -28,7 +29,7 @@ func TestSniffer_SniffTls(t *testing.T) {
|
||||
}}
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
for _, test := range tests {
|
||||
sniffer := NewPacketSniffer(test.Stream)
|
||||
sniffer := NewPacketSniffer(test.Stream, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffTls()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -140,7 +140,7 @@ func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, r
|
||||
isDns := dnsMessage != nil
|
||||
if !isDns {
|
||||
// Sniff Quic, ...
|
||||
sniffer := sniffing.NewPacketSniffer(data)
|
||||
sniffer := sniffing.NewPacketSniffer(data, c.sniffingTimeout)
|
||||
defer sniffer.Close()
|
||||
domain, err = sniffer.SniffUdp()
|
||||
if err != nil && !sniffing.IsSniffingError(err) {
|
||||
|
Loading…
Reference in New Issue
Block a user