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
, daddr
):
69 try: client
= clients
[daddr
]
70 except KeyError: dclient
= None
71 if dclient
is not None:
72 dclient
.queue_outbound(packet
)
73 elif daddr
== host
or daddr
not in network
:
76 log_discard(packet
, saddr
, daddr
, 'relay')
78 log_discard(packet
, saddr
, daddr
, 'no client')
80 def log_discard(packet
, saddr
, daddr
, why
):
81 syslog
.syslog(syslog
.LOG_DEBUG
,
82 'discarded packet %s -> %s (%s)' %
(saddr
, daddr
, why
))
84 #---------- ipif (slip subprocess) ----------
86 class IpifProcessProtocol(twisted
.internet
.protocol
.ProcessProtocol
):
89 def connectionMade(self
): pass
90 def outReceived(self
, data
):
91 #print('RECV ', repr(data))
93 packets
= slip_decode(self
._buffer
)
94 self
._buffer
= packets
.pop()
95 for packet
in packets
:
96 (saddr
, daddr
) = packet_addrs(packet
)
98 def processEnded(self
, status
):
99 status
.raiseException()
103 ipif
= IpifProcessProtocol()
104 reactor
.spawnProcess(ipif
,
105 '/bin/sh',['sh','-xc', ipif_command
],
106 childFDs
={0:'w', 1:'r', 2:2})
108 def queue_inbound(packet
):
109 ipif
.transport
.write(slip_delimiter
)
110 ipif
.transport
.write(slip_encode(packet
))
111 ipif
.transport
.write(slip_delimiter
)
113 #---------- SLIP handling ----------
117 slip_esc_end
= b
'\334'
118 slip_esc_esc
= b
'\335'
119 slip_delimiter
= slip_end
121 def slip_encode(packet
):
123 .replace(slip_esc
, slip_esc
+ slip_esc_esc
)
124 .replace(slip_end
, slip_esc
+ slip_esc_end
))
126 def slip_decode(data
):
127 print('DECODE ', repr(data
))
129 for packet
in data
.split(slip_end
):
132 eix
= packet
.find(slip_esc
)
136 #print('ESC ', repr((pdata, packet, eix)))
137 pdata
+= packet
[0 : eix
]
139 if ck
== slip_esc_esc
: pdata
+= slip_esc
140 elif ck
== slip_esc_end
: pdata
+= slip_end
141 else: raise ValueError('invalid SLIP escape')
142 packet
= packet
[eix
+2 : ]
144 print('DECODED ', repr(out
))
147 #---------- packet parsing ----------
149 def packet_addrs(packet
):
152 #---------- client ----------
155 def __init__(self
, ip
, cs
):
156 # instance data members
159 self
.pw
= cfg
.get(cs
, 'password')
160 self
._rq
= collections
.deque() # requests
161 self
._pq
= collections
.deque() # packets
166 for k
in ('max_batch_down','max_queue_time','max_request_time'):
167 req
= cfg
.getint(cs
, k
)
168 limit
= cfg
.getint('limits',k
)
169 self
.__dict__
[k
] = min(req
, limit
)
171 def process_arriving_data(self
, d
):
172 for packet
in slip_decode(d
):
173 (saddr
, daddr
) = packet_addrs(packet
)
174 if saddr
!= self
._ip
:
175 raise ValueError('wrong source address %s' % saddr
)
178 def _req_cancel(self
, request
):
181 def _req_error(self
, err
, request
):
182 self
._req_cancel(request
)
184 def queue_outbound(self
, packet
):
185 self
._pq
.append((time
.monotonic(), packet
))
187 def http_request(self
, request
):
188 request
.setHeader('Content-Type','application/octet-stream')
189 reactor
.callLater(self
.max_request_time
, self
._req_cancel
, request
)
190 request
.notifyFinish().addErrback(self
._req_error
, request
)
191 self
._rq
.append(request
)
192 self
._check_outbound()
194 def _check_outbound(self
):
196 try: request
= self
._rq
[0]
197 except IndexError: request
= None
198 if request
and request
.finished
:
202 # now request is an unfinished request, or None
203 try: (queuetime
, packet
) = self
._pq
[0]
205 # no packets, oh well
208 age
= time
.monotonic() - queuetime
209 if age
> self
.max_queue_time
:
217 # request, and also some non-expired packets
219 try: (dummy
, packet
) = self
._pq
[0]
220 except IndexError: break
222 encoded
= slip_encode(packet
)
224 if request
.sentLength
> 0:
225 if (request
.sentLength
+ len(slip_delimiter
)
226 + len(encoded
) > self
.max_batch_down
):
228 request
.write(slip_delimiter
)
230 request
.write(encoded
)
233 assert(request
.sentLength
)
236 # round again, looking for more to do
238 class IphttpResource(twisted
.web
.resource
.Resource
):
239 def render_POST(self
, request
):
240 # find client, update config, etc.
241 ci
= ipaddr(request
.args
['i'])
243 pw
= request
.args
['pw']
244 if pw
!= c
.pw
: raise ValueError('bad password')
247 for r
, w
in (('mbd', 'max_batch_down'),
248 ('mqt', 'max_queue_time'),
249 ('mrt', 'max_request_time')):
250 try: v
= request
.args
[r
]
251 except KeyError: continue
255 try: d
= request
.args
['d']
256 except KeyError: d
= ''
258 c
.process_arriving_data(d
)
259 c
.new_request(request
)
262 resource
= IphttpResource()
263 sitefactory
= twisted
.web
.server
.Site(resource
)
264 for addrspec
in cfg
.get('server','addrs').split():
266 addr
= ipaddress
.IPv4Address(addrspec
)
267 endpointfactory
= twisted
.internet
.endpoints
.TCP4ServerEndpoint
268 except AddressValueError
:
269 addr
= ipaddress
.IPv6Address(addrspec
)
270 endpointfactory
= twisted
.internet
.endpoints
.TCP6ServerEndpoint
271 ep
= endpointfactory(reactor
, cfg
.getint('server','port'), addr
)
272 ep
.listen(sitefactory
)
274 #---------- config and setup ----------
282 network
= ipnetwork(cfg
.get('virtual','network'))
283 if network
.num_addresses
< 3 + 2:
284 raise ValueError('network needs at least 2^3 addresses')
287 host
= cfg
.get('virtual','host')
288 except NoOptionError
:
289 host
= next(network
.hosts())
292 relay
= cfg
.get('virtual','relay')
293 except NoOptionError
:
294 for search
in network
.hosts():
295 if search
== host
: continue
299 for cs
in cfg
.sections():
300 if not (':' in cs
or '.' in cs
): continue
302 if ci
not in network
:
303 raise ValueError('client %s not in network' % ci
)
305 raise ValueError('multiple client cfg sections for %s' % ci
)
306 clients
[ci
] = Client(ci
, cs
)
309 mtu
= cfg
.get('virtual','mtu')
312 for k
in ('host','relay','mtu','network'):
313 iic_vars
[k
] = globals()[k
]
315 ipif_command
= cfg
.get('server','ipif', vars=iic_vars
)
317 def crash_on_critical(event
):
318 if event
.get('log_level') >= LogLevel
.critical
:
319 print('crashing: ', twisted
.logger
.formatEvent(event
), file=sys
.stderr
)
320 #print('crashing!', file=sys.stderr)
323 except twisted
.internet
.error
.ReactorNotRunning
: pass
329 op
.add_option('-c', '--config', dest
='configfile',
330 default
='/etc/hippottd/server.conf')
332 (opts
, args
) = op
.parse_args()
333 if len(args
): op
.error('no non-option arguments please')
335 twisted
.logger
.globalLogPublisher
.addObserver(crash_on_critical
)
338 cfg
.read_string(defcfg
)
339 cfg
.read(opts
.configfile
)