Aoik

Windows remote desktop connects to hosts behind NAT

In an intranet I've got a set of Windows hosts which I need remote desktop into to do maintenance job. These hosts are behind a NAT router and do not have a "public" intranet IP. So directly connecting to their port 3389 is impossible.

Setting up port forwarding on the NAT router is an option but is not scalable. There may be yet another NAT router behind the NAT router.

My first attempt is to run ssh remote port forwarding from these hosts. This maps the 3389 port of these hosts onto a remote server's 3389 port (in listening mode) to which I can connect.

This works but the only drawback is someone has to be on the host to run the ssh remote port forwarding command.

To save the manual running process, I wrote a Python program and made it an Windows service with the help of winsw.

The program sends an HTTP request every 5 seconds to an Nginx server (that has "public" intranet IP) to get the ID of the host that I want to connect to, 12345 for example. The program next compares the wanted host ID with the ID of the current host (specified via command line argument). If the two IDs are equal, the program launches a sub-process to run the ssh remote port forwarding command, if there isn't any yet. If the two IDs are not equal, the program terminates the sub-process, if there is any.

By doing so, I can use Nginx config to notify the host that I want to connect to to start doing ssh remote port forwarding.

The program code:

# coding: utf-8
import subprocess
import sys
import time
from traceback import format_exc
from urllib.request import urlopen
from _thread import start_new_thread


DEBUG = True

MY_DEVICE_ID = None

# `PF` means port forwarding.
PF_DEVICE_ID = None

PF_DEVICE_ID_URL = None

SSH_PROC = None


def debug_print(*args):
  if not DEBUG:
    return

  print(*args)


def main():
  global MY_DEVICE_ID
  global PF_DEVICE_ID
  global PF_DEVICE_ID_URL
  global SSH_PROC

  MY_DEVICE_ID = sys.argv[1]

  MY_DEVICE_ID = MY_DEVICE_ID.encode('ascii')

  PF_DEVICE_ID_URL = sys.argv[2]

  ssh_exe_file_path = sys.argv[3]

  ssh_username = sys.argv[4]

  ssh_server_host = sys.argv[5]

  ssh_server_port = sys.argv[6]

  ssh_pri_file_path = sys.argv[7]

  remote_port = sys.argv[8]

  local_host = sys.argv[9]

  local_port = sys.argv[10]

  debug_print('3F6D7: my_device_id: {0}'.format(repr(MY_DEVICE_ID)))

  cmd_and_args = [
    ssh_exe_file_path,
    '{0}@{1}:{2}'.format(ssh_username, ssh_server_host, ssh_server_port),
    '-o',
    'ConnectTimeout=3',
    '-o',
    'StrictHostKeyChecking=no',
    '-i',
    ssh_pri_file_path,
    '-N',
    '-R',
    '{0}:{1}:{2}'.format(remote_port, local_host, local_port),
    '-v',
  ]

  debug_print('5S8T7: cmd_and_args: {0}'.format(repr(cmd_and_args)))

  start_new_thread(pf_device_id_get__thread_func, ())

  while True:
    try:
      time.sleep(1)

      if MY_DEVICE_ID == PF_DEVICE_ID:
        if SSH_PROC is None:
          debug_print('4F9W6: ssh_proc_pre_sleep')

          time.sleep(10)

          debug_print('1S5C9: ssh_proc_start')

          SSH_PROC = subprocess.Popen(
            cmd_and_args,
          )

          debug_print('3Z6P5: ssh_proc_wait')

          SSH_PROC.wait()

          debug_print('4I7T1: ssh_proc_wait_end')

          SSH_PROC = None

    except Exception as exc:
      debug_print('2T9G8: exc:\n{0}', format_exc())

      time.sleep(10)


def pf_device_id_get__thread_func():
  global MY_DEVICE_ID
  global PF_DEVICE_ID
  global SSH_PROC

  while True:
    try:
      time.sleep(5)

      with urlopen(PF_DEVICE_ID_URL) as rep_file:
        PF_DEVICE_ID = rep_file.read()

        debug_print('3Z1S4: pf_device_id: {0}'.format(repr(PF_DEVICE_ID)))

        if MY_DEVICE_ID != PF_DEVICE_ID:
          if SSH_PROC is not None:
            debug_print('2Q7M4: ssh_proc_terminate')

            SSH_PROC.terminate()

            SSH_PROC = None
    except Exception as exc:
      debug_print('5L2N6: exc\n{0}', format_exc(), exc)

      time.sleep(10)


if __name__ == '__main__':
  sys.exit(main())

The ssh.exe I use is from Git for Windows.

Prev Post:
Next Post:

Comments:

Reply to: