1
votes

I have a problem that I am having a serious hard time figuring out, and I would be very grateful if anyone could provide some help.

I have a VPN server inside a local network behind a firewall that allows only outbound connections. My goal it to do a “UDP gender change” and make the VPN UDP port available to an external server where I can forward ports, by creating a reverse tunnel. Doing this using a TCP tunnel is trivial and easy to accomplish by using tools such as socat, nc or even ssh tunnels. VPN, however, should always be carried by UDP packets to avoid TCP meltdown issue (TCP over TCP).

UDP reverse tunnel created with socat/nc does not work as UDP is a connectionless protocol. This means that “client client” and “listen listen” configuration will allow data transfer only when client sends a packet first (impossible in a reverse connection).

Am I missing something? Is there any utility that can accomplish this task (by for example making UDP connection oriented by the use of some headers) without using a second VPN connection? Thank you very much

2
have you searched for "UDP hole punching" or any of the NAT traversal protocols to see what they do? webrtc is another common system that seems to face similar problems, might be a place to look - Sam Mason
Hi! Thank you for your answer. I am aware of UDP hole punching but unfortunately I have been unable to find any tool that allow its use for a reverse connection. - Francesco Brocca
Both webrtc and bittorrent use hole punching in a clever way to archieve somehow similar results. The technical aspects of the tunnel are clear to me. What I am trying to find is if my problem is solvable by using existing tools (nc, socat, (...) and as far as I have been able to understand it is not) or if the only possible solution is to code a custom tunnel. - Francesco Brocca
can't you just ping UDP packets to/from known ports on either side? that might be enough to coerce a relatively dumb NATing firewall into creating a forwarding rule for you. not sure if you have that much control over things, but might be somewhere to start - Sam Mason
That's kind of what I was trying to do with socat. The real problem is the "reverse" part of the connection. I can establish the connection, but the first packet must be sent by the client. Is there any way to send (and discard server side) an automatic "hello" packet, to establish the UDP connection? - Francesco Brocca

2 Answers

0
votes

I was thinking of something like this in 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)

i.e. run this on either server (changing REMOTE_SERVER_NAME to point to the appropriate place) and it'll forward packets between them, sending a "keep alive" packet every 100 seconds. your local process would send UDP packets to LOCAL_PORT and these would be forwarded to the remote server, the remote server would receive these and send them on to LOCAL_SERVER_PORT at the other end. there are 8 flows to worry about so naming gets awkward:

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

you might be able to detect the LOCAL_SERVER_NAME and LOCAL_SERVER_PORT using a sock_local.recvfrom and stashing the addrinfo away, but thought I'd leave them in for ease of understanding

hope you understand Python! but I was struggling to express it in words

0
votes

I adapted Sam Mason's answer to execute a UDP reverse connection. This is the configuration:

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

Host listen to a UDP port. You can reach the host from any third client that has acces to the server by using the reverse connection through the python script.

The first part is the listen listen script. It will listen for the client script on CC_PORT and will forward anything on SERVICE_PORT. It will also discard keepalive coming from the cc script.

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)

The client client script

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)

Thanks again to Sam Mason for helping me solve this problem.