7.1 网络传输分层

  如果你考过计算机等级考试,那么你就应该已经知道了网络传输分层这个概念。在网络上,人们为了传输数据时的方便,把网络的传输分为7个层次,分别是:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。分好了层以后,传输数据时,上一层如果要数据的话,就可以直接向下一层要了,而不必要管数据传输的细节;下一层也只向它的上一层提供数据,而不要去管其它东西了。如果你不想考试,你没有必要去记这些东西的。只要知道是分层的,而且各层的作用不同。

7.2 IP协议

  IP协议是在网络层的协议。它主要完成数据包的发送作用。下面这个表是IP4的数据包格式:

  0        4        8        16                       32
  +--------+--------+--------+------------------------+
  |  版本  |首部长度|服务类型|       数据包总长       |
  +--------+--------+--------+---+---+----------------+
  |           标识           |DF |MF |    碎片偏移    |
  +-----------------+--------+---+---+----------------+
  |     生存时间    |  协议  |       首部较验和       |
  +-----------------+--------+------------------------+
  |                      源IP地址                     |
  +---------------------------------------------------+
  |                     目的IP地址                    |
  +---------------------------------------------------+
  |                        选项                       |
  +===================================================+
  |                        数据                       |
  +---------------------------------------------------+

  下面我们看一看IP的结构定义<netinet/ip.h>

  struct ip
  {
  #if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;           /* header length */
    unsigned int ip_v:4;            /* version */
  #endif
  #if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;            /* version */
    unsigned int ip_hl:4;           /* header length */
  #endif
    u_int8_t ip_tos;                /* type of service */
    u_short ip_len;                 /* total length */
    u_short ip_id;                  /* identification */
    u_short ip_off;                 /* fragment offset field */
  #define IP_RF 0x8000                /* reserved fragment flag */
  #define IP_DF 0x4000                /* dont fragment flag */
  #define IP_MF 0x2000                /* more fragments flag */
  #define IP_OFFMASK 0x1fff           /* mask for fragmenting bits */
    u_int8_t ip_ttl;                /* time to live */
    u_int8_t ip_p;                  /* protocol */
    u_short ip_sum;                 /* checksum */
    struct in_addr ip_src, ip_dst;  /* source and dest address */
  };

  ip_vIP:协议的版本号,这里是4,现在IPV6已经出来了。
  ip_hlIP:包首部长度,这个值以4字节为单位。IP协议首部的固定长度为20个字节,如果IP包没有选项,那么这个值为5。
  ip_tos:服务类型,说明提供的优先权。
  ip_len:说明IP数据的长度,以字节为单位。
  ip_id:标识这个IP数据包。
  ip_off:碎片偏移,这和上面ID一起用来重组碎片的。
  ip_ttl:生存时间,每经过一个路由的时候减一,直到为0时被抛弃。
  ip_p:协议,表示创建这个IP数据包的高层协议,如TCP、UDP协议。
  ip_sum:首部校验和,提供对首部数据的校验。
  ip_srcip_dst:发送者和接收者的IP地址。

  关于IP协议的详细情况,请参考RFC791。

7.3 ICMP协议

  ICMP是消息控制协议,也处于网络层。在网络上传递IP数据包时,如果发生了错误,那么就会用ICMP协议来报告错误。

  ICMP包的结构如下:

  0              8              16                               32
  +--------------+--------------+--------------------------------+
  |     类型     |     代码     |            校验和              |
  +--------------+--------------+--------------------------------+
  |            数据             |             数据               |
  +-----------------------------+--------------------------------+

  ICMP在<netinet/ip_icmp.h>中的定义是:

  struct icmphdr
  {
    u_int8_t type;                /* message type */
    u_int8_t code;                /* type sub-code */
    u_int16_t checksum;
    union
    {
      struct
      {
        u_int16_t id;
        u_int16_t sequence;
      } echo;                     /* echo datagram */
      u_int32_t   gateway;        /* gateway address */
      struct
      {
        u_int16_t __unused;
        u_int16_t mtu;
      } frag;                     /* path mtu discovery */
    } un;
  };

  关于ICMP协议的详细情况可以查看RFC792。

7.4 UDP协议

  UDP协议是建立在IP协议基础之上的,用在传输层的协议。UDP和IP协议一样是不可靠的数据报服务。

  UDP的头格式为:

  0                       16                      32
  +-----------------------+------------------------+
  |      UDP源端口        |      UDP目的端口       |
  +-----------------------+------------------------+
  |    UDP数据报长度      |     UDP数据报校验      |
  +-----------------------+------------------------+

  UDP结构在<netinet/udp.h>中的定义为:

  struct udphdr {
    u_int16_t     source;
    u_int16_t     dest;
    u_int16_t     len;
    u_int16_t     check;
  };

  关于UDP协议的详细情况,请参考RFC768。

