Обратное туннелирование UDP

У меня есть проблема, которую мне очень трудно решить, и я был бы очень благодарен, если бы кто-нибудь мог оказать помощь.

У меня есть VPN-сервер в локальной сети за брандмауэром, который разрешает только исходящие соединения. Моя цель - сделать «изменение пола UDP» и сделать порт VPN UDP доступным для внешнего сервера, на который я могу перенаправить порты, создав обратный туннель. Сделать это с помощью TCP-туннеля тривиально, и это легко сделать с помощью таких инструментов, как socat, nc или даже ssh-туннели. Однако VPN всегда следует передавать с помощью UDP-пакетов, чтобы избежать проблем с сбоями TCP (TCP поверх TCP).

Обратный туннель UDP, созданный с помощью socat / nc, не работает, поскольку UDP является протоколом без установления соединения. Это означает, что конфигурации «клиент-клиент» и «прослушивание-прослушивание» разрешают передачу данных только тогда, когда клиент сначала отправляет пакет (что невозможно при обратном соединении).

Я что-то упускаю? Есть ли какая-нибудь утилита, которая может выполнить эту задачу (например, сделав UDP-соединение ориентированным с использованием некоторых заголовков) без использования второго VPN-соединения? Большое тебе спасибо


person Francesco Brocca    schedule 10.05.2020    source источник
comment
Вы искали пробивку отверстий UDP или какой-либо из протоколов обхода NAT, чтобы узнать, что они делают? webrtc - еще одна распространенная система, которая, кажется, сталкивается с аналогичными проблемами, может быть местом для поиска   -  person Sam Mason    schedule 15.05.2020
comment
Привет! Спасибо за ваш ответ. Мне известно о пробивке отверстий UDP, но, к сожалению, мне не удалось найти какой-либо инструмент, позволяющий использовать его для обратного соединения.   -  person Francesco Brocca    schedule 16.05.2020
comment
И webrtc, и bittorrent хитроумно используют пробивку дырок, чтобы добиться схожих результатов. Технические аспекты туннеля мне понятны. Я пытаюсь выяснить, можно ли решить мою проблему с помощью существующих инструментов (nc, socat, (...), и, насколько я понял, это не так), или единственное возможное решение - это написать код кастомный туннель.   -  person Francesco Brocca    schedule 16.05.2020
comment
нельзя ли просто пинговать UDP-пакеты в / из известных портов с обеих сторон? этого может быть достаточно, чтобы заставить относительно глупый брандмауэр NAT создать для вас правило пересылки. не уверен, что у вас так много контроля над вещами, но может быть с чего начать   -  person Sam Mason    schedule 17.05.2020
comment
Это то, что я пытался сделать с помощью socat. Настоящая проблема - обратная часть соединения. Я могу установить соединение, но первый пакет должен быть отправлен клиентом. Есть ли способ отправить (и отклонить серверную сторону) автоматический пакет приветствия, чтобы установить UDP-соединение?   -  person Francesco Brocca    schedule 19.05.2020


Ответы (2)


Я думал о чем-то вроде этого в Python:

import socket
from select import select

# immediately useful parameters are:
#   REMOTE_SERVER_NAME other network server (which will be resolved by DNS)
#   LOCAL_SERVER_PORT where forward network traffic to

REMOTE_SERVER_NAME = '8.8.8.8'
LOCAL_SERVER_PORT = 20
LOCAL_SERVER_NAME = '127.0.0.1'
REMOTE_PORT = 9990
LOCAL_PORT = REMOTE_PORT + 1

sock_remote = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_remote.bind(('', REMOTE_PORT))
sock_remote.connect((REMOTE_SERVER_NAME, REMOTE_PORT))

sock_local = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_local.bind(('', LOCAL_PORT))
sock_local.connect((LOCAL_SERVER_NAME, LOCAL_SERVER_PORT))

sockets = (sock_remote, sock_local)
for s in sockets:
    s.setblocking(0)

# loop forever forwarding packets between the connections
while True:
    avail, _, _ = select((sock_local, sock_remote), (), (), timeout=100)

    # send a keep alive message every timeout
    if not avail:
        sock_remote.send(b'keep alive')
        continue

    for s in avail:
        # something from the local server, forward it on
        if s is sock_local:
            msg = sock_local.recv(8192)
            sock_remote.send(msg)

        # something from the remote server
        if s is sock_remote:
            msg = sock_remote.recv(8192)
            # don't forward keep alives to local system
            if msg != b'keep alive':
                sock_local.send(msg)

