sniffer-agent/vendor/github.com/google/gopacket/pcapgo/ngwrite.go

398 lines
12 KiB
Go

// Copyright 2018 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 (
"bufio"
"encoding/binary"
"fmt"
"io"
"runtime"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// NgWriterOptions holds options for creating a pcapng file
type NgWriterOptions struct {
// SectionInfo will be written to the section header
SectionInfo NgSectionInfo
}
// DefaultNgWriterOptions contain defaults for a pcapng writer used by NewWriter
var DefaultNgWriterOptions = NgWriterOptions{
SectionInfo: NgSectionInfo{
Hardware: runtime.GOARCH,
OS: runtime.GOOS,
Application: "gopacket", //spread the word
},
}
// DefaultNgInterface contains default interface options used by NewWriter
var DefaultNgInterface = NgInterface{
Name: "intf0",
OS: runtime.GOOS,
SnapLength: 0, //unlimited
TimestampResolution: 9,
}
// NgWriter holds the internal state of a pcapng file writer. Internally a bufio.NgWriter is used, therefore Flush must be called before closing the underlying file.
type NgWriter struct {
w *bufio.Writer
options NgWriterOptions
intf uint32
buf [28]byte
}
// NewNgWriter initializes and returns a new writer. Additionally, one section and one interface (without statistics) is written to the file. Interface and section options are used from DefaultNgInterface and DefaultNgWriterOptions.
// Flush must be called before the file is closed, or if eventual unwritten information should be written out to the storage device.
//
// Written files are in little endian format. Interface timestamp resolution is fixed to 9 (to match time.Time).
func NewNgWriter(w io.Writer, linkType layers.LinkType) (*NgWriter, error) {
intf := DefaultNgInterface
intf.LinkType = linkType
return NewNgWriterInterface(w, intf, DefaultNgWriterOptions)
}
// NewNgWriterInterface initializes and returns a new writer. Additionally, one section and one interface (without statistics) is written to the file.
// Flush must be called before the file is closed, or if eventual unwritten information should be written out to the storage device.
//
// Written files are in little endian format. Interface timestamp resolution is fixed to 9 (to match time.Time).
func NewNgWriterInterface(w io.Writer, intf NgInterface, options NgWriterOptions) (*NgWriter, error) {
ret := &NgWriter{
w: bufio.NewWriter(w),
options: options,
}
if err := ret.writeSectionHeader(); err != nil {
return nil, err
}
if _, err := ret.AddInterface(intf); err != nil {
return nil, err
}
return ret, nil
}
// ngOptionLength returns the needed length for one option value (without padding)
func ngOptionLength(option ngOption) int {
switch val := option.raw.(type) {
case []byte:
return len(val)
case string:
return len(val)
case time.Time:
return 8
case uint64:
return 8
case uint32:
return 4
case uint8:
return 1
default:
panic("This should never happen")
}
}
// prepareNgOptions fills out the length value of the given options and returns the number of octets needed for all the given options including padding.
func prepareNgOptions(options []ngOption) uint32 {
var ret uint32
for i, option := range options {
length := ngOptionLength(option)
options[i].length = uint16(length)
length += (4-length&3)&3 + // padding
4 //header
ret += uint32(length)
}
if ret > 0 {
ret += 4 // end of options
}
return ret
}
// writeOptions writes the given options to the file. prepareOptions must be called beforehand.
func (w *NgWriter) writeOptions(options []ngOption) error {
if len(options) == 0 {
return nil
}
var zero [4]byte
for _, option := range options {
binary.LittleEndian.PutUint16(w.buf[0:2], uint16(option.code))
binary.LittleEndian.PutUint16(w.buf[2:4], option.length)
if _, err := w.w.Write(w.buf[:4]); err != nil {
return err
}
switch val := option.raw.(type) {
case []byte:
if _, err := w.w.Write(val); err != nil {
return err
}
padding := uint8((4 - option.length&3) & 3)
if padding < 4 {
if _, err := w.w.Write(zero[:padding]); err != nil {
return err
}
}
case string:
if _, err := w.w.Write([]byte(val)); err != nil {
return err
}
padding := uint8((4 - option.length&3) & 3)
if padding < 4 {
if _, err := w.w.Write(zero[:padding]); err != nil {
return err
}
}
case time.Time:
ts := val.UnixNano()
binary.LittleEndian.PutUint32(w.buf[:4], uint32(ts>>32))
binary.LittleEndian.PutUint32(w.buf[4:8], uint32(ts))
if _, err := w.w.Write(w.buf[:8]); err != nil {
return err
}
case uint64:
binary.LittleEndian.PutUint64(w.buf[:8], val)
if _, err := w.w.Write(w.buf[:8]); err != nil {
return err
}
case uint32:
binary.LittleEndian.PutUint32(w.buf[:4], val)
if _, err := w.w.Write(w.buf[:4]); err != nil {
return err
}
case uint8:
binary.LittleEndian.PutUint32(w.buf[:4], 0) // padding
w.buf[0] = val
if _, err := w.w.Write(w.buf[:4]); err != nil {
return err
}
default:
panic("This should never happen")
}
}
// options must be folled by an end of options option
binary.LittleEndian.PutUint16(w.buf[0:2], uint16(ngOptionCodeEndOfOptions))
binary.LittleEndian.PutUint16(w.buf[2:4], 0)
_, err := w.w.Write(w.buf[:4])
return err
}
// writeSectionHeader writes a section header to the file
func (w *NgWriter) writeSectionHeader() error {
var scratch [4]ngOption
i := 0
info := w.options.SectionInfo
if info.Application != "" {
scratch[i].code = ngOptionCodeUserApplication
scratch[i].raw = info.Application
i++
}
if info.Comment != "" {
scratch[i].code = ngOptionCodeComment
scratch[i].raw = info.Comment
i++
}
if info.Hardware != "" {
scratch[i].code = ngOptionCodeHardware
scratch[i].raw = info.Hardware
i++
}
if info.OS != "" {
scratch[i].code = ngOptionCodeOS
scratch[i].raw = info.OS
i++
}
options := scratch[:i]
length := prepareNgOptions(options) +
24 + // header
4 // trailer
binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeSectionHeader))
binary.LittleEndian.PutUint32(w.buf[4:8], length)
binary.LittleEndian.PutUint32(w.buf[8:12], ngByteOrderMagic)
binary.LittleEndian.PutUint16(w.buf[12:14], ngVersionMajor)
binary.LittleEndian.PutUint16(w.buf[14:16], ngVersionMinor)
binary.LittleEndian.PutUint64(w.buf[16:24], 0xFFFFFFFFFFFFFFFF) // unspecified
if _, err := w.w.Write(w.buf[:24]); err != nil {
return err
}
if err := w.writeOptions(options); err != nil {
return err
}
binary.LittleEndian.PutUint32(w.buf[0:4], length)
_, err := w.w.Write(w.buf[:4])
return err
}
// AddInterface adds the specified interface to the file, excluding statistics. Interface timestamp resolution is fixed to 9 (to match time.Time). Empty values are not written.
func (w *NgWriter) AddInterface(intf NgInterface) (id int, err error) {
id = int(w.intf)
w.intf++
var scratch [7]ngOption
i := 0
if intf.Name != "" {
scratch[i].code = ngOptionCodeInterfaceName
scratch[i].raw = intf.Name
i++
}
if intf.Comment != "" {
scratch[i].code = ngOptionCodeComment
scratch[i].raw = intf.Comment
i++
}
if intf.Description != "" {
scratch[i].code = ngOptionCodeInterfaceDescription
scratch[i].raw = intf.Description
i++
}
if intf.Filter != "" {
scratch[i].code = ngOptionCodeInterfaceFilter
scratch[i].raw = append([]byte{0}, []byte(intf.Filter)...)
i++
}
if intf.OS != "" {
scratch[i].code = ngOptionCodeInterfaceOS
scratch[i].raw = intf.OS
i++
}
if intf.TimestampOffset != 0 {
scratch[i].code = ngOptionCodeInterfaceTimestampOffset
scratch[i].raw = intf.TimestampOffset
i++
}
scratch[i].code = ngOptionCodeInterfaceTimestampResolution
scratch[i].raw = uint8(9) // fix resolution to nanoseconds (time.Time) in decimal
i++
options := scratch[:i]
length := prepareNgOptions(options) +
16 + // header
4 // trailer
binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeInterfaceDescriptor))
binary.LittleEndian.PutUint32(w.buf[4:8], length)
binary.LittleEndian.PutUint16(w.buf[8:10], uint16(intf.LinkType))
binary.LittleEndian.PutUint16(w.buf[10:12], 0) // reserved value
binary.LittleEndian.PutUint32(w.buf[12:16], intf.SnapLength)
if _, err := w.w.Write(w.buf[:16]); err != nil {
return 0, err
}
if err := w.writeOptions(options); err != nil {
return 0, err
}
binary.LittleEndian.PutUint32(w.buf[0:4], length)
_, err = w.w.Write(w.buf[:4])
return id, err
}
// WriteInterfaceStats writes the given interface statistics for the given interface id to the file. Empty values are not written.
func (w *NgWriter) WriteInterfaceStats(intf int, stats NgInterfaceStatistics) error {
if intf >= int(w.intf) || intf < 0 {
return fmt.Errorf("Can't send statistics for non existent interface %d; have only %d interfaces", intf, w.intf)
}
var scratch [4]ngOption
i := 0
if !stats.StartTime.IsZero() {
scratch[i].code = ngOptionCodeInterfaceStatisticsStartTime
scratch[i].raw = stats.StartTime
i++
}
if !stats.EndTime.IsZero() {
scratch[i].code = ngOptionCodeInterfaceStatisticsEndTime
scratch[i].raw = stats.EndTime
i++
}
if stats.PacketsDropped != NgNoValue64 {
scratch[i].code = ngOptionCodeInterfaceStatisticsInterfaceDropped
scratch[i].raw = stats.PacketsDropped
i++
}
if stats.PacketsReceived != NgNoValue64 {
scratch[i].code = ngOptionCodeInterfaceStatisticsInterfaceReceived
scratch[i].raw = stats.PacketsReceived
i++
}
options := scratch[:i]
length := prepareNgOptions(options) + 24
ts := stats.LastUpdate.UnixNano()
if stats.LastUpdate.IsZero() {
ts = 0
}
binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeInterfaceStatistics))
binary.LittleEndian.PutUint32(w.buf[4:8], length)
binary.LittleEndian.PutUint32(w.buf[8:12], uint32(intf))
binary.LittleEndian.PutUint32(w.buf[12:16], uint32(ts>>32))
binary.LittleEndian.PutUint32(w.buf[16:20], uint32(ts))
if _, err := w.w.Write(w.buf[:20]); err != nil {
return err
}
if err := w.writeOptions(options); err != nil {
return err
}
binary.LittleEndian.PutUint32(w.buf[0:4], length)
_, err := w.w.Write(w.buf[:4])
return err
}
// WritePacket writes out packet with the given data and capture info. The given InterfaceIndex must already be added to the file. InterfaceIndex 0 is automatically added by the NewWriter* methods.
func (w *NgWriter) WritePacket(ci gopacket.CaptureInfo, data []byte) error {
if ci.InterfaceIndex >= int(w.intf) || ci.InterfaceIndex < 0 {
return fmt.Errorf("Can't send statistics for non existent interface %d; have only %d interfaces", ci.InterfaceIndex, w.intf)
}
if ci.CaptureLength != len(data) {
return fmt.Errorf("capture length %d does not match data length %d", ci.CaptureLength, len(data))
}
if ci.CaptureLength > ci.Length {
return fmt.Errorf("invalid capture info %+v: capture length > length", ci)
}
length := uint32(len(data)) + 32
padding := (4 - length&3) & 3
length += padding
ts := ci.Timestamp.UnixNano()
binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeEnhancedPacket))
binary.LittleEndian.PutUint32(w.buf[4:8], length)
binary.LittleEndian.PutUint32(w.buf[8:12], uint32(ci.InterfaceIndex))
binary.LittleEndian.PutUint32(w.buf[12:16], uint32(ts>>32))
binary.LittleEndian.PutUint32(w.buf[16:20], uint32(ts))
binary.LittleEndian.PutUint32(w.buf[20:24], uint32(ci.CaptureLength))
binary.LittleEndian.PutUint32(w.buf[24:28], uint32(ci.Length))
if _, err := w.w.Write(w.buf[:28]); err != nil {
return err
}
if _, err := w.w.Write(data); err != nil {
return err
}
binary.LittleEndian.PutUint32(w.buf[:4], 0)
_, err := w.w.Write(w.buf[4-padding : 8]) // padding + length
return err
}
// Flush writes out buffered data to the storage media. Must be called before closing the underlying file.
func (w *NgWriter) Flush() error {
return w.w.Flush()
}