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
):
92 packets
= slip_decode(buffer)
93 buffer = packets
.pop()
94 for packet
in packets
:
95 (saddr
, daddr
) = packet_addrs(packet
)
97 def processEnded(self
, status
):
98 status
.raiseException()
102 ipif
= IpifProcessProtocol()
103 reactor
.spawnProcess(ipif
,
104 '/bin/sh',['sh','-c', ipif_command
],
105 childFDs
={0:'w', 1:'r', 2:2})
107 def queue_inbound(packet
):
108 ipif
.transport
.write(slip_delimiter
)
109 ipif
.transport
.write(slip_encode(packet
))
110 ipif
.transport
.write(slip_delimiter
)
112 #---------- client ----------
115 def __init__(self
, ip
, cs
):
116 # instance data members
119 self
.pw
= cfg
.get(cs
, 'password')
120 self
._rq
= collections
.deque() # requests
121 self
._pq
= collections
.deque() # packets
126 for k
in ('max_batch_down','max_queue_time','max_request_time'):
127 req
= cfg
.getint(cs
, k
)
128 limit
= cfg
.getint('limits',k
)
129 self
.__dict__
[k
] = min(req
, limit
)
131 def process_arriving_data(self
, d
):
132 for packet
in slip_decode(d
):
133 (saddr
, daddr
) = packet_addrs(packet
)
134 if saddr
!= self
._ip
:
135 raise ValueError('wrong source address %s' % saddr
)
138 def _req_cancel(self
, request
):
141 def _req_error(self
, err
, request
):
142 self
._req_cancel(request
)
144 def queue_outbound(self
, packet
):
145 self
._pq
.append((time
.monotonic(), packet
))
147 def http_request(self
, request
):
148 request
.setHeader('Content-Type','application/octet-stream')
149 reactor
.callLater(self
.max_request_time
, self
._req_cancel
, request
)
150 request
.notifyFinish().addErrback(self
._req_error
, request
)
151 self
._rq
.append(request
)
152 self
._check_outbound()
154 def _check_outbound(self
):
156 try: request
= self
._rq
[0]
157 except IndexError: request
= None
158 if request
and request
.finished
:
162 # now request is an unfinished request, or None
163 try: (queuetime
, packet
) = self
._pq
[0]
165 # no packets, oh well
168 age
= time
.monotonic() - queuetime
169 if age
> self
.max_queue_time
:
177 # request, and also some non-expired packets
179 try: (dummy
, packet
) = self
._pq
[0]
180 except IndexError: break
182 encoded
= slip_encode(packet
)
184 if request
.sentLength
> 0:
185 if (request
.sentLength
+ len(slip_delimiter
)
186 + len(encoded
) > self
.max_batch_down
):
188 request
.write(slip_delimiter
)
190 request
.write(encoded
)
193 assert(request
.sentLength
)
196 # round again, looking for more to do
198 class IphttpResource(twisted
.web
.resource
.Resource
):
199 def render_POST(self
, request
):
200 # find client, update config, etc.
201 ci
= ipaddr(request
.args
['i'])
203 pw
= request
.args
['pw']
204 if pw
!= c
.pw
: raise ValueError('bad password')
207 for r
, w
in (('mbd', 'max_batch_down'),
208 ('mqt', 'max_queue_time'),
209 ('mrt', 'max_request_time')):
210 try: v
= request
.args
[r
]
211 except KeyError: continue
215 try: d
= request
.args
['d']
216 except KeyError: d
= ''
218 c
.process_arriving_data(d
)
219 c
.new_request(request
)
222 resource
= IphttpResource()
223 sitefactory
= twisted
.web
.server
.Site(resource
)
224 for addrspec
in cfg
.get('server','addrs').split():
226 addr
= ipaddress
.IPv4Address(addrspec
)
227 endpointfactory
= twisted
.internet
.endpoints
.TCP4ServerEndpoint
228 except AddressValueError
:
229 addr
= ipaddress
.IPv6Address(addrspec
)
230 endpointfactory
= twisted
.internet
.endpoints
.TCP6ServerEndpoint
231 ep
= endpointfactory(reactor
, cfg
.getint('server','port'), addr
)
232 ep
.listen(sitefactory
)
234 #---------- config and setup ----------
242 network
= ipnetwork(cfg
.get('virtual','network'))
243 if network
.num_addresses
< 3 + 2:
244 raise ValueError('network needs at least 2^3 addresses')
247 host
= cfg
.get('virtual','host')
248 except NoOptionError
:
249 host
= next(network
.hosts())
252 relay
= cfg
.get('virtual','relay')
253 except NoOptionError
:
254 for search
in network
.hosts():
255 if search
== host
: continue
259 for cs
in cfg
.sections():
260 if not (':' in cs
or '.' in cs
): continue
262 if ci
not in network
:
263 raise ValueError('client %s not in network' % ci
)
265 raise ValueError('multiple client cfg sections for %s' % ci
)
266 clients
[ci
] = Client(ci
, cs
)
269 mtu
= cfg
.get('virtual','mtu')
272 for k
in ('host','relay','mtu','network'):
273 iic_vars
[k
] = globals()[k
]
275 ipif_command
= cfg
.get('server','ipif', vars=iic_vars
)
277 def crash_on_critical(event
):
278 if event
.get('log_level') >= LogLevel
.critical
:
279 print('crashing: ', twisted
.logger
.formatEvent(event
), file=sys
.stderr
)
280 #print('crashing!', file=sys.stderr)
283 except twisted
.internet
.error
.ReactorNotRunning
: pass
289 op
.add_option('-c', '--config', dest
='configfile',
290 default
='/etc/hippottd/server.conf')
292 (opts
, args
) = op
.parse_args()
293 if len(args
): op
.error('no non-option arguments please')
295 twisted
.logger
.globalLogPublisher
.addObserver(crash_on_critical
)
298 cfg
.read_string(defcfg
)
299 cfg
.read(opts
.configfile
)