т.е. запустите это на любом сервере (изменив REMOTE_SERVER_NAME, чтобы он указывал на соответствующее место), и он будет пересылать пакеты между ними, посылая «поддерживающий» пакет каждые 100 секунд. ваш локальный процесс будет отправлять пакеты UDP на LOCAL_PORT, и они будут перенаправлены на удаленный сервер, удаленный сервер получит их и отправит на LOCAL_SERVER_PORT на другом конце. есть 8 потоков, о которых нужно беспокоиться, поэтому именование становится неудобным:

DMZ <=> VPN <=> python <=> NAT <=> internet <=> NAT <=> python <=> VPN <=> DMZ

вы могли бы обнаружить LOCAL_SERVER_NAME и LOCAL_SERVER_PORT с помощью sock_local.recvfrom и спрятать addrinfo, но я подумал, что оставлю их для простоты понимания

надеюсь, вы понимаете Python! но я изо всех сил пытался выразить это словами

person Sam Mason    schedule 19.05.2020
comment
Привет, большое спасибо за ответ. Использование известных портов с обеих сторон сделало это невозможным в моем приложении, но направило меня в правильном направлении, и с некоторыми изменениями я смог заставить его работать в моей среде. Вскоре я опубликую его в другом ответе, и я был бы очень благодарен, если бы вы могли проверить его на предмет возможных улучшений. - person Francesco Brocca; 20.05.2020

Я адаптировал ответ Сэма Мейсона для выполнения обратного UDP-соединения. Это конфигурация:

HOST UDP-LISTEN <=> python cc <=> NAT <=> internet <=> server <=> python ll UDP-LISTEN

Хост слушает порт UDP. Вы можете связаться с хостом с любого третьего клиента, имеющего доступ к серверу, используя обратное соединение через скрипт python.

Первая часть - это скрипт прослушивания прослушивания. Он будет прослушивать клиентский сценарий на CC_PORT и пересылать что-либо на SERVICE_PORT. Он также отбрасывает сообщения поддержки активности, поступающие из сценария cc.

import socket
from select import select

CC_PORT = 8881
SERVICE_PORT = 8880
LISTEN_IP='x.x.x.x'

sock_cc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_cc.bind((LISTEN_IP,CC_PORT))

sock_serv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_serv.bind((LISTEN_IP,SERVICE_PORT))

sockets = (sock_cc, sock_serv)
for s in sockets:
    s.setblocking(0)

client_serv=None
client_cc=None

# loop forever forwarding packets between the connections
while True:
    avail, _, _ = select((sock_cc, sock_serv), (), (), 1)

    # send a keep alive message every timeout
    if not avail:
        continue

    for s in avail:
        # something from the local server, forward it on
        if s is sock_cc:
            msg = sock_cc.recvfrom(8192)
            client_cc=msg[1]
            if client_serv is not None:
                if msg[0] != b'keep alive':
                    sock_serv.sendto(msg[0], client_serv)

        # something from the remote server
        if s is sock_serv:
            msg = sock_serv.recvfrom(8192)
            client_serv=msg[1]
            if client_cc is not None:
                sock_cc.sendto(msg[0], client_cc)

Клиентский клиентский скрипт

import socket

from select import select

DEST_IP = 'x.x.x.x' 
DEST_PORT = 8881 
LOCAL_IP = '127.0.0.1' 
LOCAL_SERVICE = 8800

sock_remote = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_remote.connect((DEST_IP,DEST_PORT)) sock_local = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_local.connect((LOCAL_IP,LOCAL_SERVICE)) sockets = (sock_remote, sock_local) for s in sockets:
    s.setblocking(0)

# loop forever forwarding packets between the connections while True:
    avail, _, _ = select((sock_local, sock_remote), (), (), 1)

    # send a keep alive message every timeout
    if not avail:
        sock_remote.send(b'keep alive')
        continue

    for s in avail:
        # something from the local server, forward it on
        if s is sock_local:
            msg = sock_local.recv(8192)
            sock_remote.send(msg)

        # something from the remote server
        if s is sock_remote:
            msg = sock_remote.recv(8192)
            # don't forward keep alives to local system
            if msg != b'keep alive':
                sock_local.send(msg)

Еще раз спасибо Сэму Мэйсону за помощь в решении этой проблемы.

person Francesco Brocca    schedule 20.05.2020
comment
да, думал, ты захочешь что-то вроде этого! это сделало его более сложным, и я беспокоился о том, что первые несколько пакетов будут важны для доставки, поэтому не думал, что это стоит включать в версию, которую я опубликовал. может внести несколько стилистических улучшений, но выглядит функционально нормально - person Sam Mason; 21.05.2020
comment
это хорошо, но таким образом машина A должна достигнуть машины B, если и A, и B находятся за брандмауэрами, то единственный способ - использовать webrtc / stun / turn для соединения двух: D - person Zibri; 14.07.2021