]> git.ipfire.org Git - oddments/cappie.git/blob - cappie.py
Initial checkin.
[oddments/cappie.git] / cappie.py
1 #!/usr/bin/python
2
3 import logging
4 import logging.handlers
5 import pcapy
6 import struct
7 import sys
8 import time
9
10 from ConfigParser import ConfigParser
11 from threading import Thread
12
13 def getAllInterfaces():
14 filters = ("lo", "any")
15 ret = []
16 for dev in pcapy.findalldevs():
17 if not dev in filters:
18 ret.append(dev)
19 return ret
20
21 def val2int(val):
22 return int("".join(["%02d" % ord(c) for c in val]), 16)
23
24 def val2ip4(val):
25 return ".".join(["%d" % ord(i) for i in val])
26
27 def val2mac(val):
28 return ":".join(["%02x" % ord(i) for i in val])
29
30 def decode_packet(data):
31 for func in (decode_arp_packet,):
32 try:
33 p =func(data)
34 except PacketTypeError:
35 continue
36
37 return p
38
39 raise PacketTypeError, "Could not determine type of packet"
40
41 def decode_arp_packet(data):
42 operationmap = {
43 1 : "in",
44 2 : "out",
45 }
46
47 if not len(data) == 42:
48 raise DecodeError, "Data has wrong length"
49
50 ret = {
51 #"hwtype" : data[:2],
52 "protocol" : val2int(struct.unpack("!2s", data[12:14])[0]),
53 "hw_addr_size" : val2int(struct.unpack("!1s", data[18:19])[0]),
54 "hw_prot_size" : val2int(struct.unpack("!1s", data[19:20])[0]),
55 "operation" : val2int(struct.unpack("!2s", data[20:22])[0]),
56 }
57
58 # Sanity checks
59 if not ret["protocol"] == 0x0806:
60 raise PacketTypeError, "Not an ARP packet"
61
62 # TODO Must check hwtype here...
63
64 try:
65 ret["operation"] = operationmap[ret["operation"]]
66 except KeyError:
67 raise DecodeError, "Unknown operation type"
68
69 address_length = ret["hw_addr_size"] + ret["hw_prot_size"]
70 unpack_str = "!%ss%ss" % (ret["hw_addr_size"], ret["hw_prot_size"])
71
72 ret["source_address"], ret["source_ip_address"] = \
73 struct.unpack(unpack_str, data[22:22 + address_length])
74
75 ret["destination_address"], ret["destination_ip_address"] = \
76 struct.unpack(unpack_str, data[22 + address_length:22 + address_length * 2])
77
78 for i in ("source_address", "destination_address"):
79 ret[i] = val2mac(ret[i])
80
81 for i in ("source_ip_address", "destination_ip_address"):
82 ret[i] = val2ip4(ret[i])
83
84 return ret
85
86 def decode_ndp_packet(data):
87 raise PacketTypeError
88
89 class PacketTypeError(Exception):
90 pass
91
92 class DecodeError(Exception):
93 pass
94
95
96 class InterfaceError(Exception):
97 pass
98
99
100 class Database(object):
101 def __init__(self, interface):
102 self.interface = interface
103 self.dev = self.interface.dev
104 self.log = self.interface.log
105
106 self.__data = {}
107
108 def open(self):
109 self.log.debug("Opened database for %s" % self.dev)
110
111 def close(self):
112 self.log.debug("Closing database for %s" % self.dev)
113 print self.__data
114
115 def get(self, mac):
116 if self.has(mac):
117 return self.__data[mac]
118
119 def has(self, mac):
120 return self.__data.has_key(mac)
121
122 def put(self, mac, key, val):
123 if not self.has(mac):
124 self.__data[mac] = {}
125
126 # TODO Check key for sanity
127
128 self.__data[mac][key] = val
129
130
131 class Interface(Thread):
132 heartbeat = 0.1
133
134 def __init__(self, dev, log, promisc=False, mtu=1500):
135 Thread.__init__(self)
136
137 self.dev = dev
138 self.log = log
139 self.promisc = promisc
140 self.mtu = mtu
141
142 self.db = Database(self)
143
144 self.log.debug("Created new interface %s" % self.dev)
145
146 self.__running = True
147
148 def _callback(self, header, data):
149 self.log.debug("Received packet on %s" % self.dev)
150 try:
151 p = decode_packet(data)
152 except PacketTypeError, e:
153 self.log.error("Got unknown packet: %s" % e)
154 return
155 except DecodeError, e:
156 self.log.warning("Got decoding error: %s" % e)
157 return
158
159 # Dump packet information
160 for key, val in p.items():
161 self.log.debug(" %s: %s" % (key, val))
162
163 if not self.db.has(p["source_address"]):
164 self.db.put(p["source_address"], "SOURCE_IP_ADDRESS", p["source_ip_address"])
165
166 def run(self):
167 self.log.info("Starting interface %s" % self.dev)
168
169 self.db.open()
170
171 p = pcapy.open_live(self.dev, self.mtu, self.promisc, 0)
172 p.setfilter(self.filter)
173 #p.loop(0, self._callback)
174
175 p.setnonblock(1)
176 while True:
177 if not self.__running:
178 self.db.close()
179 return
180
181 if p.dispatch(1, self._callback):
182 continue
183
184 time.sleep(self.heartbeat)
185
186 def shutdown(self):
187 if not self.__running:
188 return
189
190 self.log.debug("Sending shutdown signal to %s" % self.dev)
191 self.__running = False
192
193 @property
194 def filter(self):
195 return "arp or rarp"
196
197
198 class Cappie(object):
199 def __init__(self):
200 self.__interfaces = []
201
202 self.log = logging.getLogger("cappie")
203 self.log.setLevel(logging.INFO)
204
205 # Log to console
206 handler = logging.StreamHandler()
207 handler.setFormatter(logging.Formatter("%(levelname)7s %(message)s"))
208 self.log.addHandler(handler)
209
210 # Setup syslog
211 handler = logging.handlers.SysLogHandler("/dev/log")
212 handler.setFormatter(logging.Formatter("cappie: %(message)s"))
213 self.log.addHandler(handler)
214
215 self.log.info("Cappie successfully started")
216
217 def __del__(self):
218 self.reset()
219 self.log.info("Exiting")
220
221 def setDebug(self, debug):
222 if debug:
223 self.log.setLevel(logging.DEBUG)
224 else:
225 self.log.setLevel(logging.INFO)
226
227 def addInterface(self, dev, **kwargs):
228 if not dev in getAllInterfaces():
229 raise InterfaceError, "No such interface %s" % dev
230
231 iface = Interface(dev, log=self.log, **kwargs)
232 self.__interfaces.append(iface)
233
234 def run(self):
235 if not self.__interfaces:
236 raise RuntimeError, "No interfaces were configured"
237
238 # Start a thread for each interface
239 for iface in self.__interfaces:
240 iface.start()
241
242 while True:
243 for iface in self.__interfaces:
244 if not iface.is_alive():
245 self.log.critical("Thread died unexpectedly. %s" % iface.dev)
246 return
247 time.sleep(60)
248
249 def readConfig(self, configfile):
250 self.reset()
251
252 config = ConfigParser()
253 config.read([configfile])
254
255 global_opts = {}
256 if config.has_section("global"):
257 for option, value in config.items("global"):
258 global_opts[option] = value
259
260 config.remove_section("global")
261
262 for iface in config.sections():
263 options = {}
264 for option, value in config.items(iface):
265 options[option] = value
266 self.addInterface(iface, **options)
267
268 def reset(self):
269 self.shutdown()
270 self.__interfaces = []
271
272 def shutdown(self):
273 for iface in self.__interfaces:
274 iface.shutdown()
275
276
277 if __name__ == "__main__":
278 from optparse import OptionParser
279 op = OptionParser()
280 op.add_option("-c", "--config", dest="config",
281 help="read configuration from file", metavar="FILE",
282 default="/etc/cappie/cappie.conf")
283 op.add_option("-d", action="store_true", dest="debug", default=False)
284
285 (options, args) = op.parse_args()
286
287 cappie = Cappie()
288 if options.config:
289 cappie.readConfig(options.config)
290 cappie.setDebug(options.debug)
291
292 try:
293 cappie.run()
294 except KeyboardInterrupt:
295 cappie.shutdown()
296 except RuntimeError, e:
297 print >>sys.stderr, e
298 sys.exit(1)
299
300 #sys.exit(0)