Skip to content

Commit

Permalink
Added pcap file writer + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
compscidr committed Aug 8, 2024
1 parent 2ad2ef8 commit b0462d8
Show file tree
Hide file tree
Showing 15 changed files with 517 additions and 54 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
package com.jasonernst.packetdumper.filedumper

import com.jasonernst.packetdumper.AbstractPacketDumper
import org.slf4j.LoggerFactory
import java.io.File
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicBoolean

abstract class AbstractFilePacketDumper : AbstractPacketDumper() {
abstract var filename: String
/**
* The base class for all file packet dumpers. We leave it as an abstract class so that we don't
* have to implement the dumpBuffer method, and it can be implemented by the specific file dumper.
*/
abstract class AbstractFilePacketDumper(
path: String,
name: String,
ext: String,
) : AbstractPacketDumper() {
private val logger = LoggerFactory.getLogger(javaClass)

abstract fun open()
// keep the filename public to make testing easier
val filename: String = "$path/${name}_${LocalDateTime.now()}.$ext"
protected val isOpen = AtomicBoolean(false)
protected lateinit var file: File
protected var loggedError = false

abstract fun close()
open fun open() {
if (isOpen.get()) {
logger.error("Trying to open a file that is already open")
return
}
file = File(filename)
logger.debug("Opened file $filename")
isOpen.set(true)
}

open fun close() {
if (!isOpen.get()) {
logger.error("Trying to close a file that is already closed")
return
}
isOpen.set(false)
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,67 @@
package com.jasonernst.packetdumper.filedumper

import com.jasonernst.packetdumper.ethernet.EtherType
import com.jasonernst.packetdumper.ethernet.EthernetHeader.Companion.prependDummyHeader
import com.jasonernst.packetdumper.pcapng.PcapNgInterfaceDescriptionBlock
import com.jasonernst.packetdumper.pcapng.PcapNgSectionHeaderBlockLive
import com.jasonernst.packetdumper.pcapng.PcapNgSimplePacketBlock
import java.io.BufferedOutputStream
import java.nio.ByteBuffer

/**
* Dumps packets to a file in the PCAP-NG format.
*
* @param path The path to the file
* @param name The name of the file
* @param isSimple If true, the file will be written in the simple format.
* If false, it will be written in the enhanced format.
*/
class PcapNgFilePacketDumper(
override var filename: String,
) : AbstractFilePacketDumper() {
path: String,
name: String,
private val isSimple: Boolean = true,
) : AbstractFilePacketDumper(path, name, "pcapng") {
private lateinit var outputStreamWriter: BufferedOutputStream

override fun open() {
TODO("Not yet implemented")
super.open()
outputStreamWriter = BufferedOutputStream(file.outputStream())
outputStreamWriter.write(PcapNgSectionHeaderBlockLive.toBytes())
outputStreamWriter.flush()
outputStreamWriter.write(PcapNgInterfaceDescriptionBlock().toBytes())
outputStreamWriter.flush()
}

override fun close() {
TODO("Not yet implemented")
super.close()
outputStreamWriter.flush()
outputStreamWriter.close()
}

/**
* Writes a packet to the file. In this case, the address parameter is ignored.
*/
override fun dumpBuffer(
buffer: ByteBuffer,
offset: Int,
length: Int,
addresses: Boolean,
etherType: EtherType?,
) {
TODO("Not yet implemented")
// optionally prepend the ethernet dummy header
val conversionBuffer =
if (etherType != null) {
prependDummyHeader(buffer, offset, length, etherType)
} else {
buffer
}

if (isSimple) {
val packetBlock = PcapNgSimplePacketBlock(conversionBuffer.array())
outputStreamWriter.write(packetBlock.toBytes())
outputStreamWriter.flush()
} else {
TODO()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,34 @@ package com.jasonernst.packetdumper.filedumper
import com.jasonernst.packetdumper.ethernet.EtherType
import com.jasonernst.packetdumper.stringdumper.StringPacketDumper
import org.slf4j.LoggerFactory
import java.io.BufferedWriter
import java.io.File
import java.nio.ByteBuffer
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicBoolean

/**
* Dumps buffers into hexdump text files that can be imported by wireshark. The format is as follows:
* Dumps packets to a text file in hexdump format.
*
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
*
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
*
* Each block of offsets is separated by a blank newline, and represents a new packet / dump. When
* the new dump starts, the offset can restart at 0.
* If addresses and EtherType is set, the output will be compatible with Wireshark Hexdump. It
* can be imported with `File -> Import from Hexdump.`
*/
class TextFilePacketDumper(
private val path: String,
private val name: String,
) : AbstractFilePacketDumper() {
path: String,
name: String,
) : AbstractFilePacketDumper(path, name, "txt") {
private val logger = LoggerFactory.getLogger(javaClass)
private val stringDumper = StringPacketDumper()

private val isOpen = AtomicBoolean(false)
override lateinit var filename: String
private lateinit var file: File
private lateinit var bufferedWriter: BufferedWriter
private var loggedError = false

override fun open() {
if (isOpen.get()) {
logger.error("Trying to open a file that is already open")
return
}
filename = "$path/${name}_${LocalDateTime.now()}.dump"
file = File(filename)
logger.debug("TextFilePacketDumper opened file $filename")
isOpen.set(true)
}

override fun close() {
if (!isOpen.get()) {
logger.error("Trying to close a file that is already closed")
return
}
isOpen.set(false)
}

/**
* Dumps buffers into hexdump text files that can be imported by wireshark. The format is as follows:
*
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
*
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
* offset(hex) up to 16 bytes of data per line, separated by a space, ending in a newline
*
* Each block of offsets is separated by a blank newline, and represents a new packet / dump. When
* the new dump starts, the offset can restart at 0.
*
*/
override fun dumpBuffer(
buffer: ByteBuffer,
offset: Int,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jasonernst.packetdumper.pcapng

import com.jasonernst.packetdumper.AbstractPacketDumper

abstract class AbstractPcapNgDumper : AbstractPacketDumper()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.jasonernst.packetdumper.pcapng

interface PcapNgBlock {
fun size(): UInt

fun toBytes(): ByteArray
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jasonernst.packetdumper.pcapng

// https://www.ietf.org/staging/draft-tuexen-opsawg-pcapng-02.html#name-block-types
enum class PcapNgBlockType(
val value: UInt,
) {
INTERFACE_DESCRIPTION_BLOCK(1u),
SIMPLE_PACKET_BLOCK(3u),
NAME_RESOLUTION_BLOCK(4u),
INTERFACE_STATISTICS_BLOCK(5u),
ENHANCED_PACKET_BLOCK(6u),
SECTION_HEADER_BLOCK(0x0A0D0D0AU),
;

companion object {
fun fromValue(value: UInt) = entries.first { it.value == value }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.jasonernst.packetdumper.pcapng

class PcapNgEnhancedPacketBlock
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.jasonernst.packetdumper.pcapng

import java.lang.Exception

class PcapNgException : Exception {
constructor(message: String?) : super(message) {}
constructor(message: String?, throwable: Throwable?) : super(message, throwable) {}

companion object {
private const val serialVersionUID = 1L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.jasonernst.packetdumper.pcapng

import java.nio.ByteBuffer
import java.nio.ByteOrder

/**
* https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-interface-description-block
*/
class PcapNgInterfaceDescriptionBlock(
private val linkType: PcapNgLinkType = PcapNgLinkType.ETHERNET,
) : PcapNgBlock {
companion object {
// block type (4) + 2x block len (4) + link type (2) + reserved (2) + snap len (4)
private const val HEADER_BLOCK_LENGTH = 20u

/**
* Reads a section header block from a stream, throws a PcapNgException if the block is not a
* section header live block.
*/
fun fromStream(stream: ByteBuffer): PcapNgInterfaceDescriptionBlock {
val startingPosition = stream.position()
stream.order(ByteOrder.LITTLE_ENDIAN)
val blockType = stream.int
if (blockType != PcapNgBlockType.INTERFACE_DESCRIPTION_BLOCK.value.toInt()) {
throw PcapNgException(
"Block type is not an interface description block, expected ${PcapNgBlockType.INTERFACE_DESCRIPTION_BLOCK.value} got $blockType",
)
}
val blockLength = stream.int
if (blockLength != HEADER_BLOCK_LENGTH.toInt()) {
throw PcapNgException("Block length is not the expected length")
}
val linkType = stream.short
val reserved = stream.short
val snapLen = stream.int
if (snapLen != 0) {
throw PcapNgException("Snap length is not the expected value")
}
val secondBlockLength = stream.int
if (secondBlockLength != blockLength) {
throw PcapNgException("Second block length is not the expected value")
}
if (stream.position() - startingPosition != HEADER_BLOCK_LENGTH.toInt()) {
throw PcapNgException(
"Stream position is not at the end of the block, expected ${startingPosition + HEADER_BLOCK_LENGTH.toInt()} got ${stream.position()}",
)
}
return PcapNgInterfaceDescriptionBlock(PcapNgLinkType.fromValue(linkType.toUShort()))
}
}

override fun size(): UInt = HEADER_BLOCK_LENGTH

override fun toBytes(): ByteArray {
val buffer = ByteBuffer.allocate(HEADER_BLOCK_LENGTH.toInt())
buffer.order(ByteOrder.LITTLE_ENDIAN)
buffer.putInt(PcapNgBlockType.INTERFACE_DESCRIPTION_BLOCK.value.toInt())
buffer.putInt(HEADER_BLOCK_LENGTH.toInt())

buffer.putShort(linkType.value.toShort())
buffer.putShort(0) // reserved
buffer.putInt(0) // snap length

// no options

buffer.putInt(HEADER_BLOCK_LENGTH.toInt())
return buffer.array()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.jasonernst.packetdumper.pcapng

// https://www.tcpdump.org/linktypes.html
enum class PcapNgLinkType(
val value: UShort,
) {
NULL(0U),
ETHERNET(1U),
AX25(3U),
IEEE802_5(6U),
ARCNET_BSD(7U),
SLIP(8U),
PPP(9U),
FDDI(10U),
PPP_HDLC(50U),
PPP_ETHER(51U),
ATM_RFC1483(100U),
RAW(101U),
C_HDLC(104U),
IEEE802_11(105U),
FRELAY(107U),
LOOP(108U),
LINUX_SLL(113U),
LTALK(114U),
PFLOG(117U),
IEEE802_11_PRISM(119U),
IP_OVER_FC(122U),
SUNATM(123U),
IEEE802_11_RADIOTAP(127U),
ARCNET_LINUX(129U),
APPLE_IP_OVER_IEEE1394(138U),
MTP2_WITH_PHDR(139U),
MTP2(140U),
MTP3(141U),
SCCP(142U),
DOCSIS(143U),
LINUX_IRDA(144U),
;

companion object {
fun fromValue(value: UShort) = entries.first { it.value == value }
}
}
Loading

0 comments on commit b0462d8

Please sign in to comment.