X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=server;h=9d8338616e76c6343a6947e890f870d222eb2e55;hb=e75e9c176dde9a52258bc43631c8a4a2973d59fe;hp=1a9049b845131db9c6148373e6f121853fd60e9a;hpb=ec88b1f1080f9adffbe1c4c05c38c01e758f124b;p=hippotat.git diff --git a/server b/server index 1a9049b..9d83386 100755 --- a/server +++ b/server @@ -1,13 +1,19 @@ -#!/usr/bin/python2 +#!/usr/bin/python3 -from twisted.web.server import Site +import twisted.web.server import Site from twisted.web.resource import Resource from twisted.web.server import NOT_DONE_YET from twisted.internet import reactor -import ConfigParser +from optparse import OptionParser +from configparser import ConfigParser +from configparser import NoOptionError import ipaddress +import collections + +import syslog + clients = { } def ipaddress(input): @@ -24,44 +30,142 @@ def ipnetwork(input): r = ipaddress.IPv6Network(input) return r -defcfg = u''' -[default] -max_batch_down: 65536 -max_queue_time: 10 -max_request_time: 54 - -[global] -max_batch_down: 262144 -max_queue_time: 121 -max_request_time: 121 +defcfg = ''' +[DEFAULT] +max_batch_down = 65536 +max_queue_time = 10 +max_request_time = 54 + +[virtual] +mtu = 1500 +# network +# [host] +# [relay] + +[server] +ipif_program = userv root ipif %(host),%(relay),%(mtu),slip %(network) + +[limits] +max_batch_down = 262144 +max_queue_time = 121 +max_request_time = 121 ''' +def route(packet. daddr): + try: client = clients[daddr] + except KeyError: dclient = None + if dclient is not None: + dclient.queue_outbound(packet) + else if daddr = server or daddr not in network: + queue_inbound(packet) + else: + syslog.syslog(syslog.LOG_DEBUG, 'no client for %s' % daddr) + class Client(): - def __init__(ip, cs): + def __init__(self, ip, cs): # instance data members self._ip = ip self._cs = cs self.pw = cfg.get(cs, 'password') - # plus: - # .cfg[] - self.cfg = { } + self._rq = collections.deque() # requests + self._pq = collections.deque() # packets + # plus from config: + # .max_batch_down + # .max_queue_time + # .max_request_time for k in ('max_batch_down','max_queue_time','max_request_time'): req = cfg.getint(cs, k) - limit = cfg.getint('global',k) - self.cfg[k] = min(req, limit) - - def process_arriving_data(d): - + limit = cfg.getint('limits',k) + self.__dict__[k] = min(req, limit) + + def process_arriving_data(self, d): + for packet in slip_decode(d): + (saddr, daddr) = ip_64_addrs(packet) + if saddr != self._ip: + raise ValueError('wrong source address %s' % saddr) + route(packet, daddr) + + def _req_cancel(self, request): + request.finish() + + def _req_error(self, err, request): + self._req_cancel(request) + + def queue_outbound(self, packet): + self._pq.append((time.monotonic(), packet)) + + def http_request(self, request): + request.setHeader('Content-Type','application/octet-stream') + reactor.callLater(self.max_request_time, self._req_cancel, request) + request.notifyFinish().addErrback(self._req_error, request) + self._rq.append(request) + self._check_outbound() + + def _check_outbound(self): + while True: + try: request = self._rq[0] + except IndexError: request = None + if request and request.finished: + self._rq.popleft() + continue + + # now request is an unfinished request, or None + try: (queuetime, packet) = self._pq[0] + except: IndexError: + # no packets, oh well + break + + age = time.monotonic() - queuetime + if age > self.max_queue_time: + self._pq.popleft() + continue + + if request is None: + # no request + break + + # request, and also some non-expired packets + while True: + try: (dummy, packet) = self._pq[0] + except IndexError: break + + encoded = slip_encode(packet) + + if request.sentLength > 0: + if (request.sentLength + len(slip_delimiter) + + len(encoded) > self.max_batch_down): + break + request.write(slip_delimiter) + + request.write(encoded) + self._pq.popLeft() + + assert(request.sentLength) + self._rq.popLeft() + request.finish() + # round again, looking for more to do def process_cfg(): global network - global ourself + global host + global relay network = ipnetwork(cfg.get('virtual','network')) + if network.num_addresses < 3 + 2: + raise ValueError('network needs at least 2^3 addresses') + + try: + host = cfg.get('virtual','host') + except NoOptionError: + host = network.hosts().next() + try: - ourself = cfg.get('virtual','server') - except ConfigParser.NoOptionError: - ourself = network.hosts().next() + relay = cfg.get('virtual','relay') + except OptionError: + for search in network.hosts(): + if search = host: continue + relay = search + break for cs in cfg.sections(): if not (':' in cs or '.' in cs): continue @@ -87,11 +191,21 @@ class FormPage(Resource): try: v = request.args[r] except KeyError: continue v = int(v) - c.cfg[w] = v + c.__dict__[w] = v try: d = request.args['d'] except KeyError: d = '' c.process_arriving_data(d) - - reactor. + c.new_request(request) + +def startup(): + op = OptionParser() + op.add_option('-c', '--config', dest='configfile', + default='/etc/hippottd/server.conf') + global opts + (opts, args) = op.parse_args() + if len(args): op.error('no non-option arguments please') + + cfg = ConfigParser() +