Reputation: 9
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
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