]> git.ipfire.org Git - oddments/cappie.git/commitdiff
Splitted into daemon and python module.
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 17 Apr 2010 18:55:02 +0000 (20:55 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 17 Apr 2010 18:55:02 +0000 (20:55 +0200)
cappie.py [deleted file]
cappie/__init__.py [new file with mode: 0644]
cappie/constants.py [new file with mode: 0644]
cappie/errors.py [new file with mode: 0644]
cappie/events.py [new file with mode: 0644]
cappie/protocol.py [new file with mode: 0644]
cappie/queue.py [new file with mode: 0644]
cappied [new file with mode: 0755]

diff --git a/cappie.py b/cappie.py
deleted file mode 100644 (file)
index 5065a9d..0000000
--- a/cappie.py
+++ /dev/null
@@ -1,431 +0,0 @@
-#!/usr/bin/python
-
-import logging
-import logging.handlers
-import os
-import pcapy
-import struct
-import subprocess
-import sys
-import time
-
-from ConfigParser import ConfigParser
-from threading import Thread
-
-TYPE_ARP = 0
-
-OPERATION_REQUEST = 0
-OPERATION_RESPONSE = 1
-
-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 : OPERATION_REQUEST,
-               2 : OPERATION_RESPONSE,
-       }
-
-       #if not len(data) == 42:
-       #       raise DecodeError, "Data has wrong length: %d" % len(data)
-
-       ret = {
-               "type" : TYPE_ARP,
-       }
-
-       #"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 protocol == 0x0806:
-               raise PacketTypeError, "Not an ARP packet"
-
-       # TODO Must check hwtype here...
-
-       try:
-               ret["operation"] = operationmap[operation]
-       except KeyError:
-               raise DecodeError, "Unknown operation type"
-
-       address_length = hw_addr_size + hw_prot_size
-       unpack_str = "!%ss%ss" % (hw_addr_size, 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, cappie, promisc=False, mtu=1500):
-               Thread.__init__(self)
-
-               self.cappie = cappie
-               self.dev = dev
-               self.log = self.cappie.log
-               self.mtu = mtu
-               self.promisc = promisc
-               self.queue = self.cappie.queue
-
-               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 QueueFullError(Exception):
-       pass
-
-
-class Queue(Thread):
-       heartbeat = 1.0
-       maxitems = 100
-
-       def __init__(self, log):
-               Thread.__init__(self)
-
-               self.log = log
-
-               self.__running = True
-               self.__queue = []
-
-       def __len__(self):
-               return self.length
-
-       def add(self, event):
-               if self.length > self.maxitems:
-                       raise QueueFullError, "Cannot queue new event."
-
-               self.__queue.append(event)
-
-       @property
-       def length(self):
-               return len(self.__queue)
-
-       def run(self):
-               self.log.debug("Started event queue")
-
-               while self.__running or self.__queue:
-                       if not self.__queue:
-                               #self.log.debug("Queue sleeping for %s seconds" % self.heartbeat)
-                               time.sleep(self.heartbeat)
-                               continue
-
-                       event = self.__queue.pop(0)
-                       self.log.debug("Processing queue event: %s" % event)
-                       try:
-                               event.run()
-                       except EventException, e:
-                               self.log.error("Catched event exception: %s" % e)
-
-       def shutdown(self):
-               self.__running = False
-               self.log.debug("Shutting down queue")
-               self.log.debug("%d events in queue left" % len(self.__queue))
-
-               # Wait until queue handled all events
-               self.join()
-
-
-class EventException(Exception):
-       pass
-
-class Event(object):
-       def __init__(self, interface):
-               self.cappie = interface.cappie
-               self.interface = interface
-               self.log = interface.log
-
-       def __str__(self):
-               return self.__class__.__name__
-
-       def run(self):
-               raise NotImplementedError
-
-
-class EventTimeout(EventException):
-       pass
-
-
-class EventShell(Event):
-       heartbeat = 0.1
-       timeout = 10
-
-       def __init__(self, interface, script):
-               Event.__init__(self, interface)
-
-               self.script = script
-
-       def run(self):
-               args = " ".join([self.script, self.interface.dev])
-
-               start = time.time()
-               self.log.debug("Running: %s" % args)
-
-               p = subprocess.Popen(args,
-                       close_fds=True,
-                       shell=True,
-                       stdin=open("/dev/null", "r"),
-                       stdout=subprocess.PIPE,
-                       stderr=subprocess.STDOUT)
-
-               while p.poll() is None:
-                       time.sleep(self.heartbeat)
-                       if (time.time() - start) > self.timeout:
-                               try:
-                                       os.killpg(p.pid, 9)
-                               except OSError:
-                                       pass
-                               raise EventTimeout, "Script took too long to return"
-
-               for line in p.stdout.read().splitlines():
-                       if not line: continue
-                       self.log.debug("  %s" % line)
-
-               self.cappie.log.debug("Child process returned with exit code: %s" % p.returncode)
-               return p.returncode
-
-
-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.queue = Queue(self.log)
-
-               self.log.info("Cappie successfully started")
-
-       def __del__(self):
-               self.shutdown()
-               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
-
-               kwargs["cappie"] = self
-
-               iface = Interface(dev, **kwargs)
-               self.__interfaces.append(iface)
-
-       def run(self):
-               if not self.__interfaces:
-                       raise RuntimeError, "No interfaces were configured"
-
-               # Start queue
-               self.queue.start()
-
-               # Start a thread for each interface
-               for iface in self.__interfaces:
-                       iface.start()
-
-               while True:
-                       if not self.queue.is_alive():
-                               self.log.critical("Queue thread died unexpectedly.")
-                               return
-
-                       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):
-               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 shutdown(self):
-               for iface in self.__interfaces:
-                       iface.shutdown()
-
-               self.queue.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)
diff --git a/cappie/__init__.py b/cappie/__init__.py
new file mode 100644 (file)
index 0000000..2fea5e0
--- /dev/null
@@ -0,0 +1,226 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import logging
+import logging.handlers
+import pcapy
+import time
+
+from ConfigParser import ConfigParser
+from threading import Thread
+
+import protocol
+import queue
+
+from errors import *
+
+def getAllInterfaces():
+       filters = ("lo", "any")
+       ret = []
+       for dev in pcapy.findalldevs():
+               if not dev in filters:
+                       ret.append(dev)
+       return ret
+
+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.queue = queue.Queue(self.log)
+
+               self.log.info("Cappie successfully started")
+
+       def __del__(self):
+               self.shutdown()
+               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
+
+               kwargs["cappie"] = self
+
+               iface = Interface(dev, **kwargs)
+               self.__interfaces.append(iface)
+
+       def run(self):
+               if not self.__interfaces:
+                       raise RuntimeError, "No interfaces were configured"
+
+               # Start queue
+               self.queue.start()
+
+               # Start a thread for each interface
+               for iface in self.__interfaces:
+                       iface.start()
+
+               while True:
+                       if not self.queue.is_alive():
+                               self.log.critical("Queue thread died unexpectedly.")
+                               return
+
+                       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):
+               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 shutdown(self):
+               for iface in self.__interfaces:
+                       iface.shutdown()
+
+               self.queue.shutdown()
+
+
+class Interface(Thread):
+       heartbeat = 0.1
+
+       def __init__(self, dev, cappie, promisc=False, mtu=1500):
+               Thread.__init__(self)
+
+               self.cappie = cappie
+               self.dev = dev
+               self.log = self.cappie.log
+               self.mtu = mtu
+               self.promisc = promisc
+               self.queue = self.cappie.queue
+
+               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 = protocol.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 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
diff --git a/cappie/constants.py b/cappie/constants.py
new file mode 100644 (file)
index 0000000..50fb016
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+TYPE_ARP = 0
+
+OPERATION_REQUEST = 0
+OPERATION_RESPONSE = 1
diff --git a/cappie/errors.py b/cappie/errors.py
new file mode 100644 (file)
index 0000000..ec9e481
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+class DecodeError(Exception):
+       pass
+
+class EventException(Exception):
+       pass
+
+class EventTimeout(EventException):
+       pass
+
+class InterfaceError(Exception):
+       pass
+
+class PacketTypeError(Exception):
+       pass
+
+class QueueFullError(Exception):
+       pass
diff --git a/cappie/events.py b/cappie/events.py
new file mode 100644 (file)
index 0000000..d403b52
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import os
+import subprocess
+import time
+
+from errors import *
+
+class Event(object):
+       def __init__(self, interface):
+               self.cappie = interface.cappie
+               self.interface = interface
+               self.log = interface.log
+
+       def __str__(self):
+               return self.__class__.__name__
+
+       def run(self):
+               raise NotImplementedError
+
+
+class EventShell(Event):
+       heartbeat = 0.1
+       timeout = 10
+
+       def __init__(self, interface, script):
+               Event.__init__(self, interface)
+
+               self.script = script
+
+       def run(self):
+               args = " ".join([self.script, self.interface.dev])
+
+               start = time.time()
+               self.log.debug("Running: %s" % args)
+
+               p = subprocess.Popen(args,
+                       close_fds=True,
+                       shell=True,
+                       stdin=open("/dev/null", "r"),
+                       stdout=subprocess.PIPE,
+                       stderr=subprocess.STDOUT)
+
+               while p.poll() is None:
+                       time.sleep(self.heartbeat)
+                       if (time.time() - start) > self.timeout:
+                               try:
+                                       os.killpg(p.pid, 9)
+                               except OSError:
+                                       pass
+                               raise EventTimeout, "Script took too long to return"
+
+               for line in p.stdout.read().splitlines():
+                       if not line: continue
+                       self.log.debug("  %s" % line)
+
+               self.cappie.log.debug("Child process returned with exit code: %s" % \
+                       p.returncode)
+
+               return p.returncode
diff --git a/cappie/protocol.py b/cappie/protocol.py
new file mode 100644 (file)
index 0000000..ed5f4a3
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import struct
+
+from constants import *
+from errors import *
+
+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 : OPERATION_REQUEST,
+               2 : OPERATION_RESPONSE,
+       }
+
+       #if not len(data) == 42:
+       #       raise DecodeError, "Data has wrong length: %d" % len(data)
+
+       ret = {
+               "type" : TYPE_ARP,
+       }
+
+       #"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 protocol == 0x0806:
+               raise PacketTypeError, "Not an ARP packet"
+
+       # TODO Must check hwtype here...
+
+       try:
+               ret["operation"] = operationmap[operation]
+       except KeyError:
+               raise DecodeError, "Unknown operation type"
+
+       address_length = hw_addr_size + hw_prot_size
+       unpack_str = "!%ss%ss" % (hw_addr_size, 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
diff --git a/cappie/queue.py b/cappie/queue.py
new file mode 100644 (file)
index 0000000..e2d0dd6
--- /dev/null
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import time
+
+from threading import Thread
+
+from errors import *
+
+class Queue(Thread):
+       heartbeat = 1.0
+       maxitems = 100
+
+       def __init__(self, log):
+               Thread.__init__(self)
+
+               self.log = log
+
+               self.__running = True
+               self.__queue = []
+
+       def __len__(self):
+               return self.length
+
+       def add(self, event):
+               if self.length > self.maxitems:
+                       raise QueueFullError, "Cannot queue new event."
+
+               self.__queue.append(event)
+
+       @property
+       def length(self):
+               return len(self.__queue)
+
+       def run(self):
+               self.log.debug("Started event queue")
+
+               while self.__running or self.__queue:
+                       if not self.__queue:
+                               #self.log.debug("Queue sleeping for %s seconds" % self.heartbeat)
+                               time.sleep(self.heartbeat)
+                               continue
+
+                       event = self.__queue.pop(0)
+                       self.log.debug("Processing queue event: %s" % event)
+                       try:
+                               event.run()
+                       except EventException, e:
+                               self.log.error("Catched event exception: %s" % e)
+
+       def shutdown(self):
+               self.__running = False
+               self.log.debug("Shutting down queue")
+               self.log.debug("%d events in queue left" % len(self.__queue))
+
+               # Wait until queue handled all events
+               self.join()
diff --git a/cappied b/cappied
new file mode 100755 (executable)
index 0000000..0d27392
--- /dev/null
+++ b/cappied
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+###############################################################################
+#                                                                             #
+# Cappie                                                                      #
+# Copyright (C) 2010 Michael Tremer                                           #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+import sys
+
+from cappie import Cappie
+
+def 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)
+
+main()