Skip to content

Commit

Permalink
Fix packetcapture bpf filter issue (#6815) (#6821)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Antonin Bas <[email protected]>
  • Loading branch information
hangyan and antoninbas authored Nov 19, 2024
1 parent 8090c96 commit ec6f9c6
Showing 1 changed file with 41 additions and 3 deletions.
44 changes: 41 additions & 3 deletions pkg/agent/packetcapture/capture/pcap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package capture
import (
"context"
"net"
"time"

"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/layers"
Expand All @@ -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)
Expand All @@ -48,21 +55,52 @@ 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
}
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 {
return nil, err
}

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
}
}
}

0 comments on commit ec6f9c6

Please sign in to comment.