Reset router with Python script and Raspberry Pi

Recently, I have written an article Automatic router reboot device with Arduino where I have presented a simple Arduino–based solution to reset router periodically. Since this is not the best idea to reset it, even if it does not require resetting, I have applied purely software–based solution. As the title says I have used a Python script which runs on Raspberry Pi connected to a local network.

The idea

The Python script should check periodically if Internet is working and if it is not then it should reset the router. While checking if there is active Internet connection, resetting the router is not so easy. I will explain every step I have made to achieve the goal and so you would be able to reproduce the idea for yourself. I have set up network 192.168.1.0/24 to carry on the tests. Let’s start with the script.

 

The code

import subprocess
import time
import requests
import json

printLevel = 1;
timeToWakeUp = 200;
numberOfRetries = 5;
timeToNextPing = 120;
timeBetweenRetries = 10;
firstWait = 250;
logFile = "/var/log/rebootRouter.log";

#level    =    0, debug
#              1, info
#              2, critical
#              
def printDbg(level, arg):
    if level >= printLevel:
        print(arg);
        
    return;
    
def logToFile(line, appendNewLine = 0, logFilePath = logFile):
    currentDate = time.strftime("%Y-%m-%d  %H:%M:%S");
    head = "[%s] >> %s" % (currentDate, line, );
    if appendNewLine == 1:
        head += "\n";
    with open( logFilePath, "a") as file:
        file.write(head);
    return;
    
def runPing():
    pingResponse = subprocess.Popen(["/bin/ping", "-c1", "8.8.8.8"], stdout=subprocess.PIPE).stdout.read();
    pingResponse = pingResponse.decode('utf-8');
    pingSuccessful = pingResponse.find("1 received");
    return pingSuccessful;
    
def rebootRouter():
    ret = 0;
    payload = { 'operation': 'login', 'username': 'YYYYYYYYYYYYYY', 'password': 'XXXXXXXXXXXXXXXXXXXX' } 
    with requests.Session() as s:
        p = s.post('http://192.168.1.1/cgi-bin/luci/;stok=/login?form=login', data=payload) 
        data=json.loads(p.text);
        try:
            rebootUrl = "http://192.168.1.1/cgi-bin/luci/;stok=%s/admin/system?form=reboot" % (data['data']['stok'],);
            r = s.get(rebootUrl);
            ret = 1;
        except:
            logToFile("Can't reboot. Someone has already logged in", 1);
    return ret;

if __name__=="__main__":
    logToFile("++++", 1);
    logToFile("Application started", 1);    
        
    time.sleep(firstWait);
    
    while True:
        pingSuccessful = runPing();
        
        if pingSuccessful > 0:
            printDbg(0, "Ping successful");
            time.sleep(timeToNextPing);
            continue;
            
        for i in range(0,numberOfRetries):
            time.sleep(timeBetweenRetries);
            pingSuccessful = runPing();
            if pingSuccessful > 0:
                break;
            printDbg(0, "Ping unsuccessful, retry %d/%d" % (i+1, numberOfRetries,));
        
        
        if pingSuccessful == -1:
            printDbg(0, "Rebooting router");
            logToFile("Rebooting router ...", 1);
            rebootOk = rebootRouter();
            if rebootOk == 1:
                printDbg(0, "Waiting for router to weak up");
                time.sleep(timeToWakeUp);
        

Above code is pretty straightforward. It checks every timeToNextPing seconds the Internet connection via pinging Google DNS 8.8.8.8. If everything is as it should be it repeats the process. The problem arises when there is no Internet connection and testing IP 8.8.8.8 does not terminate successfully. The Python scripts tries numberOfRetries times to ping desired IP and waits timeBetweenRetries seconds between trials. After this it is safe to assume that there is some problem with the network. The above code offers a solution to this by rebooting the router. And since I like to keep tabs on everything the scripts logs all activity to a file.

Debugging router web page

Login

For interacting with the device package requests was used. It allows to interact with web pages. It is a HTTP library which makes handling requests and responses a lot easier!

payload = { 'username': 'YYYYYYYYYYYYYY', 'password': 'XXXXXXXXXXXXXXXXXXXX' }
with requests.Session() as s:
  p = s.post('http://192.168.1.1/login', data=payload) 
  p = s.get('http://192.168.1.1/reboot')

Above code snippet is a general idea how it works. You define user and password and pass it to desired web page using POST method. One other thing is the Session it allows script to act like web browser and hold the history. In other word if you would like to reset router with i.e. http://192.168.0.1/reboot it would not work — the device the most probably would respond with “Access Denied”. By logging into the device you authorize yourself and by keeping session going you and the device know that you are allowed to reset the device or do any other kind of things. However, in more and more cases simple login will not work. Also above snipped makes a lot of assumptions about how validation for the device works. Address http://192.168.0.1/login is being used to pass the credentials but how do you know you should use http://192.168.0.1/login instead of http://192.168.0.1/secretlogin? Some reverse engineering and looking into code has to be done.

