udptunnelingsocat

UDP reverse tunneling


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


Solution

  • 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