A Golang library meant for establishing hole punch connections.
Based on the Peer-to-Peer Communication Across Network Address Translators.
In the diagram you can see also client2 and client3 which are in the same network therefore the connection is done by one of the clients listening and the other one dialing.
In the following section we will shortly and briefly discuss the basic idea behind hole punching. For more details see the paper linked at the beginning of the README.md.
For the hole punching to work, both clients needs to know the following:
- Target client public IP.
- Target client public port.
There are multiple ways to exchange this info between clients. The most known one is by using a proxy server accessible publicly by both clients where they register and ask for the counter peer information
In simple words the connection is established as follows:
- Client1 dials the client4's
publicIP:publicPort
. - Client2 dials the client1's
publicIP:publicPort
. - The NAT will see the other client's request incoming and considers it as a response to the initially performed request therefore will allow it.
- Connection established.
As the paper at the top is also saying and also from my own tests, this technique is not guaranteed to work across all NATs therefore you should retry first in order to be sure that this is working well.
One way to test if this technique will work in a specific network topology is to use
netcat
.run on machine1:
nc -p 1234 machine2-public-ip 1233
run on machine1:nc -p 1233 machine1-public-ip 1234
To get the public IP of the machines you can usecurl ifconfig.me
or any of the multiple alternatives online.
In order to be able to communicate, the library is creating 2 net.Listener to get 2 free ports from the system. One is used for private connectivity and the other one is used to allocate the public port that later will be used to Dial the peer in order to initiate the hole punching mechanism.
Also, by default, the library is using https://api.ipify.org
to get its public IP that it is needed for the hole punching mechanism.
You can override this behaviour by passing a new function for public IP discovery using WithPublicIPProvider
during calling NewConn
.
In order to read the data that the connection is receiving you should register at least one handler by using RegisterDataHandler
.
If no handler is registered, the data will just be discarded.
To stop the connection, two mechanisms are available:
Close()
method exposed by the connection.- Pass a cancellable context when the constructor is called.
- Call
NewConn
. During this phase, useWith
method for specific configurations of the connection. - Use
RegisterDataHandler()
for adding methods that will be called with the data read from the connection. - Use
LocalAddress()
andPublicAddress()
in order to retrieve local and public addresses on which the connection can be established. - Establish connection
- Call
AttemptConnection()
with the address(es) of the peer. - Call
AttemptConnection
from the other peer with the addresses from the step 2
- Call
- Listen on
<-conn.EstablishedEvents()
in order to know when the connection was established. - To know when/if the connection was cancelled, listen on
<-conn.Errors()
that will signal connectivity issues. When an error is received on this channel the user of the library should start establishing a new connection. - Use
Write()
to write data to the connection. - Use
Close()
or cancel the context that was given to the constructor during step #1.
For some examples you can check the dedicated directory.
For the moment the library does not support secure connections.