tplinkrouterc6u 5.0.3__py3-none-any.whl → 5.1.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.
@@ -0,0 +1,177 @@
1
+ from base64 import b64decode
2
+ from json import dumps
3
+ from macaddress import EUI48
4
+ from ipaddress import IPv4Address
5
+ from logging import Logger
6
+ from tplinkrouterc6u.common.helper import get_ip, get_mac
7
+ from tplinkrouterc6u.common.package_enum import Connection
8
+ from tplinkrouterc6u.common.dataclass import Firmware, Status, Device, IPv4Status
9
+ from tplinkrouterc6u.client_abstract import AbstractRouter
10
+ from tplinkrouterc6u.client.api_cgi_bin import TplinkEncryption
11
+
12
+
13
+ class TPLinkDecoClient(TplinkEncryption, AbstractRouter):
14
+ def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
15
+ verify_ssl: bool = True, timeout: int = 30) -> None:
16
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
17
+
18
+ self._headers_request = {'Content-Type': 'application/json'}
19
+ self._headers_login = {'Content-Type': 'application/json'}
20
+ self._data_block = 'result'
21
+ self.devices = []
22
+
23
+ def logout(self) -> None:
24
+ self.request('admin/system?form=logout', dumps({'operation': 'logout'}), True)
25
+ self._stok = ''
26
+ self._sysauth = ''
27
+ self._logged = False
28
+
29
+ def set_wifi(self, wifi: Connection, enable: bool) -> None:
30
+ en = {'enable': enable}
31
+ if Connection.HOST_2G == wifi:
32
+ params = {'band2_4': {'host': en}}
33
+ elif Connection.HOST_5G == wifi:
34
+ params = {'band5_1': {'host': en}}
35
+ elif Connection.GUEST_5G == wifi:
36
+ params = {'band5_1': {'guest': en}}
37
+ elif Connection.HOST_6G == wifi:
38
+ params = {'band6': {'host': en}}
39
+ elif Connection.GUEST_6G == wifi:
40
+ params = {'band6': {'guest': en}}
41
+ else:
42
+ params = {'band2_4': {'guest': en}}
43
+
44
+ self.request('admin/wireless?form=wlan', dumps({'operation': 'write', 'params': params}))
45
+
46
+ def reboot(self) -> None:
47
+ if not self.devices:
48
+ self.get_firmware()
49
+ self.request('admin/device?form=system', dumps({
50
+ 'operation': 'reboot',
51
+ 'params': {'mac_list': [{"mac": item['mac']} for item in self.devices]}}))
52
+
53
+ def get_firmware(self) -> Firmware:
54
+ self.devices = self.request('admin/device?form=device_list', dumps({"operation": "read"})).get(
55
+ 'device_list', [])
56
+
57
+ for item in self.devices:
58
+ if item.get('role') != 'master' and len(self.devices) != 1:
59
+ continue
60
+ firmware = Firmware(item.get('hardware_ver', ''),
61
+ item.get('device_model', ''),
62
+ item.get('software_ver', ''))
63
+
64
+ return firmware
65
+
66
+ def get_status(self) -> Status:
67
+ data = self.request('admin/network?form=wan_ipv4', dumps({'operation': 'read'}))
68
+
69
+ status = Status()
70
+ element = self._get_value(data, ['wan', 'ip_info', 'mac'])
71
+ status._wan_macaddr = EUI48(element) if element else None
72
+ status._lan_macaddr = EUI48(self._get_value(data, ['lan', 'ip_info', 'mac']))
73
+ element = self._get_value(data, ['wan', 'ip_info', 'ip'])
74
+ status._wan_ipv4_addr = IPv4Address(element) if element else None
75
+ element = self._get_value(data, ['lan', 'ip_info', 'ip'])
76
+ status._lan_ipv4_addr = IPv4Address(element) if element else None
77
+ element = self._get_value(data, ['wan', 'ip_info', 'gateway'])
78
+ status._wan_ipv4_gateway = IPv4Address(element) if element else None
79
+
80
+ data = self.request('admin/network?form=performance', dumps({"operation": "read"}))
81
+ status.mem_usage = data.get('mem_usage')
82
+ status.cpu_usage = data.get('cpu_usage')
83
+
84
+ data = self.request('admin/wireless?form=wlan', dumps({'operation': 'read'}))
85
+ status.wifi_2g_enable = self._get_value(data, ['band2_4', 'host', 'enable'])
86
+ status.guest_2g_enable = self._get_value(data, ['band2_4', 'guest', 'enable'])
87
+ status.wifi_5g_enable = self._get_value(data, ['band5_1', 'host', 'enable'])
88
+ status.guest_5g_enable = self._get_value(data, ['band5_1', 'guest', 'enable'])
89
+ status.wifi_6g_enable = self._get_value(data, ['band6', 'host', 'enable'])
90
+ status.guest_6g_enable = self._get_value(data, ['band6', 'guest', 'enable'])
91
+
92
+ devices = []
93
+ data = self.request('admin/client?form=client_list', dumps(
94
+ {"operation": "read", "params": {"device_mac": "default"}})).get('client_list', [])
95
+
96
+ for item in data:
97
+ if not item.get('online'):
98
+ continue
99
+ conn = self._map_wire_type(item)
100
+ if conn == Connection.WIRED:
101
+ status.wired_total += 1
102
+ elif conn.is_host_wifi():
103
+ status.wifi_clients_total += 1
104
+ elif conn.is_guest_wifi():
105
+ status.guest_clients_total += 1
106
+ elif conn.is_iot():
107
+ if status.iot_clients_total is None:
108
+ status.iot_clients_total = 0
109
+ status.iot_clients_total += 1
110
+
111
+ device = Device(conn,
112
+ get_mac(item.get('mac', '00:00:00:00:00:00')),
113
+ get_ip(item.get('ip', '0.0.0.0')),
114
+ b64decode(item['name']).decode())
115
+ device.down_speed = item.get('down_speed')
116
+ device.up_speed = item.get('up_speed')
117
+ devices.append(device)
118
+
119
+ status.clients_total = (status.wired_total + status.wifi_clients_total + status.guest_clients_total
120
+ + (0 if status.iot_clients_total is None else status.iot_clients_total))
121
+ status.devices = devices
122
+
123
+ return status
124
+
125
+ def get_ipv4_status(self) -> IPv4Status:
126
+ ipv4_status = IPv4Status()
127
+ data = self.request('admin/network?form=wan_ipv4', dumps({'operation': 'read'}))
128
+ ipv4_status._wan_macaddr = EUI48(self._get_value(data, ['wan', 'ip_info', 'mac']))
129
+ element = self._get_value(data, ['wan', 'ip_info', 'ip'])
130
+ ipv4_status._wan_ipv4_ipaddr = IPv4Address(element) if element else None
131
+ element = self._get_value(data, ['wan', 'ip_info', 'gateway'])
132
+ ipv4_status._wan_ipv4_gateway = IPv4Address(element) if element else None
133
+ ipv4_status.wan_ipv4_conntype = self._get_value(data, ['wan', 'dial_type'])
134
+ element = self._get_value(data, ['wan', 'ip_info', 'mask'])
135
+ ipv4_status._wan_ipv4_netmask = IPv4Address(element) if element else None
136
+ ipv4_status._wan_ipv4_pridns = IPv4Address(self._get_value(data, ['wan', 'ip_info', 'dns1']))
137
+ ipv4_status._wan_ipv4_snddns = IPv4Address(self._get_value(data, ['wan', 'ip_info', 'dns2']))
138
+ ipv4_status._lan_macaddr = EUI48(self._get_value(data, ['lan', 'ip_info', 'mac']))
139
+ ipv4_status._lan_ipv4_ipaddr = IPv4Address(self._get_value(data, ['lan', 'ip_info', 'ip']))
140
+ ipv4_status.lan_ipv4_dhcp_enable = False
141
+ ipv4_status._lan_ipv4_netmask = IPv4Address(self._get_value(data, ['lan', 'ip_info', 'mask']))
142
+
143
+ return ipv4_status
144
+
145
+ @staticmethod
146
+ def _get_value(dictionary: dict, keys: list):
147
+ nested_dict = dictionary
148
+
149
+ for key in keys:
150
+ try:
151
+ nested_dict = nested_dict[key]
152
+ except Exception:
153
+ return None
154
+ return nested_dict
155
+
156
+ def _map_wire_type(self, data: dict) -> Connection:
157
+ if data.get('wire_type') == 'wired':
158
+ return Connection.WIRED
159
+ mapping = {'band2_4': {'main': Connection.HOST_2G, 'guest': Connection.GUEST_2G, 'iot': Connection.IOT_2G},
160
+ 'band5': {'main': Connection.HOST_5G, 'guest': Connection.GUEST_5G, 'iot': Connection.IOT_5G},
161
+ 'band6': {'main': Connection.HOST_6G, 'guest': Connection.GUEST_6G, 'iot': Connection.IOT_6G}
162
+ }
163
+ result = self._get_value(mapping, [data.get('connection_type'), data.get('interface')])
164
+
165
+ return result if result else Connection.UNKNOWN
166
+
167
+ @staticmethod
168
+ def _get_login_data(crypted_pwd: str) -> str:
169
+ data = {
170
+ "params": {"password": crypted_pwd},
171
+ "operation": "login",
172
+ }
173
+
174
+ return dumps(data)
175
+
176
+ def _is_valid_response(self, data: dict) -> bool:
177
+ return 'error_code' in data and data['error_code'] == 0
@@ -0,0 +1,295 @@
1
+ from base64 import b64encode
2
+ from json import loads
3
+ from datetime import timedelta
4
+ from macaddress import EUI48
5
+ from ipaddress import IPv4Address
6
+ from logging import Logger
7
+ from tplinkrouterc6u.common.package_enum import Connection
8
+ from tplinkrouterc6u.common.dataclass import Firmware, Status, Device, IPv4Reservation, IPv4DHCPLease, IPv4Status
9
+ from tplinkrouterc6u.common.exception import ClientException, ClientError
10
+ from tplinkrouterc6u.client.mr import TPLinkMRClientBase
11
+
12
+
13
+ class TPLinkEXClient(TPLinkMRClientBase):
14
+ WIFI_SET = {
15
+ Connection.HOST_2G: '1,0,0,0,0,0',
16
+ Connection.HOST_5G: '2,0,0,0,0,0',
17
+ Connection.GUEST_2G: '1,0,0,0,0,0',
18
+ Connection.GUEST_5G: '2,0,0,0,0,0',
19
+ }
20
+
21
+ class ActItem:
22
+ GET = 'go'
23
+ GO = 'go'
24
+ SET = 'so'
25
+ ADD = 'add'
26
+ DEL = 'del'
27
+ GL = 'gl'
28
+ GS = 'gs'
29
+ OP = 'op'
30
+ CGI = 'cgi'
31
+
32
+ def __init__(self, type: str, oid: str, stack: str = '0,0,0,0,0,0', pstack: str = '0,0,0,0,0,0',
33
+ attrs: list = []):
34
+ self.type = type
35
+ self.oid = oid
36
+ self.stack = stack
37
+ self.pstack = pstack
38
+ self.attrs = attrs
39
+
40
+ def __init__(self, host: str, password: str, username: str = 'user', logger: Logger = None,
41
+ verify_ssl: bool = True, timeout: int = 30) -> None:
42
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
43
+
44
+ self.username = 'user'
45
+ self._url_rsa_key = 'cgi/getGDPRParm'
46
+
47
+ def logout(self) -> None:
48
+ '''
49
+ Logs out from the host
50
+ '''
51
+ acts = [
52
+ self.ActItem(self.ActItem.CGI, '/cgi/logout')
53
+ ]
54
+
55
+ response, _ = self.req_act(acts)
56
+
57
+ if response == '':
58
+ self._token = None
59
+
60
+ def get_firmware(self) -> Firmware:
61
+ acts = [
62
+ self.ActItem(self.ActItem.GET, 'DEV2_DEV_INFO', attrs=[
63
+ 'hardwareVersion',
64
+ 'modelName',
65
+ 'softwareVersion'
66
+ ])
67
+ ]
68
+ _, values = self.req_act(acts)
69
+
70
+ if not values:
71
+ raise ValueError('No firmware information received.')
72
+
73
+ firmware = Firmware(
74
+ values[0].get('hardwareVersion', ''),
75
+ values[0].get('modelName', ''),
76
+ values[0].get('softwareVersion', '')
77
+ )
78
+
79
+ return firmware
80
+
81
+ def get_status(self) -> Status:
82
+ status = Status()
83
+ acts = [
84
+ self.ActItem(self.ActItem.GL, 'DEV2_ADT_LAN', attrs=['MACAddress', 'IPAddress']),
85
+ self.ActItem(self.ActItem.GL, 'DEV2_ADT_WAN',
86
+ attrs=['enable', 'MACAddr', 'connIPv4Address', 'connIPv4Gateway']),
87
+ self.ActItem(self.ActItem.GL, 'DEV2_ADT_WIFI_COMMON', attrs=['primaryEnable', 'guestEnable']),
88
+ self.ActItem(self.ActItem.GL, 'DEV2_HOST_ENTRY',
89
+ attrs=['active', 'X_TP_LanConnType', 'physAddress', 'IPAddress', 'hostName']),
90
+ self.ActItem(self.ActItem.GO, 'DEV2_MEM_STATUS', attrs=['total', 'free']),
91
+ self.ActItem(self.ActItem.GO, 'DEV2_PROC_STATUS', attrs=['CPUUsage']),
92
+ ]
93
+
94
+ _, values = self.req_act(acts)
95
+
96
+ if values[0].__class__ == list:
97
+ values[0] = values[0][0]
98
+
99
+ status._lan_macaddr = EUI48(values[0]['MACAddress'])
100
+ status._lan_ipv4_addr = IPv4Address(values[0]['IPAddress'])
101
+
102
+ for item in values[1]:
103
+ if int(item['enable']) == 0 and values[1].__class__ == list:
104
+ continue
105
+ status._wan_macaddr = EUI48(item['MACAddr']) if item['MACAddr'] else None
106
+ status._wan_ipv4_addr = IPv4Address(item['connIPv4Address'])
107
+ status._wan_ipv4_gateway = IPv4Address(item['connIPv4Gateway'])
108
+
109
+ if values[2].__class__ != list:
110
+ status.wifi_2g_enable = bool(int(values[2]['primaryEnable']))
111
+ else:
112
+ status.wifi_2g_enable = bool(int(values[2][0]['primaryEnable']))
113
+ status.wifi_5g_enable = bool(int(values[2][1]['primaryEnable']))
114
+
115
+ if values[2].__class__ != list:
116
+ status.guest_2g_enable = bool(int(values[2]['guestEnable']))
117
+ else:
118
+ status.guest_2g_enable = bool(int(values[2][0]['guestEnable']))
119
+ status.guest_5g_enable = bool(int(values[2][1]['guestEnable']))
120
+
121
+ devices = {}
122
+ for val in self._to_list(values[3]):
123
+ if int(val['active']) == 0:
124
+ continue
125
+ conn = self.CLIENT_TYPES.get(int(val['X_TP_LanConnType']))
126
+ if conn is None:
127
+ continue
128
+ elif conn == Connection.WIRED:
129
+ status.wired_total += 1
130
+ elif conn.is_guest_wifi():
131
+ status.guest_clients_total += 1
132
+ elif conn.is_host_wifi():
133
+ status.wifi_clients_total += 1
134
+ devices[val['physAddress']] = Device(conn,
135
+ EUI48(val['physAddress']),
136
+ IPv4Address(val['IPAddress']),
137
+ val['hostName'])
138
+
139
+ total = int(values[4]['total'])
140
+ free = int(values[4]["free"])
141
+ status.mem_usage = ((total - free) / total)
142
+
143
+ status.cpu_usage = int(values[5]['CPUUsage']) / 100
144
+
145
+ status.devices = list(devices.values())
146
+ status.clients_total = status.wired_total + status.wifi_clients_total + status.guest_clients_total
147
+
148
+ return status
149
+
150
+ def get_ipv4_reservations(self) -> [IPv4Reservation]:
151
+ acts = [
152
+ self.ActItem(self.ActItem.GL, 'DEV2_DHCPV4_POOL_STATICADDR', attrs=['enable', 'chaddr', 'yiaddr']),
153
+ ]
154
+ _, values = self.req_act(acts)
155
+
156
+ ipv4_reservations = []
157
+ for item in values[0]:
158
+ ipv4_reservations.append(
159
+ IPv4Reservation(
160
+ EUI48(item['chaddr']),
161
+ IPv4Address(item['yiaddr']),
162
+ '',
163
+ bool(int(item['enable']))
164
+ ))
165
+
166
+ return ipv4_reservations
167
+
168
+ def get_ipv4_dhcp_leases(self) -> [IPv4DHCPLease]:
169
+ acts = [
170
+ self.ActItem(self.ActItem.GL, 'DEV2_HOST_ENTRY',
171
+ attrs=['IPAddress', 'physAddress', 'hostName', 'leaseTimeRemaining']),
172
+ ]
173
+ _, values = self.req_act(acts)
174
+
175
+ dhcp_leases = []
176
+ for item in values[0]:
177
+ lease_time = item['leaseTimeRemaining']
178
+ dhcp_leases.append(
179
+ IPv4DHCPLease(
180
+ EUI48(item['physAddress']),
181
+ IPv4Address(item['IPAddress']),
182
+ item['hostName'],
183
+ str(timedelta(seconds=int(lease_time))) if (lease_time.isdigit()
184
+ and int(lease_time)) > 0 else 'Permanent',
185
+ ))
186
+
187
+ return dhcp_leases
188
+
189
+ def get_ipv4_status(self) -> IPv4Status:
190
+ acts = [
191
+ self.ActItem(self.ActItem.GL, 'DEV2_ADT_LAN',
192
+ attrs=['MACAddress', 'IPAddress', 'IPSubnetMask', 'DHCPv4Enable']),
193
+ self.ActItem(self.ActItem.GL, 'DEV2_ADT_WAN',
194
+ attrs=['enable', 'MACAddr', 'connIPv4Address', 'connIPv4Gateway', 'name', 'connIPv4SubnetMask',
195
+ 'connIPv4DnsServer']),
196
+ ]
197
+ _, values = self.req_act(acts)
198
+
199
+ if values[0].__class__ == list:
200
+ values[0] = values[0][0]
201
+
202
+ ipv4_status = IPv4Status()
203
+ ipv4_status._lan_macaddr = EUI48(values[0]['MACAddress'])
204
+ ipv4_status._lan_ipv4_ipaddr = IPv4Address(values[0]['IPAddress'])
205
+ ipv4_status._lan_ipv4_netmask = IPv4Address(values[0]['IPSubnetMask'])
206
+ ipv4_status.lan_ipv4_dhcp_enable = bool(int(values[0]['DHCPv4Enable']))
207
+
208
+ for item in values[1]:
209
+ if int(item['enable']) == 0 and values[1].__class__ == list:
210
+ continue
211
+ ipv4_status._wan_macaddr = EUI48(item['MACAddr'])
212
+ ipv4_status._wan_ipv4_ipaddr = IPv4Address(item['connIPv4Address'])
213
+ ipv4_status._wan_ipv4_gateway = IPv4Address(item['connIPv4Gateway'])
214
+ ipv4_status.wan_ipv4_conntype = item['name']
215
+ ipv4_status._wan_ipv4_netmask = IPv4Address(item['connIPv4SubnetMask'])
216
+ dns = item['connIPv4DnsServer'].split(',')
217
+ ipv4_status._wan_ipv4_pridns = IPv4Address(dns[0])
218
+ ipv4_status._wan_ipv4_snddns = IPv4Address(dns[1])
219
+
220
+ return ipv4_status
221
+
222
+ def set_wifi(self, wifi: Connection, enable: bool) -> None:
223
+ atr = [f'"primaryEnable":"{int(enable)}"' if 'GUEST' not in str(wifi) else f'"guestEnable":"{int(enable)}"']
224
+ acts = [
225
+ self.ActItem(
226
+ self.ActItem.SET,
227
+ 'DEV2_ADT_WIFI_COMMON',
228
+ self.WIFI_SET[wifi],
229
+ attrs=atr),
230
+ ]
231
+ self.req_act(acts)
232
+
233
+ def req_act(self, acts: list):
234
+ '''
235
+ Requests ACTs via the cgi_gdpr proxy
236
+ '''
237
+
238
+ all_responses = []
239
+ url = self._get_url('cgi_gdpr?9')
240
+
241
+ for act in acts:
242
+ attrs_str = ', '.join([attr if ':' in attr else f'"{attr}":""' for attr in act.attrs])
243
+ tp_data = \
244
+ (f'{{"data":{{"stack":"{act.stack}","pstack":"{act.pstack}"{"," + attrs_str if attrs_str else ""}}},'
245
+ f'"operation":"{act.type}","oid":"{act.oid}"}}')
246
+
247
+ code, response = self._request(url, data_str=tp_data, encrypt=True)
248
+ response = response.replace("\r", "").replace("\n", "").replace("\t", "")
249
+
250
+ if code != 200:
251
+ error = 'TplinkRouter - EX - Response with error; Request {} - Response {}'.format(tp_data, response)
252
+ if self._logger:
253
+ self._logger.debug(error)
254
+ raise ClientError(error)
255
+
256
+ try:
257
+ if len(response):
258
+ json_data = loads(response)
259
+ if 'data' in json_data:
260
+ all_responses.append(json_data['data'])
261
+ except ValueError:
262
+ raise ClientError(f"Error trying to convert response to JSON: {response}")
263
+
264
+ return response, all_responses
265
+
266
+ def _req_login(self) -> None:
267
+ login_data = ('{"data":{"UserName":"%s","Passwd":"%s","Action": "1","stack":"0,0,0,0,0,0",'
268
+ '"pstack":"0,0,0,0,0,0"},"operation":"cgi","oid":"/cgi/login"}') % (
269
+ b64encode(bytes(self.username, "utf-8")).decode("utf-8"),
270
+ b64encode(bytes(self.password, "utf-8")).decode("utf-8")
271
+ )
272
+
273
+ sign, data = self._prepare_data(login_data, True)
274
+ assert len(sign) == 256
275
+
276
+ request_data = f"sign={sign}\r\ndata={data}\r\n"
277
+
278
+ url = f"{self.host}/cgi_gdpr?9"
279
+ (code, response) = self._request(url, data_str=request_data)
280
+ response = self._encryption.aes_decrypt(response)
281
+
282
+ # parse and match return code
283
+ ret_code = self._parse_ret_val(response)
284
+ error = ''
285
+ if ret_code == self.HTTP_ERR_USER_PWD_NOT_CORRECT:
286
+ error = 'TplinkRouter - EX - Login failed, wrong user or password.'
287
+ elif ret_code == self.HTTP_ERR_USER_BAD_REQUEST:
288
+ error = 'TplinkRouter - EX - Login failed. Generic error code: {}'.format(ret_code)
289
+ elif ret_code != self.HTTP_RET_OK:
290
+ error = 'TplinkRouter - EX - Login failed. Unknown error code: {}'.format(ret_code)
291
+
292
+ if error:
293
+ if self._logger:
294
+ self._logger.debug(error)
295
+ raise ClientException(error)