TCP Posix 网络抓包分析 示例

Android QNX内部Virtio net 抓包分析

工具:tcpdump + wireshark

背景:负责项目出现Android QNX内部网络丢包问题

解决思路:通过抓包进一步分析root cause,需要通过tcpdump抓两个系统的网络包,并通过wireshark分析

步骤

1. tcpdum抓包

通过ifconfig确定两个系统Virtio-Ethernet对应的网卡

得出Android的网卡是eth0, QNX的网卡是vp0

应用的服务端跑在QNX,端口号为49598,所以抓包命令为

QNX侧:
tcpdump -v -i vp0 port 49598 -c 400000 -w /blackbox/tcpdump49598.pcap

Android侧:
tcpdump -i eth0 -vvn -w tcpdump_android.pcap

后续通过tcpdump获得的pcap文件,导入wireshark继续分析

附上tcpdump常用参数:

1、指定网卡。
tcpdump -i eth0
2、指定ip
tcpdump -i eth0 ip host 172.168.1.1
3、指定端口
tcpdump -i eth0 port 80
4、指定协议
tcpdump -i eth0 udp/tcp
5、指定源和目的
tcpdump -i eth0 src/dst host 172.168.1.1
6、写入指定的文件中
tcpdump -i eth0 ip host 172.168.1.1 -w/opt/wenjian,cap
7、将抓包信息打印到前台。
tcpdump -A -i etho ip host 172.168.1.1 -vvn
8、写入1000个包到文件中
tcpdump -i eth0 ip host 172.168.1.1 -c 1000 -w /opt/wenjian,cap
9、环路抓包
tcpdump -i ol
# tcpdump -v -i vp0 port 49598 -c 400000 -w blackbox/tcpdump49598.pcap
tcpdump: listening on vp0, link-type EN10MB (Ethernet), capture size 262144 bytes
547 packets captured
9576 packets received by filter
0 packets dropped by kernel

# ls /blackbox/tcpdump49598.pcap  
/blackbox/tcpdump49598.pcap

2. 用wireshark分析pcap文件

用wireshark打开,大概是这样的界面,由于tcpdump已经过滤了port 49598的数据,所以打开不需要过滤就能看到192.068.1.1(server)和192.168.1.3(client)之间在49598上通信的报文

但是报文那么多,我们怎么知道哪条报文是我们需要找的?

为此我

1. 修改了servcer端的代码,减少了我不想抓的报文

2. 通过应用的log打印,获取发送到socket的大致时间


通过log我们能够定位发送报文的时间大概是Jan 05 01:58:34.054

3. 通过wireshar过滤掉192.168.1.3 -> 192.068.1.1的报文,只保留192.068.1.1 -> 192.168.1.3的报文

通过ip.src == 192.168.1.1只显示192.068.1.1 -> 192.168.1.3的报文

附上wireshark常用过滤参数:
注: 过滤表达式可通过&&, |, !, ^^等逻辑符号进行组合

1. 针对ip的过滤

对源地址进行过滤
ip.src == 192.168.0.1
对目的地址进行过滤
ip.dst == 192.168.0.1
对源地址或者目的地址进行过滤
ip.addr == 192.168.0.1
如果想排除以上的数据包,只需要将其用括号囊括,然后使用 "!" 即可
!(ip.addr == 192.168.0.1)

2. 针对协议的过滤
获某种协议的数据包,表达式很简单仅仅需要把协议的名字输入即可
http
注意:是否区分大小写?答:区分,只能为小写

捕获多种协议的数据包
http or telnet
排除某种协议的数据包
not arp   或者   !tcp

3. 针对端口的过滤(视传输协议而定)
捕获某一端口的数据包(以tcp协议为例)
tcp.port == 80
捕获多端口的数据包,可以使用and来连接,下面是捕获高于某端口的表达式(以udp协议为例)
udp.port >= 2048

4. 针对长度和内容的过滤
针对长度的过虑(这里的长度指定的是数据段的长度)
udp.length < 20   
http.content_length <=30
针对uri 内容的过滤
http.request.uri matches "user" (请求的uri中包含“user”关键字的)
注意:matches 后的关键字是不区分大小写的!

http.request.uri contains "User" (请求的uri中包含“user”关键字的)
注意:contains 后的关键字是区分大小写的!

5. 针对http请求的一些过滤实例。
过滤出请求地址中包含“user”的请求,不包括域名;
http.request.uri contains "User"
精确过滤域名
http.host==baidu.com
模糊过滤域名
http.host contains "baidu"
过滤请求的content_type类型
http.content_type =="text/html"
过滤http请求方法
http.request.method=="POST"
过滤tcp端口
tcp.port==80
http && tcp.port==80 or tcp.port==5566
过滤http响应状态码
http.response.code==302
过滤含有指定cookie的http数据包
http.cookie contains "userid"

4. 由于我只想知道业务报文,不关心握手报文,ACK报文以及心跳报文,所以我们可通过PSH找到有data的报文

在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.

通过找到PSH的字段,就是我们需要的业务报文

参考: https://zhuanlan.zhihu.com/p/439614017
附上TCP层,FLAGS字段含义:

