TCP/IP实现(五) IP协议

一.IP首部

1.概述

        ip数据报的首部格式如下:

                             

       首部长度是已4字节为单位的,因此首部最长为15 * 4 = 60个字节。生存时间规定了数据报可以经过的最多路由器数。标识字段ip_id唯一标识主机发送的每一份数据报,通常每发送一份报文其值就加1。

2.大端序与小端序

       大端序又叫网络字节序,其高位在低字节处,而小端序一般也叫做主机字节序,其高位在高字节处。其实这是由于TCO/IP协议的传输次序造成的,由于TCP/IP首部中所有的二进制整数都是以如下方式传递的:4个字节32bit值的传输次序为首先是0~7bit,其次是8 ~ 15 bit,然后是16 ~ 23bit,最后是24 ~ 31bit,所以传输数据之前必须将首部转化为网络字节序。我们以一个int来举例说明:

                       

      可以看到由于发送顺序的原因导致接收端先收到低位数据,从缓存读入之后字节序变反了

二.IP选路

       IP层可以从UDP,TCP,ICMP和IGMP接收数据报并进行发送,或者从一个网络接口接收数据报并进行转发(如果开启了路由功能)。IP层在内存中维护了一张路由表,每次都会根据路由表项进行转发。

1.路由表项及匹配原则

      路由表中包含以下表项:

      1)目的IP地址。既可以是一个完整的主机地址,也可以是一个网络地址。

      2)下一跳路由器的IP地址,或是直接连接的网络地址。

      3)两个标志。一个用于表明目的地址(即第一条)是主机地址还是网络地址,另一个表明下一跳是一个路由器还是一个直接相连的接口。

      4)为数据报的传输指定一个网络接口。

     匹配原则如下:

      1)优先匹配能与目的IP地址完全匹配的表目

      2)次优先匹配与目的网络号相匹配的表目

      3)搜索默认表目

      若以上均失败,则回复ICMP“主机不可达”或“网络不可达”报文

      可以使用命令netstat -rn来查看主机路由表,-r选项列出路由表,-n选项以数字格式打印处IP地址,如图所示:

                 

       所有数据报都会被网关转发到10.1.16.254, Flags标志的含义如下:U 该路由可以使用;G 该路由是到一个网关(路由器),若无该标志则说明目的地是直接相连的;H 该路由是到一个主机,若无该标志说明是到一个网络 ,即以一个网络号或是网络号与子网号的组合,D 该路由是由重定向报文创建的; M 该路由已被重定向报文修改。Genmask是目的地址的子网掩码

      内核中有一个变量叫做ipforwarding,用以决定主机是否转发IP数据报。

2.ICMP重定向差错

       只有当主机可以选择路由时才可能看到ICMP重定向报文,它的作用是高速发送方,可以直接发送到某个路由或直接链路上,而无需经过自己。举个例子:

                                                              

1)假定主机发送一份IP数据报到R1。这时长发生,因为R1是默认路由

2)R1收到后检查路由表发现R2是下一跳,当它把数据报发送给R2时,发现发送接口与接收的接口是同一个(这意味着源端可以直接到达下一跳,而无需经过自己),此时该路由器发送重定向报文给源端,告诉它以后将数据发送给R2而不是R1

      其优点是将所有的智能特性放到了路由端,所有主机在启动时只需要一个默认路由,之后通过重定向报文进行逐步学习

3.路由发现协议

     一般,主机在引导后会以广播或多播的形式传送一份路由器请求报文(即ICMP路由器请求报文)。一台或更多路由会回复ICMP路由器通告报文。报文格式如下:

                              

                  

      生存时间指的是通告地址的有效时长(秒),默认为30分钟,当路由器上的某一端口关闭时,路由器可以在该接口上发送一生存时间为0的通告报文

     当路由器启动时,它会随机的在所有广播或多播接口上发送通告报文,一般两次通告的时间间隔为450到600秒.

