1

I'm currently developing a server and app that needs to communicate using UDP. My server uses Golang, it's related code looks like this:

import (
    "flag"
    "fmt"
    "net"
    "os"
)

var (
    UDP_SOCKET_PORT  = flag.Int("udp_socket_server_port", 10001, "Socket sckServer port")
    udpSocketAddrArr = []net.Addr{}
    _udpConn         net.PacketConn
    _broadcastAddr   *net.UDPAddr
)

func StartUDPSocket() {
    _udpConn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
    if err != nil {
        panic(err)
    }

    defer _udpConn.Close()
    fmt.Printf("conn: %s\n", _udpConn.LocalAddr())

    _broadcastAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", getBroadcastIP(), *UDP_SOCKET_PORT))
    if err != nil {
        panic(err)
    }

    fmt.Printf("UDP serving at %s:%d...\n", _broadcastAddr.IP, _broadcastAddr.Port)

    for {
        buf := make([]byte, 2048)
        _, addr, err := _udpConn.ReadFrom(buf)
        if err != nil {
            panic(err)
        }

        udpSocketAddrArr = append(udpSocketAddrArr, addr)
        messageTxt := string(buf)

        checkUDPPayload(messageTxt, _udpConn, _broadcastAddr)
    }
}

func UDPSend(payload string) {
    for _, el := range udpSocketAddrArr {
        udpConnWrite(payload, GetUDPConn(), el)
    }
}

func UDPBroadcast(payload string) {
    udpConnWrite(payload, _udpConn, _broadcastAddr)
}

func udpConnWrite(payload string, conn net.PacketConn, addr net.Addr) {
    bytesWritten, err := conn.WriteTo([]byte(payload), addr)
    if err != nil {
        panic(err)
    }

    fmt.Printf("UDP Wrote %d bytes to %s\n", bytesWritten, addr.String())
}

func getBroadcastIP() string {
    return "255.255.255.255"
}

func GetCurrentIP() string {
    //find current ip
    mIpV4 := ""
    host, _ := os.Hostname()
    addrs, _ := net.LookupIP(host)
    for _, addr := range addrs {
        if ipv4 := addr.To4(); ipv4 != nil {
            mIpV4 = ipv4.String()
        }
    }

    return mIpV4
}

func GetUDPConn() net.PacketConn {
    return _udpConn
}

func GetUDPBroadcastConn() *net.UDPAddr {
    return _broadcastAddr   
}

Then inside checkUDPPayload(), I have a code that looks like this:

func checkUDPPayload(payload string, conn net.PacketConn, addr net.Addr) {
    ...
    udpConnWrite(
        "some string",
        conn,
        addr,
    )
} 

It works, it receives the payload from my android app (which was a pain to make it work on my home network for some reason, and works fairly easily at work). But my app can't receive the packet server sent, and my app can receive it's own packet (which makes sense, I guess). My android code looks like this:

private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255")
private lateinit var localBroadcastAddr: InetAddress
private lateinit var connectedToServerSocket: DatagramSocket
private lateinit var appSocket: DatagramSocket
val socketRespObservers = mutableListOf<(String) -> Unit>()
private var socketResp: String by Delegates.observable("") { _, _, newVal ->
    socketRespObservers.forEach { it(newVal) }
}
private lateinit var ctx: FragmentActivity

fun start(ctx: FragmentActivity) {
    this@SocketHelper.ctx = ctx

    //socket communication
    ctx.lifecycleScope.launch(Dispatchers.IO) {
        try {
            connectedToServerSocket = DatagramSocket()
            appSocket = DatagramSocket(UDP_SERVER_PORT)

            connectedToServerSocket.connect(getBroadcastAddr(), UDP_SERVER_PORT)
            connectedToServerSocket.broadcast = true

            Log.i(MainActivity.TAG, "connectedToServerSocket connected: ${connectedToServerSocket.isConnected}")
            Log.i(MainActivity.TAG, "appSocket connected: ${appSocket.isConnected}")
            
            sendToUDP("some string", getLocalBroadcastAddr())

            while (true) {
                receiveFromUDP()
                receiveFromServerUDP()
            }
        } catch (e: SocketException) {
            Log.e(TAG, "Socket Error:", e)
            close()
        }catch (e: SecurityException) {
            Log.e(TAG, "Sec Error:", e)
            close()
        } catch (e: IOException) {
            Log.e(TAG, "IO Error:", e)
            close()
        } catch (e: Exception) {
            Log.e(TAG, "Error:", e)
            close()
        }
    }
}

