3 import 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
8 from optparse import OptionParser
9 from configparser import ConfigParser
10 from configparser import NoOptionError
21 r = ipaddress.IPv4Address(input)
22 except AddressValueError:
23 r = ipaddress.IPv6Address(input)
28 r = ipaddress.IPv4Network(input)
29 except NetworkValueError:
30 r = ipaddress.IPv6Network(input)
35 max_batch_down = 65536
46 ipif_program = userv root ipif %(host),%(relay),%(mtu),slip %(network)
49 max_batch_down = 262144
51 max_request_time = 121
54 def route(packet. daddr):
55 try: client = clients[daddr]
56 except KeyError: dclient = None
57 if dclient is not None:
58 dclient.queue_outbound(packet)
59 else if daddr = server or daddr not in network:
62 syslog.syslog(syslog.LOG_DEBUG, 'no client for %s' % daddr)
65 def __init__(self, ip, cs):
66 # instance data members
69 self.pw = cfg.get(cs, 'password')
70 self._rq = collections.deque() # requests
71 self._pq = collections.deque() # packets
76 for k in ('max_batch_down','max_queue_time','max_request_time'):
77 req = cfg.getint(cs, k)
78 limit = cfg.getint('limits',k)
79 self.__dict__[k] = min(req, limit)
81 def process_arriving_data(self, d):
82 for packet in slip_decode(d):
83 (saddr, daddr) = ip_64_addrs(packet)
85 raise ValueError('wrong source address %s' % saddr)
88 def _req_cancel(self, request):
91 def _req_error(self, err, request):
92 self._req_cancel(request)
94 def queue_outbound(self, packet):
95 self._pq.append((time.monotonic(), packet))
97 def http_request(self, request):
98 request.setHeader('Content-Type','application/octet-stream')
99 reactor.callLater(self.max_request_time, self._req_cancel, request)
100 request.notifyFinish().addErrback(self._req_error, request)
101 self._rq.append(request)
102 self._check_outbound()
104 def _check_outbound(self):
106 try: request = self._rq[0]
107 except IndexError: request = None
108 if request and request.finished:
112 # now request is an unfinished request, or None
113 try: (queuetime, packet) = self._pq[0]
115 # no packets, oh well
118 age = time.monotonic() - queuetime
119 if age > self.max_queue_time:
127 # request, and also some non-expired packets
129 try: (dummy, packet) = self._pq[0]
130 except IndexError: break
132 encoded = slip_encode(packet)
134 if request.sentLength > 0:
135 if (request.sentLength + len(slip_delimiter)
136 + len(encoded) > self.max_batch_down):
138 request.write(slip_delimiter)
140 request.write(encoded)
143 assert(request.sentLength)
146 # round again, looking for more to do
153 network = ipnetwork(cfg.get('virtual','network'))
154 if network.num_addresses < 3 + 2:
155 raise ValueError('network needs at least 2^3 addresses')
158 host = cfg.get('virtual','host')
159 except NoOptionError:
160 host = network.hosts().next()
163 relay = cfg.get('virtual','relay')
165 for search in network.hosts():
166 if search = host: continue
170 for cs in cfg.sections():
171 if not (':' in cs or '.' in cs): continue
173 if ci not in network:
174 raise ValueError('client %s not in network' % ci)
176 raise ValueError('multiple client cfg sections for %s' % ci)
177 clients[ci] = Client(ci, cs)
179 class FormPage(Resource):
180 def render_POST(self, request):
181 # find client, update config, etc.
182 ci = ipaddress(request.args['i'])
184 pw = request.args['pw']
185 if pw != c.pw: raise ValueError('bad password')
188 for r, w in (('mbd', 'max_batch_down'),
189 ('mqt', 'max_queue_time'),
190 ('mrt', 'max_request_time')):
191 try: v = request.args[r]
192 except KeyError: continue
196 try: d = request.args['d']
197 except KeyError: d = ''
199 c.process_arriving_data(d)
200 c.new_request(request)
204 op.add_option('-c', '--config', dest='configfile',
205 default='/etc/hippottd/server.conf')
207 (opts, args) = op.parse_args()
208 if len(args): op.error('no non-option arguments please')