-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
409 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
plugins { | ||
id("java-library") | ||
alias(libs.plugins.jetbrains.kotlin.jvm) | ||
alias(libs.plugins.kotlinter) | ||
id("jacoco") | ||
} | ||
|
||
java { | ||
sourceCompatibility = JavaVersion.VERSION_17 | ||
targetCompatibility = JavaVersion.VERSION_17 | ||
} | ||
|
||
kotlin { | ||
jvmToolchain(17) | ||
} | ||
|
||
tasks.jacocoTestReport { | ||
reports { | ||
xml.required = true | ||
html.required = false | ||
} | ||
} | ||
|
||
tasks.withType<Test>().configureEach { | ||
useJUnitPlatform() | ||
finalizedBy("jacocoTestReport") | ||
} | ||
|
||
jacoco { | ||
toolVersion = "0.8.12" | ||
} | ||
|
||
dependencies { | ||
api(libs.slf4j.api) | ||
testImplementation(libs.bundles.test) | ||
testRuntimeOnly(libs.junit.jupiter.engine) | ||
testImplementation(libs.logback.classic) | ||
} |
32 changes: 32 additions & 0 deletions
32
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPacketDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
import java.nio.ByteBuffer | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
abstract class AbstractPacketDumper { | ||
protected var running = AtomicBoolean(false) | ||
|
||
/** | ||
* Dumps a ByteBuffer starting at position offset, and going until position length. If length | ||
* + offset is greater than buffer.remaining(), it will dump until the end of the buffer. | ||
* | ||
* If addresses is true, it will dump the address offsets with offset being position zero. The | ||
* addresses will be dumped at the left hand side of the dump, followed by whitespace, and then | ||
* the hexdump of the buffer. This only really applies to text dumpers or string dumpers and not | ||
* pcap dumpers. | ||
* | ||
* If etherType is set, a dummy ethernet header will be prepended to to the start of the dump. | ||
* This is particularly useful for pcap format with wireshark since it allows for wireshark to | ||
* just recognize the traffic immediately. | ||
* | ||
* Dumping the buffer should not change the: | ||
* - position, limit, etc of the buffer. | ||
*/ | ||
abstract fun dumpBuffer( | ||
buffer: ByteBuffer, | ||
offset: Int = 0, | ||
length: Int = buffer.remaining(), | ||
addresses: Boolean = false, | ||
etherType: EtherType? = null, | ||
) | ||
} |
3 changes: 3 additions & 0 deletions
3
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/AbstractPcapNgDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
abstract class AbstractPcapNgDumper : AbstractPacketDumper() |
19 changes: 19 additions & 0 deletions
19
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/DummyPacketDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
import java.nio.ByteBuffer | ||
|
||
/** | ||
* A dummy implementation of the packet dumper that does nothing. Useful so that you can use a | ||
* real dumper in a debug implementation, and switch to the DummyPacketDumper for production. | ||
*/ | ||
object DummyPacketDumper : AbstractPacketDumper() { | ||
override fun dumpBuffer( | ||
buffer: ByteBuffer, | ||
offset: Int, | ||
length: Int, | ||
addresses: Boolean, | ||
etherType: EtherType?, | ||
) { | ||
// do nothing | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/EtherType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
/** | ||
* https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1 | ||
* https://en.wikipedia.org/wiki/EtherType | ||
*/ | ||
enum class EtherType(val value: UShort) { | ||
BUMP(0x0101u), | ||
IPv4(0x0800u), | ||
ARP(0x0806u), | ||
IPv6(0x86DDu), | ||
DETECT(0xFFFFu), // used if we want to detect the type from the packet itself | ||
; | ||
|
||
companion object { | ||
fun fromValue(value: UShort) = entries.first { it.value == value } | ||
} | ||
} |
93 changes: 93 additions & 0 deletions
93
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/EthernetHeader.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
import org.slf4j.LoggerFactory | ||
import java.nio.ByteBuffer | ||
import java.security.InvalidParameterException | ||
|
||
/** | ||
* Bare minimal Ethernet frame. | ||
* | ||
* Mostly used to help us easily add a dummy header to packet dumps for Wireshark. | ||
* | ||
* https://en.wikipedia.org/wiki/Ethernet_frame | ||
*/ | ||
class EthernetHeader(private var destination: MACAddress, private var source: MACAddress, private var type: EtherType) { | ||
companion object { | ||
const val IP4_VERSION: UByte = 4u | ||
const val IP6_VERSION: UByte = 6u | ||
const val ETHERNET_HEADER_LENGTH = 14u | ||
private val logger = LoggerFactory.getLogger(this::class.java) | ||
|
||
/** | ||
* Returns a new ByteBuffer with the Ethernet header prepended to the front of the buffer. | ||
* | ||
* The buffer is copied starting from offset continuing for length bytes, or until the end of | ||
* the buffer if length + offset > buffer.limit(). The source and destination addresses are | ||
*/ | ||
fun prependDummyHeader( | ||
buffer: ByteBuffer, | ||
offset: Int = 0, | ||
length: Int = 0, | ||
etherType: EtherType | ||
): ByteBuffer { | ||
val startingPosition = buffer.position() | ||
val totalLength = minOf(length, buffer.limit() - offset) | ||
if (totalLength < length) { | ||
logger.warn("Trying to dump more bytes than are in the buffer. Dumping up to buffer limit.") | ||
} | ||
val newBuffer = ByteBuffer.allocate(totalLength + ETHERNET_HEADER_LENGTH.toInt()) | ||
val detectedEtherType = if (etherType == EtherType.DETECT) { | ||
val ipVersion = buffer.get(offset).toUByte() | ||
buffer.position(startingPosition) | ||
getEtherTypeFromIPVersionByte(ipVersion) | ||
} else { | ||
etherType | ||
} | ||
newBuffer.put(dummyEthernet(detectedEtherType).toBytes()) | ||
newBuffer.put(buffer.array(), offset, totalLength) | ||
newBuffer.rewind() | ||
return newBuffer | ||
} | ||
|
||
fun fromStream(stream: ByteBuffer): EthernetHeader { | ||
val destination = MACAddress.fromStream(stream) | ||
val source = MACAddress.fromStream(stream) | ||
val type = EtherType.fromValue(stream.short.toUShort()) | ||
return EthernetHeader(destination, source, type) | ||
} | ||
|
||
/** | ||
* Attempts to detect the ethertype based on the byte passed to it. If its not a known | ||
* ethertype, it will just leave the type as 0xFFFF (detect) which maps to RESERVED in the | ||
* actual mapping. | ||
*/ | ||
fun getEtherTypeFromIPVersionByte(ipVersion: UByte): EtherType { | ||
return when (ipVersion) { | ||
IP4_VERSION -> EtherType.IPv4 | ||
IP6_VERSION -> EtherType.IPv6 | ||
else -> { | ||
logger.warn("Couldn't detect etherType, got $ipVersion") | ||
EtherType.DETECT | ||
} | ||
} | ||
} | ||
|
||
fun dummyEthernet(etherType: EtherType): EthernetHeader { | ||
return EthernetHeader(MACAddress.DUMMY_MAC_SOURCE, MACAddress.DUMMY_MAC_DEST, etherType) | ||
} | ||
} | ||
|
||
fun size() = ETHERNET_HEADER_LENGTH | ||
|
||
fun toBytes(): ByteArray { | ||
val buffer = ByteBuffer.allocate(ETHERNET_HEADER_LENGTH.toInt()) | ||
buffer.put(destination.bytes) | ||
buffer.put(source.bytes) | ||
buffer.putShort(type.value.toShort()) | ||
return buffer.array() | ||
} | ||
|
||
override fun toString(): String { | ||
return "EthernetHeader(destination=$destination, source=$source, type=$type, size=${size()})" | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/MACAddress.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.jasonernst.packetdumper | ||
|
||
import java.nio.ByteBuffer | ||
|
||
/** | ||
* Represents an Ethernet or Wi-Fi MAC address. | ||
*/ | ||
class MACAddress { | ||
var bytes: ByteArray | ||
|
||
init { | ||
bytes = ByteArray(6) | ||
} | ||
|
||
constructor(bytes: ByteArray) { | ||
if (bytes.size != 6) { | ||
throw IllegalArgumentException("MAC address must be 6 bytes") | ||
} | ||
this.bytes = bytes | ||
} | ||
|
||
constructor(address: String) { | ||
this.bytes = address.split(":").map { it.toInt(16).toByte() }.toByteArray() | ||
} | ||
|
||
override fun toString(): String { | ||
return bytes.joinToString(":") { it.toString(16).padStart(2, '0') } | ||
} | ||
|
||
companion object { | ||
fun fromStream(stream: ByteBuffer): MACAddress { | ||
val bytes = ByteArray(6) | ||
stream.get(bytes) | ||
return MACAddress(bytes) | ||
} | ||
|
||
val DUMMY_MAC_SOURCE = MACAddress("14:c0:3e:55:0b:35") | ||
val DUMMY_MAC_DEST = MACAddress("74:d0:2b:29:a5:18") | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...dumper/src/main/kotlin/com/jasonernst/packetdumper/filedumper/AbstractFilePacketDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.jasonernst.packetdumper.filedumper | ||
|
||
import com.jasonernst.packetdumper.AbstractPacketDumper | ||
|
||
abstract class AbstractFilePacketDumper : AbstractPacketDumper() { | ||
abstract var filename: String | ||
|
||
abstract fun open() | ||
|
||
abstract fun close() | ||
} |
9 changes: 9 additions & 0 deletions
9
...er/src/main/kotlin/com/jasonernst/packetdumper/serverdumper/AbstractServerPacketDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.jasonernst.packetdumper.serverdumper | ||
|
||
import com.jasonernst.packetdumper.AbstractPacketDumper | ||
|
||
abstract class AbstractServerPacketDumper : AbstractPacketDumper() { | ||
abstract fun start() | ||
|
||
abstract fun stop() | ||
} |
103 changes: 103 additions & 0 deletions
103
packetdumper/src/main/kotlin/com/jasonernst/packetdumper/stringdumper/StringPacketDumper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package com.jasonernst.packetdumper.stringdumper | ||
|
||
import com.jasonernst.packetdumper.AbstractPacketDumper | ||
import com.jasonernst.packetdumper.EtherType | ||
import com.jasonernst.packetdumper.EthernetHeader | ||
import com.jasonernst.packetdumper.EthernetHeader.Companion | ||
import com.jasonernst.packetdumper.EthernetHeader.Companion.prependDummyHeader | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
import java.nio.ByteBuffer | ||
|
||
/** | ||
* A packet dumper that will dump packets to a string. This is useful for live debugging. It can | ||
* either log to a slf4f logger if one is provided, or it can write to stdout. By default it does | ||
* neither, however, it can still be used manually by calling `dumpBufferToString`. | ||
* @param packetLogger The slf4j logger to dump the buffers to. | ||
* @param writeToStdOut If true, will write to stdout (if the logger is null) | ||
*/ | ||
class StringPacketDumper(private val packetLogger: Logger? = null, private val writeToStdOut: Boolean = false) : AbstractPacketDumper() { | ||
private val logger = LoggerFactory.getLogger(javaClass) | ||
|
||
/** | ||
* In this case calling dump buffer will: | ||
* a) write to an slf4j logger if the constructor arg is not null | ||
* b) write to stdout if no logger has been above, and if the writeToStdout constructor arg is true | ||
*/ | ||
override fun dumpBuffer( | ||
buffer: ByteBuffer, | ||
offset: Int, | ||
length: Int, | ||
addresses: Boolean, | ||
etherType: EtherType?, | ||
) { | ||
val hexString = dumpBufferToString(buffer, offset, length, addresses, etherType) | ||
if (packetLogger != null) { | ||
packetLogger.info(hexString) | ||
} else if (writeToStdOut) { | ||
println(hexString) | ||
} | ||
} | ||
|
||
/** | ||
* This function will dump the buffer to a string and return it. | ||
* | ||
* Given the byte buffer, converts the bytes to a Hexadecimal string. Puts the buffer back to | ||
* the position it started in before returning. | ||
* | ||
* - Spaces each byte | ||
* - Newline after every 16 bytes | ||
* | ||
* Optionally: | ||
* - Prints the offset addresses at the start of the line | ||
* - Addresses start at 0x00000000 and increment by 16, regardless of the offset. The buffer | ||
* position + the offset = the starting address of 0. | ||
* | ||
* @param buffer the buffer to convert | ||
* @param offset the offset to start at | ||
* @param length the number of bytes to convert | ||
* (if offset + length > buffer.limit(), will print up to buffer.limit()) | ||
* @param addresses if true, will print the offset addresses at the start of the line | ||
* (compatible with a wireshark hex dump import) | ||
* @param etherType the ethertype of the buffer if known, or 0xFFFF if unknown. null if it is | ||
* not desired to prepend with an ethernet header | ||
*/ | ||
fun dumpBufferToString( | ||
buffer: ByteBuffer, | ||
offset: Int = 0, | ||
length: Int = 0, | ||
addresses: Boolean = false, | ||
etherType: EtherType? = null, | ||
): String { | ||
val startingPosition = buffer.position() | ||
// optionally prepend the ethernet dummy header | ||
val conversionBuffer = if (etherType != null) { | ||
prependDummyHeader(buffer, offset, length, etherType) | ||
} else { | ||
val totalLength = minOf(length, buffer.limit() - offset) | ||
if (totalLength < length) { | ||
logger.warn("Trying to dump more bytes than are in the buffer. Dumping up to buffer limit.") | ||
} | ||
val newBuffer = ByteBuffer.allocate(totalLength) | ||
newBuffer.put(buffer.array(), offset, totalLength) | ||
newBuffer.rewind() | ||
newBuffer | ||
} | ||
val output = StringBuilder() | ||
var i = 0 | ||
while (i < conversionBuffer.limit()) { | ||
if (addresses && (i % 16 == offset % 16)) { | ||
output.append(String.format("%08X ", i - (offset % 16) - (16 * (offset / 16)))) | ||
} | ||
output.append(String.format("%02X", buffer.get())) | ||
i++ | ||
if (i > offset && i % 16 == offset % 16) { | ||
output.append("\n") | ||
} else if (i < (length + offset)) { | ||
output.append(" ") | ||
} | ||
} | ||
buffer.position(startingPosition) | ||
return output.toString() | ||
} | ||
} |
Oops, something went wrong.