Java UDP - Client doesn't receive DatagramPacket without forwarding ports

Hello there!
I am making a 2D game engine in pure Java in which includes a built-in network/multiplayer feature. The network feature is self-hosted by a player, where other players/clients can connect to the host. The network feature is UDP based because of the importance of net speed. Another note is that i don’t want any external libraries involved in this. I want to do it purely with what the standard Java library has to offer.

I don’t want the clients/end-users having to manually forward any ports in order to make this work. The host should only need to do that at most.

Things are working out perfectly when testing locally between two running applications on the same computers. But the issue begins when trying it out with other people from around the world.

This is a rundown of how things are going down when trying it out:

[quote]> HOST opens a DatagramSocket on port 1234, waiting for CLIENT to send a message (this is used to accept clients and create “Connections” with new threads for each client)

CLIENT opens a DatagramSocket on port X (automatically assigned to a free one)
CLIENT creates two threads using the same DatagramSocket. One for listening and one for sending
CLIENT sends a message to HOST
HOST receives message (where it also gets the IP/port of CLIENT)
HOST creates two threads using the same DatagramSocket. One for listening and one for sending ()
HOST sends message to CLIENT

CLIENT never receives message
[/quote]
I have confirmed that the IP/port for sending/receiving between the client and host are correct. There is no one sending a message to the wrong IP/port.

I can send as many messages i like without any change. The host has port forwarded port 1234 for incoming packets. The client hasn’t port forwarded anything in the example up above.

Just to make sure this one time i asked one of the clients/testers to forward a port, which made it work just fine. So the issue is definitely related to the ports.

I tried doing TCP instead of UDP, using Socket instead of DatagramSocket which made it work just fine as long as the host had the port forwarded to begin with. The only backside was that it became practically unplayable due to slow network speed, so i am desperately looking for a solution to this UDP issue.

I also tried to use both TCP and UDP, using the same TCP Socket port for the UDP DatagramSocket port. Where i first established a connection using a TCP Socket just to later create a UDP DatagramSocket for sending/receiving on the same port. This did not work either.

Connection class (inside Network.java):

class Connection
{
  String NAME;
  InetAddress IP;
  int PORT;    
  DatagramSocket DSOCKET;
  boolean ENABLED = true;

  public Connection(InetAddress ip, int port,String name)
  {
    IP = ip;
    PORT = port;
    NAME = name;
    try
    {
      DSOCKET = new DatagramSocket();
      receive();
      send();
    }catch(Exception e){}
  }
  public void send()
  {
    Thread sendthread = new Thread()
    {
      public void run()
      {
        try
        {
          byte[] sendData;
          while(ENABLED)
          {
              try
              {
                  String message = "Hello there!";
                  sendData = new byte[message.getBytes().length];
                  sendData = message.getBytes();
                  DatagramPacket sendPacket = new DatagramPacket(sendData,sendData.length,IP,PORT);
                  DSOCKET.send(sendPacket);
              }catch(Exception e){System.out.println("NetSendError: " + e);}
          }
          DSOCKET.close();
        }catch(Exception e){}
      }  
    };
    sendthread.start();
  }
  public void receive()
  {
    Thread thread = new Thread()
    {
      public void run()
      {
        byte[] receiveData;
        try
        {
          DSOCKET.setSoTimeout(1000);
          while(ENABLED)
          {
            receiveData = new byte[4096];
            DatagramPacket receivePacket = new DatagramPacket(receiveData,receiveData.length);
            DSOCKET.receive(receivePacket);
            String message = new String(receivePacket.getData());
            System.out.println("CLIENT received message: " + message);
          }
        }catch(Exception e){}
      }
    };
    thread.start();
  }
}

Network.java:

import java.util.Vector;
import java.net.*;

public class Network
{
  boolean HOSTING = false;
  public static Vector<Connection> CONNECTIONS = new Vector<Connection>();
  
  public static void main(String[] args)
  {
    starthost();
    try
    {
      InetAddress ip = InetAddress.getByName("193.150.248.134");
      connect(ip,1234);
    }catch(Exception e){}
  }

