user23926633
user23926633

Reputation: 9

DatagramChannel port forwarding possible in Kotlin Android?

I am currently trying to build an app that should allow me to forward UDP packets. The server addresses the received packets to the destination server and sends back the packets received from the destination server to the client. The goal behind this is to create an app that makes Minecraft Bedrock servers available for consoles via LAN. I know there are already such apps available, but they are unnecessarily complicated, using protocol proxies and are filled with advertisements. This app should be simple because it only forwards the data, requires minimal updates, and thus should work without much effort.

My attempt with DatagramSocket has shown that the blocking is problematic, and DatagramChannel, which can disable blocking, is better suited. However, this requires a complex setup with DatagramChannel.register(Selector), I think, and as a beginner in Kotlin and Android app development, it can be overwhelming.

At some point, I tried to write the functions with ChatGPT, and while it produces nice templates that I can improve later, it seems unable to implement this code, likely due to a lack of examples on the web. The examples I found lead to a crash when the client connects.

So far, I have only managed to establish a connection up to the world data using DatagramSocket, but it blocks too heavily and is therefore not suitable. It almost feels like attacking the server.

Therefore, my plan was to use DatagramChannel, but it is just too complicated for me. My first attempt was without .register(Selector) and only with a loop. Although it worked, I then received the message that the messages are too long and didn't yet know that they need to be increased with DatagramChannel.socket().setReceiveBufferSize(MAX_PACKET_SIZE). Additionally, I believe the test function lacked a check if a connection has already been established.

Perhaps an experienced developer among you can help me further here, someone who is familiar with the topic and can tell me whether my approach is even correct with the use of DatagramChannel or provide a small example.

https://github.com/JanisPlayer/Minecraft-Bedrock-Connect/blob/main/README.md EN:
The basic idea of packet forwarding can be tested on Android with Userland: screen -dmS socat socat UDP4-LISTEN:19132,fork,su=nobody UDP4:[164.68.125.80]:19132

Protocol:
wiki.vg/Raknet_Protocol
wiki.bedrock.dev/
Proxies:
github.com/Pugmatt/BedrockConnect/
github.com/CloudburstMC/Protocol
github.com/haveachin/infrared
github.com/cubeworx/cbwxproxy (Docker)
github.com/cubeworx/cbwxannounce/ (Source)
github.com/illiteratealliterator/manymine/ (clock jump)
developer.android.com/reference/java/net/DatagramSocket
developer.android.com/reference/java/nio/channels/DatagramChannel (Better solution than DatagramSocket)
Here is the DatagramChannel.configureBlocking(false),
DatagramChannel.socket().setReceiveBufferSize(MAX_PACKET_SIZE),
DatagramChannel.socket().setSoTimeout(100),
DatagramChannel.register(Selector) interesting.
github.com/elixsr/FwdPortForwardingApp/

MainActivity.kt DatagramSocket:

