===== The problem ===== I often connect to Internet from a connection which blocks, by default, outgoing connections //except// for some given ports. For instance, port 80 (http) is open, as are 443 (https), 995 (pop3s) and a few others... but port 22 (ssh) is closed. So if I want to be able to connect via ssh to a server of mine, I must configure the server to listen (also) on a different port... non blocked. Configuring the server to listen on multiple ports is [[http://www.latenightpc.com/blog/archives/2006/10/11/running-an-ssh-server-on-multiple-ports|trivial]], this page is devoted to the problem of understanding //which// port can be used. ===== My solution ===== My rudimentary solution is composed of 4 simple Python scripts: - a script for opening ports, which must be ran on the server - a script to receive "orders" via http about which ports to open - a script acting as a bridge between **1.** and **2.** - a script for probing ports, which must be ran from the client Notice that you //don't need// scripts **2.** and **3.** if you have a way to connect to the remote host ((OK, and then apparently you shouldn't be reading this at all, but maybe you have a temporaneous other connection, or some friend somewhere else willing to help, or a costly connection...)). {{:informatica:open_ports.tar.bz2|Click here to download all 4 scripts in this page at once}} - they are released as GPL v.3+. ==== The server scripts ==== === The ports opener === The script just runs a dummy service on each port from **N** (provided as argument) to **N+257** (I don't check more than 257 ports at the same time, to not incur in the restrictions imposed on the maximum number of opened files). As suggested in the comment, you should replace "socket.gethostname()" with the hostname the server is accessible to, i.e. s.bind( ("example.com", port) ) because socket.gethostname() probably won't work. #! /usr/bin/python # opener.py import socket, time, sys try: start = int( sys.argv[1] ) except (IndexError, ValueError): print "usage: %s first_port" % sys.argv[0] sys.exit(1) sockets = [] for port in range(start, start + 257): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # You should probably edit the following line! s.bind( (socket.gethostname(), port) ) s.listen(1) except Exception as exc: print port, exc sockets.append( s ) time.sleep(3600) === The http control === Put this in some place where Python files will be executed by the webserver (i.e. /usr/lib/cgi-bin/ , but depends on the configuration). Notice Apache //doesn't// execute Python files out of the box: mod_python must be installed and maybe configured. #! /usr/bin/python # http_control.py import cgi import cgitb cgitb.enable() print "Content-type: text/html\n" form_c = cgi.FormContentDict() if 'p' in form_c: f = open( 'port', 'w') f.write( form_c['p'][0] ) print form_c f.close() === The bridge === The above script creates a file, in the same folder, called "port". This one looks at that script and runs "ports_opener.py" accordingly. So you must replace '**/path/to/port/file**' with the full path of the "port" file, and save it in the same folder as "opener.py". #! /usr/bin/python # bridge.py import time, subprocess port = None proc = None while True: newf = open('/path/to/port/file') try: newport = int( newf.read() ) except ValueError: newport = None newf.close() if newport != port: if proc: proc.terminate() if newport != None: print "starting", ["./opener.py", str( newport )] proc = subprocess.Popen(["./opener.py", str( newport )]) else: print "stopped" port = newport time.sleep( 5 ) ==== The client script ==== This is a very simple port scanner: it tries to connect to a given range of ports on the given host (again, from **N**, the argument, to **N+257**). You must replace "**put.here.the.hostname**" with the true address of your server (the same you presumably replaced "socket.gethostname()" with above). #! /usr/bin/python # prober.py import socket, time, sys try: start = int( sys.argv[1] ) except (IndexError, ValueError): print "usage: %s first_port" % sys.argv[0] sys.exit(1) if len(sys.argv) > 2: host = sys.argv[2] else: # Edit the following line! host = "put.here.the.hostname" sockets = [] for port in range(start, start + 257): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) res = s.connect_ex( (host, port) ) if res == 0: print "PORT ", port, " OPEN" else: print port, res ==== So, to recap ==== === What to do === If you have an alternate access to the server, just run on it "opener.py", like in ./opener.py 100 and on the client run "prober.py" with the same argument ./prober.py 100 to test ports 100 - 357 (and try again with some other argument to test other ports ranges). If you //don't// have an alternate access to the server, from another connection, some time before * save http_control.py to a place where Python scripts are ran by the webserver (and remember to make it executable) * save opener.py and bridge.py to the same folder (and remember to make them executable) * save prober.py on the client * edit the specified lines in prober.py, bridge.py and opener.py * run bridge.py (possibly from inside [[http://savannah.gnu.org/projects/screen|screen]]), or just as "nohup bridge.py" Then, once you are behind the firewalled connection, assuming that http_control.py can be accessed at http://www.example.com/http_control.py, just * visit the page http://www.example.com/http_control.py?p=100 * on the client, run "prober.py" with the same argument: ./prober.py 100 to test ports 100 - 357 (and try again with some other argument to test other ports ranges). === Understanding output === prober.py will print "OPEN" for every port it finds open. You will know those ports are not filtered. Still, notice that some of them will already be in use by existing services of your server: you must find some free one in order to run ssh at it. Just compare the output of prober.py with what you obtain when running ./prober.py against you host with opener.py not in action (if you use http_control.py, you can disable it by visiting "http://www.example.com/http_control.py?p=None"). Ports which result open in the first case but not in the second are what you're searching for.