Skype-Style Firewall Busting with Ruby and UDP
Skype and Google Talk are pretty clever in the way that they still work even if all of its users are behind firewalls (or NAT systems) that block incoming connections. The way they enable two-way connections is by using a 'firewall busting' technique. Simply, a central server does nothing but share IP addresses (and port numbers) and clients can then 'punch' holes through their firewalls and trick their firewalls and routers to route incoming packets back to them if they have certain source host and port numbers.
I was playing with the technique and have put together an example client that you can run on two separate hosts that have no open incoming ports but which demonstrates two way connection to an arbitrary port (6311 in this case) with UDP. Run this code on separate hosts and provide the hostname of the other host as a command line argument. This way, both programs will punch a hole through their own firewall and connect to each other, before randomly swapping messages back and forth.
require 'socket' remote_host = ARGV.first # Punches hole in firewall punch = UDPSocket.new punch.bind('', 6311) punch.send('', 0, remote_host, 6311) punch.close # Bind for receiving udp_in = UDPSocket.new udp_in.bind('0.0.0.0', 6311) puts "Binding to local port 6311" loop do # Receive data or time out after 5 seconds if IO.select([udp_in], nil, nil, rand(4)) data = udp_in.recvfrom(1024) remote_port = data[1][1] remote_addr = data[1][3] puts "Response from #{remote_addr}:#{remote_port} is #{data[0]}" else puts "Sending a little something.." udp_in.send(Time.now.to_s, 0, remote_host, 6311) end end
If you have a cleaner way of doing this, post a comment. This is just a quick prototype, but it works fine on my test boxes.
February 26, 2007 at 2:12 am
Wow, that code is awesome! I can't wait to try it.
February 28, 2007 at 12:20 am
I just ran this on Dreamhost and a local machine and.. it works!
This technique could, therefore, be used to send data to your own port even on shared hosts, as long as you know the IP of who's connecting so you can punch the hole.
This could be done with the normally available HTTP technique..
March 1, 2007 at 1:42 am
Does this work with TCP? Can I send a "punch" UDP packet to the remote host and then try recieving TCP connections?
March 1, 2007 at 1:59 am
Alan: Afraid not.. at least, not in its current state. TCP punching is far less elegant than UDP punching and support for it is rather patchy. This paper explains it all.
March 2, 2007 at 4:21 am
But surely Skype uses TCP? I'll take a look at that paper. Nice post, regardless.
March 2, 2007 at 5:25 am
I won't speak on their behalf, but I think those sort of programs offer fallbacks.. so if one doesn't work, it'll try another.
UDP is often used for streaming audio though since it doesn't matter if you lose a few packets here and there.