4.动态选路协议

     1)概述

              选路协议是相邻路由器间进行通信(并进行路由更新)时采用的协议,比如RIP,OSPF和BGP。路由器上有一个进程称为路由守护程序,它运行选路协议,根据协议内容与相邻路由器进行通信和更新路由表。动态选路并不影响前面所说的IP层的选路机制,它是一种路由通信与更新路由表的协议。

     2)IGP与EGP

              Internet是以一个个小的网络组成的(如一所高效或公司的网络),每个小的网络内可以使用独立的选路协议,称之为内部网络协议IGP或域内选路协议。而各个小的网络之间采用的选路协议称之为外部网关协议EGP或分隔选路协议。IGP由OSPF和RIP协议,EGP有BGP协议

     3)RIP选路协议(版本1)

               RIP报文有着自己的协议格式,RIP报文是包含在UDP中进行发送的,其协议格式如图所示:                               

               协议运作过程如下:

  •         初始化:在启动一个路由守护程序后,从该路由的所有打开接口向外广播请求报文,或指定到某些目的地址的路由,或要求所有路由信息。
  •         接收到请求:发送自己所有的路由信息或是根据请求的目的IP进行回复,对于后一种情况,若有到达指明地址的路由则度量为该路由到该地址的度量,否则为16,即不可达。
  •         接收到相应:更新路由表,会选择度量小的表项。
  •         定期选路更新:每过30秒,部分或所有路由器会将完整的路由表发送给相邻路由器。
  •         触发更新:当某一条路由的度量发生变化时,对该条路由进行更新。无需发送完整路由表,而只需向相邻路由发送发生变化的表项
  •         超时删除:每条路由都有一个与之相关的定时器,如果发现某一条路由在3分钟内未更新,就将该路由的度量设为16(不可达),再过60秒则删除。

     4)RIP选路协议(版本2)

               RIP版本2对RIP进行了一些扩充,这些扩充并不改变协议本身,只是用那些必须为0的字段来存储一些额外字段。,RIP与RIP-2可有进行互操作。其协议字段如下:                            

     5)OSPF选路协议

               OSPF协议采用的链路状态协议不同于RIP的距离向量协议,当链路出现故障时OSPF可以更快的稳定下路,且OSPF协议直接使用IP。且OSPF协议可以根据服务类型的不同选择出更好的路径,因为OSPF可以对每个IP服务类型计算各自的路由集,即对于一个目的地可以有多个路由表项。OSPF协议会根据吞吐量,往返时间,可靠性或其它性能(每种IP服务类型)为每个接口指定一个费用。总的来说OSPF协议克服了RIP的所有限制,并增加了很多优点。

三.部分实现

1.概述

       在博文《TCP/IP实现 (三) 以太网的数据收发》中已经介绍了网络接口如何将数据放至IP输入队列ipintrq中去,并如何产生一个软件中断。在软件中断处理期间,IP层调用ipintr函数不断从ipintrq中取出并处理分组,直至队列为空。若果分组已到达最终目的地则IP层对数据进行重组后总至适当的运输层协议。若没有到达最终目的地,且主机被配置为一个路由器,则IP把分组传给ip_forward函数(处理IP首部(如TTL),处理ICMP重定向等),最后交由ip_output函数进行发送,ip_output函数即从ip_forward接收转发报文,也从运输层接收上层数据包,该函数也会处理部分IP首部还进行IP分片。IP层处理如下图所示:

         

2.IP输入

         在接口层中将数据报放入IP输入队列并产生软件中断,之后再处理中断时由内核调用ipintr函数循环从ipintrq队列中取出分组,进行逐个处理,直至队列为空,处理过程如下所述。

1)ipintr()函数

        ipintr函数处理处理IP选项,数据检验,地址匹配及交付上层协议或交由ip_forward进行转发 。处理过程如下:首先判断是否由接口被分配了IP地址(通过判断IP地址链表是否为空),若未分配则丢弃所有分组。接着检查IP首部长度,IP首部检验和以及IP数据长度,若任何一个出现了差错都会增加相应错误计数。接着处理IP选项,关于IP选项在下一篇博文中介绍。之后遍历ip地址链表,检查是否有匹配的IP地址,匹配情况如下:1)与某接口地址完全匹配; 2)与接收接口相关的广播地址匹配(在下面说明);3)与某个接口相关的多播组之一(在IP多播中介绍);4)与受限广播地址之一匹配(全1和全0)。若有一个匹配的则进行重组并交由运输层协议。上述第二条中指的是与接收接口以下的地址相匹配的:

                                       

  伪代码如下:

void ipintr()
{
    while(ipintrq不为空){
        if(没有接口分配了IP地址) {
            丢弃数据包;
            return;
        }
        // 第一步:数据包检验
        增加数据包计数;
        检查首部长度,检验和及数据长度,若出现错误则丢弃并增加错误计数
        
        // 第二步:处理IP选项
        在后一片博文中介绍        

        // 第三部:匹配IP地址,交付或尝试转发
        for(in_ifaddr* ia = in_ifaddr; ia; ia = ia->next) {// 遍历地址链表
            // 完全匹配
            if(ia->sin_addr.s_addr == ip->id_dst.s_addr) {// 如果某个接口的IP与目的IP相同
                ip->ips_delivered++; // 增加交付计数,通过netstat -s可以查看:xxx incoming packets delivered
                重组并交由上次协议
                continue;
            }

            // 匹配广播地址
            if(ia->ia_ifp == m->m_pkthdr.rcvif && ia->ia_ifp->if_flags & IFF_BOARDCAST) { 
            // 如果该IP的相关接口是接收接口,且该接口可用于接收广播数据
                // 匹配上述的四种地址
                if(ia->ia_broadaddr.sin_addr.s_addr == ip->ip_dst.s_addr) { // 与接收接口的定向网络地址相匹配
                    ip->ips_delivered++; 
                    重组
                    查找协议交换表,交由上次协议(通过pr_input指针指向相应协议的接收函数,如:udp_input)
                    continue;
                }
                if(ia->ia_netbroadaddr.sin_addr.s_addr == ip->ip_dst.s_addr) { // 与接收接口的网络广播地址相匹配
                    // 同上
                }
                // 其它两个略写
            }
            匹配受限广播地址全1和全0,若匹配成功则重组交付上层协议
        } // for
        ip_forward(m);//没有匹配的IP,调用ip_forward尝试进行分组转发
    }// while
}

      此外ipintr还进行IP选项处理,在其它章节说明。

