GMS 2.3+ UDP hole punching, socket properties ?

Hello,

I'm able to set a local peer to peer connection and a communication with a server.

My goal now is to make a peer to peer network.
Here is the protocol I apply.

I have :
- a server (socat) listening on UDP port_server.
- two identical gamemaker clients

The client :
- creates a UDP socket on port_A
- send an empty packet to the server

The server:
- waits to have the two clients' ip_A/port_A and ip_B/port_B
- sends back A to B and B to A

The clients send packets to each other, until they reach each other.

My question is :
1 - Is the above protocol correct ?
2 - Is it fine if both GM clients are on the same computer with the same IP (but different ports)
3 - should I use 1 socket to talk with the server then destroy it and create a new socket to communicate with the peer ?

Thanks for reading :)
 

FrostyCat

Member
My question is :
1 - Is the above protocol correct ?
2 - Is it fine if both GM clients are on the same computer with the same IP (but different ports)
3 - should I use 1 socket to talk with the server then destroy it and create a new socket to communicate with the peer ?
  1. In a nutshell, yes.
  2. Yes.
  3. No, keep using the original one. Otherwise, the new socket will get a new port number that nobody else knows.
 
Well then it's not working.

I've tried to replace :
network_send_udp() with network_send_udp_raw().
network_create_socket() with network_create_socket_ext() and assign manually a port.

I am currently sending a buffer containing the text : "keep_alive"
Is there a trick in the type of data to send or in the frequency of sending ?

Thanks a lot !
 

mako

Member
Are you directly talking to your PC IP address inside the network, or are you trying to go through your network router?
 
Are you directly talking to your PC IP address inside the network, or are you trying to go through your network router?
It works fine with the local IP (192.168.*.*)
I am trying to go through the router.
My server (socat) provides the external IP and port (I doubt about the port though ... but don't know how to investigate further).

Any recomandations ?
 
should I have sockets created with network_create_server(), or network_create_socket() ? (since in local network_create_socket() works already)
 

mako

Member
From what I understand of your question, you're trying to access a server on your local network, which is easy enough with the local IP, but you'll have issues if you're trying to access it from the outside. Someone may correct me, but it sounds like your issue is with the router not allowing the client's initial contact through. I believe you'll need to port-forward your router. Basically, your router doesn't allow any outside source to initiate the link; it only allows inside sources to initiate the link and then allows outside packets with the matching credentials to come in.

If you want an outside source to initiate the link, you'll need to forward the port on your router to the right local IP. This is a hole in your security, which is why it's not enabled by default. I've read this is a manageable risk if you know what you are doing, but it can be a hazard. Effectively, your client connects to a specific port on your router, via the externally visible IP, then all information sent via that connection is forwarded to the correct port for the IP on the computer running the server. If you don't have the port-forward set up, the router just discards all incoming packets without the right credentials.

If I've made a mistake here, someone please enlighten me; I'm still learning all this myself! :)
 
From what I understand of your question, you're trying to access a server on your local network, which is easy enough with the local IP, but you'll have issues if you're trying to access it from the outside. Someone may correct me, but it sounds like your issue is with the router not allowing the client's initial contact through. I believe you'll need to port-forward your router. Basically, your router doesn't allow any outside source to initiate the link; it only allows inside sources to initiate the link and then allows outside packets with the matching credentials to come in.

If you want an outside source to initiate the link, you'll need to forward the port on your router to the right local IP. This is a hole in your security, which is why it's not enabled by default. I've read this is a manageable risk if you know what you are doing, but it can be a hazard. Effectively, your client connects to a specific port on your router, via the externally visible IP, then all information sent via that connection is forwarded to the correct port for the IP on the computer running the server. If you don't have the port-forward set up, the router just discards all incoming packets without the right credentials.

