From 2af52bb8bd322f94398d686fff0522ccd1c8349b Mon Sep 17 00:00:00 2001 From: mzz2017 <2017@duck.com> Date: Sun, 27 Aug 2023 00:24:18 +0800 Subject: [PATCH] refactor: allow to pass in multiple packets to a quic sniffer --- component/sniffing/conn_sniffer.go | 4 +- component/sniffing/http.go | 9 +- .../sniffing/internal/quicutils/cipher.go | 13 +- .../internal/quicutils/cipher_test.go | 2 +- .../sniffing/internal/quicutils/relocation.go | 310 +++++++----------- component/sniffing/quic.go | 113 +++---- component/sniffing/quic_bench_test.go | 34 +- component/sniffing/quic_test.go | 46 +++ component/sniffing/sniffer.go | 95 +++--- component/sniffing/tls.go | 61 +++- component/sniffing/tls_test.go | 3 +- control/udp.go | 2 +- 12 files changed, 326 insertions(+), 366 deletions(-) create mode 100644 component/sniffing/quic_test.go diff --git a/component/sniffing/conn_sniffer.go b/component/sniffing/conn_sniffer.go index bea20b8..dce4a7c 100644 --- a/component/sniffing/conn_sniffer.go +++ b/component/sniffing/conn_sniffer.go @@ -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 } diff --git a/component/sniffing/http.go b/component/sniffing/http.go index 4e72558..a2476c0 100644 --- a/component/sniffing/http.go +++ b/component/sniffing/http.go @@ -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 { diff --git a/component/sniffing/internal/quicutils/cipher.go b/component/sniffing/internal/quicutils/cipher.go index c5603ad..7c5ed15 100644 --- a/component/sniffing/internal/quicutils/cipher.go +++ b/component/sniffing/internal/quicutils/cipher.go @@ -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 } diff --git a/component/sniffing/internal/quicutils/cipher_test.go b/component/sniffing/internal/quicutils/cipher_test.go index 6ae2fea..5ae3192 100644 --- a/component/sniffing/internal/quicutils/cipher_test.go +++ b/component/sniffing/internal/quicutils/cipher_test.go @@ -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) } diff --git a/component/sniffing/internal/quicutils/relocation.go b/component/sniffing/internal/quicutils/relocation.go index 985d0de..b00ab7b 100644 --- a/component/sniffing/internal/quicutils/relocation.go +++ b/component/sniffing/internal/quicutils/relocation.go @@ -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{} diff --git a/component/sniffing/quic.go b/component/sniffing/quic.go index 654e3cd..988344a 100644 --- a/component/sniffing/quic.go +++ b/component/sniffing/quic.go @@ -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 } diff --git a/component/sniffing/quic_bench_test.go b/component/sniffing/quic_bench_test.go index 8be538f..bacb66e 100644 --- a/component/sniffing/quic_bench_test.go +++ b/component/sniffing/quic_bench_test.go @@ -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) diff --git a/component/sniffing/quic_test.go b/component/sniffing/quic_test.go new file mode 100644 index 0000000..5a2fda6 --- /dev/null +++ b/component/sniffing/quic_test.go @@ -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) +} diff --git a/component/sniffing/sniffer.go b/component/sniffing/sniffer.go index 5ae96b9..97a2311 100644 --- a/component/sniffing/sniffer.go +++ b/component/sniffing/sniffer.go @@ -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 } diff --git a/component/sniffing/tls.go b/component/sniffing/tls.go index 87ab16c..1ea4f38 100644 --- a/component/sniffing/tls.go +++ b/component/sniffing/tls.go @@ -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. diff --git a/component/sniffing/tls_test.go b/component/sniffing/tls_test.go index 91fbabc..3878f7a 100644 --- a/component/sniffing/tls_test.go +++ b/component/sniffing/tls_test.go @@ -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) diff --git a/control/udp.go b/control/udp.go index 27593f2..ef4f721 100644 --- a/control/udp.go +++ b/control/udp.go @@ -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) {