--- /dev/null
+#!/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)