tplinkrouterc6u 5.12.4__py3-none-any.whl → 5.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- test/test_client_C3200.py +706 -0
- test/test_client_c6u.py +52 -19
- test/test_client_c6u_v1_11.py +95 -0
- test/test_client_ex.py +199 -0
- test/test_client_mr.py +26 -26
- test/test_client_r.py +633 -0
- test/test_client_xdr.py +5 -0
- tplinkrouterc6u/__init__.py +4 -1
- tplinkrouterc6u/client/c3200.py +198 -0
- tplinkrouterc6u/client/c6u.py +97 -0
- tplinkrouterc6u/client/ex.py +12 -12
- tplinkrouterc6u/client/mr.py +54 -31
- tplinkrouterc6u/client/mr200.py +4 -1
- tplinkrouterc6u/client/mr6400v7.py +80 -0
- tplinkrouterc6u/client/r.py +198 -0
- tplinkrouterc6u/client/xdr.py +10 -1
- tplinkrouterc6u/provider.py +8 -1
- {tplinkrouterc6u-5.12.4.dist-info → tplinkrouterc6u-5.14.0.dist-info}/METADATA +14 -3
- {tplinkrouterc6u-5.12.4.dist-info → tplinkrouterc6u-5.14.0.dist-info}/RECORD +22 -16
- {tplinkrouterc6u-5.12.4.dist-info → tplinkrouterc6u-5.14.0.dist-info}/WHEEL +1 -1
- {tplinkrouterc6u-5.12.4.dist-info → tplinkrouterc6u-5.14.0.dist-info}/licenses/LICENSE +0 -0
- {tplinkrouterc6u-5.12.4.dist-info → tplinkrouterc6u-5.14.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import re
|
|
3
|
+
from time import sleep
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from requests import Response
|
|
8
|
+
|
|
9
|
+
from tplinkrouterc6u.client.mr200 import TPLinkMR200Client
|
|
10
|
+
from tplinkrouterc6u.common.package_enum import VPN
|
|
11
|
+
from tplinkrouterc6u.common.dataclass import (
|
|
12
|
+
LTEStatus,
|
|
13
|
+
)
|
|
14
|
+
from tplinkrouterc6u.common.exception import ClientException, ClientError
|
|
15
|
+
from tplinkrouterc6u.common.package_enum import Connection
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TplinkC3200Router(TPLinkMR200Client):
|
|
19
|
+
# This is a session variable that will contain everything needed as soon as the router is connected.
|
|
20
|
+
# - The "Referer" header which allows to be accepted by the CGI module.
|
|
21
|
+
# - the Authentification cookie
|
|
22
|
+
SESSION: requests.Session
|
|
23
|
+
|
|
24
|
+
# Possible retries limit
|
|
25
|
+
REQUEST_RETRIES = 1
|
|
26
|
+
|
|
27
|
+
# Router name to be included in logs for example,
|
|
28
|
+
# or to be redefined by subclasses.
|
|
29
|
+
ROUTER_NAME = "TP Link Router C3200"
|
|
30
|
+
|
|
31
|
+
# Connection method
|
|
32
|
+
def supports(self) -> bool:
|
|
33
|
+
if len(self.password) > 125:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
# This method checks if we can recognize the router type.
|
|
38
|
+
welcome_page = requests.get(self.host, timeout=5)
|
|
39
|
+
if welcome_page and welcome_page.status_code == 200 and re.search("Archer", welcome_page.text):
|
|
40
|
+
return True
|
|
41
|
+
except ClientException:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def authorize(self) -> None:
|
|
47
|
+
|
|
48
|
+
# ———————————————————————————————————————————
|
|
49
|
+
# Create the SESSION object and the authorization cookie
|
|
50
|
+
# ———————————————————————————————————————————
|
|
51
|
+
self.SESSION = requests.Session()
|
|
52
|
+
|
|
53
|
+
if self._logger:
|
|
54
|
+
self._logger.debug("!")
|
|
55
|
+
# We need to extract the domain form the host to fill the cookie.
|
|
56
|
+
router_host = urlparse(self.host).hostname
|
|
57
|
+
if not router_host:
|
|
58
|
+
raise ValueError(self.host & " must contain a valid host, ex. http://192.168.168.1")
|
|
59
|
+
|
|
60
|
+
"""Return the string "Basic <base64(username:password)>"."""
|
|
61
|
+
token_bytes = f"{self.username}:{self.password}".encode()
|
|
62
|
+
encoded = base64.b64encode(token_bytes).decode()
|
|
63
|
+
auth_cookie_value = f"Basic {encoded}"
|
|
64
|
+
|
|
65
|
+
self.SESSION.cookies.set(
|
|
66
|
+
name="Authorization",
|
|
67
|
+
value=auth_cookie_value,
|
|
68
|
+
domain=router_host,
|
|
69
|
+
path="/",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self.SESSION.headers = {"Referer": f"{self.host}/", "Origin": self.host}
|
|
73
|
+
|
|
74
|
+
login_url = '{}/'.format(self.host)
|
|
75
|
+
response = Response()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
response = self.SESSION.post(login_url, timeout=10)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
error = self.ROUTER_NAME + " - Cannot authorize! Error - {}; Response - {}".format(e, response.text)
|
|
81
|
+
if self._logger:
|
|
82
|
+
self._logger.debug(error)
|
|
83
|
+
raise ClientException(error)
|
|
84
|
+
|
|
85
|
+
def logout(self) -> None:
|
|
86
|
+
self.SESSION.cookies.clear(domain=urlparse(self.host).hostname, path="/")
|
|
87
|
+
|
|
88
|
+
def get_lte_status(self) -> LTEStatus:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def _get_params(self, retry=False) -> None:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def set_vpn(self, vpn: VPN, enable: bool) -> None:
|
|
95
|
+
# Unable to test it on my C3200
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def req_act(self, acts: list):
|
|
99
|
+
act_types, act_data = self._fill_acts(acts)
|
|
100
|
+
|
|
101
|
+
url = f"{self.host}/cgi?" + '&'.join(act_types)
|
|
102
|
+
data_str = ''.join(act_data)
|
|
103
|
+
(code, response) = self._request(url, data_str=data_str)
|
|
104
|
+
|
|
105
|
+
if code != 200:
|
|
106
|
+
error = self.ROUTER_NAME + ' - Response with error; Request {} - Response {}'.format(data_str, response)
|
|
107
|
+
if self._logger:
|
|
108
|
+
self._logger.debug(error)
|
|
109
|
+
raise ClientError(error)
|
|
110
|
+
|
|
111
|
+
result = self._merge_response(response)
|
|
112
|
+
|
|
113
|
+
return response, result.get('0') if len(result) == 1 and result.get('0') else result
|
|
114
|
+
|
|
115
|
+
def _request(self, url, method='POST', data_str=None, encrypt=False, is_login=False):
|
|
116
|
+
r = Response()
|
|
117
|
+
|
|
118
|
+
retry = 0
|
|
119
|
+
while retry < self.REQUEST_RETRIES:
|
|
120
|
+
# send the request
|
|
121
|
+
if method == 'POST':
|
|
122
|
+
r = self.SESSION.post(url, data=data_str)
|
|
123
|
+
elif method == 'GET':
|
|
124
|
+
r = self.SESSION.get(url, data=data_str)
|
|
125
|
+
else:
|
|
126
|
+
raise Exception('Unsupported method ' + str(method))
|
|
127
|
+
|
|
128
|
+
# sometimes we get 500 here, not sure why... just retry the request
|
|
129
|
+
if (r.status_code not in [500, 406]
|
|
130
|
+
and '<title>500 Internal Server Error</title>' not in r.text
|
|
131
|
+
and '<title>406 Not Acceptable</title>' not in r.text):
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
sleep(0.1)
|
|
135
|
+
retry += 1
|
|
136
|
+
|
|
137
|
+
return r.status_code, r.text
|
|
138
|
+
|
|
139
|
+
# Overriding the method since we have two 5G bands in the rooter.
|
|
140
|
+
# We manage both as one.
|
|
141
|
+
def set_wifi(self, wifi: Connection, enable: bool) -> None:
|
|
142
|
+
acts = []
|
|
143
|
+
|
|
144
|
+
match wifi:
|
|
145
|
+
case Connection.HOST_2G:
|
|
146
|
+
acts = [
|
|
147
|
+
self.ActItem(
|
|
148
|
+
self.ActItem.SET,
|
|
149
|
+
'LAN_WLAN',
|
|
150
|
+
'1,1,0,0,0,0',
|
|
151
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
152
|
+
]
|
|
153
|
+
case Connection.HOST_5G:
|
|
154
|
+
# We activate both 5G bands
|
|
155
|
+
acts = [
|
|
156
|
+
self.ActItem(
|
|
157
|
+
self.ActItem.SET,
|
|
158
|
+
'LAN_WLAN',
|
|
159
|
+
'1,2,0,0,0,0',
|
|
160
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
161
|
+
self.ActItem(
|
|
162
|
+
self.ActItem.SET,
|
|
163
|
+
'LAN_WLAN',
|
|
164
|
+
'1,3,0,0,0,0',
|
|
165
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
166
|
+
]
|
|
167
|
+
case Connection.GUEST_2G:
|
|
168
|
+
acts = [
|
|
169
|
+
self.ActItem(
|
|
170
|
+
self.ActItem.SET,
|
|
171
|
+
'LAN_WLAN_MSSIDENTRY',
|
|
172
|
+
'1,1,1,0,0,0',
|
|
173
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
174
|
+
]
|
|
175
|
+
case Connection.GUEST_5G:
|
|
176
|
+
acts = [
|
|
177
|
+
self.ActItem(
|
|
178
|
+
self.ActItem.SET,
|
|
179
|
+
'LAN_WLAN_MSSIDENTRY',
|
|
180
|
+
'1,2,1,0,0,0',
|
|
181
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
182
|
+
self.ActItem(
|
|
183
|
+
self.ActItem.SET,
|
|
184
|
+
'LAN_WLAN_MSSIDENTRY',
|
|
185
|
+
'1,3,1,0,0,0',
|
|
186
|
+
attrs=['enable={}'.format(int(enable))]),
|
|
187
|
+
]
|
|
188
|
+
self.req_act(acts)
|
|
189
|
+
|
|
190
|
+
def reboot(self) -> None:
|
|
191
|
+
# CGI 7 et [ACT_REBOOT#0,0,0,0,0,0#0,0,0,0,0,0]0,0
|
|
192
|
+
|
|
193
|
+
acts = [
|
|
194
|
+
self.ActItem(self.ActItem.OP, 'ACT_REBOOT'),
|
|
195
|
+
]
|
|
196
|
+
_, values = self.req_act(acts)
|
|
197
|
+
|
|
198
|
+
# print(values.keys())
|
tplinkrouterc6u/client/c6u.py
CHANGED
|
@@ -490,3 +490,100 @@ class TplinkRouter(TplinkEncryption, TplinkBaseRouter):
|
|
|
490
490
|
self._url_pptpd = 'admin/pptpd?form=config'
|
|
491
491
|
self._url_vpnconn_openvpn = 'admin/vpnconn?form=config'
|
|
492
492
|
self._url_vpnconn_pptpd = 'admin/vpnconn?form=config'
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class TplinkRouterV1_11(TplinkBaseRouter):
|
|
496
|
+
"""
|
|
497
|
+
Router client for newer TP-Link firmware (1.11.0+) that uses simplified
|
|
498
|
+
RSA-only authentication without AES encryption wrapper.
|
|
499
|
+
|
|
500
|
+
Based on fix from: https://github.com/AlexandrErohin/TP-Link-Archer-C6U/issues/90
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
|
|
504
|
+
verify_ssl: bool = True, timeout: int = 30) -> None:
|
|
505
|
+
super().__init__(host, password, username, logger, verify_ssl, timeout)
|
|
506
|
+
self._pwdNN = ''
|
|
507
|
+
self._pwdEE = ''
|
|
508
|
+
|
|
509
|
+
def supports(self) -> bool:
|
|
510
|
+
"""Check if this client can handle the router (new firmware with RSA-only auth)."""
|
|
511
|
+
if len(self.password) > 125:
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
self._request_pwd()
|
|
516
|
+
# V1_11 uses 2048-bit RSA = 512 hex chars, older firmware uses 1024-bit = 256 chars
|
|
517
|
+
return len(self._pwdNN) >= 512
|
|
518
|
+
except Exception:
|
|
519
|
+
return False
|
|
520
|
+
|
|
521
|
+
def _request_pwd(self) -> None:
|
|
522
|
+
"""Get RSA public key for password encryption."""
|
|
523
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=keys'.format(self.host)
|
|
524
|
+
|
|
525
|
+
response = post(
|
|
526
|
+
url,
|
|
527
|
+
params={'operation': 'read'},
|
|
528
|
+
timeout=self.timeout,
|
|
529
|
+
verify=self._verify_ssl,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
data = response.json()
|
|
534
|
+
self._pwdNN = data[self._data_block]['password'][0]
|
|
535
|
+
self._pwdEE = data[self._data_block]['password'][1]
|
|
536
|
+
except Exception as e:
|
|
537
|
+
error = ('TplinkRouter - {} - Failed to get encryption keys! Error - {}; Response - {}'
|
|
538
|
+
.format(self.__class__.__name__, e, response.text))
|
|
539
|
+
if self._logger:
|
|
540
|
+
self._logger.debug(error)
|
|
541
|
+
raise ClientException(error)
|
|
542
|
+
|
|
543
|
+
def authorize(self) -> None:
|
|
544
|
+
"""Authorize using simplified RSA-only authentication (no AES encryption)."""
|
|
545
|
+
if self._pwdNN == '':
|
|
546
|
+
self._request_pwd()
|
|
547
|
+
|
|
548
|
+
# RSA encrypt password using existing utility
|
|
549
|
+
encrypted_pwd = EncryptionWrapper.rsa_encrypt(self.password, self._pwdNN, self._pwdEE)
|
|
550
|
+
|
|
551
|
+
# Simple login - just operation=login&password=<HEX>
|
|
552
|
+
url = '{}/cgi-bin/luci/;stok=/login?form=login'.format(self.host)
|
|
553
|
+
response = post(
|
|
554
|
+
url,
|
|
555
|
+
data='operation=login&password={}'.format(encrypted_pwd),
|
|
556
|
+
headers=self._headers_login,
|
|
557
|
+
timeout=self.timeout,
|
|
558
|
+
verify=self._verify_ssl,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
try:
|
|
562
|
+
data = response.json()
|
|
563
|
+
if not data.get('success'):
|
|
564
|
+
error_info = data.get(self._data_block, {})
|
|
565
|
+
raise ClientException(
|
|
566
|
+
'TplinkRouter - {} - Login failed: {}'.format(
|
|
567
|
+
self.__class__.__name__,
|
|
568
|
+
error_info.get('errorcode', 'unknown error')
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
self._stok = data[self._data_block]['stok']
|
|
573
|
+
|
|
574
|
+
# Get sysauth cookie
|
|
575
|
+
if 'set-cookie' in response.headers:
|
|
576
|
+
regex_result = search(r'sysauth=([^;]+)', response.headers['set-cookie'])
|
|
577
|
+
if regex_result:
|
|
578
|
+
self._sysauth = regex_result.group(1)
|
|
579
|
+
|
|
580
|
+
self._logged = True
|
|
581
|
+
|
|
582
|
+
except ClientException:
|
|
583
|
+
raise
|
|
584
|
+
except Exception as e:
|
|
585
|
+
error = ('TplinkRouter - {} - Cannot authorize! Error - {}; Response - {}'
|
|
586
|
+
.format(self.__class__.__name__, e, response.text))
|
|
587
|
+
if self._logger:
|
|
588
|
+
self._logger.debug(error)
|
|
589
|
+
raise ClientException(error)
|
tplinkrouterc6u/client/ex.py
CHANGED
|
@@ -92,7 +92,7 @@ class TPLinkEXClient(TPLinkMRClientBase):
|
|
|
92
92
|
self.ActItem(self.ActItem.GL, 'DEV2_ADT_LAN', attrs=['MACAddress', 'IPAddress']),
|
|
93
93
|
self.ActItem(self.ActItem.GL, 'DEV2_ADT_WAN',
|
|
94
94
|
attrs=['enable', 'MACAddr', 'connIPv4Address', 'connIPv4Gateway']),
|
|
95
|
-
self.ActItem(self.ActItem.GL, 'DEV2_ADT_WIFI_COMMON'
|
|
95
|
+
self.ActItem(self.ActItem.GL, 'DEV2_ADT_WIFI_COMMON'),
|
|
96
96
|
self.ActItem(self.ActItem.GL, 'DEV2_HOST_ENTRY',
|
|
97
97
|
attrs=['active', 'X_TP_LanConnType', 'physAddress', 'IPAddress', 'hostName']),
|
|
98
98
|
self.ActItem(self.ActItem.GO, 'DEV2_MEM_STATUS', attrs=['total', 'free']),
|
|
@@ -114,17 +114,17 @@ class TPLinkEXClient(TPLinkMRClientBase):
|
|
|
114
114
|
status._wan_ipv4_addr = IPv4Address(item['connIPv4Address']) if item.get('connIPv4Address') else None
|
|
115
115
|
status._wan_ipv4_gateway = IPv4Address(item['connIPv4Gateway']) if item.get('connIPv4Address') else None
|
|
116
116
|
|
|
117
|
-
if values[2]
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
if values[2]:
|
|
118
|
+
if values[2].__class__ != list:
|
|
119
|
+
status.wifi_2g_enable = bool(int(values[2]['primaryEnable']))
|
|
120
|
+
status.guest_2g_enable = bool(int(values[2]['guestEnable'])) if values[2].get('guestEnable') else None
|
|
121
|
+
else:
|
|
122
|
+
status.wifi_2g_enable = bool(int(values[2][0]['primaryEnable']))
|
|
123
|
+
status.wifi_5g_enable = bool(int(values[2][1]['primaryEnable']))
|
|
124
|
+
status.guest_2g_enable = bool(int(values[2][0]['guestEnable'])) \
|
|
125
|
+
if values[2][0].get('guestEnable') else None
|
|
126
|
+
status.guest_5g_enable = bool(int(values[2][1]['guestEnable'])) \
|
|
127
|
+
if values[2][1].get('guestEnable') else None
|
|
128
128
|
|
|
129
129
|
devices = {}
|
|
130
130
|
for val in self._to_list(values[3]):
|
tplinkrouterc6u/client/mr.py
CHANGED
|
@@ -2,7 +2,7 @@ from hashlib import md5
|
|
|
2
2
|
from re import search
|
|
3
3
|
from time import time, sleep
|
|
4
4
|
from urllib.parse import quote
|
|
5
|
-
from requests import Session
|
|
5
|
+
from requests import Session, Response
|
|
6
6
|
from datetime import timedelta, datetime
|
|
7
7
|
from macaddress import EUI48
|
|
8
8
|
from ipaddress import IPv4Address
|
|
@@ -23,6 +23,7 @@ from tplinkrouterc6u.common.dataclass import (
|
|
|
23
23
|
)
|
|
24
24
|
from tplinkrouterc6u.common.exception import ClientException, ClientError
|
|
25
25
|
from tplinkrouterc6u.client_abstract import AbstractRouter
|
|
26
|
+
from typing import List
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class TPLinkMRClientBase(AbstractRouter):
|
|
@@ -54,6 +55,10 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
54
55
|
Connection.GUEST_5G: '1,2,1,0,0,0',
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
# Router name to be included in logs for example,
|
|
59
|
+
# or to be redefined by subclasses.
|
|
60
|
+
ROUTER_NAME = "TP Link Router MR"
|
|
61
|
+
|
|
57
62
|
class ActItem:
|
|
58
63
|
GET = 1
|
|
59
64
|
SET = 2
|
|
@@ -65,7 +70,9 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
65
70
|
CGI = 8
|
|
66
71
|
|
|
67
72
|
def __init__(self, type: int, oid: str, stack: str = '0,0,0,0,0,0', pstack: str = '0,0,0,0,0,0',
|
|
68
|
-
attrs
|
|
73
|
+
attrs=None):
|
|
74
|
+
if attrs is None:
|
|
75
|
+
attrs = []
|
|
69
76
|
self.type = type
|
|
70
77
|
self.oid = oid
|
|
71
78
|
self.stack = stack
|
|
@@ -111,6 +118,10 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
111
118
|
self._token = self._req_token()
|
|
112
119
|
self._authorized_at = datetime.now()
|
|
113
120
|
|
|
121
|
+
# Fake implementation of all abstract methods
|
|
122
|
+
def logout(self) -> None:
|
|
123
|
+
pass
|
|
124
|
+
|
|
114
125
|
def reboot(self) -> None:
|
|
115
126
|
acts = [
|
|
116
127
|
self.ActItem(self.ActItem.OP, 'ACT_REBOOT')
|
|
@@ -215,9 +226,9 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
215
226
|
|
|
216
227
|
return status
|
|
217
228
|
|
|
218
|
-
def get_ipv4_reservations(self) -> [IPv4Reservation]:
|
|
229
|
+
def get_ipv4_reservations(self) -> List[IPv4Reservation]:
|
|
219
230
|
acts = [
|
|
220
|
-
self.ActItem(self.ActItem.GL, 'LAN_DHCP_STATIC_ADDR'
|
|
231
|
+
self.ActItem(self.ActItem.GL, 'LAN_DHCP_STATIC_ADDR'),
|
|
221
232
|
]
|
|
222
233
|
_, values = self.req_act(acts)
|
|
223
234
|
|
|
@@ -227,13 +238,13 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
227
238
|
IPv4Reservation(
|
|
228
239
|
EUI48(item['chaddr']),
|
|
229
240
|
IPv4Address(item['yiaddr']),
|
|
230
|
-
'',
|
|
241
|
+
item.get('description', ''),
|
|
231
242
|
bool(int(item['enable']))
|
|
232
243
|
))
|
|
233
244
|
|
|
234
245
|
return ipv4_reservations
|
|
235
246
|
|
|
236
|
-
def get_ipv4_dhcp_leases(self) -> [IPv4DHCPLease]:
|
|
247
|
+
def get_ipv4_dhcp_leases(self) -> List[IPv4DHCPLease]:
|
|
237
248
|
acts = [
|
|
238
249
|
self.ActItem(self.ActItem.GL, 'LAN_HOST_ENTRY', attrs=['IPAddress', 'MACAddress', 'hostName',
|
|
239
250
|
'leaseTimeRemaining']),
|
|
@@ -324,12 +335,11 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
324
335
|
|
|
325
336
|
self.req_act(acts)
|
|
326
337
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
act_data = []
|
|
338
|
+
@staticmethod
|
|
339
|
+
def _fill_acts(acts: list) -> tuple[List[str], List[str]]:
|
|
340
|
+
# Fill the act lists, to refactor for other subclasses.
|
|
341
|
+
act_types: List[str] = []
|
|
342
|
+
act_data: List[str] = []
|
|
333
343
|
|
|
334
344
|
for act in acts:
|
|
335
345
|
act_types.append(str(act.type))
|
|
@@ -342,13 +352,18 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
342
352
|
'\r\n'.join(act.attrs)
|
|
343
353
|
))
|
|
344
354
|
|
|
355
|
+
return act_types, act_data
|
|
356
|
+
|
|
357
|
+
def req_act(self, acts: list):
|
|
358
|
+
act_types, act_data = self._fill_acts(acts)
|
|
359
|
+
|
|
345
360
|
data = '&'.join(act_types) + '\r\n' + ''.join(act_data)
|
|
346
361
|
|
|
347
362
|
url = self._get_url('cgi_gdpr')
|
|
348
363
|
(code, response) = self._request(url, data_str=data, encrypt=True)
|
|
349
364
|
|
|
350
365
|
if code != 200:
|
|
351
|
-
error =
|
|
366
|
+
error = self.ROUTER_NAME + ' - Response with error; Request {} - Response {}'.format(data, response)
|
|
352
367
|
if self._logger:
|
|
353
368
|
self._logger.debug(error)
|
|
354
369
|
raise ClientError(error)
|
|
@@ -391,7 +406,11 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
391
406
|
|
|
392
407
|
return result if result else []
|
|
393
408
|
|
|
394
|
-
def _get_url(self, endpoint: str, params: dict =
|
|
409
|
+
def _get_url(self, endpoint: str, params: dict = None, include_ts: bool = True) -> str:
|
|
410
|
+
# check if there is any param
|
|
411
|
+
if params is None:
|
|
412
|
+
params = {}
|
|
413
|
+
|
|
395
414
|
# add timestamp param
|
|
396
415
|
if include_ts:
|
|
397
416
|
params['_'] = str(round(time() * 1000))
|
|
@@ -410,14 +429,14 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
410
429
|
)
|
|
411
430
|
|
|
412
431
|
def _req_token(self):
|
|
413
|
-
|
|
432
|
+
"""
|
|
414
433
|
Requests the TokenID, used for CGI authentication (together with cookies)
|
|
415
434
|
- token is inlined as JS var in the index (/) html page
|
|
416
435
|
e.g.: <script type="text/javascript">var token="086724f57013f16e042e012becf825";</script>
|
|
417
436
|
|
|
418
437
|
Return value:
|
|
419
438
|
TokenID string
|
|
420
|
-
|
|
439
|
+
"""
|
|
421
440
|
url = self._get_url('')
|
|
422
441
|
(code, response) = self._request(url, method='GET')
|
|
423
442
|
assert code == 200
|
|
@@ -430,12 +449,12 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
430
449
|
return result.group(1)
|
|
431
450
|
|
|
432
451
|
def _req_rsa_key(self):
|
|
433
|
-
|
|
452
|
+
"""
|
|
434
453
|
Requests the RSA public key from the host
|
|
435
454
|
|
|
436
455
|
Return value:
|
|
437
456
|
((n, e), seq) tuple
|
|
438
|
-
|
|
457
|
+
"""
|
|
439
458
|
response = ''
|
|
440
459
|
try:
|
|
441
460
|
url = self._get_url(self._url_rsa_key)
|
|
@@ -459,7 +478,7 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
459
478
|
assert seq.isnumeric()
|
|
460
479
|
|
|
461
480
|
except Exception as e:
|
|
462
|
-
error = ('
|
|
481
|
+
error = (self.ROUTER_NAME + '- {} - Unknown error rsa_key! Error - {}; Response - {}'
|
|
463
482
|
.format(self.__class__.__name__, e, response))
|
|
464
483
|
if self._logger:
|
|
465
484
|
self._logger.debug(error)
|
|
@@ -468,7 +487,7 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
468
487
|
return nn, ee, int(seq)
|
|
469
488
|
|
|
470
489
|
def _req_login(self) -> None:
|
|
471
|
-
|
|
490
|
+
"""
|
|
472
491
|
Authenticates to the host
|
|
473
492
|
- sets the session token after successful login
|
|
474
493
|
- data/signature is passed as a GET parameter, NOT as a raw request data
|
|
@@ -476,7 +495,7 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
476
495
|
|
|
477
496
|
Example session token (set as a cookie):
|
|
478
497
|
{'JSESSIONID': '4d786fede0164d7613411c7b6ec61e'}
|
|
479
|
-
|
|
498
|
+
"""
|
|
480
499
|
# encrypt username + password
|
|
481
500
|
|
|
482
501
|
sign, data = self._prepare_data(self.username + '\n' + self.password, True)
|
|
@@ -501,12 +520,12 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
501
520
|
info = search('var currAuthTimes=(.*);\nvar currForbidTime=(.*);', response)
|
|
502
521
|
assert info is not None
|
|
503
522
|
|
|
504
|
-
error =
|
|
523
|
+
error = self.ROUTER_NAME + ' - Login failed, wrong password. Auth times: {}/5, Forbid time: {}'.format(
|
|
505
524
|
info.group(1), info.group(2))
|
|
506
525
|
elif ret_code == self.HTTP_ERR_USER_BAD_REQUEST:
|
|
507
|
-
error =
|
|
526
|
+
error = self.ROUTER_NAME + ' - Login failed. Generic error code: {}'.format(ret_code)
|
|
508
527
|
elif ret_code != self.HTTP_RET_OK:
|
|
509
|
-
error =
|
|
528
|
+
error = self.ROUTER_NAME + ' - Login failed. Unknown error code: {}'.format(ret_code)
|
|
510
529
|
|
|
511
530
|
if error:
|
|
512
531
|
if self._logger:
|
|
@@ -514,14 +533,14 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
514
533
|
raise ClientException(error)
|
|
515
534
|
|
|
516
535
|
def _request(self, url, method='POST', data_str=None, encrypt=False, is_login=False):
|
|
517
|
-
|
|
536
|
+
"""
|
|
518
537
|
Prepares and sends an HTTP request to the host
|
|
519
538
|
- sets up the headers, handles token auth
|
|
520
539
|
- encrypts/decrypts the data, if needed
|
|
521
540
|
|
|
522
541
|
Return value:
|
|
523
542
|
(status_code, response_text) tuple
|
|
524
|
-
|
|
543
|
+
"""
|
|
525
544
|
headers = self.HEADERS
|
|
526
545
|
|
|
527
546
|
# add referer to request headers,
|
|
@@ -541,6 +560,8 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
541
560
|
data = data_str
|
|
542
561
|
|
|
543
562
|
retry = 0
|
|
563
|
+
r = Response()
|
|
564
|
+
|
|
544
565
|
while retry < self.REQUEST_RETRIES:
|
|
545
566
|
# send the request
|
|
546
567
|
if method == 'POST':
|
|
@@ -566,12 +587,12 @@ class TPLinkMRClientBase(AbstractRouter):
|
|
|
566
587
|
return r.status_code, r.text
|
|
567
588
|
|
|
568
589
|
def _parse_ret_val(self, response_text):
|
|
569
|
-
|
|
590
|
+
"""
|
|
570
591
|
Parses $.ret value from the response text
|
|
571
592
|
|
|
572
593
|
Return value:
|
|
573
594
|
return code (int)
|
|
574
|
-
|
|
595
|
+
"""
|
|
575
596
|
result = search(r'\$\.ret=(.*);', response_text)
|
|
576
597
|
assert result is not None
|
|
577
598
|
assert result.group(1).isnumeric()
|
|
@@ -618,6 +639,8 @@ class TPLinkMRClientBaseGCM(TPLinkMRClientBase):
|
|
|
618
639
|
data = data_str
|
|
619
640
|
|
|
620
641
|
retry = 0
|
|
642
|
+
r = Response()
|
|
643
|
+
|
|
621
644
|
while retry < self.REQUEST_RETRIES:
|
|
622
645
|
# send the request
|
|
623
646
|
if method == 'POST':
|
|
@@ -656,9 +679,9 @@ class TPLinkMRClientBaseGCM(TPLinkMRClientBase):
|
|
|
656
679
|
class TPLinkMRClient(TPLinkMRClientBase):
|
|
657
680
|
|
|
658
681
|
def logout(self) -> None:
|
|
659
|
-
|
|
682
|
+
"""
|
|
660
683
|
Logs out from the host
|
|
661
|
-
|
|
684
|
+
"""
|
|
662
685
|
if self._token is None:
|
|
663
686
|
return
|
|
664
687
|
|
|
@@ -684,7 +707,7 @@ class TPLinkMRClient(TPLinkMRClientBase):
|
|
|
684
707
|
]
|
|
685
708
|
self.req_act(acts)
|
|
686
709
|
|
|
687
|
-
def get_sms(self) -> [SMS]:
|
|
710
|
+
def get_sms(self) -> List[SMS]:
|
|
688
711
|
acts = [
|
|
689
712
|
self.ActItem(
|
|
690
713
|
self.ActItem.SET, 'LTE_SMS_RECVMSGBOX', attrs=['PageNumber=1']),
|
tplinkrouterc6u/client/mr200.py
CHANGED
|
@@ -57,7 +57,10 @@ class TPLinkMR200Client(TPLinkMRClient):
|
|
|
57
57
|
]
|
|
58
58
|
_, values = self.req_act(acts)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
if len(values) == 0:
|
|
61
|
+
status.ipsecvpn_enable = False
|
|
62
|
+
else:
|
|
63
|
+
status.ipsecvpn_enable = values.get('enable') == '1'
|
|
61
64
|
|
|
62
65
|
return status
|
|
63
66
|
|