From 4b9259c58bd730b6a1fd22e12d29ee788d2e5e9e Mon Sep 17 00:00:00 2001 From: Jason Ernst Date: Wed, 6 Nov 2024 10:33:35 -0800 Subject: [PATCH 1/2] added the ability to filter packet destinations --- .../packetdumper/PacketDumperVpnService.kt | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt b/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt index 0b8d677..e710002 100644 --- a/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt +++ b/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt @@ -15,6 +15,7 @@ import com.jasonernst.kanonproxy.VpnProtector import com.jasonernst.knet.Packet import com.jasonernst.knet.network.ip.IpType import com.jasonernst.knet.transport.TransportHeader +import com.jasonernst.knet.transport.tcp.TcpHeader import com.jasonernst.packetdumper.ethernet.EtherType import com.jasonernst.packetdumper.serverdumper.ConnectedUsersChangedCallback import com.jasonernst.packetdumper.serverdumper.PcapNgTcpServerPacketDumper @@ -26,6 +27,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import java.net.DatagramSocket +import java.net.Inet4Address import java.net.Socket import java.nio.ByteBuffer import java.util.concurrent.LinkedBlockingDeque @@ -149,13 +151,24 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte // logger.debug("Read {} bytes from OS", totalBytesRead) stream.flip() val packets = parseStream(stream) - for (packet in packets) { - packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) - } - logger.debug("Parsed {} packets from OS, sending to proxy", packets.size) + +// val packetsToHandle = mutableListOf() +// for (packet in packets) { +// if (packet.nextHeaders is TcpHeader) { +// if (packet.ipHeader!!.destinationAddress == Inet4Address.getByName("138.68.242.6")) { +// packetsToHandle.add(packet) +// packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) +// } +// } else { +// packetsToHandle.add(packet) +// packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) +// } +// } +// logger.debug("Parsed {} packets from OS, sending to proxy", packetsToHandle.size) +// kAnonProxy.handlePackets(packetsToHandle)' kAnonProxy.handlePackets(packets) } else { - Thread.sleep(100) // wait for data to arrive + // Thread.sleep(100) // wait for data to arrive } } } @@ -244,7 +257,7 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte private fun readFromInternetWriteToOS(outputStream: AutoCloseOutputStream) { while (running.get()) { val packet = kAnonProxy.takeResponse() - logger.debug("Got packet from proxy: {}", packet) + logger.debug("Got packet from proxy: {}", packet.nextHeaders) if (packet.ipHeader == null || packet.nextHeaders == null || packet.payload == null) { logger.warn("Packet is missing headers or payload, skipping") continue @@ -291,7 +304,7 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte try { outputStream.write(bytesToWrite) outputStream.flush() - //logger.debug("Wrote {} bytes to OS", bytesToWrite.size) + logger.debug("Wrote {} bytes to OS", bytesToWrite.size) } catch (e: Exception) { logger.error("Error writing to OS, probably shutting down ", e) } From 6015dce4cf7cfb9237566d0de2640b18068a7dca Mon Sep 17 00:00:00 2001 From: Jason Ernst Date: Tue, 19 Nov 2024 15:41:09 -0800 Subject: [PATCH 2/2] working speedtests --- example-android/build.gradle.kts | 5 + .../packetdumper/PacketDumperVpnService.kt | 180 ++++-------------- gradle/libs.versions.toml | 2 +- 3 files changed, 48 insertions(+), 139 deletions(-) diff --git a/example-android/build.gradle.kts b/example-android/build.gradle.kts index 59f02c9..db78a45 100644 --- a/example-android/build.gradle.kts +++ b/example-android/build.gradle.kts @@ -54,10 +54,15 @@ dependencies { } implementation(libs.icmp.android) { exclude(group = "com.jasonernst.packetdumper", module = "packetdumper") + // turn off logback-android to make things go faster + // exclude(group = "com.github.tony19", module = "logback-android") } implementation(libs.kanonproxy) { exclude(group = "com.jasonernst.packetdumper", module = "packetdumper") + // turn off logback-android to make things go faster + // exclude(group = "com.github.tony19", module = "logback-android") } + // turn off logback-android to make things go faster implementation(libs.logback.android) implementation(libs.material) // required for the themes.xml implementation(libs.slf4j.api) diff --git a/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt b/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt index e710002..990ab93 100644 --- a/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt +++ b/example-android/src/main/java/com/jasonernst/packetdumper/PacketDumperVpnService.kt @@ -13,6 +13,7 @@ import com.jasonernst.icmp.android.IcmpAndroid import com.jasonernst.kanonproxy.KAnonProxy import com.jasonernst.kanonproxy.VpnProtector import com.jasonernst.knet.Packet +import com.jasonernst.knet.Packet.Companion.parseStream import com.jasonernst.knet.network.ip.IpType import com.jasonernst.knet.transport.TransportHeader import com.jasonernst.knet.transport.tcp.TcpHeader @@ -28,11 +29,13 @@ import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import java.net.DatagramSocket import java.net.Inet4Address +import java.net.InetSocketAddress import java.net.Socket import java.nio.ByteBuffer import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.min +import kotlin.random.Random class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, ConnectedUsersChangedCallback { private val logger = LoggerFactory.getLogger(javaClass) @@ -47,6 +50,10 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte private lateinit var sessionViewModel: SessionViewModel private val packetDumper = PcapNgTcpServerPacketDumper(callback = this, isSimple = false) private val binder = LocalBinder() + // just use a fake client since we don't care about the source address since we aren't + // supporting multiple clients for this application + val randomPort = Random.nextInt(1024, 65535) + val clientAddress = InetSocketAddress(Inet4Address.getByName("127.0.0.1"), randomPort) /** * Class used for the client Binder. Because we know this service always @@ -130,133 +137,30 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte private fun readFromOSWriteToInternet(inputStream: AutoCloseInputStream) { val stream = ByteBuffer.allocate(MAX_STREAM_BUFFER_SIZE) while (running.get()) { - // fill up the buffer with data from the OS over multiple reads, or until - // there is no more data to read - var totalBytesRead = 0 - do { - // make sure we don't overlfow the buffer - var bytesToRead = min(MAX_RECEIVE_BUFFER_SIZE, stream.remaining()) - val bytesRead: Int = inputStream.read(readBuffer, 0, bytesToRead) - if (bytesRead == -1) { - logger.warn("End of OS stream") - break - } - if (bytesRead > 0) { - //logger.debug("About to write {} bytes to buffer at position: {}", bytesRead, stream.position()) - stream.put(readBuffer, 0, bytesRead) - totalBytesRead += bytesRead - } - } while (bytesRead > 0 && stream.hasRemaining()) - if (totalBytesRead > 0) { - // logger.debug("Read {} bytes from OS", totalBytesRead) + val bytesToRead = min(MAX_RECEIVE_BUFFER_SIZE, stream.remaining()) + val bytesRead = inputStream.read(readBuffer, 0, bytesToRead) + if (bytesRead == -1) { + logger.warn("End of OS stream") + break + } + if (bytesRead > 0) { + stream.put(readBuffer, 0, bytesRead) + //logger.debug("Read {} bytes from OS. position: {} remaining {}", bytesRead, stream.position(), stream.remaining()) stream.flip() + //logger.debug("After flip: position: {} remaining {}", stream.position(), stream.remaining()) val packets = parseStream(stream) - -// val packetsToHandle = mutableListOf() -// for (packet in packets) { -// if (packet.nextHeaders is TcpHeader) { -// if (packet.ipHeader!!.destinationAddress == Inet4Address.getByName("138.68.242.6")) { -// packetsToHandle.add(packet) -// packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) -// } -// } else { -// packetsToHandle.add(packet) -// packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) -// } -// } -// logger.debug("Parsed {} packets from OS, sending to proxy", packetsToHandle.size) -// kAnonProxy.handlePackets(packetsToHandle)' - kAnonProxy.handlePackets(packets) - } else { - // Thread.sleep(100) // wait for data to arrive - } - } - } - - /** - * The packets will be either IPv4 or Ipv6 headers, followed by NextHeader(s) which are typically - * TCP, UDP, ICMP, etc. This is followed by the optional payload. - * - * For TCP packets, we need to make a request on behalf of the client on a protected TCP socket. - * We then need to listen to the return traffic and send it back to the client, and ensure that - * the sequence numbers are maintained etc. - * - * For UDP packets, we can just send them to the internet and listen for the return traffic and - * then just send it back to the client. - * - * For ICMP, we need to use an ICMP socket (https://github.com/compscidr/icmp) to send the - * request and listen for the return traffic. We then return the ICMP result to the client. This - * may be unreachable, time exceeded, etc, or just a successful ping response. - */ - private fun parseStream(stream: ByteBuffer): List { - //logger.debug("GOT STREAM: \n{}", StringPacketDumper().dumpBufferToString(buffer = stream, addresses = true, etherType = null)) - val packets = mutableListOf() - while (stream.hasRemaining()) { - val position = stream.position() - try { - val packet = Packet.fromStream(stream) - if (packet.ipHeader == null || packet.nextHeaders == null || packet.payload == null) { - logger.warn("Packet is missing headers or payload, skipping") - continue - } - //logger.debug("Parsed packet: {}", packet) - //logger.debug("Stream position after parsing: {} limit: {}", stream.position(), stream.limit()) - val ipHeader = packet.ipHeader - val nextHeader = packet.nextHeaders - - val sourcePort = if (nextHeader is TransportHeader) { - nextHeader.sourcePort - } else { - 0u + for (packet in packets) { + packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT) } - val destinationPort = if (nextHeader is TransportHeader) { - nextHeader.destinationPort - } else { - 0u - } - - val protocol = IpType.fromValue(ipHeader!!.protocol) - val key = Session.getKey( - ipHeader.sourceAddress.toString(), - sourcePort.toInt(), - ipHeader.destinationAddress.toString(), - destinationPort.toInt(), - protocol.toString() - ) - val session = sessionViewModel.sessionMap.getOrPut(key) { - Session( - ipHeader.sourceAddress.toString(), - sourcePort.toInt(), - ipHeader.destinationAddress.toString(), - destinationPort.toInt(), - protocol.toString(), - System.currentTimeMillis() - ) - } - session.outgoingPackets.intValue++ - session.outgoingBytes.intValue += ipHeader.getTotalLength().toInt() - packets.add(packet) - } catch (e: IllegalArgumentException) { - // don't bother to rewind the stream, just log and continue at position + 1 - logger.error("Error parsing stream: ", e) - stream.position(position + 1) - } catch (e: com.jasonernst.knet.PacketTooShortException) { - logger.warn("Packet too short to parse, trying again when more data arrives: {}", e.message) - //logger.debug("POSITION: {} LIMIT: {}, RESETTING TO START: {}", stream.position(), stream.limit(), position) - // rewind the stream to before we tried parsing so we can try again later - stream.position(position) - break + // logger.debug("After parse: position: {} remaining {}", stream.position(), stream.remaining()) + kAnonProxy.handlePackets(packets, clientAddress) } } - //logger.debug("Stream position before compact: {} limit: {}", stream.position(), stream.limit()) - stream.compact() - //logger.debug("Stream position after compact: {} limit: {}", stream.position(), stream.limit()) - return packets } private fun readFromInternetWriteToOS(outputStream: AutoCloseOutputStream) { while (running.get()) { - val packet = kAnonProxy.takeResponse() + val packet = kAnonProxy.takeResponse(clientAddress) logger.debug("Got packet from proxy: {}", packet.nextHeaders) if (packet.ipHeader == null || packet.nextHeaders == null || packet.payload == null) { logger.warn("Packet is missing headers or payload, skipping") @@ -280,26 +184,26 @@ class PacketDumperVpnService: VpnService(), VpnProtector, VpnUiService, Connecte 0u } - val protocol = IpType.fromValue(ipHeader!!.protocol) - val key = Session.getKey( - ipHeader.destinationAddress.toString(), - sourcePort.toInt(), - ipHeader.sourceAddress.toString(), - destinationPort.toInt(), - protocol.toString() - ) - val session = sessionViewModel.sessionMap.getOrPut(key) { - Session( - ipHeader.sourceAddress.toString(), - sourcePort.toInt(), - ipHeader.destinationAddress.toString(), - destinationPort.toInt(), - protocol.toString(), - System.currentTimeMillis() - ) - } - session.incomingPackets.intValue++ - session.incomingBytes.intValue += ipHeader.getTotalLength().toInt() +// val protocol = IpType.fromValue(ipHeader!!.protocol) +// val key = Session.getKey( +// ipHeader.destinationAddress.toString(), +// sourcePort.toInt(), +// ipHeader.sourceAddress.toString(), +// destinationPort.toInt(), +// protocol.toString() +// ) +// val session = sessionViewModel.sessionMap.getOrPut(key) { +// Session( +// ipHeader.sourceAddress.toString(), +// sourcePort.toInt(), +// ipHeader.destinationAddress.toString(), +// destinationPort.toInt(), +// protocol.toString(), +// System.currentTimeMillis() +// ) +// } +// session.incomingPackets.intValue++ +// session.incomingBytes.intValue += ipHeader.getTotalLength().toInt() try { outputStream.write(bytesToWrite) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00ea4f0..d7dc22e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ espresso-core = "3.0.2" icmp = "1.0.0" junit = "4.13.2" jupiter = "5.11.3" -kanonproxy = "0.0.26" +kanonproxy = "0.0.35" kotlin = "2.0.21" kotlinter = "4.4.1" logback-android = "3.0.0"