From cc4a1f35f9b6841e27251f2a05ed4b8836296b54 Mon Sep 17 00:00:00 2001 From: Ryan Herbst Date: Thu, 3 Nov 2022 21:46:12 -0700 Subject: [PATCH 1/5] Added RSSI network dump analyzer --- scripts/rssi_network_analyzer.py | 208 +++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 scripts/rssi_network_analyzer.py diff --git a/scripts/rssi_network_analyzer.py b/scripts/rssi_network_analyzer.py new file mode 100644 index 0000000000..2a7cadc915 --- /dev/null +++ b/scripts/rssi_network_analyzer.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 + +import pyshark +import sys + + +class RssiFrame(object): + + def __init__(self, *, payload, server): + pdata = bytearray.fromhex(payload.replace(':',' ')) + + self.server = server + self.syn = (pdata[0] & 0x80) != 0 + self.ack = (pdata[0] & 0x40) != 0 + self.rst = (pdata[0] & 0x10) != 0 + self.nul = (pdata[0] & 0x08) != 0 + self.bsy = (pdata[0] & 0x01) != 0 + self.eack = (pdata[0] & 0x20) != 0 + self.seqNum = pdata[2] + self.ackNum = pdata[3] + self.good = True + + if pdata[1] != 8: + print(f"Got bad header length: {pdata[1]}") + self.good = False + + else: + csum = sum([int.from_bytes(bytearray(pdata[i:i+2]), byteorder='big') for i in range(0,6,2)]) + csum = int((csum % 0x10000) + (csum / 0x10000)) ^ 0xFFFF + + esum = int.from_bytes(bytearray(pdata[6:8]), byteorder='big') + + if esum != csum: + print(f"Got bad checksum. Exp={esum:#}, Got={csum:#}") + self.good = False + + self.length = len(pdata) - 8 + + def __str__(self): + ret = f"Server = {self.server}, " + ret += f"Syn = {self.syn}, " + ret += f"Ack = {self.ack}, " + ret += f"Rst = {self.rst}, " + ret += f"Nul = {self.rst}, " + ret += f"Bsy = {self.bsy}, " + ret += f"EAck = {self.eack}, " + ret += f"Length = {self.length}, " + ret += f"SeqNum = {self.seqNum}, " + ret += f"AckNum = {self.ackNum} " + return ret + + +class RssiLink(object): + + def __init__(self, *, server, port): + self.server = server + self.port = int(port) + + self._lastSeq = [None] * 2 + self._lastAck = [None] * 2 + + self._lastFrames = [] + self._postDump = 0 + + def rxPacket(self, *, packet, log=None, printAll=False): + + if 'ip' not in packet or 'udp' not in packet: + return + + src = packet.ip.src + dst = packet.ip.dst + srcport = int(packet.udp.srcport) + dstport = int(packet.udp.dstport) + + # Packet is from server + if src == self.server and srcport == self.port: + idx = 1 + + # Packet is to Server + elif dst == self.server and dstport == self.port: + idx = 0 + + # Not meant for us + else: + return + + frame = RssiFrame(payload=packet.udp.payload, server=idx) + + if not frame.good: + return + + # Keep a ring buffer of the last 20 frames incase an error is found + if len(self._lastFrames) == 20: + self._lastFrames = self._lastFrames[1:] + self._lastFrames.append(frame) + + # Lets wait until we get a few bi-directional frames + if self._lastAck[0] is not None and self._lastSeq[0] is not None and self._lastAck[1] is not None and self._lastSeq[1] is not None: + seqErr = False + ackErr = False + + nxtSeq = int((self._lastSeq[idx] + 1) & 0xFF) + + # Sequence Number Error + if (frame.length > 0 or frame.nul) and frame.seqNum != nxtSeq: + seqErr = True + + # Create window of acceptable ack numbers, this is between the last ack sent and the last sequence received + lastAck = self._lastAck[idx] + lastSeq = self._lastSeq[idx ^ 0x1] + + ackList = [lastAck] + + while lastAck != lastSeq: + lastAck = int((lastAck + 1) & 0xFF) + ackList.append(lastAck) + + # Ack number error + if frame.ackNum not in ackList: + ackErr = True + + if seqErr or ackErr or frame.syn or frame.rst or frame.eack: + msg = f"Error Unexepcted Frame. Server={self.server}, Port={self.port}. Dumping last frames.\n" + self._postDump = 20 + + for f in self._lastFrames: + msg += f" {f}\n" + + if seqErr: + msg += f" Got SeqNum={frame.seqNum}, Expected={nxtSeq}\n" + + if ackErr: + msg += f" Got AckNum={frame.ackNum}, Expected={ackList}\n" + + if frame.syn: + msg += " Got Syn\n" + + if frame.rst: + msg += " Got Rst\n" + + if frame.eack: + msg += " Got Eack\n" + + print(msg[:-1]) + + if log is not None: + log.write(msg) + log.flush() + + elif self._postDump > 0: + self._postDump -= 1 + msg = f" {frame}\n" + + print(msg[:-1]) + + if log is not None: + log.write(msg) + log.flush() + + elif printAll: + msg = f"Got Frame. Server={self.server}, Port={self.port}. {frame}\n" + print(msg[:-1]) + + if log is not None: + log.write(msg) + log.flush() + + if frame.length > 0 or frame.nul: + self._lastSeq[idx] = frame.seqNum + + if frame.ack: + self._lastAck[idx] = frame.ackNum + + def __str__(self): + return f"Server = {self.server}, Port = {self.port}" + + +def analyzeRssiDump(pcapFile, links): + + print(f"Opening {pcapFile}") + print(f"Logging to {pcapFile}.txt") + print("Monitoring Links:") + for link in links: + print(f" {link}") + + with open(pcapFile + '.txt', 'w') as f: + for c in pyshark.FileCapture(sys.argv[1]): + for link in links: + link.rxPacket(packet=c, log=f, printAll=False) + + +if __name__ == "__main__": + + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} file.pcap") + exit(1) + + links = [RssiLink(server='10.0.1.102', port=8193), + RssiLink(server='10.0.1.102', port=8194), + RssiLink(server='10.0.1.103', port=8193), + RssiLink(server='10.0.1.103', port=8194), + RssiLink(server='10.0.1.104', port=8193), + RssiLink(server='10.0.1.104', port=8194), + RssiLink(server='10.0.1.105', port=8193), + RssiLink(server='10.0.1.105', port=8194)] + + analyzeRssiDump(sys.argv[1], links) + From f7257ba9898378e15deb57a69225816bc6c404fe Mon Sep 17 00:00:00 2001 From: Ryan Herbst Date: Thu, 3 Nov 2022 22:09:06 -0700 Subject: [PATCH 2/5] Update --- scripts/rssi_network_analyzer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/rssi_network_analyzer.py b/scripts/rssi_network_analyzer.py index 2a7cadc915..dbe3a48e3e 100644 --- a/scripts/rssi_network_analyzer.py +++ b/scripts/rssi_network_analyzer.py @@ -1,4 +1,13 @@ #!/usr/bin/env python3 +#----------------------------------------------------------------------------- +# This file is part of 'SLAC Firmware Standard Library'. +# It is subject to the license terms in the LICENSE.txt file found in the +# top-level directory of this distribution and at: +# https://confluence.slac.stanford.edu/display/ppareg/LICENSE.html. +# No part of 'SLAC Firmware Standard Library', including this file, +# may be copied, modified, propagated, or distributed except according to +# the terms contained in the LICENSE.txt file. +#----------------------------------------------------------------------------- import pyshark import sys From bafc1a65ef5250f5a1687f143e26328c061a49b7 Mon Sep 17 00:00:00 2001 From: Ryan Herbst Date: Fri, 4 Nov 2022 23:16:29 -0700 Subject: [PATCH 3/5] Add link count dump --- scripts/rssi_network_analyzer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/rssi_network_analyzer.py b/scripts/rssi_network_analyzer.py index dbe3a48e3e..f5cb912bb0 100644 --- a/scripts/rssi_network_analyzer.py +++ b/scripts/rssi_network_analyzer.py @@ -70,6 +70,7 @@ def __init__(self, *, server, port): self._lastFrames = [] self._postDump = 0 + self._count = [0] * 2 def rxPacket(self, *, packet, log=None, printAll=False): @@ -98,6 +99,8 @@ def rxPacket(self, *, packet, log=None, printAll=False): if not frame.good: return + self._count[idx] += 1 + # Keep a ring buffer of the last 20 frames incase an error is found if len(self._lastFrames) == 20: self._lastFrames = self._lastFrames[1:] @@ -181,7 +184,7 @@ def rxPacket(self, *, packet, log=None, printAll=False): self._lastAck[idx] = frame.ackNum def __str__(self): - return f"Server = {self.server}, Port = {self.port}" + return f"Server = {self.server}, Port = {self.port}, Server Count = {self._count[1]}, Client Count = {self._count[0]}" def analyzeRssiDump(pcapFile, links): @@ -197,6 +200,10 @@ def analyzeRssiDump(pcapFile, links): for link in links: link.rxPacket(packet=c, log=f, printAll=False) + print("Link Counts:") + for link in links: + print(f" {link}") + if __name__ == "__main__": From 79c65157229a574903b6bfdd1ddce983a408bc43 Mon Sep 17 00:00:00 2001 From: Ryan Herbst Date: Mon, 28 Nov 2022 21:57:29 -0800 Subject: [PATCH 4/5] Add register decode --- scripts/rssi_network_analyzer.py | 95 ++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/scripts/rssi_network_analyzer.py b/scripts/rssi_network_analyzer.py index f5cb912bb0..827f66c807 100644 --- a/scripts/rssi_network_analyzer.py +++ b/scripts/rssi_network_analyzer.py @@ -15,10 +15,11 @@ class RssiFrame(object): - def __init__(self, *, payload, server): - pdata = bytearray.fromhex(payload.replace(':',' ')) + def __init__(self, *, packet, server): + pdata = bytearray.fromhex(packet.udp.payload.replace(':',' ')) self.server = server + self.time = packet.sniff_timestamp self.syn = (pdata[0] & 0x80) != 0 self.ack = (pdata[0] & 0x40) != 0 self.rst = (pdata[0] & 0x10) != 0 @@ -27,6 +28,33 @@ def __init__(self, *, payload, server): self.eack = (pdata[0] & 0x20) != 0 self.seqNum = pdata[2] self.ackNum = pdata[3] + self.src = packet.ip.src + self.dst = packet.ip.dst + self.srcPort = int(packet.udp.srcport) + self.dstPort = int(packet.udp.dstport) + + self.packVer = None + self.frame = None + self.pack = None + self.tdest = None + self.tid = None + self.user = None + self.id = None + self.addr = None + self.opcode = None + + if len(pdata) > 24: + self.packVer = pdata[8] & 0xF + self.frame = int.from_bytes(pdata[8:10],byteorder='little') >> 4 + self.pack = int.from_bytes(pdata[10:13],byteorder='little') + self.tdest = pdata[13] + self.tid = pdata[14] + self.user = pdata[15] + + self.id = int.from_bytes(pdata[16:20],byteorder='little') + self.addr = (int.from_bytes(pdata[20:24],byteorder='little') & 0x3FFFFFFF) << 2 + self.opcode = (pdata[23] >> 2) & 0x3 + self.good = True if pdata[1] != 8: @@ -34,28 +62,51 @@ def __init__(self, *, payload, server): self.good = False else: - csum = sum([int.from_bytes(bytearray(pdata[i:i+2]), byteorder='big') for i in range(0,6,2)]) + csum = sum([int.from_bytes(pdata[i:i+2], byteorder='big') for i in range(0,6,2)]) csum = int((csum % 0x10000) + (csum / 0x10000)) ^ 0xFFFF - esum = int.from_bytes(bytearray(pdata[6:8]), byteorder='big') + esum = int.from_bytes(pdata[6:8], byteorder='big') if esum != csum: print(f"Got bad checksum. Exp={esum:#}, Got={csum:#}") self.good = False - self.length = len(pdata) - 8 + # subtract udp header + ssi header + self.length = int(packet.udp.length) - 16 + + #print(packet.udp.payload) + #if self.srcPort == 8193 or self.dstPort == 8193: + #print(self) + def __str__(self): - ret = f"Server = {self.server}, " + ret = f"Time = {self.time}, " + ret += f"Src={self.src}:{self.srcPort}, " + ret += f"Dst={self.dst}:{self.dstPort}, " + ret += f"Server = {self.server}, " ret += f"Syn = {self.syn}, " ret += f"Ack = {self.ack}, " ret += f"Rst = {self.rst}, " - ret += f"Nul = {self.rst}, " + ret += f"Nul = {self.nul}, " ret += f"Bsy = {self.bsy}, " ret += f"EAck = {self.eack}, " ret += f"Length = {self.length}, " ret += f"SeqNum = {self.seqNum}, " - ret += f"AckNum = {self.ackNum} " + ret += f"AckNum = {self.ackNum}, " + + + if self.srcPort == 8193 or self.dstPort == 8193: + #ret += f"PackVer = {self.packVer}, " + #ret += f"Frame = {self.frame}, " + ret += f"Packet = {self.pack}, " + ret += f"TDest = {self.tdest}, " + #ret += f"TId = {self.tid}, " + + if self.pack == 0: + ret += f"ID = {self.id:#x}, " + ret += f"Addr = {self.addr:#x}, " + ret += f"Op = {self.opcode}" + return ret @@ -94,15 +145,15 @@ def rxPacket(self, *, packet, log=None, printAll=False): else: return - frame = RssiFrame(payload=packet.udp.payload, server=idx) + frame = RssiFrame(packet=packet, server=idx) if not frame.good: return self._count[idx] += 1 - # Keep a ring buffer of the last 20 frames incase an error is found - if len(self._lastFrames) == 20: + # Keep a ring buffer of the last 200 frames incase an error is found + if len(self._lastFrames) == 200: self._lastFrames = self._lastFrames[1:] self._lastFrames.append(frame) @@ -187,7 +238,7 @@ def __str__(self): return f"Server = {self.server}, Port = {self.port}, Server Count = {self._count[1]}, Client Count = {self._count[0]}" -def analyzeRssiDump(pcapFile, links): +def analyzeRssiDump(pcapFile, links, printAll): print(f"Opening {pcapFile}") print(f"Logging to {pcapFile}.txt") @@ -198,7 +249,7 @@ def analyzeRssiDump(pcapFile, links): with open(pcapFile + '.txt', 'w') as f: for c in pyshark.FileCapture(sys.argv[1]): for link in links: - link.rxPacket(packet=c, log=f, printAll=False) + link.rxPacket(packet=c, log=f, printAll=printAll) print("Link Counts:") for link in links: @@ -218,7 +269,19 @@ def analyzeRssiDump(pcapFile, links): RssiLink(server='10.0.1.104', port=8193), RssiLink(server='10.0.1.104', port=8194), RssiLink(server='10.0.1.105', port=8193), - RssiLink(server='10.0.1.105', port=8194)] - - analyzeRssiDump(sys.argv[1], links) + RssiLink(server='10.0.1.105', port=8194), + RssiLink(server='10.0.1.106', port=8193), + RssiLink(server='10.0.1.106', port=8194), + RssiLink(server='10.1.1.102', port=8193), + RssiLink(server='10.1.1.102', port=8194), + RssiLink(server='10.1.1.103', port=8193), + RssiLink(server='10.1.1.103', port=8194), + RssiLink(server='10.1.1.104', port=8193), + RssiLink(server='10.1.1.104', port=8194), + RssiLink(server='10.1.1.105', port=8193), + RssiLink(server='10.1.1.105', port=8194), + RssiLink(server='10.1.1.106', port=8193), + RssiLink(server='10.1.1.106', port=8194)] + + analyzeRssiDump(sys.argv[1], links, False) From 9bfc4c3bbe8c8cbf16115860d1b964cab120c2eb Mon Sep 17 00:00:00 2001 From: Ryan Herbst Date: Thu, 1 Dec 2022 12:25:50 -0800 Subject: [PATCH 5/5] Change to srpv3 --- scripts/rssi_network_analyzer.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/scripts/rssi_network_analyzer.py b/scripts/rssi_network_analyzer.py index 827f66c807..2a3bf93945 100644 --- a/scripts/rssi_network_analyzer.py +++ b/scripts/rssi_network_analyzer.py @@ -43,7 +43,7 @@ def __init__(self, *, packet, server): self.addr = None self.opcode = None - if len(pdata) > 24: + if len(pdata) > 28: self.packVer = pdata[8] & 0xF self.frame = int.from_bytes(pdata[8:10],byteorder='little') >> 4 self.pack = int.from_bytes(pdata[10:13],byteorder='little') @@ -51,9 +51,9 @@ def __init__(self, *, packet, server): self.tid = pdata[14] self.user = pdata[15] - self.id = int.from_bytes(pdata[16:20],byteorder='little') - self.addr = (int.from_bytes(pdata[20:24],byteorder='little') & 0x3FFFFFFF) << 2 - self.opcode = (pdata[23] >> 2) & 0x3 + self.opcode = pdata[17] & 0x3 + self.id = int.from_bytes(pdata[20:24],byteorder='little') + self.addr = int.from_bytes(pdata[24:28],byteorder='little') self.good = True @@ -94,15 +94,14 @@ def __str__(self): ret += f"SeqNum = {self.seqNum}, " ret += f"AckNum = {self.ackNum}, " - - if self.srcPort == 8193 or self.dstPort == 8193: - #ret += f"PackVer = {self.packVer}, " + if (self.srcPort == 8193 or self.dstPort == 8193) and self.tdest is not None: + ret += f"PackVer = {self.packVer}, " #ret += f"Frame = {self.frame}, " ret += f"Packet = {self.pack}, " - ret += f"TDest = {self.tdest}, " + ret += f"TDest = {self.tdest:#x}, " #ret += f"TId = {self.tid}, " - if self.pack == 0: + if self.pack == 0 and self.tdest == 0: ret += f"ID = {self.id:#x}, " ret += f"Addr = {self.addr:#x}, " ret += f"Op = {self.opcode}"