bin/pvl.wlan-syslog
author Tero Marttila <terom@paivola.fi>
Fri, 05 Jul 2013 01:01:14 +0300
changeset 239 198dc3a19f54
parent 238 bin/pvl.wlan-sta@9702bfb124f6
child 240 8b55ca43e076
permissions -rwxr-xr-x
rename pvl.wlan-sta -> pvl.wlan-syslog
#!/usr/bin/python

"""
    Analyze WLAN STA logs.

        Jul  3 23:05:04 buffalo-g300n-647682 daemon.info hostapd: wlan0-1: STA aa:bb:cc:dd:ee:ff WPA: group key handshake completed (RSN)

"""

__version__ = '0.1'

import pvl.args
import pvl.syslog.args
import pvl.rrd.hosts

import optparse
import logging; log = logging.getLogger('main')

WLAN_STA_PROG = 'hostapd'

def parse_argv (argv, doc = __doc__) :
    """
        Parse command-line argv, returning (options, args).
    """

    prog = argv.pop(0)
    args = argv

    # optparse
    parser = optparse.OptionParser(
        prog        = prog,
        usage       = '%prog: [options] [<input.txt> [...]]',
        version     = __version__,
        description = doc,
    )

    # common
    parser.add_option_group(pvl.args.parser(parser))
    parser.add_option_group(pvl.syslog.args.parser(parser, prog=WLAN_STA_PROG))
    parser.add_option_group(pvl.verkko.db.parser(parser, table=db.wlan_sta))

    parser.add_option('--interfaces', metavar='PATH',
            help="Load interface/node names from mapping file")

    # parse
    options, args = parser.parse_args(args)

    # apply
    pvl.args.apply(options)

    return options, args

import re
from pvl.verkko import db

class WlanStaDatabase (object) :
    HOSTAPD_STA_RE = re.compile(r'(?P<wlan>.+?): STA (?P<sta>.+?) (?P<msg>.+)')

    DB_TABLE = db.wlan_sta
    DB_LAST_SEEN = db.wlan_sta.c.last_seen
    DB_SELECT = (db.dhcp_hosts.c.gw, db.dhcp_hosts.c.ip)

    def __init__ (self, db, interface_map=None) :
        """
            interface_map       - {(hostname, interface): (nodename, wlan)}
        """
        self.db = db
        self.interface_map = interface_map

    def select (self, distinct=DB_SELECT, interval=None) :
        """
            SELECT unique gw/ip hosts, for given interval.
        """

        query = db.select(distinct, distinct=True)

        if interval :
            # timedelta
            query = query.where(db.func.now() - self.DB_LAST_SEEN < interval)

        return self.db.select(query)

    def insert (self, key, update, timestamp, count=True) :
        """
            INSERT new host
        """

        query = self.DB_TABLE.insert().values(**key).values(**update).values(
                first_seen  = timestamp,
                last_seen   = timestamp,
        )

        if count :
            query = query.values(count=1)
        
        # -> id
        return self.db.insert(query)

    def update (self, key, update, timestamp, count=True) :
        """
            UPDATE existing host, or return False if not found.
        """

        table = self.DB_TABLE
        query = table.update()

        for col, value in key.iteritems() :
            query = query.where(table.c[col] == value)

        query = query.values(last_seen=timestamp)

        if count :
            query = query.values(count=db.func.coalesce(table.c.count, 0) + 1)

        query = query.values(**update)
        
        # -> any matched rows?
        return self.db.update(query)

    def touch (self, key, update, timestamp, **opts) :
        # update existing?
        if self.update(key, update, timestamp, **opts) :
            log.info("Update: %s: %s: %s", key, update, timestamp)
        else :
            log.info("Insert: %s: %s: %s", key, update, timestamp)
            self.insert(key, update, timestamp, **opts)

    def parse (self, item) :
        """
            Parse fields from a hostapd syslog message.
        """
        
        match = self.HOSTAPD_STA_RE.match(item['msg'])

        if not match :
            return None

        return match.groupdict()

    def __call__ (self, item) :
        match = self.parse(item)

        if not match :
            return

        # lookup?
        ap, wlan = item['host'], match['wlan']
        
        if self.interface_map :
            mapping = self.interface_map.get((ap, wlan))

            if mapping :
                ap, wlan = mapping
        
        # update/insert
        self.touch(
            dict(
                ap              = ap,
                wlan            = wlan,
                sta             = match['sta'],
            ), dict(
                msg             = match['msg'],
            ), item['timestamp']
        )

def main (argv) :
    options, args = parse_argv(argv)
    
    # database
    db = pvl.verkko.db.apply(options)

    if options.interfaces :
        interfaces = dict(pvl.rrd.hosts.map_interfaces(options, open(options.interfaces)))
    else :
        interfaces = None

    # syslog
    log.info("Open up syslog...")
    syslog = pvl.syslog.args.apply(options)

    # handler
    handler = WlanStaDatabase(db, interface_map=interfaces)

    log.info("Enter mainloop...")
    for source in syslog.main() :
        for item in source:
            handler(item)

    return 0
    
if __name__ == '__main__':
    import sys

    sys.exit(main(sys.argv))