fun sendToUDP(payload: String, srvAddr: InetAddress) {
    try {
        val sendPacket =
            DatagramPacket(
                payload.toByteArray(),
                payload.length,
                srvAddr,
                UDP_SERVER_PORT
            )
            
        appSocket.send(sendPacket)
    }catch (e : java.lang.Exception){
        Log.e(TAG, "err sending udp: ${e.localizedMessage}")
    }
}

private fun receiveFromUDP() {
    val receiveData = ByteArray(1024)
    val receivePacket = DatagramPacket(receiveData, receiveData.size)

    appSocket.receive(receivePacket)
    socketResp = String(receivePacket.data, 0, receivePacket.length)
}

private fun receiveFromServerUDP() {
    val receiveData = ByteArray(1024)
    val receivePacket = DatagramPacket(receiveData, receiveData.size)

    connectedToServerSocket.receive(receivePacket)
    socketResp = String(receivePacket.data, 0, receivePacket.length)
}

fun getBroadcastAddr() : InetAddress {
    return broadcastAddr
}

fun getLocalBroadcastAddr() : InetAddress {
    if(!::localBroadcastAddr.isInitialized){
        val currentIp = Utils.instance.getIpv4HostAddress()
        Log.i(TAG, "currentIp: $currentIp")
        val currentIpArr = currentIp.split(".")
        localBroadcastAddr = InetAddress.getByName("${currentIpArr[0]}.${currentIpArr[1]}.${currentIpArr[2]}.255")
    }

    return localBroadcastAddr
}

fun close() {
    appSocket.close()
}

companion object {
    const val TAG = "SocketHelper"
    val instance: SocketHelper by lazy {
        SocketHelper()
    }
    const val UDP_SERVER_PORT = 10001
}

The code that works at work uses 255.255.255.255 for sending and a DatagramSocket doesn't need a port. And I use that DatagramSocket for both sending and receiving. As you can see, the code is more uglier as I tried to have 2 DatagramSocket to see if having different configured DatagramSocket, I might receive the server's response to my "message" as right now, at my home network, the app can't receive the server's packet only the one sent my the app itself. connectedToServerSocket.isConnected return true, but I seen an answer that it was meant for TCP, and even if it says it's connected, it doesn't seem do anything useful for me.

UPDATE:

I did some testing today and continue to do some research and I still can't make this work. Hopefully, when I get back to work, nothing breaks. I did some small changes to both sides. Now I'm thinking, maybe the issue is caused by my local network at home. My ISP is kinda lame, like currently my NAT is set to strict which I think is caused by me not having a fixed IP (because it cost almost as much as my current prepaid internet plan), so I have some suspicions that my modem's configuration might have an effect on the UDP communication on my local network. So I bought a router thinking that if I connect my PC and phone to it's network, I would at least have more control to at least allowing UDP ports so my system would work, but it doesn't seem to be that easy. I already activated UPnP, but my golang server doesn't seem to be included in the apps that gets allowed or can use UPnP.

UPDATE:

So just got back home from work, and my changes did break the system from working on my work's network. Then I checked my previous working code, and at least on the android side, it's the simplest it could be. Here's what it looks like:

private lateinit var basicSocket: DatagramSocket
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255")
basicSocket = DatagramSocket()

basicSendToUDP("some string", getBroadcastAddr())

