diff --git a/router.py b/router.py index 41c6af1..b59a6a3 100644 --- a/router.py +++ b/router.py @@ -1,11 +1,29 @@ +import sys import os import socket +import logging from glob import glob from select import select from serial import serial_for_url from subprocess import Popen, PIPE, check_output, call, DEVNULL from configparser import ConfigParser +sim = False + +if sys.argv[0] == 'router.py': # started manually + loglev = logging.INFO +else: + loglev = logging.WARNING + +for arg in sys.argv[1:]: + if arg == '-v': + loglev = logging.DEBUG + elif arg == '-s': + sim = True + else: + raise ValueError(f'do not know {arg!r}') + +logging.basicConfig(format='%(levelname)1.1s: %(message)s', level=loglev) FILTER = "iptables -i enp4s0 -p tcp -m tcp --dport %d -j ACCEPT" @@ -16,65 +34,56 @@ iptables -P OUTPUT ACCEPT iptables -A INPUT -i lo -j ACCEPT """ -sim = False - - def unix_cmd(command): if sim: - print('> %r' % command) + logging.info('> %r' % command) else: - print('$ %r' % command) + logging.info('$ %r' % command) return Popen(command.split(), stdout=PIPE).communicate()[0].decode() class IoHandler: client = None handler = None - port = None def __init__(self, client, handler): self.handler = handler self.client = client - self.sentchunks = 0 - self.sentbytes = 0 - self.rcvdchunks = 0 - self.rcvdbytes = 0 def request(self): try: data = self.client.recv(1024) if data: - # print('<', data) + logging.debug('< %r', data) self.write(data) - self.sentbytes += len(data) - self.sentchunks += 1 return except Exception as e: - print('ERROR in request: %s' % e) + logging.error('ERROR in request: %r', e) self.close() self.handler.close_client(self) def reply(self): try: data = self.read() - # print('>', data) + logging.debug('> %r', data) self.client.sendall(data) - self.rcvdbytes += len(data) - self.rcvdchunks += 1 return except ConnectionResetError: pass except Exception as e: - print('ERROR in reply: %s' % e) + logging.error('ERROR in reply: %r', e) self.close() self.handler.close_client(self) class TcpHandler(IoHandler): def __init__(self, client, handler): - self.socket = socket.create_connection(handler.addr) + logging.info('create %r', handler.addr) + self.socket = socket.create_connection(handler.addr, timeout=5) + self.socket.settimeout(1) self.fno = self.socket.fileno() super().__init__(client, handler) + logging.debug('created') def read(self): data = self.socket.recv(1024) @@ -89,7 +98,7 @@ class TcpHandler(IoHandler): try: self.socket.close() except Exception as e: - print('ERROR in close: %s' % e) + logging.error('ERROR in close: %r', e) class SerialHandler(IoHandler): @@ -109,44 +118,6 @@ class SerialHandler(IoHandler): self.serial.close() -class InfoHandler(IoHandler): - clients = {} - - def __init__(self, client, handler): - super().__init__(client, handler) - info = [f'{k} -> {v}' for k, v in AcceptHandler.routes.items()] - if AcceptHandler.handlers: - info.append('\nactive routings, statistics bytes/chunks') - info.append('fno port sent received') - for fno, h in AcceptHandler.handlers.items(): - info.append(f'{fno} {h.port} {h.sentbytes:d}/{h.sentchunks:d} {h.rcvdbytes:d}/{h.rcvdchunks:d}') - info.append('') - self.client.sendall('\n'.join(info).encode('utf-8')) - self.clients[client.fileno()] = self - self.fno = None - - def read(self): - return b'' - - def write(self, data): - pass - - def close(self): - self.clients.pop(self.client.fileno()) - - @classmethod - def log(cls, line): - if cls.clients: - for c in cls.clients.values(): - try: - c.client.sendall(line.encode('utf-8')) - c.client.sendall(b'\n') - except TimeoutError: - pass - else: - print(line) - - class AcceptHandler: """handler for routing @@ -160,10 +131,8 @@ class AcceptHandler: reused: in this case maxcount has to be increased ... """ readers = {} - handlers = {} - routes = {} - def __init__(self, port, addr, iocls, maxcount=1): + def __init__(self, port, addr, iocls, maxcount=None): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('0.0.0.0', port)) @@ -173,20 +142,20 @@ class AcceptHandler: self.iocls = iocls self.readers[s.fileno()] = self.accept self.port = port + if maxcount is None: + maxcount = 8 if addr[1] == 80 else 1 self.available = maxcount self.pending = 0 + logging.info('listening at port %d for %s(%r)', port, iocls.__name__, addr) def close_client(self, iohandler): self.readers.pop(iohandler.fno, None) - client = iohandler.client - fno = client.fileno() try: - self.readers.pop(fno) + client = iohandler.client + self.readers.pop(client.fileno()) client.close() - InfoHandler.log(f'closed connection from port {self.port} fno {fno}') except Exception as e: - InfoHandler.log(f'{e!r} in close_client') - self.handlers.pop(fno, None) + logging.error('ERROR in close_client: %r', e) iohandler.client = None iohandler.fno = None self.available += 1 @@ -200,23 +169,18 @@ class AcceptHandler: return try: client, addr = self.socket.accept() - InfoHandler.log(f'accepted {addr} on {self.port} fno {client.fileno()}') + logging.info('accepted %r on %r', addr, self.port) handler = self.iocls(client, self) except Exception as e: - InfoHandler.log(f'{e!r} creating {self.iocls.__name__}({self.addr})') + logging.error('%r creating %s(%r)', e, self.iocls.__name__, self.addr) client.close() return self.readers[client.fileno()] = handler.request - if handler.fno is not None: - self.readers[handler.fno] = handler.reply - # statistics: number of chunks sent / received - handler.port = self.port - self.handlers[client.fileno()] = handler + self.readers[handler.fno] = handler.reply self.available -= 1 @classmethod def run(cls, routes, restrict=None): - cls.routes = dict(routes) if restrict is not None: lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT') unix_cmd('iptables -F') @@ -226,7 +190,6 @@ class AcceptHandler: if restrict: unix_cmd(FILTER % 22) - AcceptHandler(1111, None, InfoHandler, 5) for port, dest in routes.items(): port=int(port) if restrict: @@ -242,18 +205,21 @@ class AcceptHandler: AcceptHandler(port, (host, remoteport), TcpHandler) while True: try: + # logging.debug('select %r', list(cls.readers)) ready, _, _ = select(cls.readers, [], []) + # logging.debug('ready %r', ready) except Exception as e: for r in cls.readers: try: select([r], [], [], 0.1) except Exception as e: - print(r, e) + logging.error('%r in select([%d])', e, r) raise for fno in ready: cls.readers[fno]() + if __name__ == '__main__': parser = ConfigParser() cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())