feat(04-01): add BPF validation, pcap reading, Timestamp field, and filter support

- Add Timestamp time.Time field to ClassifiedPacket (classify/types.go)
- Create capture/bpf.go: ValidateBPFFilter and CompileSoftwareBPF
- Create capture/pcap_reader.go: ReadPcapFile with optional software BPF filter
- Update OpenCapture and StartCapture to accept filter string param
- Update cmd/netsynth/main.go to pass empty filter to StartCapture
- All new tests pass; existing tests unaffected
This commit is contained in:
2026-03-26 14:33:29 +01:00
parent 38c74415ed
commit 52c601019b
7 changed files with 353 additions and 9 deletions
+11 -3
View File
@@ -23,8 +23,9 @@ func ListInterfaces() ([]net.Interface, error) {
// OpenCapture opens a live capture handle on the given interface.
// snaplen=65535, promiscuous=false, timeout=0 (block until packet).
// If filter is non-empty, applies a BPF filter to the handle (CAPT-05).
// Returns a platform-specific error message if permission is denied (CAPT-04, D-05).
func OpenCapture(ctx context.Context, iface string) (*pcap.Handle, error) {
func OpenCapture(ctx context.Context, iface string, filter string) (*pcap.Handle, error) {
handle, err := pcap.OpenLive(ctx, iface, 65535, false, 0, false)
if err != nil {
if isPermissionError(err) {
@@ -32,15 +33,22 @@ func OpenCapture(ctx context.Context, iface string) (*pcap.Handle, error) {
}
return nil, fmt.Errorf("failed to open interface %q: %w", iface, err)
}
if filter != "" {
if err := handle.SetBPFFilter(filter); err != nil {
handle.Close()
return nil, fmt.Errorf("invalid BPF filter %q: %w", filter, err)
}
}
return handle, nil
}
// StartCapture opens a capture handle and returns a channel of gopacket.Packet.
// The channel is closed when ctx is cancelled (Ctrl+C) or capture ends.
// If filter is non-empty, applies a BPF filter to the handle (CAPT-05).
// Uses non-blocking send with drop counter per Pitfall 4 from RESEARCH.md.
// Uses dynamic link type detection per Pitfall 6 from RESEARCH.md.
func StartCapture(ctx context.Context, iface string) (<-chan gopacket.Packet, *int64, error) {
handle, err := OpenCapture(ctx, iface)
func StartCapture(ctx context.Context, iface string, filter string) (<-chan gopacket.Packet, *int64, error) {
handle, err := OpenCapture(ctx, iface, filter)
if err != nil {
return nil, nil, err
}