Broadcast server discovery - java

I'm creating a game in Java that simulates the classic 5 cards Poker with 2 to 4 players.
Most of the data will be processed by a server, but since I can't use an online server, my idea is to allow a user to host a game by creating a local one.
Now, I don't want to force the use of IPs to connect to a game, so I created a "discovery" interface within the user can see all avaible games. This is done using the UDP protocol and a broadcast research on a common group:
(code is simplified to show only the actions that are executed, may not work as it is showed here)
Client
MulticastSocket socket = new MulticastSocket(6020);
InetAddress group = InetAddress.getByName("226.0.0.1");
socket.joinGroup(group);
DatagramPacket packet = new DatagramPacket(new byte[] {(byte) 0xF0}, 1, group, 6020);
socket.send(packet);
while(true) {
buf = new byte[1];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
if(packet.getData()[0] == 15) {
Socket client = new Socket(packet.getAddress(), 6020);
}
}
Server
MulticastSocket socket = new MulticastSocket(6020);
InetAddress group = InetAddress.getByName("226.0.0.1");
socket.joinGroup(group);
// new thread listening on port 6020 TCP
ServerSocket server = new ServerSocket(6020);
new Thread(new Runnable() {
public void run() {
while(true) {
// new thread communicating with client and back listening on port 6020
new ServerThread(server.accept());
}
}
}).start();
// listening on port 6020 UDP
byte[] buf;
DatagramPacket packet;
while(true) {
buf = new byte[1];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
if(packet.getData()[0] == -16) {
DatagramPacket packet = new DatagramPacket(new byte[] {(byte) 0x0F}, 1, packet.getSocketAddress());
socket.send(packet);
}
}
The client sends an UDP broadcast packet on the port 6020. When a server receive this packet if it's composed by a byte 0xF0, he sends back a byte 0x0F to the client. Every client is also listening on the port 6020 and when receive a packet composed by a byte 0x0F it starts a new connection TCP to the server on the port 6020.
My question: is there a better way to achieve this "discovery" system?
I know this is going to work only in local networks, is it possible to extend the discovery "outside" using a local server?

Unless you want to set up some sort of known broker that can connect players with servers (or give them a listing of servers), you may be out of luck. As you discovered, multicast and broadcast is generally not sent to the WAN by most switches (and definitely cannot traverse the Internet).
If your issue with setting up a known server/broker is that you have a home connection and so a dynamic ip, I would recommend looking into dynamic DNS. There are a number of providers out there that will allow you to set up a sub-domain on their system that is automatically changed to point to your IP as your IP changes.

Related

UDP messages from Java are received only on first run

I'm using Java's DatagramSocket to send UDP messages from Java to a specific port on my localhost. I listen to this port with netcat: nc -ul 9122.
On the first run of my Java code (after starting nc) - the message is received and displayed on my shell. On each other run - messages are not received. Only restarting nc will do.
This is my Java code:
public static void main(String[] args) throws IOException, InterruptedException {
byte[] buf = "Hi There\n".getBytes();
InetAddress address = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 9122);
DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.connect(InetAddress.getLocalHost(), 9122);
if(datagramSocket.isConnected()) {
datagramSocket.send(packet);
Thread.sleep(500);
datagramSocket.send(packet);
Thread.sleep(500);
datagramSocket.send(packet);
}
}
What do I miss?
Thanks
This seems to be a feature of ncat. After receiving one UDP packet, it only accepts packets from the same origin host and port. It is similar to a connection: an instance of ncat only handles packets from a single client.
When you start the Java program, it will select an arbitrary local port, and when you restart it you will get a different port. You can set fixed local port by passing it to the DatagramSocket constructor:
DatagramSocket datagramSocket = new DatagramSocket(12345);

Why UDP hole punching does not work for public IP address?

