Привет! Хочу поделиться очередным способом проброса портов, теперь и на 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.

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

P.S.
Статью пишу впервые и с русским плохо. Просьба не судить строго.

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

Комментарии (12)


  1. Wexter
    11.07.2017 20:00
    +2

    *картинка с троллейбусом.jpg*
    поздравляю, вы изобрели vpn/ssh tunnel, только зачем?


    1. xlenz
      11.07.2017 20:19

      В сети, которую мне нужно попасть, запрещён VPN, а также SSH port forwarding (не знаю каким образом). К тому же, чем больше выбор — тем лучше. Возможно, кому-то ещё поможет.

      Плюс, есть возможность менять данные (на стороне агента) перед отправкой дальше, например, зашифровав, сжав и т.д. (тестирую эту возможность). Уже сейчас использую для обхода блокировок. Искренне надеюсь, что ВПН, прокси т.д. не запретят, а если и запретят, то чем больше решений — тем лучше.


      1. ivlis
        11.07.2017 21:57

        Как он может быть запрещен, если это делается локально.


        1. xlenz
          12.07.2017 18:08

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


  1. alexevil
    12.07.2017 10:48
    -1

    Фу фу фу, туннели на javascript. Полагаю расход памяти/процессора на такое в десятки раз больше, чем чем при использовании нормальных инструментов.


    1. xlenz
      12.07.2017 13:58

      На глаз сравнил с ssh на винде, по процессору разницы не заметил (0), по памяти — есть. Каждый инстанс ноды — пирмерно 10 метров памяти (сервер, агент, клиент).
      Путти — примерно 3 метра памяти, bitvise ssh server метров 10.


      1. Igelko
        12.07.2017 16:30
        -1

        вот только putty — это один несчастный бинарник, а это приличного размера ворох файлов, которому подавай на машине nodejs, и возможно ещё компилятор.


        В общем я клоню к тому, что в бородатые года написал Спольски про .Net http://russian.joelonsoftware.com/Articles/PleaseSirMayIHaveaLinker.html


        1. xlenz
          12.07.2017 18:05

          Спасибо за ссылку. Я с вами полностью согласен. Можно было написать ни си, или использовать готовое решение, проверенное годами.

          Мне хотелось сделать что-то подобное, но на node.js. На серьёзное решение претендовать не может.
          Для простого проброса портов ссш и рдп в домашних условиях работает отлично на винде, маке и линухах. К тому же очень удобно, что не нужно каждый раз запускать всё заново при дисконнекте. Плюс можно поиграться с подменой данных, преобразовав их (зип, шифрование), заменив ключевое слово, или, например:

          for (let i = 0; i < chunk.length; i++) {
              chunk[i] = chunk[i] + shiftValue
          }
          


    1. ice2heart
      14.07.2017 09:05

      node.js достаточно быстрая штука.
      Ощущение что JS медленный возникает из-за того что в большинстве случаев он ворочает DOM.
      А так нода достаточно быстра, плюс всегда есть возможность ботлнек переписать на сях.
      А ещё можно собрать бинарник с архивом программы и минимальной ноды.


      1. xlenz
        14.07.2017 20:11

        Сейчас боттлнек: создание соединений и шифрование (которого ещё нет).
        Для ссш, рдп проблем нет, а вот хттп и т.д., где нужно много раз открывать/закрывать соединения — не хорошо.

        Пока проблемы скорее в реализации, нежели в ноде или джс.

        Идея с бинарником хороша, попробую как-то, спасибо


  1. ice2heart
    14.07.2017 09:02
    +1

    А почему не использовать STUN между компами?
    А агента использовать только для сигналинга. Тогда одна машина с сигналером сможет обслуживать множество сетей.
    Правда придется писать TCPoverUPD.
    Чтоб не гонять трафик через сервер.


    1. xlenz
      14.07.2017 20:06

      Думал над этим, но не знаю как это сделать. Обязательно позже займусь. Эта фича и шифрование — главный фокус. Проблемы в том числе и с шифрованием пока. В данный момент не могу понять почему после декрипта — данные другие, точнее, некоторые символы.