This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "Cappie detects new hosts on a network.".
The branch, master has been updated via 54e92b606bae60e47af1739bb0d146de1769bf74 (commit) via 4eaf42980fa317423f43688ccc8c941caaa5c1e7 (commit) via b6f2a21b3fdaea9b5590e9247f614c0f1ea8ae53 (commit) via 86eda3b65c6b8bd287764e6e04ffcdf01a1d8ee0 (commit) via fdf8fd0443e9f10efd2405e09a4f82456a60b925 (commit) via fe5f16af549c45778887f0450657befa5a6d002c (commit) via 497e1b32fa24b2c407063a8f2e7dcb661b3470d4 (commit) via be750261284b471fb82f50c0b2099ce0f10524e8 (commit) via ded2591de783614b0dcaab9fc7237401795a5719 (commit) via af1f4801edfb479d83f90683dfb798f618730762 (commit) via 53fd7d33da2e486eaa0a1d02ead61d544112ed8c (commit) via 78d3013c1fa8c50586290b07b4af855639100c39 (commit) from 74e192ba1c9f30f6e5072e5532a2e766b28869b5 (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 54e92b606bae60e47af1739bb0d146de1769bf74 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 19:32:52 2010 +0200
Register some handlers to the interface callback function.
commit 4eaf42980fa317423f43688ccc8c941caaa5c1e7 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 19:32:17 2010 +0200
Add garbage collector event.
commit b6f2a21b3fdaea9b5590e9247f614c0f1ea8ae53 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 19:31:29 2010 +0200
Add some new events (still experimental).
commit 86eda3b65c6b8bd287764e6e04ffcdf01a1d8ee0 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 19:30:42 2010 +0200
Change packet decoding by reading out ethernet type.
commit fdf8fd0443e9f10efd2405e09a4f82456a60b925 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 19:29:42 2010 +0200
Log every database query when in debug mode.
commit fe5f16af549c45778887f0450657befa5a6d002c Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:52:20 2010 +0200
Add shortcut to database instance from Event class.
commit 497e1b32fa24b2c407063a8f2e7dcb661b3470d4 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:50:12 2010 +0200
Add shortcut for database from main class.
commit be750261284b471fb82f50c0b2099ce0f10524e8 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:48:55 2010 +0200
Add shortcut to add events from Event class.
commit ded2591de783614b0dcaab9fc7237401795a5719 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:47:52 2010 +0200
Add shortcut to add events from main class.
commit af1f4801edfb479d83f90683dfb798f618730762 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:42:27 2010 +0200
Add database interface to queue class.
commit 53fd7d33da2e486eaa0a1d02ead61d544112ed8c Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:40:12 2010 +0200
Remove database instance from each interface thread.
commit 78d3013c1fa8c50586290b07b4af855639100c39 Author: Michael Tremer michael.tremer@ipfire.org Date: Thu Apr 29 16:37:32 2010 +0200
Moved Database class to own file with enhancements.
-----------------------------------------------------------------------
Summary of changes: cappie/__init__.py | 49 +++++++----------------- cappie/constants.py | 5 ++ cappie/database.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ cappie/events.py | 91 +++++++++++++++++++++++++++++++++++++++++++++ cappie/protocol.py | 27 +++++++------ cappie/queue.py | 18 ++++++++- 6 files changed, 244 insertions(+), 48 deletions(-) create mode 100644 cappie/database.py
Difference in files: diff --git a/cappie/__init__.py b/cappie/__init__.py index 1cf1e8d..4152b2f 100644 --- a/cappie/__init__.py +++ b/cappie/__init__.py @@ -31,6 +31,7 @@ import protocol import queue
from errors import * +from events import *
def getAllInterfaces(): filters = ("lo", "any") @@ -125,6 +126,10 @@ class Cappie(object):
self.queue.shutdown()
+ @property + def db(self): + return self.queue.db +
class Interface(Thread): heartbeat = 0.1 @@ -139,8 +144,6 @@ class Interface(Thread): self.promisc = promisc self.queue = self.cappie.queue
- self.db = Database(self) - self.log.debug("Created new interface %s" % self.dev) self.__running = True @@ -160,14 +163,11 @@ class Interface(Thread): 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"]) + self._handlePacket(p)
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) @@ -175,7 +175,6 @@ class Interface(Thread): p.setnonblock(1) while True: if not self.__running: - self.db.close() return if p.dispatch(1, self._callback): @@ -194,33 +193,13 @@ class Interface(Thread): def filter(self): return "arp or rarp"
+ def addEvent(self, event): + return self.cappie.queue.add(event)
-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 + def _handlePacket(self, packet): + if packet.operation == OPERATION_RESPONSE: + self.addEvent(EventResponseTrigger(self, packet)) + #self.addEvent(EventCheckDuplicate(self, packet))
- self.__data[mac][key] = val + elif packet.operation == OPERATION_REQUEST: + self.addEvent(EventRequestTrigger(self, packet)) diff --git a/cappie/constants.py b/cappie/constants.py index 50fb016..d68fcc7 100644 --- a/cappie/constants.py +++ b/cappie/constants.py @@ -19,7 +19,12 @@ # # ###############################################################################
+ETHERTYPE_ARP = 0x0806 + TYPE_ARP = 0
OPERATION_REQUEST = 0 OPERATION_RESPONSE = 1 + +DB_LASTSEEN_MAX = 5*60 # 5 minutes +DB_GC_INTERVAL = 60 diff --git a/cappie/database.py b/cappie/database.py new file mode 100644 index 0000000..88d57fb --- /dev/null +++ b/cappie/database.py @@ -0,0 +1,102 @@ +#!/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 itertools +import sqlite3 + +class Database(object): + KEYS = ("EVENTS", "ADDRESSES") + _CREATE = ["CREATE TABLE IF NOT EXISTS addresses(mac, address, lastseen);", + "CREATE TABLE IF NOT EXISTS changes(address, lastchange);"] + + counter = 0 + + def __init__(self, log): + self.log = log + + self.__connection = None + + def __del__(self): + self.close() + + def open(self): + self.log.debug("Opening database") + if self.__connection: + self.close() + self.__connection = sqlite3.connect("test.db") + for statement in self._CREATE: + self.execute(statement) + + def close(self): + self.log.debug("Closing database") + self.commit() + self.__connection.close() + self.__connection = None + + def commit(self): + self.log.debug("Committing data to database") + self.__connection.commit() + + def query(self, query, *parameters): + """Returns a row list for the given query and parameters.""" + cursor = self._cursor() + self._execute(cursor, query, parameters) + column_names = [d[0] for d in cursor.description] + return [Row(itertools.izip(column_names, row)) for row in cursor] + + def get(self, query, *parameters): + """Returns the first row returned for the given query.""" + rows = self.query(query, *parameters) + if not rows: + return None + elif len(rows) > 1: + raise Exception("Multiple rows returned for Database.get() query") + else: + return rows[0] + + def _cursor(self): + if not self.__connection: + self.open() + return self.__connection.cursor() + + def execute(self, query, *parameters): + """Executes the given query, returning the lastrowid from the query.""" + cursor = self._cursor() + self._execute(cursor, query, parameters) + return cursor.lastrowid + + def _execute(self, cursor, query, parameters): + self.log.debug("Executing query: %s" % query) + try: + return cursor.execute(query, parameters) + except sqlite3.OperationalError: + self.log.error("Error connecting to database") + self.close() + raise + + +class Row(dict): + """A dict that allows for object-like property access syntax.""" + def __getattr__(self, name): + try: + return self[name] + except KeyError: + raise AttributeError(name) diff --git a/cappie/events.py b/cappie/events.py index d403b52..236e19f 100644 --- a/cappie/events.py +++ b/cappie/events.py @@ -23,6 +23,7 @@ import os import subprocess import time
+from constants import * from errors import *
class Event(object): @@ -30,10 +31,14 @@ class Event(object): self.cappie = interface.cappie self.interface = interface self.log = interface.log + self.db = self.cappie.db
def __str__(self): return self.__class__.__name__
+ def addEvent(self, event): + return self.cappie.queue.add(event) + def run(self): raise NotImplementedError
@@ -77,3 +82,89 @@ class EventShell(Event): p.returncode)
return p.returncode + + +class EventRequestTrigger(Event): + def __init__(self, interface, packet): + Event.__init__(self, interface) + + self.db = interface.cappie.db + self.packet = packet + + def _updateAddress(self, mac, address): + where = "WHERE mac = '%s' AND address = '%s'" % (mac, address) + + if self.db.get("SELECT * FROM addresses %s" % where): + self.db.execute("UPDATE addresses SET lastseen='%d' %s" % \ + (time.time(), where)) + else: + self.db.execute("INSERT INTO addresses VALUES('%s', '%s', '%d')" % \ + (mac, address, time.time())) + + def _updateChanges(self, *args): + for arg in args: + where = "WHERE address = '%s'" % arg + if self.db.get("SELECT * FROM changes %s" % where): + self.db.execute("UPDATE changes SET lastchange = '%d' %s" % \ + (time.time(), where)) + else: + self.db.execute("INSERT INTO changes VALUES('%s', '%d')" % \ + (arg, time.time())) + + def run(self): + mac = self.packet.source_address + address = self.packet.source_ip_address + + self._updateAddress(mac, address) + self._updateChanges(mac, address) + + +class EventResponseTrigger(EventRequestTrigger): + pass + + +class EventGarbageCollector(Event): + def __init__(self, db, log): + self.db = db + self.log = log + + def run(self): + # Remove old addresses + self.db.execute("DELETE FROM addresses WHERE lastseen >= '%d'" % \ + (time.time() - DB_LASTSEEN_MAX)) + + self.db.commit() + + +class EventCheckDuplicate(Event): + def __init__(self, interface, packet): + Event.__init__(self, interface) + self.packet = packet + + def run(self): + entries = self.db.query("SELECT * FROM addresses WHERE address = '%s'" % \ + self.packet.source_ip_address) + + if not entries: + return + + for entry in entries: + if self.packet.source_address == entry.mac: + entries.remove(entry) + + if len(entries) > 1: + self.addEvent(EventHandleDuplicate(self.interface, self.packet)) + + +class EventHandleDuplicate(Event): + def __init__(self, interface, packet): + Event.__init__(self, interface) + self.packet = packet + + def run(self): + self.log.warning("We probably have a mac spoofing for %s" % \ + self.packet.source_address) + + +class EventCheckFlipFlop(Event): + pass diff --git a/cappie/protocol.py b/cappie/protocol.py index ed5f4a3..c4252de 100644 --- a/cappie/protocol.py +++ b/cappie/protocol.py @@ -21,6 +21,8 @@
import struct
+import database + from constants import * from errors import *
@@ -34,15 +36,17 @@ 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 + try: + protocol = val2int(struct.unpack("!2s", data[12:14])[0]) + except: + raise DecodeError
- return p + try: + d = protocol2function[protocol](data) + except KeyErrror: + raise PacketTypeError, "Could not determine type of packet"
- raise PacketTypeError, "Could not determine type of packet" + return database.Row(d)
def decode_arp_packet(data): operationmap = { @@ -58,15 +62,10 @@ def decode_arp_packet(data): }
#"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: @@ -93,3 +92,7 @@ def decode_arp_packet(data):
def decode_ndp_packet(data): raise PacketTypeError + +protocol2function = { + ETHERTYPE_ARP : decode_arp_packet, +} diff --git a/cappie/queue.py b/cappie/queue.py index e2d0dd6..4d22553 100644 --- a/cappie/queue.py +++ b/cappie/queue.py @@ -23,11 +23,13 @@ import time
from threading import Thread
+from database import Database from errors import * +from events import *
class Queue(Thread): heartbeat = 1.0 - maxitems = 100 + maxitems = 10000
def __init__(self, log): Thread.__init__(self) @@ -37,6 +39,9 @@ class Queue(Thread): self.__running = True self.__queue = []
+ self.db = Database(log) + self.lastgc = None + def __len__(self): return self.length
@@ -53,12 +58,16 @@ class Queue(Thread): def run(self): self.log.debug("Started event queue")
+ self.db.open() + 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
+ self._checkGc() + event = self.__queue.pop(0) self.log.debug("Processing queue event: %s" % event) try: @@ -66,6 +75,8 @@ class Queue(Thread): except EventException, e: self.log.error("Catched event exception: %s" % e)
+ self.db.close() + def shutdown(self): self.__running = False self.log.debug("Shutting down queue") @@ -73,3 +84,8 @@ class Queue(Thread):
# Wait until queue handled all events self.join() + + def _checkGc(self): + if not self.lastgc or self.lastgc <= (time.time() - DB_GC_INTERVAL): + self.add(EventGarbageCollector(self.db, self.log)) + self.lastgc = time.time()
hooks/post-receive -- Cappie detects new hosts on a network.