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报文头的长度。最大的长度(即4bit都为1时)为15个长度单位,每个长度单位为4字节(TCP/IP标准,DoubleWord),所以IP协议报文头的最大长度为60个字节,最短为上图所示的20个字节。
服务类型:占8位(bit),用来获得更好的服务。其中的前3位表示报文的优先级,后面的几位分别表示要求更低时延、更高的吞吐量、更高的可靠性、更低的路由代价等。对应位为1即有相应要求,为0则不要求。
总长度16位(bit),指报文的总长度。注意这里的单位为字节,而不是4字节,所以一个IP报文的的最大长度为65535个字节。
标识identification):该字段标记当前分片为第几个分片,在数据报重组时很有用。
标志flag):该字段用于标记该报文是否为分片(有一些可能不需要分片,或不希望分片),后面是否还有分片(是否是最后一个分片)。
片偏移:指当前分片在原数据报(分片前的数据报)中相对于用户数据字段的偏移量,即在原数据报中的相对位置。
生存时间TTLTime to Live)。该字段表明当前报文还能生存多久。每经过1ms或者一个网关,TTL的值自动减1,当生存时间为0时,报文将被认为目的主机不可到达而丢弃。使用过Ping命令的用户应该有印象,在windows中输入ping命令,在返回的结果中即有TTL的数值。
协议:该字段指出在上层(网络7层结构或TCP/IP的传输层)使用的协议,可能的协议有UDPTCPICMPIGMPIGP等。
首部校验和:用于检验IP报文头部在传播的过程中是否出错,主要校验报文头中是否有某一个或几个bit被污染或修改了。
IP地址32位(bit),4个字节,每一个字节为0255之间的整数,及我们日常见到的IP地址格式。
目的IP地址32位(bit),4个字节,每一个字节为0255之间的整数,及我们日常见到的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)时,可以接收所有的协议。到来的此种类型的分组在传送到在内核中实现的协议之前,要先传送给分组套接口。

