Skip to content

Instantly share code, notes, and snippets.

@clynamen
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clynamen/7c35998b913005252bcb to your computer and use it in GitHub Desktop.
Save clynamen/7c35998b913005252bcb to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
"""copy_forwarder
Usage:
copy_forwader.py <conf_filename> [-v]
Options:
-v --verbose Print debug info during execution
-h --help Print this help
copy_forwarder.py forwards udp packets using the rules expressed in <conf_filename>, e.g.:
192.168.1.2:1234 -> 192.168.1.3:5678
192.168.1.2:5000 -> 192.168.1.255:5000
It can be used to broadcast a stream from an external source
"""
import socket
from select import select
from docopt import docopt
import re
running = True
MAX_UDP_SIZE=65535
class ForwardRecord:
def __init__(self, from_addr, from_port, to_addr, to_port):
assert(isinstance(from_addr, str))
assert(isinstance(from_port, int))
assert(isinstance(to_addr, str))
assert(isinstance(to_port, int))
self.from_addr = from_addr
self.to_addr = to_addr
self.from_port = from_port
self.to_port = to_port
def __str__(self):
return "[{}:{} -> {}:{}]".format( \
self.from_addr, self.from_port, \
self.to_addr, self.to_port)
class CopyForwarder:
def __init__(self, conf_filename, debug_enabled=False):
self.forward_table = {}
self.debug_enabled = debug_enabled
self.read_conf(conf_filename)
def conf_line_error(self, line_num, line, cause=""):
print("Error while reading line {}: ".format(line_num))
print(line)
print(cause)
def read_conf(self, conf_filename):
# e.g. 192.168.1.1:1234 -> 192.168.1.2:5678
r = re.compile('([\w.]+):(\d+) +-> +([\w.]+):(\d+)')
with open(conf_filename, 'r') as conf:
line_count=0
for line in conf.readlines():
line_count += 1
if line.isspace():
continue
groups = r.search(line).groups()
if len(groups) != 4:
self.conf_line_error(line_count, line)
continue
record = None
try:
record = ForwardRecord(groups[0], int(groups[1]), \
groups[2], int(groups[3]))
except AssertionError as e:
self.conf_line_error(line_count, line, e.message())
continue
if self.debug_enabled:
print("added record: {}".format(record))
key = (record.from_addr, record.from_port)
if key in self.forward_table:
records = self.forward_table[key]
records.append(record)
self.forward_table[key] = records
else:
self.forward_table.update({key: [record]})
def make_upd_socket(self):
return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def bind(self, socket, addr, port):
try:
socket.bind((addr, port))
except Exception as e:
print ("ERROR: Unable to bind {}:{} : {}".format(addr, port, e))
return False
return True
def start(self):
sockets = {}
for r in self.forward_table.keys():
sock = self.make_upd_socket()
if not self.bind(sock, r[0], r[1]):
print ("Unable to listen at {}:{}".format(r[0], r[1]))
continue
dest_records = []
for record in self.forward_table[r]:
dest_records.append(record)
send_sock = self.make_upd_socket()
send_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
d = {sock: (dest_records, send_sock)}
sockets.update(d)
sockets_list = sockets.keys()
while(running):
for readable in sockets_list:
readable.settimeout(0.005)
try:
data, (from_addr, from_port) = readable.recvfrom(MAX_UDP_SIZE)
except socket.timeout as e:
continue
if self.debug_enabled:
print("received from {}:{} at socket {}:{}".format(
from_addr, from_port, record.from_addr, \
record.from_port))
send_records, send_sock = sockets[readable]
for record in send_records:
if self.debug_enabled:
print("Redirecting to {}:{} data: {}".format(
record.to_addr, record.to_port, data))
try:
res = send_sock.sendto(data, (record.to_addr, record.to_port))
except PermissionError as e:
print ("No enough permission to send packets to {}:{}".format( record.to_addr, record.to_port))
except Exception as e:
print ("Unable to send packets to {}:{}".format( record.to_addr, record.to_port))
if __name__ == '__main__':
arguments = docopt(__doc__, version='Copy Forwarder 1.0')
copy_forwarder = CopyForwarder(arguments['<conf_filename>'], debug_enabled=arguments['--verbose'])
copy_forwarder.start()
@clynamen
Copy link
Author

revision 2, windows version with timeout and multiple map:
192.168.1.100:1000 -> 192.168.1.101:1000
192.168.1.100:1000 -> 192.168.1.102:1000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment