7 import twisted
.internet
8 import twisted
.internet
.endpoints
9 from twisted
.internet
import reactor
10 from twisted
.web
.server
import NOT_DONE_YET
11 from twisted
.logger
import LogLevel
14 from ipaddress
import AddressValueError
16 #import twisted.web.server import Site
17 #from twisted.web.resource import Resource
19 from optparse
import OptionParser
20 from configparser
import ConfigParser
21 from configparser
import NoOptionError
31 r
= ipaddress
.IPv4Address(input)
32 except AddressValueError
:
33 r
= ipaddress
.IPv6Address(input)
38 r
= ipaddress
.IPv4Network(input)
39 except NetworkValueError
:
40 r
= ipaddress
.IPv6Network(input)
45 max_batch_down = 65536
56 ipif = userv root ipif %(host)s,%(relay)s,%(mtu)s,slip %(network)s
61 max_batch_down = 262144
63 max_request_time = 121
66 #---------- "router" ----------
68 def route(packet
, saddr
, daddr
):
69 print('TRACE ', saddr
, daddr
, packet
)
70 try: client
= clients
[daddr
]
71 except KeyError: dclient
= None
72 if dclient
is not None:
73 dclient
.queue_outbound(packet
)
74 elif daddr
.is_multicast
:
75 log_discard(packet
, saddr
, daddr
, 'multicast')
76 elif daddr
.is_link_local
:
77 log_discard(packet
, saddr
, daddr
, 'link-local')
78 elif daddr
== host
or daddr
not in network
:
79 print('TRACE INBOUND ', saddr
, daddr
, packet
)
82 log_discard(packet
, saddr
, daddr
, 'relay')
84 log_discard(packet
, saddr
, daddr
, 'no client')
86 def log_discard(packet
, saddr
, daddr
, why
):
87 print('DROP ', saddr
, daddr
, why
, packet
)
88 # syslog.syslog(syslog.LOG_DEBUG,
89 # 'discarded packet %s -> %s (%s)' % (saddr, daddr, why))
91 #---------- ipif (slip subprocess) ----------
93 class IpifProcessProtocol(twisted
.internet
.protocol
.ProcessProtocol
):
96 def connectionMade(self
): pass
97 def outReceived(self
, data
):
98 #print('RECV ', repr(data))
100 packets
= slip_decode(self
._buffer
)
101 self
._buffer
= packets
.pop()
102 for packet
in packets
:
103 if not len(packet
): continue
104 (saddr
, daddr
) = packet_addrs(packet
)
105 route(packet
, saddr
, daddr
)
106 def processEnded(self
, status
):
107 status
.raiseException()
111 ipif
= IpifProcessProtocol()
112 reactor
.spawnProcess(ipif
,
113 '/bin/sh',['sh','-xc', ipif_command
],
114 childFDs
={0:'w', 1:'r', 2:2})
116 def queue_inbound(packet
):
117 ipif
.transport
.write(slip_delimiter
)
118 ipif
.transport
.write(slip_encode(packet
))
119 ipif
.transport
.write(slip_delimiter
)
121 #---------- SLIP handling ----------
125 slip_esc_end
= b
'\334'
126 slip_esc_esc
= b
'\335'
127 slip_delimiter
= slip_end
129 def slip_encode(packet
):
131 .replace(slip_esc
, slip_esc
+ slip_esc_esc
)
132 .replace(slip_end
, slip_esc
+ slip_esc_end
))
134 def slip_decode(data
):
135 print('DECODE ', repr(data
))
137 for packet
in data
.split(slip_end
):
140 eix
= packet
.find(slip_esc
)
144 #print('ESC ', repr((pdata, packet, eix)))
145 pdata
+= packet
[0 : eix
]
147 if ck
== slip_esc_esc
: pdata
+= slip_esc
148 elif ck
== slip_esc_end
: pdata
+= slip_end
149 else: raise ValueError('invalid SLIP escape')
150 packet
= packet
[eix
+2 : ]
152 print('DECODED ', repr(out
))
155 #---------- packet parsing ----------
157 def packet_addrs(packet
):
158 version
= packet
[0] >> 4
162 factory
= ipaddress
.IPv4Address
166 factory
= ipaddress
.IPv6Address
168 raise ValueError('unsupported IP version %d' % version
)
169 saddr
= factory(packet
[ saddroff
: saddroff
+ addrlen
])
170 daddr
= factory(packet
[ saddroff
+ addrlen
: saddroff
+ addrlen
*2 ])
171 return (saddr
, daddr
)
173 #---------- client ----------
176 def __init__(self
, ip
, cs
):
177 # instance data members
180 self
.pw
= cfg
.get(cs
, 'password')
181 self
._rq
= collections
.deque() # requests
182 self
._pq
= collections
.deque() # packets
187 for k
in ('max_batch_down','max_queue_time','max_request_time'):
188 req
= cfg
.getint(cs
, k
)
189 limit
= cfg
.getint('limits',k
)
190 self
.__dict__
[k
] = min(req
, limit
)
192 def process_arriving_data(self
, d
):
193 for packet
in slip_decode(d
):
194 (saddr
, daddr
) = packet_addrs(packet
)
195 if saddr
!= self
._ip
:
196 raise ValueError('wrong source address %s' % saddr
)
197 route(packet
, saddr
, daddr
)
199 def _req_cancel(self
, request
):
202 def _req_error(self
, err
, request
):
203 self
._req_cancel(request
)
205 def queue_outbound(self
, packet
):
206 self
._pq
.append((time
.monotonic(), packet
))
208 def http_request(self
, request
):
209 request
.setHeader('Content-Type','application/octet-stream')
210 reactor
.callLater(self
.max_request_time
, self
._req_cancel
, request
)
211 request
.notifyFinish().addErrback(self
._req_error
, request
)
212 self
._rq
.append(request
)
213 self
._check_outbound()
215 def _check_outbound(self
):
217 try: request
= self
._rq
[0]
218 except IndexError: request
= None
219 if request
and request
.finished
:
223 # now request is an unfinished request, or None
224 try: (queuetime
, packet
) = self
._pq
[0]
226 # no packets, oh well
229 age
= time
.monotonic() - queuetime
230 if age
> self
.max_queue_time
:
238 # request, and also some non-expired packets
240 try: (dummy
, packet
) = self
._pq
[0]
241 except IndexError: break
243 encoded
= slip_encode(packet
)
245 if request
.sentLength
> 0:
246 if (request
.sentLength
+ len(slip_delimiter
)
247 + len(encoded
) > self
.max_batch_down
):
249 request
.write(slip_delimiter
)
251 request
.write(encoded
)
254 assert(request
.sentLength
)
257 # round again, looking for more to do
259 class IphttpResource(twisted
.web
.resource
.Resource
):
260 def render_POST(self
, request
):
261 # find client, update config, etc.
262 ci
= ipaddr(request
.args
['i'])
264 pw
= request
.args
['pw']
265 if pw
!= c
.pw
: raise ValueError('bad password')
268 for r
, w
in (('mbd', 'max_batch_down'),
269 ('mqt', 'max_queue_time'),
270 ('mrt', 'max_request_time')):
271 try: v
= request
.args
[r
]
272 except KeyError: continue
276 try: d
= request
.args
['d']
277 except KeyError: d
= ''
279 c
.process_arriving_data(d
)
280 c
.new_request(request
)
283 resource
= IphttpResource()
284 sitefactory
= twisted
.web
.server
.Site(resource
)
285 for addrspec
in cfg
.get('server','addrs').split():
287 addr
= ipaddress
.IPv4Address(addrspec
)
288 endpointfactory
= twisted
.internet
.endpoints
.TCP4ServerEndpoint
289 except AddressValueError
:
290 addr
= ipaddress
.IPv6Address(addrspec
)
291 endpointfactory
= twisted
.internet
.endpoints
.TCP6ServerEndpoint
292 ep
= endpointfactory(reactor
, cfg
.getint('server','port'), addr
)
293 ep
.listen(sitefactory
)
295 #---------- config and setup ----------
303 network
= ipnetwork(cfg
.get('virtual','network'))
304 if network
.num_addresses
< 3 + 2:
305 raise ValueError('network needs at least 2^3 addresses')
308 host
= cfg
.get('virtual','host')
309 except NoOptionError
:
310 host
= next(network
.hosts())
313 relay
= cfg
.get('virtual','relay')
314 except NoOptionError
:
315 for search
in network
.hosts():
316 if search
== host
: continue
320 for cs
in cfg
.sections():
321 if not (':' in cs
or '.' in cs
): continue
323 if ci
not in network
:
324 raise ValueError('client %s not in network' % ci
)
326 raise ValueError('multiple client cfg sections for %s' % ci
)
327 clients
[ci
] = Client(ci
, cs
)
330 mtu
= cfg
.get('virtual','mtu')
333 for k
in ('host','relay','mtu','network'):
334 iic_vars
[k
] = globals()[k
]
336 ipif_command
= cfg
.get('server','ipif', vars=iic_vars
)
338 def crash_on_critical(event
):
339 if event
.get('log_level') >= LogLevel
.critical
:
340 print('crashing: ', twisted
.logger
.formatEvent(event
), file=sys
.stderr
)
341 #print('crashing!', file=sys.stderr)
344 except twisted
.internet
.error
.ReactorNotRunning
: pass
350 op
.add_option('-c', '--config', dest
='configfile',
351 default
='/etc/hippottd/server.conf')
353 (opts
, args
) = op
.parse_args()
354 if len(args
): op
.error('no non-option arguments please')
356 twisted
.logger
.globalLogPublisher
.addObserver(crash_on_critical
)
359 cfg
.read_string(defcfg
)
360 cfg
.read(opts
.configfile
)