December 14, 2012
UPDATE: A better-implemented Node.js version available here: https://gist.github.com/donghaoren/c7bbddd2ea865b0f25c3ed778db6d7db
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.
udp_proxy forward-ip forward-port listen-port [v6] [obfuscate] [local]
v6
to listen on ipv6 port.obfuscate
to enable packet obfuscation.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