bin/pvl.wlan-sta
changeset 239 198dc3a19f54
parent 238 9702bfb124f6
child 240 8b55ca43e076
equal deleted inserted replaced
238:9702bfb124f6 239:198dc3a19f54
     1 #!/usr/bin/python
       
     2 
       
     3 """
       
     4     Analyze WLAN STA logs.
       
     5 
       
     6         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)
       
     7 
       
     8 """
       
     9 
       
    10 __version__ = '0.1'
       
    11 
       
    12 import pvl.args
       
    13 import pvl.syslog.args
       
    14 import pvl.rrd.hosts
       
    15 
       
    16 import optparse
       
    17 import logging; log = logging.getLogger('main')
       
    18 
       
    19 WLAN_STA_PROG = 'hostapd'
       
    20 
       
    21 def parse_argv (argv, doc = __doc__) :
       
    22     """
       
    23         Parse command-line argv, returning (options, args).
       
    24     """
       
    25 
       
    26     prog = argv.pop(0)
       
    27     args = argv
       
    28 
       
    29     # optparse
       
    30     parser = optparse.OptionParser(
       
    31         prog        = prog,
       
    32         usage       = '%prog: [options] [<input.txt> [...]]',
       
    33         version     = __version__,
       
    34         description = doc,
       
    35     )
       
    36 
       
    37     # common
       
    38     parser.add_option_group(pvl.args.parser(parser))
       
    39     parser.add_option_group(pvl.syslog.args.parser(parser, prog=WLAN_STA_PROG))
       
    40     parser.add_option_group(pvl.verkko.db.parser(parser, table=db.wlan_sta))
       
    41 
       
    42     parser.add_option('--interfaces', metavar='PATH',
       
    43             help="Load interface/node names from mapping file")
       
    44 
       
    45     # parse
       
    46     options, args = parser.parse_args(args)
       
    47 
       
    48     # apply
       
    49     pvl.args.apply(options)
       
    50 
       
    51     return options, args
       
    52 
       
    53 import re
       
    54 from pvl.verkko import db
       
    55 
       
    56 class WlanStaDatabase (object) :
       
    57     HOSTAPD_STA_RE = re.compile(r'(?P<wlan>.+?): STA (?P<sta>.+?) (?P<msg>.+)')
       
    58 
       
    59     DB_TABLE = db.wlan_sta
       
    60     DB_LAST_SEEN = db.wlan_sta.c.last_seen
       
    61     DB_SELECT = (db.dhcp_hosts.c.gw, db.dhcp_hosts.c.ip)
       
    62 
       
    63     def __init__ (self, db, interface_map=None) :
       
    64         """
       
    65             interface_map       - {(hostname, interface): (nodename, wlan)}
       
    66         """
       
    67         self.db = db
       
    68         self.interface_map = interface_map
       
    69 
       
    70 
       
    71     def select (self, distinct=DB_SELECT, interval=None) :
       
    72         """
       
    73             SELECT unique gw/ip hosts, for given interval.
       
    74         """
       
    75 
       
    76         query = db.select(distinct, distinct=True)
       
    77 
       
    78         if interval :
       
    79             # timedelta
       
    80             query = query.where(db.func.now() - self.DB_LAST_SEEN < interval)
       
    81 
       
    82         return self.db.select(query)
       
    83 
       
    84     def insert (self, key, update, timestamp, count=True) :
       
    85         """
       
    86             INSERT new host
       
    87         """
       
    88 
       
    89         query = self.DB_TABLE.insert().values(**key).values(**update).values(
       
    90                 first_seen  = timestamp,
       
    91                 last_seen   = timestamp,
       
    92         )
       
    93 
       
    94         if count :
       
    95             query = query.values(count=1)
       
    96         
       
    97         # -> id
       
    98         return self.db.insert(query)
       
    99 
       
   100     def update (self, key, update, timestamp, count=True) :
       
   101         """
       
   102             UPDATE existing host, or return False if not found.
       
   103         """
       
   104 
       
   105         table = self.DB_TABLE
       
   106         query = table.update()
       
   107 
       
   108         for col, value in key.iteritems() :
       
   109             query = query.where(table.c[col] == value)
       
   110 
       
   111         query = query.values(last_seen=timestamp)
       
   112 
       
   113         if count :
       
   114             query = query.values(count=db.func.coalesce(table.c.count, 0) + 1)
       
   115 
       
   116         query = query.values(**update)
       
   117         
       
   118         # -> any matched rows?
       
   119         return self.db.update(query)
       
   120 
       
   121     def touch (self, key, update, timestamp, **opts) :
       
   122         # update existing?
       
   123         if self.update(key, update, timestamp, **opts) :
       
   124             log.info("Update: %s: %s: %s", key, update, timestamp)
       
   125         else :
       
   126             log.info("Insert: %s: %s: %s", key, update, timestamp)
       
   127             self.insert(key, update, timestamp, **opts)
       
   128 
       
   129     def parse (self, item) :
       
   130         """
       
   131             Parse fields from a hostapd syslog message.
       
   132         """
       
   133         
       
   134         match = self.HOSTAPD_STA_RE.match(item['msg'])
       
   135 
       
   136         if not match :
       
   137             return None
       
   138 
       
   139         return match.groupdict()
       
   140 
       
   141     def __call__ (self, item) :
       
   142         match = self.parse(item)
       
   143 
       
   144         if not match :
       
   145             return
       
   146 
       
   147         # lookup?
       
   148         ap, wlan = item['host'], match['wlan']
       
   149         
       
   150         if self.interface_map :
       
   151             mapping = self.interface_map.get((ap, wlan))
       
   152 
       
   153             if mapping :
       
   154                 ap, wlan = mapping
       
   155         
       
   156         # update/insert
       
   157         self.touch(
       
   158             dict(
       
   159                 ap              = ap,
       
   160                 wlan            = wlan,
       
   161                 sta             = match['sta'],
       
   162             ), dict(
       
   163                 msg             = match['msg'],
       
   164             ), item['timestamp']
       
   165         )
       
   166 
       
   167 def main (argv) :
       
   168     options, args = parse_argv(argv)
       
   169     
       
   170     # database
       
   171     db = pvl.verkko.db.apply(options)
       
   172 
       
   173     if options.interfaces :
       
   174         interfaces = dict(pvl.rrd.hosts.map_interfaces(options, open(options.interfaces)))
       
   175     else :
       
   176         interfaces = None
       
   177 
       
   178     # syslog
       
   179     log.info("Open up syslog...")
       
   180     syslog = pvl.syslog.args.apply(options)
       
   181 
       
   182     # handler
       
   183     handler = WlanStaDatabase(db, interface_map=interfaces)
       
   184 
       
   185     log.info("Enter mainloop...")
       
   186     for source in syslog.main() :
       
   187         for item in source:
       
   188             handler(item)
       
   189 
       
   190     return 0
       
   191     
       
   192 if __name__ == '__main__':
       
   193     import sys
       
   194 
       
   195     sys.exit(main(sys.argv))