📜 ⬆️ ⬇️

How to programmatically manage the TP-Link WiFi router using Python requests

Once I faced the task of implementing software control by one of the common home Wi-Fi routers TP-Link TL-WR841N, which, unfortunately, does not have a management interface via the command line (telnet, SSH). I wanted my Telegram bot, implemented in Python based on SBC on the local home network, based on my commands to perform the following router management functions:


Of course, the user can do all this himself manually using the WEB interface, but, first, I wanted to automate these functions, secondly, sometimes I had to do it remotely, but I didn’t want to keep the access to the router unencrypted HTTP over the Internet for security reasons. Management with the help of the “closed” Telegram bot seemed to me more reliable.

To access the management of the router, I used the only available interface - the user WEB interface, the interaction with which I implemented using HTTP requests Python requests. In order to determine which HTTP GET requests should be sent to the router, I used the well-known Wireshark traffic sniffer. Simply put, I used Python requests to reproduce the requests I saw in Wireshark, in the required sequence.
')

Import, authorization and source parameters


So, first of all, we import the requests library into Python, on the basis of which we will implement HTTP requests.

import requests 

As the initial parameters, we specify the internal IP address of the router as an HTTP request string. In my case, this is 192.168.0.1.

 router_ip='http://192.168.0.1' 

For authorization, we need a token, the calculation of which is based on the username and password of the user. The token calculation function is defined in the JS script on the router 192.168.0.1/login/encrypt.js . He looks like this.