  //called by host when starting a hosted session
  public static void starthost()
  {
    boolean HOSTING = true;
    Thread thread = new Thread()
    {
      public void run()
      {
        try
        {
          DatagramSocket socket = new DatagramSocket(1234);
          byte[] receiveData;
          try
          {
            while(HOSTING)
            {
              try
              {
                receiveData = new byte[4096];
                DatagramPacket receivePacket = new DatagramPacket(receiveData,receiveData.length);
                socket.receive(receivePacket);
                int ii = CONNECTIONS.size();
                boolean exists = false;
                for(int i=0;i<ii;i++)
                {
                  if(CONNECTIONS.get(i).IP.equals(receivePacket.getAddress()))
                  {
                    exists = true;
                    break;
                  }
                }
                if(!exists)
                {
                  //if this is the first time the client sends, create a connection for sending
                  CONNECTIONS.add(new Connection(receivePacket.getAddress(),1234,"HOST"));
                }
                else
                {
                  //if the client exists in the CONNECTION list, print out the received message
                  String message = new String(receivePacket.getData());
                  System.out.println("HOST received message: " + message);
                }
              }catch(Exception e){System.out.println("NetStartHostError: " + e);}
            }
            socket.close();
          }catch(Exception ee){}
        }catch(Exception e){}
      }
    };
    thread.start();
  }

  //called by clients when connecting to host
  public static void connect(InetAddress ip,int port)
  {
    CONNECTIONS.add(new Connection(ip,port,"CLIENT"));
  }
}

With all of this, my following questions are:

[quote]- Am i doing something wrong in the code up above?

  • Is there a way of working around this? (without any external libraries)
  • Should i be using the same DatagramSocket for sending and receiving? Does it make a difference?
  • Is there just no way around this? Does every client needs to port forward manually? If there isn’t, is there a “pure-java” way of port forwarding by code?
  • Why does it work with TCP and not UDP under the same circumstances (host has forwarded ports, client hasn’t)
  • When a client sends a DatagramPacket from a port, shouldn’t it be able to receive packets from the same port?
    [/quote]
    I’m kinda considering giving up on the whole UDP thing and going TCP-only since that actually works. Maybe there’s some methods of speeding it up significantly?
    After more than a week of searching and testing i’m finally creating a thread here. I’d really appreciate some help if there is any available.

Best regards!

The problem is that you are using a newly created DatagramSocket for each Connection, which gets bound to any ephemeral port on the host system which the client’s NAT does not yet know about.
You then expect the client to receive any message sent from that new Connection DatagramSocket where the client’s NAT only knows about your “accepting” DatagramSocket bound to 1234.
So the client’s NAT has only established a UDP port mapping between the client’s local port and your host’s port 1234, but NOT to your Connection’s DatagramSocket (which gets any free port).
So either use always the same DatagramSocket (that bound to 1234) for every client OR, whenever a client “connects” to your host, send the client the locally assigned port of your Connection’s new DatagramSocket in a message via the “accepting” DatagramSocket (the one bound to 1234). Then the client and the Connection’s DatagramSocket must send each other a first “hello” message (can also be empty payload) - the client using the new port that he received in the message from the host, and the host using the original client’s port.
This will ensure that both parties’ NATs record this port mapping and allow incoming traffic. But this will only work if the host (the one sending its port number in the message) is behind a port-preserving NAT. Otherwise, this approach fails.

EDIT: Also take note that the built-in firewall of Windows 7 (as opposed to the one in Windows XP!) also features a filter which is based on UDP port mappings and in effect has the same restrictions like a symmetric NAT. So even if you had port forwarding enabled in both sides’ NATs, a peer ‘A’ would only be able to receive a message from the other peer ‘B’ iff ‘A’ had previously sent a UDP message to ‘B’ using the exact same source and destination UDP ports!

Huge thanks for the detailed answer, i rewrote the code a bunch based on this.
I’m still getting the same issue that CLIENT can reach the HOST, but HOST can’t reach the CLIENT.

Just to be perfectly clear on what i’m currently doing, in case i misunderstood something important:

I’m running a single DatagramSocket for HOST on port 1234 (for both sending/receiving messages)
When connecting as CLIENT i create two threads. One for sending and one for receiving. These threads use the same single DatagramSocket which is bound to the same port as the host (1234)

