为了追查现网的声音卡顿和噪音问题,做了一些抓包。但是我们协议当时还没有采用WebSocket,而且采用了双工的HTTP,想从里面提取音频不太容易,而且更主要的是还需要和包时间进行分析,分析卡顿问题。我的设想是提取TCP包内容,解析HTTP协议并提取音频数据,根据包时间和间隔,填充需要的静音数据进去,最终保存一个音频文件。
音频部分目前是PCM格式,而且也是很规律标准的320字节,不需要编解码,直接写文件即可。比较麻烦的是抓包文件,可以人工处理的是进行流过滤,单个流保存一个文件,这样稍微简单点。而且在协议解析部分,之前在项目中已经实现过HTTP协议的解析,剩余的就是从抓包文件里面提取时间和TCP数据了。
PCAP文件可以用libpcap库进行解析,感觉其实也没这必要,根据pcap文件格式直接进行解析,并从中提取出帧头、IP头、TCP头和数据。
PCAP文件大致是由文件头和一帧帧数据包构成,文件头的C结构定义如下
typedef struct pcap_header
{
uchar8_t magic[4];
uint16_t version_major;
uint16_t version_minor;
int32_t thiszone; /*时区修正*/
uint32_t sigfigs; /*精确时间戳*/
uint32_t snaplen; /*抓包最大长度*/
uint32_t linktype; /*链路类型*/
} pcap_header_t;
其中,magic是文件起始,值依次为0xd40xc30xb20xa1,表明文件是PCAP文件,其他字段见说明。
提取了文件头之后,就可以循环提取数据包了,数据包头的C结构定义如下
typedef struct pcap_packet_header
{
uint32_t seconds; /*秒数*/
uint32_t u_seconds; /*毫秒数*/
uint32_t caplen; /*数据包长度*/
uint32_t len; /*文件数据包长度*/
} pcap_packet_header_t;
读取之后,后面len长度的数据就是包数据了,而且我们需要的时间也在这个头结构中。
数据和时间我们已经可以正常提取了,然后剩下的就是提取TCP数据,直接用帧报头结构、IP头结构、TCP头结构映射过去即可(C指针就是这么强大)。
typedef struct frame_header
{
uint8_t DstMAC[6]; //目的MAC地址
uint8_t SrcMAC[6]; //源MAC地址
u_short FrameType; //帧类型
} frame_header_t;
typedef struct ip_header
{
uint8_t Ver_HLen; //版本+报头长度
uint8_t TOS; //服务类型
uint16_t TotalLen; //总长度
uint16_t ID; //标识
uint16_t Flag_Segment; //标志+片偏移
uint8_t TTL; //生存周期
uint8_t Protocol; //协议类型
uint16_t Checksum; //头部校验和
uint32_t SrcIP; //源IP地址
uint32_t DstIP; //目的IP地址
} ip_header_t;
typedef struct tcp_header
{
uint16_t SrcPort; //源端口
uint16_t DstPort; //目的端口
uint32_t SeqNO; //序号
uint32_t AckNO; //确认号
uint8_t HeaderLen; //数据报头的长度(4 bit) + 保留(4 bit)
uint8_t Flags; //标识TCP不同的控制消息
uint16_t Window; //窗口大小
uint16_t Checksum; //校验和
uint16_t UrgentPointer; //紧急指针
}tcp_header_t;
这里面在实践中发现了2个问题。
帧头不对的问题是这样的,在正常抓包后,解析帧头是标准的定义如上,但是某些抓包却不是这样的,后来仔细对比发现不对的地方在wireshark里面显示的是linux cooked capture意思大概就是由于抓包采用了any,某些包没有报头,只能统一伪造包头了,这个需要注意下,伪造报头大概是16个字节,此次不再展示定义了,具体的相关知识可以百度。
TCP头长度不对其实是开始的理解不对,在TCP的定义里面有个头长度,根据这个头长度解析即可,因为TCP头可能还有其他数据,上面的定义只是最基础的结构,还要根据某些值来判断后续数据,因此根据TCP头里面的长度跳过头才是正确的做法。另外长度这个长度字段只有4bit,不是简单取值即可,这个真实长度是需要进行再计算获取的,大概这么计算,具体细节自行百度
uint8_t len = pData->HeaderLen;
len = (len >> 4 ) * 4;
注意这里面超过一个字节的数字需要大小端转换,网络包都是大端字节序。
剩下的就是HTTP解析和音频补偿和保存了,可以采用项目现有代码实现。
后面再回头看看,PCAP抓包文件格式还是挺简单的了,完全没有必要使用libpcap解析,而且自己解析还可以顺便了解下协议相关知识,也还是有好处的。