]> git.ipfire.org Git - oddments/cappie.git/commitdiff
Initial checkin.
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 16 Apr 2010 14:23:37 +0000 (16:23 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 16 Apr 2010 14:23:37 +0000 (16:23 +0200)
cappie.py [new file with mode: 0644]

diff --git a/cappie.py b/cappie.py
new file mode 100644 (file)
index 0000000..28f9060
--- /dev/null
+++ b/cappie.py
@@ -0,0 +1,300 @@
+#!/usr/bin/python
+
+import logging
+import logging.handlers
+import pcapy
+import struct
+import sys
+import time
+
+from ConfigParser import ConfigParser
+from threading import Thread
+
+def getAllInterfaces():
+       filters = ("lo", "any")
+       ret = []
+       for dev in pcapy.findalldevs():
+               if not dev in filters:
+                       ret.append(dev)
+       return ret
+
+def val2int(val):
+       return int("".join(["%02d" % ord(c) for c in val]), 16)
+
+def val2ip4(val):
+       return ".".join(["%d" % ord(i) for i in val])
+
+def val2mac(val):
+       return ":".join(["%02x" % ord(i) for i in val])
+
+def decode_packet(data):
+       for func in (decode_arp_packet,):
+               try:
+                       p =func(data)
+               except PacketTypeError:
+                       continue
+
+               return p
+
+       raise PacketTypeError, "Could not determine type of packet"
+
+def decode_arp_packet(data):
+       operationmap = {
+               1 : "in",
+               2 : "out",
+       }
+
+       if not len(data) == 42:
+               raise DecodeError, "Data has wrong length"
+
+       ret = {
+               #"hwtype" : data[:2],
+               "protocol" : val2int(struct.unpack("!2s", data[12:14])[0]),
+               "hw_addr_size" : val2int(struct.unpack("!1s", data[18:19])[0]),
+               "hw_prot_size" : val2int(struct.unpack("!1s", data[19:20])[0]),
+               "operation" : val2int(struct.unpack("!2s", data[20:22])[0]),
+       }
+
+       # Sanity checks
+       if not ret["protocol"] == 0x0806:
+               raise PacketTypeError, "Not an ARP packet"
+
+       # TODO Must check hwtype here...
+
+       try:
+               ret["operation"] = operationmap[ret["operation"]]
+       except KeyError:
+               raise DecodeError, "Unknown operation type"
+
+       address_length = ret["hw_addr_size"] + ret["hw_prot_size"]
+       unpack_str = "!%ss%ss" % (ret["hw_addr_size"], ret["hw_prot_size"])
+
+       ret["source_address"], ret["source_ip_address"] = \
+               struct.unpack(unpack_str, data[22:22 + address_length])
+
+       ret["destination_address"], ret["destination_ip_address"] = \
+               struct.unpack(unpack_str, data[22 + address_length:22 + address_length * 2])
+
+       for i in ("source_address", "destination_address"):
+               ret[i] = val2mac(ret[i])
+
+       for i in ("source_ip_address", "destination_ip_address"):
+               ret[i] = val2ip4(ret[i])
+
+       return ret
+
+def decode_ndp_packet(data):
+       raise PacketTypeError
+
+class PacketTypeError(Exception):
+       pass
+
+class DecodeError(Exception):
+       pass
+
+
+class InterfaceError(Exception):
+       pass
+
+
+class Database(object):
+       def __init__(self, interface):
+               self.interface = interface
+               self.dev = self.interface.dev
+               self.log = self.interface.log
+
+               self.__data = {}
+
+       def open(self):
+               self.log.debug("Opened database for %s" % self.dev)
+
+       def close(self):
+               self.log.debug("Closing database for %s" % self.dev)
+               print self.__data
+
+       def get(self, mac):
+               if self.has(mac):
+                       return self.__data[mac]
+
+       def has(self, mac):
+               return self.__data.has_key(mac)
+
+       def put(self, mac, key, val):
+               if not self.has(mac):
+                       self.__data[mac] = {}
+
+               # TODO Check key for sanity
+
+               self.__data[mac][key] = val
+
+
+class Interface(Thread):
+       heartbeat = 0.1
+
+       def __init__(self, dev, log, promisc=False, mtu=1500):
+               Thread.__init__(self)
+
+               self.dev = dev
+               self.log = log
+               self.promisc = promisc
+               self.mtu = mtu
+
+               self.db = Database(self)
+
+               self.log.debug("Created new interface %s" % self.dev)
+               
+               self.__running = True
+
+       def _callback(self, header, data):
+               self.log.debug("Received packet on %s" % self.dev)
+               try:
+                       p = decode_packet(data)
+               except PacketTypeError, e:
+                       self.log.error("Got unknown packet: %s" % e)
+                       return
+               except DecodeError, e:
+                       self.log.warning("Got decoding error: %s" % e)
+                       return
+
+               # Dump packet information
+               for key, val in p.items():
+                       self.log.debug("  %s: %s" % (key, val))
+
+               if not self.db.has(p["source_address"]):
+                       self.db.put(p["source_address"], "SOURCE_IP_ADDRESS", p["source_ip_address"])
+
+       def run(self):
+               self.log.info("Starting interface %s" % self.dev)
+
+               self.db.open()
+
+               p = pcapy.open_live(self.dev, self.mtu, self.promisc, 0)
+               p.setfilter(self.filter)
+               #p.loop(0, self._callback)
+
+               p.setnonblock(1)
+               while True:
+                       if not self.__running:
+                               self.db.close()
+                               return
+                       
+                       if p.dispatch(1, self._callback):
+                               continue
+
+                       time.sleep(self.heartbeat)
+
+       def shutdown(self):
+               if not self.__running:
+                       return
+
+               self.log.debug("Sending shutdown signal to %s" % self.dev)
+               self.__running = False
+
+       @property
+       def filter(self):
+               return "arp or rarp"
+
+
+class Cappie(object):
+       def __init__(self):
+               self.__interfaces = []
+
+               self.log = logging.getLogger("cappie")
+               self.log.setLevel(logging.INFO)
+
+               # Log to console
+               handler = logging.StreamHandler()
+               handler.setFormatter(logging.Formatter("%(levelname)7s %(message)s"))
+               self.log.addHandler(handler)
+
+               # Setup syslog
+               handler = logging.handlers.SysLogHandler("/dev/log")
+               handler.setFormatter(logging.Formatter("cappie: %(message)s"))
+               self.log.addHandler(handler)
+
+               self.log.info("Cappie successfully started")
+
+       def __del__(self):
+               self.reset()
+               self.log.info("Exiting")
+
+       def setDebug(self, debug):
+               if debug:
+                       self.log.setLevel(logging.DEBUG)
+               else:
+                       self.log.setLevel(logging.INFO)
+
+       def addInterface(self, dev, **kwargs):
+               if not dev in getAllInterfaces():
+                       raise InterfaceError, "No such interface %s" % dev
+
+               iface = Interface(dev, log=self.log, **kwargs)
+               self.__interfaces.append(iface)
+
+       def run(self):
+               if not self.__interfaces:
+                       raise RuntimeError, "No interfaces were configured"
+
+               # Start a thread for each interface
+               for iface in self.__interfaces:
+                       iface.start()
+
+               while True:
+                       for iface in self.__interfaces:
+                               if not iface.is_alive():
+                                       self.log.critical("Thread died unexpectedly. %s" % iface.dev)
+                                       return
+                               time.sleep(60)
+
+       def readConfig(self, configfile):
+               self.reset()
+
+               config = ConfigParser()
+               config.read([configfile])
+
+               global_opts = {}
+               if config.has_section("global"):
+                       for option, value in config.items("global"):
+                               global_opts[option] = value
+
+                       config.remove_section("global")
+
+               for iface in config.sections():
+                       options = {}
+                       for option, value in config.items(iface):
+                               options[option] = value
+                       self.addInterface(iface, **options)
+
+       def reset(self):
+               self.shutdown()
+               self.__interfaces = []
+
+       def shutdown(self):
+               for iface in self.__interfaces:
+                       iface.shutdown()
+
+
+if __name__ == "__main__":
+       from optparse import OptionParser
+       op = OptionParser()
+       op.add_option("-c", "--config", dest="config",
+               help="read configuration from file", metavar="FILE",
+               default="/etc/cappie/cappie.conf")
+       op.add_option("-d", action="store_true", dest="debug", default=False)
+
+       (options, args) = op.parse_args()
+
+       cappie = Cappie()
+       if options.config:
+               cappie.readConfig(options.config)
+       cappie.setDebug(options.debug)
+
+       try:
+               cappie.run()
+       except KeyboardInterrupt:
+               cappie.shutdown()
+       except RuntimeError, e:
+               print >>sys.stderr, e
+               sys.exit(1)
+
+       #sys.exit(0)