UDP Forwarder

This is a simple UDP forwarder with obfuscation support. It listens on a specific port for incoming UDP packets, and then send them to a forward address. In addition, it remembers senders’ addresses, and delivers packets from the forward address to the correct sender. Therefore it maintains ‘connections’ between the server and UDP clients.

  • Usage: udp_proxy forward-ip forward-port listen-port [v6] [obfuscate] [local]
  • Description:
  • Listen on *:listen-port for UDP packets, forward them to forward-ip:forward-port.
  • IPv6 support: forward-ip can be an ipv6 address, use option v6 to listen on ipv6 port.
  • Use option obfuscate to enable packet obfuscation.
  • Use option local to listen on localhost only.

The source code:

#!/usr/bin/python

import socket
import threading
import sys
import time

if len(sys.argv) < 4:
    print "Usage: udp_proxy forward-ip forward-port listen-port [v6] [obfuscate] [local]"
    print "Description:"
    print "1. Listen on *:listen-port for UDP packets,"
    print "   forward them to forward-ip:forward-port."
    print "2. IPv6 support: forward-ip can be an ipv6 address,"
    print "   use option 'v6' to listen on ipv6 port."
    print "3. Use option 'obfuscate' to enable packet obfuscation."
    print "4. Use option 'local' to listen on localhost only."
    exit()

# Parameters.
FORWARD_TO = int(sys.argv[2])
FORWARD_IP = sys.argv[1]
LISTEN_ON = int(sys.argv[3])

TIMEOUT_SECONDS = 180

FORWARD_V6 = ":" in FORWARD_IP
LISTEN_V6 = False
OBFUSCATE_PACKETS = False
OBFUSCATE_SEED = 2394
LISTEN_LOCAL_ONLY = False

for opt in sys.argv:
    if opt == "v6": LISTEN_V6 = True
    if opt == "obfuscate": OBFUSCATE_PACKETS = True
    if opt == "local": LISTEN_LOCAL_ONLY = True

# Server socket.
# Bind socket to LISTEN_ON port, all interfaces.
if LISTEN_V6:
    sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    if LISTEN_LOCAL_ONLY:
        print "Listen on [::1]:%s with IPv6" % LISTEN_ON
        sock.bind(("::1", LISTEN_ON))
    else:
        print "Listen on *:%s with IPv6" % LISTEN_ON
        sock.bind(("", LISTEN_ON))
else:
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    if LISTEN_LOCAL_ONLY:
        print "Listen on 127.0.0.1:%s with IPv4" % LISTEN_ON
        sock.bind(("127.0.0.1", LISTEN_ON))
    else:
        print "Listen on *:%s with IPv4" % LISTEN_ON
        sock.bind(("", LISTEN_ON))

# Socket threads.
sock_dict = { }

# Lock.
lock = threading.Lock()

# Obfuscate data
# Satisfy:
# 1. len(obfuscate(data)) = len(data)
# 2. obfuscate(obfuscate(data)) = data
def obfuscate(data, dir):
    if not OBFUSCATE_PACKETS: return data
    a = bytearray(data)
    g_seed = OBFUSCATE_SEED
    for i in range(len(a)):
        g_seed = ((214013 * g_seed + 2531011)) & 0xFFFFFFFF;
        a[i] ^= (g_seed >> 16) & 0xFF
    return str(a)

# One thread for each connection.
class ListenThread(threading.Thread):
    def __init__(self, info):
        threading.Thread.__init__(self)
        self.s_client = info['socket']
        # Set timeout to 180 seconds, which is the common UDP gateway timeout.
        self.s_client.settimeout(1)
        self.addr = info['addr']
        self.last_receive = time.time()
        self.should_stop = False

    def run(self):
        while not self.should_stop:
            try: data, r_addr = self.s_client.recvfrom(65536)
            except:
                if time.time() - self.last_receive > TIMEOUT_SECONDS:
                    break
                else:
                    continue
            # Reset timeout.
            self.last_receive = time.time()
            # Successfully received a packet, forward it.
            data = obfuscate(data, 1)
            sock.sendto(data, self.addr)
        lock.acquire()
        try:
            self.s_client.close()
            sock_dict.pop(self.addr)
        except: pass
        lock.release()
        print "Client released for ", self.addr

    def stop(self):
        self.should_stop = True

try:
    while True:
        data, addr = sock.recvfrom(65536) # buffer size is 1024 bytes
        data = obfuscate(data, 0)
        lock.acquire()
        try:
            if not addr in sock_dict:
                if FORWARD_V6:
                    s_client = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
                else:
                    s_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                item = {
                    "socket": s_client,
                    "addr": addr
                }
                print "Adding client for ", addr
                s_client.sendto(data, (FORWARD_IP, FORWARD_TO))
                t = ListenThread(item)
                t.start()
                item['thread'] = t
                sock_dict[addr] = item
            else:
                s_client = sock_dict[addr]['socket']
                s_client.sendto(data, (FORWARD_IP, FORWARD_TO))
        except: pass
        lock.release()
except: pass

# Stop all threads.
for addr in sock_dict:
    try: sock_dict[addr]['thread'].stop()
    except: pass

Posted on Dec 15, 2012, 01:00 PM, CST

Tags: Programs