7.5 TCP

  TCP协议也是建立在IP协议之上的,不过TCP协议是可靠的、按照顺序发送的。TCP的数据结构比前面的结构都要复杂。

  0        4       8    10         16               24               32
  +--------------------------------+---------------------------------+
  |            源端口              |            目的端口             |
  +--------------------------------+---------------------------------+
  |                             序列号                               |
  +------------------------------------------------------------------+
  |                             确认号                               |
  +--------+-------------+-+-+-+-+-+---------------------------------+
  |    |         |U|A|P|S|F|                                 |
  |首部长度|    保留     |R|C|S|Y|I|              窗口               |
  |    |         |G|K|H|N|N|                                 |
  +--------+-------------+-+-+-+-+-+---------------------------------+
  |            校验和              |            紧急指针             |
  +--------------------------------+-----------------+---------------+
  |                     选项                         |   填充字节    |
  +--------------------------------------------------+---------------+

  TCP的结构在<netinet/tcp.h>中定义为:

  struct tcphdr
  {
      u_int16_t source;
      u_int16_t dest;
      u_int32_t seq;
      u_int32_t ack_seq;
  #if __BYTE_ORDER == __LITTLE_ENDIAN
      u_int16_t res1:4;
      u_int16_t doff:4;
      u_int16_t fin:1;
      u_int16_t syn:1;
      u_int16_t rst:1;
      u_int16_t psh:1;
      u_int16_t ack:1;
      u_int16_t urg:1;
      u_int16_t res2:2;
  #elif __BYTE_ORDER == __BIG_ENDIAN
      u_int16_t doff:4;
      u_int16_t res1:4;
      u_int16_t res2:2;
      u_int16_t urg:1;
      u_int16_t ack:1;
      u_int16_t psh:1;
      u_int16_t rst:1;
      u_int16_t syn:1;
      u_int16_t fin:1;
  #endif
      u_int16_t window;
      u_int16_t check;
      u_int16_t urg_prt;
  };

  source:发送TCP数据的源端口。
  dest:接受TCP数据的目的端口。
  seq:标识该TCP所包含的数据字节的开始序列号。
  ack_seq:确认序列号,表示接受方下一次接受的数据序列号。
  doff:数据首部长度。和IP协议一样,以4字节为单位。一般的时候为5。
  urg:如果设置紧急数据指针,则该位为1。
  ack:如果确认号正确,那么为1。
  psh:如果设置为1,那么接收方收到数据后,立即交给上一层程序。
  rst:为1的时候,表示请求重新连接。
  syn:为1的时候,表示请求建立连接。
  fin:为1的时候,表示亲戚关闭连接。
  window:窗口,告诉接收者可以接收的大小。
  check:对TCP数据进行较核。
  urg_ptr:如果 urg=1,那么指出紧急数据对于历史数据开始的序列号的偏移值。

  关于TCP协议的详细情况,请查看RFC793。

7.6 TCP连接的建立

  TCP协议是一种可靠的连接,为了保证连接的可靠性,TCP的连接要分为几个步骤。我们把这个连接过程称为“三次握手”。

  下面我们从一个实例来分析建立连接的过程。

  第一步客户机向服务器发送一个TCP数据包,表示请求建立连接。为此,客户端将数据包的 SYN 位设置为1,并且设置序列号 seq=1000 (我们假设为1000)。
  第二步服务器收到了数据包,并从 SYN 位为1知道这是一个建立请求的连接,于是服务器也向客户端发送一个TCP数据包。因为是响应客户机的请求,于是服务器设置 ACK 为1,sak_seq=1001 (1000+1)同时设置自己的序列号,seq=2000 (我们假设为2000)。
  第三步客户机收到了服务器的TCP,并从 ACK 为1和 ack_seq=1001 知道是从服务器来的确认信息,于是客户机也向服务器发送确认信息。客户机设置 ACK=1ack_seq=2001seq=1001 发送给服务器。至此客户端完成连接。
  最后一步服务器受到确认信息,也完成连接。

  通过上面几个步骤,一个TCP连接就建立了。当然在建立过程中可能出现错误,不过TCP协议可以保证自己去处理错误的。

  说一说其中的一种错误。

  听说过DOS吗?(可不是操作系统啊)
  今年春节的时候,美国的五大网站一起受到攻击。攻击者用的就是DOS(拒绝式服务)方式。概括的说一下原理。
  客户机先进行第一个步骤。服务器收到后,进行第二个步骤。按照正常的TCP连接,客户机应该进行第三个步骤。不过攻击者实际上并不进行第三个步骤。因为客户端在进行第一个步骤的时候,修改了自己的IP地址,就是说将一个实际上不存在的IP填充在自己IP数据包的发送者的IP一栏。这样因为服务器发的IP地址没有人接收,所以服务端会收不到第三个步骤的确认信号,这样服务务端会在那边一直等待,直到超时。这样当有大量的客户发出请求后,服务端会有大量等待,直到所有的资源被用光,而不能再接收客户机的请求。这样当正常的用户向服务器发出请求时,由于没有了资源而不能成功。于是就出现了春节时所出现的情况。