Skip to content

Commit

Permalink
add -r flag for reading packets from pcapng file
Browse files Browse the repository at this point in the history
  • Loading branch information
mozillazg committed May 5, 2024
1 parent 60206cc commit 7dc87c7
Show file tree
Hide file tree
Showing 19 changed files with 298 additions and 84 deletions.
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $(OUTPUT):


.PHONY: build
build: generate libpcap
build: libpcap
CGO_CFLAGS=$(CGO_CFLAGS_STATIC) \
CGO_LDFLAGS=$(CGO_LDFLAGS_STATIC) \
CGO_ENABLED=1 go build -tags static -ldflags "$(LDFLAGS)"
Expand All @@ -60,6 +60,21 @@ test:
generate:
go generate ./...

.PHONY: lint
lint: deps fmt

.PHONY: fmt
fmt:
go fmt ./...

.PHONY: vet
vet:
go vet ./...

.PHONY: deps
deps:
go mod tidy

.PHONY: clean
clean:
$(MAKE) -C $(LIBPCAP_SRC) clean
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ptcpdump

ptcpdump is the tcpdump(8) implementation using eBPF, with an extra feature:
it adds process info as packet comments for each Ethernet frame.
it adds process info as packet comments for each Packet.

![](./docs/wireshark.png)

Expand All @@ -12,7 +12,8 @@ it adds process info as packet comments for each Ethernet frame.
* Supports using pcap-filter(7) syntax for filtering packets.
* Supports filtering packets by process ID and process name.
* Directly applies filters in the kernel space.
* Supports saving captured packets in the PCAP-NG format for offline analysis with third-party tools such as Wireshark.
* Supports saving captured packets in the PcapNG format for offline analysis with third-party tools such as Wireshark.
* Supports reading packets from pcapng file.