SYN表示建立连接,

FIN表示关闭连接,

ACK表示响应,

PSH表示有 DATA数据传输,

RST表示连接重置。

其中,ACK是可能与SYN,FIN等同时使用的,比如SYN和ACK可能同时为1,它表示的就是建立连接之后的响应,

如果只是单个的一个SYN,它表示的只是建立连接。

TCP的几次握手就是通过这样的ACK表现出来的。

但SYN与FIN是不会同时为1的,因为前者表示的是建立连接,而后者表示的是断开连接。

RST一般是在FIN之后才会出现为1的情况,表示的是连接重置。

一般地,当出现FIN包或RST包时,我们便认为客户端与服务器端断开了连接;而当出现SYN和SYN+ACK包时,我们认为客户端与服务器建立了一个连接。

PSH为1的情况,一般只出现在 DATA内容不为0的包中,也就是说PSH为1表示的是有真正的TCP数据包内容被传递。

TCP的连接建立和连接关闭,都是通过请求-响应的模式完成的。

5. 通过代码定义的header找到精确的报文

由于我们的报文是通过flatbuffer序列化后的,且不是string类型,故wireshark只能看到序列化后的十六进制数据,看不到内容,所以需要想点办法通过报文中的data定位我们需要找的数据

分析发送代码:

 bool MessagingManager::sendMessage(nio::messaging::TxMessageIds messageId, unsigned int payloadLength, uint8_t *payload)
{
    std::lock_guard<std::mutex> lock(mSendMutex);   //Prevent out of order messages from being sent from multiple threads
    bool send_valid = false;

    uint8_t buf_header[8];
    uint32_t id = static_cast<uint32_t>(messageId);
    /* not good practice : network should use big-endian */
    buf_header[0] = id&0xFF;
    buf_header[1] = (id>>8)&0xFF;
    buf_header[2] = (id>>16)&0xFF;
    buf_header[3] = (id>>24)&0xFF;

    buf_header[4] = payloadLength&0xFF;
    buf_header[5] = (payloadLength>>8)&0xFF;
    buf_header[6] = (payloadLength>>16)&0xFF;
    buf_header[7] = (payloadLength>>24)&0xFF;

    for (int i=0; i<MAX_CONNECTIONS; i++)
    {
        if (mConnections[i].isValid())
        {
            struct iovec iov[2];
            iov[0].iov_base = (void *)buf_header;
            iov[0].iov_len  = sizeof(buf_header);
            iov[1].iov_base = payload;
            iov[1].iov_len = payloadLength;

            mTcpServer->SendV(mConnections[i].getSocket(), iov, 2);
            send_valid = true;
        }
    }

    if (false == send_valid)
    {
        NioLog::warning("MessagingManager: {}  Can't send MessageId {}", __FUNCTION__, messageId);
    }

    return send_valid;
}

bool CTCPServer::SendV(const Socket socket, struct iovec *iov, int iovcnt)
{
   int iResult = 0;

   iResult = writev(socket, iov, iovcnt);
   if (iResult < 0) 
   {
      NioLog::error("[TCPServer][Error] ret {} IOV writing to socket : {} ({})", iResult, errno, strerror(errno));
      return false;
   }
   return true;
}

可以看出,报文是由两部分组成,第一部分是buf_header,第二部分是序列化后的payload。

buf_header是由8 bytes组成(uint8_t buf_header[8]),messageId是一个五位数的十进制整数,最大不超过2^17。

buf_header[0]是messageId二进制的0-7位(0xFF即 1111 1111)。
buf_header[1]是messageId二进制的8-15位。
buf_header[2]是messageId二进制的16-23位。
buf_header[3]是messageId二进制的24-31位。

分析到这里就比较简单了,我查了我想抓的报文的messageId是57275
转换成十六进制是0xDFBB,二进制为1101 1111 1011 1011。
(注: 一位十六进制是2^4, 即四位二进制,故四位二进制正好表达一位十六进制)

故这个messageId总共只有16位二进制,故buf_header[0]为BB,buf_header[1]为DF,故buf_header[2]和buf_header[3]均为0.

我们只要找到报文的data开头是BB DF 00 00 的报文,即是我们需要的报文。
到这一步,就很轻易的找到了

如下图所示,即为我们需要找的报文


   转载规则


《TCP Posix 网络抓包分析 示例》 pglprome 采用 知识共享署名 4.0 国际许可协议 进行许可。
 本篇
TCP Posix 网络抓包分析 示例 TCP Posix 网络抓包分析 示例
Android QNX内部Virtio net 抓包分析工具:tcpdump + wireshark 背景:负责项目出现Android QNX内部网络丢包问题 解决思路:通过抓包进一步分析root cause,需要通过tcpdump抓两个系
2023-01-05
下一篇 
POSIX 定时器_QNX实现 POSIX 定时器_QNX实现
一、POSXI接口定时器_QNX实现设置定时器,并设置通知方式,内核以signal的方式通知到时 #include "Handler.hpp" #include <iostream> #include <memory> #inc
2022-12-21
  目录