2)ip_forward()数据包转发函数

      该函数负责IP数据报的转发处理,真正的转发操作由ip_optput负责。ip_forward接收两个参数,一个是存有IP数据报的mbuf(一种缓存结构),另一个是int srcrt,若非0则说明该分组设置了源路由选项,即设置了发送路径。

      ip_forward函数对以下几种类型的数据报不进行转发:1.m_buf::flags设置了M_BCAST的分组,任何支持广播的网络接口必须为收到的广播分组设置M_BCAST标志。接口不转发链路层广播分组(注意:链路层广播分组与IP层广播是不同的概念)。2.对于目的地址是环回网络,网络0和E类地址,D类地址的不进行转发。D类多播地址由其它函数负责。理解了下面这两句话也就明白了链路层广播与IP广播的本质区别:链路层地址是用于发送至下一跳的,IP地址是为了寻找网络上的一点的。RFC 1122不允许以链路层广播的方式发送一个寻址到单播IP地址的分组(即以广播的方式搜索下一跳)

      值得注意的是当调用ip_output后,ip_forward会根据返回的值,来判断是否出错,若出错则回复相应的ICMP报文

【注】:只有报文需要转发时才会进入ip_forward,从而检查TTL,这样就解释了为什么是使用traceroute程序时主机不会恢复ICMP。

      伪代码如下:

void ip_forward(mbuf *m, int srcrt) 
{
    struct ip *ip = mtod(m, struct ip*);
    if(m->m_flags & M_BCAST || ip_canforward(ip->ip_dst)) { // ip_canforward用于检测前面说的几种不可转发的地址
        // 丢弃报文
        mfree(m);
        return;
    }
    // 检查TTL
    若ip->ip_ttl过小则回复ICMP超时报文。 // 只有报文需要转发时才会进入ip_forward,从而检查TTL
    更新TTL
    查找路由表进行转发
    若无路由信息则回复ICMP不可达报文
    
    // 发送
    error =  ip_output(m, xx,xx, IP_FORWARDEING | IP_ALLOWBROADCAST,0);

    if(error == 0)
        return;
    // 根据错误信息回复ICMP报文
    switch(error) {
        case ENETUNREACH:  // 直接相连的网络
        case EHOSTUNREACH: // 未找到路由信息
        case ENETDOWN:     // 接口关闭
            回复主机不可达报文;
            break;
        case EMSGSIZE:    // 由于广播报文不支持分片,因此会出现广播报文大小大于MTU此时返回EMSGSIZE
            回复ICMP不可达报文ICMP_UNREACH,不可达类型为:需要进行分片但设置了不分片比特(ICMP代码为4,ICMP_UNREACH_NEEDFRAG)
            break;
        case ENOBUFS: // 当接口层发送队列满时或申请mbuf失败
            回复源端被关闭报文 ICMP_SOURCEQUENCH;
            break;
    }
}

3)ip_output()数据报输出函数

       ip_output接收两处的分组:ip_forward和运输层协议数据,该函数的处理只要分为3步:step1.IP首部的初始化;step2.路由选择;step3 源地址选择与分片。在进行IP首部初始化并根据路由找到外出接口后,根据接口的MTU对分组进行分片,至于如何分片在其它博文中做说明。但注意广播报文不可分片,若报文大小超过外出接口的MTU会将SMSGSIZE返回至调用函数,从而做出一些处理,如回复ICMP报文(若是转发),否则返回给应用,设置errno。若未指定源地址则ip_output函数会选择输出接口的IP地址作为源地址。(注:是不是可以自己伪造IP源地址)。最后ip_output函数将数据报教至接口层,通过函数指针if_output来调用不同类型接口的专用函数,如以太网是ether_output,调用如下:error = (*ifp->if_output)(...)。若出错则会返回至上层。

四 TCP/IP错误处理流程

           

        上层协议会调用下层协议,调用下层协议时会出现一些错误,比如IP层调用接口层时可能出现接口层输出队列满,此时会向IP层返回ENOBUFS,若是发送端则直接将该错误返回给应用进程,且设置errno的值为errno,若是接收端或是中间节点,则会向源端ICMP报文ICMP_SOURCEQUENCH,当源端收到ICMP报文后会将其转化为相应的错误码,并交给相应进程。

猜你喜欢

转载自blog.csdn.net/qq_34228327/article/details/83892479