I’m currently working on creating a UDP hole punching program in python. So far, I’ve managed to successfully retrieve my public IP address and public port using a STUN server with pystun3. I’m also using an online JSON-based website as a rendezvous server. Everything up to this point is functioning well—both clients are able to upload their respective public IP and public port information to the server and retrieve the other client’s details.
However, the issue arises when I attempt to perform the actual UDP hole punch. On each client, I create a socket and bind it to the client’s private IP and public port (as discovered via the STUN server). I then create a thread dedicated to listening on the socket and waiting for incoming packets. Simultaneously, another thread continuously sends UDP packets to the other client’s public IP and public port, with a small delay between each send. The same socket is used for both sending and receiving packets, and it’s bound to the private IP and public port. This process occurs on both clients simultaneously.
Using Wireshark, I can see the packets being sent from each computer. However, they don’t appear to arrive—neither at the receiving client’s program nor at the network itself (as confirmed by Wireshark).
On Wireshark, I can verify that each packet is being sent to the correct public IP and public port of the other client. The source port matches the sending computer’s public port, as identified by the STUN server. I’ve also verified that the destination IP is the other client's actual public IP, using online tools, confirming that they’re correct. Based on my understanding of UDP hole punching, this setup should work.
I suspect that the NAT might be blocking the incoming packets for some reason. I’m aware that UDP hole punching doesn’t work with symmetric NATs, but pystun3 indicates that I have a full-cone NAT, and checkmynat.com suggests I have a port-restricted cone NAT. Therefore, it doesn’t seem like the NAT type is the issue. I’ve also tried disabling the firewalls on both clients, but the packets still don’t arrive.
This is the code:
import socket
import threading
import time
PRIVATE_IP = None #replace with private ip
PUBLIC_IP = None #replace with public ip
PUBLIC_PORT = None #replace with public port
TARGET_IP = None #replace with other client's public ip
TARGET_PORT = None #replace with other client's public port
SOCK = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
def start_udp_communication_new():
SOCK.bind((PRIVATE_IP, PUBLIC_PORT))
SOCK.sendto(b'punch',(TARGET_IP, TARGET_PORT)) #Sends to the other client
print("sent punch packet")
listener_thread = threading.Thread(target=listen, daemon=True)
listener_thread.start()
while True:
SOCK.sendto(b'hello',(TARGET_IP, TARGET_PORT)) #Sends packet to other client
print(f"Sent message to {TARGET_IP} at port {TARGET_PORT}")
time.sleep(0.1)
def listen(): #Listens continuously
while True:
data, addr = SOCK.recvfrom(1024)
print(f"Received message: {data} from {addr}")
def main():
start_udp_communication_new()
if __name__ == "__main__":
main()
So as not to overload, I did not include the functions that get the info from the STUN server, nor the rendezvous server ones, because I know they work. To run the code, you can get the private IP using tools like ipconfig. To get the port and public IP, you can use this function:
import stun
def get_stun_info():
nat_type, public_ip, public_port = stun.get_ip_info()
print(f"Nat type: {nat_type}, Public IP : {public_ip}, Public port: {public_port}")
Alternatively, for the public IP, you can use tools such as this.
I’ve experimented with several variations of this code, but they all produce the same outcome.
I don't understand why the packets are being sent, but don't punch a hole and start a UDP communication.
I’d greatly appreciate any guidance or suggestions on how to resolve this problem.
Thank you!
Consider to check if it helps to use application private IP address and port number for the STUN inquiry
stun.get_ip_info(source_ip=PRIVATE_IP, source_port="54320")
and to also use the private port number instead of PUBLIC_PORT
when binding
SOCK.bind((PRIVATE_IP, 54320))