Last active
April 23, 2018 15:31
-
-
Save donghaoren/438e71804ba6fdb11763e554316c9aff to your computer and use it in GitHub Desktop.
UDP Forwarder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# 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. | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment