chiark / gitweb /
46ce5ed9cc4558966dd1b97f0a79701c59590d5e
[hippotat.git] / server
1 #!/usr/bin/python2
2
3 from twisted.web.server import Site
4 from twisted.web.resource import Resource
5 from twisted.web.server import NOT_DONE_YET
6 from twisted.internet import reactor
7
8 import ConfigParser
9 import ipaddress
10
11 import syslog
12
13 clients = { }
14
15 def ipaddress(input):
16   try:
17     r = ipaddress.IPv4Address(input)
18   except AddressValueError:
19     r = ipaddress.IPv6Address(input)
20   return r
21
22 def ipnetwork(input):
23   try:
24     r = ipaddress.IPv4Network(input)
25   except NetworkValueError:
26     r = ipaddress.IPv6Network(input)
27   return r
28
29 defcfg = u'''
30 [default]
31 max_batch_down: 65536
32 max_queue_time: 10
33 max_request_time: 54
34
35 [global]
36 max_batch_down: 262144
37 max_queue_time: 121
38 max_request_time: 121
39 '''
40
41 def route(packet. daddr):
42   try: client = clients[daddr]
43   except KeyError: dclient = None
44   if dclient is not None:
45     dclient.queue_outbound_data(packet)
46   else if daddr = server or daddr not in network:
47     queue_inbound_data(packet)
48   else:
49     syslog.syslog(syslog.LOG_DEBUG, 'no client for %s' % daddr)
50
51 class Client():
52   def __init__(self, ip, cs):
53     # instance data members
54     self._ip = ip
55     self._cs = cs
56     self.pw = cfg.get(cs, 'password')
57     # plus from config:
58     #  .max_batch_down
59     #  .max_queue_time
60     #  .max_request_time
61     for k in ('max_batch_down','max_queue_time','max_request_time'):
62       req = cfg.getint(cs, k)
63       limit = cfg.getint('global',k)
64       self.__dict__[k] = min(req, limit)
65
66     def process_arriving_data(self, d):
67       for packet in slip_decode(d):
68         (saddr, daddr) = ip_64_addrs(packet)
69         if saddr != self._ip:
70           raise ValueError('wrong source address %s' % saddr)
71         route(packet, daddr)
72
73     def _req_cancel(self, request):
74       request.finish()
75
76     def _req_error(self, err, request):
77       self._req_cancel(request)
78
79     def http_request(self, request):
80       request.setHeader('Content-Type','application/octet-stream')
81       reactor.callLater(self.max_request_time, self._req_cancel, request)
82       request.notifyFinish().addErrback(self._req_error, request)
83
84 def process_cfg():
85   global network
86   global ourself
87
88   network = ipnetwork(cfg.get('virtual','network'))
89   try:
90     ourself = cfg.get('virtual','server')
91   except ConfigParser.NoOptionError:
92     ourself = network.hosts().next()
93
94   for cs in cfg.sections():
95     if not (':' in cs or '.' in cs): continue
96     ci = ipaddress(cs)
97     if ci not in network:
98       raise ValueError('client %s not in network' % ci)
99     if ci in clients:
100       raise ValueError('multiple client cfg sections for %s' % ci)
101     clients[ci] = Client(ci, cs)
102
103 class FormPage(Resource):
104   def render_POST(self, request):
105     # find client, update config, etc.
106     ci = ipaddress(request.args['i'])
107     c = clients[ci]
108     pw = request.args['pw']
109     if pw != c.pw: raise ValueError('bad password')
110
111     # update config
112     for r, w in (('mbd', 'max_batch_down'),
113                  ('mqt', 'max_queue_time'),
114                  ('mrt', 'max_request_time')):
115       try: v = request.args[r]
116       except KeyError: continue
117       v = int(v)
118       c.__dict__[w] = v
119
120     try: d = request.args['d']
121     except KeyError: d = ''
122
123     c.process_arriving_data(d)
124     c.new_request(request)