There are two Groovy sub programs below sending messages to each other via plain UDP sockets. They does receive the messages successfully when they are sent to 127.0.0.1. But the messages aren't received when sending them to the public IP address (the machine is behind NAT).
Why the hole is not punched? And how to fix that?
I tried querying a public STUN server via a Java library earlier but it responded with the same public IP address to me, so I use wtfismyip.com here.
class GroovyTest {
static String PUBLIC_IP = new URL('https://wtfismyip.com/text').text.trim()
//static String PUBLIC_IP = '127.0.0.1' // works fine
static void main(String[] args) {
runInstance(11111, 22222)
runInstance(22222, 11111)
}
static void runInstance(int thisPort, int anotherPort) {
def socket = new DatagramSocket(thisPort)
Thread.start {
// message listener
byte[] buf = new byte[1024]
while (true) {
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
InetAddress remoteAddr = packet.getAddress();
int remotePort = packet.getPort();
String sentence = new String(packet.getData(), 0, packet.length);
println("server-$thisPort: received [$sentence] from ${remoteAddr.hostAddress}:${remotePort}")
}
}
Thread.start {
// message sender
while (true) {
println("client-$thisPort: sending to ${PUBLIC_IP}:${anotherPort}...")
byte[] buf = ("Hello " + System.currentTimeMillis()).bytes
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getByName(PUBLIC_IP), anotherPort)
socket.send(packet)
Thread.sleep(2000)
}
}
}
}
Your problem stems from the fact that the IP address returned by wtfismyip is the IP address of the router on your network, which is not assigned to your computer. When you try and send a datagram to your router's public IP you'll probably get and ICMP Destination Unreachable error message from your router. If you need this behavior, your router may have some port forwarding capabilities that can forward inbound UDP traffic to your local IP address.
I have been successfully replying to UDP packets behind a NAT router by simply taking the address and port details from the UDP packet I'm responding to...
DatagramSocket socket = new DatagramSocket(port);
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
socket.receive(receivePacket);
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length,
receivePacket.getAddress(), receivePacket.getPort());
socket.send(sendPacket);
The code is more robust in that it doesn't matter where the packet came from, or any address translation which occurred along the way. It will always reply to the right place.
I'v also noticed you are using two different port numbers. "thisPort" and "anotherPort". As far as I know, the hole punching will only work if you reply on the same port number. This makes sense for security reasons.
The marine robot on top of my head, pictured in my avatar, uses this UDP hole punching technique.

Java Multicast Socket not sending out data

It's my first post so it may be not well stylished but I tried...
So... I have two machines, both running Java. I want them to run something like this.
Client: sends multicast to listening servers.
Server(s): the server captures the multicast and sends a unicast back with name of local machine that server runs on.
Client: receives the unicast with the server adress(es) and makes a list with their hostnames.
But the client doesn't even send the multicast (I was watching wireshark capturing packets)
It only sends something when I put 230.0.0.1 as multicast address, but then, the server doesn't receive the packet.
EDIT: When I send a unicast packet to the server it responds fine.
Here is my code:
try
{
//The client runs on LeJOS EV3 so I used their classes a bit
LCDOutputStream lcd = new LCDOutputStream();
PrintStream p = new PrintStream(lcd);
while(true)
{
if(Button.waitForAnyPress() == Button.ID_ESCAPE)
{
break;
}
byte[] buf = this.writeString("get_ip");
DatagramSocket sender = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getByName("230.0.0.1"), 5555);
sender.send(packet);
sender.close();
p.println("Sent Multicast");
}
p.close();
lcd.close();
}
catch(Exception e)
{
console.printException(e);
}
Here is the server code:
MulticastSocket s = new MulticastSocket(5555);
s.joinGroup(InetAddress.getByName("225.1.1.1"));
while(true)
{
try
{
/*
* 225.1.100.1
*
DataSender.Impl.reply("225.1.100.1", 5555, InetAddress.getLocalHost().getHostName(), "get_ip");*/
byte[] buf = new byte[256];
DatagramPacket p = new DatagramPacket(buf, buf.length);
s.receive(p);
System.out.println("DEBUG: received request");
}
catch(IOException e)
{
e.printStackTrace();
break;
}
}
s.close();
The comments made by ecle in response to the following post helped me resolve a similar issue: Java Multicast sample program is unable to deliver packets within LAN (across hosts). In my case, adding setInterface(<server address>); worked. For example:
MulticastSocket s = new MulticastSocket(5555);
s.setInterface(<server address>);
s.joinGroup(InetAddress.getByName("225.1.1.1"));

UDP Hole Punching Java Example

