Skip to content

Commit

Permalink
Merge pull request #59 from compscidr/jason/debugging-tcp
Browse files Browse the repository at this point in the history
Working TCP in example packetdumper
  • Loading branch information
compscidr authored Nov 20, 2024
2 parents 49a5d15 + 4740b31 commit f489dc6
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 128 deletions.
5 changes: 5 additions & 0 deletions example-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ 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
import com.jasonernst.packetdumper.ethernet.EtherType
import com.jasonernst.packetdumper.serverdumper.ConnectedUsersChangedCallback
import com.jasonernst.packetdumper.serverdumper.PcapNgTcpServerPacketDumper
Expand All @@ -26,11 +28,14 @@ import kotlinx.coroutines.launch
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)
Expand All @@ -45,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
Expand Down Expand Up @@ -128,123 +137,31 @@ 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)
for (packet in packets) {
packetDumper.dumpBuffer(ByteBuffer.wrap(packet.toByteArray()), etherType = EtherType.DETECT)
}
logger.debug("Parsed {} packets from OS, sending to proxy", packets.size)
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<Packet> {
//logger.debug("GOT STREAM: \n{}", StringPacketDumper().dumpBufferToString(buffer = stream, addresses = true, etherType = null))
val packets = mutableListOf<Packet>()
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
}
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()
logger.debug("Got packet from proxy: {}", packet)
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")
continue
Expand All @@ -267,31 +184,31 @@ 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)
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)
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ espresso-core = "3.0.2"
icmp = "1.0.0"
junit = "4.13.2"
jupiter = "5.11.3"
kanonproxy = "0.0.33"
kanonproxy = "0.0.35"
kotlin = "2.0.21"
kotlinter = "4.5.0"
logback-android = "3.0.0"
Expand Down

0 comments on commit f489dc6

Please sign in to comment.