从Wireshark/tcpdump文件中提取rtsp over tcp的H264数据
对于从Wireshark抓取的rtsp/tcp数据文件,要想提取出里面的h264码流数据,貌似Wireshark并未提供相关功能选项。无赖之下只有自己动手写一个吧。
下面是在linux上用 tcpdump -i enp3s0 -c 7000 src 192.168.8.0 -w /home/guoke/test.cap 抓取的test.cap文件在Wireshark中的显示界面截图。
从test.cap文件中提取h264数据的关键是,需要像剥洋葱一样,读取各个网络协议头部。剥离完了后,剩下的就是需要的h264视频数据。大致步骤如下:
1. 读取 cap格式文件 的头部
typedef struct _TCPDUMP_CAP_FILE_HEADER_
{
int magic; //4bytes [D4 C3 B2 A1] = 0xA1B2C3D4
int version_major;//2bytes [02 00] = 2
int version_minor;//2bytes [04 00] = 4
int time_zone;//4bytes 时区
int timestamp_accuracy;//4bytes 时间戳精度
int max_capture_size_per_packet;//4bytes 每个包的抓包的最大值(单位:字节) [00 00 04 00] = 0x40000 = 262144;
int data_link_layer_type;//4bytes 数据链路层类型 [01 00 00 00] = 1 = LINKTYPE_ETHERNET; https://www.tcpdump.org/linktypes.html
}TCPDUMP_CAP_FILE_HEADER;
2. 读取 以太网帧(Ethernet Frame)(物理层的数据帧)的头部
typedef struct _ETHERNET_FRAME_
{
unsigned long long timestamp; //8bytes 帧时间戳
char timestampStr[80]; //帧时间戳 Arrival Time: Nov 1, 2019 15:35:59.430461000 中国标准时间
int frame_length; //4bytes 帧数据大小(不包含头部本身16字节,单位:字节)一般为 0x05EA = 1514 bytes
int capture_length; //4bytes 捕获的帧数据大小
}ETHERNET_FRAME;
3. 读取 数据链路层以太网帧 的头部(以太网协议版本II)
typedef struct _ETHERNET_II_HEADER_
{
char destination_address[50]; //6bytes 目的MAC:厂名_序号(网卡地址) Address: HuaweiTe_70:5c:3c (08:4f:0a:70:5c:3c)
char source_address[50]; //6bytes 源MAC:厂名_序号(网卡地址) Address: Hangzhou_68:5c:9c (48:7a:da:68:5c:9c)
int type; //2bytes 帧内封装的上层协议类型(IP=0x0800,ARP=0806,RARP=0835 [TCP-IP详解卷1:协议 图2-1 16页])Type: IPv4 (0x0800)
}ETHERNET_II_HEADER;
4. 读取 互联网层IP包 的头部 [TCP-IP详解卷1:协议 图3-1 24页]
注意:在此处将只挑选含有rtsp server ip的以太帧数据,其他的以太帧全被过滤掉(即不进行接下来的分析步骤)
typedef struct _INTERNET_PROTOCOL_V4_HEADER_
{
int version; //4bit 版本 Version: 4
int ip_header_length; //4bit IP包头部长度 Header Length: 20 bytes (5)
int differentiated_services_field; //8bit 差分服务字段 Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
int total_length; //16bit IP包的总长度 Total Length: 52
int identification; //16bit 标志字段 Identification: 0x0000 (0)
int flags_reserved_1bit; //1bit 标志字段 0 = Reserved bit: Not set
int flags_do_not_fragment_set_1bit; //1bit 标志字段 .1.. .... .... .... = Don't fragment: Set
int flags_more_fragments_1bit; //1bit 标志字段 ..0. .... .... .... = More fragments: Not set
int flags_fragments_offset_13bits; //13bit 标志字段 分段偏移量(将一个IP包分段后传输时,本段的标识)...0 0000 0000 0000 = Fragment offset: 0
int time_to_live; //8bit 生存期TTL Time to live: 62
int protocol; //8bit 此包内封装的上层协议 Protocol: TCP (6); UDP(17) https://tools.ietf.org/html/rfc1700 Page7
int header_checksum; //16bit 头部数据的校验和 Header checksum: 0x326e [validation disabled]
int source_ip_addr; //32bit 源IP地址 Source: 192.168.8.0
int destination_ip_addr; //32bit 目的IP地址 Destination: 192.168.8.1
}INTERNET_PROTOCOL_V4_HEADER;
5. 读取 传输层TCP数据段 的头部 [TCP-IP详解卷1:协议 图17-2 172页]
typedef struct _TCP_OPTIONS_
{
int kind; //8bits TCP选项的类型
int length; //8bits TCP选项的总长度(单位:byte,包含本字段长度)
char data[32]; //只有当length大于2时,此字段才有效
}TCP_OPTIONS;
typedef struct _TRANSMISSION_CONTROL_PROTOCOL_HEADER_
{
int source_port; //16bit 源端口号 Source Port: 554
int destination_port; //16bit 目的端口号 Destination Port: 55014
unsigned int sequence_number; //32bit *** Sequence number: 0 (relative sequence number)
unsigned int next_sequence_number; //32bit 下一个期望的*** = sequence_number + tcp_payload_length
unsigned int acknowledgment_number; //32bit 确认*** Acknowledgment number: 1 (relative ack number)
int tcp_header_length; //4bit 给出头部占32比特的数目。没有任何选项字段的TCP头部长度为20字节(5x32=160比特);最多可以有60字节的TCP头部。 1000 .... = Header Length: 32 bytes (8)
int flags_reserved_3bit; //3bit 保留字段 000. .... .... = Reserved: Not set
int flags_nonce_1bit; //1bit 保留字段 ....0 .... .... = Nonce: Not set
int flags_congestion_window_reduced_1bit; //1bit 拥塞窗口减少 ...... 0... .... = Congestion Window Reduced (CWR): Not set
int flags_ecn_echo_1bit; //1bit 显式拥塞通知Explicit Congestion Notification .... .0.. .... = ECN-Echo: Not set
int flags_urgent_1bit; //1bit 紧急指针URG( urgent pointer)有效 .... ..0. .... = Urgent: Not set
int flags_acknowledgment_1bit; //1bit 确认序号有效ACK .... ...1 .... = Acknowledgment: Set
int flags_push_1bit; //1bit 接收方应该尽快将这个报文段交给应用层PSH .... .... 0... = Push: Not set
int flags_reset_1bit; //1bit 重建连接RST .... .... .0.. = Reset: Not set
int flags_syn_1bit; //1bit 同步序号用来发起一个连接SYN .... .... ..1. = Syn: Set
int flags_fin_1bit; //1bit 发端完成发送任务FIN .... .... ...0 = Fin: Not set
int window_size; //16bit 流量控制的窗口大小 Window size value: 29200
int checksum; //16bit TCP数据段的校验和 Checksum: 0x7f9e [unverified]
int urgent_pointer; //16bit 紧急指针 Urgent pointer: 0
int tcp_options_size; //TCP可选项的数目,范围[1,40],等于0时tcp_options[40]字段无效
TCP_OPTIONS tcp_options[40]; //TCP可选项
unsigned char *tcp_payload; //TCP有效载荷
int tcp_payload_length; //注意:一个TCP包的有效载荷可能被RTP人为的分成两个部分,原因是:单个RTP包的大小超出了TCP的最大载荷容量(即1460bytes),这时候超出的部分只有放入下一个TCP包
}TRANSMISSION_CONTROL_PROTOCOL_HEADER;
6. 读取 TCP payload 有效载荷数据中的 RTSP 数据,如果tcp_payload的第一个字符是 '$'=0x24,则表示是RTP包数据。否则是 RTSP的请求信令字符串,比如 "OPTIONS rtsp://192.168.8.0:554/h264/ch1/main/av_stream RTSP/1.0"
typedef struct _RTSP_INTERLEAVED_FRAME_
{
int magic;//1byte 0x24 => '$'
int channel; //1byte 0-1
int rtp_length; //2bytes rtp包发送时单个包的载荷总大小(即不包含 magic、channel、rtp_length 这3个字段所占的4字节),但实际上可能超过TCP最大的1460字节
int rtp_real_read_bytes_in_single_tcp_packet; //在单个TCP包中实际上读到的字节数目,如果该值小于rtp_length,则剩下的字节需要在下一个TCP包中读取
unsigned char * interleaved_frame_data;
RTP_AND_RTCP_INFO rtp_and_rtcp;
}RTSP_INTERLEAVED_FRAME;
7. 读取 RTP数据包 的头部
typedef struct _RTP_HEADER_AND_PAYLOAD_
{
int rtp_packet_total_size; //该rtp包的总大小(单位:字节)
int channel; //rtsp通道,偶数为数据通道,比如0-表示视频通道,2-表示音频通道
int version;//2bits 用来标志使用的RTP版本 10.. .... = Version: RFC 1889 Version (2)
int padding;//1bit .如果为1,则该RTP包的尾部包含附加的填充字节 1. .... = Padding: True
int extension;//1bit 如果为1,RTP头部后面有一个扩展头部 ...0 .... = Extension: False
int contributing_source_identifiers_count;//4bits 头部后面跟着的CSRC的数目 .... 0000 = Contributing source identifiers count: 0
int marker;//1bit 标记位(1代表一帧数据的结束) 0... .... = Marker: False
int payload_type;//7bits RTP载荷类型 Payload type: DynamicRTP-Type-96 (96); 96是指h264编码
unsigned int sequence_number;//16bits *** Sequence number: 48657
unsigned int timestamp;//32bits 该RTP包中数据的第一个字节的采样时刻 Timestamp: 706810978
int synchronization_source_identifier;//32bits 同步源标识符(SSRC)指RTP包流的来源 Synchronization Source identifier: 0x45439e03 (1162059267)
int rtp_header_extension_defined_by_profile;//32bits
int rtp_header_extension_length;//32bits (长度不包含本身)
int rtp_payload_size;//RTP包有效载荷大小
unsigned char *rtp_payload;//RTP包有效载荷 Payload: 420101016000000300b0000003000003007ba003c08010e5...
int padding_data;//(padding_count - 8)bits 附加的填充字节 Padding data: 0000
int padding_count;//8bits 附加的填充字节数目(包含自身) Padding count: 3
H264_DATA h264_data; //h264数据
}RTP_HEADER_AND_PAYLOAD;
8. 读取 RTP payload载荷中的h264视频数据 的头部
typedef struct _H264_DATA_
{
char start_code[5]; // 00 00 00 01 67
int start_code_length; // 0 or 5,如果为5,则表示一个h264包的开始,为0表示中间部分或结束部分
int nal_unit_type_h264; //nal的类型(取值范围:1-23)
int nal_unit_type_h264_rtp; //nal的rtp类型(1-23时表示的意思和h264的nal_unit_type定义一致)
unsigned char *h264_sub_packet_data; //一个h264包可能被拆成多个RTP包碎片
int h264_sub_packet_data_length; //h264碎片大小
}H264_DATA;
注意:RTP对H264打包时,有几种打包方式,比如H264 I 帧数据太大了,则需要分成多个RTP包,或者H264 P帧数据不大,将多个P帧数据打包成单个RTP包
Type Packet Type name Section
---------------------------------------------------------
0 undefined -
1-23 NAL unit Single NAL unit packet per H.264 5.6
24 STAP-A Single-time aggregation packet 5.7.1
25 STAP-B Single-time aggregation packet 5.7.1
26 MTAP16 Multi-time aggregation packet 5.7.2
27 MTAP24 Multi-time aggregation packet 5.7.2
28 FU-A Fragmentation unit 5.8
29 FU-B Fragmentation unit 5.8
30-31 undefined - -
---------------------------------------------
工程代码的地址:https://github.com/jfu222/wireshark_rtsp_over_tcp