private fun startUDPServer() {
    try {
        val serverSocket = DatagramSocket(SOURCE_PORT, InetAddress.getByName("0.0.0.0"))
        val clientSocket  = DatagramSocket()

        var maxPacketSize = 65000
        var timeout = 100

        while (true) {
            try {
                serverSocket.setSoTimeout(timeout)
                serverSocket.setReceiveBufferSize(maxPacketSize)
                serverSocket.setSendBufferSize(maxPacketSize)
                clientSocket.setSoTimeout(timeout)
                clientSocket.setReceiveBufferSize(maxPacketSize)
                clientSocket.setSendBufferSize(maxPacketSize)

                val packet = DatagramPacket(ByteArray(maxPacketSize), maxPacketSize)
                serverSocket.receive(packet)

                Log.d(TAG, "Received UDP packet: ${packet.length}")

                val sendPacket = DatagramPacket(packet.data, packet.length, InetAddress.getByName(DESTINATION_IP), DESTINATION_PORT)
                clientSocket.send(sendPacket)

                val receivePacket = DatagramPacket(ByteArray(maxPacketSize), maxPacketSize, packet.address, packet.port)
                clientSocket.receive(receivePacket)

                receivePacket.address = packet.address
                receivePacket.port = packet.port

                serverSocket.send(receivePacket)
            } catch (e: Exception) {
                Log.d(TAG, "Error receiving UDP packet: ${e.message}")
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "Error starting UDP server: ${e.message}")
    }
}

I could only connect up to the world data, after that it usually lost the connection. It doesn't work without a timeout because it blocks too much. This should actually be divided into threads, but all attempts to do this have ended in crashes. In addition, timeout is not a real solution to the problem that the data does not arrive in the send and receive pattern, so that just makes everything slow and is not intended for that. And I haven't found a way to create a thread in DatagramSocket just for receiving so that sending is not blocked and the whole program is not blocked. However, this function is available in DatagramChannel.

private fun startnewUDPServer() {
    try {
        // Create a DatagramChannel to receive UDP packets on the source port
        channel = DatagramChannel.open()
        channel.socket().bind(InetSocketAddress(SOURCE_PORT))
        channel.socket().setReceiveBufferSize(MAX_PACKET_SIZE)
        channel.socket().setSoTimeout(100)
        channel.configureBlocking(true)
        // Infinite loop to receive packets continuously
        val buffer = ByteBuffer.allocate(MAX_PACKET_SIZE)
        while (true) {
            buffer.clear()
            val senderAddress = channel.receive(buffer)
            // Forward the received packet to the destination if senderAddress is not null
            senderAddress?.let {
                Log.d(TAG, "Received data from $senderAddress.")
                buffer.flip()
                forwardPacket(buffer, it as InetSocketAddress, senderAddress) // senderAddress sent here twice because of an attempt.
            }
        }
    } catch (e: IOException) {
        e.printStackTrace()
        Log.d(TAG, "Error starting UDP server")
    }
}

private fun forwardPacket(buffer: ByteBuffer, senderAddress: InetSocketAddress, clientSenderAddress: SocketAddress) {
    try {
        // Create a new DatagramChannel for the forwarding process
        val forwardChannel = DatagramChannel.open()
        forwardChannel.socket().setReceiveBufferSize(MAX_PACKET_SIZE)
        forwardChannel.socket().setSoTimeout(1000)
        forwardChannel.configureBlocking(true)
        // Connect the forwarding channel to the destination address and port
        // Does it need to be checked to see if it is connected?
        forwardChannel.connect(InetSocketAddress(DESTINATION_IP, DESTINATION_PORT))
        // Send the package to the destination
        forwardChannel.write(buffer)
        val receiveBuffer = ByteBuffer.allocate(MAX_PACKET_SIZE)
        receiveBuffer.clear()
        val senderAddress = forwardChannel.receive(receiveBuffer)
        // Check if senderAddress is not null and then send the packet to the client
        if (senderAddress != null) {
            Log.d(TAG, "Received data from $senderAddress.")
            receiveBuffer.flip()
            //Send the data to the client
            channel.send(receiveBuffer, clientSenderAddress)
        }
        buffer.clear()
        // Need to close the channel after sending the packet?
        forwardChannel.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

I tried to replicate the UdpForwarder() function using ChatGPT, based on this project. Then, I attempted to correct the result, but it resulted in crashes. Some of the proposed solutions by ChatGPT, like UdpForwarderTask(), seemed a bit nonsensical, but I still tried them because my own solutions weren't much better at the time. :D

Upvotes: 0

Views: 152

Answers (1)

user23926633
user23926633

Reputation: 9

private fun startNewUDPServer() {
    try {
        // Create a DatagramChannel to receive UDP packets on the source port
        val channel = DatagramChannel.open()
        channel.socket().bind(InetSocketAddress(SOURCE_PORT))
        channel.socket().setReceiveBufferSize(MAX_PACKET_SIZE)
        //channel.socket().setSoTimeout(100)
        channel.configureBlocking(false)

        val forwardChannel = DatagramChannel.open()
        forwardChannel.socket().setReceiveBufferSize(MAX_PACKET_SIZE)
        //forwardChannel.socket().setSoTimeout(100)
        forwardChannel.configureBlocking(false)
        var clientSenderAddress: SocketAddress = InetSocketAddress(DESTINATION_IP, DESTINATION_PORT)
        var forwardSenderAddress: SocketAddress = InetSocketAddress(DESTINATION_IP, DESTINATION_PORT)

        // Infinite loop for continuous packet reception
        val buffer = ByteBuffer.allocate(MAX_PACKET_SIZE)
        val receiveBuffer = ByteBuffer.allocate(MAX_PACKET_SIZE)

        while (true) {
            buffer.clear()
            val senderAddress = channel.receive(buffer)

            senderAddress?.let {
                Log.d(TAG, "Received data from $senderAddress: ${buffer}")
                clientSenderAddress = senderAddress
                buffer.flip()
                forwardChannel.send(buffer, forwardSenderAddress)
            }

            receiveBuffer.clear()
            val forwardSenderAddressTemp = forwardChannel.receive(receiveBuffer)

            forwardSenderAddressTemp?.let {
                Log.d(TAG, "Received data from $forwardSenderAddress: ${receiveBuffer} $clientSenderAddress")
                receiveBuffer.flip()
                channel.send(receiveBuffer, clientSenderAddress)
            }
        }
    } catch (e: IOException) {
        e.printStackTrace()
        Log.d(TAG, "Error occurred")
    }
}
    private fun startnewUDPServerThreads() {
        val channel = DatagramChannel.open()
        val forwardChannel = DatagramChannel.open()
        var forwardSenderAddress = InetSocketAddress(DESTINATION_IP, DESTINATION_PORT) as SocketAddress
        var clinetSenderAddress = InetSocketAddress(DESTINATION_IP, DESTINATION_PORT) as SocketAddress

        val receiveThread = Thread {
            try {
                channel.socket().bind(InetSocketAddress(SOURCE_PORT))
                channel.socket().receiveBufferSize = MAX_PACKET_SIZE
                channel.configureBlocking(true)
                //channel.socket().soTimeout = 100 //Does not work to prevent reception stop. For this to work at all, channel must be at 100 and forwardChannel must be at 1000.

                val buffer = ByteBuffer.allocate(MAX_PACKET_SIZE)

                while (true) {
                    buffer.clear()
                    val senderAddress = channel.receive(buffer)
                    senderAddress?.let {
                        clinetSenderAddress = senderAddress
                        buffer.flip()
                        forwardChannel.send(buffer, forwardSenderAddress)
                    }
                }
            } catch (e: IOException) {
                e.printStackTrace()
                Log.d(TAG, "Error occurred")
            }
        }
        receiveThread.start()

        val forwardThread = Thread {
            try {
                forwardChannel.socket().receiveBufferSize = MAX_PACKET_SIZE
                forwardChannel.configureBlocking(true)
                forwardChannel.socket().soTimeout = 1000

                val receiveBuffer = ByteBuffer.allocate(MAX_PACKET_SIZE)

                while (true) {
                    receiveBuffer.clear()
                    val forwardSenderAddressTemp = forwardChannel.receive(receiveBuffer)
                    forwardSenderAddressTemp?.let {
                        receiveBuffer.flip()
                        channel.send(receiveBuffer, clinetSenderAddress)
                    }
                }
            } catch (e: IOException) {
                e.printStackTrace()
                Log.d(TAG, "Error occurred")
            }
        }
        forwardThread.start()
    }

Upvotes: -1

Related Questions