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
21 # these need to be defined here so that they can be imported by import *
23 optparser = OptionParser()
26 def __init__(self, d = { }):
29 return 'ConfigResults('+repr(self.__dict__)+')'
33 #---------- packet parsing ----------
35 def packet_addrs(packet):
36 version = packet[0] >> 4
40 factory = ipaddress.IPv4Address
44 factory = ipaddress.IPv6Address
46 raise ValueError('unsupported IP version %d' % version)
47 saddr = factory(packet[ saddroff : saddroff + addrlen ])
48 daddr = factory(packet[ saddroff + addrlen : saddroff + addrlen*2 ])
51 #---------- address handling ----------
55 r = ipaddress.IPv4Address(input)
56 except AddressValueError:
57 r = ipaddress.IPv6Address(input)
62 r = ipaddress.IPv4Network(input)
63 except NetworkValueError:
64 r = ipaddress.IPv6Network(input)
67 #---------- ipif (SLIP) subprocess ----------
69 class _IpifProcessProtocol(twisted.internet.protocol.ProcessProtocol):
70 def __init__(self, router):
73 def connectionMade(self): pass
74 def outReceived(self, data):
75 #print('RECV ', repr(data))
77 packets = slip.decode(self._buffer)
78 self._buffer = packets.pop()
79 for packet in packets:
80 if not len(packet): continue
81 (saddr, daddr) = packet_addrs(packet)
82 self._router(packet, saddr, daddr)
83 def processEnded(self, status):
84 status.raiseException()
86 def start_ipif(command, router):
88 ipif = _IpifProcessProtocol(router)
89 reactor.spawnProcess(ipif,
90 '/bin/sh',['sh','-xc', command],
91 childFDs={0:'w', 1:'r', 2:2})
93 def queue_inbound(packet):
94 ipif.transport.write(slip.delimiter)
95 ipif.transport.write(slip.encode(packet))
96 ipif.transport.write(slip.delimiter)
98 #---------- packet queue ----------
101 def __init__(self, max_queue_time):
102 self._max_queue_time = max_queue_time
103 self._pq = collections.deque() # packets
105 def append(self, packet):
106 self._pq.append((time.monotonic(), packet))
110 try: (queuetime, packet) = self._pq[0]
111 except IndexError: return False
113 age = time.monotonic() - queuetime
114 if age > self.max_queue_time:
115 # strip old packets off the front
122 # caller must have checked nonempty
123 try: (dummy, packet) = self._pq[0]
124 except IndexError: return None
127 #---------- error handling ----------
130 print('CRASH ', err, file=sys.stderr)
132 except twisted.internet.error.ReactorNotRunning: pass
134 def crash_on_defer(defer):
135 defer.addErrback(lambda err: crash(err))
137 def crash_on_critical(event):
138 if event.get('log_level') >= LogLevel.critical:
139 crash(twisted.logger.formatEvent(event))
141 #---------- config processing ----------
143 def process_cfg_common_always():
145 c.mtu = cfg.get('virtual','mtu')
147 #---------- startup ----------
149 def common_startup(defcfg):
150 twisted.logger.globalLogPublisher.addObserver(crash_on_critical)
152 optparser.add_option('-c', '--config', dest='configfile',
153 default='/etc/hippotat/config')
154 (opts, args) = optparser.parse_args()
155 if len(args): optparser.error('no non-option arguments please')
157 cfg.read_string(defcfg)
158 cfg.read(opts.configfile)
162 print('CRASHED (end)', file=sys.stderr)