TCP/UDP hole punching or NAT traversal works as following:
A and B are behind NAT and want to communicate, while you have public relay server, S.
1. A connects to S, B connects to S
2. S send A ip and port to B, and send B ip and port to A
3. One of A or B try to connect to the other by the address S shared
Note1: For hole punching you don’t need uPnP IGD or port forwarding
Note2: UDP hole punching works more reliably than TCP hole punching as it’s connectionless by nature and don’t need SYN packet
Note3: Hole punching isn’t a reliable technique as router or other firewall may see B ip address is different from S ip address and block the inbound connection
Note4: STUN is a standard protocol that implement UDP hole punching although you can create a custom protocol as well following the above steps