前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《嵌入式设备端的网络报文在wireshark显示》-- 原理篇

《嵌入式设备端的网络报文在wireshark显示》-- 原理篇

作者头像
Rice加饭
发布2022-05-10 17:46:12
7800
发布2022-05-10 17:46:12
举报
文章被收录于专栏:Rice嵌入式

背景:

  1. 最近在项目中遇到一个问题,追溯WIFI模块是否丢包的问题。因为丢包的环节很多。
  2. 我所有用到平台场景:主控(跑LWIP协议栈)+ SDIO wifi。
  • 在上面的场景中可能丢包的情况很多:
    1. wifi模块没有接收到网络报文(空中丢包)。
    2. wifi模块没有发送网络报文成功(空中丢包)。
    3. 主控与wifi数据传输丢掉报文(SDIO传输丢包)。
  1. 在设备端如果通过串口打印查看丢包现象是非常麻烦的,网络报文很多,而且无法辨别是否丢包。
  2. 通过wireshark抓网络包,虽然可以清晰查看报文,但是无法判别wifi有没有收到网络报文,以及无法知道SDIO是否丢包。
  3. 在第3点和第4点中都有缺点,串口可以知道设备端的报文,但无法辨别网络报文。wireshark可以清晰辨别报文,但是无法知道设备端的情况。如果将两者结合,就可以取长补短,便可以实现设备端网络报文清晰化。
  4. 上述的操作来源于我的上级--老吴,非常感谢。他把这个思想交给了我,我将这个方法通过python实现完成。
  5. 这个抓包工具命名为: SP2WS(serial packet to wireshark)通过串口抓取设备端网络报文,然后显示在wireshark中
    • github: https://github.com/RiceChen/SP2WS.git

serial packet to wireshark的实现过程:

  • SP2WS的原理很简单,通过python实现串口抓取设备端的网络报文,然后将网络报文封装成wireshark能识别的数据包,wireshark的命名管道实现捕获,便可以在wireshark查看设备端的报文。如果想进一步了解情况wireshark的命名管道,可以直接到官网查看详细的说明。
  • 官网链接:https://wiki.wireshark.org/CaptureSetup/Pipes
  • 描述实现原理之前,需要了解几个概念:
命名管道
  • 对于wireshark的命名管道的实现方法,官方提供了多种实现方法。而我们的SP2WS是采用python来实现的,我们参看如下的例子:
python环境
  • 该工具采用python3实现,所以PC端需要安装python3开发环境(记得是python3,python2需要自己修改)。
  • 因为SP2WS采用串口抓包,如果你使用我的工具,运行报如下错误:
代码语言:javascript
复制
Traceback (most recent call last):
  File ".\SP2WS.py", line 5, in <module>
    import serial
ModuleNotFoundError: No module named 'serial'
  • 解决:
代码语言:javascript
复制
pip insatll pyserial
代码语言:javascript
复制
Traceback (most recent call last):
  File ".\SP2WS.py", line 1, in <module>
    import win32pipe, win32file
ModuleNotFoundError: No module named 'win32pipe'
  • 解决:
代码语言:javascript
复制
pip install pypiwin32
wireshark的包格式
  1. wireshark有多种保存文件格式,而pacp是比较简单的格式。我的工具也是采用这个格式捕获。
  2. wireshark数据包格式如下,有一个文件头部,然后没有一个报文前面有一个报文头部。
  • pacp header格式:
代码语言:javascript
复制
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 */
}

字段

大小(Byte)

含义

magic_number

4

用来标示文件的开始(值为: 0xA1, 0xB2, 0xC3, 0xD4)

version_major

2

当前文件主要的版本号(值为:0x00, 0x02)

version_minor

2

当前文件次要的版本号(值为:0x00, 0x04)

thiszone

4

当地的标准时间(值为:0)

sigfigs

4

时间戳的精度(值为:0)

snaplen

4

捕获数据包的最大长度(值为:0)

linktype

4

链路类型(值为: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 the AF_value in network byte order) 113: special Linux "cooked" capture 114: LocalTalk

  • pcaprec header格式:
代码语言:javascript
复制
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 */
};

字段

大小(Byte)

含义

ts_sec

4

时间戳高位,精确到seconds(值是自从January 1, 1970 00:00:00 GMT以来的秒数来记)

ts_usec

4

时间戳低位,精确到microseconds(数据包被捕获时候的微秒(microseconds)数,是自ts-sec的偏移量)

incl_len

4

当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。

orig_len

4

网络中实际数据帧的长度,一般不大于caplen,多数情况下和Caplen数值相等。

  • packet:网络报文(工具中指的是串口抓到设备端的网络报文)
实现:
  1. 设备端实现:代码比较简单,只需要网络报文加上头部和尾部。其中入参:buf--设备端接收到的网络报文,len--设备端接收到网络报文长度。
代码语言:javascript
复制
#define PIPE_CAP_HDR_SIZE       3
#define PIPE_CAP_TAIL_SIZE      3

static 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;
  }
}
  1. SP2WS工具的实现:主要包含两个类,sp2ws_serial和sp2ws_pipe。
  • sp2ws_serial类:主要实现串口的打开,关闭,读,写
代码语言:javascript
复制
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类:主要实现pipe的创建,连接(连接成功,会往pipe发送pacp的头部),pipe数据传输(传输报文前加上报文头部)。
代码语言:javascript
复制
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)
        # 文件头部
        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 = 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)
  • SP2WS工具业务逻辑实现:
    1. 创建串口对象。
    2. 创建pipe对象。
    3. 连接pipe。
    4. 读取串口数据,抓取有效网络报文。
    5. 将网络报文发送到pipe中。
    6. 代码如下:
代码语言:javascript
复制
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]

    # 创建串口对象。
    sp2ws_serial = sp2ws_serial(port, 115200)
    sp2ws_serial.open()

    # 创建pipe对象
    sp2ws_pipe = sp2ws_pipe(pipe_name)
    sp2ws_pipe.create_pipe()
    print("start connect pipi...")
    # 连接pipe
    sp2ws_pipe.connect_pipe()
    print("pipe connect success!!!")

    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:
        # 读取串口数据
        raw = sp2ws_serial.read(1024)
        raw_len = len(raw)

        if raw_len > 0:
            raw_buf = raw_buf + raw

            # 抓取有效网络报文
            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]
                # 将网络报文发送到pipe中
                sp2ws_pipe.write_pipe(frame)

                end += 3
                raw_buf = raw_buf[end : ]
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景:
  • serial packet to wireshark的实现过程:
    • 实现:
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档