I want to do UDP Hole Punching with two clients with the help of a server with a static IP. The server waits for the two clients on port 7070 and 7071. After that it sends the IP address and port to each other. This part is working fine. But I'm not able to establish a communication between the two clients. I tried the code in different Wifi networks and in 3G mobile network. The client program throws the IO-Exception "No route to host".
The client code is used for both clients. Once executed with port 7070 and once with 7071.
Do you think I've implemented the UDP hole punching concept correctly? Any ideas to make it work?
Here's the server code first, followed by the client code.
Thank you for help.
Code of server:
public class UDPHolePunchingServer {
public static void main(String args[]) throws Exception {
// Waiting for Connection of Client1 on Port 7070
// ////////////////////////////////////////////////
// open serverSocket on Port 7070
DatagramSocket serverSocket1 = new DatagramSocket(7070);
System.out.println("Waiting for Client 1 on Port "
+ serverSocket1.getLocalPort());
// receive Data
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
serverSocket1.receive(receivePacket);
// Get IP-Address and Port of Client1
InetAddress IPAddress1 = receivePacket.getAddress();
int port1 = receivePacket.getPort();
String msgInfoOfClient1 = IPAddress1 + "-" + port1 + "-";
System.out.println("Client1: " + msgInfoOfClient1);
// Waiting for Connection of Client2 on Port 7071
// ////////////////////////////////////////////////
// open serverSocket on Port 7071
DatagramSocket serverSocket2 = new DatagramSocket(7071);
System.out.println("Waiting for Client 2 on Port "
+ serverSocket2.getLocalPort());
// receive Data
receivePacket = new DatagramPacket(new byte[1024], 1024);
serverSocket2.receive(receivePacket);
// GetIP-Address and Port of Client1
InetAddress IPAddress2 = receivePacket.getAddress();
int port2 = receivePacket.getPort();
String msgInfoOfClient2 = IPAddress2 + "-" + port2 + "-";
System.out.println("Client2:" + msgInfoOfClient2);
// Send the Information to the other Client
// /////////////////////////////////////////////////
// Send Information of Client2 to Client1
serverSocket1.send(new DatagramPacket(msgInfoOfClient2.getBytes(),
msgInfoOfClient2.getBytes().length, IPAddress1, port1));
// Send Infos of Client1 to Client2
serverSocket2.send(new DatagramPacket(msgInfoOfClient1.getBytes(),
msgInfoOfClient1.getBytes().length, IPAddress2, port2));
//close Sockets
serverSocket1.close();
serverSocket2.close();
}
Code of client
public class UDPHolePunchingClient {
public static void main(String[] args) throws Exception {
// prepare Socket
DatagramSocket clientSocket = new DatagramSocket();
// prepare Data
byte[] sendData = "Hello".getBytes();
// send Data to Server with fix IP (X.X.X.X)
// Client1 uses port 7070, Client2 uses port 7071
DatagramPacket sendPacket = new DatagramPacket(sendData,
sendData.length, InetAddress.getByName("X.X.X.X"), 7070);
clientSocket.send(sendPacket);
// receive Data ==> Format:"<IP of other Client>-<Port of other Client>"
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
clientSocket.receive(receivePacket);
// Convert Response to IP and Port
String response = new String(receivePacket.getData());
String[] splitResponse = response.split("-");
InetAddress ip = InetAddress.getByName(splitResponse[0].substring(1));
int port = Integer.parseInt(splitResponse[1]);
// output converted Data for check
System.out.println("IP: " + ip + " PORT: " + port);
// close socket and open new socket with SAME localport
int localPort = clientSocket.getLocalPort();
clientSocket.close();
clientSocket = new DatagramSocket(localPort);
// set Timeout for receiving Data
clientSocket.setSoTimeout(1000);
// send 5000 Messages for testing
for (int i = 0; i < 5000; i++) {
// send Message to other client
sendData = ("Datapacket(" + i + ")").getBytes();
sendPacket = new DatagramPacket(sendData, sendData.length, ip, port);
clientSocket.send(sendPacket);
// receive Message from other client
try {
receivePacket.setData(new byte[1024]);
clientSocket.receive(receivePacket);
System.out.println("REC: "
+ new String(receivePacket.getData()));
} catch (Exception e) {
System.out.println("SERVER TIMED OUT");
}
}
// close connection
clientSocket.close();
}
UPDATE
The code is generally working. I've tried it in two different home networks now and it's working. But it isn't working in my 3G or university network. In 3G, I verified that the NAT is mapping the two ports (the client port and by the router assigned port) together again, even after closing and opening the clientSocket. Has anyone an idea why it isn't working then?
UDP hole punching can't be achieved with all types of NAT. There is no universal or reliable way defined for all types of NAT. It is even very difficult for symmetric NAT.
Depending on the NAT behaviour, the port mapping could be different for different devices sending the UDP packets.
Like, If A sends a UDP packet to B, it may get some port like 50000. But if A sends a UDP packet to C, then it may get a different mapping like 50002. So, in your case sending a packet to server may give a client some port but sending a packet to other client may give some other port.
You shall read more about NAT behaviour here:
https://www.rfc-editor.org/rfc/rfc4787
https://www.rfc-editor.org/rfc/rfc5128
UDP hole punching not going through on 3G
For symmetric NAT (3G network connecting to a different mobile network), you need to do Multi-UDP hole punching.
See:
https://drive.google.com/file/d/0B1IimJ20gG0SY2NvaE4wRVVMbG8/view?usp=sharing
http://tools.ietf.org/id/draft-takeda-symmetric-nat-traversal-00.txt
https://www.goto.info.waseda.ac.jp/~wei/file/wei-apan-v10.pdf
http://journals.sfu.ca/apan/index.php/apan/article/view/75/pdf_31
Either that or relay all the data through a TURN server.
You rightly use a rendezvous server to inform each node of the others IP / port based on the UDP connection. However using the public IP and port which is the combination which will is obtained by the connection as you have, means that in scenarios where both hosts exist on the same private network hairpin translation is required by the NAT which is sometimes not supported.
To remedy this you can send the IP and port your node believes itself to have in the message to the server (private ip / port) and include this in the information each node receives on the other. Then attempt a connection on both the public combination (the one you are using) and the one I just mentioned and just use the first one which is successfully established.

No reply to UDP broadcast, only unicast

In my Android app I'm sending a UDP broadcast to address 255.255.255.255, port 6400. I use the PC program Packet Sender to act as a UDP server that automatically sends a reply.
When I'm listening for this reply in the Android app, it never receives the reply. I can only receive the reply if I don't send the packet to 255.255.255.255, but to the specific IP address of the PC.
private String udpDestinationAddress = "255.255.255.255";
private int udpDestinationPort = 6400;
private int udpTimeoutMs = 5000;
private String udpData = "test";
DatagramSocket socket;
socket = new DatagramSocket(12345);
socket.setBroadcast(true);
socket.setSoTimeout(udpTimeoutMs);
socket.connect(InetAddress.getByName(udpDestinationAddress), udpDestinationPort);
// send part
byte[] data = udpData.getBytes();
int length = data.length;
DatagramPacket packet = new DatagramPacket(data, length);
socket.send(packet);
// receive part
byte[] buffer = new byte[1000];
// Initialize the packet
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// Receive the packet
socket.receive(packet); // it timeouts here (if timeout=0, then it hangs here forever)
// Get the host IP
String hostIP = packet.getAddress().toString().replace("/", "");
In the PC program, I have set it to reply automatically (with another random string) to a packet on port 6400. This works quite well with other apps I've tested (various UDP test Android apps). However, my app cannot seem to get the reply.
I can only get a reply in my app when I've set udpDestinationAddress to the specific IP of the PC. I've also tried "192.168.5.255" (broadcast on my local subnet) instead of "255.255.255.255" - still doesn't work.
I found the problem.
Instead of binding the destination IP and port to the socket, I needed to bind it to the packet instead.
So, instead of:
socket.connect(InetAddress.getByName(udpDestinationAddress), udpDestinationPort);
...
DatagramPacket packet = new DatagramPacket(data, length);
... use this instead:
InetAddress addr = InetAddress.getByName("255.255.255.255");
DatagramPacket packet = new DatagramPacket(udpData.getBytes(), udpData.length(), addr, udpDestinationPort);

Categories

Resources