// Package capture provides network packet capture functionality for ja4sentinel package capture import ( "fmt" "sync" "github.com/google/gopacket" "github.com/google/gopacket/pcap" "ja4sentinel/api" ) // CaptureImpl implements the capture.Capture interface for packet capture type CaptureImpl struct { handle *pcap.Handle mu sync.Mutex } // New creates a new capture instance func New() *CaptureImpl { return &CaptureImpl{} } // Run starts network packet capture according to the configuration func (c *CaptureImpl) Run(cfg api.Config, out chan<- api.RawPacket) error { handle, err := pcap.OpenLive(cfg.Interface, 1600, true, pcap.BlockForever) if err != nil { return fmt.Errorf("failed to open interface %s: %w", cfg.Interface, err) } c.mu.Lock() c.handle = handle c.mu.Unlock() defer func() { c.mu.Lock() if c.handle != nil { c.handle.Close() c.handle = nil } c.mu.Unlock() }() // Apply BPF filter if provided if cfg.BPFFilter != "" { err = handle.SetBPFFilter(cfg.BPFFilter) if err != nil { return fmt.Errorf("failed to set BPF filter: %w", err) } } else { // Create default filter for monitored ports defaultFilter := buildBPFForPorts(cfg.ListenPorts) err = handle.SetBPFFilter(defaultFilter) if err != nil { return fmt.Errorf("failed to set default BPF filter: %w", err) } } packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { // Convert packet to RawPacket rawPkt := packetToRawPacket(packet) if rawPkt != nil { select { case out <- *rawPkt: // Packet sent successfully default: // Channel full, drop packet } } } return nil } // buildBPFForPorts builds a BPF filter for the specified TCP ports func buildBPFForPorts(ports []uint16) string { if len(ports) == 0 { return "tcp" } filterParts := make([]string, len(ports)) for i, port := range ports { filterParts[i] = fmt.Sprintf("tcp port %d", port) } return "(" + joinString(filterParts, ") or (") + ")" } // joinString joins strings with a separator func joinString(parts []string, sep string) string { if len(parts) == 0 { return "" } result := parts[0] for _, part := range parts[1:] { result += sep + part } return result } // packetToRawPacket converts a gopacket packet to RawPacket func packetToRawPacket(packet gopacket.Packet) *api.RawPacket { data := packet.Data() if len(data) == 0 { return nil } return &api.RawPacket{ Data: data, Timestamp: packet.Metadata().Timestamp.UnixNano(), } } // Close properly closes the capture handle func (c *CaptureImpl) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.handle != nil { c.handle.Close() c.handle = nil return nil } return nil }