while (true) {
    basicReceiveFromUDP()
}

fun basicSendToUDP(payload: String, iAddr: InetAddress) {
    Thread {
        val sendPacket =
            DatagramPacket(
                payload.toByteArray(),
                payload.length,
                iAddr,
                UDP_SERVER_PORT
            )
        basicSocket.send(sendPacket)
    }.start()
}

private fun basicReceiveFromUDP() {
    val receiveData = ByteArray(1024)
    val receivePacket = DatagramPacket(receiveData, receiveData.size)

    basicSocket.receive(receivePacket)
    socketResp = String(receivePacket.data, 0, receivePacket.length)
}

fun getBroadcastAddr(): InetAddress {
    return broadcastAddr
}

Then here's what the golang side looks like, it's isn't that different from what I posted earlier:

import (
    "flag"
    "fmt"
    "net"
    "os"
)

var (
    UDP_SOCKET_PORT  = flag.Int("udp_socket_server_port", 10001, "Socket sckServer port")
    udpSocketAddrArr = []net.Addr{}
    _udpServer         net.PacketConn
    _broadcastAddr   *net.UDPAddr
)

func StartUDPSocket() {
    conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
    if err != nil {
        panic(err)
    }

    _udpServer = conn//don't remove this; this seems to be quite important
    defer _udpServer.Close()

    fmt.Printf("UDP serving at %v...\n", _udpServer.LocalAddr().String())

    _broadcastAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", getBroadcastIP(), *UDP_SOCKET_PORT))
    if err != nil {
        panic(err)
    }

    for {
        buf := make([]byte, 2048)
        _, addr, err := _udpServer.ReadFrom(buf)
        if err != nil {
            panic(err)
        }

        udpSocketAddrArr = append(udpSocketAddrArr, addr)
        messageTxt := string(buf)

        checkUDPPayload(messageTxt, _udpServer, addr)
    }
}

func UDPSend(payload string) {
    for _, el := range udpSocketAddrArr {
        udpConnWrite(payload, GetUDPConn(), el)
    }
}

func UDPBroadcast(payload string) {
    udpConnWrite(payload, GetUDPConn(), GetUDPBroadcastConn())
}

func udpConnWrite(payload string, conn net.PacketConn, addr net.Addr) {
    if conn == nil {
        fmt.Println("udpConnWrite conn nil")
    }

    if addr == nil {
        fmt.Println("udpConnWrite addr nil")
    }

    bytesWritten, err := conn.WriteTo([]byte(payload), addr)
    if err != nil {
        panic(err)
    }

    fmt.Printf("UDP Wrote %d bytes to %s\n", bytesWritten, addr.String())
}

func getBroadcastIP() string {
    return "255.255.255.255"
}

func GetCurrentIP() string {
    //find current ip
    mIpV4 := ""
    host, _ := os.Hostname()
    addrs, _ := net.LookupIP(host)
    for _, addr := range addrs {
        if ipv4 := addr.To4(); ipv4 != nil {
            mIpV4 = ipv4.String()
        }
    }

    return mIpV4
}

func GetUDPConn() net.PacketConn {
    return _udpServer
}

func GetUDPBroadcastConn() *net.UDPAddr {
    return _broadcastAddr   
}

func GetUDPConnLst() []net.Addr {
    return udpSocketAddrArr
}

func ClearUDPConnLst() {
    udpSocketAddrArr = nil
}

So what could be wrong on my home network when the simplest code works flawlessly on my work network?

rminaj
  • 560
  • 6
  • 28

1 Answers1

0

In your Golang server code, you are using the same port for listening for incoming messages and for broadcasting (which is supported by a connectionless protocol like UDP).

// Check if UDP Server is listening at the correct address and port
func StartUDPSocket() {
    // Consider printing this information to confirm it is what you expect.
    fmt.Printf("Listening on UDP port: %d\n", *UDP_SOCKET_PORT)
    // ...
}

