package capture import ( "fmt" "os" "telephony-inspector/internal/logger" "telephony-inspector/internal/sip" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" ) // PcapReader reads and parses pcap files type PcapReader struct { path string handle *pcap.Handle packets []*sip.Packet } // NewPcapReader creates a new pcap reader func NewPcapReader(path string) *PcapReader { logger.Info("PcapReader: Creating reader for %s", path) return &PcapReader{ path: path, packets: make([]*sip.Packet, 0), } } // ReadAll reads all SIP packets from the pcap file func (r *PcapReader) ReadAll() ([]*sip.Packet, error) { logger.Info("PcapReader: Opening file %s", r.path) // Check if file exists if _, err := os.Stat(r.path); os.IsNotExist(err) { logger.Error("PcapReader: File does not exist: %s", r.path) return nil, fmt.Errorf("file does not exist: %s", r.path) } handle, err := pcap.OpenOffline(r.path) if err != nil { logger.Error("PcapReader: Failed to open pcap: %v", err) return nil, fmt.Errorf("failed to open pcap: %w", err) } defer handle.Close() logger.Info("PcapReader: File opened successfully, link type: %v", handle.LinkType()) // Try setting BPF filter (optional) if err := handle.SetBPFFilter("port 5060 or port 5061"); err != nil { logger.Warn("PcapReader: Could not set BPF filter: %v (continuing without filter)", err) } else { logger.Debug("PcapReader: BPF filter set for SIP ports") } packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) totalPackets := 0 sipPackets := 0 for packet := range packetSource.Packets() { totalPackets++ logger.Debug("PcapReader: Processing packet %d, layers: %v", totalPackets, packet.Layers()) sipPacket := r.extractSIPPacket(packet, totalPackets) if sipPacket != nil { sipPackets++ r.packets = append(r.packets, sipPacket) logger.Info("PcapReader: Found SIP packet %d: %s %s", sipPackets, func() string { if sipPacket.IsRequest { return string(sipPacket.Method) } else { return fmt.Sprintf("%d", sipPacket.StatusCode) } }(), sipPacket.CallID) } } logger.Info("PcapReader: Finished reading. Total packets: %d, SIP packets: %d", totalPackets, sipPackets) return r.packets, nil } // extractSIPPacket extracts SIP data from a gopacket func (r *PcapReader) extractSIPPacket(packet gopacket.Packet, packetNum int) *sip.Packet { // Get network layer for IPs var srcIP, dstIP string var srcPort, dstPort int var payload []byte if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { ip := ipLayer.(*layers.IPv4) srcIP = ip.SrcIP.String() dstIP = ip.DstIP.String() logger.Debug("PcapReader: Packet %d IPv4 %s -> %s", packetNum, srcIP, dstIP) } else if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil { ip := ipLayer.(*layers.IPv6) srcIP = ip.SrcIP.String() dstIP = ip.DstIP.String() logger.Debug("PcapReader: Packet %d IPv6 %s -> %s", packetNum, srcIP, dstIP) } else { logger.Debug("PcapReader: Packet %d has no IP layer", packetNum) } // Get transport layer for ports AND payload if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil { udp := udpLayer.(*layers.UDP) srcPort = int(udp.SrcPort) dstPort = int(udp.DstPort) payload = udp.Payload logger.Debug("PcapReader: Packet %d UDP %d -> %d, payload len: %d", packetNum, srcPort, dstPort, len(payload)) } else if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { tcp := tcpLayer.(*layers.TCP) srcPort = int(tcp.SrcPort) dstPort = int(tcp.DstPort) payload = tcp.Payload logger.Debug("PcapReader: Packet %d TCP %d -> %d, payload len: %d", packetNum, srcPort, dstPort, len(payload)) } else { logger.Debug("PcapReader: Packet %d has no TCP/UDP layer", packetNum) return nil } if len(payload) == 0 { logger.Debug("PcapReader: Packet %d has empty payload", packetNum) return nil } payloadStr := string(payload) // Log first 200 chars to see full SIP headers (only on trace/debug if needed) /* preview := payloadStr if len(preview) > 200 { preview = preview[:200] } logger.Debug("PcapReader: Packet %d full payload preview: %q", packetNum, preview) */ // Check if it looks like SIP if !isSIPPayload(payloadStr) { // logger.Debug("PcapReader: Packet %d is not SIP", packetNum) return nil } logger.Debug("PcapReader: Packet %d detected as SIP, parsing...", packetNum) // Parse the SIP message sipPacket, err := sip.Parse(payloadStr) if err != nil { logger.Warn("PcapReader: Packet %d SIP parse error: %v", packetNum, err) return nil } if sipPacket == nil { logger.Warn("PcapReader: Packet %d SIP parse returned nil", packetNum) return nil } // Set network info sipPacket.SourceIP = srcIP sipPacket.SourcePort = srcPort sipPacket.DestIP = dstIP sipPacket.DestPort = dstPort sipPacket.Timestamp = packet.Metadata().Timestamp return sipPacket } // isSIPPayload checks if payload looks like a SIP message func isSIPPayload(payload string) bool { if len(payload) < 4 { return false } // Check for SIP request methods methods := []string{"INVITE ", "ACK ", "BYE ", "CANCEL ", "REGISTER ", "OPTIONS ", "PRACK ", "SUBSCRIBE ", "NOTIFY ", "PUBLISH ", "INFO ", "REFER ", "MESSAGE ", "UPDATE "} for _, m := range methods { if len(payload) >= len(m) && payload[:len(m)] == m { logger.Debug("isSIPPayload: Detected SIP method: %s", m[:len(m)-1]) return true } } // Check for SIP response if len(payload) >= 7 && payload[:7] == "SIP/2.0" { logger.Debug("isSIPPayload: Detected SIP response") return true } // Check if this looks like an SDP body (might be reassembled SIP) if len(payload) >= 4 && payload[:4] == "v=0\r" { logger.Debug("isSIPPayload: Detected SDP-only payload (no SIP headers)") // This is SDP without SIP headers - likely a reassembly issue // Log first 50 chars for debugging preview := payload if len(preview) > 50 { preview = preview[:50] } logger.Debug("isSIPPayload: SDP payload: %q", preview) return false } // Log what the payload starts with for debugging preview := payload if len(preview) > 20 { preview = preview[:20] } logger.Debug("isSIPPayload: Unrecognized payload start: %q", preview) return false } // GetPacketCount returns number of packets read func (r *PcapReader) GetPacketCount() int { return len(r.packets) } // Close closes the reader func (r *PcapReader) Close() error { if r.handle != nil { r.handle.Close() } return nil } // ListPcapFiles lists .pcap files in a directory func ListPcapFiles(dir string) ([]string, error) { entries, err := os.ReadDir(dir) if err != nil { return nil, err } var files []string for _, e := range entries { if e.IsDir() { continue } name := e.Name() if len(name) > 5 && (name[len(name)-5:] == ".pcap" || name[len(name)-7:] == ".pcapng") { files = append(files, name) } } return files, nil }