package pcap import ( "bufio" "encoding/binary" "errors" "fmt" "io" "slices" ) // MaxRecordLen is the largest captured packet length Reader accepts; larger // values are taken as a sign of a corrupt file. const MaxRecordLen = 64 << 20 // ErrTruncated is returned by Reader.Next when the file ends in the middle of // a packet record. var ErrTruncated = errors.New("pcap: file ends with a truncated packet record") // Reader reads a classic pcap file, in either byte order and with microsecond // or nanosecond timestamps. It returns records in their raw on-disk form so // they can be copied unchanged to another file with the same header. type Reader struct { r *bufio.Reader order binary.ByteOrder header [24]byte linkType uint32 rec []byte n int // records read } // NewReader reads and validates the pcap file header. func NewReader(r io.Reader) (*Reader, error) { pr := &Reader{r: bufio.NewReaderSize(r, 1<<20)} if _, err := io.ReadFull(pr.r, pr.header[:]); err != nil { if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { return nil, errors.New("not a pcap file: shorter than the 24 byte file header") } return nil, err } switch magic := binary.LittleEndian.Uint32(pr.header[0:4]); magic { case 0xa1b2c3d4, 0xa1b23c4d: // microsecond, nanosecond pr.order = binary.LittleEndian case 0xd4c3b2a1, 0x4d3cb2a1: pr.order = binary.BigEndian case 0x0a0d0d0a: return nil, errors.New("this is a pcapng file, which is not supported; convert it with: editcap -F pcap in.pcapng out.pcap") case 0xa1b2cd34, 0x34cdb2a1: return nil, errors.New("modified (Kuznetzov) pcap files are not supported; convert with: editcap -F pcap in out.pcap") default: return nil, fmt.Errorf("not a pcap file: unknown magic number 0x%08x", magic) } pr.linkType = pr.order.Uint32(pr.header[20:24]) return pr, nil } // Header returns the raw 24 byte file header. func (r *Reader) Header() []byte { return r.header[:] } // LinkType returns the link-layer header type from the file header. func (r *Reader) LinkType() uint32 { return r.linkType } // Next returns the next raw packet record: the 16 byte record header followed // by the captured data. The slice is only valid until the next call. At the // end of the file Next returns io.EOF, or ErrTruncated if the last record is // incomplete. func (r *Reader) Next() ([]byte, error) { if cap(r.rec) < 16 { r.rec = make([]byte, 16, 64*1024) } r.rec = r.rec[:16] if _, err := io.ReadFull(r.r, r.rec); err != nil { if errors.Is(err, io.ErrUnexpectedEOF) { return nil, ErrTruncated } return nil, err // io.EOF at a record boundary } inclLen := r.order.Uint32(r.rec[8:12]) if inclLen > MaxRecordLen { return nil, fmt.Errorf("corrupt pcap file: packet %d has captured length %d", r.n+1, inclLen) } r.rec = slices.Grow(r.rec, int(inclLen))[:16+inclLen] if _, err := io.ReadFull(r.r, r.rec[16:]); err != nil { if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { return nil, ErrTruncated } return nil, err } r.n++ return r.rec, nil }