From 9e8ab807c527d12fa1d9ecfb497104316e81b77b Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 17:37:05 +0200 Subject: [PATCH 01/55] Remove total_payload buggy and unused --- smrt/network.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/smrt/network.py b/smrt/network.py index 2ed44ab..4f5da5d 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -39,7 +39,7 @@ def __init__(self, switch_mac, host_mac, ip_address): ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) ss.bind((ip_address, UDP_RECEIVE_FROM_PORT)) - + # Receiving socket rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) rs.bind((BROADCAST_ADDR, UDP_RECEIVE_FROM_PORT)) @@ -65,23 +65,18 @@ def send(self, op_code, payload): def receive(self): try: - fragment_offset = 1 - total_payload = b'' - while fragment_offset: - data, addr = self.rs.recvfrom(1500) - data = decode(data) - header, payload = split(data) - header, payload = interpret_header(header), interpret_payload(payload) - fragment_offset = header['fragment_offset'] - logger.debug('Received Header: ' + str(header)) - logger.debug('Received Payload: ' + str(payload)) - self.header['token_id'] = header['token_id'] - total_payload += payload + data, addr = self.rs.recvfrom(1500) + data = decode(data) + header, payload = split(data) + header, payload = interpret_header(header), interpret_payload(payload) + logger.debug('Received Header: ' + str(header)) + logger.debug('Received Payload: ' + str(payload)) + self.header['token_id'] = header['token_id'] return header, payload except: - print("no response") - raise ConnectionProblem() - return {}, {} + print("no response") + raise ConnectionProblem() + return {}, {} def query(self, op_code, payload): self.send(op_code, payload) From 4878ac7329ce38f22d7601cea8a69e1f31eba26e Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 17:37:26 +0200 Subject: [PATCH 02/55] Add 'status' cmd --- smrt/smrt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smrt/smrt.py b/smrt/smrt.py index e797160..ef972a1 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -38,8 +38,8 @@ def main(): if args.action == 'query_port_mirror': header, payload = sc.query(GET, query_port_mirror_payload()) print(payload[16640]) - #elif args.action == 'status': - # header, payload = sc.query(GET, query_port_mirror_payload()) + elif args.action == 'status': + header, payload = sc.query(GET, {4096: b''}) if __name__ == "__main__": main() From d44f96d97c98dffd17f5355e816cf842175008a6 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 18:47:40 +0200 Subject: [PATCH 03/55] Add missing id 13 --- smrt/protocol.py | 1 + 1 file changed, 1 insertion(+) diff --git a/smrt/protocol.py b/smrt/protocol.py index f38f405..e71d161 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -78,6 +78,7 @@ 8: ['*s', 'str', 'hardware_version'], 9: ['?', 'bool', 'dhcp'], 10: ['b', 'dec', 'num_ports'], + 13: ['?', 'bool', 'v4'], 512: ['*s', 'str', 'username'], 514: ['*s', 'str', 'password'], 2304: ['a', 'action','save'], From 3cea5c06c8d3ad2b11e5605f0ba768b856978350 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 18:48:05 +0200 Subject: [PATCH 04/55] Change OrderedDict in list --- smrt/protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index e71d161..8b4b78c 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -176,12 +176,12 @@ def interpret_header(header): return dict(zip(names, vals)) def interpret_payload(payload): - results = OrderedDict() + results = [] while len(payload) > len(PACKET_END): dtype, dlen = struct.unpack('!hh', payload[0:4]) data = payload[4:4+dlen] payload = payload[4+dlen:] - results[dtype] = data + results.append( (dtype, data) ) return results def assemble_packet(header, payload): From 1391062bbd576e93b8c10aa33dab449254e040ac Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 18:55:11 +0200 Subject: [PATCH 05/55] Adapt discovery --- smrt/discovery.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 975242e..22fc54c 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -57,8 +57,6 @@ def discover_switches(interfaces='all'): ss.sendto(packet, (BROADCAST_ADDR, UDP_SEND_TO_PORT)) ss.close() - - while True: try: data, addr = rs.recvfrom(1500) @@ -78,7 +76,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('interfaces', metavar='INTERFACE', nargs='*', default='all') args = parser.parse_args() - logging.basicConfig(level=logging.WARNING) + # logging.basicConfig(level=logging.WARNING) switches = discover_switches(interfaces=args.interfaces) for context, header, payload in switches: get = lambda kind: get_payload_item_value(payload, kind) From 14b75fc502eab5872b97286c580ecf8bb4b2cfc6 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 22:45:34 +0200 Subject: [PATCH 06/55] Adapt get_payload_item_context for using list --- smrt/protocol.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index 8b4b78c..093df5f 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -212,12 +212,12 @@ def interpret_value(value, kind): return value def get_payload_item_context(items, name_key): - hits = [key for key in items.keys() if receive_ids[key][2] == name_key] + hits = [x for x in items if receive_ids[x[0]][2] == name_key] assert len(hits) == 1 - item_id = hits[0] + item_id = hits[0][0] kind = receive_ids[item_id][1] - raw_value = items[item_id] + raw_value = hits[0][1] value = interpret_value(raw_value, kind) return { From 23fdd4b8459d4d68a411d0b860988691e83a66a6 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 22:46:46 +0200 Subject: [PATCH 07/55] Use integer for cmd --- smrt/smrt.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/smrt/smrt.py b/smrt/smrt.py index ef972a1..ed79728 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -35,11 +35,19 @@ def main(): sc.login(args.username, args.password) - if args.action == 'query_port_mirror': - header, payload = sc.query(GET, query_port_mirror_payload()) - print(payload[16640]) - elif args.action == 'status': - header, payload = sc.query(GET, {4096: b''}) + #if args.action == 'query_port_mirror': + # header, payload = sc.query(GET, query_port_mirror_payload()) + # print(payload[16640]) + #elif args.action == 'stat': + # header, payload = sc.query(GET, {4096: b''}) + #elif args.action == 'vlan': + # header, payload = sc.query(GET, {8707: b''}) + #elif args.action == 'vlans': + # header, payload = sc.query(GET, {8705: b''}) + #elif args.action == 'pvid': + # header, payload = sc.query(GET, {8706: b''}) + header, payload = sc.query(GET, {int(args.action): b''}) + print(payload) if __name__ == "__main__": main() From e832167c7ffd78f365285da5b6191191db605a97 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 20 Oct 2020 23:42:06 +0200 Subject: [PATCH 08/55] Decode vlan and pvid --- smrt/protocol.py | 16 +++++++++++++--- smrt/smrt.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index 093df5f..39a3dcd 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -88,8 +88,9 @@ 4608: ['5s', 'hex', 'port_trunk'], 8192: ['2s', 'hex', 'mtu_vlan'], 8704: ['?', 'hex', '802.1q vlan enabled'], - 8705: ['*s', 'hex', '802.1q vlan'], - 8706: ['*s', 'hex', '802.1q vlan pvid'], + 8705: ['*s', 'vlan', '802.1q vlan'], + 8706: ['*s', 'pvid', '802.1q vlan pvid'], + 8707: ['*s', 'str', '802.1q vlan filler'], 12288: ['?', 'bool', 'QoS Basic 1'], 12289: ['2s', 'hex', 'QoS Basic 2'], 16640: ['10s','hex', 'port_mirror'], @@ -205,6 +206,10 @@ def interpret_value(value, kind): value = "n/a" elif kind == 'dec': value = int(''.join('%02X' % byte for byte in value), 16) + elif kind == 'vlan': + value = (int(value[1]), bin(value[5]), bin(value[9]), bin(value[5] & ~value[9]), value[10:-1].decode('ascii')) + elif kind == 'pvid': + value = (int(value[0]), int(value[2])) elif kind == 'bool': if len(value) == 0: pass elif len(value) == 1: value = value[0] > 0 @@ -220,7 +225,7 @@ def get_payload_item_context(items, name_key): raw_value = hits[0][1] value = interpret_value(raw_value, kind) - return { + ret = { 'id': item_id, 'struct_fmt': receive_ids[item_id][0], 'kind': kind, @@ -228,6 +233,11 @@ def get_payload_item_context(items, name_key): 'value': value, 'raw_value': raw_value, } + print("R", ret) + return ret + +def decode_payload(payload_list): + return [(receive_ids[x[0]][2], interpret_value(x[1], receive_ids[x[0]][1])) for x in payload_list] def get_payload_item_value(items, name_key): context = get_payload_item_context(items, name_key) diff --git a/smrt/smrt.py b/smrt/smrt.py index ed79728..f9a48b1 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -47,7 +47,7 @@ def main(): #elif args.action == 'pvid': # header, payload = sc.query(GET, {8706: b''}) header, payload = sc.query(GET, {int(args.action): b''}) - print(payload) + print(*decode_payload(payload), sep="\n") if __name__ == "__main__": main() From 3e9fcd070d62ad8c3ad4c09f69051dcc17a71696 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 18:31:21 +0100 Subject: [PATCH 09/55] Refactoring --- smrt/__init__.py | 9 --------- smrt/operations.py | 6 +----- smrt/protocol.py | 31 +++++++++++++------------------ smrt/smrt.py | 24 ++++++++++++------------ 4 files changed, 26 insertions(+), 44 deletions(-) diff --git a/smrt/__init__.py b/smrt/__init__.py index 7b5a59c..0942ccc 100644 --- a/smrt/__init__.py +++ b/smrt/__init__.py @@ -1,12 +1,3 @@ - class SmrtPyException(Exception): pass - class ConnectionException(SmrtPyException): pass - class IncompatiblePlatformException(SmrtPyException): pass - -#from . import protocol -#from . import operations -#from . import network -#from . import discovery -#from . import smrt diff --git a/smrt/operations.py b/smrt/operations.py index 55529c1..191e5f0 100644 --- a/smrt/operations.py +++ b/smrt/operations.py @@ -1,8 +1,4 @@ - -from .protocol import * - -def query_port_mirror_payload(): - return { get_id('port_mirror'): b'' } +from .protocol import get_id def login_payload(username, password): username = username.encode('utf-8') + b'\x00' diff --git a/smrt/protocol.py b/smrt/protocol.py index 39a3dcd..125298e 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -1,9 +1,5 @@ """ -TP-Link TL-SG105E and TL-SG108E switces -UDP configuration protocol - -Wireshark filter: -ip.addr ==192.168.178.250 || ip.addr ==192.168.178.254 && udp.port == 29808 +TP-Link TL-SG105E and TL-SG108E switches """ import struct @@ -88,8 +84,8 @@ 4608: ['5s', 'hex', 'port_trunk'], 8192: ['2s', 'hex', 'mtu_vlan'], 8704: ['?', 'hex', '802.1q vlan enabled'], - 8705: ['*s', 'vlan', '802.1q vlan'], - 8706: ['*s', 'pvid', '802.1q vlan pvid'], + 8705: ['*s', 'vlan', '802.1q vlan'], + 8706: ['*s', 'pvid', '802.1q vlan pvid'], 8707: ['*s', 'str', '802.1q vlan filler'], 12288: ['?', 'bool', 'QoS Basic 1'], 12289: ['2s', 'hex', 'QoS Basic 2'], @@ -98,15 +94,15 @@ 17152: ['?', 'bool', 'loop_prevention'], } +ids = {v[2]: k for k, v in receive_ids.items()} + def get_sequence_kind(sequence): for key, value in sequences.items(): if value == sequence: return key return 'unknown' def get_id(name): - for key, value in receive_ids.items(): - if value[2] == name: return key - raise Exception() + return ids[name] def hex_readable(bts): return ':'.join(['{:02X}'.format(byte) for byte in bts]) @@ -117,6 +113,7 @@ def payload_str(payload): items = interpret_payload(payload) else: items = payload + for item_id in items.keys(): value = items[item_id] try: @@ -153,15 +150,13 @@ def decode(data): 65, 154, 141, 122, 34, 140, 128, 238, 88, 89, 9, 146, 171, 149, 53, 102, 61, 114, 69, 217, 175, 103, 228, 35, 180, 252, 200, 192, 165, 159, 221, 244, 110, 119, 48] - length = len(data) - #s = [bytes([char]) for char in key] s = key - i, j = 0, 0 - for k in range(length): - i = (k + 1) % 256; - j = (j + s[i]) % 256; + j = 0 + for k in range(len(data)): + i = (k + 1) & 255 + j = (j + s[i]) & 255 s[i], s[j] = s[j], s[i] - data[k] = data[k] ^ s[(s[i] + s[j]) % 256]; + data[k] = data[k] ^ s[(s[i] + s[j]) & 255] return bytes(data) encode = decode @@ -233,7 +228,7 @@ def get_payload_item_context(items, name_key): 'value': value, 'raw_value': raw_value, } - print("R", ret) + # print("R", ret) return ret def decode_payload(payload_list): diff --git a/smrt/smrt.py b/smrt/smrt.py index f9a48b1..742ddcd 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -35,18 +35,18 @@ def main(): sc.login(args.username, args.password) - #if args.action == 'query_port_mirror': - # header, payload = sc.query(GET, query_port_mirror_payload()) - # print(payload[16640]) - #elif args.action == 'stat': - # header, payload = sc.query(GET, {4096: b''}) - #elif args.action == 'vlan': - # header, payload = sc.query(GET, {8707: b''}) - #elif args.action == 'vlans': - # header, payload = sc.query(GET, {8705: b''}) - #elif args.action == 'pvid': - # header, payload = sc.query(GET, {8706: b''}) - header, payload = sc.query(GET, {int(args.action): b''}) + actions = { + "stat": 4096, + "mirror": 16640, + "vlan": 8704, + "pvid": 8706, + } + + if args.action in actions: + header, payload = sc.query(GET, {actions[args.action]: b''}) + else: + header, payload = sc.query(GET, {int(args.action): b''}) + print(*decode_payload(payload), sep="\n") if __name__ == "__main__": From 71ace333670046e272c7d0a41f7e090c8d6ac346 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 22:04:28 +0100 Subject: [PATCH 10/55] Create Protocol object --- smrt/network.py | 51 +++-- smrt/operations.py | 9 - smrt/protocol.py | 466 +++++++++++++++++++++++---------------------- smrt/smrt.py | 13 +- 4 files changed, 269 insertions(+), 270 deletions(-) diff --git a/smrt/network.py b/smrt/network.py index 4f5da5d..9d6fa6e 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -2,22 +2,15 @@ import socket, random, logging -from .protocol import * -from .operations import * - -BROADCAST_ADDR = "255.255.255.255" -UDP_SEND_TO_PORT = 29808 -UDP_RECEIVE_FROM_PORT = 29809 +from .protocol import Protocol logger = logging.getLogger(__name__) -def mac_to_bytes(mac): - return bytes(int(byte, 16) for byte in mac.split(':')) - -def mac_to_str(mac): - return ':'.join('{:02X}'.format(byte) for byte in mac) +class Network: -class SwitchConversation(object): + BROADCAST_ADDR = "255.255.255.255" + UDP_SEND_TO_PORT = 29808 + UDP_RECEIVE_FROM_PORT = 29809 def __init__(self, switch_mac, host_mac, ip_address): @@ -27,26 +20,32 @@ def __init__(self, switch_mac, host_mac, ip_address): self.sequence_id = random.randint(0, 1000) - header = DEFAULT_HEADER.copy() + header = Protocol.DEFAULT_HEADER.copy() header.update({ 'sequence_id': self.sequence_id, - 'host_mac': mac_to_bytes(host_mac), - 'switch_mac': mac_to_bytes(switch_mac), + 'host_mac': Network.mac_to_bytes(host_mac), + 'switch_mac': Network.mac_to_bytes(switch_mac), }) self.header = header # Sending socket ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - ss.bind((ip_address, UDP_RECEIVE_FROM_PORT)) + ss.bind((ip_address, Network.UDP_RECEIVE_FROM_PORT)) # Receiving socket rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.bind((BROADCAST_ADDR, UDP_RECEIVE_FROM_PORT)) + rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) rs.settimeout(0.4) self.ss, self.rs = ss, rs + def mac_to_bytes(mac): + return bytes(int(byte, 16) for byte in mac.split(':')) + + def mac_to_str(mac): + return ':'.join('{:02X}'.format(byte) for byte in mac) + def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 @@ -58,17 +57,17 @@ def send(self, op_code, payload): logger.debug('Sending Header: ' + str(header)) logger.debug('Sending Payload: ' + str(payload)) - packet = assemble_packet(header, payload) - packet = encode(packet) + packet = Protocol.assemble_packet(header, payload) + packet = Protocol.encode(packet) - self.ss.sendto(packet, (BROADCAST_ADDR, UDP_SEND_TO_PORT)) + self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) def receive(self): try: data, addr = self.rs.recvfrom(1500) - data = decode(data) - header, payload = split(data) - header, payload = interpret_header(header), interpret_payload(payload) + data = Protocol.decode(data) + header, payload = Protocol.split(data) + header, payload = Protocol.interpret_header(header), Protocol.interpret_payload(payload) logger.debug('Received Header: ' + str(header)) logger.debug('Received Payload: ' + str(payload)) self.header['token_id'] = header['token_id'] @@ -81,11 +80,11 @@ def receive(self): def query(self, op_code, payload): self.send(op_code, payload) header, payload = self.receive() - sequence_kind = get_sequence_kind((op_code, header['op_code'])) + sequence_kind = Protocol.get_sequence_kind((op_code, header['op_code'])) logger.debug('Sequence kind: ' + sequence_kind) return header, payload def login(self, username, password): - self.query(GET, get_token_id_payload()) - self.query(LOGIN, login_payload(username, password)) + self.query(Protocol.GET, Protocol.get_dict_id("get_token_id")) + self.query(Protocol.LOGIN, Protocol.login_payload(username, password)) diff --git a/smrt/operations.py b/smrt/operations.py index 191e5f0..e69de29 100644 --- a/smrt/operations.py +++ b/smrt/operations.py @@ -1,9 +0,0 @@ -from .protocol import get_id - -def login_payload(username, password): - username = username.encode('utf-8') + b'\x00' - password = password.encode('utf-8') + b'\x00' - return { get_id('username'): username, get_id('password'): password } - -def get_token_id_payload(): - return { get_id('get_token_id'): b'' } diff --git a/smrt/protocol.py b/smrt/protocol.py index 125298e..8d1496e 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -6,234 +6,244 @@ from ipaddress import ip_address from collections import OrderedDict -HEADER_LEN = 32 -PACKET_END = b'\xff\xff\x00\x00' - -DEFAULT_HEADER = { - 'fragment_offset': 0, - 'sequence_id': 0, - 'checksum': 0, - 'switch_mac': b'\x00\x00\x00\x00\x00\x00', - 'check_length': 0, - 'token_id': 0, - 'op_code': 0, - 'error_code': 0, - 'host_mac': b'\x00\x00\x00\x00\x00\x00', - 'version': 1, - 'flag': 0 -} - -header_structure = { - 'fmt': '!bb6s6shihhhhi', - 'designators': [ - 'version', - 'op_code', - 'switch_mac', - 'host_mac', - 'sequence_id', - 'error_code', - 'check_length', - 'fragment_offset', - 'flag', - 'token_id', - 'checksum', - ] -} - -DISCOVERY = 0 -GET = 1 -SET = 2 -LOGIN = 3 -RETURN = 4 -READ5 = 5 - -op_codes = { - DISCOVERY: 'DISCOVERY', - GET: 'GET', - SET: 'SET', - LOGIN: 'LOGIN', - RETURN: 'RETURN', - READ5: 'READ5' -} - -sequences = { - # name ->switch switch-> - 'login/change': (LOGIN, RETURN), - 'query': (GET, SET), - 'discover': (DISCOVERY, SET), -} - -receive_ids = { - 1: ['*s', 'str', 'type'], - 2: ['*s', 'str', 'hostname'], - 3: ['6s', 'hex', 'mac'], - 4: ['4s', 'ip', 'ip_addr'], - 5: ['4s', 'ip', 'ip_mask'], - 6: ['4s', 'ip', 'gateway'], - 7: ['*s', 'str', 'firmware_version'], - 8: ['*s', 'str', 'hardware_version'], - 9: ['?', 'bool', 'dhcp'], - 10: ['b', 'dec', 'num_ports'], - 13: ['?', 'bool', 'v4'], - 512: ['*s', 'str', 'username'], - 514: ['*s', 'str', 'password'], - 2304: ['a', 'action','save'], - 2305: ['a', 'action','get_token_id'], - 4352: ['?', 'bool', 'igmp_snooping'], - 4096: ['*s', 'hex', 'port_settings'], - 4608: ['5s', 'hex', 'port_trunk'], - 8192: ['2s', 'hex', 'mtu_vlan'], - 8704: ['?', 'hex', '802.1q vlan enabled'], - 8705: ['*s', 'vlan', '802.1q vlan'], - 8706: ['*s', 'pvid', '802.1q vlan pvid'], - 8707: ['*s', 'str', '802.1q vlan filler'], - 12288: ['?', 'bool', 'QoS Basic 1'], - 12289: ['2s', 'hex', 'QoS Basic 2'], - 16640: ['10s','hex', 'port_mirror'], - 16384: ['*s', 'hex', 'port_statistics'], - 17152: ['?', 'bool', 'loop_prevention'], -} - -ids = {v[2]: k for k, v in receive_ids.items()} - -def get_sequence_kind(sequence): - for key, value in sequences.items(): - if value == sequence: return key - return 'unknown' - -def get_id(name): - return ids[name] - -def hex_readable(bts): - return ':'.join(['{:02X}'.format(byte) for byte in bts]) - -def payload_str(payload): - ret = '' - if type(payload) == bytes: - items = interpret_payload(payload) - else: - items = payload - - for item_id in items.keys(): - value = items[item_id] - try: - value = interpret_value(value, receive_ids[item_id][1]) - except: - pass - if item_id not in receive_ids: - ret += 'Unknown code: %s (content: %s)\n' % (item_id, value) - continue - struct_fmt = receive_ids[item_id][0] - kind = receive_ids[item_id][1] - name = receive_ids[item_id][2] - fmt = '{name}: {value} (id: {id}, kind: {kind})\n' - ret += fmt.format(name=name, value=value, id=item_id, kind=kind) - return ret - -def decode(data): - data = list(data) - key = [ 191, 155, 227, 202, 99, 162, 79, 104, 49, 18, 190, 164, 30, - 76, 189, 131, 23, 52, 86, 106, 207, 125, 126, 169, 196, 28, 172, 58, - 188, 132, 160, 3, 36, 120, 144, 168, 12, 231, 116, 44, 41, 97, 108, - 213, 42, 198, 32, 148, 218, 107, 247, 112, 204, 14, 66, 68, 91, 224, - 206, 235, 33, 130, 203, 178, 1, 134, 199, 78, 249, 123, 7, 145, 73, - 208, 209, 100, 74, 115, 72, 118, 8, 22, 243, 147, 64, 96, 5, 87, 60, - 113, 233, 152, 31, 219, 143, 174, 232, 153, 245, 158, 254, 70, 170, - 75, 77, 215, 211, 59, 71, 133, 214, 157, 151, 6, 46, 81, 94, 136, - 166, 210, 4, 43, 241, 29, 223, 176, 67, 63, 186, 137, 129, 40, 248, - 255, 55, 15, 62, 183, 222, 105, 236, 197, 127, 54, 179, 194, 229, - 185, 37, 90, 237, 184, 25, 156, 173, 26, 187, 220, 2, 225, 0, 240, - 50, 251, 212, 253, 167, 17, 193, 205, 177, 21, 181, 246, 82, 226, - 38, 101, 163, 182, 242, 92, 20, 11, 95, 13, 230, 16, 121, 124, 109, - 195, 117, 39, 98, 239, 84, 56, 139, 161, 47, 201, 51, 135, 250, 10, - 19, 150, 45, 111, 27, 24, 142, 80, 85, 83, 234, 138, 216, 57, 93, - 65, 154, 141, 122, 34, 140, 128, 238, 88, 89, 9, 146, 171, 149, 53, - 102, 61, 114, 69, 217, 175, 103, 228, 35, 180, 252, 200, 192, 165, - 159, 221, 244, 110, 119, 48] - s = key - j = 0 - for k in range(len(data)): - i = (k + 1) & 255 - j = (j + s[i]) & 255 - s[i], s[j] = s[j], s[i] - data[k] = data[k] ^ s[(s[i] + s[j]) & 255] - return bytes(data) - -encode = decode - -def split(data): - assert len(data) >= HEADER_LEN + len(PACKET_END) - assert data.endswith(PACKET_END) - return (data[0:HEADER_LEN], data[HEADER_LEN:]) - -def interpret_header(header): - names = header_structure['designators'] - vals = struct.unpack(header_structure['fmt'], header) - return dict(zip(names, vals)) - -def interpret_payload(payload): - results = [] - while len(payload) > len(PACKET_END): - dtype, dlen = struct.unpack('!hh', payload[0:4]) - data = payload[4:4+dlen] - payload = payload[4+dlen:] - results.append( (dtype, data) ) - return results - -def assemble_packet(header, payload): - payload_bytes = b'' - for dtype, value in payload.items(): - payload_bytes += struct.pack('!hh', dtype, len(value)) - payload_bytes += value - header['check_length'] = HEADER_LEN + len(payload_bytes) + len(PACKET_END) - header = tuple(header[part] for part in header_structure['designators']) - header_bytes = struct.pack(header_structure['fmt'], *header) - return header_bytes + payload_bytes + PACKET_END - -def interpret_value(value, kind): - if kind == 'str': - value = value.split(b'\x00', 1)[0].decode('ascii') - elif kind == 'ip': - value = ip_address(value) - elif kind == 'hex': - value = ':'.join(['{:02X}'.format(byte) for byte in value]) - elif kind == 'action': - value = "n/a" - elif kind == 'dec': - value = int(''.join('%02X' % byte for byte in value), 16) - elif kind == 'vlan': - value = (int(value[1]), bin(value[5]), bin(value[9]), bin(value[5] & ~value[9]), value[10:-1].decode('ascii')) - elif kind == 'pvid': - value = (int(value[0]), int(value[2])) - elif kind == 'bool': - if len(value) == 0: pass - elif len(value) == 1: value = value[0] > 0 - else: raise AssertionError('boolean should be one byte long') - return value - -def get_payload_item_context(items, name_key): - hits = [x for x in items if receive_ids[x[0]][2] == name_key] - assert len(hits) == 1 - item_id = hits[0][0] - - kind = receive_ids[item_id][1] - raw_value = hits[0][1] - value = interpret_value(raw_value, kind) - - ret = { - 'id': item_id, - 'struct_fmt': receive_ids[item_id][0], - 'kind': kind, - 'name': receive_ids[item_id][2], - 'value': value, - 'raw_value': raw_value, +class Protocol: + + HEADER_LEN = 32 + PACKET_END = b'\xff\xff\x00\x00' + + DEFAULT_HEADER = { + 'fragment_offset': 0, + 'sequence_id': 0, + 'checksum': 0, + 'switch_mac': b'\x00\x00\x00\x00\x00\x00', + 'check_length': 0, + 'token_id': 0, + 'op_code': 0, + 'error_code': 0, + 'host_mac': b'\x00\x00\x00\x00\x00\x00', + 'version': 1, + 'flag': 0 + } + + header_structure = { + 'fmt': '!bb6s6shihhhhi', + 'designators': [ + 'version', + 'op_code', + 'switch_mac', + 'host_mac', + 'sequence_id', + 'error_code', + 'check_length', + 'fragment_offset', + 'flag', + 'token_id', + 'checksum', + ] + } + + DISCOVERY = 0 + GET = 1 + SET = 2 + LOGIN = 3 + RETURN = 4 + READ5 = 5 + + op_codes = { + DISCOVERY: 'DISCOVERY', + GET: 'GET', + SET: 'SET', + LOGIN: 'LOGIN', + RETURN: 'RETURN', + READ5: 'READ5' } - # print("R", ret) - return ret -def decode_payload(payload_list): - return [(receive_ids[x[0]][2], interpret_value(x[1], receive_ids[x[0]][1])) for x in payload_list] + sequences = { + # name ->switch switch-> + 'login/change': (LOGIN, RETURN), + 'query': (GET, SET), + 'discover': (DISCOVERY, SET), + } + + receive_ids = { + 1: ['*s', 'str', 'type'], + 2: ['*s', 'str', 'hostname'], + 3: ['6s', 'hex', 'mac'], + 4: ['4s', 'ip', 'ip_addr'], + 5: ['4s', 'ip', 'ip_mask'], + 6: ['4s', 'ip', 'gateway'], + 7: ['*s', 'str', 'firmware_version'], + 8: ['*s', 'str', 'hardware_version'], + 9: ['?', 'bool', 'dhcp'], + 10: ['b', 'dec', 'num_ports'], + 13: ['?', 'bool', 'v4'], + 512: ['*s', 'str', 'username'], + 514: ['*s', 'str', 'password'], + 2304: ['a', 'action','save'], + 2305: ['a', 'action','get_token_id'], + 4352: ['?', 'bool', 'igmp_snooping'], + 4096: ['*s', 'hex', 'port_settings'], + 4608: ['5s', 'hex', 'port_trunk'], + 8192: ['2s', 'hex', 'mtu_vlan'], + 8704: ['?', 'hex', '802.1q vlan enabled'], + 8705: ['*s', 'vlan', '802.1q vlan'], + 8706: ['*s', 'pvid', '802.1q vlan pvid'], + 8707: ['*s', 'str', '802.1q vlan filler'], + 12288: ['?', 'bool', 'QoS Basic 1'], + 12289: ['2s', 'hex', 'QoS Basic 2'], + 16640: ['10s','hex', 'port_mirror'], + 16384: ['*s', 'hex', 'port_statistics'], + 17152: ['?', 'bool', 'loop_prevention'], + } -def get_payload_item_value(items, name_key): - context = get_payload_item_context(items, name_key) - return context['value'] + ids = {v[2]: k for k, v in receive_ids.items()} + + def get_sequence_kind(sequence): + for key, value in Protocol.sequences.items(): + if value == sequence: return key + return 'unknown' + + def get_id(name): + return Protocol.ids[name] + + def hex_readable(bts): + return ':'.join(['{:02X}'.format(byte) for byte in bts]) + + def payload_str(payload): + ret = '' + if type(payload) == bytes: + items = interpret_payload(payload) + else: + items = payload + + for item_id in items.keys(): + value = items[item_id] + try: + value = interpret_value(value, receive_ids[item_id][1]) + except: + pass + if item_id not in receive_ids: + ret += 'Unknown code: %s (content: %s)\n' % (item_id, value) + continue + struct_fmt = receive_ids[item_id][0] + kind = receive_ids[item_id][1] + name = receive_ids[item_id][2] + fmt = '{name}: {value} (id: {id}, kind: {kind})\n' + ret += fmt.format(name=name, value=value, id=item_id, kind=kind) + return ret + + def decode(data): + data = list(data) + key = [ 191, 155, 227, 202, 99, 162, 79, 104, 49, 18, 190, 164, 30, + 76, 189, 131, 23, 52, 86, 106, 207, 125, 126, 169, 196, 28, 172, 58, + 188, 132, 160, 3, 36, 120, 144, 168, 12, 231, 116, 44, 41, 97, 108, + 213, 42, 198, 32, 148, 218, 107, 247, 112, 204, 14, 66, 68, 91, 224, + 206, 235, 33, 130, 203, 178, 1, 134, 199, 78, 249, 123, 7, 145, 73, + 208, 209, 100, 74, 115, 72, 118, 8, 22, 243, 147, 64, 96, 5, 87, 60, + 113, 233, 152, 31, 219, 143, 174, 232, 153, 245, 158, 254, 70, 170, + 75, 77, 215, 211, 59, 71, 133, 214, 157, 151, 6, 46, 81, 94, 136, + 166, 210, 4, 43, 241, 29, 223, 176, 67, 63, 186, 137, 129, 40, 248, + 255, 55, 15, 62, 183, 222, 105, 236, 197, 127, 54, 179, 194, 229, + 185, 37, 90, 237, 184, 25, 156, 173, 26, 187, 220, 2, 225, 0, 240, + 50, 251, 212, 253, 167, 17, 193, 205, 177, 21, 181, 246, 82, 226, + 38, 101, 163, 182, 242, 92, 20, 11, 95, 13, 230, 16, 121, 124, 109, + 195, 117, 39, 98, 239, 84, 56, 139, 161, 47, 201, 51, 135, 250, 10, + 19, 150, 45, 111, 27, 24, 142, 80, 85, 83, 234, 138, 216, 57, 93, + 65, 154, 141, 122, 34, 140, 128, 238, 88, 89, 9, 146, 171, 149, 53, + 102, 61, 114, 69, 217, 175, 103, 228, 35, 180, 252, 200, 192, 165, + 159, 221, 244, 110, 119, 48] + s = key + j = 0 + for k in range(len(data)): + i = (k + 1) & 255 + j = (j + s[i]) & 255 + s[i], s[j] = s[j], s[i] + data[k] = data[k] ^ s[(s[i] + s[j]) & 255] + return bytes(data) + + encode = decode + + def split(data): + assert len(data) >= Protocol.HEADER_LEN + len(Protocol.PACKET_END) + assert data.endswith(Protocol.PACKET_END) + return (data[0:Protocol.HEADER_LEN], data[Protocol.HEADER_LEN:]) + + def interpret_header(header): + names = Protocol.header_structure['designators'] + vals = struct.unpack(Protocol.header_structure['fmt'], header) + return dict(zip(names, vals)) + + def interpret_payload(payload): + results = [] + while len(payload) > len(Protocol.PACKET_END): + dtype, dlen = struct.unpack('!hh', payload[0:4]) + data = payload[4:4+dlen] + payload = payload[4+dlen:] + results.append( (dtype, data) ) + return results + + def assemble_packet(header, payload): + payload_bytes = b'' + for dtype, value in payload.items(): + payload_bytes += struct.pack('!hh', dtype, len(value)) + payload_bytes += value + header['check_length'] = Protocol.HEADER_LEN + len(payload_bytes) + len(Protocol.PACKET_END) + header = tuple(header[part] for part in Protocol.header_structure['designators']) + header_bytes = struct.pack(Protocol.header_structure['fmt'], *header) + return header_bytes + payload_bytes + Protocol.PACKET_END + + def interpret_value(value, kind): + if kind == 'str': + value = value.split(b'\x00', 1)[0].decode('ascii') + elif kind == 'ip': + value = ip_address(value) + elif kind == 'hex': + value = ':'.join(['{:02X}'.format(byte) for byte in value]) + elif kind == 'action': + value = "n/a" + elif kind == 'dec': + value = int(''.join('%02X' % byte for byte in value), 16) + elif kind == 'vlan': + value = (int(value[1]), bin(value[5]), bin(value[9]), bin(value[5] & ~value[9]), value[10:-1].decode('ascii')) + elif kind == 'pvid': + value = (int(value[0]), int(value[2])) + elif kind == 'bool': + if len(value) == 0: pass + elif len(value) == 1: value = value[0] > 0 + else: raise AssertionError('boolean should be one byte long') + return value + + def get_payload_item_context(items, name_key): + hits = [x for x in items if receive_ids[x[0]][2] == name_key] + assert len(hits) == 1 + item_id = hits[0][0] + + kind = receive_ids[item_id][1] + raw_value = hits[0][1] + value = interpret_value(raw_value, kind) + + ret = { + 'id': item_id, + 'struct_fmt': receive_ids[item_id][0], + 'kind': kind, + 'name': receive_ids[item_id][2], + 'value': value, + 'raw_value': raw_value, + } + # print("R", ret) + return ret + + def decode_payload(payload_list): + return [(Protocol.receive_ids[x[0]][2], Protocol.interpret_value(x[1], Protocol.receive_ids[x[0]][1])) for x in payload_list] + + def get_payload_item_value(items, name_key): + context = Protocol.get_payload_item_context(items, name_key) + return context['value'] + + def login_payload(username, password): + username = username.encode('ascii') + b'\x00' + password = password.encode('ascii') + b'\x00' + return {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + + def get_dict_id(name): + return {Protocol.get_id(name): b''} diff --git a/smrt/smrt.py b/smrt/smrt.py index 742ddcd..83c57df 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -2,9 +2,8 @@ import socket, time, random, argparse, logging -from .protocol import * -from .network import SwitchConversation -from .operations import * +from .protocol import Protocol +from .network import Network def loglevel(x): try: @@ -31,7 +30,7 @@ def main(): host_mac = args.host_mac ip_address = args.ip_address - sc = SwitchConversation(switch_mac, host_mac, ip_address) + sc = Network(switch_mac, host_mac, ip_address) sc.login(args.username, args.password) @@ -43,11 +42,11 @@ def main(): } if args.action in actions: - header, payload = sc.query(GET, {actions[args.action]: b''}) + header, payload = sc.query(Protocol.GET, {actions[args.action]: b''}) else: - header, payload = sc.query(GET, {int(args.action): b''}) + header, payload = sc.query(Protocol.GET, {int(args.action): b''}) - print(*decode_payload(payload), sep="\n") + print(*Protocol.decode_payload(payload), sep="\n") if __name__ == "__main__": main() From 7aa341f3e68708a385ed2616eb2cc75890bcd503 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 22:17:05 +0100 Subject: [PATCH 11/55] Use header --- smrt/network.py | 2 +- smrt/protocol.py | 66 +++++++++++++++++------------------------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/smrt/network.py b/smrt/network.py index 9d6fa6e..8e7b168 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -20,7 +20,7 @@ def __init__(self, switch_mac, host_mac, ip_address): self.sequence_id = random.randint(0, 1000) - header = Protocol.DEFAULT_HEADER.copy() + header = Protocol.header["blank"].copy() header.update({ 'sequence_id': self.sequence_id, 'host_mac': Network.mac_to_bytes(host_mac), diff --git a/smrt/protocol.py b/smrt/protocol.py index 8d1496e..d7a4480 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -1,45 +1,25 @@ -""" -TP-Link TL-SG105E and TL-SG108E switches -""" - import struct from ipaddress import ip_address -from collections import OrderedDict class Protocol: - - HEADER_LEN = 32 PACKET_END = b'\xff\xff\x00\x00' - DEFAULT_HEADER = { - 'fragment_offset': 0, - 'sequence_id': 0, - 'checksum': 0, - 'switch_mac': b'\x00\x00\x00\x00\x00\x00', - 'check_length': 0, - 'token_id': 0, - 'op_code': 0, - 'error_code': 0, - 'host_mac': b'\x00\x00\x00\x00\x00\x00', - 'version': 1, - 'flag': 0 - } - - header_structure = { - 'fmt': '!bb6s6shihhhhi', - 'designators': [ - 'version', - 'op_code', - 'switch_mac', - 'host_mac', - 'sequence_id', - 'error_code', - 'check_length', - 'fragment_offset', - 'flag', - 'token_id', - 'checksum', - ] + header = { + "len": 32, + "fmt": '!bb6s6shihhhhi', + "blank": { + 'version': 1, + 'op_code': 0, + 'switch_mac': b'\x00\x00\x00\x00\x00\x00', + 'host_mac': b'\x00\x00\x00\x00\x00\x00', + 'sequence_id': 0, + 'error_code': 0, + 'check_length': 0, + 'fragment_offset': 0, + 'flag': 0, + 'token_id': 0, + 'checksum': 0, + } } DISCOVERY = 0 @@ -164,13 +144,13 @@ def decode(data): encode = decode def split(data): - assert len(data) >= Protocol.HEADER_LEN + len(Protocol.PACKET_END) + assert len(data) >= Protocol.header["len"] + len(Protocol.PACKET_END) assert data.endswith(Protocol.PACKET_END) - return (data[0:Protocol.HEADER_LEN], data[Protocol.HEADER_LEN:]) + return (data[0:Protocol.header["len"]], data[Protocol.header["len"]:]) def interpret_header(header): - names = Protocol.header_structure['designators'] - vals = struct.unpack(Protocol.header_structure['fmt'], header) + names = Protocol.header['blank'].keys() + vals = struct.unpack(Protocol.header['fmt'], header) return dict(zip(names, vals)) def interpret_payload(payload): @@ -187,9 +167,9 @@ def assemble_packet(header, payload): for dtype, value in payload.items(): payload_bytes += struct.pack('!hh', dtype, len(value)) payload_bytes += value - header['check_length'] = Protocol.HEADER_LEN + len(payload_bytes) + len(Protocol.PACKET_END) - header = tuple(header[part] for part in Protocol.header_structure['designators']) - header_bytes = struct.pack(Protocol.header_structure['fmt'], *header) + header['check_length'] = Protocol.header["len"] + len(payload_bytes) + len(Protocol.PACKET_END) + header = tuple(header[part] for part in Protocol.header['blank'].keys()) + header_bytes = struct.pack(Protocol.header['fmt'], *header) return header_bytes + payload_bytes + Protocol.PACKET_END def interpret_value(value, kind): From dae5da1a42e7c64ecd8cfac14ef88cfc782a3a61 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 22:23:39 +0100 Subject: [PATCH 12/55] Adapt Discovery --- smrt/discovery.py | 26 +++++++++++++------------- smrt/protocol.py | 19 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 22fc54c..80902b6 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -5,8 +5,8 @@ import netifaces from . import IncompatiblePlatformException -from .protocol import * -from .network import * +from .protocol import Protocol +from .network import Network DISCOVERY_TIMEOUT = 0.5 @@ -29,22 +29,22 @@ def discover_switches(interfaces='all'): for iface, ip, mac, broadcast in settings: logger.warning((iface, ip, mac, broadcast)) sequence_id = random.randint(0, 1000) - header = DEFAULT_HEADER.copy() + header = Protocol.header["blank"].copy() header.update({ 'sequence_id': sequence_id, 'host_mac': bytes(int(byte, 16) for byte in mac.split(':')), }) - packet = assemble_packet(header, {}) - packet = encode(packet) + packet = Protocol.assemble_packet(header, {}) + packet = Protocol.encode(packet) if platform.system().lower() == 'darwin': rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) rs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - rs.bind(('', UDP_RECEIVE_FROM_PORT)) + rs.bind(('', Network.UDP_RECEIVE_FROM_PORT)) rs.settimeout(DISCOVERY_TIMEOUT) elif platform.system().lower() == 'linux': rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.bind((BROADCAST_ADDR, UDP_RECEIVE_FROM_PORT)) + rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) #rs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) rs.settimeout(DISCOVERY_TIMEOUT) else: @@ -53,8 +53,8 @@ def discover_switches(interfaces='all'): ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - ss.bind((ip, UDP_RECEIVE_FROM_PORT)) - ss.sendto(packet, (BROADCAST_ADDR, UDP_SEND_TO_PORT)) + ss.bind((ip, Network.UDP_RECEIVE_FROM_PORT)) + ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) ss.close() while True: @@ -62,9 +62,9 @@ def discover_switches(interfaces='all'): data, addr = rs.recvfrom(1500) except: break - data = decode(data) - header, payload = split(data) - payload = interpret_payload(payload) + data = Protocol.decode(data) + header, payload = Protocol.split(data) + payload = Protocol.interpret_payload(payload) context = {'iface': iface, 'ip': ip, 'mac': mac, 'broadcast': broadcast} yield context, header, payload rs.close() @@ -79,7 +79,7 @@ def main(): # logging.basicConfig(level=logging.WARNING) switches = discover_switches(interfaces=args.interfaces) for context, header, payload in switches: - get = lambda kind: get_payload_item_value(payload, kind) + get = lambda kind: Protocol.get_payload_item_value(payload, kind) fmt = "Found a switch: Host: (Interface: {iface:8s} IP: {host_ip} Broadcast: {broadcast})\n" fmt += " Switch: (Kind: {kind:12s} MAC Address: {mac} IP Address: {switch_ip})" print(fmt.format(iface=context['iface'], host_ip=context['ip'], broadcast=context['broadcast'], diff --git a/smrt/protocol.py b/smrt/protocol.py index d7a4480..119e359 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -194,23 +194,22 @@ def interpret_value(value, kind): return value def get_payload_item_context(items, name_key): - hits = [x for x in items if receive_ids[x[0]][2] == name_key] + hits = [x for x in items if Protocol.receive_ids[x[0]][2] == name_key] assert len(hits) == 1 item_id = hits[0][0] - kind = receive_ids[item_id][1] + kind = Protocol.receive_ids[item_id][1] raw_value = hits[0][1] - value = interpret_value(raw_value, kind) + value = Protocol.interpret_value(raw_value, kind) ret = { - 'id': item_id, - 'struct_fmt': receive_ids[item_id][0], - 'kind': kind, - 'name': receive_ids[item_id][2], - 'value': value, - 'raw_value': raw_value, + 'id': item_id, + 'struct_fmt': Protocol.receive_ids[item_id][0], + 'kind': kind, + 'name': Protocol.receive_ids[item_id][2], + 'value': value, + 'raw_value': raw_value, } - # print("R", ret) return ret def decode_payload(payload_list): From 22f101094bfe2f729165c425ce4dbc6e10b3f2d7 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 23:09:06 +0100 Subject: [PATCH 13/55] Interpret value in payload --- smrt/discovery.py | 30 ++++++++++++------------------ smrt/protocol.py | 6 +++++- smrt/smrt.py | 6 ++++-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 80902b6..523b838 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -37,21 +37,14 @@ def discover_switches(interfaces='all'): packet = Protocol.assemble_packet(header, {}) packet = Protocol.encode(packet) - if platform.system().lower() == 'darwin': - rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - rs.bind(('', Network.UDP_RECEIVE_FROM_PORT)) - rs.settimeout(DISCOVERY_TIMEOUT) - elif platform.system().lower() == 'linux': - rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) - #rs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - rs.settimeout(DISCOVERY_TIMEOUT) - else: - raise IncompatiblePlatformException() + # Receiving socket + rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) + rs.settimeout(0.4) + rs.settimeout(DISCOVERY_TIMEOUT) ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) ss.bind((ip, Network.UDP_RECEIVE_FROM_PORT)) ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) @@ -79,10 +72,11 @@ def main(): # logging.basicConfig(level=logging.WARNING) switches = discover_switches(interfaces=args.interfaces) for context, header, payload in switches: - get = lambda kind: Protocol.get_payload_item_value(payload, kind) - fmt = "Found a switch: Host: (Interface: {iface:8s} IP: {host_ip} Broadcast: {broadcast})\n" - fmt += " Switch: (Kind: {kind:12s} MAC Address: {mac} IP Address: {switch_ip})" - print(fmt.format(iface=context['iface'], host_ip=context['ip'], broadcast=context['broadcast'], - kind=get('type'), mac=get('mac'), switch_ip=get('ip_addr'))) + print(context, header, payload) + #get = lambda kind: Protocol.get_payload_item_value(payload, kind) + #fmt = "Found a switch: Host: (Interface: {iface:8s} IP: {host_ip} Broadcast: {broadcast})\n" + #fmt += " Switch: (Kind: {kind:12s} MAC Address: {mac} IP Address: {switch_ip})" + #print(fmt.format(iface=context['iface'], host_ip=context['ip'], broadcast=context['broadcast'], + # kind=get('type'), mac=get('mac'), switch_ip=get('ip_addr'))) if __name__ == "__main__": main() diff --git a/smrt/protocol.py b/smrt/protocol.py index 119e359..7068ec2 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -159,7 +159,11 @@ def interpret_payload(payload): dtype, dlen = struct.unpack('!hh', payload[0:4]) data = payload[4:4+dlen] payload = payload[4+dlen:] - results.append( (dtype, data) ) + results.append( ( + dtype, + Protocol.interpret_value(data, Protocol.receive_ids[dtype][1]) + ) + ) return results def assemble_packet(header, payload): diff --git a/smrt/smrt.py b/smrt/smrt.py index 83c57df..b68778a 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -35,7 +35,8 @@ def main(): sc.login(args.username, args.password) actions = { - "stat": 4096, + "ports": 4096, + "stats": 16384, "mirror": 16640, "vlan": 8704, "pvid": 8706, @@ -46,7 +47,8 @@ def main(): else: header, payload = sc.query(Protocol.GET, {int(args.action): b''}) - print(*Protocol.decode_payload(payload), sep="\n") + # print(*Protocol.interpret_payload(payload), sep="\n") + print(*payload, sep="\n") if __name__ == "__main__": main() From 681a8c310fe2e854e580e535da8f986a64c77bd5 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 23:12:50 +0100 Subject: [PATCH 14/55] Remove unused methods --- smrt/protocol.py | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index 7068ec2..afdec20 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -45,7 +45,7 @@ class Protocol: 'discover': (DISCOVERY, SET), } - receive_ids = { + tp_ids = { 1: ['*s', 'str', 'type'], 2: ['*s', 'str', 'hostname'], 3: ['6s', 'hex', 'mac'], @@ -76,7 +76,7 @@ class Protocol: 17152: ['?', 'bool', 'loop_prevention'], } - ids = {v[2]: k for k, v in receive_ids.items()} + ids = {v[2]: k for k, v in tp_ids.items()} def get_sequence_kind(sequence): for key, value in Protocol.sequences.items(): @@ -99,15 +99,15 @@ def payload_str(payload): for item_id in items.keys(): value = items[item_id] try: - value = interpret_value(value, receive_ids[item_id][1]) + value = interpret_value(value, tp_ids[item_id][1]) except: pass - if item_id not in receive_ids: + if item_id not in tp_ids: ret += 'Unknown code: %s (content: %s)\n' % (item_id, value) continue - struct_fmt = receive_ids[item_id][0] - kind = receive_ids[item_id][1] - name = receive_ids[item_id][2] + struct_fmt = tp_ids[item_id][0] + kind = tp_ids[item_id][1] + name = tp_ids[item_id][2] fmt = '{name}: {value} (id: {id}, kind: {kind})\n' ret += fmt.format(name=name, value=value, id=item_id, kind=kind) return ret @@ -161,7 +161,7 @@ def interpret_payload(payload): payload = payload[4+dlen:] results.append( ( dtype, - Protocol.interpret_value(data, Protocol.receive_ids[dtype][1]) + Protocol.interpret_value(data, Protocol.tp_ids[dtype][1]) ) ) return results @@ -197,32 +197,6 @@ def interpret_value(value, kind): else: raise AssertionError('boolean should be one byte long') return value - def get_payload_item_context(items, name_key): - hits = [x for x in items if Protocol.receive_ids[x[0]][2] == name_key] - assert len(hits) == 1 - item_id = hits[0][0] - - kind = Protocol.receive_ids[item_id][1] - raw_value = hits[0][1] - value = Protocol.interpret_value(raw_value, kind) - - ret = { - 'id': item_id, - 'struct_fmt': Protocol.receive_ids[item_id][0], - 'kind': kind, - 'name': Protocol.receive_ids[item_id][2], - 'value': value, - 'raw_value': raw_value, - } - return ret - - def decode_payload(payload_list): - return [(Protocol.receive_ids[x[0]][2], Protocol.interpret_value(x[1], Protocol.receive_ids[x[0]][1])) for x in payload_list] - - def get_payload_item_value(items, name_key): - context = Protocol.get_payload_item_context(items, name_key) - return context['value'] - def login_payload(username, password): username = username.encode('ascii') + b'\x00' password = password.encode('ascii') + b'\x00' From b38c52ad715bbed3a64aa2852b17427ccb950afe Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 23:43:03 +0100 Subject: [PATCH 15/55] Add stat type in protocol --- smrt/protocol.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index afdec20..5a71380 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -72,7 +72,7 @@ class Protocol: 12288: ['?', 'bool', 'QoS Basic 1'], 12289: ['2s', 'hex', 'QoS Basic 2'], 16640: ['10s','hex', 'port_mirror'], - 16384: ['*s', 'hex', 'port_statistics'], + 16384: ['*s', 'stat', 'port_statistics'], 17152: ['?', 'bool', 'loop_prevention'], } @@ -161,6 +161,7 @@ def interpret_payload(payload): payload = payload[4+dlen:] results.append( ( dtype, + data.hex(sep=" "), Protocol.interpret_value(data, Protocol.tp_ids[dtype][1]) ) ) @@ -182,15 +183,17 @@ def interpret_value(value, kind): elif kind == 'ip': value = ip_address(value) elif kind == 'hex': - value = ':'.join(['{:02X}'.format(byte) for byte in value]) + value = value.hex(sep=":") elif kind == 'action': value = "n/a" elif kind == 'dec': - value = int(''.join('%02X' % byte for byte in value), 16) + value = int.from_bytes(value, 'big') elif kind == 'vlan': - value = (int(value[1]), bin(value[5]), bin(value[9]), bin(value[5] & ~value[9]), value[10:-1].decode('ascii')) + value = struct.unpack("!hii", value[:10]) + (value[10:-1].decode('ascii'), ) elif kind == 'pvid': - value = (int(value[0]), int(value[2])) + value = struct.unpack("!bh", value) + elif kind == 'stat': + value = struct.unpack("!bbbiiii", value) elif kind == 'bool': if len(value) == 0: pass elif len(value) == 1: value = value[0] > 0 From ac81b31633d1c53483ecab96a884f3ea3b34016a Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 25 Oct 2020 23:57:13 +0100 Subject: [PATCH 16/55] Reduce code --- smrt/discovery.py | 4 +++- smrt/network.py | 10 +++++++--- smrt/protocol.py | 35 +---------------------------------- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 523b838..b57bcaa 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -72,11 +72,13 @@ def main(): # logging.basicConfig(level=logging.WARNING) switches = discover_switches(interfaces=args.interfaces) for context, header, payload in switches: - print(context, header, payload) + #print(context, header, payload) #get = lambda kind: Protocol.get_payload_item_value(payload, kind) #fmt = "Found a switch: Host: (Interface: {iface:8s} IP: {host_ip} Broadcast: {broadcast})\n" #fmt += " Switch: (Kind: {kind:12s} MAC Address: {mac} IP Address: {switch_ip})" #print(fmt.format(iface=context['iface'], host_ip=context['ip'], broadcast=context['broadcast'], # kind=get('type'), mac=get('mac'), switch_ip=get('ip_addr'))) + print(*payload, sep="\n") + print("-"*16) if __name__ == "__main__": main() diff --git a/smrt/network.py b/smrt/network.py index 8e7b168..42f7ccd 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -85,6 +85,10 @@ def query(self, op_code, payload): return header, payload def login(self, username, password): - self.query(Protocol.GET, Protocol.get_dict_id("get_token_id")) - self.query(Protocol.LOGIN, Protocol.login_payload(username, password)) - + self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) + username = username.encode('ascii') + b'\x00' + password = password.encode('ascii') + b'\x00' + self.query( + Protocol.LOGIN, + {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + ) diff --git a/smrt/protocol.py b/smrt/protocol.py index 5a71380..774dca8 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -86,32 +86,6 @@ def get_sequence_kind(sequence): def get_id(name): return Protocol.ids[name] - def hex_readable(bts): - return ':'.join(['{:02X}'.format(byte) for byte in bts]) - - def payload_str(payload): - ret = '' - if type(payload) == bytes: - items = interpret_payload(payload) - else: - items = payload - - for item_id in items.keys(): - value = items[item_id] - try: - value = interpret_value(value, tp_ids[item_id][1]) - except: - pass - if item_id not in tp_ids: - ret += 'Unknown code: %s (content: %s)\n' % (item_id, value) - continue - struct_fmt = tp_ids[item_id][0] - kind = tp_ids[item_id][1] - name = tp_ids[item_id][2] - fmt = '{name}: {value} (id: {id}, kind: {kind})\n' - ret += fmt.format(name=name, value=value, id=item_id, kind=kind) - return ret - def decode(data): data = list(data) key = [ 191, 155, 227, 202, 99, 162, 79, 104, 49, 18, 190, 164, 30, @@ -158,13 +132,13 @@ def interpret_payload(payload): while len(payload) > len(Protocol.PACKET_END): dtype, dlen = struct.unpack('!hh', payload[0:4]) data = payload[4:4+dlen] - payload = payload[4+dlen:] results.append( ( dtype, data.hex(sep=" "), Protocol.interpret_value(data, Protocol.tp_ids[dtype][1]) ) ) + payload = payload[4+dlen:] return results def assemble_packet(header, payload): @@ -200,10 +174,3 @@ def interpret_value(value, kind): else: raise AssertionError('boolean should be one byte long') return value - def login_payload(username, password): - username = username.encode('ascii') + b'\x00' - password = password.encode('ascii') + b'\x00' - return {Protocol.get_id('username'): username, Protocol.get_id('password'): password} - - def get_dict_id(name): - return {Protocol.get_id(name): b''} From e6aeed1102a06a671c17570b3837046ae8567e8a Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Mon, 26 Oct 2020 23:20:01 +0100 Subject: [PATCH 17/55] key is a class constant --- smrt/protocol.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index 774dca8..07317d7 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -4,6 +4,25 @@ class Protocol: PACKET_END = b'\xff\xff\x00\x00' + KEY = bytes ([191, 155, 227, 202, 99, 162, 79, 104, 49, 18, 190, 164, 30, + 76, 189, 131, 23, 52, 86, 106, 207, 125, 126, 169, 196, 28, 172, 58, + 188, 132, 160, 3, 36, 120, 144, 168, 12, 231, 116, 44, 41, 97, 108, + 213, 42, 198, 32, 148, 218, 107, 247, 112, 204, 14, 66, 68, 91, 224, + 206, 235, 33, 130, 203, 178, 1, 134, 199, 78, 249, 123, 7, 145, 73, + 208, 209, 100, 74, 115, 72, 118, 8, 22, 243, 147, 64, 96, 5, 87, 60, + 113, 233, 152, 31, 219, 143, 174, 232, 153, 245, 158, 254, 70, 170, + 75, 77, 215, 211, 59, 71, 133, 214, 157, 151, 6, 46, 81, 94, 136, + 166, 210, 4, 43, 241, 29, 223, 176, 67, 63, 186, 137, 129, 40, 248, + 255, 55, 15, 62, 183, 222, 105, 236, 197, 127, 54, 179, 194, 229, + 185, 37, 90, 237, 184, 25, 156, 173, 26, 187, 220, 2, 225, 0, 240, + 50, 251, 212, 253, 167, 17, 193, 205, 177, 21, 181, 246, 82, 226, + 38, 101, 163, 182, 242, 92, 20, 11, 95, 13, 230, 16, 121, 124, 109, + 195, 117, 39, 98, 239, 84, 56, 139, 161, 47, 201, 51, 135, 250, 10, + 19, 150, 45, 111, 27, 24, 142, 80, 85, 83, 234, 138, 216, 57, 93, + 65, 154, 141, 122, 34, 140, 128, 238, 88, 89, 9, 146, 171, 149, 53, + 102, 61, 114, 69, 217, 175, 103, 228, 35, 180, 252, 200, 192, 165, + 159, 221, 244, 110, 119, 48]) + header = { "len": 32, "fmt": '!bb6s6shihhhhi', @@ -87,26 +106,8 @@ def get_id(name): return Protocol.ids[name] def decode(data): - data = list(data) - key = [ 191, 155, 227, 202, 99, 162, 79, 104, 49, 18, 190, 164, 30, - 76, 189, 131, 23, 52, 86, 106, 207, 125, 126, 169, 196, 28, 172, 58, - 188, 132, 160, 3, 36, 120, 144, 168, 12, 231, 116, 44, 41, 97, 108, - 213, 42, 198, 32, 148, 218, 107, 247, 112, 204, 14, 66, 68, 91, 224, - 206, 235, 33, 130, 203, 178, 1, 134, 199, 78, 249, 123, 7, 145, 73, - 208, 209, 100, 74, 115, 72, 118, 8, 22, 243, 147, 64, 96, 5, 87, 60, - 113, 233, 152, 31, 219, 143, 174, 232, 153, 245, 158, 254, 70, 170, - 75, 77, 215, 211, 59, 71, 133, 214, 157, 151, 6, 46, 81, 94, 136, - 166, 210, 4, 43, 241, 29, 223, 176, 67, 63, 186, 137, 129, 40, 248, - 255, 55, 15, 62, 183, 222, 105, 236, 197, 127, 54, 179, 194, 229, - 185, 37, 90, 237, 184, 25, 156, 173, 26, 187, 220, 2, 225, 0, 240, - 50, 251, 212, 253, 167, 17, 193, 205, 177, 21, 181, 246, 82, 226, - 38, 101, 163, 182, 242, 92, 20, 11, 95, 13, 230, 16, 121, 124, 109, - 195, 117, 39, 98, 239, 84, 56, 139, 161, 47, 201, 51, 135, 250, 10, - 19, 150, 45, 111, 27, 24, 142, 80, 85, 83, 234, 138, 216, 57, 93, - 65, 154, 141, 122, 34, 140, 128, 238, 88, 89, 9, 146, 171, 149, 53, - 102, 61, 114, 69, 217, 175, 103, 228, 35, 180, 252, 200, 192, 165, - 159, 221, 244, 110, 119, 48] - s = key + data = bytearray(data) + s = bytearray(Protocol.KEY) j = 0 for k in range(len(data)): i = (k + 1) & 255 From c2cf64d6b10b59956be1c4f063f66349a0686546 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 09:36:22 +0100 Subject: [PATCH 18/55] Simplify discovery --- smrt/discovery.py | 61 ++++++++++------------------------------------ smrt/network.py | 8 +++--- smrt/operations.py | 0 smrt/protocol.py | 7 ++++++ 4 files changed, 25 insertions(+), 51 deletions(-) delete mode 100644 smrt/operations.py diff --git a/smrt/discovery.py b/smrt/discovery.py index b57bcaa..557e9e8 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -1,20 +1,18 @@ #!/usr/bin/env python -import sys, socket, random, logging, platform, argparse - +import random +import logging +import argparse import netifaces from . import IncompatiblePlatformException from .protocol import Protocol from .network import Network -DISCOVERY_TIMEOUT = 0.5 - logger = logging.getLogger(__name__) -def discover_switches(interfaces='all'): - if interfaces == 'all': - interfaces = netifaces.interfaces() +def discover_switches(): + interfaces = netifaces.interfaces() settings = [] for iface in interfaces: addrs = netifaces.ifaddresses(iface) @@ -27,6 +25,7 @@ def discover_switches(interfaces='all'): settings.append((iface, addr['addr'], mac, addr['broadcast'])) for iface, ip, mac, broadcast in settings: + net = Network(None, mac, ip) logger.warning((iface, ip, mac, broadcast)) sequence_id = random.randint(0, 1000) header = Protocol.header["blank"].copy() @@ -36,49 +35,15 @@ def discover_switches(interfaces='all'): }) packet = Protocol.assemble_packet(header, {}) packet = Protocol.encode(packet) - - # Receiving socket - rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) - rs.settimeout(0.4) - rs.settimeout(DISCOVERY_TIMEOUT) - - ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - # ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - ss.bind((ip, Network.UDP_RECEIVE_FROM_PORT)) - ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) - ss.close() - - while True: - try: - data, addr = rs.recvfrom(1500) - except: - break - data = Protocol.decode(data) - header, payload = Protocol.split(data) - payload = Protocol.interpret_payload(payload) - context = {'iface': iface, 'ip': ip, 'mac': mac, 'broadcast': broadcast} - yield context, header, payload - rs.close() + net.send_packet(packet) + header, payload = net.receive() + yield header, payload def main(): - if platform.system().lower() not in ('darwin', 'linux'): - sys.stderr.write('Discovery is not implemented for the platform %s' % platform.system()) - sys.exit(4) - parser = argparse.ArgumentParser() - parser.add_argument('interfaces', metavar='INTERFACE', nargs='*', default='all') - args = parser.parse_args() - # logging.basicConfig(level=logging.WARNING) - switches = discover_switches(interfaces=args.interfaces) - for context, header, payload in switches: - #print(context, header, payload) - #get = lambda kind: Protocol.get_payload_item_value(payload, kind) - #fmt = "Found a switch: Host: (Interface: {iface:8s} IP: {host_ip} Broadcast: {broadcast})\n" - #fmt += " Switch: (Kind: {kind:12s} MAC Address: {mac} IP Address: {switch_ip})" - #print(fmt.format(iface=context['iface'], host_ip=context['ip'], broadcast=context['broadcast'], - # kind=get('type'), mac=get('mac'), switch_ip=get('ip_addr'))) + switches = discover_switches() + for header, payload in switches: print(*payload, sep="\n") print("-"*16) -if __name__ == "__main__": main() +if __name__ == "__main__": + main() diff --git a/smrt/network.py b/smrt/network.py index 42f7ccd..517ba13 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -14,7 +14,7 @@ class Network: def __init__(self, switch_mac, host_mac, ip_address): - self.switch_mac = switch_mac + self.switch_mac = '00:00:00:00:00:00' if switch_mac is None else switch_mac self.host_mac = host_mac self.ip_address = ip_address @@ -23,8 +23,8 @@ def __init__(self, switch_mac, host_mac, ip_address): header = Protocol.header["blank"].copy() header.update({ 'sequence_id': self.sequence_id, - 'host_mac': Network.mac_to_bytes(host_mac), - 'switch_mac': Network.mac_to_bytes(switch_mac), + 'host_mac': Network.mac_to_bytes(self.host_mac), + 'switch_mac': Network.mac_to_bytes(self.switch_mac), }) self.header = header @@ -59,7 +59,9 @@ def send(self, op_code, payload): logger.debug('Sending Payload: ' + str(payload)) packet = Protocol.assemble_packet(header, payload) packet = Protocol.encode(packet) + self.send_packet(packet) + def send_packet(self, packet): self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) def receive(self): diff --git a/smrt/operations.py b/smrt/operations.py deleted file mode 100644 index e69de29..0000000 diff --git a/smrt/protocol.py b/smrt/protocol.py index 07317d7..acf178f 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -175,3 +175,10 @@ def interpret_value(value, kind): else: raise AssertionError('boolean should be one byte long') return value + def set_vlan(vlan_num, member_mask, tagged_mask, vlan_name): + value = struct.pack("!hii",vlan_num, member_mask, tagged_mask) + vlan_name.encode("ascii") + return value + +if __name__ == "__main__": + v = Protocol.set_vlan(10, 255, 254, "test") + print(v) From 9c1da5b8e3450fb84cdf299670986fa82916803a Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 09:40:39 +0100 Subject: [PATCH 19/55] Use send --- smrt/discovery.py | 10 +--------- smrt/network.py | 3 --- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 557e9e8..4c77e1f 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -27,15 +27,7 @@ def discover_switches(): for iface, ip, mac, broadcast in settings: net = Network(None, mac, ip) logger.warning((iface, ip, mac, broadcast)) - sequence_id = random.randint(0, 1000) - header = Protocol.header["blank"].copy() - header.update({ - 'sequence_id': sequence_id, - 'host_mac': bytes(int(byte, 16) for byte in mac.split(':')), - }) - packet = Protocol.assemble_packet(header, {}) - packet = Protocol.encode(packet) - net.send_packet(packet) + net.send(Protocol.DISCOVERY, {}) header, payload = net.receive() yield header, payload diff --git a/smrt/network.py b/smrt/network.py index 517ba13..95b3c61 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -59,9 +59,6 @@ def send(self, op_code, payload): logger.debug('Sending Payload: ' + str(payload)) packet = Protocol.assemble_packet(header, payload) packet = Protocol.encode(packet) - self.send_packet(packet) - - def send_packet(self, packet): self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) def receive(self): From fe6c35f4407f8421f5974043b3924d57edafc53d Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 09:46:32 +0100 Subject: [PATCH 20/55] Change parameters order in Network --- smrt/discovery.py | 2 +- smrt/network.py | 19 ++++++++----------- smrt/smrt.py | 8 +------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 4c77e1f..4aad311 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -25,7 +25,7 @@ def discover_switches(): settings.append((iface, addr['addr'], mac, addr['broadcast'])) for iface, ip, mac, broadcast in settings: - net = Network(None, mac, ip) + net = Network(ip, mac) logger.warning((iface, ip, mac, broadcast)) net.send(Protocol.DISCOVERY, {}) header, payload = net.receive() diff --git a/smrt/network.py b/smrt/network.py index 95b3c61..dc42f8f 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -12,9 +12,8 @@ class Network: UDP_SEND_TO_PORT = 29808 UDP_RECEIVE_FROM_PORT = 29809 - def __init__(self, switch_mac, host_mac, ip_address): - - self.switch_mac = '00:00:00:00:00:00' if switch_mac is None else switch_mac + def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): + self.switch_mac = switch_mac self.host_mac = host_mac self.ip_address = ip_address @@ -29,16 +28,14 @@ def __init__(self, switch_mac, host_mac, ip_address): self.header = header # Sending socket - ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - ss.bind((ip_address, Network.UDP_RECEIVE_FROM_PORT)) + self.ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.ss.bind((ip_address, Network.UDP_RECEIVE_FROM_PORT)) # Receiving socket - rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) - rs.settimeout(0.4) - - self.ss, self.rs = ss, rs + self.rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) + self.rs.settimeout(0.5) def mac_to_bytes(mac): return bytes(int(byte, 16) for byte in mac.split(':')) diff --git a/smrt/smrt.py b/smrt/smrt.py index b68778a..64e683c 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -26,12 +26,7 @@ def main(): logging.basicConfig(level=args.loglevel) - switch_mac = args.switch_mac - host_mac = args.host_mac - ip_address = args.ip_address - - sc = Network(switch_mac, host_mac, ip_address) - + sc = Network(args.ip_address, args.host_mac, args.switch_mac) sc.login(args.username, args.password) actions = { @@ -47,7 +42,6 @@ def main(): else: header, payload = sc.query(Protocol.GET, {int(args.action): b''}) - # print(*Protocol.interpret_payload(payload), sep="\n") print(*payload, sep="\n") if __name__ == "__main__": From b38af458546ec62f01fa2009b5b13c65075d3cb6 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 09:47:36 +0100 Subject: [PATCH 21/55] Remove unused option --- smrt/smrt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/smrt/smrt.py b/smrt/smrt.py index 64e683c..5947b6d 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -14,7 +14,6 @@ def loglevel(x): def main(): logger = logging.getLogger(__name__) parser = argparse.ArgumentParser() - parser.add_argument('--configfile', '-c') parser.add_argument('--switch-mac', '-s') parser.add_argument('--host-mac', ) parser.add_argument('--ip-address', '-i') From 0a322d782ba3f79923e8ac59171d73fee403fa4d Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 10:03:59 +0100 Subject: [PATCH 22/55] Use ids_tp for actions --- smrt/protocol.py | 31 ++++++++++++++++--------------- smrt/smrt.py | 16 ++++------------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/smrt/protocol.py b/smrt/protocol.py index acf178f..512b2c2 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -64,17 +64,17 @@ class Protocol: 'discover': (DISCOVERY, SET), } - tp_ids = { + ids_tp = { 1: ['*s', 'str', 'type'], 2: ['*s', 'str', 'hostname'], 3: ['6s', 'hex', 'mac'], 4: ['4s', 'ip', 'ip_addr'], 5: ['4s', 'ip', 'ip_mask'], 6: ['4s', 'ip', 'gateway'], - 7: ['*s', 'str', 'firmware_version'], - 8: ['*s', 'str', 'hardware_version'], + 7: ['*s', 'str', 'firmware'], + 8: ['*s', 'str', 'hardware'], 9: ['?', 'bool', 'dhcp'], - 10: ['b', 'dec', 'num_ports'], + 10: ['b', 'dec', 'ports'], 13: ['?', 'bool', 'v4'], 512: ['*s', 'str', 'username'], 514: ['*s', 'str', 'password'], @@ -84,18 +84,18 @@ class Protocol: 4096: ['*s', 'hex', 'port_settings'], 4608: ['5s', 'hex', 'port_trunk'], 8192: ['2s', 'hex', 'mtu_vlan'], - 8704: ['?', 'hex', '802.1q vlan enabled'], - 8705: ['*s', 'vlan', '802.1q vlan'], - 8706: ['*s', 'pvid', '802.1q vlan pvid'], - 8707: ['*s', 'str', '802.1q vlan filler'], - 12288: ['?', 'bool', 'QoS Basic 1'], - 12289: ['2s', 'hex', 'QoS Basic 2'], + 8704: ['?', 'hex', 'vlan_enabled'], + 8705: ['*s', 'vlan', 'vlan'], + 8706: ['*s', 'pvid', 'pvid'], + 8707: ['*s', 'str', 'vlan_filler'], + 12288: ['?', 'bool', 'qos1'], + 12289: ['2s', 'hex', 'qos2'], 16640: ['10s','hex', 'port_mirror'], - 16384: ['*s', 'stat', 'port_statistics'], - 17152: ['?', 'bool', 'loop_prevention'], + 16384: ['*s', 'stat', 'stats'], + 17152: ['?', 'bool', 'loop_prev'], } - ids = {v[2]: k for k, v in tp_ids.items()} + tp_ids = {v[2]: k for k, v in ids_tp.items()} def get_sequence_kind(sequence): for key, value in Protocol.sequences.items(): @@ -103,7 +103,7 @@ def get_sequence_kind(sequence): return 'unknown' def get_id(name): - return Protocol.ids[name] + return Protocol.tp_ids[name] def decode(data): data = bytearray(data) @@ -135,8 +135,9 @@ def interpret_payload(payload): data = payload[4:4+dlen] results.append( ( dtype, + Protocol.ids_tp[dtype][2], data.hex(sep=" "), - Protocol.interpret_value(data, Protocol.tp_ids[dtype][1]) + Protocol.interpret_value(data, Protocol.ids_tp[dtype][1]) ) ) payload = payload[4+dlen:] diff --git a/smrt/smrt.py b/smrt/smrt.py index 5947b6d..2a36f0e 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -20,28 +20,20 @@ def main(): parser.add_argument('--username', '-u') parser.add_argument('--password', '-p') parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') - parser.add_argument('action') + parser.add_argument('action', default=None, nargs='?') args = parser.parse_args() logging.basicConfig(level=args.loglevel) sc = Network(args.ip_address, args.host_mac, args.switch_mac) sc.login(args.username, args.password) - - actions = { - "ports": 4096, - "stats": 16384, - "mirror": 16640, - "vlan": 8704, - "pvid": 8706, - } + actions = Protocol.tp_ids if args.action in actions: header, payload = sc.query(Protocol.GET, {actions[args.action]: b''}) + print(*payload, sep="\n") else: - header, payload = sc.query(Protocol.GET, {int(args.action): b''}) - - print(*payload, sep="\n") + print("Actions:" , *actions.keys()) if __name__ == "__main__": main() From 9ad2ad5c18ff413b9af3a78e3405424c8200930c Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 10:06:01 +0100 Subject: [PATCH 23/55] Rename sc->net --- smrt/smrt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smrt/smrt.py b/smrt/smrt.py index 2a36f0e..1626fe8 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -25,12 +25,12 @@ def main(): logging.basicConfig(level=args.loglevel) - sc = Network(args.ip_address, args.host_mac, args.switch_mac) - sc.login(args.username, args.password) + net = Network(args.ip_address, args.host_mac, args.switch_mac) + net.login(args.username, args.password) actions = Protocol.tp_ids if args.action in actions: - header, payload = sc.query(Protocol.GET, {actions[args.action]: b''}) + header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) print(*payload, sep="\n") else: print("Actions:" , *actions.keys()) From c0c2914e1b631224ce11fcaf5cab5976df0bf658 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 10:36:30 +0100 Subject: [PATCH 24/55] Simplify commands --- smrt/discovery.py | 13 +++++++++---- smrt/network.py | 6 +++--- smrt/protocol.py | 10 +++++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/smrt/discovery.py b/smrt/discovery.py index 4aad311..275caad 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -16,20 +16,25 @@ def discover_switches(): settings = [] for iface in interfaces: addrs = netifaces.ifaddresses(iface) - if netifaces.AF_INET not in addrs: continue - if netifaces.AF_LINK not in addrs: continue + if netifaces.AF_INET not in addrs: + continue + if netifaces.AF_LINK not in addrs: + continue assert len(addrs[netifaces.AF_LINK]) == 1 mac = addrs[netifaces.AF_LINK][0]['addr'] for addr in addrs[netifaces.AF_INET]: - if 'broadcast' not in addr or 'addr' not in addr: continue + if 'broadcast' not in addr or 'addr' not in addr: + continue settings.append((iface, addr['addr'], mac, addr['broadcast'])) + ret = [] for iface, ip, mac, broadcast in settings: net = Network(ip, mac) logger.warning((iface, ip, mac, broadcast)) net.send(Protocol.DISCOVERY, {}) header, payload = net.receive() - yield header, payload + ret.append( (header, payload) ) + return ret def main(): switches = discover_switches() diff --git a/smrt/network.py b/smrt/network.py index dc42f8f..e4163e6 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -41,7 +41,7 @@ def mac_to_bytes(mac): return bytes(int(byte, 16) for byte in mac.split(':')) def mac_to_str(mac): - return ':'.join('{:02X}'.format(byte) for byte in mac) + return a.hex(":") def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 @@ -85,6 +85,6 @@ def login(self, username, password): username = username.encode('ascii') + b'\x00' password = password.encode('ascii') + b'\x00' self.query( - Protocol.LOGIN, - {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + Protocol.LOGIN, + {Protocol.get_id('username'): username, Protocol.get_id('password'): password} ) diff --git a/smrt/protocol.py b/smrt/protocol.py index 512b2c2..d75c146 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -74,15 +74,15 @@ class Protocol: 7: ['*s', 'str', 'firmware'], 8: ['*s', 'str', 'hardware'], 9: ['?', 'bool', 'dhcp'], - 10: ['b', 'dec', 'ports'], + 10: ['b', 'dec', 'num_ports'], 13: ['?', 'bool', 'v4'], 512: ['*s', 'str', 'username'], 514: ['*s', 'str', 'password'], 2304: ['a', 'action','save'], 2305: ['a', 'action','get_token_id'], 4352: ['?', 'bool', 'igmp_snooping'], - 4096: ['*s', 'hex', 'port_settings'], - 4608: ['5s', 'hex', 'port_trunk'], + 4096: ['*s', 'hex', 'ports'], + 4608: ['5s', 'hex', 'trunk'], 8192: ['2s', 'hex', 'mtu_vlan'], 8704: ['?', 'hex', 'vlan_enabled'], 8705: ['*s', 'vlan', 'vlan'], @@ -90,7 +90,7 @@ class Protocol: 8707: ['*s', 'str', 'vlan_filler'], 12288: ['?', 'bool', 'qos1'], 12289: ['2s', 'hex', 'qos2'], - 16640: ['10s','hex', 'port_mirror'], + 16640: ['10s','hex', 'mirror'], 16384: ['*s', 'stat', 'stats'], 17152: ['?', 'bool', 'loop_prev'], } @@ -136,7 +136,7 @@ def interpret_payload(payload): results.append( ( dtype, Protocol.ids_tp[dtype][2], - data.hex(sep=" "), + # data.hex(sep=" "), Protocol.interpret_value(data, Protocol.ids_tp[dtype][1]) ) ) From d9ad54e3dc3f93fea05a0d96a307c982253c6016 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 13:57:10 +0100 Subject: [PATCH 25/55] Amorce config lan --- smrt/network.py | 19 ++++++++++++++----- smrt/protocol.py | 4 ++++ smrt/smrt.py | 4 +++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/smrt/network.py b/smrt/network.py index e4163e6..1df9595 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -80,11 +80,20 @@ def query(self, op_code, payload): logger.debug('Sequence kind: ' + sequence_kind) return header, payload - def login(self, username, password): + def login(self, username, password, payload = None): self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) username = username.encode('ascii') + b'\x00' password = password.encode('ascii') + b'\x00' - self.query( - Protocol.LOGIN, - {Protocol.get_id('username'): username, Protocol.get_id('password'): password} - ) + if payload is None: + self.query( + Protocol.LOGIN, + {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + ) + else: + payload.update( + {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + ) + self.query( + Protocol.LOGIN, + payload + ) diff --git a/smrt/protocol.py b/smrt/protocol.py index d75c146..977ad9e 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -143,6 +143,10 @@ def interpret_payload(payload): payload = payload[4+dlen:] return results + def analyze(data): + header, payload = Protocol.split(data) + return Protocol.interpret_header(header), Protocol.interpret_payload(payload) + def assemble_packet(header, payload): payload_bytes = b'' for dtype, value in payload.items(): diff --git a/smrt/smrt.py b/smrt/smrt.py index 1626fe8..c6d7b05 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -26,8 +26,10 @@ def main(): logging.basicConfig(level=args.loglevel) net = Network(args.ip_address, args.host_mac, args.switch_mac) - net.login(args.username, args.password) actions = Protocol.tp_ids + net.login(args.username, args.password) + v = Protocol.set_vlan(10, 255, 254, "test3") + net.login(args.username, args.password, {actions["vlan"]: v}) if args.action in actions: header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) From 3c23ae450954a5805512e26f41c7fe49a82c0747 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 13:57:30 +0100 Subject: [PATCH 26/55] Add analyze.py --- smrt/analyze.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 smrt/analyze.py diff --git a/smrt/analyze.py b/smrt/analyze.py new file mode 100755 index 0000000..7e066ac --- /dev/null +++ b/smrt/analyze.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import socket, time, random, argparse, logging +from protocol import Protocol + +def main(): + capture = [ +b"\x5d\x75\xb2\x09\x6a\xdd" \ +b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbd\x42\x2b\xa2\xf5\xd7\x87" \ +b"\xae\xed\x50\x8f\x63\x5c\xc2\x02\x90\x9a\x52\x12\x81\xc6\xd8\xd5" \ +b"\x2b\x36", + +b"\x5d\x76\xb2\x09\x6a\xdd" \ +b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbd\x42\x2b\xa2\xf5\xd7\x8b" \ +b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\xa4\xec\x81\xc6", + +b"\x5d\x77\xb2\x09\x6a\xdd" \ +b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbc\x42\x2b\xa2\xf5\xd7\xe3" \ +b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\x59\x13\x81\xc0\x46\x4e" \ +b"\x46\x5f\x27\xd9\x42\xb5\xa4\x46\x08\x40\x34\x18\x22\x2a\x5d\x4f" \ +b"\x9b\x67\x38\x1e\x76\x08\x73\x71\xf2\x9b\x54\xbe\x89\xd9\xc8\x91" \ +b"\x5a\xb3\x73\xda\x4c\x00", + +b"\x5d\x70\xb2\x09\x6a\xdd" \ +b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbc\x42\x2b\xa2\xf5\xd7\x00" \ +b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\x79\x13\x81\xc7\x26\x08" \ +b"\x2a\x36\x5b\xd9\x41\xb7\xa4\x40\x69\x24\x59\x71\x4c\x6e\x1a\x28" \ +b"\xfa\x02\x54\x60\x76\x2a\x72\xe1\xe2\x9b\x56\x2e\xfd\xbc\x1b\xe5" \ +b"\x68\xb3\x0c\x75\x24\x6f\x49\x44\x2a\x92\xab\x86\xfc\xa4\xd4\x12" \ +b"\xf1\xc0\x16\x83\x9f\x9a\x2c\xfa\x99\x3a\x62\x90\x19\x59\xb8\xb3" \ +b"\x82\xdf\x2e\xe6\xe5\xf9\x54\x80\x85\x09\xce\x81\xc6\xb0\x6e\xf7" \ +b"\xba\xe8\x86\xb2\x52\xb6\x96\xd7\xfd\x73\xe1\xf9\x8f\xc0\xa5\x50" \ +b"\x2e\xec\xe6\x97\xc1\x74\xf9\x71\xdc\x89\xa8\xbb\x6f\xd2\x47\xf1" \ +b"\x77\x7c\xd8\x70\xb7\xf7\xad\x06\xa2\xe2\x9b\x5c\x69\xc8\xf1\x0d" \ +b"\xc1\x58\x72\xa1\xb2\x5a\x31\x04\xdc" +] + for s in capture: + data = Protocol.decode(s) + print(Protocol.analyze(data)) + +# net = Network(args.ip_address, args.host_mac, args.switch_mac) +# net.login(args.username, args.password) +# actions = Protocol.tp_ids +# v = Protocol.set_vlan(10, 255, 254, "pipo") +# +# if args.action in actions: +# header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) +# print(*payload, sep="\n") +# else: +# print("Actions:" , *actions.keys()) +# header, payload = net.query(Protocol.SET, {actions[args.action]: v}) + +if __name__ == "__main__": + main() From f43bb9c85020fdbc30824555a8df23ce554bb897 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 18:12:06 +0100 Subject: [PATCH 27/55] Set vlan OK --- smrt/network.py | 55 +++++++++++++++++++++++++----------------------- smrt/protocol.py | 5 +++-- smrt/smrt.py | 20 ++++++++++-------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/smrt/network.py b/smrt/network.py index 1df9595..70026f8 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -19,13 +19,12 @@ def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): self.sequence_id = random.randint(0, 1000) - header = Protocol.header["blank"].copy() - header.update({ + self.header = Protocol.header["blank"].copy() + self.header.update({ 'sequence_id': self.sequence_id, 'host_mac': Network.mac_to_bytes(self.host_mac), 'switch_mac': Network.mac_to_bytes(self.switch_mac), }) - self.header = header # Sending socket self.ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -45,23 +44,22 @@ def mac_to_str(mac): def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 - - header = self.header - header.update({ + self.header.update({ 'sequence_id': self.sequence_id, 'op_code': op_code, }) - - logger.debug('Sending Header: ' + str(header)) - logger.debug('Sending Payload: ' + str(payload)) - packet = Protocol.assemble_packet(header, payload) + packet = Protocol.assemble_packet(self.header, payload) + logger.debug('Sending Packet: ' + packet.hex(" ")) packet = Protocol.encode(packet) + logger.debug('Sending Header: ' + str(self.header)) + logger.debug('Sending Payload: ' + str(payload)) self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) def receive(self): try: data, addr = self.rs.recvfrom(1500) data = Protocol.decode(data) + logger.debug('Receive Packet: ' + data.hex(" ")) header, payload = Protocol.split(data) header, payload = Protocol.interpret_header(header), Protocol.interpret_payload(payload) logger.debug('Received Header: ' + str(header)) @@ -76,24 +74,29 @@ def receive(self): def query(self, op_code, payload): self.send(op_code, payload) header, payload = self.receive() - sequence_kind = Protocol.get_sequence_kind((op_code, header['op_code'])) - logger.debug('Sequence kind: ' + sequence_kind) return header, payload - def login(self, username, password, payload = None): + def login_dict(self, username, password): + return { + Protocol.get_id('username'): username.encode('ascii') + b'\x00', + Protocol.get_id('password'): password.encode('ascii') + b'\x00' + } + + def login(self, username, password): + self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) + self.query( + Protocol.LOGIN, + self.login_dict(username, password) + ) + + def set(self, username, password, payload): self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) username = username.encode('ascii') + b'\x00' password = password.encode('ascii') + b'\x00' - if payload is None: - self.query( - Protocol.LOGIN, - {Protocol.get_id('username'): username, Protocol.get_id('password'): password} - ) - else: - payload.update( - {Protocol.get_id('username'): username, Protocol.get_id('password'): password} - ) - self.query( - Protocol.LOGIN, - payload - ) + payload.update( + {Protocol.get_id('username'): username, Protocol.get_id('password'): password} + ) + self.query( + Protocol.LOGIN, + payload + ) diff --git a/smrt/protocol.py b/smrt/protocol.py index 977ad9e..1bacc53 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -152,6 +152,7 @@ def assemble_packet(header, payload): for dtype, value in payload.items(): payload_bytes += struct.pack('!hh', dtype, len(value)) payload_bytes += value + print(len(payload_bytes), payload_bytes) header['check_length'] = Protocol.header["len"] + len(payload_bytes) + len(Protocol.PACKET_END) header = tuple(header[part] for part in Protocol.header['blank'].keys()) header_bytes = struct.pack(Protocol.header['fmt'], *header) @@ -181,9 +182,9 @@ def interpret_value(value, kind): return value def set_vlan(vlan_num, member_mask, tagged_mask, vlan_name): - value = struct.pack("!hii",vlan_num, member_mask, tagged_mask) + vlan_name.encode("ascii") + value = struct.pack("!hii",vlan_num, member_mask, tagged_mask) + vlan_name.encode("ascii") + b'\x00' return value if __name__ == "__main__": v = Protocol.set_vlan(10, 255, 254, "test") - print(v) + print(v, len(v)) diff --git a/smrt/smrt.py b/smrt/smrt.py index c6d7b05..66d893d 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -27,15 +27,17 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - net.login(args.username, args.password) - v = Protocol.set_vlan(10, 255, 254, "test3") - net.login(args.username, args.password, {actions["vlan"]: v}) - - if args.action in actions: - header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) - print(*payload, sep="\n") - else: - print("Actions:" , *actions.keys()) + l = net.login_dict(args.username, args.password) + v = Protocol.set_vlan(10, 0, 0, "test6") + l.update({actions["vlan"]: v}) + print(l) + net.set(args.username, args.password, l) + + #if args.action in actions: + # header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) + # print(*payload, sep="\n") + #else: + # print("Actions:" , *actions.keys()) if __name__ == "__main__": main() From 3cd85a99d7c8f15c90fd56626447fc01a36ffefd Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Tue, 27 Oct 2020 20:41:13 +0100 Subject: [PATCH 28/55] Get multiple switchs in discovery --- smrt/__init__.py | 3 --- smrt/discovery.py | 12 +++++++----- smrt/network.py | 5 +++-- smrt/protocol.py | 1 - smrt/smrt.py | 22 +++++++++++----------- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/smrt/__init__.py b/smrt/__init__.py index 0942ccc..e69de29 100644 --- a/smrt/__init__.py +++ b/smrt/__init__.py @@ -1,3 +0,0 @@ -class SmrtPyException(Exception): pass -class ConnectionException(SmrtPyException): pass -class IncompatiblePlatformException(SmrtPyException): pass diff --git a/smrt/discovery.py b/smrt/discovery.py index 275caad..eadc48c 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -5,9 +5,8 @@ import argparse import netifaces -from . import IncompatiblePlatformException from .protocol import Protocol -from .network import Network +from .network import Network, ConnectionProblem logger = logging.getLogger(__name__) @@ -32,9 +31,12 @@ def discover_switches(): net = Network(ip, mac) logger.warning((iface, ip, mac, broadcast)) net.send(Protocol.DISCOVERY, {}) - header, payload = net.receive() - ret.append( (header, payload) ) - return ret + while True: + try: + header, payload = net.receive() + yield header, payload + except ConnectionProblem: + break def main(): switches = discover_switches() diff --git a/smrt/network.py b/smrt/network.py index 70026f8..ed421fb 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -6,6 +6,9 @@ logger = logging.getLogger(__name__) +class ConnectionProblem(Exception): + pass + class Network: BROADCAST_ADDR = "255.255.255.255" @@ -67,9 +70,7 @@ def receive(self): self.header['token_id'] = header['token_id'] return header, payload except: - print("no response") raise ConnectionProblem() - return {}, {} def query(self, op_code, payload): self.send(op_code, payload) diff --git a/smrt/protocol.py b/smrt/protocol.py index 1bacc53..9b3dda3 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -152,7 +152,6 @@ def assemble_packet(header, payload): for dtype, value in payload.items(): payload_bytes += struct.pack('!hh', dtype, len(value)) payload_bytes += value - print(len(payload_bytes), payload_bytes) header['check_length'] = Protocol.header["len"] + len(payload_bytes) + len(Protocol.PACKET_END) header = tuple(header[part] for part in Protocol.header['blank'].keys()) header_bytes = struct.pack(Protocol.header['fmt'], *header) diff --git a/smrt/smrt.py b/smrt/smrt.py index 66d893d..4a2c8c4 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -27,17 +27,17 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - l = net.login_dict(args.username, args.password) - v = Protocol.set_vlan(10, 0, 0, "test6") - l.update({actions["vlan"]: v}) - print(l) - net.set(args.username, args.password, l) - - #if args.action in actions: - # header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) - # print(*payload, sep="\n") - #else: - # print("Actions:" , *actions.keys()) + net.login(args.username, args.password) + # l = net.login_dict(args.username, args.password) + # v = Protocol.set_vlan(10, 255, 248, "test2") + # l.update({actions["vlan"]: v}) + # net.set(args.username, args.password, l) + + if args.action in actions: + header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) + print(*payload, sep="\n") + else: + print("Actions:" , *actions.keys()) if __name__ == "__main__": main() From 221afec5746946f85e61d4b33b1b6722fce155d0 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 16:50:59 +0100 Subject: [PATCH 29/55] Ajoute module set_vlan --- smrt/network.py | 3 ++- smrt/set_vlan.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100755 smrt/set_vlan.py diff --git a/smrt/network.py b/smrt/network.py index ed421fb..a6f4657 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -97,7 +97,8 @@ def set(self, username, password, payload): payload.update( {Protocol.get_id('username'): username, Protocol.get_id('password'): password} ) - self.query( + header, payload = self.query( Protocol.LOGIN, payload ) + return header, payload diff --git a/smrt/set_vlan.py b/smrt/set_vlan.py new file mode 100755 index 0000000..29ba255 --- /dev/null +++ b/smrt/set_vlan.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +import socket, time, random, argparse, logging + +from .protocol import Protocol +from .network import Network + +def loglevel(x): + try: + return getattr(logging, x.upper()) + except AttributeError: + raise argparse.ArgumentError('Select a proper loglevel') + +def main(): + logger = logging.getLogger(__name__) + parser = argparse.ArgumentParser() + parser.add_argument('--switch-mac', '-s') + parser.add_argument('--host-mac', ) + parser.add_argument('--ip-address', '-i') + parser.add_argument('--username', '-u') + parser.add_argument('--password', '-p') + parser.add_argument('--vlan', type=int) + parser.add_argument('--vlan_name') + parser.add_argument('--vlan_member', type=int) + parser.add_argument('--vlan_tagged', type=int) + parser.add_argument('--delete', action='store_true') + parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') + parser.add_argument('action', default=None, nargs='?') + args = parser.parse_args() + + logging.basicConfig(level=args.loglevel) + + net = Network(args.ip_address, args.host_mac, args.switch_mac) + actions = Protocol.tp_ids + net.login(args.username, args.password) + l = net.login_dict(args.username, args.password) + if (args.delete): + v = Protocol.set_vlan(int(args.vlan), 0, 0, "") + else: + v = Protocol.set_vlan(int(args.vlan), int(args.vlan_member), int(args.vlan_tagged), args.vlan_name) + l.update({actions["vlan"]: v}) + header, payload = net.set(args.username, args.password, l) + print(*payload, sep="\n") + +if __name__ == "__main__": + main() From 61ed1762b66e5a05d862d759a00368a9492a81cb Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 17:24:50 +0100 Subject: [PATCH 30/55] Add ports2byte and byte2ports functions --- smrt/binary.py | 22 ++++++++++++++++++++++ smrt/protocol.py | 5 ++++- smrt/set_vlan.py | 7 ++++--- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 smrt/binary.py diff --git a/smrt/binary.py b/smrt/binary.py new file mode 100644 index 0000000..bfae345 --- /dev/null +++ b/smrt/binary.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +SEP = "," + +def ports2byte(numports): + out = 0 + for i in numports.split(SEP): + out |= (1 << (int(i) - 1)) + return out + +def byte2ports(byte): + out = [] + for i in range(32): + if byte % 2: + out.append(str(i + 1)) + byte = (byte >> 1) + return SEP.join(out) + +if __name__ == '__main__': + #print(ports2byte("12345678")) + a = ports2byte("1:2:5:6:8:12:15") + print(a, byte2ports(a)) diff --git a/smrt/protocol.py b/smrt/protocol.py index 9b3dda3..1dad748 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -1,5 +1,6 @@ import struct from ipaddress import ip_address +from smrt.binary import byte2ports class Protocol: PACKET_END = b'\xff\xff\x00\x00' @@ -169,7 +170,9 @@ def interpret_value(value, kind): elif kind == 'dec': value = int.from_bytes(value, 'big') elif kind == 'vlan': - value = struct.unpack("!hii", value[:10]) + (value[10:-1].decode('ascii'), ) + value = list(struct.unpack("!hii", value[:10]) + (value[10:-1].decode('ascii'), )) + value[1] = byte2ports(value[1]) + value[2] = byte2ports(value[2]) elif kind == 'pvid': value = struct.unpack("!bh", value) elif kind == 'stat': diff --git a/smrt/set_vlan.py b/smrt/set_vlan.py index 29ba255..dbc5629 100755 --- a/smrt/set_vlan.py +++ b/smrt/set_vlan.py @@ -4,6 +4,7 @@ from .protocol import Protocol from .network import Network +from .binary import ports2byte def loglevel(x): try: @@ -21,8 +22,8 @@ def main(): parser.add_argument('--password', '-p') parser.add_argument('--vlan', type=int) parser.add_argument('--vlan_name') - parser.add_argument('--vlan_member', type=int) - parser.add_argument('--vlan_tagged', type=int) + parser.add_argument('--vlan_member') + parser.add_argument('--vlan_tagged') parser.add_argument('--delete', action='store_true') parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') parser.add_argument('action', default=None, nargs='?') @@ -37,7 +38,7 @@ def main(): if (args.delete): v = Protocol.set_vlan(int(args.vlan), 0, 0, "") else: - v = Protocol.set_vlan(int(args.vlan), int(args.vlan_member), int(args.vlan_tagged), args.vlan_name) + v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) l.update({actions["vlan"]: v}) header, payload = net.set(args.username, args.password, l) print(*payload, sep="\n") From f59c24b0937477e44ea07248ef4015e4d9bbf01f Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 18:38:12 +0100 Subject: [PATCH 31/55] payload is now a list, not a dict --- smrt/binary.py | 22 ++++++++++++++++++---- smrt/network.py | 21 +++++++++------------ smrt/protocol.py | 14 +++++++++++--- smrt/set_vlan.py | 17 +++++++++++++---- smrt/smrt.py | 2 +- 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/smrt/binary.py b/smrt/binary.py index bfae345..6bdcfbe 100644 --- a/smrt/binary.py +++ b/smrt/binary.py @@ -2,10 +2,24 @@ SEP = "," -def ports2byte(numports): +def ports2list(ports): + if ports is None: + l = [] + else: + try: + l = [int(x) for x in ports.split(SEP)] + except ValueError: + l = [] + return l + +def ports2byte(ports): out = 0 - for i in numports.split(SEP): - out |= (1 << (int(i) - 1)) + l = ports2list(ports) + if l == []: + out = 0 + else: + for i in l: + out |= (1 << (int(i) - 1)) return out def byte2ports(byte): @@ -18,5 +32,5 @@ def byte2ports(byte): if __name__ == '__main__': #print(ports2byte("12345678")) - a = ports2byte("1:2:5:6:8:12:15") + a = ports2byte("1,2,5,6,8,12,15") print(a, byte2ports(a)) diff --git a/smrt/network.py b/smrt/network.py index a6f4657..300c8f0 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -78,27 +78,24 @@ def query(self, op_code, payload): return header, payload def login_dict(self, username, password): - return { - Protocol.get_id('username'): username.encode('ascii') + b'\x00', - Protocol.get_id('password'): password.encode('ascii') + b'\x00' - } + return [ + (Protocol.get_id('username'), username.encode('ascii') + b'\x00'), + (Protocol.get_id('password'), password.encode('ascii') + b'\x00'), + ] def login(self, username, password): - self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) + self.query(Protocol.GET, [(Protocol.get_id("get_token_id"), b'')]) self.query( Protocol.LOGIN, self.login_dict(username, password) ) def set(self, username, password, payload): - self.query(Protocol.GET, {Protocol.get_id("get_token_id"): b''}) - username = username.encode('ascii') + b'\x00' - password = password.encode('ascii') + b'\x00' - payload.update( - {Protocol.get_id('username'): username, Protocol.get_id('password'): password} - ) + self.query(Protocol.GET, [(Protocol.get_id("get_token_id"), b'')]) + real_payload = self.login_dict(username, password) + real_payload += payload header, payload = self.query( Protocol.LOGIN, - payload + real_payload ) return header, payload diff --git a/smrt/protocol.py b/smrt/protocol.py index 1dad748..57fd084 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -150,7 +150,7 @@ def analyze(data): def assemble_packet(header, payload): payload_bytes = b'' - for dtype, value in payload.items(): + for dtype, value in payload: payload_bytes += struct.pack('!hh', dtype, len(value)) payload_bytes += value header['check_length'] = Protocol.header["len"] + len(payload_bytes) + len(Protocol.PACKET_END) @@ -187,6 +187,14 @@ def set_vlan(vlan_num, member_mask, tagged_mask, vlan_name): value = struct.pack("!hii",vlan_num, member_mask, tagged_mask) + vlan_name.encode("ascii") + b'\x00' return value + def set_pvid(vlan_num, port): + value = (struct.pack("!bh", port, vlan_num)) + return value + if __name__ == "__main__": - v = Protocol.set_vlan(10, 255, 254, "test") - print(v, len(v)) + #v = Protocol.set_vlan(10, 255, 254, "test") + #print(v, len(v)) + out = bytearray() + pvid = "1,3" + print(Protocol.set_pvid(90, pvid).hex()) + diff --git a/smrt/set_vlan.py b/smrt/set_vlan.py index dbc5629..2b8abd0 100755 --- a/smrt/set_vlan.py +++ b/smrt/set_vlan.py @@ -4,7 +4,7 @@ from .protocol import Protocol from .network import Network -from .binary import ports2byte +from .binary import ports2byte, ports2list def loglevel(x): try: @@ -24,7 +24,8 @@ def main(): parser.add_argument('--vlan_name') parser.add_argument('--vlan_member') parser.add_argument('--vlan_tagged') - parser.add_argument('--delete', action='store_true') + parser.add_argument('--vlan_pvid') + parser.add_argument('--delete', action="store_true") parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') parser.add_argument('action', default=None, nargs='?') args = parser.parse_args() @@ -34,14 +35,22 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids net.login(args.username, args.password) - l = net.login_dict(args.username, args.password) + if (args.delete): v = Protocol.set_vlan(int(args.vlan), 0, 0, "") else: v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) - l.update({actions["vlan"]: v}) + l = [(actions["vlan"], v)] header, payload = net.set(args.username, args.password, l) print(*payload, sep="\n") + if args.vlan_pvid is not None: + l = [] + for port in ports2list(args.vlan_pvid): + if port != 0: + l.append( (actions["pvid"], Protocol.set_pvid(args.vlan, port)) ) + header, payload = net.set(args.username, args.password, l) + print(*payload, sep="\n") + if __name__ == "__main__": main() diff --git a/smrt/smrt.py b/smrt/smrt.py index 4a2c8c4..aa3f52d 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -34,7 +34,7 @@ def main(): # net.set(args.username, args.password, l) if args.action in actions: - header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) + header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) print(*payload, sep="\n") else: print("Actions:" , *actions.keys()) From 57896e5f65687e1c7e99adcb12bb2e8248f79ede Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 18:48:39 +0100 Subject: [PATCH 32/55] Return to classic imports --- smrt/__init__.py | 0 smrt/discovery.py | 4 ++-- smrt/network.py | 2 +- smrt/protocol.py | 2 +- smrt/smrt.py | 4 ++-- smrt/tp_analyse.py | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 39 insertions(+), 6 deletions(-) delete mode 100644 smrt/__init__.py create mode 100644 smrt/tp_analyse.py diff --git a/smrt/__init__.py b/smrt/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/smrt/discovery.py b/smrt/discovery.py index eadc48c..52c9e5e 100755 --- a/smrt/discovery.py +++ b/smrt/discovery.py @@ -5,8 +5,8 @@ import argparse import netifaces -from .protocol import Protocol -from .network import Network, ConnectionProblem +from protocol import Protocol +from network import Network, ConnectionProblem logger = logging.getLogger(__name__) diff --git a/smrt/network.py b/smrt/network.py index 300c8f0..ca80b1b 100755 --- a/smrt/network.py +++ b/smrt/network.py @@ -2,7 +2,7 @@ import socket, random, logging -from .protocol import Protocol +from protocol import Protocol logger = logging.getLogger(__name__) diff --git a/smrt/protocol.py b/smrt/protocol.py index 57fd084..bfcd170 100644 --- a/smrt/protocol.py +++ b/smrt/protocol.py @@ -1,6 +1,6 @@ import struct from ipaddress import ip_address -from smrt.binary import byte2ports +from binary import byte2ports class Protocol: PACKET_END = b'\xff\xff\x00\x00' diff --git a/smrt/smrt.py b/smrt/smrt.py index aa3f52d..774787a 100755 --- a/smrt/smrt.py +++ b/smrt/smrt.py @@ -2,8 +2,8 @@ import socket, time, random, argparse, logging -from .protocol import Protocol -from .network import Network +from protocol import Protocol +from network import Network def loglevel(x): try: diff --git a/smrt/tp_analyse.py b/smrt/tp_analyse.py new file mode 100644 index 0000000..dfec9ed --- /dev/null +++ b/smrt/tp_analyse.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import socket, time, random, argparse, logging +from protocol import Protocol + +def main(): + with open("tp_analyse.txt") as f: + first = True + ret = [] + for l in f: + try: + adr = int(l[:4], 16) + except ValueError: + continue + if adr == 0: + if not first: + ret.append(data) + first = False + data = bytearray() + if adr % 16 == 0: + #print(adr, bytearray.fromhex(l[6:53].split(" "))) + data += bytearray.fromhex(l[6:53]) + ret.append(data) + + for s in ret: + data = Protocol.decode(s) + print(data.hex(" ")) + print(Protocol.analyze(data)) + + +if __name__ == '__main__': + main() + From 17c12e2d15c95374cfd1759041ee74ec66c3e260 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 18:50:59 +0100 Subject: [PATCH 33/55] Move src to root --- smrt/analyze.py => analyze.py | 0 smrt/binary.py => binary.py | 0 smrt/discovery.py => discovery.py | 0 smrt/network.py => network.py | 0 smrt/protocol.py => protocol.py | 0 smrt/set_vlan.py => set_vlan.py | 0 setup.py | 63 ----------------------------- smrt/smrt.py => smrt.py | 0 smrt/tp_analyse.py => tp_analyse.py | 0 9 files changed, 63 deletions(-) rename smrt/analyze.py => analyze.py (100%) rename smrt/binary.py => binary.py (100%) rename smrt/discovery.py => discovery.py (100%) rename smrt/network.py => network.py (100%) rename smrt/protocol.py => protocol.py (100%) rename smrt/set_vlan.py => set_vlan.py (100%) delete mode 100644 setup.py rename smrt/smrt.py => smrt.py (100%) rename smrt/tp_analyse.py => tp_analyse.py (100%) diff --git a/smrt/analyze.py b/analyze.py similarity index 100% rename from smrt/analyze.py rename to analyze.py diff --git a/smrt/binary.py b/binary.py similarity index 100% rename from smrt/binary.py rename to binary.py diff --git a/smrt/discovery.py b/discovery.py similarity index 100% rename from smrt/discovery.py rename to discovery.py diff --git a/smrt/network.py b/network.py similarity index 100% rename from smrt/network.py rename to network.py diff --git a/smrt/protocol.py b/protocol.py similarity index 100% rename from smrt/protocol.py rename to protocol.py diff --git a/smrt/set_vlan.py b/set_vlan.py similarity index 100% rename from smrt/set_vlan.py rename to set_vlan.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 0ccf676..0000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -try: - import pypandoc - # for PyPI: Removing images with relative paths and their descriptions: - import re - LDESC = open('README.md', 'r').read() - matches = re.findall(r'\n\n(.*(\n.+)*:\n\n!\[.*\]\((.*\))(\n\n)?)', LDESC) - for match in matches: - text, _, link, _ = match - if text.startswith('http://'): continue - LDESC = LDESC.replace(text, '') - # Converting to rst - LDESC = pypandoc.convert(LDESC, 'rst', format='md') -except (ImportError, IOError, RuntimeError): - LDESC = '' - -setup(name='smrt', - version = '0.1.dev', - description = 'Python package and software for the TP-Link TL-SG105E and TL-SG108E smart switches', - long_description = LDESC, - author = 'Philipp Klaus', - author_email = 'philipp.l.klaus@web.de', - url = 'https://github.com/pklaus/smrt', - license = 'GPL', - #packages = ['smrt', 'smrt.discovery', 'smrt.operations', 'smrt.protocol', 'smrt.smrt'], - packages = ['smrt'], - entry_points = { - 'console_scripts': [ - 'smrt.cli = smrt.smrt:main', - 'smrt.discovery = smrt.discovery:main', - ], - }, - include_package_data = True, - zip_safe = True, - platforms = 'any', - install_requires = ['netifaces'], - extras_require = { - #'savescreen': ["Pillow",], - }, - #package_data = { - # '': ['resources/*.png'], - #}, - keywords = 'TP-Link TL-SG105E TL-SG108E', - classifiers = [ - 'Development Status :: 4 - Beta', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering :: Visualization', - 'Topic :: Scientific/Engineering', - 'Topic :: System :: Hardware :: Hardware Drivers', - 'Intended Audience :: Science/Research', - ] -) - - diff --git a/smrt/smrt.py b/smrt.py similarity index 100% rename from smrt/smrt.py rename to smrt.py diff --git a/smrt/tp_analyse.py b/tp_analyse.py similarity index 100% rename from smrt/tp_analyse.py rename to tp_analyse.py From 9c8464ed3a5fe7d5bf94389b444638823b333e12 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 21:01:13 +0100 Subject: [PATCH 34/55] Remove first field in ids_tp --- .gitignore | 3 ++- protocol.py | 75 +++++++++++++++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 71b820f..9f97fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc -dumps __pycache__ +*.txt +/com/ diff --git a/protocol.py b/protocol.py index bfcd170..f347426 100644 --- a/protocol.py +++ b/protocol.py @@ -66,37 +66,37 @@ class Protocol: } ids_tp = { - 1: ['*s', 'str', 'type'], - 2: ['*s', 'str', 'hostname'], - 3: ['6s', 'hex', 'mac'], - 4: ['4s', 'ip', 'ip_addr'], - 5: ['4s', 'ip', 'ip_mask'], - 6: ['4s', 'ip', 'gateway'], - 7: ['*s', 'str', 'firmware'], - 8: ['*s', 'str', 'hardware'], - 9: ['?', 'bool', 'dhcp'], - 10: ['b', 'dec', 'num_ports'], - 13: ['?', 'bool', 'v4'], - 512: ['*s', 'str', 'username'], - 514: ['*s', 'str', 'password'], - 2304: ['a', 'action','save'], - 2305: ['a', 'action','get_token_id'], - 4352: ['?', 'bool', 'igmp_snooping'], - 4096: ['*s', 'hex', 'ports'], - 4608: ['5s', 'hex', 'trunk'], - 8192: ['2s', 'hex', 'mtu_vlan'], - 8704: ['?', 'hex', 'vlan_enabled'], - 8705: ['*s', 'vlan', 'vlan'], - 8706: ['*s', 'pvid', 'pvid'], - 8707: ['*s', 'str', 'vlan_filler'], - 12288: ['?', 'bool', 'qos1'], - 12289: ['2s', 'hex', 'qos2'], - 16640: ['10s','hex', 'mirror'], - 16384: ['*s', 'stat', 'stats'], - 17152: ['?', 'bool', 'loop_prev'], + 1: ('str', 'type'), + 2: ('str', 'hostname'), + 3: ('hex', 'mac'), + 4: ('ip', 'ip_addr'), + 5: ('ip', 'ip_mask'), + 6: ('ip', 'gateway'), + 7: ('str', 'firmware'), + 8: ('str', 'hardware'), + 9: ('bool', 'dhcp'), + 10: ('dec', 'num_ports'), + 13: ('bool', 'v4'), + 512: ('str', 'username'), + 514: ('str', 'password'), + 2304: ('action','save'), + 2305: ('action','get_token_id'), + 4352: ('bool', 'igmp_snooping'), + 4096: ('hex', 'ports'), + 4608: ('hex', 'trunk'), + 8192: ('hex', 'mtu_vlan'), + 8704: ('hex', 'vlan_enabled'), + 8705: ('vlan', 'vlan'), + 8706: ('pvid', 'pvid'), + 8707: ('str', 'vlan_filler'), + 12288: ('bool', 'qos1'), + 12289: ('hex', 'qos2'), + 16640: ('hex', 'mirror'), + 16384: ('stat', 'stats'), + 17152: ('bool', 'loop_prev'), } - tp_ids = {v[2]: k for k, v in ids_tp.items()} + tp_ids = {v[1]: k for k, v in ids_tp.items()} def get_sequence_kind(sequence): for key, value in Protocol.sequences.items(): @@ -136,9 +136,9 @@ def interpret_payload(payload): data = payload[4:4+dlen] results.append( ( dtype, - Protocol.ids_tp[dtype][2], + Protocol.ids_tp[dtype][1], # data.hex(sep=" "), - Protocol.interpret_value(data, Protocol.ids_tp[dtype][1]) + Protocol.interpret_value(data, Protocol.ids_tp[dtype][0]) ) ) payload = payload[4+dlen:] @@ -178,9 +178,12 @@ def interpret_value(value, kind): elif kind == 'stat': value = struct.unpack("!bbbiiii", value) elif kind == 'bool': - if len(value) == 0: pass - elif len(value) == 1: value = value[0] > 0 - else: raise AssertionError('boolean should be one byte long') + if len(value) == 0: + pass + elif len(value) == 1: + value = value[0] > 0 + else: + raise AssertionError('boolean should be one byte long') return value def set_vlan(vlan_num, member_mask, tagged_mask, vlan_name): @@ -195,6 +198,4 @@ def set_pvid(vlan_num, port): #v = Protocol.set_vlan(10, 255, 254, "test") #print(v, len(v)) out = bytearray() - pvid = "1,3" - print(Protocol.set_pvid(90, pvid).hex()) - + print(Protocol.set_pvid(90, 5).hex()) From 1249d10ff6b273cb01293d03f31d4b619919f74c Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 1 Nov 2020 23:04:22 +0100 Subject: [PATCH 35/55] Simplification --- binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binary.py b/binary.py index 6bdcfbe..5e2f3a2 100644 --- a/binary.py +++ b/binary.py @@ -27,7 +27,7 @@ def byte2ports(byte): for i in range(32): if byte % 2: out.append(str(i + 1)) - byte = (byte >> 1) + byte >>= 1 return SEP.join(out) if __name__ == '__main__': From 5aa4ca7aea4a572ac8083728497250ffbcd12867 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sat, 7 Nov 2020 12:33:41 +0100 Subject: [PATCH 36/55] Only --vlan_pvid is possible --- set_vlan.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/set_vlan.py b/set_vlan.py index 2b8abd0..e02f172 100755 --- a/set_vlan.py +++ b/set_vlan.py @@ -2,9 +2,9 @@ import socket, time, random, argparse, logging -from .protocol import Protocol -from .network import Network -from .binary import ports2byte, ports2list +from protocol import Protocol +from network import Network +from binary import ports2byte, ports2list def loglevel(x): try: @@ -38,11 +38,13 @@ def main(): if (args.delete): v = Protocol.set_vlan(int(args.vlan), 0, 0, "") - else: + elif args.vlan_member or args.vlan_tagged: v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) - l = [(actions["vlan"], v)] - header, payload = net.set(args.username, args.password, l) - print(*payload, sep="\n") + + if args.vlan_member or args.vlan_tagged or args.delete: + l = [(actions["vlan"], v)] + header, payload = net.set(args.username, args.password, l) + print(*payload, sep="\n") if args.vlan_pvid is not None: l = [] From 8c7d1463d63b6bb03d7bc3d74b4c34d5a51a482c Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sat, 7 Nov 2020 15:10:56 +0100 Subject: [PATCH 37/55] Add tests for pvid --- analyze.py | 54 --------------------------------------------------- binary.py | 5 +++-- protocol.py | 4 ++-- set_vlan.py | 11 ++++++----- tp_analyse.py | 4 ++-- 5 files changed, 13 insertions(+), 65 deletions(-) delete mode 100755 analyze.py mode change 100644 => 100755 tp_analyse.py diff --git a/analyze.py b/analyze.py deleted file mode 100755 index 7e066ac..0000000 --- a/analyze.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -import socket, time, random, argparse, logging -from protocol import Protocol - -def main(): - capture = [ -b"\x5d\x75\xb2\x09\x6a\xdd" \ -b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbd\x42\x2b\xa2\xf5\xd7\x87" \ -b"\xae\xed\x50\x8f\x63\x5c\xc2\x02\x90\x9a\x52\x12\x81\xc6\xd8\xd5" \ -b"\x2b\x36", - -b"\x5d\x76\xb2\x09\x6a\xdd" \ -b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbd\x42\x2b\xa2\xf5\xd7\x8b" \ -b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\xa4\xec\x81\xc6", - -b"\x5d\x77\xb2\x09\x6a\xdd" \ -b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbc\x42\x2b\xa2\xf5\xd7\xe3" \ -b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\x59\x13\x81\xc0\x46\x4e" \ -b"\x46\x5f\x27\xd9\x42\xb5\xa4\x46\x08\x40\x34\x18\x22\x2a\x5d\x4f" \ -b"\x9b\x67\x38\x1e\x76\x08\x73\x71\xf2\x9b\x54\xbe\x89\xd9\xc8\x91" \ -b"\x5a\xb3\x73\xda\x4c\x00", - -b"\x5d\x70\xb2\x09\x6a\xdd" \ -b"\xd3\xd7\x6b\x8d\xbc\x14\x6d\x7b\x7f\xbc\x42\x2b\xa2\xf5\xd7\x00" \ -b"\xae\xed\x50\x8f\x7f\xb7\xc2\x02\x90\x9a\x79\x13\x81\xc7\x26\x08" \ -b"\x2a\x36\x5b\xd9\x41\xb7\xa4\x40\x69\x24\x59\x71\x4c\x6e\x1a\x28" \ -b"\xfa\x02\x54\x60\x76\x2a\x72\xe1\xe2\x9b\x56\x2e\xfd\xbc\x1b\xe5" \ -b"\x68\xb3\x0c\x75\x24\x6f\x49\x44\x2a\x92\xab\x86\xfc\xa4\xd4\x12" \ -b"\xf1\xc0\x16\x83\x9f\x9a\x2c\xfa\x99\x3a\x62\x90\x19\x59\xb8\xb3" \ -b"\x82\xdf\x2e\xe6\xe5\xf9\x54\x80\x85\x09\xce\x81\xc6\xb0\x6e\xf7" \ -b"\xba\xe8\x86\xb2\x52\xb6\x96\xd7\xfd\x73\xe1\xf9\x8f\xc0\xa5\x50" \ -b"\x2e\xec\xe6\x97\xc1\x74\xf9\x71\xdc\x89\xa8\xbb\x6f\xd2\x47\xf1" \ -b"\x77\x7c\xd8\x70\xb7\xf7\xad\x06\xa2\xe2\x9b\x5c\x69\xc8\xf1\x0d" \ -b"\xc1\x58\x72\xa1\xb2\x5a\x31\x04\xdc" -] - for s in capture: - data = Protocol.decode(s) - print(Protocol.analyze(data)) - -# net = Network(args.ip_address, args.host_mac, args.switch_mac) -# net.login(args.username, args.password) -# actions = Protocol.tp_ids -# v = Protocol.set_vlan(10, 255, 254, "pipo") -# -# if args.action in actions: -# header, payload = net.query(Protocol.GET, {actions[args.action]: b''}) -# print(*payload, sep="\n") -# else: -# print("Actions:" , *actions.keys()) -# header, payload = net.query(Protocol.SET, {actions[args.action]: v}) - -if __name__ == "__main__": - main() diff --git a/binary.py b/binary.py index 5e2f3a2..f8a5a73 100644 --- a/binary.py +++ b/binary.py @@ -32,5 +32,6 @@ def byte2ports(byte): if __name__ == '__main__': #print(ports2byte("12345678")) - a = ports2byte("1,2,5,6,8,12,15") - print(a, byte2ports(a)) + #a = ports2byte("1,2,5,6,8,12,15") + #print(a, byte2ports(a)) + print(ports2list("1,2")) diff --git a/protocol.py b/protocol.py index f347426..565f208 100644 --- a/protocol.py +++ b/protocol.py @@ -174,7 +174,7 @@ def interpret_value(value, kind): value[1] = byte2ports(value[1]) value[2] = byte2ports(value[2]) elif kind == 'pvid': - value = struct.unpack("!bh", value) + value = struct.unpack("!bh", value) if value else None elif kind == 'stat': value = struct.unpack("!bbbiiii", value) elif kind == 'bool': @@ -191,7 +191,7 @@ def set_vlan(vlan_num, member_mask, tagged_mask, vlan_name): return value def set_pvid(vlan_num, port): - value = (struct.pack("!bh", port, vlan_num)) + value = struct.pack("!bh", port, vlan_num) return value if __name__ == "__main__": diff --git a/set_vlan.py b/set_vlan.py index e02f172..234789e 100755 --- a/set_vlan.py +++ b/set_vlan.py @@ -34,19 +34,20 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - net.login(args.username, args.password) - if (args.delete): - v = Protocol.set_vlan(int(args.vlan), 0, 0, "") - elif args.vlan_member or args.vlan_tagged: - v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) if args.vlan_member or args.vlan_tagged or args.delete: + if (args.delete): + v = Protocol.set_vlan(int(args.vlan), 0, 0, "") + else: + v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) + net.login(args.username, args.password) l = [(actions["vlan"], v)] header, payload = net.set(args.username, args.password, l) print(*payload, sep="\n") if args.vlan_pvid is not None: + net.login(args.username, args.password) l = [] for port in ports2list(args.vlan_pvid): if port != 0: diff --git a/tp_analyse.py b/tp_analyse.py old mode 100644 new mode 100755 index dfec9ed..263a714 --- a/tp_analyse.py +++ b/tp_analyse.py @@ -24,8 +24,8 @@ def main(): for s in ret: data = Protocol.decode(s) - print(data.hex(" ")) - print(Protocol.analyze(data)) + # print(data.hex(" ")) + print(Protocol.analyze(data)[1]) if __name__ == '__main__': From 7aa838112e77e1b6d568e4a338eab77c8b5e9eb1 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Wed, 11 Nov 2020 11:25:52 +0100 Subject: [PATCH 38/55] Add interface parameter to discovery --- .gitignore | 1 + discovery.py | 49 ++++++++++++++++++++++++++++++++++--------------- loglevel.py | 10 ++++++++++ smrt.py | 17 +++++++---------- 4 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 loglevel.py diff --git a/.gitignore b/.gitignore index 9f97fc1..22961d7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__ *.txt /com/ +.*.swp diff --git a/discovery.py b/discovery.py index 52c9e5e..670568f 100755 --- a/discovery.py +++ b/discovery.py @@ -7,29 +7,43 @@ from protocol import Protocol from network import Network, ConnectionProblem +from loglevel import loglevel logger = logging.getLogger(__name__) -def discover_switches(): - interfaces = netifaces.interfaces() +class InterfaceProblem(Exception): + pass + +def discover_switches(interface=None): + if interface is None: + interfaces = netifaces.interfaces() + if "lo" in interfaces: + interfaces.remove("lo") + if len(interfaces) > 1: + msg = [""] + msg.append("Error: more than 1 interface. Use -i or --interface to specify the name") + msg.append("Interfaces:") + for iface in interfaces: + msg.append(" " + repr(iface)) + raise InterfaceProblem("\n".join(msg)) + settings = [] - for iface in interfaces: - addrs = netifaces.ifaddresses(iface) - if netifaces.AF_INET not in addrs: - continue - if netifaces.AF_LINK not in addrs: + addrs = netifaces.ifaddresses(interface) + if netifaces.AF_INET not in addrs: + raise ConnectionProblem("Error: not AF_INTER address") + if netifaces.AF_LINK not in addrs: + raise ConnectionProblem("Error: not AF_LINK address") + assert len(addrs[netifaces.AF_LINK]) == 1 + mac = addrs[netifaces.AF_LINK][0]['addr'] + for addr in addrs[netifaces.AF_INET]: + if 'broadcast' not in addr or 'addr' not in addr: continue - assert len(addrs[netifaces.AF_LINK]) == 1 - mac = addrs[netifaces.AF_LINK][0]['addr'] - for addr in addrs[netifaces.AF_INET]: - if 'broadcast' not in addr or 'addr' not in addr: - continue - settings.append((iface, addr['addr'], mac, addr['broadcast'])) + settings.append((interface, addr['addr'], mac, addr['broadcast'])) ret = [] for iface, ip, mac, broadcast in settings: net = Network(ip, mac) - logger.warning((iface, ip, mac, broadcast)) + logger.debug((iface, ip, mac, broadcast)) net.send(Protocol.DISCOVERY, {}) while True: try: @@ -39,7 +53,12 @@ def discover_switches(): break def main(): - switches = discover_switches() + parser = argparse.ArgumentParser() + parser.add_argument('--interface', '-i') + parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') + args = parser.parse_args() + logging.basicConfig(level=args.loglevel) + switches = discover_switches(args.interface) for header, payload in switches: print(*payload, sep="\n") print("-"*16) diff --git a/loglevel.py b/loglevel.py new file mode 100644 index 0000000..d013946 --- /dev/null +++ b/loglevel.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +import logging +def loglevel(x): + try: + return getattr(logging, x.upper()) + except AttributeError: + raise argparse.ArgumentError('Select a proper loglevel') + +if __name__ == '__main__': + pass diff --git a/smrt.py b/smrt.py index 774787a..d525d98 100755 --- a/smrt.py +++ b/smrt.py @@ -1,15 +1,16 @@ #!/usr/bin/env python -import socket, time, random, argparse, logging +import socket +import time +import random +import logging +import argparse +import netifaces from protocol import Protocol from network import Network -def loglevel(x): - try: - return getattr(logging, x.upper()) - except AttributeError: - raise argparse.ArgumentError('Select a proper loglevel') +from loglevel import loglevel def main(): logger = logging.getLogger(__name__) @@ -28,10 +29,6 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids net.login(args.username, args.password) - # l = net.login_dict(args.username, args.password) - # v = Protocol.set_vlan(10, 255, 248, "test2") - # l.update({actions["vlan"]: v}) - # net.set(args.username, args.password, l) if args.action in actions: header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) From f556c6308de1400f7b0177665d213054367fd9af Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Wed, 11 Nov 2020 12:05:31 +0100 Subject: [PATCH 39/55] Only take first address of interface --- discovery.py | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/discovery.py b/discovery.py index 670568f..7620e67 100755 --- a/discovery.py +++ b/discovery.py @@ -29,39 +29,45 @@ def discover_switches(interface=None): settings = [] addrs = netifaces.ifaddresses(interface) + logger.debug("addrs:" + repr(addrs)) if netifaces.AF_INET not in addrs: - raise ConnectionProblem("Error: not AF_INTER address") + raise InterfaceProblem("Error: not AF_INTER address") if netifaces.AF_LINK not in addrs: - raise ConnectionProblem("Error: not AF_LINK address") - assert len(addrs[netifaces.AF_LINK]) == 1 + raise InterfaceProblem("Error: not AF_LINK address") + mac = addrs[netifaces.AF_LINK][0]['addr'] - for addr in addrs[netifaces.AF_INET]: - if 'broadcast' not in addr or 'addr' not in addr: - continue - settings.append((interface, addr['addr'], mac, addr['broadcast'])) + # take first address of interface + addr = addrs[netifaces.AF_INET][0] + if 'broadcast' not in addr or 'addr' not in addr: + raise InterfaceProblem("Error: no addr or broadcast for address") + ip = addr['addr'] - ret = [] - for iface, ip, mac, broadcast in settings: - net = Network(ip, mac) - logger.debug((iface, ip, mac, broadcast)) - net.send(Protocol.DISCOVERY, {}) - while True: - try: - header, payload = net.receive() - yield header, payload - except ConnectionProblem: - break + net = Network(ip, mac) + logger.debug((interface, ip, mac)) + net.send(Protocol.DISCOVERY, {}) + while True: + try: + header, payload = net.receive() + yield ip, mac, header, payload + except ConnectionProblem: + break def main(): parser = argparse.ArgumentParser() parser.add_argument('--interface', '-i') + parser.add_argument('--command', '-c', action="store_true") parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') args = parser.parse_args() logging.basicConfig(level=args.loglevel) switches = discover_switches(args.interface) - for header, payload in switches: - print(*payload, sep="\n") - print("-"*16) + for ip, mac, header, payload in switches: + if args.command: + p = {x[1]: x[2] for x in payload} + cmd = f"./smrt.py --username admin --password admin --host-mac={mac} --ip-address={ip} --switch-mac {p['mac']}" + print(cmd) + else: + print(ip, mac, *payload, sep="\n") + print("-"*16) if __name__ == "__main__": main() From e8b822f4867cfee0117657353842dd7017d734f4 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Wed, 11 Nov 2020 12:41:32 +0100 Subject: [PATCH 40/55] Rewrite README --- README.md | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/README.md b/README.md index e199d4e..2a00888 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +# smrt A utility to configure your TP-Link Easy Smart Switch on Linux or Mac OS X. This tool is written in Python. @@ -10,3 +11,253 @@ Supposedly supported switches: * TL-SG1016DE * TL-SG1024DE +## Discover switches + +### Simple discovery + +``` +$ ./discovery.py +``` + +### Multiple interfaces + +If more than one interface, this error message gives the list: + +``` +Traceback (most recent call last): + File "./discovery.py", line 67, in + main() + File "./discovery.py", line 62, in main + for header, payload in switches: + File "./discovery.py", line 28, in discover_switches + raise InterfaceProblem("\n".join(msg)) +__main__.InterfaceProblem: +Error: more than 1 interface. Use -i or --interface to specify the name +Interfaces: + 'enp3s0' + 'virbr0' + 'virbr0-nic' +``` + +### Default output + +Give the right interface: +``` +$ ./discovery.py -i enp3s0 +``` + +Output (found 2 switches): +``` +192.168.9.36 +ba.ff.ee.ff.ac.ee +(1, 'type', 'TL-SG108E') +(2, 'hostname', 'sg108ev4') +(3, 'mac', '01:01:01:01:01:01') +(7, 'firmware', '1.0.0 Build 20181120 Rel.40749') +(8, 'hardware', 'TL-SG108E 4.0') +(9, 'dhcp', False) +(4, 'ip_addr', IPv4Address('192.168.9.202')) +(5, 'ip_mask', IPv4Address('255.255.255.0')) +(6, 'gateway', IPv4Address('192.168.9.1')) +(13, 'v4', True) +---------------- +(1, 'type', 'TL-SG108E') +(2, 'hostname', 'sg108ev1') +(3, 'mac', '02:02:02:02:02:02') +(7, 'firmware', '1.1.2 Build 20141017 Rel.50749') +(8, 'hardware', 'TL-SG108E 1.0') +(9, 'dhcp', False) +(4, 'ip_addr', IPv4Address('192.168.9.203')) +(5, 'ip_mask', IPv4Address('255.255.255.0')) +(6, 'gateway', IPv4Address('192.168.9.1')) +``` + +### Command output with -c + +With switch `-c` or `--command`, output gives the syntax for using smrt with right arguments + +``` +$ ./discovery.py -i enp3s0 -c +./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 01:01:01:01:01:01 +./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 +``` + +## smrt.py + +### without command gives list of command + + +``` +$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 01:01:01:01:01:01 +Actions: type hostname mac ip_addr ip_mask gateway firmware hardware dhcp num_ports v4 username password save get_token_id igmp_snooping ports trunk mtu_vlan vlan_enabled vlan pvid vlan_filler qos1 qos2 mirror stats loop_prev +``` + +Note: not all actions are of interest. Some TP-link code (13: "v4", 8707: "vlan_filler") correspond to unknown codes but are presents in output, so must be presents in code/command list. + +### vlan + +``` +$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 vlan + +(8704, 'vlan_enabled', '01') +(8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) +(8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) +(8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) +(8707, 'vlan_filler', ' ') +``` + +### pvid + +``` +$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 pvid +(8706, 'pvid', (1, 90)) +(8706, 'pvid', (2, 90)) +(8706, 'pvid', (3, 90)) +(8706, 'pvid', (4, 90)) +(8706, 'pvid', (5, 90)) +(8706, 'pvid', (6, 90)) +(8706, 'pvid', (7, 90)) +(8706, 'pvid', (8, 90)) +(8707, 'vlan_filler', ' ') +``` + +### stats + +``` +$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 stats +(16384, 'stats', (1, 1, 6, 48, 0, 6356, 14)) +(16384, 'stats', (2, 1, 0, 0, 0, 0, 0)) +(16384, 'stats', (3, 1, 0, 0, 0, 0, 0)) +(16384, 'stats', (4, 1, 5, 6404, 0, 0, 14)) +(16384, 'stats', (5, 1, 0, 0, 0, 0, 0)) +(16384, 'stats', (6, 1, 0, 0, 0, 0, 0)) +(16384, 'stats', (7, 1, 0, 0, 0, 0, 0)) +(16384, 'stats', (8, 1, 0, 0, 0, 0, 0)) +``` + +### ports + +``` +$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 ports +(4096, 'ports', '01:01:00:01:06:00:00') +(4096, 'ports', '02:01:00:01:00:00:00') +(4096, 'ports', '03:01:00:01:00:00:00') +(4096, 'ports', '04:01:00:01:05:00:00') +(4096, 'ports', '05:01:00:01:00:00:00') +(4096, 'ports', '06:01:00:01:00:00:00') +(4096, 'ports', '07:01:00:01:00:00:00') +(4096, 'ports', '08:01:00:01:00:00:00') +``` +## Set VLAN settings + +### Syntax + +`smrt.py` shows parameters but can't change them. `set_vlan.py` permits to set VLAN settings for switches + +Command syntax is the same than smrt with some new parameters: + +* `--vlan`: vlan number (1-4094) +* `--vlan_name`: acceptable vlan name for TP-Link switch +* `--vlan_member`: comma separated list without space of member ports. Ex: 1,2,4 +* `--vlan_tagged`: comma separated list without space of tagged ports. Ex: 1,2 +* `--vlan_pvid`: set pvid to `--vlan` for given ports + +### Alias + +After discovery, setting an shell alias reduces command size + +``` +$ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' +$ alias set_vlan='python ~/smrt/set_vlan.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' +``` + +### Example 1 : add new vlan 120 + +``` +$ smrt vlan +(8704, 'vlan_enabled', '01') +(8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) +(8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) +(8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) +(8707, 'vlan_filler', ' ') +$ set_vlan --vlan 120 --vlan_name "vlan_test_2" --vlan_member 1,4,5,6 --vlan_tagged 1 +(8704, 'vlan_enabled', '01') +(8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) +(8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) +(8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) +(8705, 'vlan', [120, '1,4,5,6', '1', 'vlan_test_2']) +(8707, 'vlan_filler', ' ') +``` + +### Example 2 : change to pvid 120 for ports 5 and 6 + +Note : vlan must exist (see example 1) before this command. + +``` +$ smrt pvid +(8706, 'pvid', (1, 90)) +(8706, 'pvid', (2, 90)) +(8706, 'pvid', (3, 90)) +(8706, 'pvid', (4, 90)) +(8706, 'pvid', (5, 90)) +(8706, 'pvid', (6, 90)) +(8706, 'pvid', (7, 90)) +(8706, 'pvid', (8, 90)) +(8707, 'vlan_filler', ' ') +$ set_vlan --vlan 120 --vlan_pvid 5,6 +(8706, 'pvid', (1, 90)) +(8706, 'pvid', (2, 90)) +(8706, 'pvid', (3, 90)) +(8706, 'pvid', (4, 90)) +(8706, 'pvid', (5, 120)) +(8706, 'pvid', (6, 120)) +(8706, 'pvid', (7, 90)) +(8706, 'pvid', (8, 90)) +(8707, 'vlan_filler', ' ') +``` + +### Exemple 3 : remove vlan 120 + +``` +$ set_vlan --vlan 120 --delete +(8704, 'vlan_enabled', '01') +(8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) +(8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) +(8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) +(8707, 'vlan_filler', ' ') +``` + +Note that, for PVID, corresponding ports are reinitialized to vlan 1: +``` +$ smrt pvid +(8706, 'pvid', (1, 90)) +(8706, 'pvid', (2, 90)) +(8706, 'pvid', (3, 90)) +(8706, 'pvid', (4, 90)) +(8706, 'pvid', (5, 1)) +(8706, 'pvid', (6, 1)) +(8706, 'pvid', (7, 90)) +(8706, 'pvid', (8, 90)) +(8707, 'vlan_filler', ' ') +``` + +### Exemple 4 : add new vlan and pvid + +``` +$ set_vlan --vlan 130 --vlan_name "vlan_test_3" --vlan_member 1,4,5,6 --vlan_tagged 1 --vlan_pvid 4,5,6 +(8704, 'vlan_enabled', '01') +(8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) +(8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) +(8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) +(8705, 'vlan', [130, '1,4,5,6', '1', 'vlan_test_3']) +(8707, 'vlan_filler', ' ') +(8706, 'pvid', (1, 90)) +(8706, 'pvid', (2, 90)) +(8706, 'pvid', (3, 90)) +(8706, 'pvid', (4, 130)) +(8706, 'pvid', (5, 130)) +(8706, 'pvid', (6, 130)) +(8706, 'pvid', (7, 90)) +(8706, 'pvid', (8, 90)) +(8707, 'vlan_filler', ' ') +``` From 1faf6098cd4b5700fcbc7efba26dac9c9de58719 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Thu, 12 Nov 2020 09:20:28 +0100 Subject: [PATCH 41/55] Get better exception handling in main --- README.md | 10 +--------- discovery.py | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 2a00888..9efe5b9 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,9 @@ $ ./discovery.py ### Multiple interfaces -If more than one interface, this error message gives the list: +If more than one interface, error message gives the list: ``` -Traceback (most recent call last): - File "./discovery.py", line 67, in - main() - File "./discovery.py", line 62, in main - for header, payload in switches: - File "./discovery.py", line 28, in discover_switches - raise InterfaceProblem("\n".join(msg)) -__main__.InterfaceProblem: Error: more than 1 interface. Use -i or --interface to specify the name Interfaces: 'enp3s0' diff --git a/discovery.py b/discovery.py index 7620e67..289a611 100755 --- a/discovery.py +++ b/discovery.py @@ -20,8 +20,7 @@ def discover_switches(interface=None): if "lo" in interfaces: interfaces.remove("lo") if len(interfaces) > 1: - msg = [""] - msg.append("Error: more than 1 interface. Use -i or --interface to specify the name") + msg = ["more than 1 interface. Use -i or --interface to specify the name"] msg.append("Interfaces:") for iface in interfaces: msg.append(" " + repr(iface)) @@ -31,26 +30,28 @@ def discover_switches(interface=None): addrs = netifaces.ifaddresses(interface) logger.debug("addrs:" + repr(addrs)) if netifaces.AF_INET not in addrs: - raise InterfaceProblem("Error: not AF_INTER address") + raise InterfaceProblem("not AF_INTER address") if netifaces.AF_LINK not in addrs: - raise InterfaceProblem("Error: not AF_LINK address") + raise InterfaceProblem("not AF_LINK address") mac = addrs[netifaces.AF_LINK][0]['addr'] # take first address of interface addr = addrs[netifaces.AF_INET][0] if 'broadcast' not in addr or 'addr' not in addr: - raise InterfaceProblem("Error: no addr or broadcast for address") + raise InterfaceProblem("no addr or broadcast for address") ip = addr['addr'] net = Network(ip, mac) logger.debug((interface, ip, mac)) net.send(Protocol.DISCOVERY, {}) + ret = [] while True: try: header, payload = net.receive() - yield ip, mac, header, payload + ret.append((ip, mac, header, payload)) except ConnectionProblem: break + return ret def main(): parser = argparse.ArgumentParser() @@ -59,15 +60,19 @@ def main(): parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') args = parser.parse_args() logging.basicConfig(level=args.loglevel) - switches = discover_switches(args.interface) - for ip, mac, header, payload in switches: - if args.command: - p = {x[1]: x[2] for x in payload} - cmd = f"./smrt.py --username admin --password admin --host-mac={mac} --ip-address={ip} --switch-mac {p['mac']}" - print(cmd) - else: - print(ip, mac, *payload, sep="\n") - print("-"*16) + try: + switches = discover_switches(args.interface) + except InterfaceProblem as e: + print("Error:", e) + else: + for ip, mac, header, payload in switches: + if args.command: + p = {x[1]: x[2] for x in payload} + cmd = f"./smrt.py --username admin --password admin --host-mac={mac} --ip-address={ip} --switch-mac {p['mac']}" + print(cmd) + else: + print(ip, mac, *payload, sep="\n") + print("-"*16) if __name__ == "__main__": main() From 32323450d730a248a231264fdfcaccb823402487 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Thu, 12 Nov 2020 09:31:10 +0100 Subject: [PATCH 42/55] Remove set_vlan : only smrt.py script --- README.md | 40 +++++++++++++++++------------------- set_vlan.py | 59 ----------------------------------------------------- smrt.py | 49 +++++++++++++++++++++++++++++++++----------- 3 files changed, 56 insertions(+), 92 deletions(-) delete mode 100755 set_vlan.py diff --git a/README.md b/README.md index 9efe5b9..ebbf806 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,18 @@ $ ./discovery.py -i enp3s0 -c ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 ``` +### Alias + +After discovery, setting an shell alias reduces command size + +``` +$ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' +``` + ## smrt.py ### without command gives list of command - ``` $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 01:01:01:01:01:01 Actions: type hostname mac ip_addr ip_mask gateway firmware hardware dhcp num_ports v4 username password save get_token_id igmp_snooping ports trunk mtu_vlan vlan_enabled vlan pvid vlan_filler qos1 qos2 mirror stats loop_prev @@ -89,7 +96,7 @@ Note: not all actions are of interest. Some TP-link code (13: "v4", 8707: "vlan_ ### vlan ``` -$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 vlan +$ smrt vlan # use alias (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) @@ -101,7 +108,7 @@ $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip- ### pvid ``` -$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 pvid +$ smrt pvid (8706, 'pvid', (1, 90)) (8706, 'pvid', (2, 90)) (8706, 'pvid', (3, 90)) @@ -116,7 +123,7 @@ $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip- ### stats ``` -$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 stats +$ smrt stats (16384, 'stats', (1, 1, 6, 48, 0, 6356, 14)) (16384, 'stats', (2, 1, 0, 0, 0, 0, 0)) (16384, 'stats', (3, 1, 0, 0, 0, 0, 0)) @@ -127,7 +134,7 @@ $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip- (16384, 'stats', (8, 1, 0, 0, 0, 0, 0)) ``` -### ports +### smrt ports ``` $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 ports @@ -144,25 +151,16 @@ $ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip- ### Syntax -`smrt.py` shows parameters but can't change them. `set_vlan.py` permits to set VLAN settings for switches +`smrt.py` shows parameters and can change them for VLANs if `--vlan` is present -Command syntax is the same than smrt with some new parameters: +Specific VLAN parameters: -* `--vlan`: vlan number (1-4094) +* `--vlan`: vlan number (1-4093) * `--vlan_name`: acceptable vlan name for TP-Link switch * `--vlan_member`: comma separated list without space of member ports. Ex: 1,2,4 * `--vlan_tagged`: comma separated list without space of tagged ports. Ex: 1,2 * `--vlan_pvid`: set pvid to `--vlan` for given ports -### Alias - -After discovery, setting an shell alias reduces command size - -``` -$ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' -$ alias set_vlan='python ~/smrt/set_vlan.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' -``` - ### Example 1 : add new vlan 120 ``` @@ -172,7 +170,7 @@ $ smrt vlan (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) (8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) (8707, 'vlan_filler', ' ') -$ set_vlan --vlan 120 --vlan_name "vlan_test_2" --vlan_member 1,4,5,6 --vlan_tagged 1 +$ smrt --vlan 120 --vlan_name "vlan_test_2" --vlan_member 1,4,5,6 --vlan_tagged 1 (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) @@ -196,7 +194,7 @@ $ smrt pvid (8706, 'pvid', (7, 90)) (8706, 'pvid', (8, 90)) (8707, 'vlan_filler', ' ') -$ set_vlan --vlan 120 --vlan_pvid 5,6 +$ smrt --vlan 120 --vlan_pvid 5,6 (8706, 'pvid', (1, 90)) (8706, 'pvid', (2, 90)) (8706, 'pvid', (3, 90)) @@ -211,7 +209,7 @@ $ set_vlan --vlan 120 --vlan_pvid 5,6 ### Exemple 3 : remove vlan 120 ``` -$ set_vlan --vlan 120 --delete +$ smrt --vlan 120 --delete (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) @@ -236,7 +234,7 @@ $ smrt pvid ### Exemple 4 : add new vlan and pvid ``` -$ set_vlan --vlan 130 --vlan_name "vlan_test_3" --vlan_member 1,4,5,6 --vlan_tagged 1 --vlan_pvid 4,5,6 +$ smrt --vlan 130 --vlan_name "vlan_test_3" --vlan_member 1,4,5,6 --vlan_tagged 1 --vlan_pvid 4,5,6 (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) diff --git a/set_vlan.py b/set_vlan.py deleted file mode 100755 index 234789e..0000000 --- a/set_vlan.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python - -import socket, time, random, argparse, logging - -from protocol import Protocol -from network import Network -from binary import ports2byte, ports2list - -def loglevel(x): - try: - return getattr(logging, x.upper()) - except AttributeError: - raise argparse.ArgumentError('Select a proper loglevel') - -def main(): - logger = logging.getLogger(__name__) - parser = argparse.ArgumentParser() - parser.add_argument('--switch-mac', '-s') - parser.add_argument('--host-mac', ) - parser.add_argument('--ip-address', '-i') - parser.add_argument('--username', '-u') - parser.add_argument('--password', '-p') - parser.add_argument('--vlan', type=int) - parser.add_argument('--vlan_name') - parser.add_argument('--vlan_member') - parser.add_argument('--vlan_tagged') - parser.add_argument('--vlan_pvid') - parser.add_argument('--delete', action="store_true") - parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') - parser.add_argument('action', default=None, nargs='?') - args = parser.parse_args() - - logging.basicConfig(level=args.loglevel) - - net = Network(args.ip_address, args.host_mac, args.switch_mac) - actions = Protocol.tp_ids - - - if args.vlan_member or args.vlan_tagged or args.delete: - if (args.delete): - v = Protocol.set_vlan(int(args.vlan), 0, 0, "") - else: - v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) - net.login(args.username, args.password) - l = [(actions["vlan"], v)] - header, payload = net.set(args.username, args.password, l) - print(*payload, sep="\n") - - if args.vlan_pvid is not None: - net.login(args.username, args.password) - l = [] - for port in ports2list(args.vlan_pvid): - if port != 0: - l.append( (actions["pvid"], Protocol.set_pvid(args.vlan, port)) ) - header, payload = net.set(args.username, args.password, l) - print(*payload, sep="\n") - -if __name__ == "__main__": - main() diff --git a/smrt.py b/smrt.py index d525d98..6c41bf0 100755 --- a/smrt.py +++ b/smrt.py @@ -1,16 +1,16 @@ #!/usr/bin/env python -import socket -import time -import random -import logging -import argparse -import netifaces +import socket, time, random, argparse, logging from protocol import Protocol from network import Network +from binary import ports2byte, ports2list -from loglevel import loglevel +def loglevel(x): + try: + return getattr(logging, x.upper()) + except AttributeError: + raise argparse.ArgumentError('Select a proper loglevel') def main(): logger = logging.getLogger(__name__) @@ -20,6 +20,12 @@ def main(): parser.add_argument('--ip-address', '-i') parser.add_argument('--username', '-u') parser.add_argument('--password', '-p') + parser.add_argument('--vlan', type=int) + parser.add_argument('--vlan_name') + parser.add_argument('--vlan_member') + parser.add_argument('--vlan_tagged') + parser.add_argument('--vlan_pvid') + parser.add_argument('--delete', action="store_true") parser.add_argument('--loglevel', '-l', type=loglevel, default='INFO') parser.add_argument('action', default=None, nargs='?') args = parser.parse_args() @@ -28,13 +34,32 @@ def main(): net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - net.login(args.username, args.password) - if args.action in actions: - header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) - print(*payload, sep="\n") + if args.vlan: + if args.vlan_member or args.vlan_tagged or args.delete: + if (args.delete): + v = Protocol.set_vlan(int(args.vlan), 0, 0, "") + else: + v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) + net.login(args.username, args.password) + l = [(actions["vlan"], v)] + header, payload = net.set(args.username, args.password, l) + print(*payload, sep="\n") + + if args.vlan_pvid is not None: + net.login(args.username, args.password) + l = [] + for port in ports2list(args.vlan_pvid): + if port != 0: + l.append( (actions["pvid"], Protocol.set_pvid(args.vlan, port)) ) + header, payload = net.set(args.username, args.password, l) + print(*payload, sep="\n") else: - print("Actions:" , *actions.keys()) + if args.action in actions: + header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) + print(*payload, sep="\n") + else: + print("Actions:" , *actions.keys()) if __name__ == "__main__": main() From ffacefde23d46334a75929af50cc54c71c552bc1 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Thu, 12 Nov 2020 09:36:16 +0100 Subject: [PATCH 43/55] Minor corrections --- README.md | 2 +- binary.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ebbf806..e2b40cf 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ $ smrt stats ### smrt ports ``` -$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 ports +$ smrt ports (4096, 'ports', '01:01:00:01:06:00:00') (4096, 'ports', '02:01:00:01:00:00:00') (4096, 'ports', '03:01:00:01:00:00:00') diff --git a/binary.py b/binary.py index f8a5a73..14bf3b1 100644 --- a/binary.py +++ b/binary.py @@ -31,7 +31,6 @@ def byte2ports(byte): return SEP.join(out) if __name__ == '__main__': - #print(ports2byte("12345678")) - #a = ports2byte("1,2,5,6,8,12,15") - #print(a, byte2ports(a)) + a = ports2byte("1,2,5,6,8,12,15") + print(a, byte2ports(a)) print(ports2list("1,2")) From bbc9fe4d5e7fed5e788ea935435e04c2b3255079 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Thu, 12 Nov 2020 09:49:59 +0100 Subject: [PATCH 44/55] Simplify smrt logic --- smrt.py | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/smrt.py b/smrt.py index 6c41bf0..810bb6b 100755 --- a/smrt.py +++ b/smrt.py @@ -31,35 +31,30 @@ def main(): args = parser.parse_args() logging.basicConfig(level=args.loglevel) - net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - if args.vlan: - if args.vlan_member or args.vlan_tagged or args.delete: - if (args.delete): - v = Protocol.set_vlan(int(args.vlan), 0, 0, "") - else: - v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) - net.login(args.username, args.password) - l = [(actions["vlan"], v)] - header, payload = net.set(args.username, args.password, l) - print(*payload, sep="\n") - - if args.vlan_pvid is not None: - net.login(args.username, args.password) - l = [] - for port in ports2list(args.vlan_pvid): - if port != 0: - l.append( (actions["pvid"], Protocol.set_pvid(args.vlan, port)) ) - header, payload = net.set(args.username, args.password, l) - print(*payload, sep="\n") + if args.action not in Protocol.tp_ids and not args.vlan: + print("Actions:" , *actions.keys()) else: - if args.action in actions: + net.login(args.username, args.password) + if args.vlan: + if args.vlan_member or args.vlan_tagged or args.delete: + if (args.delete): + v = Protocol.set_vlan(int(args.vlan), 0, 0, "") + else: + v = Protocol.set_vlan(int(args.vlan), ports2byte(args.vlan_member), ports2byte(args.vlan_tagged), args.vlan_name) + l = [(actions["vlan"], v)] + + if args.vlan_pvid is not None: + l = [] + for port in ports2list(args.vlan_pvid): + if port != 0: + l.append( (actions["pvid"], Protocol.set_pvid(args.vlan, port)) ) + header, payload = net.set(args.username, args.password, l) + elif args.action in actions: header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) - print(*payload, sep="\n") - else: - print("Actions:" , *actions.keys()) + print(*payload, sep="\n") if __name__ == "__main__": main() From 4fe3c4468671864323a7a1137b463374e5f62d96 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 31 Jan 2021 23:14:36 +0100 Subject: [PATCH 45/55] Fix typo --- discovery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discovery.py b/discovery.py index 289a611..7a42238 100755 --- a/discovery.py +++ b/discovery.py @@ -30,7 +30,7 @@ def discover_switches(interface=None): addrs = netifaces.ifaddresses(interface) logger.debug("addrs:" + repr(addrs)) if netifaces.AF_INET not in addrs: - raise InterfaceProblem("not AF_INTER address") + raise InterfaceProblem("not AF_INET address") if netifaces.AF_LINK not in addrs: raise InterfaceProblem("not AF_LINK address") From e375b9fcb711a55c347b608bb4348501dcd128f0 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sun, 31 Jan 2021 23:32:26 +0100 Subject: [PATCH 46/55] Remove sep parameter in hex function Only supported for python >= 3.8 --- network.py | 6 +++--- protocol.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/network.py b/network.py index ca80b1b..4a219ab 100755 --- a/network.py +++ b/network.py @@ -43,7 +43,7 @@ def mac_to_bytes(mac): return bytes(int(byte, 16) for byte in mac.split(':')) def mac_to_str(mac): - return a.hex(":") + return a.hex() def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 @@ -52,7 +52,7 @@ def send(self, op_code, payload): 'op_code': op_code, }) packet = Protocol.assemble_packet(self.header, payload) - logger.debug('Sending Packet: ' + packet.hex(" ")) + logger.debug('Sending Packet: ' + packet.hex()) packet = Protocol.encode(packet) logger.debug('Sending Header: ' + str(self.header)) logger.debug('Sending Payload: ' + str(payload)) @@ -62,7 +62,7 @@ def receive(self): try: data, addr = self.rs.recvfrom(1500) data = Protocol.decode(data) - logger.debug('Receive Packet: ' + data.hex(" ")) + logger.debug('Receive Packet: ' + data.hex()) header, payload = Protocol.split(data) header, payload = Protocol.interpret_header(header), Protocol.interpret_payload(payload) logger.debug('Received Header: ' + str(header)) diff --git a/protocol.py b/protocol.py index 565f208..3fe0d94 100644 --- a/protocol.py +++ b/protocol.py @@ -137,7 +137,6 @@ def interpret_payload(payload): results.append( ( dtype, Protocol.ids_tp[dtype][1], - # data.hex(sep=" "), Protocol.interpret_value(data, Protocol.ids_tp[dtype][0]) ) ) @@ -164,7 +163,7 @@ def interpret_value(value, kind): elif kind == 'ip': value = ip_address(value) elif kind == 'hex': - value = value.hex(sep=":") + value = value.hex() elif kind == 'action': value = "n/a" elif kind == 'dec': From e545df10a0abf4c576b1aedf1b54a8d0faebf290 Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Sat, 17 Apr 2021 23:31:34 +0200 Subject: [PATCH 47/55] Fix mac translation --- binary.py | 9 +++++++++ network.py | 11 +++-------- protocol.py | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/binary.py b/binary.py index 14bf3b1..674f8f6 100644 --- a/binary.py +++ b/binary.py @@ -30,7 +30,16 @@ def byte2ports(byte): byte >>= 1 return SEP.join(out) +def mac_to_bytes(mac): + return bytes(int(byte, 16) for byte in mac.split(':')) + +def mac_to_str(mac): + return ':'.join(format(s, '02x') for s in mac) + + if __name__ == '__main__': a = ports2byte("1,2,5,6,8,12,15") print(a, byte2ports(a)) print(ports2list("1,2")) + m = mac_to_bytes("ba:ff:ee:ff:ac:ee") + print(mac_to_str(m)) diff --git a/network.py b/network.py index 4a219ab..68fd579 100755 --- a/network.py +++ b/network.py @@ -3,6 +3,7 @@ import socket, random, logging from protocol import Protocol +from binary import byte2ports,mac_to_str,mac_to_bytes logger = logging.getLogger(__name__) @@ -25,8 +26,8 @@ def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): self.header = Protocol.header["blank"].copy() self.header.update({ 'sequence_id': self.sequence_id, - 'host_mac': Network.mac_to_bytes(self.host_mac), - 'switch_mac': Network.mac_to_bytes(self.switch_mac), + 'host_mac': mac_to_bytes(self.host_mac), + 'switch_mac': mac_to_bytes(self.switch_mac), }) # Sending socket @@ -39,12 +40,6 @@ def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) self.rs.settimeout(0.5) - def mac_to_bytes(mac): - return bytes(int(byte, 16) for byte in mac.split(':')) - - def mac_to_str(mac): - return a.hex() - def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 self.header.update({ diff --git a/protocol.py b/protocol.py index 3fe0d94..21b8ed8 100644 --- a/protocol.py +++ b/protocol.py @@ -1,6 +1,6 @@ import struct from ipaddress import ip_address -from binary import byte2ports +from binary import byte2ports,mac_to_str class Protocol: PACKET_END = b'\xff\xff\x00\x00' @@ -163,7 +163,7 @@ def interpret_value(value, kind): elif kind == 'ip': value = ip_address(value) elif kind == 'hex': - value = value.hex() + value = mac_to_str(value) elif kind == 'action': value = "n/a" elif kind == 'dec': From 18294fc5deb41fd688b000193be03f39d6f979d1 Mon Sep 17 00:00:00 2001 From: Rui Lopes Date: Sun, 9 May 2021 06:54:08 +0100 Subject: [PATCH 48/55] wait a little longer for replies this is especially required when the switch has to actually apply changes (e.g. toggling between changing pvid with ./smrt.py ... pvid --vlan 4 --vlan_pvid 3,4 then --vlan 1) --- network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network.py b/network.py index 68fd579..b73b37f 100755 --- a/network.py +++ b/network.py @@ -38,7 +38,7 @@ def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): # Receiving socket self.rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) - self.rs.settimeout(0.5) + self.rs.settimeout(10) def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 From 6b980bd941773df9bdea544998f7bae6aa2b598e Mon Sep 17 00:00:00 2001 From: Rui Lopes Date: Tue, 11 May 2021 07:15:13 +0100 Subject: [PATCH 49/55] replace assert with non-debug AssertionError --- protocol.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/protocol.py b/protocol.py index 21b8ed8..bec6be7 100644 --- a/protocol.py +++ b/protocol.py @@ -120,8 +120,10 @@ def decode(data): encode = decode def split(data): - assert len(data) >= Protocol.header["len"] + len(Protocol.PACKET_END) - assert data.endswith(Protocol.PACKET_END) + if len(data) < Protocol.header["len"] + len(Protocol.PACKET_END): + raise AssertionError('invalid data length') + if not data.endswith(Protocol.PACKET_END): + raise AssertionError('data without packet end') return (data[0:Protocol.header["len"]], data[Protocol.header["len"]:]) def interpret_header(header): From 9790077ee4af846dc2dae33323bc529621151356 Mon Sep 17 00:00:00 2001 From: Nathan Gardiner Date: Tue, 1 Jun 2021 18:53:08 +1000 Subject: [PATCH 50/55] Add reboot command, make interface selection global rather than in discovery only, prefix vlan actions with vlan command for standardization, separate action commands from data, improve help output --- README.md | 20 ++++++++---------- discovery.py | 35 ++----------------------------- network.py | 42 +++++++++++++++++++++++++++++++++---- protocol.py | 59 +++++++++++++++++++++++++++------------------------- smrt.py | 37 +++++++++++++++++++++++++------- 5 files changed, 109 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index e2b40cf..a3c80e7 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ ba.ff.ee.ff.ac.ee (4, 'ip_addr', IPv4Address('192.168.9.202')) (5, 'ip_mask', IPv4Address('255.255.255.0')) (6, 'gateway', IPv4Address('192.168.9.1')) -(13, 'v4', True) +(13, 'auto_save', True) ---------------- (1, 'type', 'TL-SG108E') (2, 'hostname', 'sg108ev1') @@ -70,8 +70,8 @@ With switch `-c` or `--command`, output gives the syntax for using smrt with rig ``` $ ./discovery.py -i enp3s0 -c -./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 01:01:01:01:01:01 -./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 02:02:02:02:02:02 +./smrt.py --username admin --password admin -i enp3s0 --switch-mac 01:01:01:01:01:01 +./smrt.py --username admin --password admin -i enp3s0 --switch-mac 02:02:02:02:02:02 ``` ### Alias @@ -79,7 +79,7 @@ $ ./discovery.py -i enp3s0 -c After discovery, setting an shell alias reduces command size ``` -$ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F --ip-address 192.168.9.36 --host-mac=ba.ff.ee.ff.ac.ee --username admin --password admin' +$ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F -i eth0 --username admin --password admin' ``` ## smrt.py @@ -87,12 +87,10 @@ $ alias smrt='python ~/smrt/smrt.py --switch-mac 60:E3:27:83:25:3F --ip-address ### without command gives list of command ``` -$ ./smrt.py --username admin --password admin --host-mac=ba.ff.ee.ff.ac.ee --ip-address=192.168.9.36 --switch-mac 01:01:01:01:01:01 +$ ./smrt.py --username admin --password admin --interface==eth0 --switch-mac 01:01:01:01:01:01 Actions: type hostname mac ip_addr ip_mask gateway firmware hardware dhcp num_ports v4 username password save get_token_id igmp_snooping ports trunk mtu_vlan vlan_enabled vlan pvid vlan_filler qos1 qos2 mirror stats loop_prev ``` -Note: not all actions are of interest. Some TP-link code (13: "v4", 8707: "vlan_filler") correspond to unknown codes but are presents in output, so must be presents in code/command list. - ### vlan ``` @@ -170,7 +168,7 @@ $ smrt vlan (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) (8705, 'vlan', [100, '1,2,3', '1', 'vlan_test_1']) (8707, 'vlan_filler', ' ') -$ smrt --vlan 120 --vlan_name "vlan_test_2" --vlan_member 1,4,5,6 --vlan_tagged 1 +$ smrt vlan --vlan 120 --vlan_name "vlan_test_2" --vlan_member 1,4,5,6 --vlan_tagged 1 (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) @@ -194,7 +192,7 @@ $ smrt pvid (8706, 'pvid', (7, 90)) (8706, 'pvid', (8, 90)) (8707, 'vlan_filler', ' ') -$ smrt --vlan 120 --vlan_pvid 5,6 +$ smrt vlan --vlan 120 --vlan_pvid 5,6 (8706, 'pvid', (1, 90)) (8706, 'pvid', (2, 90)) (8706, 'pvid', (3, 90)) @@ -209,7 +207,7 @@ $ smrt --vlan 120 --vlan_pvid 5,6 ### Exemple 3 : remove vlan 120 ``` -$ smrt --vlan 120 --delete +$ smrt vlan --vlan 120 --delete (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) @@ -234,7 +232,7 @@ $ smrt pvid ### Exemple 4 : add new vlan and pvid ``` -$ smrt --vlan 130 --vlan_name "vlan_test_3" --vlan_member 1,4,5,6 --vlan_tagged 1 --vlan_pvid 4,5,6 +$ smrt vlan --vlan 130 --vlan_name "vlan_test_3" --vlan_member 1,4,5,6 --vlan_tagged 1 --vlan_pvid 4,5,6 (8704, 'vlan_enabled', '01') (8705, 'vlan', [1, '1,2,3,4,5,6,7,8', '', 'Default_VLAN']) (8705, 'vlan', [90, '1,2,3,4,5,6,7,8', '', 'LAN']) diff --git a/discovery.py b/discovery.py index 7a42238..914c155 100755 --- a/discovery.py +++ b/discovery.py @@ -3,46 +3,15 @@ import random import logging import argparse -import netifaces from protocol import Protocol -from network import Network, ConnectionProblem +from network import Network, ConnectionProblem, InterfaceProblem from loglevel import loglevel logger = logging.getLogger(__name__) -class InterfaceProblem(Exception): - pass - def discover_switches(interface=None): - if interface is None: - interfaces = netifaces.interfaces() - if "lo" in interfaces: - interfaces.remove("lo") - if len(interfaces) > 1: - msg = ["more than 1 interface. Use -i or --interface to specify the name"] - msg.append("Interfaces:") - for iface in interfaces: - msg.append(" " + repr(iface)) - raise InterfaceProblem("\n".join(msg)) - - settings = [] - addrs = netifaces.ifaddresses(interface) - logger.debug("addrs:" + repr(addrs)) - if netifaces.AF_INET not in addrs: - raise InterfaceProblem("not AF_INET address") - if netifaces.AF_LINK not in addrs: - raise InterfaceProblem("not AF_LINK address") - - mac = addrs[netifaces.AF_LINK][0]['addr'] - # take first address of interface - addr = addrs[netifaces.AF_INET][0] - if 'broadcast' not in addr or 'addr' not in addr: - raise InterfaceProblem("no addr or broadcast for address") - ip = addr['addr'] - - net = Network(ip, mac) - logger.debug((interface, ip, mac)) + net = Network(interface) net.send(Protocol.DISCOVERY, {}) ret = [] while True: diff --git a/network.py b/network.py index b73b37f..f3e8c10 100755 --- a/network.py +++ b/network.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import netifaces import socket, random, logging from protocol import Protocol @@ -10,16 +11,18 @@ class ConnectionProblem(Exception): pass +class InterfaceProblem(Exception): + pass + class Network: BROADCAST_ADDR = "255.255.255.255" UDP_SEND_TO_PORT = 29808 UDP_RECEIVE_FROM_PORT = 29809 - def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): + def __init__(self, interface=None, switch_mac="00:00:00:00:00:00"): self.switch_mac = switch_mac - self.host_mac = host_mac - self.ip_address = ip_address + self.ip_address, self.host_mac = self.get_interface(interface) self.sequence_id = random.randint(0, 1000) @@ -33,13 +36,44 @@ def __init__(self, ip_address, host_mac, switch_mac="00:00:00:00:00:00"): # Sending socket self.ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - self.ss.bind((ip_address, Network.UDP_RECEIVE_FROM_PORT)) + self.ss.bind((self.ip_address, Network.UDP_RECEIVE_FROM_PORT)) # Receiving socket self.rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) self.rs.settimeout(10) + def get_interface(self, interface=None): + if interface is None: + interfaces = netifaces.interfaces() + if "lo" in interfaces: + interfaces.remove("lo") + if len(interfaces) > 1: + msg = ["more than 1 interface. Use -i or --interface to specify the name"] + msg.append("Interfaces:") + for iface in interfaces: + msg.append(" " + repr(iface)) + raise InterfaceProblem("\n".join(msg)) + + settings = [] + addrs = netifaces.ifaddresses(interface) + logger.debug("addrs:" + repr(addrs)) + if netifaces.AF_INET not in addrs: + raise InterfaceProblem("not AF_INET address") + if netifaces.AF_LINK not in addrs: + raise InterfaceProblem("not AF_LINK address") + + mac = addrs[netifaces.AF_LINK][0]['addr'] + # take first address of interface + addr = addrs[netifaces.AF_INET][0] + if 'broadcast' not in addr or 'addr' not in addr: + raise InterfaceProblem("no addr or broadcast for address") + ip = addr['addr'] + + logger.debug("get_interface: %s %s %s " % (interface, ip, mac)) + + return ip, mac + def send(self, op_code, payload): self.sequence_id = (self.sequence_id + 1) % 1000 self.header.update({ diff --git a/protocol.py b/protocol.py index bec6be7..f7d72a6 100644 --- a/protocol.py +++ b/protocol.py @@ -66,34 +66,37 @@ class Protocol: } ids_tp = { - 1: ('str', 'type'), - 2: ('str', 'hostname'), - 3: ('hex', 'mac'), - 4: ('ip', 'ip_addr'), - 5: ('ip', 'ip_mask'), - 6: ('ip', 'gateway'), - 7: ('str', 'firmware'), - 8: ('str', 'hardware'), - 9: ('bool', 'dhcp'), - 10: ('dec', 'num_ports'), - 13: ('bool', 'v4'), - 512: ('str', 'username'), - 514: ('str', 'password'), - 2304: ('action','save'), - 2305: ('action','get_token_id'), - 4352: ('bool', 'igmp_snooping'), - 4096: ('hex', 'ports'), - 4608: ('hex', 'trunk'), - 8192: ('hex', 'mtu_vlan'), - 8704: ('hex', 'vlan_enabled'), - 8705: ('vlan', 'vlan'), - 8706: ('pvid', 'pvid'), - 8707: ('str', 'vlan_filler'), - 12288: ('bool', 'qos1'), - 12289: ('hex', 'qos2'), - 16640: ('hex', 'mirror'), - 16384: ('stat', 'stats'), - 17152: ('bool', 'loop_prev'), + 1: ('str', 'type', 0), + 2: ('str', 'hostname', 0), + 3: ('hex', 'mac', 0), + 4: ('ip', 'ip_addr', 0), + 5: ('ip', 'ip_mask', 0), + 6: ('ip', 'gateway', 0), + 7: ('str', 'firmware', 0), + 8: ('str', 'hardware', 0), + 9: ('bool', 'dhcp', 0), + 10: ('dec', 'num_ports', 0), + 12: ('bool', 'led_status', 0), + 13: ('bool', 'auto_save', 0), + 14: ('bool', 'is_factory', 0), + 512: ('str', 'username', 0), + 514: ('str', 'password', 0), + 773: ('bool', 'reboot', 1, "Reboot the device"), + 2304: ('action','save', 1, "Saves the current configuration"), + 2305: ('action','get_token_id',1, "Unknown"), + 4352: ('bool', 'igmp_snooping', 0), + 4096: ('hex', 'ports', 0), + 4608: ('hex', 'trunk', 0), + 8192: ('hex', 'mtu_vlan', 0), + 8704: ('hex', 'vlan_enabled', 0), + 8705: ('vlan', 'vlan', 1, "Configure VLAN Membership"), + 8706: ('pvid', 'pvid', 0), + 8707: ('str', 'vlan_filler', 0), + 12288: ('bool', 'qos1', 0), + 12289: ('hex', 'qos2', 0), + 16640: ('hex', 'mirror', 0), + 16384: ('stat', 'stats', 0), + 17152: ('bool', 'loop_prev', 0), } tp_ids = {v[1]: k for k, v in ids_tp.items()} diff --git a/smrt.py b/smrt.py index 810bb6b..bd08f9c 100755 --- a/smrt.py +++ b/smrt.py @@ -3,7 +3,7 @@ import socket, time, random, argparse, logging from protocol import Protocol -from network import Network +from network import Network, InterfaceProblem from binary import ports2byte, ports2list def loglevel(x): @@ -16,8 +16,7 @@ def main(): logger = logging.getLogger(__name__) parser = argparse.ArgumentParser() parser.add_argument('--switch-mac', '-s') - parser.add_argument('--host-mac', ) - parser.add_argument('--ip-address', '-i') + parser.add_argument('--interface', '-i') parser.add_argument('--username', '-u') parser.add_argument('--password', '-p') parser.add_argument('--vlan', type=int) @@ -31,14 +30,30 @@ def main(): args = parser.parse_args() logging.basicConfig(level=args.loglevel) - net = Network(args.ip_address, args.host_mac, args.switch_mac) actions = Protocol.tp_ids - if args.action not in Protocol.tp_ids and not args.vlan: - print("Actions:" , *actions.keys()) + if args.action not in Protocol.tp_ids: + print("Actions:\n") + for action in Protocol.ids_tp.keys(): + if Protocol.ids_tp[action][2]: + print("%s:%s %s [%s]" % + (Protocol.ids_tp[action][1], + tabout(Protocol.ids_tp[action][1]), + Protocol.ids_tp[action][3], + Protocol.ids_tp[action][0])) else: - net.login(args.username, args.password) - if args.vlan: + net = None + try: + net = Network(args.interface, args.switch_mac) + except InterfaceProblem as e: + print("Error:", e) + + if net: + net.login(args.username, args.password) + else: + exit(1) + + if args.action == "vlan" and args.vlan: if args.vlan_member or args.vlan_tagged or args.delete: if (args.delete): v = Protocol.set_vlan(int(args.vlan), 0, 0, "") @@ -56,5 +71,11 @@ def main(): header, payload = net.query(Protocol.GET, [(actions[args.action], b'')]) print(*payload, sep="\n") +def tabout(cmd): + # Format help output + if len(cmd) < 8: + return "\t\t" + return "\t" + if __name__ == "__main__": main() From 82c2d5c2b146309348714327bcba2c0a176a7cab Mon Sep 17 00:00:00 2001 From: Nathan Gardiner Date: Tue, 1 Jun 2021 20:00:49 +1000 Subject: [PATCH 51/55] Add flash_type, which was stopping discovery from working for TL-SG1016DE --- discovery.py | 2 +- protocol.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/discovery.py b/discovery.py index 914c155..6002b11 100755 --- a/discovery.py +++ b/discovery.py @@ -17,7 +17,7 @@ def discover_switches(interface=None): while True: try: header, payload = net.receive() - ret.append((ip, mac, header, payload)) + ret.append((net.ip_address, net.host_mac, header, payload)) except ConnectionProblem: break return ret diff --git a/protocol.py b/protocol.py index f7d72a6..720ee82 100644 --- a/protocol.py +++ b/protocol.py @@ -79,6 +79,7 @@ class Protocol: 12: ('bool', 'led_status', 0), 13: ('bool', 'auto_save', 0), 14: ('bool', 'is_factory', 0), + 15: ('hex', 'flash_type', 0), 512: ('str', 'username', 0), 514: ('str', 'password', 0), 773: ('bool', 'reboot', 1, "Reboot the device"), From 54dd5a6b77478be2f799a92fbfca2a9ac2290dad Mon Sep 17 00:00:00 2001 From: Nathan Gardiner Date: Tue, 1 Jun 2021 20:01:44 +1000 Subject: [PATCH 52/55] Update README to reflect tested versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3c80e7..800c9cb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Supposedly supported switches: * TL-SG105E (tested) * TL-SG108E (tested) * TL-SG108PE -* TL-SG1016DE +* TL-SG1016DE (tested) * TL-SG1024DE ## Discover switches From e73810f46ed66f8d350a03f41eb1575a81a0da06 Mon Sep 17 00:00:00 2001 From: Nathan Gardiner Date: Fri, 4 Jun 2021 23:52:09 +1000 Subject: [PATCH 53/55] Enable monitoring of TP-Link traffic across a network --- network.py | 51 +++++++++++++++++++++++++++++++++++++------- tp_analyse.py | 59 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/network.py b/network.py index f3e8c10..a2aa381 100755 --- a/network.py +++ b/network.py @@ -21,6 +21,12 @@ class Network: UDP_RECEIVE_FROM_PORT = 29809 def __init__(self, interface=None, switch_mac="00:00:00:00:00:00"): + + # Normally, this module will be initialized with the MAC address of the switch we want to talk to + # There are however two other modes that might be used: + # - Specify MAC address fe:ff:ff:ff:ff:ff to go into broadcast listen mode. We use this to snoop on bidirectional traffic + # - Specify MAC address ff:ff:ff:ff:ff:ff to go into "fake switch" mode, where we reply to other clients + self.switch_mac = switch_mac self.ip_address, self.host_mac = self.get_interface(interface) @@ -35,13 +41,28 @@ def __init__(self, interface=None, switch_mac="00:00:00:00:00:00"): # Sending socket self.ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - self.ss.bind((self.ip_address, Network.UDP_RECEIVE_FROM_PORT)) + + if switch_mac == "fe:ff:ff:ff:ff:ff" or switch_mac == "ff:ff:ff:ff:ff:ff": + self.ss.bind((Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) + self.ss.settimeout(10) + else: + self.ss.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.ss.bind((self.ip_address, Network.UDP_RECEIVE_FROM_PORT)) # Receiving socket self.rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) - self.rs.settimeout(10) + + if switch_mac == "ff:ff:ff:ff:ff:ff": + # This is a signal that we'll be operating in fake switch mode, rather than sending out commands + # For this, we need to switch the way that the recieving socket binds, to bind locally instead. + self.rs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.rs.bind((self.ip_address, Network.UDP_SEND_TO_PORT)) + + else: + + # Receiving socket + self.rs.bind((Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) + self.rs.settimeout(10) def get_interface(self, interface=None): if interface is None: @@ -85,11 +106,17 @@ def send(self, op_code, payload): packet = Protocol.encode(packet) logger.debug('Sending Header: ' + str(self.header)) logger.debug('Sending Payload: ' + str(payload)) - self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) + if self.switch_mac == "ff:ff:ff:ff:ff:ff": + self.rs.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_RECEIVE_FROM_PORT)) + else: + self.ss.sendto(packet, (Network.BROADCAST_ADDR, Network.UDP_SEND_TO_PORT)) + + def setHeader(self, header): + self.header = header def receive(self): - try: - data, addr = self.rs.recvfrom(1500) + data = self.recieve_socket(self.rs) + if data: data = Protocol.decode(data) logger.debug('Receive Packet: ' + data.hex()) header, payload = Protocol.split(data) @@ -98,9 +125,17 @@ def receive(self): logger.debug('Received Payload: ' + str(payload)) self.header['token_id'] = header['token_id'] return header, payload - except: + else: raise ConnectionProblem() + def recieve_socket(self, socket): + data = False + try: + data, addr = socket.recvfrom(1500) + except: + return False + return data + def query(self, op_code, payload): self.send(op_code, payload) header, payload = self.receive() diff --git a/tp_analyse.py b/tp_analyse.py index 263a714..28daec7 100755 --- a/tp_analyse.py +++ b/tp_analyse.py @@ -1,32 +1,47 @@ #!/usr/bin/env python +################################################################################################ +# Listen to bidirectional network communication (to and from TP-Link switches / easy switch tool +# Analyze the network communications + import socket, time, random, argparse, logging from protocol import Protocol +from network import Network -def main(): - with open("tp_analyse.txt") as f: - first = True - ret = [] - for l in f: - try: - adr = int(l[:4], 16) - except ValueError: - continue - if adr == 0: - if not first: - ret.append(data) - first = False - data = bytearray() - if adr % 16 == 0: - #print(adr, bytearray.fromhex(l[6:53].split(" "))) - data += bytearray.fromhex(l[6:53]) - ret.append(data) - - for s in ret: - data = Protocol.decode(s) - # print(data.hex(" ")) +def show_packet(payload): + print(payload) + data = Protocol.decode(payload) + print("TEST: " + data.hex(" ")) + try: print(Protocol.analyze(data)[1]) + except AssertionError: + return + except KeyError: + return + +def main(): + + # Handle command-line parameters + parser = argparse.ArgumentParser() + parser.add_argument('--interface', '-i') + args = parser.parse_args() + + # Instantiate network driver + net = None + try: + net = Network(args.interface, "fe:ff:ff:ff:ff:ff") + except InterfaceProblem as e: + print("Error:", e) + + while True: + data = net.recieve_socket(net.rs) + if data: + show_packet(data) + data = net.recieve_socket(net.ss) + if data: + show_packet(data) + time.sleep(0.1) if __name__ == '__main__': main() From 2941c6bd55be8393808f3a64b85117a3536c3843 Mon Sep 17 00:00:00 2001 From: Nathan Gardiner Date: Sat, 5 Jun 2021 00:01:39 +1000 Subject: [PATCH 54/55] Additional command --- protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol.py b/protocol.py index 720ee82..47c9f00 100644 --- a/protocol.py +++ b/protocol.py @@ -67,7 +67,7 @@ class Protocol: ids_tp = { 1: ('str', 'type', 0), - 2: ('str', 'hostname', 0), + 2: ('str', 'hostname', 1, "Without arguments, shows switch details"), 3: ('hex', 'mac', 0), 4: ('ip', 'ip_addr', 0), 5: ('ip', 'ip_mask', 0), From 51666a2cc92027a5495ffbb70bfc101d4768229a Mon Sep 17 00:00:00 2001 From: Philippe Chataignon Date: Wed, 28 Sep 2022 14:43:31 +0200 Subject: [PATCH 55/55] Fix typo recieve->receive --- network.py | 4 ++-- tp_analyse.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/network.py b/network.py index a2aa381..0bfbb20 100755 --- a/network.py +++ b/network.py @@ -115,7 +115,7 @@ def setHeader(self, header): self.header = header def receive(self): - data = self.recieve_socket(self.rs) + data = self.receive_socket(self.rs) if data: data = Protocol.decode(data) logger.debug('Receive Packet: ' + data.hex()) @@ -128,7 +128,7 @@ def receive(self): else: raise ConnectionProblem() - def recieve_socket(self, socket): + def receive_socket(self, socket): data = False try: data, addr = socket.recvfrom(1500) diff --git a/tp_analyse.py b/tp_analyse.py index 28daec7..2d09406 100755 --- a/tp_analyse.py +++ b/tp_analyse.py @@ -34,10 +34,10 @@ def main(): print("Error:", e) while True: - data = net.recieve_socket(net.rs) + data = net.receive_socket(net.rs) if data: show_packet(data) - data = net.recieve_socket(net.ss) + data = net.receive_socket(net.ss) if data: show_packet(data)