IP选项初识

来源:互联网 发布:java电信项目 编辑:程序博客网 时间:2024/06/06 02:04
IPv4允许在20字节首部固定部分之后跟以最多40个字节的选项,最常用的是源路径选项。这些选项的访问途径是存取IP_OPTIONS套接字选项。
IPv6允许在固定长度的40字节IPv6首部和传输层(ICMPv6、TCP或UDP)首部之间出现扩展首部。 不同于IPv4的是,IPv6的扩展首部的访问途径是函数接口。


IPv4选项:
    根据下图IPv4首部格式,发现IPv4选项字段跟在20字节IPv4首部固定部分之后。首部中占4位的首部长度字段限制了首部长度最多60个字节(15个32位字),因此选项字段最长40个字节。
    以下几个选项我们也在TCP/IP详解中见过并使用过:
    1、NOP    为某个后续选项落在4字节边界上提供填充
    2、EOL    end of list,终止选项的处理
    3、LSRR    宽松源路径选站
    4、SSRR    严格源路径选站
    5、TIMESTAMP        IP的时间戳选项
    6、Record Route    IP记录路由选项

读取和设置IP选项字段使用getsockopt , setsockopt

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);//此时level参数为 IPPROTO_IP, optname为IP_OPTIONS//第四个参数是指向某个缓冲区(大小 <= 44字节) 的一个指针,第五个参数为该缓冲区的大小//该缓冲区的大小之所以可以比选项字段的最长长度多出4个字节是由源路径选项的处理方式使然,稍后会讲解到,除了两种源路径选项外,其他选项在该缓冲区中的格式就是把他们置于IP数据报中的格式//使用setsockopt设置IP选项后,在相应套接字上发送的所有IP数据报都将包括这些选项。接收IP选项的套接字包括TCP,UDP和原始套接字//清除套接字选项的也使用setsockopt,只是既可以把第四个参数指定为空指针,也可把第五个指定为0



int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);//当调用此函数获取由accept创建的某个已连接TCP套接字的IP选项时,返回的是在相应监听套接子上收到的客户SYN分节所在IP数据报中可能出现的源路径选项的逆转。源路径(我们知道,这里面是发送端指定的几个路由地址)被TCP自动逆转顺序,因为由客户端指定的是从客户到服务器的源路径,服务器却需要在发送到客户的数据报中使用该路径的逆转。如果没有源路径伴随SYN分节,那么getsockopt通过第五个参数返回的长度为0。//对于所有其他TCP套接字及所有UDP套接字和所有原始IP套接字而言,调用getsockopt获取IP选项返回的仅仅是以前对于同一个套接字调用setsockopt设置的IP选项的一个副本。//注意对于一个原始IP套接字,输入函数总是返回包括任何IP选项在内的接收IP首部(也就是到达IP首部),因此接收IP选项也总是可得的



IPv4源路径选项
    源路径是由IP数据报的发送者指定的一个IP地址列表。
        如果是严格源路径,那么必须且只能经过所列的节点。
        如果是宽松源路径选站,那么必须经过列出的点,也可以经过未列出的点。
    下图为我们传递给setsockopt的缓冲区格式:
    



    放置NOP选项是因为使所有IP选项在各自的4字节边界对齐,这么做并非必须(这么做也并不会占用空间,IP选项也总是会填充成4字节的倍数,并不会有什么影响)
    结合我们在tcp/ip详解中学过的源路径选项,这里给出更详细的描述:
        图中展示的地址最多有10个,不过所列出的第一个地址将在相应套接字的每个外出IP数据报即将离开源主机时被移出源路径选项地址列表,并称为IP数据报的目的地址。尽管40字节的IP选项空间只能存放9个地址,但如果把目的地址字段包含进来,IPv4首部实际上有10个IP地址
        前3个字节已经认识过了。由一个IP地址构成的源路径len为11,(前三个字符,目的IP地址,地址清单中的一个),由9个构成的len为43。NOP不属于本SSR选项,因为它自成一个单字节IP选项,因而不包括在len字段的涵盖范围之内,不过包括在给setsockopt指定的缓冲区大小之中。
        当源路径地址表中的第一个地址被移走并置于IP首部的目的地址字段中时,这个len值被减去4。ptr初始值为4,之后每次加4

    下面书上给出3个函数,分别初始化、创建和处理一个源路径选项。这些函数只处理源路径IP选项

#include "unp.h"#include <netinet/ip.h>#include <netinet/in_systm.h>static u_char *optr;        // pointer into options being formedstatic u_char *lenptr;        //pointer to length bytein SRR optionstatic int ocnt;        //count of # address//初始化传给setsockopt的缓存区u_char *inet_srcrt_init(int type){    optr = malloc(44);        //NOP, code, len, ptr, up to 10 addresses    if(optr == NULL)        oops("memory is out");    bzero(optr,44);            //guarantee EOL at the end    ocnt = 0;    *optr++ = IPOPT_NOP;        //NOP    *optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;    lenptr = opt++;            //we will fill in length later    *optr++ = 4;            //offset to first address    return (optr-4);        //pointer for setsockopt()}



