4 signal
.signal(signal
.SIGINT
, signal
.SIG_DFL
)
10 import twisted
.internet
11 import twisted
.internet
.endpoints
12 from twisted
.internet
import reactor
13 from twisted
.web
.server
import NOT_DONE_YET
14 from twisted
.logger
import LogLevel
17 from ipaddress
import AddressValueError
19 #import twisted.web.server import Site
20 #from twisted.web.resource import Resource
22 from optparse
import OptionParser
23 from configparser
import ConfigParser
24 from configparser
import NoOptionError
34 r
= ipaddress
.IPv4Address(input)
35 except AddressValueError
:
36 r
= ipaddress
.IPv6Address(input)
41 r
= ipaddress
.IPv4Network(input)
42 except NetworkValueError
:
43 r
= ipaddress
.IPv6Network(input)
48 max_batch_down = 65536
59 ipif = userv root ipif %(host)s,%(relay)s,%(mtu)s,slip %(network)s
64 max_batch_down = 262144
66 max_request_time = 121
69 #---------- error handling ----------
72 print('CRASH ', err
, file=sys
.stderr
)
74 except twisted
.internet
.error
.ReactorNotRunning
: pass
76 def crash_on_defer(defer
):
77 defer
.addErrback(lambda err
: crash(err
))
79 def crash_on_critical(event
):
80 if event
.get('log_level') >= LogLevel
.critical
:
81 crash(twisted
.logger
.formatEvent(event
))
83 #---------- "router" ----------
85 def route(packet
, saddr
, daddr
):
86 print('TRACE ', saddr
, daddr
, packet
)
87 try: client
= clients
[daddr
]
88 except KeyError: dclient
= None
89 if dclient
is not None:
90 dclient
.queue_outbound(packet
)
91 elif saddr
.is_link_local
or daddr
.is_link_local
:
92 log_discard(packet
, saddr
, daddr
, 'link-local')
93 elif daddr
== host
or daddr
not in network
:
94 print('TRACE INBOUND ', saddr
, daddr
, packet
)
97 log_discard(packet
, saddr
, daddr
, 'relay')
99 log_discard(packet
, saddr
, daddr
, 'no client')
101 def log_discard(packet
, saddr
, daddr
, why
):
102 print('DROP ', saddr
, daddr
, why
)
103 # syslog.syslog(syslog.LOG_DEBUG,
104 # 'discarded packet %s -> %s (%s)' % (saddr, daddr, why))
106 #---------- ipif (slip subprocess) ----------
108 class IpifProcessProtocol(twisted
.internet
.protocol
.ProcessProtocol
):
111 def connectionMade(self
): pass
112 def outReceived(self
, data
):
113 #print('RECV ', repr(data))
115 packets
= slip_decode(self
._buffer
)
116 self
._buffer
= packets
.pop()
117 for packet
in packets
:
118 if not len(packet
): continue
119 (saddr
, daddr
) = packet_addrs(packet
)
120 route(packet
, saddr
, daddr
)
121 def processEnded(self
, status
):
122 status
.raiseException()
126 ipif
= IpifProcessProtocol()
127 reactor
.spawnProcess(ipif
,
128 '/bin/sh',['sh','-xc', ipif_command
],
129 childFDs
={0:'w', 1:'r', 2:2})
131 def queue_inbound(packet
):
132 ipif
.transport
.write(slip_delimiter
)
133 ipif
.transport
.write(slip_encode(packet
))
134 ipif
.transport
.write(slip_delimiter
)
136 #---------- SLIP handling ----------
140 slip_esc_end
= b
'\334'
141 slip_esc_esc
= b
'\335'
142 slip_delimiter
= slip_end
144 def slip_encode(packet
):
146 .replace(slip_esc
, slip_esc
+ slip_esc_esc
)
147 .replace(slip_end
, slip_esc
+ slip_esc_end
))
149 def slip_decode(data
):
150 print('DECODE ', repr(data
))
152 for packet
in data
.split(slip_end
):
155 eix
= packet
.find(slip_esc
)
159 #print('ESC ', repr((pdata, packet, eix)))
160 pdata
+= packet
[0 : eix
]
162 #print('ESC... %o' % ck)
163 if ck
== slip_esc_esc
[0]: pdata
+= slip_esc
164 elif ck
== slip_esc_end
[0]: pdata
+= slip_end
165 else: raise ValueError('invalid SLIP escape')
166 packet
= packet
[eix
+2 : ]
168 print('DECODED ', repr(out
))
171 #---------- packet parsing ----------
173 def packet_addrs(packet
):
174 version
= packet
[0] >> 4
178 factory
= ipaddress
.IPv4Address
182 factory
= ipaddress
.IPv6Address
184 raise ValueError('unsupported IP version %d' % version
)
185 saddr
= factory(packet
[ saddroff
: saddroff
+ addrlen
])
186 daddr
= factory(packet
[ saddroff
+ addrlen
: saddroff
+ addrlen
*2 ])
187 return (saddr
, daddr
)
189 #---------- client ----------
192 def __init__(self
, ip
, cs
):
193 # instance data members
196 self
.pw
= cfg
.get(cs
, 'password')
197 self
._rq
= collections
.deque() # requests
198 self
._pq
= collections
.deque() # packets
203 for k
in ('max_batch_down','max_queue_time','max_request_time'):
204 req
= cfg
.getint(cs
, k
)
205 limit
= cfg
.getint('limits',k
)
206 self
.__dict__
[k
] = min(req
, limit
)
208 def process_arriving_data(self
, d
):
209 for packet
in slip_decode(d
):
210 (saddr
, daddr
) = packet_addrs(packet
)
211 if saddr
!= self
._ip
:
212 raise ValueError('wrong source address %s' % saddr
)
213 route(packet
, saddr
, daddr
)
215 def _req_cancel(self
, request
):
218 def _req_error(self
, err
, request
):
219 self
._req_cancel(request
)
221 def queue_outbound(self
, packet
):
222 self
._pq
.append((time
.monotonic(), packet
))
224 def http_request(self
, request
):
225 request
.setHeader('Content-Type','application/octet-stream')
226 reactor
.callLater(self
.max_request_time
, self
._req_cancel
, request
)
227 request
.notifyFinish().addErrback(self
._req_error
, request
)
228 self
._rq
.append(request
)
229 self
._check_outbound()
231 def _check_outbound(self
):
233 try: request
= self
._rq
[0]
234 except IndexError: request
= None
235 if request
and request
.finished
:
239 # now request is an unfinished request, or None
240 try: (queuetime
, packet
) = self
._pq
[0]
242 # no packets, oh well
245 age
= time
.monotonic() - queuetime
246 if age
> self
.max_queue_time
:
254 # request, and also some non-expired packets
256 try: (dummy
, packet
) = self
._pq
[0]
257 except IndexError: break
259 encoded
= slip_encode(packet
)
261 if request
.sentLength
> 0:
262 if (request
.sentLength
+ len(slip_delimiter
)
263 + len(encoded
) > self
.max_batch_down
):
265 request
.write(slip_delimiter
)
267 request
.write(encoded
)
270 assert(request
.sentLength
)
273 # round again, looking for more to do
275 class IphttpResource(twisted
.web
.resource
.Resource
):
277 def render_POST(self
, request
):
278 # find client, update config, etc.
279 ci
= ipaddr(request
.args
['i'])
281 pw
= request
.args
['pw']
282 if pw
!= c
.pw
: raise ValueError('bad password')
285 for r
, w
in (('mbd', 'max_batch_down'),
286 ('mqt', 'max_queue_time'),
287 ('mrt', 'max_request_time')):
288 try: v
= request
.args
[r
]
289 except KeyError: continue
293 try: d
= request
.args
['d']
294 except KeyError: d
= ''
296 c
.process_arriving_data(d
)
297 c
.new_request(request
)
299 def render_GET(self
, request
):
300 return b
'<html><body>hippotit</body></html>'
303 resource
= IphttpResource()
304 site
= twisted
.web
.server
.Site(resource
)
305 for addrspec
in cfg
.get('server','addrs').split():
307 addr
= ipaddress
.IPv4Address(addrspec
)
308 endpointfactory
= twisted
.internet
.endpoints
.TCP4ServerEndpoint
309 except AddressValueError
:
310 addr
= ipaddress
.IPv6Address(addrspec
)
311 endpointfactory
= twisted
.internet
.endpoints
.TCP6ServerEndpoint
312 ep
= endpointfactory(reactor
, cfg
.getint('server','port'), addr
)
313 crash_on_defer(ep
.listen(site
))
315 #---------- config and setup ----------
323 network
= ipnetwork(cfg
.get('virtual','network'))
324 if network
.num_addresses
< 3 + 2:
325 raise ValueError('network needs at least 2^3 addresses')
328 host
= cfg
.get('virtual','host')
329 except NoOptionError
:
330 host
= next(network
.hosts())
333 relay
= cfg
.get('virtual','relay')
334 except NoOptionError
:
335 for search
in network
.hosts():
336 if search
== host
: continue
340 for cs
in cfg
.sections():
341 if not (':' in cs
or '.' in cs
): continue
343 if ci
not in network
:
344 raise ValueError('client %s not in network' % ci
)
346 raise ValueError('multiple client cfg sections for %s' % ci
)
347 clients
[ci
] = Client(ci
, cs
)
350 mtu
= cfg
.get('virtual','mtu')
353 for k
in ('host','relay','mtu','network'):
354 iic_vars
[k
] = globals()[k
]
356 ipif_command
= cfg
.get('server','ipif', vars=iic_vars
)
362 op
.add_option('-c', '--config', dest
='configfile',
363 default
='/etc/hippottd/server.conf')
365 (opts
, args
) = op
.parse_args()
366 if len(args
): op
.error('no non-option arguments please')
368 twisted
.logger
.globalLogPublisher
.addObserver(crash_on_critical
)
371 cfg
.read_string(defcfg
)
372 cfg
.read(opts
.configfile
)
380 print('CRASHED (end)', file=sys
.stderr
)