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%)',