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

WIP: Packet Capture Collector #1666

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/godbus/dbus/v5 v5.1.0
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.6.0
github.com/gopacket/gopacket v1.2.0
github.com/gorilla/handlers v1.5.2
github.com/hashicorp/go-getter v1.7.6
github.com/hashicorp/go-multierror v1.1.1
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMd
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I3A=
github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
Expand Down Expand Up @@ -885,6 +887,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY=
github.com/vladimirvivien/gexe v0.2.0/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w=
github.com/vmware-tanzu/velero v1.14.1 h1:HYj73scn7ZqtfTanjW/X4W0Hn3w/qcfoRbrHCWM52iI=
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/troubleshoot/v1beta2/hostcollector_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ type HostDNS struct {
Hostnames []string `json:"hostnames" yaml:"hostnames"`
}

type HostPacketCapture struct {
HostCollectorMeta `json:",inline" yaml:",inline"`
}

type HostCollect struct {
CPU *CPU `json:"cpu,omitempty" yaml:"cpu,omitempty"`
Memory *Memory `json:"memory,omitempty" yaml:"memory,omitempty"`
Expand Down Expand Up @@ -251,6 +255,7 @@ type HostCollect struct {
HostJournald *HostJournald `json:"journald,omitempty" yaml:"journald,omitempty"`
HostCGroups *HostCGroups `json:"cgroups,omitempty" yaml:"cgroups,omitempty"`
HostDNS *HostDNS `json:"dns,omitempty" yaml:"dns,omitempty"`
HostPacketCapture *HostPacketCapture `json:"packetCapture,omitempty" yaml:"packetCapture,omitempty"`
}

// GetName gets the name of the collector
Expand Down
2 changes: 2 additions & 0 deletions pkg/collect/host_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ func GetHostCollector(collector *troubleshootv1beta2.HostCollect, bundlePath str
return &CollectHostCGroups{collector.HostCGroups, bundlePath}, true
case collector.HostDNS != nil:
return &CollectHostDNS{collector.HostDNS, bundlePath}, true
case collector.HostPacketCapture != nil:
return &CollectHostPacketCapture{collector.HostPacketCapture, bundlePath}, true
default:
return nil, false
}
Expand Down
122 changes: 122 additions & 0 deletions pkg/collect/host_packet_capture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package collect

import (
"fmt"
"os"
"path/filepath"
"sync"
"time"

"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/layers"
"github.com/gopacket/gopacket/pcap"
"github.com/gopacket/gopacket/pcapgo"
"github.com/pkg/errors"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
)

const HostPacketCapturePath = `host-collectors/system/host_pcap.pcap`
const HostPacketCaptureResultPath = `host-collectors/system/host_pcap.json`
const HostPacketCaptureFileName = `host_pcap.pcap`

type CollectHostPacketCapture struct {
hostCollector *troubleshootv1beta2.HostPacketCapture
BundlePath string
}

func (c *CollectHostPacketCapture) Title() string {
return hostCollectorTitleOrDefault(c.hostCollector.HostCollectorMeta, "Host Packet Capture")
}

func (c *CollectHostPacketCapture) IsExcluded() (bool, error) {
return isExcluded(c.hostCollector.Exclude)
}

func (c *CollectHostPacketCapture) Collect(progressChan chan<- interface{}) (map[string][]byte, error) {
// Use WaitGroup to wait for packet capture from all devices
var wg sync.WaitGroup

//pcapFilePath := filepath.Join(c.BundlePath, HostPacketCapturePath)
pcapFilePath := "./host_pcap.pcap"
// Ensure the directory exists
dir := filepath.Dir(pcapFilePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, errors.Wrap(err, "failed to create directories for pcap file")
}

// Now create the file
f, err := os.Create(pcapFilePath)
if err != nil {
return nil, errors.Wrap(err, "failed to create pcap file")
}
defer f.Close()

// Create a pcap writer for the file
w := pcapgo.NewWriter(f)

// Write the PCAP file header (link type and snapshot length)
err = w.WriteFileHeader(1600, layers.LinkTypeEthernet)
if err != nil {
return nil, errors.Wrap(err, "failed to write pcap file header")
}

// Start packet capture on all network devices concurrently for 5 seconds
devices := []string{"lo0", "en0"} // Add other devices here, replace with actual devices to capture
for _, device := range devices {
wg.Add(1)
go func(deviceName string) {
defer wg.Done()
err := capturePacketsToPcap(deviceName, w, 5*time.Second)
if err != nil {
fmt.Printf("Error capturing packets on device %s: %v\n", deviceName, err)
}
}(device)
}

// Wait for all packet captures to complete
wg.Wait()

// Open the saved pcap file for reading
pcapFile, err := os.Open(pcapFilePath)
if err != nil {
return nil, errors.Wrap(err, "failed to open pcap file for reading")
}
defer pcapFile.Close()

// Use the pcap file as the reader for SaveResult
output := NewResult()
if err := output.SaveResult(c.BundlePath, HostPacketCapturePath, pcapFile); err != nil {
return nil, errors.Wrap(err, "failed to save result")
}

return output, nil
}

// Helper function to capture packets using pcapgo on a specific device
func capturePacketsToPcap(device string, pcapw *pcapgo.Writer, duration time.Duration) error {
// Open the Ethernet handle for packet capture (use pcapgo)
handle, err := pcap.OpenLive(device, 1600, true, pcap.BlockForever)
if err != nil {
return errors.Wrapf(err, "error opening live capture for device %s", device)
}
defer handle.Close()

// Set up packet source for capturing packets
pkgsrc := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)

// Context for packet capturing
timeout := time.After(duration)
for {
select {
case packet := <-pkgsrc.Packets():
// Write each captured packet to the pcap file
err := pcapw.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
if err != nil {
return errors.Wrap(err, "error writing packet to pcap file")
}
case <-timeout:
// Timeout reached, stop capturing
return nil
}
}
}