package capture import ( "fmt" "os" "github.com/gopacket/gopacket" "github.com/gopacket/gopacket/layers" "github.com/gopacket/gopacket/pcapgo" "golang.org/x/net/bpf" ) // ReadPcapFile opens a pcap file and returns a channel of packets. // If filter is non-empty, applies software BPF filtering (kernel BPF unavailable for files). // Per D-03: replaces -i as packet source. Per D-04: --filter works with --read. // The returned channel is closed when all packets have been emitted. func ReadPcapFile(path string, filter string) (<-chan gopacket.Packet, error) { f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("cannot open pcap file %q: %w", path, err) } r, err := pcapgo.NewReader(f) if err != nil { f.Close() return nil, fmt.Errorf("not a valid pcap file %q: %w", path, err) } // Compile software BPF filter if provided. var vm *bpf.VM if filter != "" { vm, err = CompileSoftwareBPF(filter) if err != nil { f.Close() return nil, err } } lt := layers.LinkType(r.LinkType()) packetSource := gopacket.NewPacketSource(r, lt) packetSource.NoCopy = false // CRITICAL: do NOT use NoCopy=true with pcapgo (Pitfall 3) packets := make(chan gopacket.Packet, 512) go func() { defer close(packets) defer f.Close() for pkt := range packetSource.Packets() { // Apply software BPF filter if active. if vm != nil { result, err := vm.Run(pkt.Data()) if err != nil || result == 0 { continue // packet does not match filter } } packets <- pkt } }() return packets, nil }