Most of web browsers has debug console which allows you to track all changes happening to web page. Using one of those and some deducing we can find out how the validation works for specific device. For this example Chrome web browser was used because it allow to keep history of the operation. You can start the debug console with Ctrl+Shift+I.

Before typing in router IP address go to Network tab and check Preserve log. But before typing anything to the login form let’s investigate HTML code a bit. Below you can see an example:

What you need to identify is how login name and password are named. Above those input fields are username and password. It is important to make a good identification because you have to use the exact names; otherwise the device will not understand. Ok! Let’s return to the Debug console. Type in username and password and hit login button. Something similar to below should appear:

There are a few thing about above figure. First you should identify which action you should look for. In this case it is login?form=login. After this you are almost there. I have marked three things, the last one will be attended to later on. First is the URL address you need to “request” with POST method. It was marked with red line. Now you can see this address is the same as in my Python script. The second thing is what actually was being sent over. It was marked with blue line — the Form Data. You can instantly notice that three fields were sent over: operation, username, password instead of two as it was shown in HTML code. That is why using Debug console is good thing to do. It can save a lot of time. The additional field was added with JavaScript script present on the web page. By using the Debug console you do not have to analyse the code and find out that below actually adds this field:

 var doLogin = function(){
        loginForm.form("submit", {
            "operation": "login"
        }, function(data, status, xhr){
            var token = data.stok || (function(){

You can notice that the password value was awfully long. This is because a JavaScript is being used to encode plain password. You can either use it as it is or find a JavaScript function which is responsible for encoding the password. If you decide to go JavaScript way you can use Python to actually call JS function on some text — plain password. However, this approach would be useful if the encoded text would change. Presented approach is more like recording and replaying than actually interactive conversation between Python script and the router.

Reboot

Now, we should reboot the device. After that we could record something similar to:

Once again you have to find the right action. In this case it was: system?form=reboot. I have also marked with red line the most important thing — the URL address for rebooting device. As you can see it has something curious — an extra piece of information. It is a token. But wait … will it be the same every time? Unfortunately not! The thing with tokens is that they tend to change every time for a session. But there is one thing certain about tokens. You should receive one after successful login!

Retrieving token

You can retrieve the token from response the router sends.

p = s.post('http://192.168.1.1/cgi-bin/luci/;stok=/login?form=login', data=payload)
data=json.loads(p.text);

Since it is in JSON format you need to translate it. You can use json Python library for decoding the data. But how do we know this is JSON format. Let’s go back to the first screen shoot, the one with login information, for a moment. Notice the text highlighted with green lines. It show the Response Header and inside it you can see that there is a field named Content-Type and it says it is json.

It is helpful to print the response to the output like this:

print(data);

You will notice that there is a field named stock. This one carries the token!

All what was left to do is to parse a URL for reboot and call it like this:

rebootUrl = "http://192.168.1.1/cgi-bin/luci/;stok=%s/admin/system?form=reboot" % (data['data']['stok'],);
r = s.get(rebootUrl);

That is all. You can tweak this solution to facilitate reboot of your own router.

Final remarks

The code was written in Python 3 and it was not tested with Python 2.

Also you can run this script automatically every time the system boots. Since I have a Raspberry Pi connected to my local network it monitoring the network. You can add below line to /etc/rc.local to make the script run an boot:

python3 /path/to/script/rebootRouter.py &

 

 

 

2 thoughts on “Reset router with Python script and Raspberry Pi

  1. Chris J

    I really learned a lot from your page. Your script worked on my TP Link C9 router with minimal modification. I just had to delete the user field since my version of the firmware doesn’t use a user name. Before I found your page, I was looking at the headers and the post data from my router, I noticed the token in the middle of the address, and that it was doing something weird to my password and making it really long. I didn’t know how to deal with the token, I sure I could have figured it out, but it would have been a pain. I found your page, figured out your nice python script, and added a function to reboot my modem as well, in less time than it would have taken me to learn to do the token thing in curl. Plus now I figured out how it works, I like the requests library better than curl. Your tip to use the chrome inspect panel to look at the network traffic, was awesome. I was using fiddler before but that gives you all the traffic from your computer, not just the page you are interested in. Thanks for posting your script, and for giving such a detailed explanation of how everything works.

    Reply
    1. Wojciech Domski Post author

      I am really glad you have found it helpful. Other routers are much simpler to work with. You just send the data via POST and that’s all. Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.