35 ) |
35 ) |
36 |
36 |
37 # common |
37 # common |
38 parser.add_option_group(pvl.args.parser(parser)) |
38 parser.add_option_group(pvl.args.parser(parser)) |
39 parser.add_option_group(pvl.syslog.args.parser(parser, prog=WLAN_STA_PROG)) |
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)) |
40 |
41 |
41 parser.add_option('--interfaces', metavar='PATH', |
42 parser.add_option('--interfaces', metavar='PATH', |
42 help="Load interface/node names from mapping file") |
43 help="Load interface/node names from mapping file") |
43 |
|
44 parser.set_defaults( |
|
45 ) |
|
46 |
44 |
47 # parse |
45 # parse |
48 options, args = parser.parse_args(args) |
46 options, args = parser.parse_args(args) |
49 |
47 |
50 # apply |
48 # apply |
51 pvl.args.apply(options) |
49 pvl.args.apply(options) |
52 |
50 |
53 return options, args |
51 return options, args |
54 |
52 |
55 import re |
53 import re |
|
54 from pvl.verkko import db |
56 |
55 |
57 class HostapdHandler (object) : |
56 class WlanStaDatabase (object) : |
58 HOSTAPD_STA_RE = re.compile(r'(?P<wlan>.+?): STA (?P<sta>.+?) (?P<msg>.+)') |
57 HOSTAPD_STA_RE = re.compile(r'(?P<wlan>.+?): STA (?P<sta>.+?) (?P<msg>.+)') |
59 |
58 |
60 def __init__ (self, interface_map=None) : |
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) : |
61 """ |
64 """ |
62 interface_map - {(hostname, interface): (nodename, wlan)} |
65 interface_map - {(hostname, interface): (nodename, wlan)} |
63 """ |
66 """ |
|
67 self.db = db |
64 self.interface_map = interface_map |
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) |
65 |
128 |
66 def parse (self, item) : |
129 def parse (self, item) : |
67 """ |
130 """ |
68 Parse fields from a hostapd syslog message. |
131 Parse fields from a hostapd syslog message. |
69 """ |
132 """ |
71 match = self.HOSTAPD_STA_RE.match(item['msg']) |
134 match = self.HOSTAPD_STA_RE.match(item['msg']) |
72 |
135 |
73 if not match : |
136 if not match : |
74 return None |
137 return None |
75 |
138 |
76 return dict(item, **match.groupdict()) |
139 return match.groupdict() |
77 |
140 |
78 def build_sta (self, match) : |
141 def __call__ (self, item) : |
79 ap, wlan = match['host'], match['wlan'] |
142 match = self.parse(item) |
|
143 |
|
144 if not match : |
|
145 return |
|
146 |
|
147 # lookup? |
|
148 ap, wlan = item['host'], match['wlan'] |
80 |
149 |
81 # override? |
|
82 if self.interface_map : |
150 if self.interface_map : |
83 mapping = self.interface_map.get((ap, wlan)) |
151 mapping = self.interface_map.get((ap, wlan)) |
84 |
152 |
85 if mapping : |
153 if mapping : |
86 ap, wlan = mapping |
154 ap, wlan = mapping |
87 |
155 |
88 build = dict( |
156 # update/insert |
89 timestamp = match['timestamp'], |
157 self.touch( |
|
158 dict( |
90 ap = ap, |
159 ap = ap, |
91 wlan = wlan, |
160 wlan = wlan, |
92 sta = match['sta'], |
161 sta = match['sta'], |
|
162 ), dict( |
93 msg = match['msg'], |
163 msg = match['msg'], |
|
164 ), item['timestamp'] |
94 ) |
165 ) |
95 |
|
96 |
|
97 return build |
|
98 |
166 |
99 def main (argv) : |
167 def main (argv) : |
100 options, args = parse_argv(argv) |
168 options, args = parse_argv(argv) |
|
169 |
|
170 # database |
|
171 db = pvl.verkko.db.apply(options) |
101 |
172 |
102 if options.interfaces : |
173 if options.interfaces : |
103 interfaces = dict(pvl.rrd.hosts.map_interfaces(options, open(options.interfaces))) |
174 interfaces = dict(pvl.rrd.hosts.map_interfaces(options, open(options.interfaces))) |
104 else : |
175 else : |
105 interfaces = None |
176 interfaces = None |
106 |
177 |
107 # syslog |
178 # syslog |
108 log.info("Open up syslog...") |
179 log.info("Open up syslog...") |
109 syslog = pvl.syslog.args.apply(options) |
180 syslog = pvl.syslog.args.apply(options) |
110 handler = HostapdHandler(interface_map=interfaces) |
181 |
|
182 # handler |
|
183 handler = WlanStaDatabase(db, interface_map=interfaces) |
111 |
184 |
112 log.info("Enter mainloop...") |
185 log.info("Enter mainloop...") |
113 for source in syslog.main() : |
186 for source in syslog.main() : |
114 for item in source: |
187 for item in source: |
115 match = handler.parse(item) |
188 handler(item) |
116 |
|
117 if not match : |
|
118 continue |
|
119 elif 'sta' in match : |
|
120 sta = handler.build_sta(match) |
|
121 else : |
|
122 continue |
|
123 |
|
124 print "{ap:>30}/{wlan:10} {sta:20} : {msg}".format(**sta) |
|
125 |
189 |
126 return 0 |
190 return 0 |
127 |
191 |
128 if __name__ == '__main__': |
192 if __name__ == '__main__': |
129 import sys |
193 import sys |