wip
[hippotat] / 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)