9

I've tried to write a basic TCP hole puncher for a firewall in Python 3 using the principles outlined in this article. I'm having trouble getting anything to connect, though. Here is the code:

#!/usr/bin/python3

import sys
import socket
import _thread as thread

def client():
    c = socket.socket()

    c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    c.bind((socket.gethostbyname(socket.gethostname()), int(sys.argv[3])))
    while(c.connect_ex((sys.argv[1], int(sys.argv[2])))):
        pass
    print("connected!")
    thread.interrupt_main()

def server():
    c = socket.socket()

    c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    c.bind((socket.gethostbyname(socket.gethostname()), int(sys.argv[3])))
    c.listen(5)
    c.accept()
    print("connected!")
    thread.interrupt_main()

def main():
    thread.start_new_thread(client, ())
    thread.start_new_thread(server, ())

    while True:
        pass

if __name__ == '__main__':
    main()

I decided to try the puncher on my local machine, so that I could capture all the traffic sent by both instances. I first set up a loopback firewall:

iptables -A INPUT -i lo -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j DROP

Then I launched two copies of the script:

left$ ./tcphole.py localhost 20012 20011

and

right$ ./tcphole.py localhost 20011 20012

I can see according to Wireshark that the SYN packets are being transmitted both ways:

Wireshark capture

But nothing ever prints "connected!" What am I doing wrong?

George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • Wait, wouldn't your firewall rules (specifically, rule #2) drop incoming `SYN` packets on `lo`? There are no "ESTABLISHED" or "RELATED" connections in your example, right? – NPE Apr 23 '14 at 16:16
  • Yes, that's exactly the behavior I am trying to work around. (The firewall is the adversary in this case.) SYN packets identified as RELATED should make it back through though. – George Hilliard Apr 23 '14 at 16:18
  • And thanks for one of the most complete and clearly articulated questions I've seen for the while. (+1) – NPE Apr 23 '14 at 16:18
  • Haha, thanks! I always try to thoroughly hash things out before asking so I don't look like an idiot. Sometimes I end up looking like an idiot anyway, but you can only do so much. – George Hilliard Apr 23 '14 at 16:20
  • I could be missing something obvious, but it's not entirely clear to me why you are expecting those SYN packets to be identified as RELATED. – NPE Apr 23 '14 at 16:28
  • Because they're coming from the same machine/port that I sent them to. The first SYN will get dropped by the remote firewall, but the local firewall doesn't know that. – George Hilliard Apr 23 '14 at 16:30
  • OK, I should read the paper (don't have time to do that right now). – NPE Apr 23 '14 at 16:31
  • Is this really hole punching or just plain TCP? Don't you need outbound connections on both ends for hole punching? You're connecting on one and accepting on the other... – user541686 Jul 14 '17 at 11:31
  • @Mehrdad Yes, bear in mind that I launch two copies of the script – George Hilliard Jul 14 '17 at 15:35
  • @thirtythreeforty: Oh, I didn't realize, okay thanks! – user541686 Jul 14 '17 at 18:12

1 Answers1

6

The answer turned out to be quite simple: packets aren't considered RELATED if they aren't coming to the same IP address!

Changing the bind lines to

c.bind('', int(sys.argv[3])))

(the '' binds to the loopback address) fixes the problem entirely.

George Hilliard
  • 15,402
  • 9
  • 58
  • 96