If I've made a mistake here, someone please enlighten me; I'm still learning all this myself! :)
No the server is not on my local network. (it's a digital ocean server actually).

Then client A and client B are two identical game maker projects on the same computer (mine) on my local network.
I can confirm that the server gets the connection and send IP and ports to the clients (A to B / B to A). However from that I can't exchange data (AB / BA) using the external IP.
 
I've tried to put the 2 clients on different machines but makes not difference...

Here is the code of the client I am using.

//create event
GML:
//create the server
randomize();
do {
    prt_self = irandom_range(3000,4000);
    SKT_server = network_create_server_raw(network_socket_udp,prt_self,5);
}
until (SKT_server >= 0)
show_debug_message("Server created: " + string(SKT_server) + " / port: " + string(prt_self));

//mode for transmissions
url = "SERVER_URL";
prt_target = -1;

//protocols
mode = "send_to_server";
B_send = buffer_create(1,buffer_grow,1);
got = "";

//flood
n_flood = 0;
T_flood = irandom_range(10,60);

B_flood = buffer_create(1,buffer_fixed,1);
buffer_seek(B_flood,buffer_seek_start,0);
buffer_write(B_flood,buffer_u8,ord("#"));
//step event
Code:
switch (mode) {
    case "send_to_server" :
        if (keyboard_check_pressed(vk_enter)) {
            var to_send
            
            to_send = string_hash_to_newline("hey#");
            
            buffer_resize(B_send,1);
            buffer_seek(B_send,buffer_seek_start,0);
            buffer_write(B_send,buffer_string,to_send);
            
            network_send_udp_raw(SKT_server,url,40952,B_send,buffer_get_size(B_send));
        }
        break;
    
    case "flood" :
        network_send_udp_raw(SKT_server,ip_target,prt_target,B_flood,1);
        break;
    
    case "send_to_peer" :
        if (keyboard_check_pressed(vk_enter)) {
            
            buffer_resize(B_send,1);
            buffer_seek(B_send,buffer_seek_start,0);
            buffer_write(B_send,buffer_string,keyboard_string + "#");
            
            network_send_udp_raw(SKT_server,ip_target,prt_target,B_send,buffer_get_size(B_send));
        }
        break;
}
//Async - Networking event
Code:
//read the string
var bloc,txt

txt = "";
buffer_seek(async_load[? "buffer"],buffer_seek_start,0);
bloc = buffer_read(async_load[? "buffer"],buffer_u8);

while (bloc != ord("#")) {
    txt += chr(bloc);
    bloc = buffer_read(async_load[? "buffer"],buffer_u8);
}
got = txt;


switch (mode) {
    case "send_to_server" :
        var col = string_pos(":",txt);
        
        ip_target = string_copy(txt,1,col-1);
        prt_target = real(string_delete(txt,1,col));
        
        show_debug_message(ip_target + " / " + string(prt_target));
        
        mode = "flood";
        break;
        
    case "flood" :
        mode = "send_to_peer";
        break;
}
Any other idea ?
 

mako

Member
Okay, that makes more sense.
I haven't been working with UDP or raw connections, but your code is very readable. I'm not super familiar with socat, but shouldn't the send_to_peer networking packet be addressed to the same address as the send_to_server? You're trying to bounce each packet off the DO server, which means it should be sent there and then the server forwards it, right?
 
Okay, that makes more sense.
I haven't been working with UDP or raw connections, but your code is very readable. I'm not super familiar with socat, but shouldn't the send_to_peer networking packet be addressed to the same address as the send_to_server? You're trying to bounce each packet off the DO server, which means it should be sent there and then the server forwards it, right?
That's not peer to peer that's a relay server. The idea of holepunching is to use the server to tell each client the ip/port of another client so they can communicate directly.

I've tried to replace :
network_send_udp() with network_send_udp_raw().
network_create_socket() with network_create_socket_ext() and assign manually a port.
In my experience, any combination of the above should work.

I am currently sending a buffer containing the text : "keep_alive"
Is there a trick in the type of data to send or in the frequency of sending ?
Sometimes it takes more than 1 packet from each player but if your ip/port combo is correct and both players are sending to each other, it should work. You can send any data, it doesn't matter (the smaller the better so it doesn't get lost). Frequency doesn't matter either, I've been able to holepunch a 5 minute old connection before, as soon as B sends the first packet to A (while A has already sent to B), anything A sent to B will suddenly come through (that didn't get lost).

Firstly, why are you using string search functions to find data in your buffer?
GML:
ip_target = string_copy(txt,1,col-1);
prt_target = real(string_delete(txt,1,col));
This is an accident waiting to happen, it would be much better to use a format and then read from the format.

This code here:
GML:
while (bloc != ord("#")) {
    txt += chr(bloc);
    bloc = buffer_read(async_load[? "buffer"],buffer_u8);
}
got = txt;
does what game maker's built in buffer_string does, except the terminating character is chr(0) not #, and it's applied automatically.

You say this is the client but why is it using
GML:
SKT_server = network_create_server_raw(network_socket_udp,prt_self,5);
This should be network_create_socket_ext

However from that I can't exchange data (AB / BA) using the external IP.
Well if your two computers are on the same pc/network they're not going to be able to connect to each other using the external ip. You have to use the internal ip for that (LAN).
 
Last edited:
Well if your two computers are on the same pc/network they're not going to be able to connect to each other using the external ip. You have to use the internal ip for that (LAN).
That is what I suspected but frosty cat above said the opposite in his answer. I will soon ask a friend to help me testing that so I can be fixed.

Firstly, why are you using string search functions to find data in your buffer?
Because when it plain text using buffer_read_string is fine. however on the "real" version en encrypt datas and do the transfert in base64. When it comes from the server the output is more certain that way. I had some irregularities using buffer size control systems.

Thanks for your very complete answer ! I am gonna do more tests soon.
 
Well you don't need to use udp holepunching if both players are on the same network, you can just connect straight away (you can find each other + your own local IP with a broadcast)
 
All right ! Here is the answer that blocked me !

The NAT blocs LAN connections through public IP.
I've tested the code above with somebody on an other NAT and it worked immediately.

So I am gonna have 2 case :
1- The 2 players are behind the same NAT -> The server will see two identical IPs, so the connection has to be made in LAN (192.168.*.*) with a broadcast method...
2- The players are behind different NAT -> The server will see different IPs, hole punch.

@FrostyCat Answer number 2 is wrong and it made me loose so much time ! Please edit your reply and appology !

Thanks everybody else :)
 

