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 }