Mercurial > ~darius > hgwebdir.cgi > adslstats
diff adslstats.py @ 22:a53f90508a06
Switch to TG-1.
Doesn't show FEC errors so skip graphing that.
mysrp.py is a modified version of https://github.com/cocagne/pysrp/blob/master/srp/_pysrp.py
to implement SRP-6 (vs SRP-6a)
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Thu, 15 Jun 2017 15:07:01 +0930 |
parents | 8c44182a2984 |
children | 4b6c811e77df |
line wrap: on
line diff
--- a/adslstats.py Mon May 23 14:54:31 2016 +0930 +++ b/adslstats.py Thu Jun 15 15:07:01 2017 +0930 @@ -1,11 +1,11 @@ #!/usr/bin/env python2 ############################################################################ # -# Parse DSL link stats for TP-Link W9970 & generate RRD archives & graphs +# Parse DSL link stats for iiNet TG-1 & generate RRD archives & graphs # ############################################################################ # -# Copyright (C) 2015 Daniel O'Connor. All rights reserved. +# Copyright (C) 2017 Daniel O'Connor. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,7 +31,12 @@ ############################################################################ import base64 +import binascii +import bs4 import ConfigParser +import json +import mechanize +import mysrp as srp import optparse import os import re @@ -40,13 +45,12 @@ import sys import time import urllib -from bs4 import BeautifulSoup conf = ConfigParser.ConfigParser() conf.add_section('global') conf.set('global', 'username', 'admin') conf.set('global', 'password', 'admin') -conf.set('global', 'name', '10.0.2.13') +conf.set('global', 'name', '10.0.2.14') conflist = ['adslstats.ini'] if ('HOME' in os.environ): @@ -83,13 +87,11 @@ Maximum Rate - Up: %d kbit, Down %s kbit Noise Margin - Up: %.1f dB, Down %.1f dB Attenuation - Up: %.1f dB, Down %.1f dB -Errors - Up: %d, Down %d Power - Up: %.1f dBm, Down %.1f dBm Uptime - %d sec''' % (self.upstream, self.downstream, self.upstreammax, self.downstreammax, self.nmup, self.nmdown, self.attenup, self.attendown, - self.fecATUC, self.fecATUR, self.uppower, self.downpower, self.uptime) return s @@ -98,34 +100,66 @@ stats = DSLStats() parser = ConfigParser.ConfigParser() base = 'http://%s' % (conf.get('global', 'name')) - # Gunk extracted from Chrome (what the page is requesting). Note it's sensitive to line ending type... - # Plus information from http://forum.kitz.co.uk/index.php?topic=15738.0 - # ATUR = ADSL Termination Unit Remote - # ATUC = ADSL Termination Unit Central office - query = '[WAN_DSL_INTF_CFG#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n[WAN_DSL_INTF_STATS_TOTAL#0,0,0,0,0,0#0,0,0,0,0,0]1,0\r\n' - cookies = {'Authorization' : 'Basic ' + base64.standard_b64encode(conf.get('global', 'username') + ':' + conf.get('global', 'password'))} - headers = {'Referer' : base} - r = requests.post(base + '/cgi?5&5' , data = query, headers = headers, cookies = cookies, stream = True) - parser.readfp(r.raw) - res = {} - tmp = '1,0,0,0,0,0' - if parser.get(tmp, 'status') == 'Up': + + # Connect and authenticate + br = mechanize.Browser() + r = br.open(base) + bs = bs4.BeautifulSoup(r) + token = bs.head.find(lambda tag: tag.has_attr('name') and tag['name'] == 'CSRFtoken')['content'] + #print('Got CSRF token ' + token) + + usr = srp.User(conf.get('global', 'username'), conf.get('global', 'password'), hash_alg = srp.SHA256, ng_type = srp.NG_2048) + uname, A = usr.start_authentication() + + req = mechanize.Request(base + '/authenticate', data = urllib.urlencode({'CSRFtoken' : token, 'I' : uname, 'A' : binascii.hexlify(A)})) + r = br.open(req) + j = json.decoder.JSONDecoder().decode(r.read()) + #print('Sent challenge, got ' + str(j)) + + M = usr.process_challenge(binascii.unhexlify(j['s']), binascii.unhexlify(j['B'])) + req = mechanize.Request(base + '/authenticate', data = urllib.urlencode({'CSRFtoken' : token, 'M' : binascii.hexlify(M)})) + r = br.open(req) + j = json.decoder.JSONDecoder().decode(r.read()) + #print('Got response ' + str(j)) + + usr.verify_session(binascii.unhexlify(j['M'])) + if not usr.authenticated(): + print('Failed to authenticate') + return None + + # Fetch stats and parse + r = br.open(base + '/modals/broadband-bridge-modal.lp') + bs = bs4.BeautifulSoup(r) + + # Helper function to extract data + def getvals(bs, text): + subs = bs.findAll('label', text = text)[0].fetchNextSiblings()[0].strings + return map(lambda s: float(s.split()[0]), subs) + + if map(None, bs.findAll('label', text = 'DSL Status')[0].fetchNextSiblings()[0].strings)[0] == 'Up': stats.linkup = True else: stats.linkup = False - stats.upstream = float(parser.get(tmp, 'upstreamCurrRate')) - stats.downstream = float(parser.get(tmp, 'downstreamCurrRate')) - stats.upstreammax = float(parser.get(tmp, 'upstreamMaxRate')) - stats.downstreammax = float(parser.get(tmp, 'downstreamMaxRate')) - stats.nmup = float(parser.get(tmp, 'upstreamNoiseMargin')) / 10.0 - stats.nmdown = float(parser.get(tmp, 'downstreamNoiseMargin')) / 10.0 - stats.attenup = float(parser.get(tmp, 'upstreamAttenuation')) / 10.0 - stats.attendown = float(parser.get(tmp, 'downstreamAttenuation')) / 10.0 - stats.fecATUR = int(parser.get(tmp, 'FECErrors')) - stats.fecATUC = int(parser.get(tmp, 'ATUCFECErrors')) - stats.uppower = float(parser.get(tmp, 'upstreamPower')) / 10.0 # I think it's tenths of a dBm but who knows - stats.downpower = float(parser.get(tmp, 'downstreamPower')) / 10.0 - stats.uptime = int(parser.get(tmp, 'showtimeStart')) + + stats.upstreammax, stats.downstreammax = getvals(bs, 'Maximum Line rate') + stats.upstream, stats.downstream = getvals(bs, 'Line Rate') + stats.uppower, stats.downpower = getvals(bs, 'Output Power') + stats.nmup, stats.nmdown = getvals(bs, 'Noise Margin') + + # Line attenuation returns several values for each direction, parse specially and just take the first one + upattens, downattens = map(None, bs.findAll('label', text = 'Line Attenuation')[0].fetchNextSiblings()[0].strings) + stats.attenup = float(re.findall('([0-9.N/A]+)', upattens)[0]) + stats.attendown = float(re.findall('([0-9.N/A]+)', downattens)[0]) + + # Convert something like '2days 17hours 28min 19sec' into seconds + uptime = re.findall('([0-9]+)', map(None, bs.findAll('label', text = 'DSL Uptime')[0].fetchNextSiblings()[0].strings)[0]) + uptime.reverse() # End up with an array of seconds, minutes, hours, etc + mults = [1, 60, 60 * 60, 24 * 60 * 60] + if len(uptime) > mults: + print('Too many uptime elements to work out') + stats.uptime = None + else: + stats.uptime = reduce(lambda a, b: a + b, map(lambda a: int(a[0]) * a[1], zip(uptime, mults))) return stats @@ -158,7 +192,7 @@ # Update the RRD (format stats as expected) def updaterrd(filename, tstamp, stats): rrdtool.update(filename, - '%d:%d:%d:%d:%d:%f:%f:%f:%f:%d:%d:%f:%f:%d' % ( + '%d:%d:%d:%d:%d:%f:%f:%f:%f:U:U:%f:%f:%d' % ( tstamp, stats.upstream, stats.downstream, @@ -168,8 +202,6 @@ stats.nmdown, stats.attenup, stats.attendown, - stats.fecATUC, - stats.fecATUR, stats.uppower, stats.downpower, stats.uptime)) @@ -280,12 +312,6 @@ 'AREA:powerdowndif#604872::STACK', 'LINE1:powerdown#c090e5:Power - Down (dBm)', - 'DEF:fecATUC=%s:fecATUC:AVERAGE' % rrdname, - 'LINE1:fecATUC#fff384:Upstream errors', - - 'DEF:fecATUR=%s:fecATUR:AVERAGE' % rrdname, - 'LINE1:fecATUR#45cfc9:Downstream errors', - 'DEF:uptime=%s:uptime:AVERAGE' % rrdname, 'CDEF:uptimepct=uptime,10,*', 'LINE1:uptimepct#606060:Uptime (10\'s%)',