FrostyCat

Member
All right ! Here is the answer that blocked me !

The NAT blocs LAN connections through public IP.
I've tested the code above with somebody on an other NAT and it worked immediately.

So I am gonna have 2 case :
1- The 2 players are behind the same NAT -> The server will see two identical IPs, so the connection has to be made in LAN (192.168.*.*) with a broadcast method...
2- The players are behind different NAT -> The server will see different IPs, hole punch.

@FrostyCat Answer number 2 is wrong and it made me loose so much time ! Please edit your reply and appology !
My answer is correct if you kept track both the IP address AND the port. If the two clients are behind the same LAN, the holepunch server will see 2 identical IP addresses, but each time with a different port. If your system conflates two users just because the source IP address is the same, it isn't done right.

So no, I'm not apologizing.
 
My answer is correct if you kept track both the IP address AND the port. If the two clients are behind the same LAN, the holepunch server will see 2 identical IP addresses, but each time with a different port. If your system conflates two users just because the source IP address is the same, it isn't done right.

So no, I'm not apologizing.
In the case that A and B are behind the same NAT then hole punch doesn't apply and that was my question 2 in which you answered that it applies and work.
It is wrong !
If two clients are behind the same NAT hole punching doesn't work and doesn't apply, you have to connect through LAN.

End of story :)
 

Vusur

Member
But.. this is a device problem? You asked, if it's fine that two clients are on the same computer, so behind the same NAT device. The answer IS yes.
That it didn't work in your case has nothing to do with that. If the NAT can't do hairpinning, then it's a device limitation at your end. How should we know this? This doesn't make the answer wrong. Because NO would also be wrong with the information we had. Hairpinning is just a special case.
We can nitpick, that it's actually a maybe if you want, so you both were wrong?

Many NAT devices act naive, assume everything goes out to the interent and if they see, that it should go back, start dropping it.

Maybe give more information next time. And if you didn't know it beforehand, don't blame others for an answer, that didn't work for your special case and demand an apology? ;)

And here something constructive from my side: How NAT traversal works
Nice and long read, but worth it.
 
Last edited:
But.. this is a device problem? You asked, if it's fine that two clients are on the same computer, so behind the same NAT device. The answer IS yes.
That it didn't work in your case has nothing to do with that. If the NAT can't do hairpinning, then it's a device limitation at your end. How should we know this? This doesn't make the answer wrong. Because NO would also be wrong with the information we had. Hairpinning is just a special case.
We can nitpick, that it's actually a maybe if you want, so you both were wrong?

Many NAT devices act naive, assume everything goes out to the interent and if they see, that it should go back, start dropping it.

Maybe give more information next time. And if you didn't know it beforehand, don't blame others for an answer, that didn't work for your special case and demand an apology? ;)

And here something constructive from my side: How NAT traversal works
Nice and long read, but worth it.
If you just ask "Is it fine to have 2 computers behind the same NAT" without any context well ok, the answer is obviously "yes". That is what routers are for ....

Now what's the topic ? "UDP hole punching".
Regarding to the result of all conversation answering "yes" is far too incomplete to be right.

Let say that I didn't have this hairpining problem on MY router. Imagine I sell the game and some people have the hairpining problem what should I say ? This is your fault because your router is well configured to protect you ?
Of course not ! The problem is understanding how does the NAT works and how to have a 100% successful peer to peer connection.

However in the case your answer is "yes", you will get a random success chance at your p2p connection depending on customers router configuration (on which the game developper as no informations and no control for safety reasons).

In the case your answer is "no" then you pass over the hairpin problem (so you don't understand it) but the success rate appear to be certain whatever the client's router configuration.

A correct answer would have been : "Yes, if your router accept hairpin connections."
A "yes" like that Is extremly miss leading and made me think that my problem was from the code or the server. However my problem was on the understanding of NAT and the ignorance of "hairpin" rejected on SOME routers.

As a conclusion, I've been lucky that my router blocks hairpin so I learnt a lot about NAT and solved my problem too :)



I put again the correct answer here :

case 1 :
client A router A and client B router B
The connection has to be done through hole punching.

case 2 :
client A and client B behind the same router.
broadcast to find each other and connect through LAN. (hole punching is not reliable cause some routers reject LAN connection via public IP -> hairpin).
 
Top