From a5acb70441ad9761d2da49ce978a63ee437f83ea Mon Sep 17 00:00:00 2001 From: Walter Huf Date: Sat, 2 Nov 2024 19:36:15 -0700 Subject: [PATCH] Split out TcpMuxer --- .../bcl/client/BclProxyServer.kt | 111 ------------------ .../bcl/multiplex/BclProxyServer.kt | 78 ++++++++++++ .../bimmergestalt/bcl/multiplex/TcpMuxer.kt | 66 +++++++++++ .../bcl/protocols/TcpProxyProtocol.kt | 2 +- 4 files changed, 145 insertions(+), 112 deletions(-) delete mode 100644 app/src/main/java/io/bimmergestalt/bcl/client/BclProxyServer.kt create mode 100644 app/src/main/java/io/bimmergestalt/bcl/multiplex/BclProxyServer.kt create mode 100644 app/src/main/java/io/bimmergestalt/bcl/multiplex/TcpMuxer.kt diff --git a/app/src/main/java/io/bimmergestalt/bcl/client/BclProxyServer.kt b/app/src/main/java/io/bimmergestalt/bcl/client/BclProxyServer.kt deleted file mode 100644 index b4defe3..0000000 --- a/app/src/main/java/io/bimmergestalt/bcl/client/BclProxyServer.kt +++ /dev/null @@ -1,111 +0,0 @@ -package io.bimmergestalt.bcl - -import io.bimmergestalt.bcl.client.ProxyConnectionGrantor -import io.bimmergestalt.bcl.protocols.ProxyClientConnection -import org.tinylog.kotlin.Logger -import java.io.IOException -import java.io.OutputStream -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.CancelledKeyException -import java.nio.channels.SelectionKey -import java.nio.channels.Selector -import java.nio.channels.ServerSocketChannel -import java.nio.channels.SocketChannel - -class BclProxyServer(val listenPort: Short, val connection: ProxyConnectionGrantor) { - val selector = Selector.open() - val serverSocket = ServerSocketChannel.open() - val connections = HashMap() - - fun listen() { - serverSocket.configureBlocking(false) - serverSocket.register(selector, SelectionKey.OP_ACCEPT) - serverSocket.bind(InetSocketAddress(listenPort.toInt())) - } - - fun run() { - val inputBuffer = ByteBuffer.allocate(4000) - while (serverSocket.isOpen) { - selector.select(10000) - val readyKeys = selector.selectedKeys() - readyKeys.forEach { key -> - try { - if (key.isAcceptable) { - // new client connection - val socket = serverSocket.accept() - socket.configureBlocking(false) - socket.socket().tcpNoDelay = true - socket.register(selector, SelectionKey.OP_READ) - val bclProxyConnection = connection.openConnection(SocketChannelOutputStream(socket)) - connections[socket] = bclProxyConnection - } - if (key.isReadable) { - // new data from the client - val channel = key.channel() - if (channel is SocketChannel) { - val len = channel.read(inputBuffer) -// Logger.debug { "Read $len bytes from client socket" } - if (len > 0) { - try { - connections[channel]?.toTunnel?.write(inputBuffer.array(), 0, len) - } catch (e: IOException) { - Logger.warn(e) { "IOException while writing to BclConnection from client" } - channel.close() - connections.remove(channel)?.close() - } - } else if (len == -1) { - key.cancel() - connections.remove(channel)?.close() - } - inputBuffer.clear() - } - } - } catch (_: CancelledKeyException) {} - } - readyKeys.clear() - } - shutdown() - } - - fun shutdown() { - synchronized(connections) { - connections.keys.forEach { - try { - it.close() - } catch (e: IOException) { - Logger.warn(e) { "IOException while shutting down BclProxy client" } - } - } - connections.clear() - try { - serverSocket.close() - } catch (e: IOException) { - Logger.warn(e) { "IOException while shutting down BclProxy ServerSocket" } - } - } - } -} - -/** - * Masquerades a SocketChannel as an OutputStream - */ -class SocketChannelOutputStream(private val channel: SocketChannel): OutputStream() { - override fun write(b: Int) { - channel.write(ByteBuffer.wrap(byteArrayOf(b.toByte()))) - } - - override fun write(b: ByteArray?) { - b ?: return - channel.write(ByteBuffer.wrap(b)) - } - - override fun write(b: ByteArray?, off: Int, len: Int) { - b ?: return - channel.write(ByteBuffer.wrap(b, off, len)) - } - - override fun close() { - channel.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/io/bimmergestalt/bcl/multiplex/BclProxyServer.kt b/app/src/main/java/io/bimmergestalt/bcl/multiplex/BclProxyServer.kt new file mode 100644 index 0000000..1f6478b --- /dev/null +++ b/app/src/main/java/io/bimmergestalt/bcl/multiplex/BclProxyServer.kt @@ -0,0 +1,78 @@ +package io.bimmergestalt.bcl.multiplex + +import io.bimmergestalt.bcl.client.ProxyConnectionGrantor +import org.tinylog.kotlin.Logger +import java.io.IOException +import java.io.OutputStream +import java.net.InetSocketAddress +import java.nio.ByteBuffer +import java.nio.channels.CancelledKeyException +import java.nio.channels.SelectionKey +import java.nio.channels.Selector +import java.nio.channels.ServerSocketChannel +import java.nio.channels.SocketChannel + +class BclProxyServer(val listenPort: Short, val connection: ProxyConnectionGrantor) { + val selector = Selector.open() + val serverSocket = ServerSocketChannel.open() + val muxer = TcpMuxer(selector) + + fun listen() { + serverSocket.configureBlocking(false) + serverSocket.register(selector, SelectionKey.OP_ACCEPT) + serverSocket.bind(InetSocketAddress(listenPort.toInt())) + } + + fun run() { + while (serverSocket.isOpen) { + selector.select(10000) + val readyKeys = selector.selectedKeys() + readyKeys.forEach { key -> + try { + if (key.isAcceptable) { + // new client connection + val socket: SocketChannel = serverSocket.accept() + val bclProxyConnection = connection.openConnection(SocketChannelOutputStream(socket)) + muxer.addChannel(socket, bclProxyConnection) + } + } catch (_: CancelledKeyException) {} + } + readyKeys.clear() + + muxer.process() + } + shutdown() + } + + fun shutdown() { + try { + serverSocket.close() + } catch (e: IOException) { + Logger.warn(e) { "IOException while shutting down BclProxy ServerSocket" } + } + muxer.shutdown() + } +} + +/** + * Masquerades a SocketChannel as an OutputStream + */ +class SocketChannelOutputStream(private val channel: SocketChannel): OutputStream() { + override fun write(b: Int) { + channel.write(ByteBuffer.wrap(byteArrayOf(b.toByte()))) + } + + override fun write(b: ByteArray?) { + b ?: return + channel.write(ByteBuffer.wrap(b)) + } + + override fun write(b: ByteArray?, off: Int, len: Int) { + b ?: return + channel.write(ByteBuffer.wrap(b, off, len)) + } + + override fun close() { + channel.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/bimmergestalt/bcl/multiplex/TcpMuxer.kt b/app/src/main/java/io/bimmergestalt/bcl/multiplex/TcpMuxer.kt new file mode 100644 index 0000000..1a42cdf --- /dev/null +++ b/app/src/main/java/io/bimmergestalt/bcl/multiplex/TcpMuxer.kt @@ -0,0 +1,66 @@ +package io.bimmergestalt.bcl.multiplex + +import io.bimmergestalt.bcl.protocols.ProxyClientConnection +import org.tinylog.kotlin.Logger +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.channels.CancelledKeyException +import java.nio.channels.SelectionKey +import java.nio.channels.Selector +import java.nio.channels.SocketChannel + +class TcpMuxer(val selector: Selector) { + val connections = HashMap() + fun addChannel(channel: SocketChannel, muxStream: ProxyClientConnection) { + channel.configureBlocking(false) + channel.socket().tcpNoDelay = true + channel.register(selector, SelectionKey.OP_READ) + connections[channel] = muxStream + } + + fun process() { + val inputBuffer = ByteBuffer.allocate(4000) + + selector.selectNow() + val readyKeys = selector.selectedKeys() + readyKeys.forEach { key -> + try { + if (key.isReadable) { + // new data from the client + val channel = key.channel() + if (channel is SocketChannel) { + val len = channel.read(inputBuffer) +// Logger.debug { "Read $len bytes from client socket" } + if (len > 0) { + try { + connections[channel]?.toTunnel?.write(inputBuffer.array(), 0, len) + } catch (e: IOException) { + Logger.warn(e) { "IOException while writing to BclConnection from client" } + channel.close() + connections.remove(channel)?.close() + } + } else if (len == -1) { + key.cancel() + connections.remove(channel)?.close() + } + inputBuffer.clear() + } + } + } catch (_: CancelledKeyException) {} + } + readyKeys.clear() + } + + fun shutdown() { + synchronized(connections) { + connections.keys.forEach { + try { + it.close() + } catch (e: IOException) { + Logger.warn(e) { "IOException while shutting down muxer client" } + } + } + connections.clear() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/bimmergestalt/bcl/protocols/TcpProxyProtocol.kt b/app/src/main/java/io/bimmergestalt/bcl/protocols/TcpProxyProtocol.kt index 9589ef6..1174ef4 100644 --- a/app/src/main/java/io/bimmergestalt/bcl/protocols/TcpProxyProtocol.kt +++ b/app/src/main/java/io/bimmergestalt/bcl/protocols/TcpProxyProtocol.kt @@ -1,6 +1,6 @@ package io.bimmergestalt.bcl.protocols -import io.bimmergestalt.bcl.BclProxyServer +import io.bimmergestalt.bcl.multiplex.BclProxyServer import io.bimmergestalt.bcl.ConnectionState import io.bimmergestalt.bcl.MutableConnectionState import io.bimmergestalt.bcl.client.ProxyConnectionGrantor