comparison service/tomatoWifi/wifi.py @ 62:f8cc3d1baa85

redo wifi scraper to work with zyxel router report page too. add last connected time (from mongo) to web table Ignore-this: 18bade72e14d40532bd019791d03fa7d
author drewp@bigasterisk.com
date Sat, 06 Apr 2013 17:08:19 -0700
parents 875a37be1228
children c81a451f9b26
comparison
equal deleted inserted replaced
61:1afb0564636d 62:f8cc3d1baa85
1 import re, ast, logging, socket 1 import re, ast, logging, socket
2 import lxml.html.soupparser
2 from twisted.internet.defer import inlineCallbacks, returnValue 3 from twisted.internet.defer import inlineCallbacks, returnValue
3 from cyclone.httpclient import fetch 4 from cyclone.httpclient import fetch
4 from rdflib import Literal, Graph 5 from rdflib import Literal, Graph
5 6
6 log = logging.getLogger() 7 log = logging.getLogger()
10 return repr(self.__dict__) 11 return repr(self.__dict__)
11 12
12 class Wifi(object): 13 class Wifi(object):
13 """ 14 """
14 gather the users of wifi from the tomato routers 15 gather the users of wifi from the tomato routers
16
17 with host names from /var/lib/dhcp/dhcpd.leases
15 """ 18 """
16 def __init__(self, tomatoConfig="/my/site/magma/tomato_config.js", 19 def __init__(self, tomatoConfig="/my/site/magma/tomato_config.js",
17 accessN3="/my/proj/openid_proxy/access.n3"): 20 accessN3="/my/proj/openid_proxy/access.n3"):
18 21
19 # ideally this would all be in the same rdf store, with int and 22 # ideally this would all be in the same rdf store, with int and
46 r = Router() 49 r = Router()
47 http, tail = url.split('//', 1) 50 http, tail = url.split('//', 1)
48 userPass, tail = tail.split("@", 1) 51 userPass, tail = tail.split("@", 1)
49 r.url = http + '//' + tail 52 r.url = http + '//' + tail
50 r.headers = {'Authorization': ['Basic %s' % userPass.encode('base64').strip()]} 53 r.headers = {'Authorization': ['Basic %s' % userPass.encode('base64').strip()]}
51 r.name = {'tomato1' : 'bigasterisk3', 54 r.name = {'tomato1' : 'bigasterisk5',
52 'tomato2' : 'bigasterisk4'}[name.split('/')[1]] 55 'tomato2' : 'bigasterisk4'}[name.split('/')[1]]
53 self.routers.append(r) 56 self.routers.append(r)
54 57
55 @inlineCallbacks 58 @inlineCallbacks
56 def getPresentMacAddrs(self): 59 def getPresentMacAddrs(self):
57 aboutIp = {} 60 rows = []
58 byMac = {} # mac : [ip] 61
59
60 for router in self.routers: 62 for router in self.routers:
61 log.debug("GET %s", router) 63 log.debug("GET %s", router)
62 try: 64 try:
63 resp = yield fetch(router.url, headers=router.headers, 65 resp = yield fetch(router.url, headers=router.headers,
64 timeout=2) 66 timeout=2)
65 except socket.error: 67 except socket.error:
66 log.warn("get on %s failed" % router) 68 log.warn("get on %s failed" % router)
67 continue 69 continue
68 data = resp.body 70 data = resp.body
71 if 'Wireless -- Authenticated Stations' in data:
72 # zyxel 'Station Info' page
73 rows.extend(self.parseZyxel(data, router.name))
74 else:
75 # tomato page
76 rows.extend(self.parseTomato(data, router.name))
69 77
70 for (ip, mac, iface) in jsValue(data, 'arplist'): 78 for r in rows:
71 aboutIp.setdefault(ip, {}).update(dict( 79 try:
72 ip=ip, 80 r['name'] = self.knownMacAddr[r['mac']]
73 router=router.name, 81 except KeyError:
74 mac=mac, 82 pass
75 iface=iface, 83
76 )) 84 returnValue(rows)
85
86 def parseZyxel(self, data, routerName):
87 root = lxml.html.soupparser.fromstring(data)
88 for tr in root.cssselect('tr'):
89 mac, assoc, uth, ssid, iface = [td.text_content().strip() for td in tr.getchildren()]
90 if mac == "MAC":
91 continue
92 assoc = assoc.lower() == 'yes'
93 yield dict(router=routerName, mac=mac, assoc=assoc, connected=assoc)
77 94
78 byMac.setdefault(mac, set()).add(ip) 95 def parseTomato(self, data, routerName):
79 96 for iface, mac, signal in jsValue(data, 'wldev'):
80 for (name, ip, mac, lease) in jsValue(data, 'dhcpd_lease'): 97 yield dict(router=routerName, mac=mac, signal=signal, connected=bool(signal))
81 if lease.startswith('0 days, '): 98
82 lease = lease[len('0 days, '):] 99
83 aboutIp.setdefault(ip, {}).update(dict(
84 router=router.name,
85 rawName=name,
86 mac=mac,
87 lease=lease
88 ))
89
90 byMac.setdefault(mac, set()).add(ip)
91
92 for iface, mac, signal in jsValue(data, 'wldev'):
93 matched = False
94 for addr in aboutIp.values():
95 if (addr['router'], addr['mac']) == (router.name, mac):
96 addr.update(dict(signal=signal, iface=iface))
97 matched = True
98 if not matched:
99 aboutIp["mac-%s-%s" % (router, mac)] = dict(
100 router=router.name,
101 mac=mac,
102 signal=signal,
103 )
104
105 ret = []
106 for addr in aboutIp.values():
107 if addr.get('ip') in ['192.168.1.1', '192.168.1.2', '192.168.0.2']:
108 continue
109 try:
110 addr['name'] = self.knownMacAddr[addr['mac']]
111 except KeyError:
112 addr['name'] = addr.get('rawName')
113 if addr['name'] in [None, '*']:
114 addr['name'] = 'unknown'
115 ret.append(addr)
116
117 returnValue(ret)
118
119
120 def jsValue(js, variableName): 100 def jsValue(js, variableName):
121 # using literal_eval instead of json parser to handle the trailing commas 101 # using literal_eval instead of json parser to handle the trailing commas
122 val = re.search(variableName + r'\s*=\s*(.*?);', js, re.DOTALL).group(1) 102 val = re.search(variableName + r'\s*=\s*(.*?);', js, re.DOTALL).group(1)
123 return ast.literal_eval(val) 103 return ast.literal_eval(val)