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);
Related
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.
Each time I am running the client the server tells me a different port number. I searched on that and found that when I set the port to zero it looks for an available port, but I changed it to the number I want public static final int MYPORT = 5555; and still getting a new port number each time from the server.
This is the print method:
System.out.printf(" using port %d\n", receivePacket.getPort());
DatagramSocket socket = new DatagramSocket(null);
SocketAddress localBindPoint = new InetSocketAddress(MYPORT); socket.bind(localBindPoint);
SocketAddress remoteBindPoint = new InetSocketAddress(args[0], Integer.valueOf(args[1]));
I think you missed the point, this piece of code listen on port 5555:
The istruction packet.getPort() in the following code returns the port number on the remote host to which this datagram is being sent or from which the datagram was received.
int MYPORT = 5555;
DatagramSocket dsocket = new DatagramSocket(MYPORT);
byte[] buffer = new byte[2048];
// Create a packet to receive data into the buffer
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// Wait to receive a datagram
dsocket.receive(packet);
// Convert the contents to a string, and display them
String msg = new String(buffer, 0, packet.getLength());
System.out.println(packet.getAddress().getHostName() + ": "
+ msg);
// Reset the length of the packet before reusing it.
packet.setLength(buffer.length);
System.out.printf(" using port %d\n", packet.getPort());
}
I have double checked locally:
sudo lsof -iUDP -n -P | grep 5555
java 1606 freedev 5u IPv6 0x9ed7290ce134656f 0t0 UDP *:5555
I have UDP server and client, my code works inside home with internal IPs such as 192.168.0.X. If I put my code inside my online server with external IP (X.X.X.X) server receives and send but Android can only send and doesn't receive. What is the problem?
Android send code :
byte[] sendData = new byte[1024];
try {
DatagramSocket serverSocket = new DatagramSocket(1500);
InetAddress IP = InetAddress.getByName("My server IP");
String invia = "I am Android";
sendData = invia.getBytes();
DatagramPacket send = new DatagramPacket(sendData, sendData.length, IP, 1500);
serverSocket.send(send);
}
Android receive code :
byte[] receiveData = new byte[1024];
try {
DatagramSocket serverSocket = new DatagramSocket(1600);
while (true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
String sentence = new String( receivePacket.getData());
Log.i("FROM SERVER:", "" + sentence);
}
} catch (Exception e) {
}
Server code :
DatagramSocket serverSocket = new DatagramSocket(1500);
byte[] receiveData = new byte[1024];
byte[] sendData = new byte[1024];
while(true)
{
/** receive **/
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
String sentence = new String( receivePacket.getData());
System.out.println("RECEIVED: " + sentence);
/** send **/
InetAddress IPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
sentence = "I am PC";
sendData = sentence.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 1600);
serverSocket.send(sendPacket);
}
Why does it not work externally, yet it does locally ?
You have to understand NATPAT.
Even running a webserver on your home pc will not work.
When you browse with your browser, you are taking initiative and send a GET to a server using its IP# and port. Your local IP# behind the NATPAT router is not unique across the internet, it is unique only behind your router. So your local ip# of your pc cannot be used to talk to the server. So your router is going to build an internal table based on your local ip# and local port and a port he allocates himself, he then forwards your request to the webserver you want to talk to but using your public ip# and that allocated port. when the server replies he is going to reply to your public ip# and that allocated port. Your NATPAT router sees the reply and know ah it is a reply on that allocated port, it looks in his internal table and forwards to the local ip# and local port that he remembered in his lookup table.
If you host a server behind a NAT PAT router, you cannot get any incoming messages to your webserver because the table is empty.
And that is the problem with your android listening on port 1600. He never sent anything on that port and ip# so your router does not know for whom it is.
It works locally because then all your local ip# are used that your DHCP enabled router has assigned to your devices. And there is never any translation needed to the public IP# that your ISP has provided you with.
Read this :
http://searchnetworking.techtarget.com/definition/Port-Address-Translation-PAT
and this :
Following is an illustration. Let's say the ISP Public IP address is 3.12.62.154. The objective is to run public-accessible web server that run on standard protocol TCP port 80 (the web port). The local network Private Subnet is 10.0.0.0/24 where the server IP address is 10.0.0.10. For the public access, the 10.0.0.10 will be PAT-ed to the 3.12.62.154 on TCP port 80.
When users on the Internet try to connect to the public web server, those users connect to the 3.12.62.154 IP address and not to the 10.0.0.10 IP address. The reason is that on the Internet, the web server is seen as the 3.12.62.154 IP address. The 10.0.0.10 IP address is only seen within local LAN.
from here : http://www.dslreports.com/faq/13449
If you don't understand any of this, stop trying, start reading instead.
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.
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.