4 signal.signal(signal.SIGINT, signal.SIG_DFL)
7 from twisted.internet import reactor
8 from twisted.logger import LogLevel
11 from ipaddress import AddressValueError
13 import hippotat.slip as slip
15 from optparse import OptionParser
16 from configparser import ConfigParser
17 from configparser import NoOptionError
22 optparser = OptionParser()
24 #---------- packet parsing ----------
26 def packet_addrs(packet):
27 version = packet[0] >> 4
31 factory = ipaddress.IPv4Address
35 factory = ipaddress.IPv6Address
37 raise ValueError('unsupported IP version %d' % version)
38 saddr = factory(packet[ saddroff : saddroff + addrlen ])
39 daddr = factory(packet[ saddroff + addrlen : saddroff + addrlen*2 ])
42 #---------- address handling ----------
46 r = ipaddress.IPv4Address(input)
47 except AddressValueError:
48 r = ipaddress.IPv6Address(input)
53 r = ipaddress.IPv4Network(input)
54 except NetworkValueError:
55 r = ipaddress.IPv6Network(input)
58 #---------- ipif (SLIP) subprocess ----------
60 class _IpifProcessProtocol(twisted.internet.protocol.ProcessProtocol):
61 def __init__(self, router):
64 def connectionMade(self): pass
65 def outReceived(self, data):
66 #print('RECV ', repr(data))
68 packets = slip.decode(self._buffer)
69 self._buffer = packets.pop()
70 for packet in packets:
71 if not len(packet): continue
72 (saddr, daddr) = packet_addrs(packet)
73 self._router(packet, saddr, daddr)
74 def processEnded(self, status):
75 status.raiseException()
77 def start_ipif(command, router):
79 ipif = _IpifProcessProtocol(router)
80 reactor.spawnProcess(ipif,
81 '/bin/sh',['sh','-xc', command],
82 childFDs={0:'w', 1:'r', 2:2})
84 def queue_inbound(packet):
85 ipif.transport.write(slip.delimiter)
86 ipif.transport.write(slip.encode(packet))
87 ipif.transport.write(slip.delimiter)
89 #---------- packet queue ----------
92 def __init__(self, max_queue_time):
93 self._max_queue_time = max_queue_time
94 self._pq = collections.deque() # packets
96 def append(self, packet):
97 self._pq.append((time.monotonic(), packet))
101 try: (queuetime, packet) = self._pq[0]
102 except IndexError: return False
104 age = time.monotonic() - queuetime
105 if age > self.max_queue_time:
106 # strip old packets off the front
113 # caller must have checked nonempty
114 try: (dummy, packet) = self._pq[0]
115 except IndexError: return None
118 #---------- error handling ----------
121 print('CRASH ', err, file=sys.stderr)
123 except twisted.internet.error.ReactorNotRunning: pass
125 def crash_on_defer(defer):
126 defer.addErrback(lambda err: crash(err))
128 def crash_on_critical(event):
129 if event.get('log_level') >= LogLevel.critical:
130 crash(twisted.logger.formatEvent(event))
132 #---------- startup ----------
134 def common_startup(defcfg):
135 twisted.logger.globalLogPublisher.addObserver(crash_on_critical)
137 optparser.add_option('-c', '--config', dest='configfile',
138 default='/etc/hippotat/config')
139 (opts, args) = optparser.parse_args()
140 if len(args): optparser.error('no non-option arguments please')
142 cfg.read_string(defcfg)
143 cfg.read(opts.configfile)
147 print('CRASHED (end)', file=sys.stderr)