172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
// Copyright 2019 The GoPacket Authors. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file in the root of the source
|
|
// tree.
|
|
|
|
package pcapgo
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
)
|
|
|
|
const snoopMagic uint64 = 0x736e6f6f70000000 //8 byte in big endian
|
|
const snoopVersion uint32 = 2
|
|
const defaultBufLen uint32 = 150
|
|
const maxCaptureLen int = 4096
|
|
|
|
// Errors
|
|
const unknownMagic = "Unknown Snoop Magic Bytes"
|
|
const unknownVersion = "Unknown Snoop Format Version"
|
|
const unkownLinkType = "Unknown Link Type"
|
|
const originalLenExceeded = "Capture length exceeds original packet length"
|
|
const captureLenExceeded = "Capture length exceeds max capture length"
|
|
|
|
type snoopHeader struct {
|
|
Version uint32
|
|
linkType uint32
|
|
}
|
|
|
|
// SnoopReader wraps an underlying io.SnoopReader to read packet data in SNOOP
|
|
// format. See https://tools.ietf.org/html/rfc1761
|
|
// for information on the file format.
|
|
// We currenty read v2 file format and convert microsecond to nanoseconds
|
|
// byte order in big-endian encoding.
|
|
type SnoopReader struct {
|
|
r io.Reader
|
|
header snoopHeader
|
|
//reuseable
|
|
pad int
|
|
packetBuf []byte
|
|
buf [24]byte
|
|
}
|
|
|
|
var (
|
|
layerTypes = map[uint32]layers.LinkType{
|
|
0: layers.LinkTypeEthernet, // IEEE 802.3
|
|
2: layers.LinkTypeTokenRing, // IEEE 802.5 Token Ring
|
|
4: layers.LinkTypeEthernet, // Ethernet
|
|
5: layers.LinkTypeC_HDLC, // HDLC
|
|
8: layers.LinkTypeFDDI, // FDDI
|
|
/*
|
|
10 - 4294967295 Unassigned
|
|
not supported:
|
|
1 - IEEE 802.4 Token Bus
|
|
3 - IEEE 802.6 Metro Net
|
|
6 - Character Synchronous
|
|
7 - IBM Channel-to-Channel
|
|
9 - Other
|
|
*/
|
|
}
|
|
)
|
|
|
|
// LinkType return the mapped gopacket LinkType
|
|
func (r *SnoopReader) LinkType() (*layers.LinkType, error) {
|
|
if _, ok := layerTypes[r.header.linkType]; ok {
|
|
lt := layerTypes[r.header.linkType]
|
|
return <, nil
|
|
}
|
|
return nil, fmt.Errorf("%s, Code:%d", unkownLinkType, r.header.linkType)
|
|
|
|
}
|
|
|
|
// NewSnoopReader returns a new SnoopReader object, for reading packet data from
|
|
// the given SnoopReader. The SnoopReader must be open and header data is
|
|
// read from it at this point.
|
|
// If the file format is not supported an error is returned
|
|
func NewSnoopReader(r io.Reader) (*SnoopReader, error) {
|
|
ret := SnoopReader{r: r}
|
|
|
|
if err := ret.readHeader(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
func (r *SnoopReader) readHeader() error {
|
|
buf := make([]byte, 16)
|
|
|
|
if n, err := io.ReadFull(r.r, buf); err != nil {
|
|
return err
|
|
} else if n < 16 {
|
|
return errors.New("Not enough data for read")
|
|
}
|
|
|
|
if magic := binary.BigEndian.Uint64(buf[0:8]); magic != snoopMagic {
|
|
return fmt.Errorf("%s: %x", unknownMagic, magic)
|
|
}
|
|
|
|
if r.header.Version = binary.BigEndian.Uint32(buf[8:12]); r.header.Version != snoopVersion {
|
|
return fmt.Errorf("%s: %d", unknownVersion, r.header.Version)
|
|
}
|
|
|
|
if r.header.linkType = binary.BigEndian.Uint32(buf[12:16]); r.header.linkType > 10 {
|
|
return fmt.Errorf("%s, Code:%d", unkownLinkType, r.header.linkType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *SnoopReader) readPacketHeader() (ci gopacket.CaptureInfo, err error) {
|
|
|
|
if _, err = io.ReadFull(r.r, r.buf[:]); err != nil {
|
|
return
|
|
}
|
|
// OriginalLength uint32 4
|
|
// IncludedLength uint32 8
|
|
// PacketRecordLength uint32 12
|
|
// CumulativeDrops uint32 16
|
|
// TimestampSeconds uint32 20
|
|
// TimestampMicroseconds uint32 24
|
|
|
|
ci.Timestamp = time.Unix(int64(binary.BigEndian.Uint32(r.buf[16:20])), int64(binary.BigEndian.Uint32(r.buf[20:24])*1000)).UTC()
|
|
ci.Length = int(binary.BigEndian.Uint32(r.buf[0:4]))
|
|
ci.CaptureLength = int(binary.BigEndian.Uint32(r.buf[4:8]))
|
|
r.pad = int(binary.BigEndian.Uint32(r.buf[8:12])) - (24 + ci.Length)
|
|
|
|
if ci.CaptureLength > ci.Length {
|
|
err = errors.New(originalLenExceeded)
|
|
return
|
|
}
|
|
|
|
if ci.CaptureLength > maxCaptureLen {
|
|
err = errors.New(captureLenExceeded)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ReadPacketData reads next packet data.
|
|
func (r *SnoopReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
|
|
if ci, err = r.readPacketHeader(); err != nil {
|
|
return
|
|
}
|
|
data = make([]byte, ci.CaptureLength+r.pad)
|
|
_, err = io.ReadFull(r.r, data)
|
|
return data[:ci.CaptureLength], ci, err
|
|
|
|
}
|
|
|
|
// ZeroCopyReadPacketData reads next packet data. The data buffer is owned by the SnoopReader,
|
|
// and each call to ZeroCopyReadPacketData invalidates data returned by the previous one.
|
|
//
|
|
// It is not true zero copy, as data is still copied from the underlying SnoopReader. However,
|
|
// this method avoids allocating heap memory for every packet.
|
|
func (r *SnoopReader) ZeroCopyReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
|
|
if ci, err = r.readPacketHeader(); err != nil {
|
|
return
|
|
}
|
|
|
|
if cap(r.packetBuf) < ci.CaptureLength+r.pad {
|
|
r.packetBuf = make([]byte, ci.CaptureLength+r.pad)
|
|
}
|
|
_, err = io.ReadFull(r.r, r.packetBuf[:ci.CaptureLength+r.pad])
|
|
return r.packetBuf[:ci.CaptureLength], ci, err
|
|
}
|