1
1
mirror of https://github.com/wader/fq.git synced 2024-11-29 23:27:12 +03:00
fq/format/pcap/pcapng.go
2024-02-06 01:06:59 +01:00

384 lines
13 KiB
Go

package pcap
// https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html
import (
"encoding/binary"
"net"
"github.com/wader/fq/format"
"github.com/wader/fq/format/inet/flowsdecoder"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
var pcapngLinkFrameGroup decode.Group
var pcapngTCPStreamGroup decode.Group
var pcapngIPvPacket4Group decode.Group
func init() {
interp.RegisterFormat(
format.PCAPNG,
&decode.Format{
Description: "PCAPNG packet capture",
RootArray: true,
Groups: []*decode.Group{format.Probe},
Dependencies: []decode.Dependency{
{Groups: []*decode.Group{format.Link_Frame}, Out: &pcapngLinkFrameGroup},
{Groups: []*decode.Group{format.TCP_Stream}, Out: &pcapngTCPStreamGroup},
{Groups: []*decode.Group{format.IPv4Packet}, Out: &pcapngIPvPacket4Group},
},
DecodeFn: decodePcapng,
})
}
const (
ngBigEndian = 0x1a2b3c4d
ngLittleEndian = 0x4d3c2b1a
)
var ngEndianMap = scalar.UintMapSymStr{
ngBigEndian: "big_endian",
ngLittleEndian: "little_endian",
}
const (
blockTypeSectionHeader = 0x0a0d0d0a
blockTypeInterfaceDescription = 0x00000001
blockTypeNameResolution = 0x00000004
blockTypeInterfaceStatistics = 0x00000005
blockTypeEnhancedPacketBlock = 0x00000006
)
// from https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.html#section_block_code_registry
var blockTypeMap = scalar.UintMap{
blockTypeInterfaceDescription: {Sym: "interface_description"},
0x00000002: {Description: "Packet Block"},
0x00000003: {Description: "Simple Packet Block"},
blockTypeNameResolution: {Sym: "name_resolution"},
blockTypeInterfaceStatistics: {Sym: "interface_statistics"},
blockTypeEnhancedPacketBlock: {Sym: "enhanced_packet"},
0x00000007: {Description: "IRIG Timestamp Block"},
0x00000008: {Description: "ARINC 429 in AFDX Encapsulation Information Block"},
0x00000009: {Description: "systemd Journal Export Block"},
0x0000000a: {Description: "Decryption Secrets Block"},
0x00000101: {Description: "Hone Project Machine Info Block"},
0x00000102: {Description: "Hone Project Connection Event Block"},
0x00000201: {Description: "Sysdig Machine Info Block"},
0x00000202: {Description: "Sysdig Process Info Block, version 1"},
0x00000203: {Description: "Sysdig FD List Block"},
0x00000204: {Description: "Sysdig Event Block"},
0x00000205: {Description: "Sysdig Interface List Block"},
0x00000206: {Description: "Sysdig User List Block"},
0x00000207: {Description: "Sysdig Process Info Block, version 2"},
0x00000208: {Description: "Sysdig Event Block with flags"},
0x00000209: {Description: "Sysdig Process Info Block, version 3"},
0x00000210: {Description: "Sysdig Process Info Block, version 4"},
0x00000211: {Description: "Sysdig Process Info Block, version 5"},
0x00000212: {Description: "Sysdig Process Info Block, version 6"},
0x00000213: {Description: "Sysdig Process Info Block, version 7"},
0x00000bad: {Description: "Custom Block that rewriters can copy into new files"},
0x40000bad: {Description: "Custom Block that rewriters should not copy into new files"},
blockTypeSectionHeader: {Sym: "section_header"},
}
const (
optionEnd = 0
optionComment = 1
sectionHeaderOptionHardware = 2
sectionHeaderOptionOS = 3
sectionHeaderOptionUserAppl = 4
interfaceDescriptionName = 2
interfaceDescriptionDescription = 3
interfaceDescriptionIPv4addr = 4
interfaceDescriptionMACaddr = 6
interfaceDescriptionEUIaddr = 7
interfaceDescriptionSpeed = 8
interfaceDescriptionTsresol = 9
interfaceDescriptionTzone = 10
interfaceDescriptionFilter = 11
interfaceDescriptionOS = 12
interfaceDescriptionFcslen = 13
interfaceDescriptionTsoffset = 14
enhancedPacketFlags = 2
enhancedPacketHash = 3
enhancedPacketDropcount = 4
nameResolutionDNSName = 2
nameResolutionDNSIP4addr = 3
nameResolutionDNSIP6addr = 4
interfaceStatisticsStarttime = 2
interfaceStatisticsEndtime = 3
interfaceStatisticsIfRecv = 4
interfaceStatisticsIfDrop = 5
interfaceStatisticsFilterAccept = 6
interfaceStatisticsOSDrop = 7
interfaceStatisticsUsrdeliv = 8
)
var sectionHeaderOptionsMap = scalar.UintMap{
optionEnd: {Sym: "end", Description: "End of options"},
optionComment: {Sym: "comment", Description: "Comment"},
sectionHeaderOptionHardware: {Sym: "hardware"},
sectionHeaderOptionOS: {Sym: "os"},
sectionHeaderOptionUserAppl: {Sym: "userappl"},
}
var interfaceDescriptionOptionsMap = scalar.UintMap{
optionEnd: {Sym: "end", Description: "End of options"},
optionComment: {Sym: "comment", Description: "Comment"},
interfaceDescriptionName: {Sym: "name"},
interfaceDescriptionDescription: {Sym: "description"},
interfaceDescriptionIPv4addr: {Sym: "ipv4addr"},
interfaceDescriptionMACaddr: {Sym: "macaddr"},
interfaceDescriptionEUIaddr: {Sym: "euiaddr"},
interfaceDescriptionSpeed: {Sym: "speed"},
interfaceDescriptionTsresol: {Sym: "tsresol"},
interfaceDescriptionTzone: {Sym: "tzone"},
interfaceDescriptionFilter: {Sym: "filter"},
interfaceDescriptionOS: {Sym: "os"},
interfaceDescriptionFcslen: {Sym: "fcslen"},
interfaceDescriptionTsoffset: {Sym: "tsoffset"},
}
var enhancedPacketOptionsMap = scalar.UintMap{
optionEnd: {Sym: "end", Description: "End of options"},
optionComment: {Sym: "comment", Description: "Comment"},
enhancedPacketFlags: {Sym: "flags"},
enhancedPacketHash: {Sym: "hash"},
enhancedPacketDropcount: {Sym: "dropcount"},
}
var nameResolutionOptionsMap = scalar.UintMap{
optionEnd: {Sym: "end", Description: "End of options"},
optionComment: {Sym: "comment", Description: "Comment"},
nameResolutionDNSName: {Sym: "dnsname"},
nameResolutionDNSIP4addr: {Sym: "dnsip4addr"},
nameResolutionDNSIP6addr: {Sym: "dnsip6addr"},
}
var interfaceStatisticsOptionsMap = scalar.UintMap{
optionEnd: {Sym: "end", Description: "End of options"},
optionComment: {Sym: "comment", Description: "Comment"},
interfaceStatisticsStarttime: {Sym: "starttime"},
interfaceStatisticsEndtime: {Sym: "endtime"},
interfaceStatisticsIfRecv: {Sym: "ifrecv"},
interfaceStatisticsIfDrop: {Sym: "ifdrop"},
interfaceStatisticsFilterAccept: {Sym: "filteraccept"},
interfaceStatisticsOSDrop: {Sym: "osdrop"},
interfaceStatisticsUsrdeliv: {Sym: "usrdeliv"},
}
const (
nameResolutionRecordEnd = 0x0000
nameResolutionRecordIpv4 = 0x0001
nameResolutionRecordIpv6 = 0x0002
)
var nameResolutionRecordMap = scalar.UintMapSymStr{
nameResolutionRecordEnd: "end",
nameResolutionRecordIpv4: "ipv4",
nameResolutionRecordIpv6: "ipv6",
}
func decoodeOptions(d *decode.D, opts scalar.UintMap) {
if d.BitsLeft() < 32 {
return
}
seenEnd := false
for !seenEnd {
d.FieldStruct("option", func(d *decode.D) {
code := d.FieldU16("code", opts)
length := d.FieldU16("length")
if code == optionEnd {
seenEnd = true
return
}
d.FieldUTF8NullFixedLen("value", int(length))
d.FieldRawLen("padding", int64(d.AlignBits(32)))
})
}
}
// TODO: share
var mapUToIPv4Sym = scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(s.Actual))
s.Sym = net.IP(b[:]).String()
return s, nil
})
var blockFns = map[uint64]func(d *decode.D, dc *decodeContext){
// TODO: SimplePacket
// TODO: Packet
blockTypeSectionHeader: func(d *decode.D, dc *decodeContext) {
d.FieldU32BE("byte_order_magic", ngEndianMap, scalar.UintHex)
d.FieldU16("major_version")
d.FieldU16("minor_version")
dc.sectionLength = d.FieldS64("section_length")
d.FieldArray("options", func(d *decode.D) { decoodeOptions(d, sectionHeaderOptionsMap) })
dc.sectionHeaderFound = true
},
blockTypeInterfaceDescription: func(d *decode.D, dc *decodeContext) {
typ := d.FieldU16("link_type", format.LinkTypeMap)
d.FieldU16("reserved")
d.FieldU32("snap_len")
d.FieldArray("options", func(d *decode.D) { decoodeOptions(d, interfaceDescriptionOptionsMap) })
dc.interfaceTypes[len(dc.interfaceTypes)] = int(typ)
},
blockTypeEnhancedPacketBlock: func(d *decode.D, dc *decodeContext) {
interfaceID := d.FieldU32("interface_id")
d.FieldU32("timestamp_high")
d.FieldU32("timestamp_low")
capturedLength := d.FieldU32("capture_packet_length")
d.FieldU32("original_packet_length")
bs := d.ReadAllBits(d.BitBufRange(d.Pos(), int64(capturedLength)*8))
linkType := dc.interfaceTypes[int(interfaceID)]
if fn, ok := linkToDecodeFn[linkType]; ok {
// TODO: report decode errors
_ = fn(dc.flowDecoder, bs)
}
d.FieldFormatOrRawLen(
"packet",
int64(capturedLength)*8,
&pcapngLinkFrameGroup,
format.Link_Frame_In{
Type: linkType,
IsLittleEndian: d.Endian == decode.LittleEndian,
},
)
d.FieldRawLen("padding", int64(d.AlignBits(32)))
d.FieldArray("options", func(d *decode.D) { decoodeOptions(d, enhancedPacketOptionsMap) })
},
blockTypeNameResolution: func(d *decode.D, _ *decodeContext) {
seenEnd := false
d.FieldArray("records", func(d *decode.D) {
for !seenEnd {
d.FieldStruct("record", func(d *decode.D) {
typ := d.FieldU16("type", nameResolutionRecordMap)
length := d.FieldU16("length")
if typ == nameResolutionRecordEnd {
seenEnd = true
return
}
d.FramedFn(int64(length)*8, func(d *decode.D) {
switch typ {
case nameResolutionRecordIpv4:
d.FieldU32BE("address", mapUToIPv4Sym, scalar.UintHex)
d.FieldArray("entries", func(d *decode.D) {
for !d.End() {
d.FieldUTF8Null("string")
}
})
default:
d.FieldUTF8NullFixedLen("value", int(d.BitsLeft()/8))
}
})
d.FieldRawLen("padding", int64(d.AlignBits(32)))
})
}
})
d.FieldArray("options", func(d *decode.D) { decoodeOptions(d, nameResolutionOptionsMap) })
},
blockTypeInterfaceStatistics: func(d *decode.D, _ *decodeContext) {
d.FieldU32("interface_id")
d.FieldU32("timestamp_high")
d.FieldU32("timestamp_low")
d.FieldRawLen("padding", int64(d.AlignBits(32)))
d.FieldArray("options", func(d *decode.D) { decoodeOptions(d, interfaceStatisticsOptionsMap) })
},
}
func decodeBlock(d *decode.D, dc *decodeContext) {
d.Endian = dc.endian
typ := d.FieldU32("type", blockTypeMap, scalar.UintHex)
// section header is special as it stores byte order marker
if typ == blockTypeSectionHeader {
d.SeekRel(32) // skip length
endian := d.U32BE() // force BE
switch endian {
case ngBigEndian:
dc.endian = decode.BigEndian
case ngLittleEndian:
dc.endian = decode.LittleEndian
default:
d.Fatalf("unknown endian %d", endian)
}
d.Endian = dc.endian
d.SeekRel(-64)
}
length := d.FieldU32("length") - 8
const footerLengthSize = 32
blockLen := int64(length)*8 - footerLengthSize
if blockLen <= 0 {
d.Fatalf("%d blockLen < 0", blockLen)
}
d.FramedFn(blockLen, func(d *decode.D) {
if fn, ok := blockFns[typ]; ok {
fn(d, dc)
} else {
d.FieldRawLen("data", d.BitsLeft())
}
})
d.FieldU32("footer_length")
}
func decodeSection(d *decode.D, dc *decodeContext) {
d.FieldArray("blocks", func(d *decode.D) {
sectionStart := d.Pos()
// assume and read first section header
d.FieldStruct("block", func(d *decode.D) { decodeBlock(d, dc) })
for (dc.sectionLength == -1 && !d.End()) ||
(dc.sectionLength != -1 && d.Pos()-sectionStart < dc.sectionLength*8) {
d.FieldStruct("block", func(d *decode.D) { decodeBlock(d, dc) })
}
})
}
type decodeContext struct {
endian decode.Endian
sectionLength int64
sectionHeaderFound bool
interfaceTypes map[int]int
flowDecoder *flowsdecoder.Decoder
}
func decodePcapng(d *decode.D) any {
sectionHeaders := 0
for !d.End() {
fd := flowsdecoder.New(flowsdecoder.DecoderOptions{CheckTCPOptions: false})
dc := decodeContext{
interfaceTypes: map[int]int{},
flowDecoder: fd,
}
d.FieldStruct("section", func(d *decode.D) {
decodeSection(d, &dc)
fd.Flush()
fieldFlows(d, dc.flowDecoder, pcapngTCPStreamGroup, pcapngIPvPacket4Group)
})
if dc.sectionHeaderFound {
sectionHeaders++
}
}
if sectionHeaders == 0 {
d.Fatalf("no section headers found")
}
return nil
}