github.com/gopacket/gopacket@v1.1.0/pcapgo/ngwrite.go (about)

     1  // Copyright 2018 The GoPacket Authors. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style license
     4  // that can be found in the LICENSE file in the root of the source
     5  // tree.
     6  
     7  package pcapgo
     8  
     9  import (
    10  	"bufio"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"io"
    14  	"runtime"
    15  	"time"
    16  
    17  	"github.com/gopacket/gopacket"
    18  	"github.com/gopacket/gopacket/layers"
    19  )
    20  
    21  // NgWriterOptions holds options for creating a pcapng file
    22  type NgWriterOptions struct {
    23  	// SectionInfo will be written to the section header
    24  	SectionInfo NgSectionInfo
    25  }
    26  
    27  // DefaultNgWriterOptions contain defaults for a pcapng writer used by NewWriter
    28  var DefaultNgWriterOptions = NgWriterOptions{
    29  	SectionInfo: NgSectionInfo{
    30  		Hardware:    runtime.GOARCH,
    31  		OS:          runtime.GOOS,
    32  		Application: "gopacket", //spread the word
    33  	},
    34  }
    35  
    36  // DefaultNgInterface contains default interface options used by NewWriter
    37  var DefaultNgInterface = NgInterface{
    38  	Name:                "intf0",
    39  	OS:                  runtime.GOOS,
    40  	SnapLength:          0, //unlimited
    41  	TimestampResolution: 9,
    42  }
    43  
    44  // 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.
    45  type NgWriter struct {
    46  	w       *bufio.Writer
    47  	options NgWriterOptions
    48  	intf    uint32
    49  	buf     [28]byte
    50  }
    51  
    52  // 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.
    53  // Flush must be called before the file is closed, or if eventual unwritten information should be written out to the storage device.
    54  //
    55  // Written files are in little endian format. Interface timestamp resolution is fixed to 9 (to match time.Time).
    56  func NewNgWriter(w io.Writer, linkType layers.LinkType) (*NgWriter, error) {
    57  	intf := DefaultNgInterface
    58  	intf.LinkType = linkType
    59  	return NewNgWriterInterface(w, intf, DefaultNgWriterOptions)
    60  }
    61  
    62  // NewNgWriterInterface initializes and returns a new writer. Additionally, one section and one interface (without statistics) is written to the file.
    63  // Flush must be called before the file is closed, or if eventual unwritten information should be written out to the storage device.
    64  //
    65  // Written files are in little endian format. Interface timestamp resolution is fixed to 9 (to match time.Time).
    66  func NewNgWriterInterface(w io.Writer, intf NgInterface, options NgWriterOptions) (*NgWriter, error) {
    67  	ret := &NgWriter{
    68  		w:       bufio.NewWriter(w),
    69  		options: options,
    70  	}
    71  	if err := ret.writeSectionHeader(); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	if _, err := ret.AddInterface(intf); err != nil {
    76  		return nil, err
    77  	}
    78  	return ret, nil
    79  }
    80  
    81  // ngOptionLength returns the needed length for one option value (without padding)
    82  func ngOptionLength(option ngOption) int {
    83  	switch val := option.raw.(type) {
    84  	case []byte:
    85  		return len(val)
    86  	case string:
    87  		return len(val)
    88  	case time.Time:
    89  		return 8
    90  	case uint64:
    91  		return 8
    92  	case uint32:
    93  		return 4
    94  	case uint8:
    95  		return 1
    96  	default:
    97  		panic("This should never happen")
    98  	}
    99  }
   100  
   101  // prepareNgOptions fills out the length value of the given options and returns the number of octets needed for all the given options including padding.
   102  func prepareNgOptions(options []ngOption) uint32 {
   103  	var ret uint32
   104  	for i, option := range options {
   105  		length := ngOptionLength(option)
   106  		options[i].length = uint16(length)
   107  		length += (4-length&3)&3 + // padding
   108  			4 //header
   109  		ret += uint32(length)
   110  	}
   111  	if ret > 0 {
   112  		ret += 4 // end of options
   113  	}
   114  	return ret
   115  }
   116  
   117  // writeOptions writes the given options to the file. prepareOptions must be called beforehand.
   118  func (w *NgWriter) writeOptions(options []ngOption) error {
   119  	if len(options) == 0 {
   120  		return nil
   121  	}
   122  
   123  	var zero [4]byte
   124  	for _, option := range options {
   125  		binary.LittleEndian.PutUint16(w.buf[0:2], uint16(option.code))
   126  		binary.LittleEndian.PutUint16(w.buf[2:4], option.length)
   127  		if _, err := w.w.Write(w.buf[:4]); err != nil {
   128  			return err
   129  		}
   130  		switch val := option.raw.(type) {
   131  		case []byte:
   132  			if _, err := w.w.Write(val); err != nil {
   133  				return err
   134  			}
   135  			padding := uint8((4 - option.length&3) & 3)
   136  			if padding < 4 {
   137  				if _, err := w.w.Write(zero[:padding]); err != nil {
   138  					return err
   139  				}
   140  			}
   141  		case string:
   142  			if _, err := w.w.Write([]byte(val)); err != nil {
   143  				return err
   144  			}
   145  			padding := uint8((4 - option.length&3) & 3)
   146  			if padding < 4 {
   147  				if _, err := w.w.Write(zero[:padding]); err != nil {
   148  					return err
   149  				}
   150  			}
   151  		case time.Time:
   152  			ts := val.UnixNano()
   153  			binary.LittleEndian.PutUint32(w.buf[:4], uint32(ts>>32))
   154  			binary.LittleEndian.PutUint32(w.buf[4:8], uint32(ts))
   155  			if _, err := w.w.Write(w.buf[:8]); err != nil {
   156  				return err
   157  			}
   158  		case uint64:
   159  			binary.LittleEndian.PutUint64(w.buf[:8], val)
   160  			if _, err := w.w.Write(w.buf[:8]); err != nil {
   161  				return err
   162  			}
   163  		case uint32:
   164  			binary.LittleEndian.PutUint32(w.buf[:4], val)
   165  			if _, err := w.w.Write(w.buf[:4]); err != nil {
   166  				return err
   167  			}
   168  		case uint8:
   169  			binary.LittleEndian.PutUint32(w.buf[:4], 0) // padding
   170  			w.buf[0] = val
   171  			if _, err := w.w.Write(w.buf[:4]); err != nil {
   172  				return err
   173  			}
   174  		default:
   175  			panic("This should never happen")
   176  		}
   177  	}
   178  
   179  	// options must be folled by an end of options option
   180  	binary.LittleEndian.PutUint16(w.buf[0:2], uint16(ngOptionCodeEndOfOptions))
   181  	binary.LittleEndian.PutUint16(w.buf[2:4], 0)
   182  	_, err := w.w.Write(w.buf[:4])
   183  	return err
   184  }
   185  
   186  // writeSectionHeader writes a section header to the file
   187  func (w *NgWriter) writeSectionHeader() error {
   188  	var scratch [4]ngOption
   189  	i := 0
   190  	info := w.options.SectionInfo
   191  	if info.Application != "" {
   192  		scratch[i].code = ngOptionCodeUserApplication
   193  		scratch[i].raw = info.Application
   194  		i++
   195  	}
   196  	if info.Comment != "" {
   197  		scratch[i].code = ngOptionCodeComment
   198  		scratch[i].raw = info.Comment
   199  		i++
   200  	}
   201  	if info.Hardware != "" {
   202  		scratch[i].code = ngOptionCodeHardware
   203  		scratch[i].raw = info.Hardware
   204  		i++
   205  	}
   206  	if info.OS != "" {
   207  		scratch[i].code = ngOptionCodeOS
   208  		scratch[i].raw = info.OS
   209  		i++
   210  	}
   211  	options := scratch[:i]
   212  
   213  	length := prepareNgOptions(options) +
   214  		24 + // header
   215  		4 // trailer
   216  
   217  	binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeSectionHeader))
   218  	binary.LittleEndian.PutUint32(w.buf[4:8], length)
   219  	binary.LittleEndian.PutUint32(w.buf[8:12], ngByteOrderMagic)
   220  	binary.LittleEndian.PutUint16(w.buf[12:14], ngVersionMajor)
   221  	binary.LittleEndian.PutUint16(w.buf[14:16], ngVersionMinor)
   222  	binary.LittleEndian.PutUint64(w.buf[16:24], 0xFFFFFFFFFFFFFFFF) // unspecified
   223  	if _, err := w.w.Write(w.buf[:24]); err != nil {
   224  		return err
   225  	}
   226  
   227  	if err := w.writeOptions(options); err != nil {
   228  		return err
   229  	}
   230  
   231  	binary.LittleEndian.PutUint32(w.buf[0:4], length)
   232  	_, err := w.w.Write(w.buf[:4])
   233  	return err
   234  }
   235  
   236  // 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.
   237  func (w *NgWriter) AddInterface(intf NgInterface) (id int, err error) {
   238  	id = int(w.intf)
   239  	w.intf++
   240  
   241  	var scratch [7]ngOption
   242  	i := 0
   243  	if intf.Name != "" {
   244  		scratch[i].code = ngOptionCodeInterfaceName
   245  		scratch[i].raw = intf.Name
   246  		i++
   247  	}
   248  	if intf.Comment != "" {
   249  		scratch[i].code = ngOptionCodeComment
   250  		scratch[i].raw = intf.Comment
   251  		i++
   252  	}
   253  	if intf.Description != "" {
   254  		scratch[i].code = ngOptionCodeInterfaceDescription
   255  		scratch[i].raw = intf.Description
   256  		i++
   257  	}
   258  	if intf.Filter != "" {
   259  		scratch[i].code = ngOptionCodeInterfaceFilter
   260  		scratch[i].raw = append([]byte{0}, []byte(intf.Filter)...)
   261  		i++
   262  	}
   263  	if intf.OS != "" {
   264  		scratch[i].code = ngOptionCodeInterfaceOS
   265  		scratch[i].raw = intf.OS
   266  		i++
   267  	}
   268  	if intf.TimestampOffset != 0 {
   269  		scratch[i].code = ngOptionCodeInterfaceTimestampOffset
   270  		scratch[i].raw = intf.TimestampOffset
   271  		i++
   272  	}
   273  	scratch[i].code = ngOptionCodeInterfaceTimestampResolution
   274  	scratch[i].raw = uint8(9) // fix resolution to nanoseconds (time.Time) in decimal
   275  	i++
   276  	options := scratch[:i]
   277  
   278  	length := prepareNgOptions(options) +
   279  		16 + // header
   280  		4 // trailer
   281  
   282  	binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeInterfaceDescriptor))
   283  	binary.LittleEndian.PutUint32(w.buf[4:8], length)
   284  	binary.LittleEndian.PutUint16(w.buf[8:10], uint16(intf.LinkType))
   285  	binary.LittleEndian.PutUint16(w.buf[10:12], 0) // reserved value
   286  	binary.LittleEndian.PutUint32(w.buf[12:16], intf.SnapLength)
   287  	if _, err := w.w.Write(w.buf[:16]); err != nil {
   288  		return 0, err
   289  	}
   290  
   291  	if err := w.writeOptions(options); err != nil {
   292  		return 0, err
   293  	}
   294  
   295  	binary.LittleEndian.PutUint32(w.buf[0:4], length)
   296  	_, err = w.w.Write(w.buf[:4])
   297  	return id, err
   298  }
   299  
   300  // WriteInterfaceStats writes the given interface statistics for the given interface id to the file. Empty values are not written.
   301  func (w *NgWriter) WriteInterfaceStats(intf int, stats NgInterfaceStatistics) error {
   302  	if intf >= int(w.intf) || intf < 0 {
   303  		return fmt.Errorf("Can't send statistics for non existent interface %d; have only %d interfaces", intf, w.intf)
   304  	}
   305  
   306  	var scratch [4]ngOption
   307  	i := 0
   308  	if !stats.StartTime.IsZero() {
   309  		scratch[i].code = ngOptionCodeInterfaceStatisticsStartTime
   310  		scratch[i].raw = stats.StartTime
   311  		i++
   312  	}
   313  	if !stats.EndTime.IsZero() {
   314  		scratch[i].code = ngOptionCodeInterfaceStatisticsEndTime
   315  		scratch[i].raw = stats.EndTime
   316  		i++
   317  	}
   318  	if stats.PacketsDropped != NgNoValue64 {
   319  		scratch[i].code = ngOptionCodeInterfaceStatisticsInterfaceDropped
   320  		scratch[i].raw = stats.PacketsDropped
   321  		i++
   322  	}
   323  	if stats.PacketsReceived != NgNoValue64 {
   324  		scratch[i].code = ngOptionCodeInterfaceStatisticsInterfaceReceived
   325  		scratch[i].raw = stats.PacketsReceived
   326  		i++
   327  	}
   328  	options := scratch[:i]
   329  
   330  	length := prepareNgOptions(options) + 24
   331  
   332  	ts := stats.LastUpdate.UnixNano()
   333  	if stats.LastUpdate.IsZero() {
   334  		ts = 0
   335  	}
   336  
   337  	binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeInterfaceStatistics))
   338  	binary.LittleEndian.PutUint32(w.buf[4:8], length)
   339  	binary.LittleEndian.PutUint32(w.buf[8:12], uint32(intf))
   340  	binary.LittleEndian.PutUint32(w.buf[12:16], uint32(ts>>32))
   341  	binary.LittleEndian.PutUint32(w.buf[16:20], uint32(ts))
   342  	if _, err := w.w.Write(w.buf[:20]); err != nil {
   343  		return err
   344  	}
   345  
   346  	if err := w.writeOptions(options); err != nil {
   347  		return err
   348  	}
   349  
   350  	binary.LittleEndian.PutUint32(w.buf[0:4], length)
   351  	_, err := w.w.Write(w.buf[:4])
   352  	return err
   353  }
   354  
   355  // 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.
   356  func (w *NgWriter) WritePacket(ci gopacket.CaptureInfo, data []byte) error {
   357  	if ci.InterfaceIndex >= int(w.intf) || ci.InterfaceIndex < 0 {
   358  		return fmt.Errorf("Can't send statistics for non existent interface %d; have only %d interfaces", ci.InterfaceIndex, w.intf)
   359  	}
   360  	if ci.CaptureLength != len(data) {
   361  		return fmt.Errorf("capture length %d does not match data length %d", ci.CaptureLength, len(data))
   362  	}
   363  	if ci.CaptureLength > ci.Length {
   364  		return fmt.Errorf("invalid capture info %+v:  capture length > length", ci)
   365  	}
   366  
   367  	length := uint32(len(data)) + 32
   368  	padding := (4 - length&3) & 3
   369  	length += padding
   370  
   371  	ts := ci.Timestamp.UnixNano()
   372  
   373  	binary.LittleEndian.PutUint32(w.buf[:4], uint32(ngBlockTypeEnhancedPacket))
   374  	binary.LittleEndian.PutUint32(w.buf[4:8], length)
   375  	binary.LittleEndian.PutUint32(w.buf[8:12], uint32(ci.InterfaceIndex))
   376  	binary.LittleEndian.PutUint32(w.buf[12:16], uint32(ts>>32))
   377  	binary.LittleEndian.PutUint32(w.buf[16:20], uint32(ts))
   378  	binary.LittleEndian.PutUint32(w.buf[20:24], uint32(ci.CaptureLength))
   379  	binary.LittleEndian.PutUint32(w.buf[24:28], uint32(ci.Length))
   380  
   381  	if _, err := w.w.Write(w.buf[:28]); err != nil {
   382  		return err
   383  	}
   384  
   385  	if _, err := w.w.Write(data); err != nil {
   386  		return err
   387  	}
   388  
   389  	binary.LittleEndian.PutUint32(w.buf[:4], 0)
   390  	_, err := w.w.Write(w.buf[4-padding : 8]) // padding + length
   391  	return err
   392  }
   393  
   394  // Flush writes out buffered data to the storage media. Must be called before closing the underlying file.
   395  func (w *NgWriter) Flush() error {
   396  	return w.w.Flush()
   397  }