Проброс портов или как попасть в сеть за NAT используя Node.JS

в 13:23, , рубрики: forwarding, node.js, port forwading, ssh, Tunnel

Привет! Хочу поделиться очередным способом проброса портов, теперь и на Node.JS!

Для чего это нужно? Представим, есть удалённый компьютер, к которому нужно подключиться, например, по ssh, rdp, http(s), proxy, vnc, и т.д. Но, увы, у него нет общедоступного IP по той или иной причине.
image

В этом примере предполагается, что у вашего устройства есть внешний IP.

Что в таком случае можно сделать? Подключиться из удалённого ПК к вашему, который слушает, например, порт 3000, пробросив определённый порт, например, 22. В результате зайдя по ssh на localhost:3000, мы зайдём на удалённый ПК.

Что же для этого нужно? На вашем и удалённом ПК:

Клонируем github.com/mgrybyk/node-tunnel и инсталим npm модули:

cd node-tunnel
npm install

Запускаем

На вашем ПК запускаем:

node server

в другом терминале:

node client

На удалённом ПК создаём файл .env, где мы укажем какой порт нужно пробросить:

N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=22

node agent

На этом настройка закончена, можем подключиться:

ssh -p 8000 localhost

Сессия ssh к удалённой машине установлена!

Для начала (и чтобы поиграться) server, client и agent можно запустить на одном устройстве, тогда подключившись к localhost:8000 по ssh вы зайдёте к себе же. Вместо localhost можно указать другой хост; вместо ssh можно использовать другой TCP порт, например, http(s)

Но, что же делать, если у вас нет внешнего IP? Нужно найти промежуточную точку, где он имеется, например, бесплатный контейнер на AWS.

image

Суть примерно та же, для примера возьмём теперь порт rdp и дадим имена агенту и клиенту.
Имена отдельного агента и клиентов должны совпадать.

На удалённом ПК отредактируем .env файл, в этот раз укажем ещё и хост Windows PC внутри вашей сети:

N_T_SERVER_HOST=хост ПК с внешним IP
N_T_AGENT_DATA_HOST=Windows PC внутри удалённой сети
N_T_AGENT_DATA_PORT=3389
N_T_AGENT_NAME=test-rdp

И запустим:

node agent

На ПК с внешним IP просто запустим node server предварительно склонив репозиторий и установив модули npm.

На вашем ПК создадим .env файл, так же указав порт, который клиент будет слушать:

N_T_SERVER_HOST=хост ПК с внешним IP
N_T_CLIENT_NAME=test-rdp
N_T_CLIENT_PORT=3388

Запустим node client. Здорово! Теперь мы можем подключиться по RDP на localhost:3388 открыв rdp сессию к ПК внутри сети агента.

Больше клиентов?

Также, можно рассказать как настроить (создать .env) клиент другу. Запустив у себя клиент, он также сможет заходить по rdp туда же.

.env

Для удобства, можно создавать много файлов типа .env.ssh, .env.rdp, .env.proxy и т.д., после чего запускать agent/client/server передав имя файла как аргумент, например:

node client .env.rdp

Шифрование

ВНИМАНИЕ! Шифрование трафика ещё не готово, пока не разобрался как это лучше сделать.
При шифровании данных их длина растёт, из-за чего сообщение часто делится на два. После чего, на другой стороне их нужно склеить, перед тем как пускать дальше. Выглядит слишком криво :(

Ух, начну самое сложное, попытаюсь в двух словах объяснить как это работает.
Использовал стандартный модуль Net, который работает по TCP.

Клиенты и агенты подключаются к серверу, который перенаправляет трафик с агента — клиенту и обратно. Это и есть основная магия.

У клиента, сервера и агента есть важные две части.

Первая — сокет, для установки соединения, чтобы дать понять кто есть кто, назовём его — сервисный сокет.

Вторая — сокет для данных. Именно тут и происходит создание pipe'ов:

agentSocket.pipe(clientSocket)
clientSocket.pipe(agentSocket)

Пример с ssh

image

  1. Подключаюсь к клиенту
  2. Клиент перенаправляет трафик на сервер
  3. Сервер перенаправляет трафик на агента
  4. Агент создаёт соединение на указанный host:port и перенаправляет туда трафик
    Ну и обратно. Ответ SSH сервера агенту, далее сервер, клиент, ssh клиент.

… немного по каждому отдельно

Server

  • Сервер ждёт на клиентов и агентов
  • Когда приходит агент — сервер создает отдельный сервер для него, через который будет в дальнейшем идти весь трафик
  • При подключении клиента с тем же именем что и агент — сервер отправляет клиенту порт сервера для агента
  • может быть много агентов, но имена должны быть уникальны
  • может быть много клиентов для каждого агента, связка много к одному

Зная, кто есть кто, сервер, созданный для агента, перенаправляет трафик от агента к клиенту и обратно. Без шифрования! Работает примерно так:

  1. Приходит клиент, сохраняем ссылку на сокет
  2. Сервер уведомляет агента, что есть клиент и пора открывать соединение
  3. Когда агент приходит — сервер пайпит сокет агента на первый доступный сокет клиента и обратно
  4. Сервер уведомляет клиента, что пайп создан и пора форвардить трафик
    В дальнейшем сервер не слушает событие «data» клиента и агента.

Agent

установив соединение с сервером агент ждёт команд от клиентов, как только приходит команда — агент устанавливает соединение на указанный host:port. После чего перенаправляет трафик с сервера на открытое соединение и обратно.

Client

клиент создаёт локальный сервер (порт N_T_CLIENT_PORT). При успешном соединении с удалённым сервером перенаправляет весь трафик с удалённого на локальный сервер и обратно.

Это всё!

image

Спасибо за прочтение.

Надеюсь, вам было хоть немного интересно, капельку понятно и, может быть, даже пригодиться это приложение, как пригодилось мне и моим коллегам.

Github

Интересные модули

Кому нужно работать с пайпами, рекомендую посмотреть through2.

При его помощи можно вклиниться посреди цепочки из пайпов для различных целей, например, логирование, обработка ошибок, в конце концов, замены всех ключевых слов на свои или даже единичек на нолики :)

П.С.

Статью пишу впервые и с русским плохо. Заранее прошу прощения.

П.П.С.
Писал приложение изначально для себя и потому что просто было интересно что-то такое сделать на Node. Сам использую для ssh, rdp, proxy, vnc и других целей :)

Автор: xlenz

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js