## Installation
Expand All @@ -39,6 +40,8 @@ Examples:
ptcpdump -i any -w ptcpdump.pcapng
ptcpdump -r ptcpdump.pcapng
Expression: see "man 7 pcap-filter"
Flags:
Expand All @@ -50,9 +53,10 @@ Flags:
--pid uint Filter by process ID
--pname string Filter by process name
--print Print parsed packet output, even if the raw packets are being saved to a file with the -w flag
-r, --read-file string Read packets from file (which was created with the -w option). e.g. ptcpdump.pcapng
-c, --receive-count uint Exit after receiving count packets
--version Print the ptcpdump and libpcap version strings and exit
-w, --write-file string Write the raw packets to file rather than parsing and printing them out. e.g. ptcpdump.pcapng
-w, --write-file string Write the raw packets to file rather than parsing and printing them out. They can later be printed with the -r option. e.g. ptcpdump.pcapng
```


Expand Down
12 changes: 6 additions & 6 deletions bpf/bpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,18 @@ func (b *BPF) AttachKprobes() error {

func (b *BPF) AttachTracepoints() error {
lk, err := link.AttachRawTracepoint(link.RawTracepointOptions{
"sched_process_exec",
b.objs.RawTracepointSchedProcessExec,
})
"sched_process_exec",
b.objs.RawTracepointSchedProcessExec,
})
if err != nil {
return xerrors.Errorf("attach raw_tracepoint/sched_process_exec: %w", err)
}
b.links = append(b.links, lk)

lk, err = link.AttachRawTracepoint(link.RawTracepointOptions{
"sched_process_exit",
b.objs.RawTracepointSchedProcessExit,
})
"sched_process_exit",
b.objs.RawTracepointSchedProcessExit,
})
if err != nil {
return xerrors.Errorf("attach raw_tracepoint/sched_process_exit: %w", err)
}
Expand Down
50 changes: 50 additions & 0 deletions cmd/capture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"context"
"github.com/mozillazg/ptcpdump/internal/consumer"
"github.com/mozillazg/ptcpdump/internal/metadata"
"log"
)

func capture(ctx context.Context, opts Options) error {
pcache := metadata.NewProcessCache()
writers, err := getWriters(opts, pcache)
if err != nil {
return err
}
defer func() {
for _, w := range writers {
w.Flush()
}
}()
go pcache.Start()

bf, err := attachHooks(opts)
if err != nil {
if bf != nil {
bf.Close()
}
return err
}
defer bf.Close()

packetEvensCh, err := bf.PullPacketEvents(ctx)
if err != nil {
return err
}
execEvensCh, err := bf.PullExecEvents(ctx)
if err != nil {
return err
}

execConsumer := consumer.NewExecEventConsumer(pcache)
go execConsumer.Start(ctx, execEvensCh)

log.Println("capturing...")

packetConsumer := consumer.NewPacketEventConsumer(writers)
packetConsumer.Start(ctx, packetEvensCh, opts.maxPacketCount)

return nil
}
5 changes: 5 additions & 0 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Options struct {
comm string
followForks bool
writeFilePath string
readFilePath string
pcapFilter string
listInterfaces bool
version bool
Expand All @@ -18,6 +19,10 @@ func (o Options) WritePath() string {
return o.writeFilePath
}

func (o Options) ReadPath() string {
return o.readFilePath
}

func (o Options) DirectionIn() bool {
return o.DirectionInOut() || o.direction == "in"
}
Expand Down
41 changes: 41 additions & 0 deletions cmd/read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cmd

import (
"context"
"io"
"os"

"github.com/mozillazg/ptcpdump/internal/metadata"
"github.com/mozillazg/ptcpdump/internal/parser"
"github.com/mozillazg/ptcpdump/internal/writer"
)

func read(ctx context.Context, opts Options) error {
f, err := os.Open(opts.ReadPath())
if err != nil {
return err
}
defer f.Close()

pcache := metadata.NewProcessCache()
p, err := parser.NewPcapNGParser(f, pcache)
if err != nil {
return err
}
stdoutWriter := writer.NewStdoutWriter(os.Stdout, pcache)

for {
e, err := p.Parse()
if err != nil {
if err == io.EOF {
break
}
return err
}
if err := stdoutWriter.Write(e); err != nil {
return err
}
}

return nil
}
67 changes: 14 additions & 53 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ package cmd

import (
"context"
"github.com/mozillazg/ptcpdump/internal/consumer"
"github.com/mozillazg/ptcpdump/internal/metadata"
"github.com/spf13/cobra"
"log"
"os/signal"
"runtime"
"strings"
"syscall"
)
Expand All @@ -26,6 +22,8 @@ Examples:
ptcpdump -i any -w ptcpdump.pcapng
ptcpdump -r ptcpdump.pcapng
Expression: see "man 7 pcap-filter"`,
DisableFlagsInUseLine: true,
Short: "ptcpdump is the tcpdump(8) implementation using eBPF, with an extra feature: it adds process info as packet comments for each Ethernet frame.",
Expand All @@ -40,7 +38,9 @@ Expression: see "man 7 pcap-filter"`,

func init() {
rootCmd.Flags().StringVarP(&opts.writeFilePath, "write-file", "w", "",
"Write the raw packets to file rather than parsing and printing them out. e.g. ptcpdump.pcapng")
"Write the raw packets to file rather than parsing and printing them out. They can later be printed with the -r option. e.g. ptcpdump.pcapng")
rootCmd.Flags().StringVarP(&opts.readFilePath, "read-file", "r", "",
"Read packets from file (which was created with the -w option). e.g. ptcpdump.pcapng")
rootCmd.Flags().StringSliceVarP(&opts.ifaces, "interface", "i", []string{"lo"},
"Interfaces to capture")
rootCmd.Flags().UintVar(&opts.pid, "pid", 0, "Filter by process ID")
Expand All @@ -64,60 +64,21 @@ func Execute() error {
}

func run(cmd *cobra.Command, args []string) error {
switch {
case opts.listInterfaces:
return listInterfaces()
case opts.version:
return printVersion()
}

pcache := metadata.NewProcessCache()
writers, err := getWriters(opts, pcache)
if err != nil {
return err
}
defer func() {
for _, w := range writers {
w.Flush()
}
}()
go pcache.Start()

bf, err := attachHooks(opts)
if err != nil {
if bf != nil {
bf.Close()
}
return err
}
defer bf.Close()

ctx, stop := signal.NotifyContext(
context.Background(), syscall.SIGINT, syscall.SIGTERM,
)
defer stop()

packetEvensCh, err := bf.PullPacketEvents(ctx)
if err != nil {
return err
}
execEvensCh, err := bf.PullExecEvents(ctx)
if err != nil {
return err
switch {
case opts.listInterfaces:
return listInterfaces()
case opts.version:
return printVersion()
case opts.ReadPath() != "":
return read(ctx, opts)
default:
return capture(ctx, opts)
}

execConsumer := consumer.NewExecEventConsumer(pcache)
go execConsumer.Start(ctx, execEvensCh)
packetConsumer := consumer.NewPacketEventConsumer(writers)
go func() {
packetConsumer.Start(ctx, packetEvensCh, opts.maxPacketCount)
stop()
}()

runtime.Gosched()
log.Println("capturing...")
<-ctx.Done()
log.Println("bye bye")

return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ require (
)

replace (
github.com/gopacket/gopacket => github.com/mozillazg/gopacket v0.0.0-20240429121216-bf7893b04e11
github.com/gopacket/gopacket => github.com/mozillazg/gopacket v0.0.0-20240505040301-d7d31317e811
github.com/x-way/pktdump => github.com/mozillazg/pktdump v0.0.0-20240422135914-a9ab652291b1
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWC
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mozillazg/gopacket v0.0.0-20240429121216-bf7893b04e11 h1:c5vYy0pngzBHc8NcxiGTckc2qpQzw4ImPesF2qAU8Ok=
github.com/mozillazg/gopacket v0.0.0-20240429121216-bf7893b04e11/go.mod h1:lnXM4VDqJTe4d2NoZr8DZMtidkhss2Y82QFlamXWfXo=
github.com/mozillazg/gopacket v0.0.0-20240505040301-d7d31317e811 h1:s/S56+28/iWjYuSCC1BucognB/Ll8dFDjV/0Ic2C6XU=
github.com/mozillazg/gopacket v0.0.0-20240505040301-d7d31317e811/go.mod h1:lnXM4VDqJTe4d2NoZr8DZMtidkhss2Y82QFlamXWfXo=
github.com/mozillazg/pktdump v0.0.0-20240422135914-a9ab652291b1 h1:hAP2PH4czj22vr78Dh6dPlXlxDsKK/uYNxPiE9jvRUM=
github.com/mozillazg/pktdump v0.0.0-20240422135914-a9ab652291b1/go.mod h1:InLCDK8kgkk26VtyPZ51e0igf15eiXDMvvQuV62Wqmw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
4 changes: 2 additions & 2 deletions internal/consumer/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ func (c *ExecEventConsumer) Start(ctx context.Context, ch <-chan bpf.BpfExecEven
case <-ctx.Done():
return
case et := <-ch:
c.parseExecEvent(et)
c.handleExecEvent(et)
}
}
}

func (c *ExecEventConsumer) parseExecEvent(et bpf.BpfExecEventT) {
func (c *ExecEventConsumer) handleExecEvent(et bpf.BpfExecEventT) {
e, err := event.ParseProcessExecEvent(et)
if err != nil {
log.Printf("[ExecEventConsumer] parse event failed: %s", err)
Expand Down
4 changes: 2 additions & 2 deletions internal/consumer/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (c *PacketEventConsumer) Start(ctx context.Context, ch <-chan bpf.BpfPacket
case <-ctx.Done():
return
case pt := <-ch:
c.parsePacketEvent(pt)
c.handlePacketEvent(pt)
n++
if maxPacketCount > 0 && n == maxPacketCount {
log.Printf("%d packets captured", n)
Expand All @@ -39,7 +39,7 @@ func (c *PacketEventConsumer) Start(ctx context.Context, ch <-chan bpf.BpfPacket
}
}

func (c *PacketEventConsumer) parsePacketEvent(pt bpf.BpfPacketEventT) {
func (c *PacketEventConsumer) handlePacketEvent(pt bpf.BpfPacketEventT) {
pevent, err := event.ParsePacketEvent(c.devices, pt)
if err != nil {
log.Printf("[PacketEventConsumer] parse event failed: %s", err)
Expand Down
14 changes: 14 additions & 0 deletions internal/event/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package event

import (
"fmt"
"github.com/gopacket/gopacket"
"github.com/mozillazg/ptcpdump/bpf"
"github.com/mozillazg/ptcpdump/internal/dev"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -53,6 +54,19 @@ func ParsePacketEvent(devices map[int]dev.Device, event bpf.BpfPacketEventT) (*P
return &p, nil
}

func FromPacket(ci gopacket.CaptureInfo, data []byte) (*Packet, error) {
p := Packet{
Time: ci.Timestamp,
Type: -1,
Device: dev.Device{},
Pid: 0,
Truncated: false,
Len: ci.Length,
Data: data,
}
return &p, nil
}

func (p Packet) Ingress() bool {
return p.Type == packetTypeIngress
}
Expand Down
Loading

0 comments on commit 7dc87c7

Please sign in to comment.