Add a pcap-split tool

This commit is contained in:
2026-10-05 20:16:01 +02:00
parent 5b5cf211d0
commit c11c893845
13 changed files with 769 additions and 22 deletions
+91
View File
@@ -0,0 +1,91 @@
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
}