Reverse SSH Tunnel Launcher
Here is a script which helps with starting a reverse SSH tunnel. It gets the instructions from a web server, in the form of a JSON file, which determines at what time to open the tunnel(s). The JSON file also determines when to check for a JSON file next time. It operates on a daily schedule and uses the HH:MM format.
The idea is to leave this script running on a box behind a firewall. It is hardcoded to only keep the tunnel open for 5 minutes before closing it again. So to get a connection that lasts longer, a new tunnel probably has to be made manually after connecting.
The Python code:
#!/usr/bin/python
import urllib2
import json
import subprocess
import signal
import datetime
import time
import logging
import sys
config_url = "http://192.168.0.1/config.json"
ssh_host = "192.168.0.1"
ssh_port = 22
local_port = 1337
tunnel_open_time = 300 # In seconds.
tunnel = None
def alarm_handler(signum, frame):
global tunnel
logger.info("Terminating current tunnel.")
tunnel.kill();
tunnel = None
def minutes_until(minute_ts):
if minute_ts == None:
return (24 * 60)
now = datetime.datetime.now().timetuple()
now_ts = (now.tm_hour * 60) + now.tm_min
if now_ts > minute_ts:
# Already passed, will be next day.
return minute_ts - now_ts + (24 * 60)
else:
return minute_ts - now_ts
def update_config():
try:
config = json.load(urllib2.urlopen(config_url))
# Pick closest time to update next time:
closest_config_ts = None
for uc in config["ConfigUpdate"]:
config_ts = (int(uc.split(":")[0]) * 60) + int(uc.split(":")[1])
if minutes_until(config_ts) < minutes_until(closest_config_ts):
closest_config_ts = config_ts
tunnel_ts = list()
for to in config["TunnelOpen"]:
tunnel_ts.append((int(to.split(":")[0]) * 60) + int(to.split(":")[1]))
logger.debug("Config timestamp: %d" % (closest_config_ts))
logger.debug("Tunnel timestamps: %s" % (str(tunnel_ts)))
return closest_config_ts, tunnel_ts
except Exception, e:
logger.error("Exception during config update: %s" % (e))
return 0, []
if __name__ == "__main__":
logger = logging.getLogger('reverse_tunnel')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
# Get initial configuration:
config_ts, tunnel_ts = update_config()
if len(tunnel_ts) == 0:
sys.exit(1) # Exit right away if initial configuration fails.
signal.signal(signal.SIGALRM, alarm_handler)
while True:
now = datetime.datetime.now().timetuple()
now_ts = (now.tm_hour * 60) + now.tm_min
# Is it time for a config update?
if now_ts == config_ts:
logger.info("Timed config update.")
config_ts, tunnel_ts = update_config()
if config_ts == now_ts:
config_ts = config_ts - 2 # Prevent immediate update.
# Is it time to open a new tunnel?
for ts in tunnel_ts:
if now_ts == ts:
if tunnel == None:
logger.info("Opening new tunnel.")
tunnel = subprocess.Popen(["ssh", "-R", str(local_port) + ":localhost:22", "-N", "-p", str(ssh_port), ssh_host])
signal.alarm(tunnel_open_time)
else:
logger.warning("Tunnel already running.")
time.sleep(10)
Example JSON file:
{
"ConfigUpdate" : ["17:00"],
"TunnelOpen" : ["18:00","19:00","20:00"]
}