├── .gitignore ├── README.rst ├── actiontec ├── __init__.py ├── actiontec.py └── statistics.py ├── ganglia └── actiontec_metrics.py ├── help.txt └── scripts └── actiontec_if_stats.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================= 2 | Python utilities for the Actiontec MI424WR Router 3 | ================================================= 4 | 5 | :Author: Lars Kellogg-Stedman 6 | :URL: http://github.com/larsks/python-actiontec 7 | 8 | Overview 9 | ======== 10 | 11 | This is a Python API for interacting with the Actiontec MI424WR 12 | router, as used by Verizon's FIOS service. The package includes the 13 | following classes: 14 | 15 | - actiontec.actiontec.Actiontec 16 | 17 | This is a very thin wrapper around the pexpect_ project that lets 18 | you send commands to the Actiontec router via the telnet interface 19 | and receive back the result text. 20 | 21 | - actiontec.statistics.Statistics 22 | 23 | This class extends the Actiontec class to include convenience 24 | functions for retrieving common operating statistics from the 25 | Actiontec router. 26 | 27 | Requirements 28 | ============ 29 | 30 | - You need to enable the telnet administration interface on your 31 | router. 32 | 33 | - Log in to your router. 34 | 35 | - Select "Advanced". 36 | 37 | - "Yes", you want to proceed. 38 | 39 | - Select "Local Administration", located in the menu under the 40 | toolbox icon. 41 | 42 | - Under "Allow local telnet access", enable "Using Primary Telnet 43 | port (23)". 44 | 45 | - Click "Apply". 46 | 47 | - You need the ``telnet`` command. If you're on Linux or OS X you're 48 | probably in good shape. 49 | 50 | - You will need to install pexpect_. 51 | 52 | - If you are on CentOS:: 53 | 54 | # yum install pexpect 55 | 56 | - If you are on Ubuntu:: 57 | 58 | # apt-get install python-pexpect 59 | 60 | - If you are on something else and you have the ``setuptools`` package 61 | installed:: 62 | 63 | # easy_install pexpect 64 | 65 | **NB**: It's possible that the pexpect_ module will only run on 66 | Unix-like platforms (Linux, OS X, (Free|Net|Open|)BSD, etc). 67 | 68 | Examples 69 | ======== 70 | 71 | Following are some simple examples. See the inline documentation 72 | (``pydoc actiontec``) for additional API details. 73 | 74 | Basic usage 75 | ----------- 76 | 77 | The following code prints out the top level help output:: 78 | 79 | from actiontec.actiontec import Actiontec 80 | 81 | a = Actiontec() 82 | text = a.run('help') 83 | 84 | This assumes you are using the default password ("password"). If you 85 | have changed your password, the code would look like this:: 86 | 87 | a = Actiontec(password='mypassword') 88 | 89 | If you provide invalid credentials for logging in, the code will raise 90 | ``actiontec.actiontec.LoginFailed``. If you try to run an invalid 91 | command, the code will raise ``actiontec.actiontec.BadCommand``. 92 | 93 | Getting statistics 94 | ------------------ 95 | 96 | If you would like to retrieve the 1, 5, and 15 minute load average 97 | from your router, you would run code similar to the following:: 98 | 99 | from actiontec.statistics import Statistics 100 | 101 | a = Statistics() 102 | avg1, avg5, avg15 = a.loadavg() 103 | 104 | To get detailed interface statistics:: 105 | 106 | stats = a.ifstats() 107 | 108 | The ``ifstats`` method returns a dictionary of which the keys are 109 | interface names and the following parameters extracted from 110 | ``/proc/net/dev``: 111 | 112 | - rx_bytes 113 | - rx_packets 114 | - rx_errs 115 | - rx_drop 116 | - rx_fifo 117 | - rx_frame 118 | - rx_compressed 119 | - rx_multicast' 120 | - tx_bytes 121 | - tx_packets 122 | - tx_errs 123 | - tx_drop 124 | - tx_fifo 125 | - tx_colls 126 | - tx_carrier 127 | - tx_compressed 128 | 129 | Security considerations 130 | ======================= 131 | 132 | This code uses telnet to interact with your router. This means that 133 | the username and password are sent over your network in cleartext. 134 | This has security implications, especially in a wireless environment: 135 | it is possible that with an appropriate level of access someone would 136 | be able to acquire your credentials and then have administrative 137 | access to your router. 138 | 139 | You have several options for responding to this information: 140 | 141 | #. You can ignore it. You're the best judge of the particular 142 | security risks associated with your own network. 143 | 144 | #. You can use an SSL-enabled telnet client. 145 | 146 | While the router support SSL-encrypted telnet, the telnet client 147 | distributed with most operating systems does not include SSL 148 | support. You may need to build your own version with SSL support. 149 | 150 | You will need to specify an alternate port when using this 151 | API if you pursue this solution. 152 | 153 | #. You can use an SSL wrapper. 154 | 155 | Programs such as stunnel_ can be used to provide SSL support to 156 | tools that otherwise would communicate over a cleartext connection. 157 | You would use stunnel_ to forward a local port on your computer to 158 | the SSL telnet port on the router. 159 | 160 | You will need to specify an alternate port when using this 161 | API if you pursue this solution. 162 | 163 | Ganglia 164 | ======= 165 | 166 | Ganglia_ is a distributed monitoring system that, like other such systems, 167 | stores data in RRD_ files and graphs the data on a web interface. 168 | This package includes a script (``ganglia/actiontec_metrics.py``) that 169 | produces metrics for Ganglia using the ``gmetric`` tool. 170 | 171 | Using this script you can get graphs of system load and network interface 172 | statistics for your Actiontec router. 173 | 174 | .. _pexpect: http://pexpect.sourceforge.net/pexpect.html 175 | .. _stunnel: http://www.stunnel.org/ 176 | .. _ganglia: http://ganglia.sourceforge.net/ 177 | .. _rrd: http://oss.oetiker.ch/rrdtool/ 178 | 179 | -------------------------------------------------------------------------------- /actiontec/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsks/python-actiontec/5719fc7c820ed3a2d7e56eca3e50aebc3f8b448d/actiontec/__init__.py -------------------------------------------------------------------------------- /actiontec/actiontec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import optparse 6 | import cStringIO as StringIO 7 | 8 | import pexpect 9 | 10 | defaults = { 11 | 'username' : 'admin', 12 | 'password' : 'password', 13 | 'address' : '192.168.1.1', 14 | 'port' : 23, 15 | 'telnet_path' : 'telnet' 16 | } 17 | 18 | class ActiontecException(Exception): 19 | pass 20 | 21 | class LoginFailed(ActiontecException): 22 | pass 23 | 24 | class BadCommand(ActiontecException): 25 | pass 26 | 27 | class Actiontec (object): 28 | def __init__ (self, 29 | address = '192.168.1.1', 30 | username = 'admin', 31 | password = 'password', 32 | port = 23, 33 | telnet_path = 'telnet'): 34 | self.address = address 35 | self.username = username 36 | self.password = password 37 | self.port = int(port) 38 | self.telnet_path = telnet_path 39 | 40 | for k,v in defaults.items(): 41 | if getattr(self, k) is None: 42 | setattr(self, k, v) 43 | 44 | self.connect() 45 | 46 | def connect(self): 47 | self.spawn = pexpect.spawn('%s %s %d' % ( 48 | self.telnet_path, 49 | self.address, 50 | self.port)) 51 | self.spawn.expect('Username:') 52 | self.spawn.sendline(self.username) 53 | self.spawn.expect('Password:') 54 | self.spawn.sendline(self.password) 55 | if self.spawn.expect(['Router>', 'Username:']) == 1: 56 | raise LoginFailed(self.username) 57 | 58 | def run(self, command): 59 | out = StringIO.StringIO() 60 | self.spawn.logfile_read = out 61 | self.spawn.sendline(command) 62 | which = self.spawn.expect(['Router>', 'Bad command']) 63 | self.spawn.logfile_read = None 64 | 65 | if which == 1: 66 | raise BadCommand(command) 67 | 68 | return out.getvalue() 69 | 70 | def disconnect(self): 71 | self.spawn.close() 72 | self.spawn = None 73 | 74 | def interfaces(self): 75 | res = self.run('net ifconfig') 76 | 77 | iface = None 78 | interfaces = {} 79 | 80 | for line in res.split('\n'): 81 | if 'Device' in line: 82 | if iface: 83 | interfaces[iface['name']] = iface 84 | iface = { 'name': line.split()[1] } 85 | if 'state=' in line: 86 | iface['state'] = line.split('=')[-1].strip() 87 | 88 | return interfaces 89 | 90 | def parse_args(): 91 | p = optparse.OptionParser() 92 | p.add_option('-H', '--host', '--address', default='192.168.1.1') 93 | p.add_option('-u', '--user', default='admin') 94 | p.add_option('-p', '--password', default='password') 95 | 96 | return p.parse_args() 97 | 98 | def main(): 99 | opts, args = parse_args() 100 | 101 | a = Actiontec(opts.host, opts.user, opts.password) 102 | text = a.run(' '.join(args)) 103 | print text 104 | 105 | if __name__ == '__main__': 106 | main() 107 | 108 | -------------------------------------------------------------------------------- /actiontec/statistics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import actiontec 4 | 5 | IFSTAT_FIELDS = ''' 6 | rx_bytes rx_packets rx_errs rx_drop rx_fifo rx_frame rx_compressed rx_multicast' 7 | tx_bytes tx_packets tx_errs tx_drop tx_fifo tx_colls tx_carrier tx_compressed 8 | '''.split() 9 | 10 | class Statistics (actiontec.Actiontec): 11 | def ifstats(self): 12 | res = self.run('system cat /proc/net/dev') 13 | 14 | devices = {} 15 | for line in res.split('\n'): 16 | if ':' in line: 17 | iface, stats = [x.strip() for x in line.split(':')] 18 | devices[iface] = dict(zip(IFSTAT_FIELDS, 19 | [int(x) for x in stats.split()])) 20 | 21 | return devices 22 | 23 | def loadavg(self): 24 | res = self.run('kernel cpu_load_avg') 25 | state = 0 26 | 27 | for line in res.split('\n'): 28 | if state == 0: 29 | if line.startswith('1 Min.'): 30 | state = 1 31 | elif state == 1: 32 | loadavg = line.split() 33 | break 34 | 35 | return [float(x) for x in loadavg] 36 | 37 | def meminfo(self): 38 | res = self.run('kernel meminfo') 39 | meminfo = {} 40 | 41 | for line in res.split('\n'): 42 | if line.startswith('Memory info:'): 43 | pass 44 | elif ':' in line: 45 | k = line.split(':')[0] 46 | v = line.split(':')[1].split()[0] 47 | meminfo[k] = int(v) 48 | 49 | return meminfo 50 | 51 | def processes(self): 52 | res = self.run('kernel top') 53 | state = 0 54 | processes = [] 55 | 56 | for line in res.split('\n'): 57 | if state == 0: 58 | if line.startswith('Command'): 59 | state = 1 60 | elif state == 1: 61 | if line.startswith('Wireless Broadband'): 62 | break 63 | else: 64 | processes.append(line.split()[0]) 65 | 66 | return processes 67 | 68 | def cpus(self): 69 | res = self.run('system cat /proc/cpuinfo') 70 | 71 | cpus = [] 72 | cpu = {} 73 | for line in res.split('\n'): 74 | if not ':' in line: 75 | continue 76 | elif ':' in line: 77 | k,v = [x.strip() for x in line.split(':')] 78 | if k == 'system type': 79 | continue 80 | elif k == 'processor' and cpu: 81 | cpus.append(cpu) 82 | cpu = {} 83 | 84 | cpu[k] = v 85 | 86 | if cpu: 87 | cpus.append(cpu) 88 | 89 | return cpus 90 | 91 | -------------------------------------------------------------------------------- /ganglia/actiontec_metrics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import optparse 6 | import time 7 | import subprocess 8 | 9 | from actiontec.statistics import Statistics 10 | 11 | def parse_args(): 12 | p = optparse.OptionParser() 13 | p.add_option('-a', '--address', default='192.168.1.1') 14 | p.add_option('-n', '--name') 15 | p.add_option('-u', '--user', default='admin') 16 | p.add_option('-p', '--password', default='password') 17 | 18 | return p.parse_args() 19 | 20 | def gmetric(name, type, value, units='', slope='both', 21 | tmax=60, dmax=0, spoof=None, heartbeat=False): 22 | args = [str(x) for x in [ 23 | '-n', name, 24 | '-t', type, 25 | '-v', value, 26 | '-u', units, 27 | '-s', slope, 28 | '-x', tmax, 29 | '-d', dmax, 30 | ]] 31 | 32 | if spoof is not None: 33 | args.extend(['-S', spoof]) 34 | 35 | if heartbeat: 36 | args.append('-H') 37 | 38 | print 'gmetric', ' '.join(args) 39 | subprocess.call(['gmetric'] + args) 40 | 41 | def main(): 42 | opts, args = parse_args() 43 | if opts.name is None: 44 | opts.name = opts.address 45 | 46 | a = Statistics(opts.address, opts.user, opts.password) 47 | 48 | lastval = {} 49 | while True: 50 | start_time = time.time() 51 | 52 | cpus = a.cpus() 53 | gmetric('cpu_num', 'uint32', len(cpus), 54 | dmax=300, 55 | spoof='%s:%s' % (opts.address, opts.name)) 56 | 57 | loadavg = a.loadavg() 58 | for x in ((0, 'one'), (1, 'five'), (2, 'fifteen')): 59 | gmetric('load_%s' % x[1], 'float', loadavg[x[0]], 60 | dmax=300, 61 | spoof='%s:%s' % (opts.address, opts.name)) 62 | 63 | processes = a.processes() 64 | gmetric('proc_run', 'uint32', len(processes), 65 | dmax=300, 66 | spoof='%s:%s' % (opts.address, opts.name)) 67 | gmetric('proc_total', 'uint32', len(processes), 68 | dmax=300, 69 | spoof='%s:%s' % (opts.address, opts.name)) 70 | 71 | interfaces = a.interfaces() 72 | ifstats = a.ifstats() 73 | 74 | totals = {} 75 | 76 | for iface,data in interfaces.items(): 77 | if data['state'] not in ['running']: 78 | continue 79 | 80 | for name in ['rx_packets', 'rx_bytes', 'rx_errs', 'rx_drop', 81 | 'tx_packets', 'tx_bytes', 'tx_errs', 'tx_drop']: 82 | 83 | fqname = '%s_%s' % (iface, name) 84 | curval = ifstats[iface][name] 85 | totals[name] = totals.get(name, 0) + curval 86 | 87 | rate = curval - lastval.get(fqname, curval) 88 | gmetric(fqname, 'uint32', rate, 89 | dmax=300, 90 | spoof='%s:%s' % (opts.address, opts.name)) 91 | lastval[fqname] = curval 92 | 93 | for name in [('rx_packets', 'pkts_in'), ('rx_bytes', 'bytes_in'), 94 | ('tx_packets', 'pkts_out'), ('tx_bytes', 'bytes_out')]: 95 | curval = totals[name[0]] 96 | rate = curval - lastval.get(name[0], curval) 97 | gmetric(name[1], 'float', rate, 98 | dmax=300, 99 | spoof='%s:%s' % (opts.address, opts.name)) 100 | lastval[name[0]] = totals[name[0]] 101 | 102 | meminfo = a.meminfo() 103 | for k1,k2 in (('mem_total', 'MemTotal'), ('mem_cached', 'Cached'), 104 | ('mem_free', 'MemFree'), ('mem_buffers', 'Buffers'), 105 | ('mem_shared', None)): 106 | 107 | gmetric(k1, 'float', meminfo.get(k2, 0), 108 | units='KB', 109 | dmax=300, 110 | spoof='%s:%s' % (opts.address, opts.name)) 111 | 112 | time.sleep (60 - (time.time() - start_time)) 113 | 114 | 115 | if __name__ == '__main__': 116 | main() 117 | 118 | -------------------------------------------------------------------------------- /help.txt: -------------------------------------------------------------------------------- 1 | help all 2 | 3 | Command Category conf - Read and write Wireless Broadband Router configuration data 4 | factory Factory related commands 5 | print Print Wireless Broadband Router configuration 6 | set Set Wireless Broadband Router configuration path to value 7 | set_obscure Set Wireless Broadband Router configuration path to an 8 | obscured value 9 | del Delete subtree from Wireless Broadband Router configuration 10 | ram_set Set Wireless Broadband Router dynamic configuration 11 | ram_print Print Wireless Broadband Router dynamic configuration 12 | reconf Reconfigure the system according to the current Wireless 13 | Broadband Router configuration 14 | firmware_restore Restore to saved firmware and reboot. 15 | exit Exit sub menu 16 | help Show help for commands within this menu 17 | 18 | Command Category upnp - UPnP commands 19 | igd IGD commands 20 | status Display UPnP status 21 | exit Exit sub menu 22 | help Show help for commands within this menu 23 | 24 | Command Category qos - Control and display QoS data 25 | utilization Connection utilization information 26 | exit Exit sub menu 27 | help Show help for commands within this menu 28 | 29 | Command Category cwmp - CWMP related commands 30 | status Print CWMP status 31 | session_start Start CWMP session to ACS 32 | session_stop Stop CWMP session 33 | exit Exit sub menu 34 | help Show help for commands within this menu 35 | 36 | Command Category bridge - API for managing ethernet bridge 37 | connection connect separate network interfaces to form one seamless LAN 38 | config Configure bridge 39 | info Print bridge information 40 | exit Exit sub menu 41 | help Show help for commands within this menu 42 | 43 | Command Category firewall - Control and display Firewall and NAT data 44 | restart Stop and start Firewall & NAT 45 | start Start Firewall & NAT 46 | stop Stop Firewall & NAT 47 | filter Turn Firewall packet inspection on/off 48 | mac_cache_dump Dump MAC cache data 49 | dump Display Firewall data 50 | variable Display variables of the firewall rules 51 | trace Trace packet traversal via the Firewall ruleset 52 | fastpath Turns firewall fastpath feature on/off (default is on) 53 | exit Exit sub menu 54 | help Show help for commands within this menu 55 | 56 | Command Category connection - API for managing connections 57 | pppoe Configure pppoe interface 58 | vlan Configure vlan interface 59 | exit Exit sub menu 60 | help Show help for commands within this menu 61 | 62 | Command Category inet_connection - API for managing internet connections 63 | pppoe Configure pppoe internet connection 64 | ether Configure ethernet internet connection 65 | exit Exit sub menu 66 | help Show help for commands within this menu 67 | 68 | Command Category misc - API for Wireless Broadband Router miscellaneous tasks 69 | print_ram print ram consumption for each process 70 | vlan_add Add VLAN interface 71 | top Profiling over event loop and estream 72 | wbm_debug_set Stop and start WBM debug mode 73 | wbm_border_set Stop and start WBM border mode 74 | knet_hooks_dump Dump to console which knet_hooks run on each device 75 | exit Exit sub menu 76 | help Show help for commands within this menu 77 | 78 | Command Category firmware_update - Firmware update commands 79 | start Remotely upgrade Wireless Broadband Router 80 | cancel Kill running remote upgrade 81 | exit Exit sub menu 82 | help Show help for commands within this menu 83 | 84 | Command Category log - Controls Wireless Broadband Router logging behavior 85 | filter Controls the CLI session logging behavior 86 | print Print the contents of a given syslog buffer to the console 87 | clear Clear the contents of a given syslog buffer 88 | exit Exit sub menu 89 | help Show help for commands within this menu 90 | 91 | Command Category dev - Device related commands 92 | moca MOCA commands 93 | mii_reg_get Get Ethernet MII register value 94 | mii_reg_set Set Ethernet MII register value 95 | mii_phy_reg_get Get Ethernet MII register value 96 | mii_phy_reg_set Set Ethernet MII register value 97 | exit Exit sub menu 98 | help Show help for commands within this menu 99 | 100 | Command Category kernel - Kernel related commands 101 | sys_ioctl issue openrg ioctl 102 | meminfo Print memory information 103 | top Print Wireless Broadband Router's processes memory usage 104 | cpu_load_on Periodically shows cpu usage. 105 | cpu_load_off Stop showing cpu usage (triggered by cpu_load_on). 106 | cpu_load_avg Shows average cpu usage of last 1, 5 and 15 minutes. 107 | exit Exit sub menu 108 | help Show help for commands within this menu 109 | 110 | Command Category system - Commands to control Wireless Broadband Router execution 111 | die Exit from Wireless Broadband Router and return ret 112 | ps Print Wireless Broadband Router's tasks 113 | entity_close Close an entity 114 | etask_list_dump Dump back trace of all etasks 115 | restore_factory_settings Restore factory configuration 116 | reboot Reboot the system 117 | ver Display version information 118 | print_config Print compilation configuration. Search for option 119 | if specified 120 | exec Execute program 121 | cat Print file contents to console 122 | shell Spawn busybox shell in foreground 123 | date Print the current UTC and local time 124 | exit Exit sub menu 125 | help Show help for commands within this menu 126 | 127 | Command Category flash - Flash and loader related commands 128 | commit Save Wireless Broadband Router configuration to flash 129 | erase Erase a given section in the flash 130 | load Load and burn image 131 | boot Boot the system 132 | bset Configure bootloader 133 | layout Print the flash layout and content 134 | dump Dump the flash content 135 | lock Lock mtd region 136 | unlock Unlock mtd region 137 | exit Exit sub menu 138 | help Show help for commands within this menu 139 | 140 | Command Category net - Network related commands 141 | dns_route Dyncamic Routing according to DNS replies 142 | igmp IGMP Proxy related commands 143 | host Resolve host by name 144 | wsc wps related commands 145 | ifconfig Configure network interface 146 | ping Test network connectivity 147 | rg_ifconfig List Wireless Broadband Router Network Devices 148 | route Print route table 149 | main_wan Print the name of the current main wan device 150 | intercept_state Print interception state 151 | exit Exit sub menu 152 | help Show help for commands within this menu 153 | 154 | Command Category cmd - Commands related to the Command module 155 | exit Exit from the current CLI session 156 | help Show help for commands within this menu 157 | 158 | Returned 0 159 | Wireless Broadband Router> 160 | -------------------------------------------------------------------------------- /scripts/actiontec_if_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import optparse 6 | 7 | from actiontec.statistics import Statistics 8 | 9 | def parse_args(): 10 | p = optparse.OptionParser() 11 | p.add_option('-H', '--host', '--address', default='192.168.1.1') 12 | p.add_option('-u', '--user', default='admin') 13 | p.add_option('-p', '--password', default='password') 14 | p.add_option('-a', '--all', action='store_true') 15 | 16 | return p.parse_args() 17 | 18 | def main(): 19 | opts, args = parse_args() 20 | 21 | a = Statistics(opts.host, opts.user, opts.password) 22 | 23 | ifaces = a.interfaces() 24 | print ifaces 25 | 26 | for iface, stats in a.ifstats().items(): 27 | try: 28 | if (not opts.all) and ifaces[iface]['state'] not in ['running']: 29 | continue 30 | except KeyError: 31 | if not opts.all: 32 | continue 33 | 34 | print iface 35 | for k,v in stats.items(): 36 | print '%20s = %s' % (k,v) 37 | 38 | if __name__ == '__main__': 39 | main() 40 | 41 | --------------------------------------------------------------------------------