Background:
- Lately encountered a problem in a project, tracing the issue of packet loss in the WIFI module. There are many stages where packet loss can occur.
- All platforms used in my scenario: Main control (running LWIP protocol stack) + SDIO wifi.
- In the above scenario, there are many cases where packet loss may occur:
- The wifi module did not receive network packets (packet loss in the air).
- The wifi module did not successfully send network packets (packet loss in the air).
- Data transmission between main control and wifi dropped packets (SDIO transmission packet loss).
- On the device side, viewing packet loss through serial printing is very cumbersome; there are many network packets, and it is difficult to identify whether packets have been lost.
- Capturing network packets via Wireshark can clearly view packets, but it cannot determine whether the wifi received the network packets nor if SDIO had packet loss.
- Both points 3 and 4 have drawbacks. Serial can know the device-end packets but cannot discern network packets. Wireshark can clearly identify packets but cannot know the device-side situation. If combined, each can complement the other and achieve the clarity of device-end network packets.
- The above operations come from my supervisorâOld Wu, many thanks. He gave me this idea, and I implemented this method through Python.
- This capture tool is named: SP2WS (serial packet to Wireshark) captures device-end network packets via the serial port, then displays them in Wireshark.
- GitHub: https://github.com/RiceChen/SP2WS.git
Implementation Process of serial packet to Wireshark:
- The principle of SP2WS is very simple, implemented by Python to capture device-end network packets through a serial port, then package the network packets into data packets recognizable by Wireshark, and use Wiresharkâs named pipe to capture, to allow checking device-end packets in Wireshark. For further understanding of Wiresharkâs named pipes, you can directly check detailed instructions on the official website.
- Official website link: https://wiki.wireshark.org/CaptureSetup/Pipes
- Before describing the implementation principle, several concepts need to be understood:
Named Pipe
- The official method for implementing named pipes for Wireshark offers multiple implementations. Our SP2WS is implemented using Python, and we refer to the following example:
Python Environment
- This tool is implemented using Python3, so the PC needs to have the Python3 development environment installed (remember, itâs Python3; for Python2, you need to modify it yourself).
- Since SP2WS uses serial capture, if you encounter the following error while running my tool:
- Â
Traceback (most recent call last): File ".\SP2WS.py", line 5, in import serialModuleNotFoundError: No module named 'serial'
- Solution:
pip install pyserial
- Â
Traceback (most recent call last): File ".\SP2WS.py", line 1, in import win32pipe, win32fileModuleNotFoundError: No module named 'win32pipe'
- Solution:
pip install pypiwin32
Wireshark Packet Format
- Wireshark has multiple file saving formats, and pacp is a relatively simple format. My tool also uses this format for capture.
- The Wireshark packet format includes a file header, followed by a packet header in front of each packet.
- pacp header format:
struct pcap_file_header{ uint32_t magic_number; /* magic number */ uint16_t version_major; /* major version number */ uint16_t version_minor; /* minor version number */ int32_t thiszone; /* GMT to local correction */ uint32_t sigfigs; /* accuracy of timestamps */ uint32_t snaplen; /* max length of captured packets, in octets */ uint32_t linktype; /* data link type */}
Field |
Size(Byte) |
Description |
---|---|---|
magic_number |
4 |
Indicates the start of the file (value: 0xA1, 0xB2, 0xC3, 0xD4) |
version_major |
2 |
Main version number of the current file (value: 0x00, 0x02) |
version_minor |
2 |
Minor version number of the current file (value: 0x00, 0x04) |
thiszone |
4 |
Standard local time (value: 0) |
sigfigs |
4 |
Accuracy of timestamps (value: 0) |
snaplen |
4 |
Max length of captured packets (value: 0) |
linktype |
4 |
Link type (value: 1) 0: BSD loopback devices, except for later OpenBSD 1: Ethernet, and Linux loopback devices 6: 802.5 Token Ring 7: ARCnet 8: SLIP 9: PPP 10: FDDI 100: LLC/SNAP-encapsulated ATM 101: âRaw IPâ, with no link 102: BSD/OS SLIP 103: BSD/OS PPP 104: Cisco HDLC 105: 802.11 108: later OpenBSD loopback devices (with AF_value in network byte order) 113: Special Linux âcookedâ capture 114: LocalTalk |
- pcaprec header format:
struct pcaprec_hdr { uint32_t ts_sec; /* timestamp seconds */ uint32_t ts_usec; /* timestamp microseconds (nsecs for PCAP_NSEC_MAGIC) */ uint32_t incl_len; /* number of octets of packet saved in file*/ uint32_t orig_len; /* actual length of packet */};
Field |
Size(Byte) |
Description |
---|---|---|
ts_sec |
4 |
High part of the timestamp, accurate to seconds (value is counted since January 1, 1970 00:00:00 GMT) |
ts_usec |
4 |
Low part of the timestamp, accurate to microseconds (the number of microseconds when the packet was captured, offset from ts_sec) |
incl_len |
4 |
Length of the current data block, i.e., the length of the data frame captured, from which the position of the next data frame can be obtained. |
orig_len |
4 |
Actual length of the data frame in the network, generally not greater than caplen, mostly equal to Caplen. |
- packet: Network packet (referred to in the tool as the network packet captured on the device side through the serial port)
Implementation:
- Device-side implementation: The code is relatively simple, only needing to add a header and footer to the network packet. Where parameters: bufâthe network packet received on the device side, lenâthe length of the network packet received on the device side.
#define PIPE_CAP_HDR_SIZE 3#define PIPE_CAP_TAIL_SIZE 3static void pipe_cap_dump(char *buf, int len){ char pipe_hdr[PIPE_CAP_HDR_SIZE] = {114, 116, 116}; char pipe_end[PIPE_CAP_TAIL_SIZE] = {101, 110, 100}; char *pipe_data = (char *)malloc(len + PIPE_CAP_HDR_SIZE + PIPE_CAP_TAIL_SIZE); if(pipe_data != NULL) { memcpy(pipe_data, pipe_hdr, PIPE_CAP_HDR_SIZE); memcpy(&pipe_data[PIPE_CAP_HDR_SIZE], buf, len); memcpy(&pipe_data[PIPE_CAP_HDR_SIZE + len], pipe_end, PIPE_CAP_TAIL_SIZE); uart_send(pipe_data, len + PIPE_CAP_HDR_SIZE + PIPE_CAP_TAIL_SIZE); free(pipe_data); pipe_data = NULL; }}
- Implementation of the SP2WS tool: Mainly consists of two classes, sp2ws_serial and sp2ws_pipe.
- sp2ws_serial class: Primarily handles the opening, closing, reading, and writing of the serial port.
class sp2ws_serial(): def __init__(self, port, baudrate): self.port = port self.baudrate = baudrate def open(self): self.ser = serial.Serial(self.port, self.baudrate, timeout = 0.5) def close(self): self.ser.close() def read(self, length): return self.ser.read(length) def write(self, buff): self.ser.write(buff)
- sp2ws_pipe class: Primarily handles the creation and connection of pipes (on successful connection, it sends the pacp header to the pipe), and pipe data transmission (adds a packet header before transmitting packets).
class sp2ws_pipe(): def __init__(self, pipe_name): self.magic_num = 0xa1b2c3d4 self.major_ver = 0x02 self.minor_ver = 0x04 self.link_type = 1 self.pipe_name = pipe_name def create_pipe(self): self.pipe = win32pipe.CreateNamedPipe( self.pipe_name, win32pipe.PIPE_ACCESS_OUTBOUND, win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_WAIT, 1, 65536, 65536, 300, None) def connect_pipe(self): win32pipe.ConnectNamedPipe(self.pipe, None) # File header global_header = struct.pack('IHHiIII', self.magic_num, # magic number 4 self.major_ver, # major version number 2 self.minor_ver, # minor version number 2 0, # GMT to local correction 4 0, # accuracy of timestamps 4 0, # max length of captured packets, in octets 4 self.link_type # data link type 4 ) win32file.WriteFile(self.pipe, global_header) def write_pipe(self, packet): packet_len = len(packet) if packet_len <= 0: return # Packet header packet_header = struct.pack('IIII', int(time.time()), # timestamp seconds datetime.datetime.now().microsecond, # timestamp microseconds packet_len, # number of octets of packet packet_len # actual length of packet ) win32file.WriteFile(self.pipe, packet_header) win32file.WriteFile(self.pipe, packet)
- Business logic implementation of the SP2WS tool:
- Create a serial port object.
- Create a pipe object.
- Connect the pipe.
- Read serial port data, capture effective network packets.
- Send the network packets to the pipe.
- The code is as follows:
if __name__ == "__main__": if len(sys.argv) < 3: print("SP2WS.py com[port_num] wifi_master [ssid] [pwd]") print("SP2WS.py com[port_num] wifi_slave") print(r'\\.\pipe\wifi_master') print(r'\\.\pipe\wifi_slave') port = sys.argv[1].upper() if sys.argv[2].find("wifi_master") > -1: ssid = sys.argv[3] password = sys.argv[4] wifi_connect = r'wifi join ' + ssid + ' ' + password pipe_name = r'\\.\pipe' + '\\' + sys.argv[2] # Create a serial port object. sp2ws_serial = sp2ws_serial(port, 115200) sp2ws_serial.open() # Create a pipe object sp2ws_pipe = sp2ws_pipe(pipe_name) sp2ws_pipe.create_pipe() print("Start connecting pipe...") # Connect the pipe sp2ws_pipe.connect_pipe() print("Pipe connection successful!!!") raw_buf = bytearray() start = 0 init = 0 if sys.argv[2].find("wifi_master") > -1: sp2ws_serial.write(bytes(wifi_connect + "\r\n", encoding='utf-8')) sp2ws_serial.write(bytes("pipe_start\r\n", encoding='utf-8')) while True: # Read serial port data raw = sp2ws_serial.read(1024) raw_len = len(raw) if raw_len > 0: raw_buf = raw_buf + raw # Capture effective network packets while True: raw_len = len(raw_buf) # Find packet header for index in range(raw_len): if (index + 2) < (raw_len - 1): if raw_buf[index] == 114 and raw_buf[index + 1] == 116 and raw_buf[index + 2] == 116: start = index + 3 break start = 0 else: start = 0 break if start == 0: break # Find packet tail for index in range(start, raw_len): if (index + 2) < (raw_len - 1): if raw_buf[index] == 101 and raw_buf[index + 1] == 110 and raw_buf[index + 2] == 100: end = index break end = 0 else: end = 0 break if end == 0: break frame = raw_buf[start : end] # Send the network packets to the pipe sp2ws_pipe.write_pipe(frame) end += 3 raw_buf = raw_buf[end : ]