So all in all, HOST gets the initial message from the client on port 1234. HOST then sends back a message to CLIENT on port 1234 using the same DatagramSocket the HOST received on.
The initial message comes from CLIENT on port 1234 to HOST on port 1234. HOST sends back using the same socket it received on towards the same socket CLIENT sent on (port 1234) but the CLIENT still never receives anything. Is there something crucial i’ve missed from your last reply?

Okay, then the client’s NAT is not port-preserving.
This means that even if the client uses the same local port 1234 to bind to, the outbound UDP message - when leaving the client’s NAT - gets another (random) source port assigned by the client’s NAT for the first time.
The NAT then establishes a mapping between the internal port of the client and the external port assigned by the NAT.
Therefore the HOST cannot simply send to port 1234 back to the client, because that is not the port which the client’s NAT expects.
Your host must therefore remember the client’s external port (assigned by the client’s NAT) which it can read from the received DatagramPacket via getPort() or simply getSocketAddress() (together with the IP).
It is this port that the host must send data back to the client.
Once that packet reaches the client’s NAT the NAT translates that port number back to 1234 and forwards that to the client.

[/quote]

  1. Yes, I will be very blunt but that code is very messy and ugly and incorrect in a lot of parts.
  2. Yes, if it’s done properly it works in the way that ONLY THE HOST computer must have the port forwarded.
  3. Yes use the same socket.
  4. Same as number 2. Except YOU CAN use UPnP to attempt to “auto portforward” for the HOST, so that no end user has to manually port forward.
  5. See number 2.
  6. Calling .getPort() on the client from the server side will get you the correct port you need to send to. If the server port is 1234 that DOES NOT MEAN the CLIENT can recieve packets on that port. KaiHH’s response explains the reasoning behind this.

I’m at work right now but when I get home, if I remember about this post and have time ill write you a working example of a client-server that will handle multiple connections (on the server side obviously).

Thanks for your answers!

It’s working fully fine after editing the code with KaiHH’s latest answer in mind. I never used .getPort() which seemed to be the cause of the issue.
I’m gonna keep on working on the code to improve some features/logic/performance, and i really think i got a good helping hand in starting out.

I only got a little side thought/question…

Right now the client side looks like this:

1 DatagramSocket
1 Thread for sending
1 Thread for receiving

And the host looks like this:

1 DatagramSocket
X Thread(s) for sending (depending on how many clients there are connected)
1 Thread for receiving

I get the feeling that the host won’t be able to receive messages from all clients at the same time, so my current plan is doing something like this for the host:

1 DatagramSocket (a)
1 Thread for receiving incoming/initial connections from socket (a)
When a client is accepted, create two threads for sending(below) and receiving on another port, then send back a message to the client (from socket (a)) with the “new” port to start sending to.
X Thread(s) for sending (depending on how many clients there are connected, on another port than socket (a))
X Thread(s) for receiving (on another port than socket (a))

Would i be wrong in thinking this way? Is it fine to have 1 single socket for receiving data from multiple clients at the same time?
If socket.receive(); hasn’t been called the incoming messages would just disappear right? Or is there some kind of automatic “queue” for incoming data that the socket can later .receive()?
I just want to be sure that i’m not committing some horrible sin.

Huge thanks, and best regards!

[quote]When a client is accepted, create two threads for sending(below) and receiving on another port, then send back a message to the client (from socket (a)) with the “new” port to start sending to
[/quote]
Well that’s what I outlined above. But that solution would most likely not work since the host can be expected to be behind a NAT which also creates a random source port for outgoing UDP messages from your newly created DatagramSocket (i.e. is not port-preserving). Therefore, the host simply does not know its own external port (because of port translation) and hence cannot send its own perceived local port to the client because that is not the effective host’s external port that the client needs to send to.
This is when people usually fall back to solutions which incorporates a third entity, which is kind of a mediating server which is not behind a NAT (or is at most behind a full cone NAT, i.e “static port forwarding”) . This server is used to see and report the external ports of host and client back to each other.
Read about STUN.
But this also has a limitation where it fails to work. Namely if one of the host or the client is behind a so called symmetric NAT.
Because in this case the third entity would see a different external port of the client (or host) than what the host (or client) see of each other, since the port mapping is not just per internal source address:port but also per destination address:port.
Read about that also.