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