TCP/IP实现(六) IP选项处理

一.概述

       IP选项是IP固定首部之后的选项部分,由于IP首部长度是用4bit来计数,以4个字节为表示的,所以首部长度最多为60个字节,IP选项最多为40个字节。IP选项字段可能包含0~多个单独选项。选项包含两类:单字节与多字节。单字节选项只有1个字节的类型字段,多字节字段包含1个字节的类型字段,1个字节的长度字段,以及之后的数据字段(数据字段的第一个字节一般是数据偏移,如在源路由选项中代表下一个待处理的字段)。其结构如下图所示:

         

       常见的IP选项有以下几种:

  1.      NOP选项              IPOPT_NOP      单字节选项       用于与后续选项凑成4字节,使后续选项落在4字节边界上,比如常与多字节选项中的type,len,offset凑成4个字节。
  2.      EOL选项              IPOPT_EOL       单字节选项       用于补充在IP选项的最后部分,因为IP选项的总长度必须是4字节的倍数。
  3.      LSRR选项           IPOPT_LSRR     多字节选项       宽松路由选项
  4.      SSRR选项           IPOPT_SSRR    多字节选项       严格路由选项
  5.      Record Route      IPOPT_RR         多字节选项       记录路由选项
  6.      Timestamp          IPOPT_RR         多字节选项       时间戳选项

        可以在其上设置IP选项的套接字包括TCP,UDP和原始IP套接字,要清除这些选项可以使用setsockopt(第四个值参数为空或第五个值长度参数为0)设置。TCP套接字自动使用来自SYN所在数据报的原路径选项的逆转,而不必我们使用setsockopt告诉内核使用什么路径来发送。

二.IP选项使用
1.LSRR(宽松源路由选项) & SSRR(严格源路由选项)

      虽然IP选项最多只含有40个字节,但是传递给函数setsockopt的参数却是一个指向大小小于等于44的缓冲区,这是因为缓冲区中指定的源路由路径中的第一个地址会被赋值到IP数据报的目的地址中,然后这个地址就可以被移除了。每到一个指定的路由节点都会将offset所指的地址设置为下一目的地址,在将从当前路由外出的接口地址放至offset处,随后将offset后移。其传输过程中填充步骤的示意图如下:

   

   具体的调用setsockopt的写法在此不再赘述,可以参考UNP p564 ~ p566。

三.IP选项处理实现

        在博文《TCP/IP实现(五) IP协议》中提到,IP选项是由IP层的ipintr函数在验证IP固定首部之后,匹配目的地址之前进行处理的。在这期间会调用ip_dooptions处理IP选项。其总体实现如下:

int ip_dooptions(mbuf* m)
{
    struct ip* = mtod(m, ip*);//指向mbuf中的ip数据报部分
    u_char* *cp = (u_char*)(ip + 1); //指向ip选项
    int cnt = (ip->ip_hl << 2) - sizeof(struct ip); // 计算ip选项长度
    int optlen = 0, icmp_type = ICMP_PARAMROB;// 默认ICMP报文为参数错误 
    for(; cnt > 0; cnt - optlen, cp += optlen) { // 遍历选项
        opt = cp[type];// 选项类型
        if(opt == IPOPT_EOL) // EOL为尾部填充选项,结束遍历
            break;
        if(opt == IPOPT_NOP) // 填充选项,不做处理
            optlen = 1;      // 选项长度
        else {
            optlen = cp[len];
            if(optlen <= 0 || optlen > cnt) { // 检查选项长度
                goto bad; // 返送ICMP报文
            }
            switch(opt){
                处理各选项

                default: // 未识别选项不做处理
                    break;
            } // switch
        } //else
    } //for
    
    判断是否根据源路由进行转发
    return 1;
    bad:
        //回复ICMP报文;
        icmp_error(xx, icmp_type, code,...);
}