代码如下:

 

  1. /*************************************
  2.  * mysinff.c
  3. *************************************/
  4.  
  5. #include 
  6. #include 
  7. #include 
  8. #include 
  9. #include 
  10. #include 
  11. #include 
  12. #include 
  13. #include 
  14. #include 
  15. #include 
  16. #include 
  17. #include 
  18. #include 
  19.  
  20. #include "mysniff.h"
  21.  
  22. #define BUFSIZE 2048
  23.  
  24. static void hex_print(const u_char *buf, int len, int offset);
  25. static int echo_eth(const u_char *buf);
  26. static int echo_ip(const u_char *buf);
  27. static int echo_arp(const u_char *buf);
  28. static int echo_tcp(const u_char *buf);
  29. static int echo_udp(const u_char *buf);
  30.  
  31. int main()
  32. {
  33.     int listenfd;
  34.     int n;
  35.     char buf[BUFSIZE];
  36.  
  37.     /*建立PACKET套接字*/
  38.     if ((listenfd = socket(PF_PACKET, SOCK_RAW,
  39.           htons(ETH_P_ALL))) < 0) {
  40.  
  41.           perror("socket");
  42.           exit(1);
  43.     }
  44.  
  45.     for (;;) {
  46.           if ((n = recv(listenfd, buf, BUFSIZE, 0)) > 0){
  47.                 hex_print(buf, n, 0);
  48.                 echo_eth(buf);
  49.           }
  50.     }
  51.     return 0;
  52. }
  53.  
  54. static int echo_eth(const u_char *buf)
  55. {
  56.     struct ethhdr *eth = (struct ethhdr *) buf;
  57.  
  58.     printf("/n以太帧包头");
  59.     printf("/n目的MAC地址: %02X:%02X:%02X:%02X:%02X:%02X",
  60.           HWADDR(eth->h_dest));
  61.     printf("/n源MAC地址:%02X: %02X:%02X:%02X:%02X:%02X",
  62.           HWADDR(eth->h_source));
  63.     printf("/n协议: %02X-%02X", PWORD(eth->h_proto));
  64.  
  65.     switch (ntohs(eth->h_proto)) {
  66.     case ETH_P_IP:
  67.           return echo_ip((u_char *)(eth+1));
  68.     case ETH_P_ARP:
  69.           return echo_arp((u_char *)(eth+1));
  70.     default:
  71.           return -1;
  72.     }
  73.     return 0;
  74. }
  75.  
  76. static int echo_arp(const u_char *buf)
  77. {
  78.     struct ether_arp *arph = (struct ether_arp *) buf;
  79.  
  80.     printf("/nARP地址解析协议包头");
  81.     printf("/n硬件类型: %d", ntohs(arph->arp_hrd));
  82.     printf("/n协议类型: %02X-%02X", PWORD(arph->arp_pro));
  83.     printf("/n硬件地址长度: %d", arph->arp_hln);
  84.     printf("/n协议地址长度: %d", arph->arp_pln);
  85.     printf("/n操作码: %d", ntohs(arph->arp_op));
  86.     printf("/n发送方硬件地址: %02X:%02X:%02X:%02X:%02X:%02X",
  87.           HWADDR(arph->arp_sha));
  88.     printf("/n发送方IP地址: %d.%d.%d.%d", NIPQUAD(arph->arp_spa));
  89.     printf("/n目的方硬件地址: %02X:%02X:%02X:%02X:%02X:%02X",
  90.           HWADDR(arph->arp_tha));
  91.     printf("/n目的方IP地址: %d.%d.%d.%d", NIPQUAD(arph->arp_tpa));
  92.  
  93.     return 0;
  94. }
  95.  
  96. static int echo_ip(const u_char *buf)
  97. {
  98.     struct iphdr *iph = (struct iphdr *) buf;
  99.  
  100.     printf("/nIPv4包头");
  101.     printf("/n版本: %d", iph->version);
  102.     printf("/n包头长度: %d (%d bytes)", iph->ihl, iph->ihl*4);
  103.     printf("/n服务类型(TOS):0x%02X", iph->tos);
  104.     printf("/n总长度: %d", ntohs(iph->tot_len));
  105.     printf("/n标识: %d", ntohs(iph->id));
  106.     printf("/n标志: %02X %02X", PWORD(iph->frag_off));
  107.     printf("/n生存时间(TTL): %d hops", iph->ttl);
  108.     printf("/n协议: %d", iph->protocol);
  109.     printf("/n校验和: 0x%02X%02X", PWORD(iph->check));
  110.     printf("/n源IP地址: %d.%d.%d.%d", NIPQUAD(iph->saddr));
  111.     printf("/n目的地址: %d.%d.%d.%d", NIPQUAD(iph->daddr));
  112.  
  113.     switch (iph->protocol) {
  114.     case IPPROTO_TCP:
  115.           return echo_tcp((u_char *)&iph[1]);
  116.     case IPPROTO_UDP:
  117.           return echo_udp((u_char *)&iph[1]);
  118.     default:
  119.           return -1;
  120.     }
  121.     return 0;
  122. }
  123.  
  124. static int echo_tcp(const u_char *buf)
  125. {
  126.     struct tcphdr *tcph = (struct tcphdr *) buf;
  127.  
  128.     printf("/nTCP包头");
  129.     printf("/n源端口: %d", ntohs(tcph->source));
  130.     printf("/n目的端口: %d", ntohs(tcph->dest));
  131.     printf("/n序列号: %u", ntohl(tcph->seq));
  132.     printf("/n确认号: %u", ntohl(tcph->ack_seq));
  133.     printf("/n头部长度: %d (%d bytes)", tcph->doff, tcph->doff*4);
  134.     printf("保留位/n: 0x%X", tcph->res1);
  135.     printf("/n代码位: r:%d,u:%d,a:%d,p:%d,r:%d,s:%d,f:%d",
  136.           tcph->res2, tcph->urg, tcph->ack, tcph->psh,
  137.           tcph->rst, tcph->syn, tcph->fin);
  138.     printf("/n接收窗口大小: %d", ntohs(tcph->window));
  139.     printf("/n校验和: 0x%02X%02X", PWORD(tcph->check));
  140.     printf("/n紧急数据指针: %d", ntohs(tcph->urg_ptr));
  141.  
  142.     return 0;
  143. }
  144.  
  145. static int echo_udp(const u_char *buf)
  146. {
  147.     struct udphdr *udph = (struct udphdr *) buf;
  148.  
  149.     printf("/nUDP包头");
  150.     printf("/n源端口: %d", ntohs(udph->source));
  151.     printf("/n目的端口: %d", ntohs(udph->dest));
  152.     printf("/n数据长度: %d bytes", ntohs(udph->len));
  153.     printf("/n校验位: 0x%X", udph->check);
  154.  
  155.     return 0;
  156. }
  157.  
  158. static void hex_print(const u_char *buf, int len, int offset)
  159. {
  160.     u_int i, j, jm;
  161.     int c;
  162.  
  163.     printf("/n");
  164.     for (i = 0; i < len; i += 0x10) {
  165.           printf(" %04x: ", (u_int)(i + offset));
  166.           jm = len - i;
  167.           jm = jm > 16 ? 16 : jm;
  168.  
  169.           for (j = 0; j < jm; j++) {
  170.                 if ((j % 2) == 1)
  171.                     printf("%02x ", (u_int) buf[i+j]);
  172.                 else printf("%02x", (u_int) buf[i+j]);
  173.           }
  174.           for (; j < 16; j++) {
  175.                 if ((j % 2) == 1) printf("   ");
  176.                 else printf(" ");
  177.           }
  178.           printf(" ");
  179.  
  180.           for (j = 0; j < jm; j++) {
  181.                 c = buf[i+j];
  182.                 c = isprint(c) ? c : '.';
  183.                 printf("%c", c);
  184.           }
  185.           printf("/n");
  186.     }
  187. }

用到的头文件mysniff.h

  1. /*************************************
  2.   mysniff.h
  3. *************************************/
  4.  
  5. ifndef _SNIFFER_H
  6. #define _SNIFFER_H
  7.  
  8.  
  9. /*
  10. *     Display an MAC address in readable format.
  11. */
  12. #define HWADDR(addr) /
  13.     ((unsigned char *)&addr)[0], /
  14.     ((unsigned char *)&addr)[1], /
  15.     ((unsigned char *)&addr)[2], /
  16.     ((unsigned char *)&addr)[3], /
  17.     ((unsigned char *)&addr)[4], /
  18.     ((unsigned char *)&addr)[5]
  19.  
  20. /*
  21. *     Display an IP address in readable format.
  22. */
  23. #define NIPQUAD(addr) /
  24.     ((unsigned char *)&addr)[0], /
  25.     ((unsigned char *)&addr)[1], /
  26.     ((unsigned char *)&addr)[2], /
  27.     ((unsigned char *)&addr)[3]
  28.  
  29. #define HIPQUAD(addr) /
  30.     ((unsigned char *)&addr)[3], /
  31.     ((unsigned char *)&addr)[2], /
  32.     ((unsigned char *)&addr)[1], /
  33.     ((unsigned char *)&addr)[0]
  34.  
  35. #define PWORD(addr) /
  36.     ((unsigned char *)&addr)[0], /
  37.     ((unsigned char *)&addr)[1]
  38.  
  39. #endif
  40.  
  41.  

程序在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........