Skip to content

Instantly share code, notes, and snippets.

@koenbollen
Created July 5, 2010 19:10
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save koenbollen/464613 to your computer and use it in GitHub Desktop.
Save koenbollen/464613 to your computer and use it in GitHub Desktop.
Proof of Concept: UDP Hole Punching
#!/usr/bin/env python
#
# Proof of Concept: UDP Hole Punching
# Two client connect to a server and get redirected to each other.
#
# This is the client.
#
# Koen Bollen <meneer koenbollen nl>
# 2010 GPL
#
import sys
import socket
from select import select
import struct
def bytes2addr( bytes ):
"""Convert a hash to an address pair."""
if len(bytes) != 6:
raise ValueError, "invalid bytes"
host = socket.inet_ntoa( bytes[:4] )
port, = struct.unpack( "H", bytes[-2:] )
return host, port
def main():
try:
master = (sys.argv[1], int(sys.argv[2]))
pool = sys.argv[3].strip()
except (IndexError, ValueError):
print >>sys.stderr, "usage: %s <host> <port> <pool>" % sys.argv[0]
sys.exit(65)
sockfd = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sockfd.sendto( pool, master )
data, addr = sockfd.recvfrom( len(pool)+3 )
if data != "ok "+pool:
print >>sys.stderr, "unable to request!"
sys.exit(1)
sockfd.sendto( "ok", master )
print >>sys.stderr, "request sent, waiting for parkner in pool '%s'..." % pool
data, addr = sockfd.recvfrom( 6 )
target = bytes2addr(data)
print >>sys.stderr, "connected to %s:%d" % target
while True:
rfds,_,_ = select( [0, sockfd], [], [] )
if 0 in rfds:
data = sys.stdin.readline()
if not data:
break
sockfd.sendto( data, target )
elif sockfd in rfds:
data, addr = sockfd.recvfrom( 1024 )
sys.stdout.write( data )
sockfd.close()
if __name__ == "__main__":
main()
# vim: expandtab shiftwidth=4 softtabstop=4 textwidth=79:
#!/usr/bin/env python
#
# Proof of Concept: UDP Hole Punching
# Two client connect to a server and get redirected to each other.
#
# This is the rendezvous server.
#
# Koen Bollen <meneer koenbollen nl>
# 2010 GPL
#
import socket
import struct
import sys
def addr2bytes( addr ):
"""Convert an address pair to a hash."""
host, port = addr
try:
host = socket.gethostbyname( host )
except (socket.gaierror, socket.error):
raise ValueError, "invalid host"
try:
port = int(port)
except ValueError:
raise ValueError, "invalid port"
bytes = socket.inet_aton( host )
bytes += struct.pack( "H", port )
return bytes
def main():
port = 4653
try:
port = int(sys.argv[1])
except (IndexError, ValueError):
pass
sockfd = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sockfd.bind( ("", port) )
print "listening on *:%d (udp)" % port
poolqueue = {}
while True:
data, addr = sockfd.recvfrom(32)
print "connection from %s:%d" % addr
pool = data.strip()
sockfd.sendto( "ok "+pool, addr )
data, addr = sockfd.recvfrom(2)
if data != "ok":
continue
print "request received for pool:", pool
try:
a, b = poolqueue[pool], addr
sockfd.sendto( addr2bytes(a), b )
sockfd.sendto( addr2bytes(b), a )
print "linked", pool
del poolqueue[pool]
except KeyError:
poolqueue[pool] = addr
if __name__ == "__main__":
main()
# vim: expandtab shiftwidth=4 softtabstop=4 textwidth=79:
@shanet
Copy link

shanet commented Jul 9, 2013

This is great! You saved me so much time having to write this myself. Thanks! 👑 💩

@jinglehp
Copy link

jinglehp commented Nov 5, 2013

Thank's for your share. Although your code is quite clear, it seems to work only on the local environment. When I tried to run punchd.py on a server which has a public ip, and run punch.py in private network behind NATs, it does not work. I have also tried to write my own udp-hole-punching multi-people chatting program in python. But it does not work either. I hope to know whether there is a need to config my NAT routers to make UDP punching work.

@laike9m
Copy link

laike9m commented Mar 6, 2014

print >>, what's that?

@tmr232
Copy link

tmr232 commented Mar 7, 2014

It's python 2.x syntax for redirection of a print info a file.
Official Documentation, see last paragraph.

@laike9m
Copy link

laike9m commented Mar 19, 2014

@tmr232 Got it, thank you.

Copy link

ghost commented Jul 12, 2014

are you trying to make tcp hole punching?

@Eluvatar
Copy link

Eluvatar commented Oct 7, 2014

This doesn't work on windows, due only to the use of select on fd 0 (which isn't a thing on windows).

Will try to suggest a patch to this very useful example.

@cjohnweb
Copy link

I've tried a few udp hole punch python scripts. None of them work out on the net through any kind of nat'd firewall. The thing is, in order for the hole punch to work you need to make an outbound connection THROUGH the nat. That is what I don't quite understand. When udp packets go out the nat should remember who it's from so that a response can come back through. If you don't send data in or out for a period of time your nat will clean up the states table and the incoming connection ability would be lost untill you make another outbound connection. You get around this by sending an "I'm alive" packet out. ...but where are you sending it if no one is connected yet? Any where? But still, despite this, I still can't get udp traffic in through a nat. You shouldn't need to set any special rules for this to work (just like you don't need to set special rules to get a response from Google.com in your browser.so who has any insight into making this work through a nat? I don't think it's the code per say but rather a lack of understanding of how the nat actually works. Any input on this?

@AlonsoMackenlly
Copy link

This problem is relevant, I also tried to punch a hole through UDP and TCP using an external STUN server, and nothing happened either. If any of those present here managed to do this, leave a comment, I will be very grateful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment