TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。大家最熟悉的一种通讯协议,TCP/IP协议组之所以流行,部分原因是因为它可以用在各种各样的信道和底层协议(例如T1和X.25、以太网以及RS-232串行接口)之上。确切地说,TCP/IP协议是一组包括TCP协议和IP协议,UDP(User Datagram Protocol)协议、ICMP(Internet Control Message Protocol)协议和其他一些协议的协议组。 另外许多通信协议都是基于TCP/IP协议,比如HTTP、SSL等。今天我们通过网络抓包来分析TCP/IP的包结构。
首先我们来看看TCP/IP的整体构架,大家知道OSI的七层参考模型,包括:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而而TCP/IP通讯协议采用了4层的层级结构:应用层、传输层、互联层和网络接口层。
数据封装:主机间发送数据到另一主机,数据首先必须打包,打包的过程为封装,封装就是在数据前加上特定的协议头部,在OSI参考模型中,对等层协议间交换数据的信息单元统称为PDU,为了提供服务,下层把上层的PDU作为本层的数据进行封装,然后加入本层的头部(和尾部),头部中含有完成数据传输所需的控制信息,这样,数据自上而下递交的过程实质上就是不断封装的过程,而到达目的地时自下而上的过程就是不断拆封的过程,由此可知在物理链路上传输的数据,实际上被包封了许多个“信封”,某一层只识别由对等层封装的信封。
以太网帧结构:
目的MAC地址(6字节) | 源MAC地址(6字节) | 上层协议(2字节) | 数据字段 | 检验(4字节) |
首部长度:占4位(bit),指IP报文头的长度。最大的长度(即4个bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。
服务类型:占8位(bit),用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。
总长度:16位(bit),指报文的总长度。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。
标识(identification):该字段标记当前分片为第几个分片,在数据报重组时很有用。
标志(flag):该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。
片偏移:指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。
生存时间:TTL(Time to Live)。该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。
协议:该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDP、TCP、ICMP、IGMP、IGP等。
首部校验和:用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。
源IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
目的IP地址:32位(bit),4个字节,每一个字节为0~255之间的整数,及我们日常见到的IP地址格式。
TCP包头结构:
报头字段名 | 位数 | 说明 |
源端口号 | 16 | 本地端口号 |
目的端口号 | 16 | 远程端口号 |
序号SEQ | 32 | 数据部分第一字节的序列号 |
确认号ACK | 32 | 本地希望接收的下一数据的序号 |
数据偏移值 | 4 | TCP段中数据起始位置 |
URG | 1 | 紧急数据指针的有效标志 |
ACK | 1 | 确认标志,指示ACK字段有效 |
PST | 1 | 复位连接标志,指示本段为复位段 |
RSH | 1 | PUSH操作标志 |
SYN | 1 | 建立同步连接 |
FIN | 1 | 本地数据发送结束终止连接标志 |
窗口大小 | 16 | 本地接受窗口(缓冲区)大小 |
校验和 | 16 | 包括TCP报头及数据在内的校验和 |
紧急数据指针 | 16 | 指示从发送数据序号开始到紧急数据之后的第一字节偏移 |
选项 | 可变 | 提供可选服务 |
填充 | 可变 | 保证TCP报头以32位为边界对齐 |
我们知道数据的整体结构后可通过下面程序进行验证,在这里首先我们建立一个Packet套接字,用来接收原始包,对于包含链路层报头的原始分组,socket_type 参数是 SOCK_RAW;对于去除了链路层报头的加工过的分组,socket_type 参数是 SOCK_DGRAM。链路层报头信息可在作为一般格式的 sockaddr_ll 中的中得到。socket 的 protocol 参数指的是 IEEE 802.3 的按网络层排序的协议号,在头文件中有所有被允许的协议的列表。当 protocol 被设置为 htons(ETH_P_ALL)时,可以接收所有的协议。到来的此种类型的分组在传送到在内核中实现的协议之前,要先传送给分组套接口。
代码如下:
- /*************************************
- * mysinff.c
- *************************************/
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "mysniff.h"
- #define BUFSIZE 2048
- static void hex_print(const u_char *buf, int len, int offset);
- static int echo_eth(const u_char *buf);
- static int echo_ip(const u_char *buf);
- static int echo_arp(const u_char *buf);
- static int echo_tcp(const u_char *buf);
- static int echo_udp(const u_char *buf);
- int main()
- {
- int listenfd;
- int n;
- char buf[BUFSIZE];
- /*建立PACKET套接字*/
- if ((listenfd = socket(PF_PACKET, SOCK_RAW,
- htons(ETH_P_ALL))) < 0) {
- perror("socket");
- exit(1);
- }
- for (;;) {
- if ((n = recv(listenfd, buf, BUFSIZE, 0)) > 0){
- hex_print(buf, n, 0);
- echo_eth(buf);
- }
- }
- return 0;
- }
- static int echo_eth(const u_char *buf)
- {
- struct ethhdr *eth = (struct ethhdr *) buf;
- printf("/n以太帧包头");
- printf("/n目的MAC地址: %02X:%02X:%02X:%02X:%02X:%02X",
- HWADDR(eth->h_dest));
- printf("/n源MAC地址:%02X: %02X:%02X:%02X:%02X:%02X",
- HWADDR(eth->h_source));
- printf("/n协议: %02X-%02X", PWORD(eth->h_proto));
- switch (ntohs(eth->h_proto)) {
- case ETH_P_IP:
- return echo_ip((u_char *)(eth+1));
- case ETH_P_ARP:
- return echo_arp((u_char *)(eth+1));
- default:
- return -1;
- }
- return 0;
- }
- static int echo_arp(const u_char *buf)
- {
- struct ether_arp *arph = (struct ether_arp *) buf;
- printf("/nARP地址解析协议包头");
- printf("/n硬件类型: %d", ntohs(arph->arp_hrd));
- printf("/n协议类型: %02X-%02X", PWORD(arph->arp_pro));
- printf("/n硬件地址长度: %d", arph->arp_hln);
- printf("/n协议地址长度: %d", arph->arp_pln);
- printf("/n操作码: %d", ntohs(arph->arp_op));
- printf("/n发送方硬件地址: %02X:%02X:%02X:%02X:%02X:%02X",
- HWADDR(arph->arp_sha));
- printf("/n发送方IP地址: %d.%d.%d.%d", NIPQUAD(arph->arp_spa));
- printf("/n目的方硬件地址: %02X:%02X:%02X:%02X:%02X:%02X",
- HWADDR(arph->arp_tha));
- printf("/n目的方IP地址: %d.%d.%d.%d", NIPQUAD(arph->arp_tpa));
- return 0;
- }
- static int echo_ip(const u_char *buf)
- {
- struct iphdr *iph = (struct iphdr *) buf;
- printf("/nIPv4包头");
- printf("/n版本: %d", iph->version);
- printf("/n包头长度: %d (%d bytes)", iph->ihl, iph->ihl*4);
- printf("/n服务类型(TOS):0x%02X", iph->tos);
- printf("/n总长度: %d", ntohs(iph->tot_len));
- printf("/n标识: %d", ntohs(iph->id));
- printf("/n标志: %02X %02X", PWORD(iph->frag_off));
- printf("/n生存时间(TTL): %d hops", iph->ttl);
- printf("/n协议: %d", iph->protocol);
- printf("/n校验和: 0x%02X%02X", PWORD(iph->check));
- printf("/n源IP地址: %d.%d.%d.%d", NIPQUAD(iph->saddr));
- printf("/n目的地址: %d.%d.%d.%d", NIPQUAD(iph->daddr));
- switch (iph->protocol) {
- case IPPROTO_TCP:
- return echo_tcp((u_char *)&iph[1]);
- case IPPROTO_UDP:
- return echo_udp((u_char *)&iph[1]);
- default:
- return -1;
- }
- return 0;
- }
- static int echo_tcp(const u_char *buf)
- {
- struct tcphdr *tcph = (struct tcphdr *) buf;
- printf("/nTCP包头");
- printf("/n源端口: %d", ntohs(tcph->source));
- printf("/n目的端口: %d", ntohs(tcph->dest));
- printf("/n序列号: %u", ntohl(tcph->seq));
- printf("/n确认号: %u", ntohl(tcph->ack_seq));
- printf("/n头部长度: %d (%d bytes)", tcph->doff, tcph->doff*4);
- printf("保留位/n: 0x%X", tcph->res1);
- printf("/n代码位: r:%d,u:%d,a:%d,p:%d,r:%d,s:%d,f:%d",
- tcph->res2, tcph->urg, tcph->ack, tcph->psh,
- tcph->rst, tcph->syn, tcph->fin);
- printf("/n接收窗口大小: %d", ntohs(tcph->window));
- printf("/n校验和: 0x%02X%02X", PWORD(tcph->check));
- printf("/n紧急数据指针: %d", ntohs(tcph->urg_ptr));
- return 0;
- }
- static int echo_udp(const u_char *buf)
- {
- struct udphdr *udph = (struct udphdr *) buf;
- printf("/nUDP包头");
- printf("/n源端口: %d", ntohs(udph->source));
- printf("/n目的端口: %d", ntohs(udph->dest));
- printf("/n数据长度: %d bytes", ntohs(udph->len));
- printf("/n校验位: 0x%X", udph->check);
- return 0;
- }
- static void hex_print(const u_char *buf, int len, int offset)
- {
- u_int i, j, jm;
- int c;
- printf("/n");
- for (i = 0; i < len; i += 0x10) {
- printf(" %04x: ", (u_int)(i + offset));
- jm = len - i;
- jm = jm > 16 ? 16 : jm;
- for (j = 0; j < jm; j++) {
- if ((j % 2) == 1)
- printf("%02x ", (u_int) buf[i+j]);
- else printf("%02x", (u_int) buf[i+j]);
- }
- for (; j < 16; j++) {
- if ((j % 2) == 1) printf(" ");
- else printf(" ");
- }
- printf(" ");
- for (j = 0; j < jm; j++) {
- c = buf[i+j];
- c = isprint(c) ? c : '.';
- printf("%c", c);
- }
- printf("/n");
- }
- }
用到的头文件mysniff.h
- /*************************************
- mysniff.h
- *************************************/
- ifndef _SNIFFER_H
- #define _SNIFFER_H
- /*
- * Display an MAC address in readable format.
- */
- #define HWADDR(addr) /
- ((unsigned char *)&addr)[0], /
- ((unsigned char *)&addr)[1], /
- ((unsigned char *)&addr)[2], /
- ((unsigned char *)&addr)[3], /
- ((unsigned char *)&addr)[4], /
- ((unsigned char *)&addr)[5]
- /*
- * Display an IP address in readable format.
- */
- #define NIPQUAD(addr) /
- ((unsigned char *)&addr)[0], /
- ((unsigned char *)&addr)[1], /
- ((unsigned char *)&addr)[2], /
- ((unsigned char *)&addr)[3]
- #define HIPQUAD(addr) /
- ((unsigned char *)&addr)[3], /
- ((unsigned char *)&addr)[2], /
- ((unsigned char *)&addr)[1], /
- ((unsigned char *)&addr)[0]
- #define PWORD(addr) /
- ((unsigned char *)&addr)[0], /
- ((unsigned char *)&addr)[1]
- #endif
程序在linux下编译,用root用户执行可看到如下结果:
以太帧包头
目的MAC地址: 00:0C:29:D2:F5:BB
源MAC地址:00: 50:56:C0:00:08
协议: 08-00
IPv4包头
版本: 4
包头长度: 5 (20 bytes)
服务类型(TOS):0x00
总长度: 40
标识: 12648
标志: 40 00
生存时间(TTL): 128 hops
协议: 6
校验和: 0x096E
源IP地址: 192.168.31.1
目的地址: 192.168.31.168
TCP包头
源端口: 2494
目的端口: 23
序列号: 3528579567
确认号: 2445250164
头部长度: 5 (20 bytes)保留位
: 0x0
代码位: r:0,u:0,a:1,p:0,r:0,s:0,f:0
接收窗口大小: 4704
校验和: 0x0730
紧急数据指针: 0
0000: 000c 29d2 f5bb 0050 56c0 0008 0800 4500 ..)....PV.....E.
0010: 0028 3169 4000 8006 096d c0a8 1f01 c0a8 .([email protected]......
0020: 1fa8 09be 0017 d251 d9ef 91bf 99dc 5010 .......Q......P.
0030: 06f8 0730 0000 0000 0000 0000 ...0........