//向源路径地址清单中加入地址,并返回缓存区现有长度int inet_srcrt_add(char *hostptr){    int len;    struct addrinfo *ai;    struct sockaddr_in *sin;    if(ocnt > 9)                //如果加入的地址个数太多了,那么就报错    {        fprintf(stderr,"too many source routes with %s\n",hostptr);        exit(1);    }    ai = host_serv(hostptr,NULL,AF_INET,0);            //将主机名或点分十进制进行转换    sin = (struct sockaddr_in *)ai->ai_addr;    memcpy(optr,&sin->sin_addr,sizeof(struct in_addr));    //把最终的二进制地址存入地址清单    freeaddrinfo(ai);    optr += sizeof(struct in_addr);            //改变指针位置,方便以后添加地址    ocnt ++;    len = 3+(sizeof(struct in_addr)*ocnt);            *lenptr = len;    return len+1;                //size for setsockopt()}


    通过getsockopt调用返回给应用进程的接收源路径格式不同于发送源路径的格式,如下图:
    



    首先,地址的顺序是所收取的源路径被内核逆转后的顺序。这里的“逆转”指的是与接收的顺序相反的顺序排列的地址清单(A,B,C,D变为D,C,B,A)。头四个字节是该列表的第一个IP地址,后跟一个单字节NOP(为了对齐),再跟以3个字节选项首部(code,len,ptr),最后是其余IP地址。len相应最大值为39,又NOP始终存在,因此getsockopt返回的长度总是4的倍数。
    既然我们知道了get...返回的格式不同于set的...的格式,如果我们想要把get...的格式转到set..的格式,就必须对换头4个字节和随后的4个字节,再给len加上4。

    这里我们获取到上面格式的一个接收源路径并显示该信息:

//显示一个接收源路径void inet_srcrt_print(u_char *ptr, int len){    u_char c;    char str[INET_ADDRSTRLEN];    struct in_addr hopl;    memcpy(&hopl,ptr,sizeof(struct in_addr));    //取走第一个地址    ptr += sizeof(struct in_addr);    while((c=*ptr++) == IPOPT_NOP);            //跳过NOP    if(c == IPOPT_LSRR)                //判断选项类型        printf("received lsrr\n");    else if(c == IPOPT_SSRR)        printf("received ssrr\n");    else    {        printf("received option type %d\n",c);        return;    }    printf("%s\n", inet_ntop(AF_INET, &hopl, str, sizeof(str)));    len = *ptr++ - sizeof(struct in_addr);        //减去目的地址长度,因为我们不打算显示它    ptr++;                        //skip pointer    while(len > 0)    {        printf("%s",inet_ntop(AF_INET, &hopl, str, sizeof(str)));        ptr += sizeof(struct in_addr);        len -= sizeof(struct in_addr);    }    puts("");}



下面我们为一个回射客户端指定源路径地址:

//这里是提供源路径实现的回射客户端。可以在命令行指明-g(宽松选站)或-G(严格选站),并在后面提供地址清单进行源路径选站操作//实现并不复杂,关于使用 getopt 来处理命令行输入这个有待加强。//大致步骤就是查看是否有-g或-G,有的话就分配内存空间,并将地址加进去,最后用setsockopt处理这个套接字就可以了。#include "unp.h"int main(int ac, char *av[]){    int sockfd, c, len = 0;    u_char *ptr = NULL;    struct addrinfo *ai;    if(ac < 2)    {        fprintf(stderr, "Usage: tcpcli [-gG <hostname> ...] hostname\n");        exit(1);    }    opterr = 0;            //不希望getopt出错信息显示出来    while((c=getopt(ac,av,"gG")) != -1)    {        switch(c)        {        case 'g':            if(ptr)            {                fprintf(stderr,"cannot use both g and G\n");                exit(2);            }            ptr = inet_srcrt_init(0);            break;        case 'G':            if(ptr)            {                fprintf(stderr,"cannot use both g and G\n");                exit(2);            }            ptr = inet_srcrt_init(1);            break;        case '?':                    //不明物体,比如-x这种例外就是问号了            fprintf(stderr,"WTF!\n");            exit(3);        }    }    if(ptr)    {        while(optind < ac-1)            //把最后一个目标地址留下来,我们还要有其他用途            len = inet_srcrt_add(av[optind++]);    }    else if(optind < ac-1)            //如果没分配内存,表明没有指定-g或-G,那就该只有两个参数,optind为1,即optind=ac-1,但这里如果optind位置不对,说明没有指定-g或-G,但是给出了地址清单                        //具体可以看 Skills/getopt_usage.c    {        fprintf(stderr,"need to specify -g or -G\n");        exit(1);    }    if(optind != ac-1)                {        fprintf(stderr, "missing hostname\n");        exit(1);    }    ai = host_serv(av[optind],"13000",AF_INET,SOCK_STREAM);    sockfd = socket(ai->ai_family, ai-ai_socktype, ai->ai_protocol);    if(ptr)    {        len = inet_srcrt_add(av[optind]);        //如果使用了源路径,就要将目标地址加在最后        setsockopt(sockfd, IPPROTO_IP, IP_OPTIONS, ptr, len);        free(ptr);    }    connect(sockfd, ai->ai_addr, ai->ai_addrlen);    str_cli(stdin, sockfd);    return 0;}



服务器端的就相对简单些了,只要在原服务器基础上调用getsockopt和之前写好的 inet_srcrt_print这两个函数就可以了。
只是记得要在accept之后,fork之前,因为我们分析的是SYN数据报的IP选项。
如果SYN所在的IP数据报不含有IP选项,那么通过getsockopt返回的len就是0
0 0
原创粉丝点击