encrypt.js
 function hex_md5(s) { return binl2hex(core_md5(str2binl(s), s.length * 8)); } function core_md5(x, len) { /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return Array(a, b, c, d); } function md5_cmn(q, a, b, x, s, t) { return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); } function md5_ff(a, b, c, d, x, s, t) { return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); } function md5_gg(a, b, c, d, x, s, t) { return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); } function md5_hh(a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); } function md5_ii(a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } function bit_rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } function str2binl(str) { var bin = Array(); var mask = (1 << 8) - 1; for(var i = 0; i < str.length * 8; i += 8) bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (i%32); return bin; } function binl2hex(binarray) { var hex_tab = "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); } return str; } function Base64Encoding(input) { var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; //input = utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); } return output; } function utf8_encode (string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; } 


But, I confess, I did not begin to port this function from JS to Python. I simplified the approach and looked at the value of the parameter that my browser sends to Wireshark.



The highlighted request is an authorization request, which we will reproduce later. Thus, I copied the cookie pair parameter in the authorization request

 auth_token='Authorization=Basic%20YWRtaW46YjAxYzZmYzYyMDgwMzA5Y2ZiMzc2ZTE4NzI3YzMwNzk%3D' 

Perhaps this is not the most elegant, but simple approach. In addition, we will not store the login and password from the router in plain text, which, in my opinion, is not so bad.

Let's go to the authorization function.

 def login(): r = requests.get(router_ip+'/userRpm/LoginRpm.htm?Save=Save',headers={'Referer':router_ip+'/','Cookie': auth_token}) if r.status_code==200: x=1 while x<3: try: session_id=r.text[r.text.index(router_ip)+len(router_ip)+1:r.text.index('userRpm')-1] return session_id break except ValueError: return 'Login error' x+=1 else: return 'IP unreachable' 

I used the while loop, as the router did not always authorize my bot the first time. Perhaps you can do without him.

In case of successful authorization, the function returns the session_id value. This is the session ID that the router generates during authorization. After authorization, session_id must be present in all subsequent HTTP requests to the router.

Next, we implement the logout output function.

 def logout(session_id): r = requests.get(router_ip+'/'+session_id+'/userRpm/LogoutRpm.htm',headers={'Referer':router_ip+'/'+session_id+'/userRpm/MenuRpm.htm','Cookie': auth_token}) if r.status_code==200: return 'Loging out: '+str(r.status_code) else: return 'Unable to logout'' 

I logout after each operation, since the router allows only one user to connect at a time. Therefore, if your bot is authorized and does not exit the router, then you will not be able to log in until the router closes the open session on a timeout in a few minutes. Thus, I decided to strictly adhere to the sequence “login -> operation -> logout”.

By the way, it is worth considering that if someone is already authorized on the router, the bot will obviously not be able to log in until the user logs out, or the active session closes on timeout. In short, "who first got up, that and sneakers." It is worth noting that the Python bot performs router management operations in a fraction of seconds. Thus, your router will not be occupied by the bot for a long time.

Reboot, NAP Port Forwarding


Let us turn to the operations that we can perform after successful authorization.

 # r = requests.get(router_ip+'/'+session+'/userRpm/SysRebootRpm.htm?Reboot=%D0%9F%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%B8%D1%82%D1%8C',headers={'Referer':router_ip+'/'+session+'/userRpm/SysRebootRpm.htm','Cookie': auth_token}) # Port Forwarding r = requests.get(router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm?doAll=EnAll&Page=1',headers={'Referer':router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm','Cookie': auth_token}) # Port Forwarding r = requests.get(router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm?doAll=DisAll&Page=1',headers={'Referer':router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm','Cookie': auth_token}) 

Here I would like to emphasize that these requests activate / deactivate all the Port Forwarding rules created earlier in the corresponding section of the router management.



Simply put, requests are similar to clicking the "Enable All" and "Disable All" buttons. By analogy, you can implement and create / activate individual rules.

Remote access to the router from the WAN (Internet)



 # IP    r = requests.get(router_ip+'/'+session+'/userRpm/ManageControlRpm.htm?port=5110&ip='+remote_ip+'&Save=%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C',headers={'Referer':router_ip+'/'+session+'/userRpm/SysRebootRpm.htm','Cookie': auth_token}) 

Here, the remote_ip parameter specifies the IP address of the remote control, i.e. the IP address from which you can remotely access the router via the Internet.



 remote_ip='255.255.255.255' #  . remote_ip='0.0.0.0' #  . 

If desired, you can specify a specific IP address from which you want to connect remotely to the router.

Determining the list of connected devices


 #   r = requests.get(router_ip+'/'+session+'/userRpm/WlanStationRpm.htm',headers={'Referer':router_ip+'/'+session+'/userRpm/MenuRpm.htm','Cookie': auth_token}) presence=' :' if 'DC-31-54-97-51-06' in r.text: presence=presence+'\n'+'DC-31-54-97-51-06' 

What is the need for the definition of devices registered in the home WiFi network? Suppose I have a smartphone with WiFi and MAC address DC-31-54-97-51-06. When I get home, my smartphone registers on my home WiFi network. In this simple way, I can track my presence (or rather, the presence of my smartphone) on my home network (that is, at home). This functionality allows you to automate a number of functions related to the definition of my presence. For example, when I come home, the surveillance camera's motion detector turns off, etc. Earlier, I used Ping to detect the presence of devices on the network using their IP addresses, but in the end I was disappointed in this method, as smartphones, as it turned out, were reluctant and do not always respond to Ping. Additionally, I use the ARP packet sniffer implemented in Python, which allows you to track the moment of registration of the device with a specific MAC address on the WiFi network.

So, r.text in the code above returns, among other things, a list of MAC addresses of devices registered in the router’s WiFi network. What you will do with this list depends only on your imagination.

 var hostList = new Array( "94-36-44-8F-2F-ED", 5, 23487, 10618, 2, "60-46-37-C0-43-FC", 5, 27088, 10126, 2, "EF-71-44-63-51-E1", 5, 600, 364, 2, "77-25-9D-99-ED-33", 5, 1547, 1722, 2, 0,0 ); 

So, I can only summarize. All code looks like this.

router.py
 import requests router_ip='http://192.168.0.1' auth_token='Authorization=Basic%20YWRtaW46YjAxYzZmYzYyMDgwMzA5Y2ZiMzc2ZTE4NzI3YzMwNzk%3D' def logout(session_id): r = requests.get(router_ip+'/'+session_id+'/userRpm/LogoutRpm.htm',headers={'Referer':router_ip+'/'+session_id+'/userRpm/MenuRpm.htm','Cookie': auth_token}) if r.status_code==200: return 'Loging out: '+str(r.status_code) else: return 'Unnable to logout' def login(): r = requests.get(router_ip+'/userRpm/LoginRpm.htm?Save=Save',headers={'Referer':router_ip+'/','Cookie': auth_token}) if r.status_code==200: x=1 while x<3: try: session_id=r.text[r.text.index(router_ip)+len(router_ip)+1:r.text.index('userRpm')-1] return session_id break except ValueError: return 'Login error' x+=1 else: return 'IP unreachable' def routercontrol(operation,remote_ip='255.255.255.255'): # if login()=='IP unreachable' or login()=='Login error': return login() exit(0) else: session=login() print ('Login OK: '+session) if operation=='Enable ports': # Port Forwarding r = requests.get(router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm?doAll=EnAll&Page=1',headers={'Referer':router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm','Cookie': auth_token}) status=str(r.status_code) print (logout(session)) return 'Enable all ports: '+status+' http://31.207.73.10:8082' elif operation=='Disable ports': # Port Forwarding r = requests.get(router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm?doAll=DisAll&Page=1',headers={'Referer':router_ip+'/'+session+'/userRpm/VirtualServerRpm.htm','Cookie': auth_token}) status=str(r.status_code) print (logout(session)) return 'Disable all ports: '+status elif operation=='Reboot': # r = requests.get(router_ip+'/'+session+'/userRpm/SysRebootRpm.htm?Reboot=%D0%9F%D0%B5%D1%80%D0%B5%D0%B7%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%B8%D1%82%D1%8C',headers={'Referer':router_ip+'/'+session+'/userRpm/SysRebootRpm.htm','Cookie': auth_token}) status=str(r.status_code) print (logout(session)) return 'Reboot: '+status elif operation=='Remote IP': # IP    r = requests.get(router_ip+'/'+session+'/userRpm/ManageControlRpm.htm?port=5110&ip='+remote_ip+'&Save=%D0%A1%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C',headers={'Referer':router_ip+'/'+session+'/userRpm/SysRebootRpm.htm','Cookie': auth_token}) status=str(r.status_code) print (logout(session)) return 'Remote IP '+remote_ip+': '+status elif operation=='Check presence': #   r = requests.get(router_ip+'/'+session+'/userRpm/WlanStationRpm.htm',headers={'Referer':router_ip+'/'+session+'/userRpm/MenuRpm.htm','Cookie': auth_token}) status=str(r.status_code) print (logout(session)) presence=' :' if 'DC-31-54-97-51-06' in r.text: presence=presence+'\n'+'DC-31-54-97-51-06' return presence else: return 'Wrong command' 


Obviously, similarly, you can automate other functions of managing devices without a command line using access to the WEB interface. For example, I implemented a reboot of the DLink IP camera in a similar way using HTTP Post. Thanks for attention!

Source: https://habr.com/ru/post/342194/


All Articles