From 182f909cce1a00feea7417c3918fc23e705fccf0 Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Tue, 16 Apr 2019 15:34:02 +0200 Subject: [PATCH] Implement reverse-tcp server/router --- src/Test.kt | 2 +- src/dev/w1zzrd/bungee/BungeeRTCPRouter.kt | 55 ++++----- src/dev/w1zzrd/bungee/BungeeRTCPServer.kt | 132 ++++++++++++++++++++++ src/dev/w1zzrd/bungee/BungeeServer.kt | 1 + 4 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 src/dev/w1zzrd/bungee/BungeeRTCPServer.kt diff --git a/src/Test.kt b/src/Test.kt index be06897..5d592e9 100644 --- a/src/Test.kt +++ b/src/Test.kt @@ -4,7 +4,7 @@ import java.net.InetAddress fun main(args: Array){ // Route localhost:80 -> google.com val server = BungeeServer( - InetAddress.getByName("0.0.0.0"), 25565 .. 25565, + InetAddress.getByName("0.0.0.0"), 25565, InetAddress.getByName("192.168.1.145"), 25565 ) server.start() diff --git a/src/dev/w1zzrd/bungee/BungeeRTCPRouter.kt b/src/dev/w1zzrd/bungee/BungeeRTCPRouter.kt index 9ef40c0..b868cb6 100644 --- a/src/dev/w1zzrd/bungee/BungeeRTCPRouter.kt +++ b/src/dev/w1zzrd/bungee/BungeeRTCPRouter.kt @@ -6,6 +6,7 @@ import java.security.PublicKey import java.security.Signature import java.util.concurrent.ThreadLocalRandom +// TODO: Inherit BungeeServer // Reverse TCP connection router (put this on a publicly accessible server) // A public key is provided so that a route can authenticate itself against this router class BungeeRTCPRouter( @@ -196,31 +197,6 @@ class BungeeRTCPRouter( } private fun ServerSocket.tryAccept() = try{ accept() }catch(e: SocketTimeoutException){ null } - private fun ByteArray.intify(offset: Int) - = this[offset].toInt().and(0xFF).or( - this[offset + 1].toInt().and(0xFF).shl(8) - ).or( - this[offset + 2].toInt().and(0xFF).shl(16) - ).or( - this[offset + 3].toInt().and(0xFF).shl(24) - ) - - private fun ByteArray.longify(offset: Int) - = this[offset].toLong().and(0xFF).or( - this[offset + 1].toLong().and(0xFF).shl(8) - ).or( - this[offset + 2].toLong().and(0xFF).shl(16) - ).or( - this[offset + 3].toLong().and(0xFF).shl(24) - ).or( - this[offset + 4].toLong().and(0xFF).shl(32) - ).or( - this[offset + 5].toLong().and(0xFF).shl(40) - ).or( - this[offset + 6].toLong().and(0xFF).shl(48) - ).or( - this[offset + 7].toLong().and(0xFF).shl(56) - ) private fun makeClientUID(): Long { var uid: Long @@ -253,4 +229,31 @@ class BungeeRTCPRouter( }catch(e: Throwable){ false } -} \ No newline at end of file +} + + +fun ByteArray.intify(offset: Int) + = this[offset].toInt().and(0xFF).or( + this[offset + 1].toInt().and(0xFF).shl(8) +).or( + this[offset + 2].toInt().and(0xFF).shl(16) +).or( + this[offset + 3].toInt().and(0xFF).shl(24) +) + +fun ByteArray.longify(offset: Int) + = this[offset].toLong().and(0xFF).or( + this[offset + 1].toLong().and(0xFF).shl(8) +).or( + this[offset + 2].toLong().and(0xFF).shl(16) +).or( + this[offset + 3].toLong().and(0xFF).shl(24) +).or( + this[offset + 4].toLong().and(0xFF).shl(32) +).or( + this[offset + 5].toLong().and(0xFF).shl(40) +).or( + this[offset + 6].toLong().and(0xFF).shl(48) +).or( + this[offset + 7].toLong().and(0xFF).shl(56) +) \ No newline at end of file diff --git a/src/dev/w1zzrd/bungee/BungeeRTCPServer.kt b/src/dev/w1zzrd/bungee/BungeeRTCPServer.kt new file mode 100644 index 0000000..d447a11 --- /dev/null +++ b/src/dev/w1zzrd/bungee/BungeeRTCPServer.kt @@ -0,0 +1,132 @@ +package dev.w1zzrd.bungee + +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.ServerSocket +import java.net.Socket +import java.nio.ByteBuffer +import java.security.PrivateKey +import java.security.Signature + +// TODO: Inherit BungeeServer +// Private key used to authenticate against router +class BungeeRTCPServer( + private val routerAddr: InetAddress, + private val routerPort: Int, + private val routeTo: InetAddress, + private val routePort: Int, + private val privateKey: PrivateKey +){ + // A map of a client UID to the "virtual client" (a socket from this server to the provided route endpoint) + private val vClients = HashMap() + private var canStart = true + private val serverSocket = Socket() + private val buffer = ByteArray(BUFFER_SIZE) + private val clientBuffer = ByteArray(BUFFER_SIZE) + private var alive = false + private val headerBuffer = ByteBuffer.allocateDirect(13) + + fun start(){ + if(!canStart) throw IllegalStateException("Already started/stopped") + canStart = false + + serverSocket.connect(InetSocketAddress(routerAddr, routerPort)) + + // Await data to sign + val read = serverSocket.getInputStream() + val write = serverSocket.getOutputStream() + var readCount = 0 + while(readCount < 256) readCount += read.read(buffer, readCount, buffer.size - readCount) + + val sig = Signature.getInstance("NONEwithRSA") + sig.initSign(privateKey) + sig.update(buffer, 0, 256) + val signLen = sig.sign(buffer, 8, buffer.size - 8) + buffer[0] = 0x69.toByte() + buffer[1] = 0x69.toByte() + buffer[2] = 0x37.toByte() + buffer[3] = 0x13.toByte() + buffer[4] = signLen.and(0xFF).toByte() + buffer[5] = signLen.ushr(8).and(0xFF).toByte() + buffer[6] = signLen.ushr(16).and(0xFF).toByte() + buffer[7] = signLen.ushr(24).and(0xFF).toByte() + + // Send signature + write.write(buffer, 0, 8 + signLen) + + var bufferBytes = 0 + alive = true + while(alive){ + if(read.available() > 0) + bufferBytes += read.read(buffer, bufferBytes, buffer.size - bufferBytes) + + var parsed = 0 + parseLoop@while(bufferBytes - parsed > 9){ + val uid = buffer.longify(parsed + 1) + + when(buffer[parsed]){ + 0.toByte() -> { + // New client + vClients[uid] = Socket(routeTo, routePort) + } + + 1.toByte() -> { + // Data from client + if(bufferBytes - parsed > 13){ + val dLen = buffer.intify(parsed + 9) + if(bufferBytes < parsed + dLen) break@parseLoop // Not enough data + try { + // Send data to server + vClients[uid]?.getOutputStream()?.write(buffer, parsed + 13, dLen) + }catch(e: Throwable){ + notifyClientDrop(uid) + } + + parsed += 4 + dLen + }else break@parseLoop // Not enough data + } + + 2.toByte() -> { + // Remote disconnection + vClients[uid]?.forceClose() + vClients.remove(uid) + } + } + + parsed += 9 + } + + System.arraycopy(buffer, parsed, buffer, 0, bufferBytes - parsed) + bufferBytes -= parsed + + + // Accept data from route endpoint + for((uid, client) in vClients){ + try { + val stream = client.getInputStream() + if (read.available() > 0) { + val clientRead = stream.read(clientBuffer, 0, clientBuffer.size) + if (clientRead > 0) sendVClientPacket(uid, clientBuffer, 0, clientRead) + } + }catch(e: Throwable){ + notifyClientDrop(uid) + } + } + } + } + + fun sendVClientPacket(uid: Long, data: ByteArray, off: Int, len: Int){ + notifyClientAction(uid, 0, data.size) + sendMessageToRouter(data, off, len) + } + fun notifyClientDrop(uid: Long) = notifyClientAction(uid, 1) + fun notifyClientAction(uid: Long, action: Byte, meta: Int? = null){ + headerBuffer.put(0, action) + headerBuffer.putLong(1, uid) + if(meta != null) headerBuffer.putInt(9, meta) + sendMessageToRouter(headerBuffer.array(), 0, if(meta == null) 9 else 13) + } + fun sendMessageToRouter(data: ByteArray, off: Int, len: Int){ + serverSocket.getOutputStream().write(data, off, len) + } +} \ No newline at end of file diff --git a/src/dev/w1zzrd/bungee/BungeeServer.kt b/src/dev/w1zzrd/bungee/BungeeServer.kt index 5329c32..d97ea9e 100644 --- a/src/dev/w1zzrd/bungee/BungeeServer.kt +++ b/src/dev/w1zzrd/bungee/BungeeServer.kt @@ -8,6 +8,7 @@ const val BUFFER_SIZE = 16777216 // 16 MiB communication buffer fun Socket.forceClose() = try{ close() }catch(t: Throwable){} +// TODO: Make better class BungeeServer( private val listenAddr: InetAddress, private val port: Int,