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
):
276 def render_POST(self
, request
):
277 # find client, update config, etc.
278 ci
= ipaddr(request
.args
['i'])
280 pw
= request
.args
['pw']
281 if pw
!= c
.pw
: raise ValueError('bad password')
284 for r
, w
in (('mbd', 'max_batch_down'),
285 ('mqt', 'max_queue_time'),
286 ('mrt', 'max_request_time')):
287 try: v
= request
.args
[r
]
288 except KeyError: continue
292 try: d
= request
.args
['d']
293 except KeyError: d
= ''
295 c
.process_arriving_data(d
)
296 c
.new_request(request
)
298 def render_GET(self
, request
):
299 return '<html><body>hippotit</body></html>'
302 resource
= IphttpResource()
303 sitefactory
= twisted
.web
.server
.Site(resource
)
304 for addrspec
in cfg
.get('server','addrs').split():
306 addr
= ipaddress
.IPv4Address(addrspec
)
307 endpointfactory
= twisted
.internet
.endpoints
.TCP4ServerEndpoint
308 except AddressValueError
:
309 addr
= ipaddress
.IPv6Address(addrspec
)
310 endpointfactory
= twisted
.internet
.endpoints
.TCP6ServerEndpoint
311 ep
= endpointfactory(reactor
, cfg
.getint('server','port'), addr
)
312 crash_on_defer(ep
.listen(sitefactory
))
314 #---------- config and setup ----------
322 network
= ipnetwork(cfg
.get('virtual','network'))
323 if network
.num_addresses
< 3 + 2:
324 raise ValueError('network needs at least 2^3 addresses')
327 host
= cfg
.get('virtual','host')
328 except NoOptionError
:
329 host
= next(network
.hosts())
332 relay
= cfg
.get('virtual','relay')
333 except NoOptionError
:
334 for search
in network
.hosts():
335 if search
== host
: continue
339 for cs
in cfg
.sections():
340 if not (':' in cs
or '.' in cs
): continue
342 if ci
not in network
:
343 raise ValueError('client %s not in network' % ci
)
345 raise ValueError('multiple client cfg sections for %s' % ci
)
346 clients
[ci
] = Client(ci
, cs
)
349 mtu
= cfg
.get('virtual','mtu')
352 for k
in ('host','relay','mtu','network'):
353 iic_vars
[k
] = globals()[k
]
355 ipif_command
= cfg
.get('server','ipif', vars=iic_vars
)
361 op
.add_option('-c', '--config', dest
='configfile',
362 default
='/etc/hippottd/server.conf')
364 (opts
, args
) = op
.parse_args()
365 if len(args
): op
.error('no non-option arguments please')
367 twisted
.logger
.globalLogPublisher
.addObserver(crash_on_critical
)
370 cfg
.read_string(defcfg
)
371 cfg
.read(opts
.configfile
)
379 print('CRASHED (end)', file=sys
.stderr
)