From f1ff1d89beacdc09d459cf1fd07c37eb1612e554 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 16 Apr 2010 16:23:37 +0200 Subject: [PATCH 1/1] Initial checkin. --- cappie.py | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 cappie.py diff --git a/cappie.py b/cappie.py new file mode 100644 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) -- 2.47.3