Make sure the port is open and accessible in your local network. You could also check whether the packet connection is set up correctly.

Plus, in your updated Go code, the size of the buffer buf is hard-coded to 2048 bytes. That may cause issues if the incoming packet is larger, leading to truncation, or if it is smaller, resulting in unnecessary memory allocation.

And your current code uses panic(err) to handle errors, which is not the best approach in production code, as it would crash the entire application.

for {
    buf := make([]byte, 2048)
    _, addr, err := _udpServer.ReadFrom(buf)
    if err != nil {
        panic(err)
    }
    // other logic
}

You might consider instead, for instance:

const maxUDPSize = 65507 // The maximum UDP packet size for IPv4

func StartUDPSocket() {
    conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", *UDP_SOCKET_PORT))
    if err != nil {
        log.Fatalf("Could not start UDP server: %v", err) // Replaced panic with log
        return
    }

    _udpServer = conn
    defer _udpServer.Close()

    // (other code)

    for {
        buf := make([]byte, maxUDPSize) // Allocate buffer with maximum UDP size
        n, addr, err := _udpServer.ReadFrom(buf)
        if err != nil {
            log.Printf("Error reading UDP packet: %v", err) // Logging instead of panicking
            continue // Skip to next iteration instead of stopping the entire application
        }

        udpSocketAddrArr = append(udpSocketAddrArr, addr)
        messageTxt := string(buf[:n]) // Only consider received bytes

        checkUDPPayload(messageTxt, _udpServer, addr)
    }
}

I changed the hard-coded buffer length of 2048 bytes to 65507 bytes, which is the maximum size for a UDP packet in an IPv4 network. That ensures you will not miss any part of the incoming packet. Additionally, I sliced the buffer to buf[:n] to only include the bytes actually received.

I also replaced panic(err) with log.Printf() for logging the error. That way, the application will not crash if an error occurs. I have added a continue statement to skip to the next iteration in case of an error.


In your Android code, you are sending data to 255.255.255.255. That is a limited broadcast address, which will broadcast the packet to all hosts in the local network. However, this may not always work depending on your network configuration.

// Using 255.255.255.255 may not always work. 
// You might want to use the exact server's IP address to debug.
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255");

I would add some debugging information in the function receiveFromServerUDP:

private fun receiveFromServerUDP() {
    val receiveData = ByteArray(1024);
    val receivePacket = new DatagramPacket(receiveData, receiveData.length);

    // Log a message before trying to receive to confirm that the code reaches this point
    Log.i(TAG, "Waiting to receive UDP packet");
    connectedToServerSocket.receive(receivePacket);
    
    // Log received data
    socketResp = new String(receivePacket.getData(), 0, receivePacket.getLength());
    Log.i(TAG, "Received UDP packet: " + socketResp);
}

Check whether your local firewall settings might be blocking incoming or outgoing UDP packets. Since you mentioned your home network has a strict NAT, this could be one of the causes of your issue.

You mentioned your ISP does not provide a fixed IP and you suspect your modem's configuration might affect UDP communication. Check whether your modem or router has a setting that could be affecting UDP traffic. Also, UPnP settings are usually not relevant for local network communication, so having it enabled or disabled should not make a difference in your specific case (a code you are trying to debug, which involves direct UDP communication between a Golang server and an Android client).

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Looks interesting, will try this as soon as I can – rminaj Sep 01 '23 at 08:11
  • Now that I have more time processing these text, your answer seems to be as simple as it gets. You basically didn't answer anything. You just repackaged what I already mentioned and just stuff it with more fluff to make it look educated info. And you even made up a problem that I didn't said was a problem like that byte size. Is this the best answer that someone with 1M reputation can give? – rminaj Sep 02 '23 at 05:21
  • @rminaj That answer is more about debugging suggestions, to help you get some clues as to why your application does not receive those UDP messages. Hopefully, your bounty will attract a more targeted answer. – VonC Sep 02 '23 at 10:00