chiark / gitweb /
wip
[hippotat.git] / server
diff --git a/server b/server
index fb6bcc5e48a658e0168104d1723cca0eb7002ba2..775a0c34362458b93dffc0ec3fb6ade28b054e35 100755 (executable)
--- a/server
+++ b/server
@@ -1,16 +1,24 @@
 #!/usr/bin/python3
 
+import sys
+import os
+
 import twisted
+import twisted.internet
+import twisted.internet.endpoints
+from twisted.internet import reactor
+from twisted.web.server import NOT_DONE_YET
+from twisted.logger import LogLevel
+
+import ipaddress
+from ipaddress import AddressValueError
 
 #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
 
 from optparse import OptionParser
 from configparser import ConfigParser
 from configparser import NoOptionError
-import ipaddress
 
 import collections
 
@@ -18,7 +26,7 @@ import syslog
 
 clients = { }
 
-def ipaddress(input):
+def ipaddr(input):
   try:
     r = ipaddress.IPv4Address(input)
   except AddressValueError:
@@ -45,7 +53,7 @@ mtu = 1500
 # [relay]
 
 [server]
-ipif = userv root ipif %(host),%(relay),%(mtu),slip %(network)
+ipif = userv root ipif %(host)s,%(relay)s,%(mtu)s,slip %(network)s
 addrs = 127.0.0.1 ::1
 port = 80
 
@@ -57,14 +65,14 @@ max_request_time = 121
 
 #---------- "router" ----------
 
-def route(packet. daddr):
+def route(packet, daddr):
   try: client = clients[daddr]
   except KeyError: dclient = None
   if dclient is not None:
     dclient.queue_outbound(packet)
-  else if daddr = host or daddr not in network:
+  elif daddr == host or daddr not in network:
     queue_inbound(packet)
-  else if daddr = relay:
+  elif daddr == relay:
     log_discard(packet, saddr, daddr, 'relay')
   else:
     log_discard(packet, saddr, daddr, 'no client')
@@ -80,9 +88,10 @@ class IpifProcessProtocol(twisted.internet.protocol.ProcessProtocol):
     self._buffer = b''
   def connectionMade(self): pass
   def outReceived(self, data):
-    buffer += data
-    packets = slip_decode(buffer)
-    buffer = packets.pop()
+    #print('RECV ', repr(data))
+    self._buffer += data
+    packets = slip_decode(self._buffer)
+    self._buffer = packets.pop()
     for packet in packets:
       (saddr, daddr) = packet_addrs(packet)
       route(packet, daddr)
@@ -93,7 +102,7 @@ def start_ipif():
   global ipif
   ipif = IpifProcessProtocol()
   reactor.spawnProcess(ipif,
-                       '/bin/sh',['-c', ipif_command],
+                       '/bin/sh',['sh','-xc', ipif_command],
                        childFDs={0:'w', 1:'r', 2:2})
 
 def queue_inbound(packet):
@@ -101,6 +110,45 @@ def queue_inbound(packet):
   ipif.transport.write(slip_encode(packet))
   ipif.transport.write(slip_delimiter)
 
+#---------- SLIP handling ----------
+
+slip_end = b'\300'
+slip_esc = b'\333'
+slip_esc_end = b'\334'
+slip_esc_esc = b'\335'
+slip_delimiter = slip_end
+
+def slip_encode(packet):
+  return (packet
+          .replace(slip_esc, slip_esc + slip_esc_esc)
+          .replace(slip_end, slip_esc + slip_esc_end))
+
+def slip_decode(data):
+  print('DECODE ', repr(data))
+  out = []
+  for packet in data.split(slip_end):
+    pdata = b''
+    while True:
+      eix = packet.find(slip_esc)
+      if eix == -1:
+        pdata += packet
+        break
+      #print('ESC ', repr((pdata, packet, eix)))
+      pdata += packet[0 : eix]
+      ck = packet[eix+1]
+      if   ck == slip_esc_esc: pdata += slip_esc
+      elif ck == slip_esc_end: pdata += slip_end
+      else: raise ValueError('invalid SLIP escape')
+      packet = packet[eix+2 : ]
+    out.append(pdata)
+  print('DECODED ', repr(out))
+  return out
+
+#---------- packet parsing ----------
+
+def packet_addrs(packet):
+  pass
+
 #---------- client ----------
 
 class Client():
@@ -153,7 +201,7 @@ class Client():
 
         # now request is an unfinished request, or None
         try: (queuetime, packet) = self._pq[0]
-        except: IndexError:
+        except IndexError:
           # no packets, oh well
           break
 
@@ -190,7 +238,7 @@ class Client():
 class IphttpResource(twisted.web.resource.Resource):
   def render_POST(self, request):
     # find client, update config, etc.
-    ci = ipaddress(request.args['i'])
+    ci = ipaddr(request.args['i'])
     c = clients[ci]
     pw = request.args['pw']
     if pw != c.pw: raise ValueError('bad password')
@@ -213,7 +261,7 @@ class IphttpResource(twisted.web.resource.Resource):
 def start_http():
   resource = IphttpResource()
   sitefactory = twisted.web.server.Site(resource)
-  for addrspec in cfg.get('server','addresses').split():
+  for addrspec in cfg.get('server','addrs').split():
     try:
       addr = ipaddress.IPv4Address(addrspec)
       endpointfactory = twisted.internet.endpoints.TCP4ServerEndpoint
@@ -238,32 +286,45 @@ def process_cfg():
   try:
     host = cfg.get('virtual','host')
   except NoOptionError:
-    host = network.hosts().next()
+    host = next(network.hosts())
 
   try:
     relay = cfg.get('virtual','relay')
-  except OptionError:
+  except NoOptionError:
     for search in network.hosts():
-      if search = host: continue
+      if search == host: continue
       relay = search
       break
 
   for cs in cfg.sections():
     if not (':' in cs or '.' in cs): continue
-    ci = ipaddress(cs)
+    ci = ipaddr(cs)
     if ci not in network:
       raise ValueError('client %s not in network' % ci)
     if ci in clients:
       raise ValueError('multiple client cfg sections for %s' % ci)
     clients[ci] = Client(ci, cs)
 
+  global mtu
+  mtu = cfg.get('virtual','mtu')
+
   iic_vars = { }
   for k in ('host','relay','mtu','network'):
     iic_vars[k] = globals()[k]
 
   ipif_command = cfg.get('server','ipif', vars=iic_vars)
 
+def crash_on_critical(event):
+  if event.get('log_level') >= LogLevel.critical:
+    print('crashing: ', twisted.logger.formatEvent(event), file=sys.stderr)
+    #print('crashing!', file=sys.stderr)
+    #os._exit(1)
+    try: reactor.stop()
+    except twisted.internet.error.ReactorNotRunning: pass
+
 def startup():
+  global cfg
+
   op = OptionParser()
   op.add_option('-c', '--config', dest='configfile',
                 default='/etc/hippottd/server.conf')
@@ -271,10 +332,15 @@ def startup():
   (opts, args) = op.parse_args()
   if len(args): op.error('no non-option arguments please')
 
+  twisted.logger.globalLogPublisher.addObserver(crash_on_critical)
+
   cfg = ConfigParser()
   cfg.read_string(defcfg)
-  cfg.read_file(opts['configfile'])
+  cfg.read(opts.configfile)
   process_cfg()
 
   start_ipif()
   start_http()
+
+startup()
+reactor.run()