commit 32314eba68e31ddbd2f889ee12fce6c4e4262489 Author: Gabriel Tofvesson Date: Tue Apr 16 13:12:57 2019 +0200 Add standard relay server diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b08e5aa --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..3c50bd3 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 0000000..9fbfb0d --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0319d5d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..694bf74 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Bungee.iml b/Bungee.iml new file mode 100644 index 0000000..245d342 --- /dev/null +++ b/Bungee.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Test.kt b/src/Test.kt new file mode 100644 index 0000000..be06897 --- /dev/null +++ b/src/Test.kt @@ -0,0 +1,12 @@ +import dev.w1zzrd.bungee.BungeeServer +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("192.168.1.145"), 25565 + ) + server.start() + server.waitFor() +} \ No newline at end of file diff --git a/src/dev/w1zzrd/bungee/BungeeServer.kt b/src/dev/w1zzrd/bungee/BungeeServer.kt new file mode 100644 index 0000000..8fd11d8 --- /dev/null +++ b/src/dev/w1zzrd/bungee/BungeeServer.kt @@ -0,0 +1,106 @@ +package dev.w1zzrd.bungee + +import java.net.* + +const val SERVER_CONNECTION_BACKLOG = 65536 +const val BUFFER_SIZE = 16777216 // 16 MiB communication buffer + +class BungeeServer( + private val listenAddr: InetAddress, + private val port: Int, + private val routeTo: InetAddress, + private val routePort: Int +){ + private var canStart = true + private val serverSocket = ServerSocket() + private val buffer = ByteArray(BUFFER_SIZE) + private val listenThread = Thread(this::acceptSocket) + private val clients = HashMap() + private var alive = false + + init { + serverSocket.soTimeout = 1 + } + + /** + * Start listening for incoming connections. + */ + fun start(){ + if(!canStart) throw IllegalStateException("Already started/stopped") + canStart = false + + // Set up server socket + serverSocket.bind(InetSocketAddress(listenAddr, port), SERVER_CONNECTION_BACKLOG) + + listenThread.start() + } + + fun waitFor() = listenThread.join() + + private fun acceptSocket(){ + alive = true + while(alive) { + // Accept new clients + serverSocket.acceptPassthrough() + + // Remove connections that have been dropped + purgeDeadConnections() + + // Forward data + for ((clientSocket, routeSocket) in clients) { + // Bidirectional communication + clientSocket.forwardConnection(routeSocket) + routeSocket.forwardConnection(clientSocket) + } + } + } + + private fun purgeDeadConnections(){ + for((clientSocket, routeSocket) in clients){ + if( + !(clientSocket.isConnected && routeSocket.isConnected) || + clientSocket.isClosed || + routeSocket.isClosed + ){ + purgeConnectionRoute(clientSocket, routeSocket) + } + } + } + + private fun purgeConnectionRoute(clientSocket: Socket, routeSocket: Socket){ + clientSocket.forceClose() + routeSocket.forceClose() + clients.remove(clientSocket, routeSocket) + } + + private fun Socket.forceClose() = try{ close() }catch(t: Throwable){} + + private fun Socket.forwardConnection(dest: Socket){ + val from = getInputStream() + val to = dest.getOutputStream() + + try { + if (from.available() > 0) { + val read = from.read(buffer, 0, buffer.size) + if (read > 0) to.write(buffer, 0, read) + } + }catch(t: Throwable){ + // Probably a disconnection tbh + purgeConnectionRoute(this, dest) + } + } + + + private fun ServerSocket.acceptPassthrough(){ + val client: Socket + try { + client = accept() + } catch (e: SocketTimeoutException) { + // Accepting clients timed out: no new clients + return + } + + // Create a corresponding, outgoing connection for the new client + clients[client] = Socket(routeTo, routePort) + } +} \ No newline at end of file