Skip to content

Commit

Permalink
feat(capture): Add --netns flag to capture traffic from/to interfac…
Browse files Browse the repository at this point in the history
…es in other network namespaces (#160)

* feat(capture): Add `--netns` flag to capture traffic from/to interfaces in other network namespaces

closes #153

* fix tests
  • Loading branch information
mozillazg authored Oct 6, 2024
1 parent 52782fc commit cdd4253
Show file tree
Hide file tree
Showing 23 changed files with 1,108 additions and 111 deletions.
5 changes: 5 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ jobs:
command: |
sudo bash testdata/test_write_stdout.sh ./ptcpdump
- run:
name: e2e (test netns)
command: |
sudo bash testdata/test_netns.sh ./ptcpdump
- run:
name: e2e (test nat)
command: |
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ jobs:
bash /host/testdata/test_arp.sh /host/ptcpdump/ptcpdump
bash /host/testdata/test_icmp.sh /host/ptcpdump/ptcpdump
- name: Test netns
# if: ${{ (!startsWith(matrix.kernel, '5.4')) && (!startsWith(matrix.kernel, '4.')) }}
uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19
with:
provision: 'false'
cmd: |
set -ex
bash /host/testdata/test_netns.sh /host/ptcpdump/ptcpdump
- name: Test run sub program
uses: cilium/little-vm-helper@97c89f004bd0ab4caeacfe92ebc956e13e362e6b # v0.0.19
with:
Expand Down
29 changes: 11 additions & 18 deletions bpf/bpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,40 +312,33 @@ func (b *BPF) AttachTracepoints() error {
return nil
}

func (b *BPF) AttachTcHooks(ifindex int, egress, ingress bool) error {
func (b *BPF) AttachTcHooks(ifindex int, egress, ingress bool) ([]func(), error) {
var closeFuncs []func()
closeFunc, err := ensureTcQdisc(ifindex)
if err != nil {
if closeFunc != nil {
closeFunc()
}
return fmt.Errorf("attach tc hooks: %w", err)
closeFuncs = append(closeFuncs, closeFunc)
return closeFuncs, fmt.Errorf("attach tc hooks: %w", err)
}

if egress {
c1, err := attachTcHook(ifindex, b.objs.TcEgress, false)
if err != nil {
if c1 != nil {
c1()
}
closeFunc()
return fmt.Errorf("attach tc hooks: %w", err)
closeFuncs = append(closeFuncs, c1)
return closeFuncs, fmt.Errorf("attach tc hooks: %w", err)
}
b.closeFuncs = append(b.closeFuncs, c1)
closeFuncs = append(closeFuncs, c1)
}

if ingress {
c2, err := attachTcHook(ifindex, b.objs.TcIngress, true)
if err != nil {
if c2 != nil {
c2()
}
closeFunc()
return fmt.Errorf("attach tc hooks: %w", err)
closeFuncs = append(closeFuncs, c2)
return closeFuncs, fmt.Errorf("attach tc hooks: %w", err)
}
b.closeFuncs = append(b.closeFuncs, c2)
closeFuncs = append(closeFuncs, c2)
}

return nil
return closeFuncs, nil
}

func (opts Options) attachForks() bool {
Expand Down
25 changes: 14 additions & 11 deletions cmd/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import (
"github.com/mozillazg/ptcpdump/internal/utils"
)

func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {
func capture(ctx context.Context, stop context.CancelFunc, opts *Options) error {
devices, err := opts.GetDevices()
if err != nil {
return err
}
btfSpec, btfPath, err := btf.LoadBTFSpec(opts.btfPath)
if err != nil {
return err
Expand All @@ -28,7 +32,7 @@ func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {

log.Info("start process and container cache")
pcache := metadata.NewProcessCache()
cc, _ := applyContainerFilter(ctx, &opts)
cc, _ := applyContainerFilter(ctx, opts)
if cc != nil {
pcache.WithContainerCache(cc)
}
Expand All @@ -45,7 +49,7 @@ func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {
fcloser()
}
}()
gcr, err := getGoKeyLogEventConsumer(&opts, writers)
gcr, err := getGoKeyLogEventConsumer(opts, writers)
if err != nil {
return err
}
Expand All @@ -68,14 +72,12 @@ func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {
conns := getCurrentConnects(ctx, pcache, opts)

log.Info("start attach hooks")
bf, err := attachHooks(btfSpec, conns, opts)
bf, closers, err := attachHooks(btfSpec, conns, opts)
if err != nil {
if bf != nil {
bf.Close()
}
runClosers(closers)
return err
}
defer bf.Close()
defer runClosers(closers)

packetEvensCh, err := bf.PullPacketEvents(ctx, int(opts.eventChanSize), int(opts.snapshotLength))
if err != nil {
Expand Down Expand Up @@ -104,7 +106,8 @@ func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {
go gcr.Start(ctx, goTlsKeyLogEventsCh)

var stopByInternal bool
packetConsumer := consumer.NewPacketEventConsumer(writers).WithDelay(opts.delayBeforeHandlePacketEvents)
packetConsumer := consumer.NewPacketEventConsumer(writers, devices).
WithDelay(opts.delayBeforeHandlePacketEvents)
if subProcessLoaderPid > 0 {
go func() {
log.Infof("notify loader %d to start sub process", subProcessLoaderPid)
Expand Down Expand Up @@ -136,7 +139,7 @@ func capture(ctx context.Context, stop context.CancelFunc, opts Options) error {
return nil
}

func headerTips(opts Options) {
func headerTips(opts *Options) {
interfaces := opts.ifaces[0]
if len(opts.ifaces) > 1 {
interfaces = fmt.Sprintf("[%s]", strings.Join(opts.ifaces, ", "))
Expand Down Expand Up @@ -177,7 +180,7 @@ func getCaptureCounts(bf *bpf.BPF, c *consumer.PacketEventConsumer) []string {
return ret
}

func getCurrentConnects(ctx context.Context, pcache *metadata.ProcessCache, opts Options) []metadata.Connection {
func getCurrentConnects(ctx context.Context, pcache *metadata.ProcessCache, opts *Options) []metadata.Connection {
var pids []int
var filterPid bool

Expand Down
73 changes: 52 additions & 21 deletions cmd/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@ import (
btftype "github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/rlimit"
"github.com/mozillazg/ptcpdump/bpf"
"github.com/mozillazg/ptcpdump/internal/dev"
"github.com/mozillazg/ptcpdump/internal/log"
"github.com/mozillazg/ptcpdump/internal/metadata"
"github.com/mozillazg/ptcpdump/internal/utils"
)

func attachHooks(btfSpec *btftype.Spec, currentConns []metadata.Connection, opts Options) (*bpf.BPF, error) {
devices, err := dev.GetDevices(opts.ifaces)
func attachHooks(btfSpec *btftype.Spec, currentConns []metadata.Connection, opts *Options) (*bpf.BPF, []func(), error) {
devices, err := opts.GetDevices()
if err != nil {
return nil, err
return nil, nil, err
}
if err := rlimit.RemoveMemlock(); err != nil {
return nil, err
return nil, nil, err
}
bf, err := bpf.NewBPF()
if err != nil {
return nil, err
return nil, nil, err
}
bpfopts := &bpf.Options{}
bpfopts = bpfopts.WithPids(opts.pids).
Expand All @@ -39,12 +38,14 @@ func attachHooks(btfSpec *btftype.Spec, currentConns []metadata.Connection, opts
WithKernelTypes(btfSpec)

if err := bf.Load(*bpfopts); err != nil {
return nil, err
return nil, nil, err
}
var finalCloseFuncs []func()
finalCloseFuncs = append(finalCloseFuncs, bf.Close)

if len(currentConns) > 0 {
if err := updateFlowPidMapValues(bf, currentConns); err != nil {
return nil, err
return bf, finalCloseFuncs, err
}
}

Expand All @@ -54,32 +55,62 @@ func attachHooks(btfSpec *btftype.Spec, currentConns []metadata.Connection, opts
}
if cgroupPath != "" {
if err := bf.AttachCgroups(cgroupPath); err != nil {
return bf, err
return bf, finalCloseFuncs, err
}
}

if err := attachGoTLSHooks(opts, bf); err != nil {
return bf, err
return bf, finalCloseFuncs, err
}
if err := bf.AttachKprobes(); err != nil {
return bf, err
return bf, finalCloseFuncs, err
}
if err := bf.AttachTracepoints(); err != nil {
return bf, err
return bf, finalCloseFuncs, err
}
for _, iface := range devices {
if err := bf.AttachTcHooks(iface.Ifindex, opts.DirectionOut(), opts.DirectionIn()); err != nil {
// TODO: use errors.Is(xxx) or ==
if strings.Contains(err.Error(), "netlink receive: no such file or directory") ||
strings.Contains(err.Error(), "netlink receive: no such device") {
log.Warnf("skip interface %s due to %s", iface.Name, err)
continue

for _, iface := range devices.Devs() {
var finalErr error
log.Infof("start to attach tc hook to %s in netns %s", iface.Name, iface.NetNs)
err := iface.NetNs.Do(func() {
closeFuncs, err := bf.AttachTcHooks(iface.Ifindex, opts.DirectionOut(), opts.DirectionIn())
if err != nil {
runClosers(closeFuncs)
// TODO: use errors.Is(xxx) or ==
if strings.Contains(err.Error(), "netlink receive: no such file or directory") ||
strings.Contains(err.Error(), "netlink receive: no such device") {
log.Warnf("skip interface %s due to %s", iface.Name, err)
return
}
finalErr = err
} else {
finalCloseFuncs = append(finalCloseFuncs, func() {
iface.NetNs.Do(func() {
runClosers(closeFuncs)
})
})
}
return bf, fmt.Errorf("attach tc hooks for interface %d.%s: %w", iface.Ifindex, iface.Name, err)
})
if finalErr == nil {
finalErr = err
}
if finalErr != nil {
return bf, finalCloseFuncs, fmt.Errorf("attach tc hooks for interface %d.%s: %w",
iface.Ifindex, iface.Name, finalErr)
}

}

return bf, nil
return bf, finalCloseFuncs, nil
}

func runClosers(funcs []func()) {
for i := len(funcs) - 1; i >= 0; i-- {
f := funcs[i]
if f != nil {
f()
}
}
}

func updateFlowPidMapValues(bf *bpf.BPF, conns []metadata.Connection) error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/gotls.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func getGoKeyLogEventConsumer(opts *Options, packetWriters []writer.PacketWriter
return cr, nil
}

func attachGoTLSHooks(opts Options, bf *bpf.BPF) error {
func attachGoTLSHooks(opts *Options, bf *bpf.BPF) error {
if !opts.shouldEnableGoTLSHooks() {
log.Info("skip go tls hooks")
return nil
Expand Down
16 changes: 4 additions & 12 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,16 @@ package cmd

import (
"fmt"
"sort"
"strings"

"github.com/mozillazg/ptcpdump/internal/dev"
)

func listInterfaces() error {
devices, err := dev.GetDevices(nil)
func listInterfaces(opts Options) error {
opts.ifaces = nil
devices, err := opts.GetDevices()
if err != nil {
return err
}
var interfaces []dev.Device
for _, d := range devices {
interfaces = append(interfaces, d)
}
sort.Slice(interfaces, func(i, j int) bool {
return interfaces[i].Ifindex < interfaces[j].Ifindex
})
interfaces := devices.Devs()

outputs := []string{}
for _, d := range interfaces {
Expand Down
42 changes: 42 additions & 0 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package cmd

import (
"fmt"
"github.com/mozillazg/ptcpdump/internal/dev"
"github.com/mozillazg/ptcpdump/internal/log"
"github.com/mozillazg/ptcpdump/internal/utils"
"github.com/mozillazg/ptcpdump/internal/writer"
"github.com/x-way/pktdump"
"os"
Expand Down Expand Up @@ -69,6 +72,9 @@ type Options struct {
mntnsIds []uint32
netnsIds []uint32
pidnsIds []uint32

netNsPaths []string
devices *dev.Interfaces
}

func (o Options) filterByContainer() bool {
Expand Down Expand Up @@ -194,3 +200,39 @@ func (o Options) getWriteTLSKeyLogPath() string {
}
return os.Getenv("SSLKEYLOGFILE")
}

func (o *Options) GetDevices() (*dev.Interfaces, error) {
if o.devices != nil {
return o.devices, nil
}

if len(o.netNsPaths) == 0 {
o.netNsPaths = append(o.netNsPaths, "")
}
if o.netNsPaths[0] == "any" {
o.netNsPaths = []string{""}
ps, err := utils.GetAllNamedNetNsName()
if err != nil {
return nil, err
}
o.netNsPaths = append(o.netNsPaths, ps...)
}
log.Infof("o.netNsPaths=%v", o.netNsPaths)

devices := dev.NewInterfaces()
ifaces := o.ifaces
if len(ifaces) > 0 && ifaces[0] == "any" {
ifaces = nil
}

for _, p := range o.netNsPaths {
devs, err := dev.GetDevices(ifaces, p)
if err != nil {
return nil, err
}
devices.Merge(devs)
}

o.devices = devices
return o.devices, nil
}
Loading

0 comments on commit cdd4253

Please sign in to comment.