From ec6f9c63d2abb0e373b751bc8f9d6edfc62e0fe6 Mon Sep 17 00:00:00 2001 From: Hang Yan Date: Wed, 20 Nov 2024 04:49:07 +0800 Subject: [PATCH] Fix packetcapture bpf filter issue (#6815) (#6821) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In PacketCapture, packets which don’t match the target BPF can be received after the socket is created and before the bpf filter is applied. This patch uses a zero bpf filter (matches no packet), then empties out any packets that arrived before the "zero-BPF" filter was applied. At this point the socket is definitely empty and it can’t fill up with junk because the zero-BPF is in place. Then we replace the zero-BPF with the real BPF we want. Signed-off-by: Hang Yan Co-authored-by: Antonin Bas --- pkg/agent/packetcapture/capture/pcap_linux.go | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/pkg/agent/packetcapture/capture/pcap_linux.go b/pkg/agent/packetcapture/capture/pcap_linux.go index 0656a118123..58cb7559ec8 100644 --- a/pkg/agent/packetcapture/capture/pcap_linux.go +++ b/pkg/agent/packetcapture/capture/pcap_linux.go @@ -17,6 +17,7 @@ package capture import ( "context" "net" + "time" "github.com/gopacket/gopacket" "github.com/gopacket/gopacket/layers" @@ -39,6 +40,12 @@ func NewPcapCapture() (*pcapCapture, error) { return &pcapCapture{}, nil } +// zeroFilter is a filter that will drop all packets. +// see: https://github.com/antrea-io/antrea/issues/6815 for the user case. +func zeroFilter() []bpf.Instruction { + return []bpf.Instruction{returnDrop} +} + func (p *pcapCapture) Capture(ctx context.Context, device string, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet) (chan gopacket.Packet, error) { // Compile the BPF filter in advance to reduce the time window between starting the capture and applying the filter. inst := compilePacketFilter(packet, srcIP, dstIP) @@ -48,6 +55,11 @@ func (p *pcapCapture) Capture(ctx context.Context, device string, srcIP, dstIP n return nil, err } + zeroRawInst, err := bpf.Assemble(zeroFilter()) + if err != nil { + return nil, err + } + eth, err := pcapgo.NewEthernetHandle(device) if err != nil { return nil, err @@ -55,7 +67,17 @@ func (p *pcapCapture) Capture(ctx context.Context, device string, srcIP, dstIP n if err = eth.SetPromiscuous(false); err != nil { return nil, err } - if err = eth.SetBPF(rawInst); err != nil { + // Install a BPF filter that won't match any packets + // see: https://natanyellin.com/posts/ebpf-filtering-done-right/. + // Packets which don’t match the target BPF can be received after the socket + // is created and before setsockopt is called. Those packets will remain + // in the socket’s buffer even after the BPF is applied and will later + // be transferred to the application via recv. Here we use a zero + // bpf filter(match no packet), then empty out any packets that arrived + // before the “zero-BPF” filter was applied. At this point the socket is + // definitely empty and it can’t fill up with junk because the zero-BPF + // is in place. Then we replace the zero-BPF with the real BPF we want. + if err = eth.SetBPF(zeroRawInst); err != nil { return nil, err } if err = eth.SetCaptureLength(maxSnapshotBytes); err != nil { @@ -63,6 +85,22 @@ func (p *pcapCapture) Capture(ctx context.Context, device string, srcIP, dstIP n } packetSource := gopacket.NewPacketSource(eth, layers.LinkTypeEthernet, gopacket.WithNoCopy(true)) - return packetSource.PacketsCtx(ctx), nil - + packetCh := packetSource.PacketsCtx(ctx) + // Drain the channel + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-packetCh: + klog.V(5).InfoS("Found irrelevant packet, discard it", "device", device) + break + case <-time.After(50 * time.Millisecond): + // timeout: channel is drained so socket is drained + // install the correct BPF filter + if err := eth.SetBPF(rawInst); err != nil { + return nil, err + } + return packetCh, nil + } + } }