github.com/gopacket/gopacket@v1.1.0/pcapgo/ngread.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 "errors" 13 "fmt" 14 "io" 15 "time" 16 17 "github.com/gopacket/gopacket" 18 "github.com/gopacket/gopacket/layers" 19 ) 20 21 // NgReaderOptions holds options for reading a pcapng file 22 type NgReaderOptions struct { 23 // WantMixedLinkType enables reading a pcapng file containing multiple interfaces with varying link types. If false all link types must match, which is the libpcap behaviour and LinkType returns the link type of the first interface. 24 // If true the link type of the packet is also exposed via ci.AncillaryData[0]. 25 WantMixedLinkType bool 26 // ErrorOnMismatchingLinkType enables returning an error if a packet with a link type not matching the first interface is encountered and WantMixedLinkType == false. 27 // If false packets those packets are just silently ignored, which is the libpcap behaviour. 28 ErrorOnMismatchingLinkType bool 29 // SkipUnknownVersion enables automatically skipping sections with an unknown version, which is recommended by the pcapng standard. Otherwise ErrVersionMismatch is returned. 30 SkipUnknownVersion bool 31 // SectionEndCallback gets called at the end of a section (execept for the last section, which is ends on EOF). The current list of interfaces and additional section information is provided. 32 // This is a good way to read interface statistics. 33 SectionEndCallback func([]NgInterface, NgSectionInfo) 34 // StatisticsCallback is called when a interface statistics block is read. The interface id and the read statistics are provided. 35 StatisticsCallback func(int, NgInterfaceStatistics) 36 } 37 38 // DefaultNgReaderOptions provides sane defaults for a pcapng reader. 39 var DefaultNgReaderOptions = NgReaderOptions{} 40 41 // NgReader wraps an underlying bufio.NgReader to read packet data in pcapng. 42 type NgReader struct { 43 r *bufio.Reader 44 options NgReaderOptions 45 sectionInfo NgSectionInfo 46 linkType layers.LinkType 47 ifaces []NgInterface 48 currentBlock ngBlock 49 currentOption ngOption 50 buf [24]byte 51 packetBuf []byte 52 ci gopacket.CaptureInfo 53 ancil [1]interface{} 54 blen int 55 firstSectionFound bool 56 activeSection bool 57 bigEndian bool 58 decryptionSecrets []decryptionSecret 59 } 60 61 // NewNgReader initializes a new writer, reads the first section header, and if necessary according to the options the first interface. 62 func NewNgReader(r io.Reader, options NgReaderOptions) (*NgReader, error) { 63 ret := &NgReader{ 64 r: bufio.NewReader(r), 65 currentOption: ngOption{ 66 value: make([]byte, 1024), 67 }, 68 decryptionSecrets: make([]decryptionSecret, 0), 69 options: options, 70 } 71 72 //pcapng _must_ start with a section header 73 if err := ret.readBlock(); err != nil { 74 return nil, err 75 } 76 if ret.currentBlock.typ != ngBlockTypeSectionHeader { 77 return nil, fmt.Errorf("Unknown magic %x", ret.currentBlock.typ) 78 } 79 80 if err := ret.readSectionHeader(); err != nil { 81 return nil, err 82 } 83 84 return ret, nil 85 } 86 87 // First a couple of helper functions to speed things up 88 89 // This is way faster than calling io.ReadFull since io.ReadFull needs an itab lookup, does an additional function call into ReadAtLeast, and ReadAtLeast does additional stuff we don't need 90 // Additionally this removes the bounds check compared to io.ReadFull due to the use of uint 91 func (r *NgReader) readBytes(buffer []byte) error { 92 n := uint(0) 93 for n < uint(len(buffer)) { 94 nn, err := r.r.Read(buffer[n:]) 95 n += uint(nn) 96 if err != nil { 97 return err 98 } 99 } 100 return nil 101 } 102 103 // The following functions make the binary.* functions inlineable (except for getUint64, which is too big, but not in any hot path anyway) 104 // Compared to storing binary.*Endian in a binary.ByteOrder this shaves off about 20% for (ZeroCopy)ReadPacketData, which is caused by the needed itab lookup + indirect go call 105 func (r *NgReader) getUint16(buffer []byte) uint16 { 106 if r.bigEndian { 107 return binary.BigEndian.Uint16(buffer) 108 } 109 return binary.LittleEndian.Uint16(buffer) 110 } 111 112 func (r *NgReader) getUint32(buffer []byte) uint32 { 113 if r.bigEndian { 114 return binary.BigEndian.Uint32(buffer) 115 } 116 return binary.LittleEndian.Uint32(buffer) 117 } 118 119 func (r *NgReader) getUint64(buffer []byte) uint64 { 120 if r.bigEndian { 121 return binary.BigEndian.Uint64(buffer) 122 } 123 return binary.LittleEndian.Uint64(buffer) 124 } 125 126 // Now the pcapng implementation 127 128 // readBlock reads a the blocktype and length from the file. If the type is a section header, endianess is also read. 129 func (r *NgReader) readBlock() error { 130 if err := r.readBytes(r.buf[0:8]); err != nil { 131 return err 132 } 133 r.currentBlock.typ = ngBlockType(r.getUint32(r.buf[0:4])) 134 // The next part is a bit fucked up since a section header could change the endianess... 135 // So first read then length just into a buffer, check if its a section header and then do the endianess part... 136 if r.currentBlock.typ == ngBlockTypeSectionHeader { 137 if err := r.readBytes(r.buf[8:12]); err != nil { 138 return err 139 } 140 if binary.BigEndian.Uint32(r.buf[8:12]) == ngByteOrderMagic { 141 r.bigEndian = true 142 } else if binary.LittleEndian.Uint32(r.buf[8:12]) == ngByteOrderMagic { 143 r.bigEndian = false 144 } else { 145 return errors.New("Wrong byte order value in Section Header") 146 } 147 // Set length to remaining length (length - (type + lengthfield = 8) - 4 for byteOrderMagic) 148 r.currentBlock.length = r.getUint32(r.buf[4:8]) - 8 - 4 149 return nil 150 } 151 // Set length to remaining length (length - (type + lengthfield = 8) 152 r.currentBlock.length = r.getUint32(r.buf[4:8]) - 8 153 return nil 154 } 155 156 // readOption reads a single arbitrary option (type and value). If there is no space left for options and end of options is missing, it is faked. 157 func (r *NgReader) readOption() error { 158 if r.currentBlock.length == 4 { 159 // no more options 160 r.currentOption.code = ngOptionCodeEndOfOptions 161 return nil 162 } 163 if err := r.readBytes(r.buf[:4]); err != nil { 164 return err 165 } 166 r.currentBlock.length -= 4 167 r.currentOption.code = ngOptionCode(r.getUint16(r.buf[:2])) 168 length := r.getUint16(r.buf[2:4]) 169 if r.currentOption.code == ngOptionCodeEndOfOptions { 170 if length != 0 { 171 return errors.New("End of Options must be zero length") 172 } 173 return nil 174 } 175 if length != 0 { 176 if length < uint16(cap(r.currentOption.value)) { 177 r.currentOption.value = r.currentOption.value[:length] 178 } else { 179 r.currentOption.value = make([]byte, length) 180 } 181 if err := r.readBytes(r.currentOption.value); err != nil { 182 return err 183 } 184 //consume padding 185 padding := length % 4 186 if padding > 0 { 187 padding = 4 - padding 188 if _, err := r.r.Discard(int(padding)); err != nil { 189 return err 190 } 191 } 192 r.currentBlock.length -= uint32(length + padding) 193 } 194 return nil 195 } 196 197 // readSectionHeader parses the full section header and implements section skipping in case of version mismatch 198 // if needed, the first interface is read 199 func (r *NgReader) readSectionHeader() error { 200 if r.options.SectionEndCallback != nil && r.activeSection { 201 interfaces := make([]NgInterface, len(r.ifaces)) 202 for i := range r.ifaces { 203 interfaces[i] = r.ifaces[i] 204 } 205 r.options.SectionEndCallback(interfaces, r.sectionInfo) 206 } 207 // clear the interfaces 208 r.ifaces = r.ifaces[:0] 209 r.activeSection = false 210 211 RESTART: 212 // read major, minor, section length 213 if err := r.readBytes(r.buf[:12]); err != nil { 214 return err 215 } 216 r.currentBlock.length -= 12 217 218 vMajor := r.getUint16(r.buf[0:2]) 219 vMinor := r.getUint16(r.buf[2:4]) 220 if vMajor != ngVersionMajor || vMinor != ngVersionMinor { 221 if !r.options.SkipUnknownVersion { 222 // Well the standard actually says to skip unknown version section headers, 223 // but this would mean user would be kept in the dark about whats going on... 224 return ErrNgVersionMismatch 225 } 226 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 227 return err 228 } 229 if err := r.skipSection(); err != nil { 230 return err 231 } 232 goto RESTART 233 } 234 235 var section NgSectionInfo 236 237 OPTIONS: 238 for { 239 if err := r.readOption(); err != nil { 240 return err 241 } 242 switch r.currentOption.code { 243 case ngOptionCodeEndOfOptions: 244 break OPTIONS 245 case ngOptionCodeComment: 246 section.Comment = string(r.currentOption.value) 247 case ngOptionCodeHardware: 248 section.Hardware = string(r.currentOption.value) 249 case ngOptionCodeOS: 250 section.OS = string(r.currentOption.value) 251 case ngOptionCodeUserApplication: 252 section.Application = string(r.currentOption.value) 253 } 254 } 255 256 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 257 return err 258 } 259 r.activeSection = true 260 r.sectionInfo = section 261 262 if !r.options.WantMixedLinkType { 263 // If we don't want mixed link type, we need the first interface to fill Reader.LinkType() 264 // This handles most of the pcapngs out there, since they start with an IDB 265 if err := r.firstInterface(); err != nil { 266 return err 267 } 268 } 269 270 return nil 271 } 272 273 // skipSection skips blocks until the next section 274 func (r *NgReader) skipSection() error { 275 for { 276 if err := r.readBlock(); err != nil { 277 return err 278 } 279 if r.currentBlock.typ == ngBlockTypeSectionHeader { 280 return nil 281 } 282 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 283 return err 284 } 285 } 286 } 287 288 // SkipSection skips the contents of the rest of the current section and reads the next section header. 289 func (r *NgReader) SkipSection() error { 290 if err := r.skipSection(); err != nil { 291 return err 292 } 293 return r.readSectionHeader() 294 } 295 296 // firstInterface reads the first interface from the section and panics if a packet is encountered. 297 func (r *NgReader) firstInterface() error { 298 for { 299 if err := r.readBlock(); err != nil { 300 return err 301 } 302 switch r.currentBlock.typ { 303 case ngBlockTypeInterfaceDescriptor: 304 if err := r.readInterfaceDescriptor(); err != nil { 305 return err 306 } 307 if !r.firstSectionFound { 308 r.linkType = r.ifaces[0].LinkType 309 r.firstSectionFound = true 310 } else if r.linkType != r.ifaces[0].LinkType { 311 if r.options.ErrorOnMismatchingLinkType { 312 return ErrNgLinkTypeMismatch 313 } 314 continue 315 } 316 return nil 317 case ngBlockTypePacket, ngBlockTypeEnhancedPacket, ngBlockTypeSimplePacket, ngBlockTypeInterfaceStatistics: 318 return errors.New("A section must have an interface before a packet block") 319 case ngBlockTypeDecryptionSecrets: 320 if err := r.readDecryptionSecretsBlock(); err != nil { 321 return err 322 } 323 } 324 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 325 return err 326 } 327 } 328 } 329 330 // readInterfaceDescriptor parses an interface descriptor, prepares timing calculation, and adds the interface details to the current list 331 func (r *NgReader) readInterfaceDescriptor() error { 332 if err := r.readBytes(r.buf[:8]); err != nil { 333 return err 334 } 335 r.currentBlock.length -= 8 336 var intf NgInterface 337 intf.LinkType = layers.LinkType(r.getUint16(r.buf[:2])) 338 intf.SnapLength = r.getUint32(r.buf[4:8]) 339 340 OPTIONS: 341 for { 342 if err := r.readOption(); err != nil { 343 return err 344 } 345 switch r.currentOption.code { 346 case ngOptionCodeEndOfOptions: 347 break OPTIONS 348 case ngOptionCodeInterfaceName: 349 intf.Name = string(r.currentOption.value) 350 case ngOptionCodeComment: 351 intf.Comment = string(r.currentOption.value) 352 case ngOptionCodeInterfaceDescription: 353 intf.Description = string(r.currentOption.value) 354 case ngOptionCodeInterfaceFilter: 355 // ignore filter type (first byte) since it is not specified 356 intf.Filter = string(r.currentOption.value[1:]) 357 case ngOptionCodeInterfaceOS: 358 intf.OS = string(r.currentOption.value) 359 case ngOptionCodeInterfaceTimestampOffset: 360 intf.TimestampOffset = r.getUint64(r.currentOption.value[:8]) 361 case ngOptionCodeInterfaceTimestampResolution: 362 intf.TimestampResolution = NgResolution(r.currentOption.value[0]) 363 } 364 } 365 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 366 return err 367 } 368 if intf.TimestampResolution == 0 { 369 intf.TimestampResolution = 6 370 } 371 372 //parse options 373 if intf.TimestampResolution.Binary() { 374 //negative power of 2 375 intf.secondMask = 1 << intf.TimestampResolution.Exponent() 376 } else { 377 //negative power of 10 378 intf.secondMask = 1 379 for j := uint8(0); j < intf.TimestampResolution.Exponent(); j++ { 380 intf.secondMask *= 10 381 } 382 } 383 intf.scaleDown = 1 384 intf.scaleUp = 1 385 if intf.secondMask < 1e9 { 386 intf.scaleUp = 1e9 / intf.secondMask 387 } else { 388 intf.scaleDown = intf.secondMask / 1e9 389 } 390 r.ifaces = append(r.ifaces, intf) 391 return nil 392 } 393 394 // convertTime adds offset + shifts the given time value according to the given interface 395 func (r *NgReader) convertTime(ifaceID int, ts uint64) (int64, int64) { 396 iface := r.ifaces[ifaceID] 397 return int64(ts/iface.secondMask + iface.TimestampOffset), int64(ts % iface.secondMask * iface.scaleUp / iface.scaleDown) 398 } 399 400 // readInterfaceStatistics updates the statistics of the given interface 401 func (r *NgReader) readInterfaceStatistics() error { 402 if err := r.readBytes(r.buf[:12]); err != nil { 403 return err 404 } 405 r.currentBlock.length -= 12 406 ifaceID := int(r.getUint32(r.buf[:4])) 407 ts := uint64(r.getUint32(r.buf[4:8]))<<32 | uint64(r.getUint32(r.buf[8:12])) 408 if int(ifaceID) >= len(r.ifaces) { 409 return fmt.Errorf("Interface id %d not present in section (have only %d interfaces)", ifaceID, len(r.ifaces)) 410 } 411 stats := &r.ifaces[ifaceID].Statistics 412 *stats = ngEmptyStatistics 413 stats.LastUpdate = time.Unix(r.convertTime(ifaceID, ts)).UTC() 414 415 OPTIONS: 416 for { 417 if err := r.readOption(); err != nil { 418 return err 419 } 420 switch r.currentOption.code { 421 case ngOptionCodeEndOfOptions: 422 break OPTIONS 423 case ngOptionCodeComment: 424 stats.Comment = string(r.currentOption.value) 425 case ngOptionCodeInterfaceStatisticsStartTime: 426 ts = uint64(r.getUint32(r.currentOption.value[:4]))<<32 | uint64(r.getUint32(r.currentOption.value[4:8])) 427 stats.StartTime = time.Unix(r.convertTime(ifaceID, ts)).UTC() 428 case ngOptionCodeInterfaceStatisticsEndTime: 429 ts = uint64(r.getUint32(r.currentOption.value[:4]))<<32 | uint64(r.getUint32(r.currentOption.value[4:8])) 430 stats.EndTime = time.Unix(r.convertTime(ifaceID, ts)).UTC() 431 case ngOptionCodeInterfaceStatisticsInterfaceReceived: 432 stats.PacketsReceived = r.getUint64(r.currentOption.value[:8]) 433 case ngOptionCodeInterfaceStatisticsInterfaceDropped: 434 stats.PacketsDropped = r.getUint64(r.currentOption.value[:8]) 435 } 436 } 437 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 438 return err 439 } 440 if r.options.StatisticsCallback != nil { 441 r.options.StatisticsCallback(ifaceID, *stats) 442 } 443 return nil 444 } 445 446 // readPacketHeader looks for a packet (enhanced, simple, or packet) and parses the header. 447 // If an interface descriptor, an interface statistics block, or a section header is encountered, those are handled accordingly. 448 // All other block types are skipped. New block types must be added here. 449 func (r *NgReader) readPacketHeader() error { 450 RESTART: 451 FIND_PACKET: 452 for { 453 if err := r.readBlock(); err != nil { 454 return err 455 } 456 switch r.currentBlock.typ { 457 case ngBlockTypeEnhancedPacket: 458 if err := r.readBytes(r.buf[:20]); err != nil { 459 return err 460 } 461 r.currentBlock.length -= 20 462 r.ci.InterfaceIndex = int(r.getUint32(r.buf[:4])) 463 if r.ci.InterfaceIndex >= len(r.ifaces) { 464 return fmt.Errorf("Interface id %d not present in section (have only %d interfaces)", r.ci.InterfaceIndex, len(r.ifaces)) 465 } 466 r.ci.Timestamp = time.Unix(r.convertTime(r.ci.InterfaceIndex, uint64(r.getUint32(r.buf[4:8]))<<32|uint64(r.getUint32(r.buf[8:12])))).UTC() 467 r.ci.CaptureLength = int(r.getUint32(r.buf[12:16])) 468 r.ci.Length = int(r.getUint32(r.buf[16:20])) 469 break FIND_PACKET 470 case ngBlockTypeSimplePacket: 471 if err := r.readBytes(r.buf[:4]); err != nil { 472 return err 473 } 474 r.currentBlock.length -= 4 475 r.ci.Timestamp = time.Time{} 476 r.ci.InterfaceIndex = 0 477 r.ci.Length = int(r.getUint32(r.buf[:4])) 478 r.ci.CaptureLength = r.ci.Length 479 if len(r.ifaces) == 0 { 480 return errors.New("At least one Interface is needed for a packet") 481 } 482 if r.ifaces[0].SnapLength != 0 && uint32(r.ci.CaptureLength) > r.ifaces[0].SnapLength { 483 r.ci.CaptureLength = int(r.ifaces[0].SnapLength) 484 } 485 break FIND_PACKET 486 case ngBlockTypeInterfaceDescriptor: 487 if err := r.readInterfaceDescriptor(); err != nil { 488 return err 489 } 490 case ngBlockTypeInterfaceStatistics: 491 if err := r.readInterfaceStatistics(); err != nil { 492 return err 493 } 494 case ngBlockTypeSectionHeader: 495 if err := r.readSectionHeader(); err != nil { 496 return err 497 } 498 case ngBlockTypePacket: 499 if err := r.readBytes(r.buf[:20]); err != nil { 500 return err 501 } 502 r.currentBlock.length -= 20 503 r.ci.InterfaceIndex = int(r.getUint16(r.buf[0:2])) 504 if r.ci.InterfaceIndex >= len(r.ifaces) { 505 return fmt.Errorf("Interface id %d not present in section (have only %d interfaces)", r.ci.InterfaceIndex, len(r.ifaces)) 506 } 507 r.ci.Timestamp = time.Unix(r.convertTime(r.ci.InterfaceIndex, uint64(r.getUint32(r.buf[4:8]))<<32|uint64(r.getUint32(r.buf[8:12])))).UTC() 508 r.ci.CaptureLength = int(r.getUint32(r.buf[12:16])) 509 r.ci.Length = int(r.getUint32(r.buf[16:20])) 510 break FIND_PACKET 511 default: 512 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 513 return err 514 } 515 } 516 } 517 if !r.options.WantMixedLinkType { 518 if r.ifaces[r.ci.InterfaceIndex].LinkType != r.linkType { 519 if _, err := r.r.Discard(int(r.currentBlock.length)); err != nil { 520 return err 521 } 522 if r.options.ErrorOnMismatchingLinkType { 523 return ErrNgLinkTypeMismatch 524 } 525 goto RESTART 526 } 527 return nil 528 } 529 r.ancil[0] = r.ifaces[r.ci.InterfaceIndex].LinkType 530 return nil 531 } 532 533 // ReadPacketData returns the next packet available from this data source. 534 // If WantMixedLinkType is true, ci.AncillaryData[0] contains the link type. 535 func (r *NgReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) { 536 if err = r.readPacketHeader(); err != nil { 537 return 538 } 539 ci = r.ci 540 if r.options.WantMixedLinkType { 541 ci.AncillaryData = make([]interface{}, 1) 542 ci.AncillaryData[0] = r.ancil[0] 543 } 544 data = make([]byte, r.ci.CaptureLength) 545 if err = r.readBytes(data); err != nil { 546 return 547 } 548 // handle options somehow - this would be expensive 549 _, err = r.r.Discard(int(r.currentBlock.length) - r.ci.CaptureLength) 550 return 551 } 552 553 // ZeroCopyReadPacketData returns the next packet available from this data source. 554 // If WantMixedLinkType is true, ci.AncillaryData[0] contains the link type. 555 // Warning: Like data, ci.AncillaryData is also reused and overwritten on the next call to ZeroCopyReadPacketData. 556 // 557 // It is not true zero copy, as data is still copied from the underlying reader. However, 558 // this method avoids allocating heap memory for every packet. 559 func (r *NgReader) ZeroCopyReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) { 560 if err = r.readPacketHeader(); err != nil { 561 return 562 } 563 ci = r.ci 564 if r.options.WantMixedLinkType { 565 ci.AncillaryData = r.ancil[:] 566 } 567 if cap(r.packetBuf) < ci.CaptureLength { 568 snaplen := int(r.ifaces[ci.InterfaceIndex].SnapLength) 569 if snaplen < ci.CaptureLength { 570 snaplen = ci.CaptureLength 571 } 572 r.packetBuf = make([]byte, snaplen) 573 } 574 data = r.packetBuf[:ci.CaptureLength] 575 if err = r.readBytes(data); err != nil { 576 return 577 } 578 // handle options somehow - this would be expensive 579 _, err = r.r.Discard(int(r.currentBlock.length) - ci.CaptureLength) 580 return 581 } 582 583 // LinkType returns the link type of the first interface, as a layers.LinkType. This is only valid, if WantMixedLinkType is false. 584 func (r *NgReader) LinkType() layers.LinkType { 585 return r.linkType 586 } 587 588 // SectionInfo returns information about the current section. 589 func (r *NgReader) SectionInfo() NgSectionInfo { 590 return r.sectionInfo 591 } 592 593 // Interface returns interface information and statistics of interface with the given id. 594 func (r *NgReader) Interface(i int) (NgInterface, error) { 595 if i >= len(r.ifaces) || i < 0 { 596 return NgInterface{}, fmt.Errorf("Interface %d invalid. There are only %d interfaces", i, len(r.ifaces)) 597 } 598 return r.ifaces[i], nil 599 } 600 601 // NInterfaces returns the current number of interfaces. 602 func (r *NgReader) NInterfaces() int { 603 return len(r.ifaces) 604 } 605 606 // Resolution returns the timestamp resolution of acquired timestamps before scaling to NanosecondTimestampResolution. 607 func (r *NgReader) Resolution() gopacket.TimestampResolution { 608 if r.options.WantMixedLinkType { 609 return gopacket.TimestampResolution{} 610 } 611 return r.ifaces[0].Resolution() 612 }