1.记录路由选项IPOPT_RR

       首先检查数据偏移offset是否过小(小于4),若是则发送ICMP_PARAMROB参数问题ICMP报文,code为出错字节在分组内的偏移。接着计算该IP选项的可用存储空间,若不够则不记录,否则查找路由,记录外出接口的IP地址,若无路由信息则返回ICMP住居不可达报文。选项处理代码如下:

case IPORT_RR:
    if((off = cp[offset]) < 4) {
        code = &cp[offset] - (u_char*)ip;
        goto bad;
    }

    if(off + sizeof(struct in_addr) > optlen) // 选项的存储空间不足
        break;
    
    //将目的地址拷贝到一个存储地址的全局buf中
    bcopy((caddr_t)(&ip->ip_dst), (caddr_t)&ipaddr.sin_addr, sizeof(ipaddr.sin_addr)); 

    if(邻接网络和路由搜索失败){
        icmp_type = ICMP_UNREACH;
        code = ICMP_UNREACH_HOST;
        goto bad;
    }

    若目的地址就是本机则将接收接口的地址拷贝到选项中,否则将外出接口的地址拷贝到选项中
    break;

2.源路由选项LSRR & SSRR

       源路由选项分为宽松源路由选项和严格源路由选项。宽松源路由选项只指定某些中间路由器的路由,而严格路由选项却包含了源站到目的栈之间的全部路由。从源站出发时会将IP数据报的目的地址设置为源路由选项中的offset所指的地址,且每到一个源路由选项中所指明的站,都会将IP数据报的目的地址设置为源路由表项中的下一站,即offset偏移处的地址。实现代码如下:

case IPOPT_LSRR:
case IPOPT_SSRR:
    if((off = cp[offset]) < 4){   // 偏移过小
        code = &cp[offset] - (u_char *)ip; //计算错误字节在分组中的偏移
		goto bad;
    }

    // 第一步
    将IP数据报中的目的地址与本地地址进行匹配
    if(若无匹配的本地地址){
        if(是严格路由选项) { //说明源路由失败,因为指定了每一个节点
            icmp_type = ICMP_UNREACH;
            code = ICMP_UNREACH_SRCFALL;// 源路由失败ICMP报文 
            goto bad;
        }
        // 运行到此处说明是宽松路由协议,宽松路由允许经过未指定的节点,对于未指定的节点不做过多处理
        // 若无其它选项则会交还IP处理函数ipintr继续处理,否则处理其它选项
        break;
    }

    // 允许到此处说明本站是源路由站之一
    if(源路由表项中已经没有下一个路由项了) {
        //说明已到达终点
        break;
    }
    
    // 运行到此处说明还未到终点
    // 检查源路由表项中的下一站地址
    if(是严格路由协议){
        下一站只能在邻接网络上,因此调用调用ifa_ifwithdstaddr匹配各接口的目的地址(SLIP接口就指定了目的地址),
        若匹配失败则继续调用ifa_ifwithnet匹配邻接网络
    }else {
        //宽松路由
        进行路由查找
    }
    
    if(下一站地址搜索失败) {
            icmp_type = ICMP_UNREACH;
            code = ICMP_UNREACH_SRCFALL;// 源路由失败ICMP报文 
            goto bad;
    }

    将IP地址设置未下一站地址,即将cp[off]处的地址设置为下一站地址
    将外出接口的地址放至cp[off]处
    break;

    当我们调用getsockopt函数获取源路径时,会发现得到的原路径是反向的,且最后一个地址是收到IP数据报源站地址,在源路由中的最后一个地址(即发送端设置的目的地址,也就是接收者的本机地址)也会被放至到第一个字节(选项类型之前)。示意图如下:

          

    这个过程是通过两个函数和一个结构体实现的,save_rte函数将IP数据报中的源路由及源地址存储到结构体ip_srcrt中,接着再由函数ip_srcroute将其逆转存储到一个mbuf中,存储后的格式如上图所示。对于其具体实现在此不进行赘述,可以参考TCP V2 P205 ~ P207。

3.时间戳选项IPOPT_TS

      待补充

猜你喜欢

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