From dbc6ca854049ebf75e0c9de61d5fbc90cf5fa8d7 Mon Sep 17 00:00:00 2001 From: Jason Ernst Date: Wed, 9 Oct 2024 15:29:25 -0700 Subject: [PATCH] WiP: sample is able to read packets without crashing --- .../ExampleInstrumentedTest.kt | 24 -------- .../example_android/PacketDumperVPNService.kt | 60 +++++++++++++------ .../example_android/ExampleUnitTest.kt | 17 ------ gradle/libs.versions.toml | 2 +- .../filedumper/TestPcapNgFilePacketDumper.kt | 3 +- 5 files changed, 45 insertions(+), 61 deletions(-) delete mode 100644 example-android/src/androidTest/java/com/jasonernst/example_android/ExampleInstrumentedTest.kt delete mode 100644 example-android/src/test/java/com/jasonernst/example_android/ExampleUnitTest.kt diff --git a/example-android/src/androidTest/java/com/jasonernst/example_android/ExampleInstrumentedTest.kt b/example-android/src/androidTest/java/com/jasonernst/example_android/ExampleInstrumentedTest.kt deleted file mode 100644 index eacdc08..0000000 --- a/example-android/src/androidTest/java/com/jasonernst/example_android/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.jasonernst.example_android - -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.jasonernst.example_android", appContext.packageName) - } -} \ No newline at end of file diff --git a/example-android/src/main/java/com/jasonernst/example_android/PacketDumperVPNService.kt b/example-android/src/main/java/com/jasonernst/example_android/PacketDumperVPNService.kt index 88b6b8c..357425f 100644 --- a/example-android/src/main/java/com/jasonernst/example_android/PacketDumperVPNService.kt +++ b/example-android/src/main/java/com/jasonernst/example_android/PacketDumperVPNService.kt @@ -6,6 +6,8 @@ import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor.AutoCloseInputStream import android.os.ParcelFileDescriptor.AutoCloseOutputStream import com.jasonernst.knet.Packet +import com.jasonernst.knet.network.nextheader.ICMPNextHeaderWrapper +import com.jasonernst.knet.transport.TransportHeader import com.jasonernst.packetdumper.ethernet.EtherType import com.jasonernst.packetdumper.stringdumper.StringPacketDumper import kotlinx.coroutines.CoroutineScope @@ -17,6 +19,7 @@ import org.slf4j.LoggerFactory import java.nio.ByteBuffer import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.atomic.AtomicBoolean +import kotlin.math.min class PacketDumperVPNService: VpnService() { private val logger = LoggerFactory.getLogger(javaClass) @@ -46,6 +49,7 @@ class PacketDumperVPNService: VpnService() { .addAddress(VPN6_ADDRESS, VPN_SUBNET6_MASK) .addDnsServer(DNS_SERVER) .addDnsServer(DNS6_SERVER) + .setMtu(MAX_RECEIVE_BUFFER_SIZE) .addRoute("0.0.0.0", 0) .addRoute("2000::", 3) // https://wiki.strongswan.org/issues/1261 @@ -68,25 +72,30 @@ class PacketDumperVPNService: VpnService() { withContext(Dispatchers.IO) { val stream = ByteBuffer.allocate(MAX_STREAM_BUFFER_SIZE) while (running.get()) { - val bytesRead: Int - try { - bytesRead = inputStream.read(readBuffer) + // 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.debug("End of stream") + logger.warn("End of OS stream") break } - if (bytesRead == 0) { - continue + if (bytesRead > 0) { + logger.debug("About to write {} bytes to buffer at position: {}", bytesRead, stream.position()) + stream.put(readBuffer, 0, bytesRead) + totalBytesRead += bytesRead } - logger.debug("Read {} bytes from OS", bytesRead) - } catch (e: Exception) { - logger.error("Error reading from OS", e) - break + } while (bytesRead > 0 && stream.hasRemaining()) + if (totalBytesRead > 0) { + logger.debug("Read {} bytes from OS", totalBytesRead) + stream.flip() + handlePackets(parseStream(stream)) + } else { + Thread.sleep(100) // wait for data to arrive } - - stream.put(readBuffer, 0, bytesRead) - stream.flip() - val packets = parseStream(stream) } } } @@ -107,26 +116,43 @@ class PacketDumperVPNService: VpnService() { * 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 = EtherType.DETECT)) + 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) logger.debug("Parsed packet: {}", packet) + logger.debug("Stream position after parsing: {} limit: {}", stream.position(), stream.limit()) 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: PacketTooShortException) { + } 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("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 handlePackets(packets: List) { + packets.forEach { packet -> + if (packet.nextHeaders is TransportHeader) { + // todo: use https://github.com/compscidr/kanonproxy + } else if (packet.nextHeaders is ICMPNextHeaderWrapper) { + // todo: use https://github.com/compscidr/kanonproxy + } else { + logger.error("Unsupported packet type: {}", packet.javaClass) + } + } } private suspend fun readFromInternetWriteToOS(outputStream: AutoCloseOutputStream) { @@ -157,6 +183,6 @@ class PacketDumperVPNService: VpnService() { private const val DNS_SERVER = "8.8.8.8" private const val DNS6_SERVER = "2001:4860:4860::8888" private const val MAX_STREAM_BUFFER_SIZE = 1048576 // max we can write into the stream without parsing - private const val MAX_RECEIVE_BUFFER_SIZE = 1024 // max amount we can recv in one read + private const val MAX_RECEIVE_BUFFER_SIZE = 1500 // max amount we can recv in one read (should be the MTU or bigger probably) } } \ No newline at end of file diff --git a/example-android/src/test/java/com/jasonernst/example_android/ExampleUnitTest.kt b/example-android/src/test/java/com/jasonernst/example_android/ExampleUnitTest.kt deleted file mode 100644 index 9a83cc2..0000000 --- a/example-android/src/test/java/com/jasonernst/example_android/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.jasonernst.example_android - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0673f42..694b62b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ compose-activity = "1.9.1" espresso-core = "3.0.2" junit = "4.13.2" jupiter = "5.11.2" -knet = "0.0.2" +knet = "0.0.4" kotlin = "2.0.20" kotlinter = "4.4.1" logback-android = "3.0.0" diff --git a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt index d89795e..24e790a 100644 --- a/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt +++ b/packetdumper/src/test/kotlin/com/jasonernst/packetdumper/filedumper/TestPcapNgFilePacketDumper.kt @@ -10,7 +10,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory -import java.io.File import java.nio.ByteBuffer class TestPcapNgFilePacketDumper { @@ -24,7 +23,7 @@ class TestPcapNgFilePacketDumper { // delete the file created for the test to cleanup try { - //File(dumper.filename).delete() + // File(dumper.filename).delete() logger.debug("Deleted file ${dumper.filename}") } catch (e: Exception) { // ignore