Skip to content
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

[feature] Support for gzip compressed files in pcap source #31

Merged
merged 2 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions capture/pcap/pcap.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package pcap

import (
"bufio"
"compress/gzip"
"errors"
"fmt"
"io"
Expand All @@ -15,7 +17,8 @@ import (

// Source denotes a pcap file capture source
type Source struct {
io.Reader
reader *bufio.Reader
gzipReader *gzip.Reader

header Header
buf []byte
Expand All @@ -35,19 +38,25 @@ func NewSource(iface string, r io.Reader) (*Source, error) {
if r == nil {
return nil, errors.New("nil io.Reader provided")
}

obj := Source{
Reader: r,
reader: bufio.NewReader(r),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapped the "normal" io.Reader in a buffered one in order to support peeking ahead (probably also increasing performance for large files).

buf: make([]byte, HeaderSize),
}

// Check if the source is compressed
if err := obj.checkCompression(); err != nil {
return nil, err
}

// Parse the main header
if err := obj.read(obj.buf); err != nil {
return nil, err
}

// If required, swap endianess as defined here:
// https://wiki.wireshark.org/Development/LibpcapFileFormat
obj.header = *(*Header)(unsafe.Pointer(&obj.buf[0]))
obj.header = *(*Header)(unsafe.Pointer(&obj.buf[0])) // #nosec G103
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but I started to audit the unsafe use as suggested by the Linter (basically telling it "I know what I'm doing here"), will do the same everywhere else soon.

if obj.header.MagicNumber == MagicSwappedEndianess {
obj.header = obj.header.SwapEndianess()
obj.swapEndianess = true
Expand Down Expand Up @@ -163,8 +172,8 @@ func (s *Source) Unblock() error {

// Close stops / closes the capture source
func (s *Source) Close() error {
if readCloser, ok := s.Reader.(io.ReadCloser); ok {
return readCloser.Close()
if s.gzipReader != nil {
return s.gzipReader.Close()
}
return nil
}
Expand All @@ -184,6 +193,30 @@ func (s *Source) PacketAddCallbackFn(fn func(payload []byte, totalLen uint32, pk

////////////////////////////////////////////////////////////////////////

func (s *Source) checkCompression() error {

// Check if the first two bytes match a gzip file magic in either
// endianess
magicBytes, err := s.reader.Peek(2)
if err != nil {
return err
}
if (magicBytes[0] == 0x1f && magicBytes[1] == 0x8b) ||
(magicBytes[0] == 0x8b && magicBytes[1] == 0x1f) {

// Attempt to open a new gzip decompressor
s.gzipReader, err = gzip.NewReader(s.reader)
if err != nil {
return err
}

// Wrap a new bufio.Reader around the gzip decompressor
s.reader = bufio.NewReader(s.gzipReader)
}

return nil
}

func (s *Source) nextPacket() (pktHeader PacketHeader, err error) {
pktHeader, err = s.nextPacketHeader()
if err != nil {
Expand All @@ -209,7 +242,7 @@ func (s *Source) nextPacketHeader() (PacketHeader, error) {
if err := s.read(s.buf[:PacketHeaderSize]); err != nil {
return PacketHeader{}, err
}
return *(*PacketHeader)(unsafe.Pointer(&s.buf[0])), nil
return *(*PacketHeader)(unsafe.Pointer(&s.buf[0])), nil // #nosec G103
}

func (s *Source) nextPacketData(snapLen int) error {
Expand All @@ -222,7 +255,7 @@ func (s *Source) nextPacketData(snapLen int) error {
}

func (s *Source) read(buf []byte) error {
n, err := io.ReadAtLeast(s.Reader, buf, len(buf))
n, err := io.ReadAtLeast(s.reader, buf, len(buf))
if err != nil {
return err
}
Expand Down
35 changes: 0 additions & 35 deletions capture/pcap/pcap_data_test.go

This file was deleted.

58 changes: 36 additions & 22 deletions capture/pcap/pcap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package pcap

import (
"bytes"
"encoding/base64"
"embed"
"io"
"io/fs"
"net"
"os"
"path/filepath"
"testing"

"github.com/fako1024/slimcap/capture"
Expand All @@ -14,7 +16,12 @@ import (
"github.com/stretchr/testify/require"
)

var testDataLE, testDataBE []byte
const (
pcapTestInputNPackets = 6
testDataPath = "testdata"
)

var testData []fs.DirEntry

func TestInvalidInput(t *testing.T) {

Expand Down Expand Up @@ -53,12 +60,14 @@ func TestInvalidInput(t *testing.T) {

func TestReader(t *testing.T) {

for _, testData := range [][]byte{
testDataLE,
testDataBE,
} {
for _, dirent := range testData {
file, err := os.Open(filepath.Join(testDataPath, dirent.Name()))
require.Nil(t, err)
defer func() {
require.Nil(t, file.Close())
}()

src, err := NewSource("pcap", bytes.NewBuffer(testData))
src, err := NewSource("pcap", file)
require.Nil(t, err)

require.Equal(t, &link.Link{
Expand Down Expand Up @@ -86,7 +95,7 @@ func TestReader(t *testing.T) {
require.Equal(t, capture.PacketUnknown, pktType)
require.Nil(t, ipLayer)

src.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) error {
err = src.NextPacketFn(func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte) error {
require.Nil(t, payload)
require.Zero(t, totalLen)
require.Equal(t, capture.PacketUnknown, pktType)
Expand All @@ -107,11 +116,14 @@ func TestReader(t *testing.T) {

func TestMockPipe(t *testing.T) {

for _, testData := range [][]byte{
testDataLE,
testDataBE,
} {
src, err := NewSource("pcap", bytes.NewBuffer(testData))
for _, dirent := range testData {
file, err := os.Open(filepath.Join(testDataPath, dirent.Name()))
require.Nil(t, err)
defer func() {
require.Nil(t, file.Close())
}()

src, err := NewSource("pcap", file)
require.Nil(t, err)

// Setup a mock source
Expand Down Expand Up @@ -232,12 +244,14 @@ func TestCaptureMethods(t *testing.T) {
}

func testCaptureMethods(t *testing.T, fn func(t *testing.T, src *Source)) {
for _, dirent := range testData {
file, err := os.Open(filepath.Join(testDataPath, dirent.Name()))
require.Nil(t, err)
defer func() {
require.Nil(t, file.Close())
}()

for _, testData := range [][]byte{
testDataLE,
testDataBE,
} {
src, err := NewSource("pcap", bytes.NewBuffer(testData))
src, err := NewSource("pcap", file)
require.Nil(t, err)

for i := 0; i < pcapTestInputNPackets; i++ {
Expand All @@ -248,13 +262,13 @@ func testCaptureMethods(t *testing.T, fn func(t *testing.T, src *Source)) {
}
}

//go:embed testdata/*
var pcaps embed.FS
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@els0r Don't know if you ever used this, but I stumbled across it while doing mock test extension on goProbe. It's a really nice and easy way to embed test files from the repository (e.g. pcap files) inside the test binary (which makes it portable) without having to rely on crude mechanisms like base-64 embedding in .go files: https://pkg.go.dev/embed


func TestMain(m *testing.M) {

var err error
if testDataLE, err = base64.StdEncoding.DecodeString(pcapTestInputLE); err != nil {
panic(err)
}
if testDataBE, err = base64.StdEncoding.DecodeString(pcapTestInputBE); err != nil {
if testData, err = pcaps.ReadDir(testDataPath); err != nil {
panic(err)
}

Expand Down
Binary file added capture/pcap/testdata/be.pcap
Binary file not shown.
Binary file added capture/pcap/testdata/be.pcap.gz
Binary file not shown.
Binary file added capture/pcap/testdata/le.pcap
Binary file not shown.
Binary file added capture/pcap/testdata/le.pcap.gz
Binary file not shown.