Occasionally I have connection issues and have to restart my cable modem. This is most likely due to the signal strength being just under the recommended minimum, due to the signal running over the old RG-59 coaxial cable that is installed in my house.

When I purchased the modem, to upgrade to DOCSIS 3.0, I noticed that the modem had an embedded webserver running on 192.168.100.1. The other day I was looking at the log on the page trying to determine whether the issue I was experiencing was with my ISP or my modem and I noticed that there was a Restart Modem button and I got the idea to write a short script to restart the modem in case I am elsewhere and don't have any access to the modem.

First, I used the Inspect Element tool in Chrome to figure out what was happening when the button was clicked.

Modem Configuration Page

Here I could see see that when the action of the button form was to send a HTTP GET request to "/reset.htm"

<form method="get" action="reset.htm">
	<input name="reset_modem" value="Restart Cable Modem" type="submit"> 
</form>

Requests makes sending a request through Python very simple, so in a few minutes I had a short script that when ran would send the GET request and the modem would restart.

Since I wouldn't be able to manually run the script if I wasn't at home because my connection was down, I worked on it some more to add some monitoring, logic to determine when to restart the modem, and then finally to log the information for further review.

I found out that each page was actually made up of a few different html files. In the screenshot below, the url is /index.htm but all of the relevant information is actually coming from /indexData.htm. Each of the other tab pages were structured similarly.

Modem Status Page

Below is the quick script that I wrote. Some of the logic for deciding whether to restart the modem or not could be improved but I need to wait until the next time the problem occurs to gather some more information to add additional checks.

Here is the link to the Github repo.

The first function, connection_up, is for checking the state of the connection. I used the GET request instead of a ping because access to the sockets requires root access or using subprocess to run the ping command, which is installed setuid root which allows non privileged users to use the command.

get_page requests a page from the cable modem and returns it as text.

build_dictionary takes the HTML and uses BeautifulSoup to strip out the <td> elements and then strip away the the tags and create a key value dictionary so we have the information from the HTML tables in a more accessible form.

reset_modem sends an HTTP GET request to /reset.htm to restart the modem. I moved this to a function from the main loop to make the script a little more readable.

The last function, get_filename, is for generating the log file name and including the current time to keep the incidents separated. I moved this too a function to make things a little more readable.

import requests
from bs4 import BeautifulSoup
import itertools
import netifaces
import time
from datetime import datetime

def connection_up():
	r = requests.get('google.com')

	return int(r.status_code) == 200

def get_page(tab):
	r = requests.get('http://192.168.100.1/' + tab)

	return r.text

def build_dictionary(html):
	soup = BeautifulSoup(html)
	table = soup.find_all(['td'])

	information = [str(item)[4:-5] for item in table]

	return dict(itertools.izip_longest(*[iter(information)] * 2, fillvalue=''))

def reset_modem():
	r = requests.get('http://192.168.1.100/reset.htm')

def get_filename():
	return 'incident-{}.html'.format(datetime.now().strftime("%Y%m%d-%H%M%S"))

When the main loop starts, the script waits for 60 seconds and checks the connection state. If it is down then it checks again 3 more times waiting 10 seconds between each check.

Then if the connection is still down it records the interface address information for the local host and pulls the diagnostic information from the modem. The Status and Address information is built into key value dictionaries to make the data more accessible.

If the script can't connect to the modem then it breaks from the loop, in this scenario the host might not be connected to the network or the modem might not have power.

while True:
	time.sleep(60)

	for _ in range(3):
		if connection_up:
			break

		time.sleep(10)

	else:
		# Get diagnostic information about local host and from Modem
		nic = netifaces.ifaddresses('eth0')
		nic[netifaces.AF_INET]

		try:
			status_html = get_page('indexData.htm')
			status = build_dictionary(status_html)

			address_html = get_page('cmAddressData.htm')
			address = build_dictionary(address_html)

			signal_html = get_page('cmSignalData.htm')
			logs_html = get_page('cmLogsData.htm')

		except:
			# Modem power off or unreachable
			break

Once it has the diagnostic information the script decides whether or not to restart the modem. Right now the only check is to see if the modem thinks it is operational but traffic is unable to go through the connection. The next time I run into the problem I plan on taking a closer look at the diagnostic information to add some additional logic here.

Then, after restarting the modem the diagnostic pages from the modem are put into a single file and the script waits for 120 seconds for the modem to restart and then checks the connection state and records the results to the incident report file and then finally waits for 30 mins before checking returning to the beginning of the main loop.

		# Decide whether or not to restart the Modem
		if status['Cable Modem Status'] == 'Operational':
			reset_modem()

			with open(get_filename(), 'a') as f:
				f.write(status_html)
				f.write(address_html)
				f.write(signal_html)
				f.write(logs_html)

				time.sleep(120)
				
				if connection_up():
					f.write('Connection restored after restart')
				else:
					f.write('Connection still down after retart')
		
        	time.sleep(1800)

I really like Python for creating quick utilities like this. The Beautiful Soup and Requests modules make scraping HTML and sending HTTP both really simple and you can throw something together from scratch really quick.