UNP Chapter 24 - IP选项

24.1. 概述

IPv4允许在固定的20字节头部之后跟以40字节的选项。虽然定义了10个不同的选项,但最常用的是源路径选项,这些选项透过IP_OPTIONS套接口选项访问。

IPv6允许在固定的40字节IPv6头部和传输层头部(如ICMPv6,TCP或UDP)之间出现扩展头部(extension header)。和IPv4不同的是,访问IPv6扩展头部的途径不是强迫用户去理解头部如何出现在IPv6分组中的实际细节,而是通过函数接口进行。

 

24.2. IPv4选项

getsockopt和setsockopt(level参数为IPPROTO_IP,optname参数为IP_OPTIONS)读取和设置IP选项。getsockopt和setsockopt的第四个参数是指向一个缓冲区(大于小于等于44字节)的指针,第五个参数是该缓冲区的大小。该缓冲区的大小之所以可以比选项的最大总长度还大4个字节是源路径选项的处理方法使然,我们将很快描述到这一点。除了两个源路径选项之外,该缓冲区的格式就是IP数据报中放置的选项的格式。

当用setsockopt设置了IP选项后,在该套接口上发出的所有IP数据报都将包括这些选项。套接口可以是TCP,UDP或原始IP套接口。为了清除这些选项,可以调用setsockopt,置第四个参数为空指针,或者置第五个参数(长度)为0

当调用getsockopt获取一个由accept创建的已连接TCP套接口的IP选项时,返回的是在监听套接口上收到的客户的SYN中源路径选项的倒序值。源路径自动被TCP倒序,因为客户说明的是从客户到服务器的源路径,服务器需要使用该路径的倒序值,以发送从服务器到客户的数据报。如果SYN中没有源路径,那么getsockopt通过第五个参数返回的值-结果参数长度将为0。对所有其他的TCP套接口,所有的UDP套接口以及原始IP套接口而言,getsockopt只返回用setsockopt设置的IP选项的拷贝。注意,对一个原始IP套接口来说,收到的IP头部,包括任何IP选项,总是由输入函数返回的,所以收到的IP选项总是可得的。

 

24.3. IP源路径选项

源路径是IP数据报的发送者说明的一组IP地址。如果源路径是严格的(strict),那么数据报必须且只能经过所列的节点。如果源路径是宽松的(loose),数据报必须经过所列的节点但也可以经过未在源路径中列出的节点。

IPv4源路径称为源和记录路径(source and record route, SRR),宽松源路径选项称为LSRR,严格源路径选项称为SSRR,原因是当数据报穿过所列的全部节点到来时,每个节点都将自己的所列地址替换为自己的外出接口地址。这允许接收者将新的列表倒序以沿逆向的路径回到发送者。这两个源路径的例子以及相应的tcpdump输出在TCPv1的8.5节

我们将源路径说明为一个IPv4地址数组,前边附以三个1字节字段(如图24.1所示),这是我们传递给setsockopt的缓冲区格式。

在源路径选项之前,我们放置一个NOP,这使得所有的IP地址与4字节的边界对齐。这不是必须的,但并不用额外的空间(IP选项总是填充成4字节的倍数),而且对齐了地址。

在本图中,我们展示了路径上的10个IP地址,但是所列的第一个地址被从源路径选项中挪出,在IP数据报离开源主机时作为它的目的地址。虽然在40字节的IP选项空间中只有9个IP地址的位置(别忘记我们刚要讨论的3个字节选项头部),但是加上目的地址(不是指源路径的最终目的地址,而是按源路径转发的各个IP数据报的目的地址字段地址),IPv4头部中实际上有10个IP地址。

code对LSRR为0x83,对SSRR为0x89。len用于说明选项的字节长度,包括3字节头部和尾部额外的目的地址。对包括1个IP地址的路径,len为11,2个IP地址的路径,len为15,依此类推,len最大为43.NOP不是选项的一部分,所以不包括在len字段,但是却包括在给setsockopt指定的缓冲区的大小中。当源路径选项的第一个地址挪走并被置入IP头部的目的地址字段后,这个len值将减4。ptr是一个指向路径中下一个要处理的IP地址的指针或偏移量,其初始值为4,即指向第一个IP地址,该字段的值每经过一个所列节点将加4。

现在我们开发三个函数以初始化,创建和处理一个源路径选项。我们的函数只处理一个源路径选项,虽然有可能将源路径和其他IP选项(例如timestamp)结合起来,但是除了两个源路径选项之外,其他选项很少使用。图24.2 是第一个函数inet_srcrt_int以及构造选项要用到的一些静态变量。

#include "unp.h"
#include <netinet/in_systm.h>
#include <netinet/ip.h>
static u_char * optr;  /* pointer into options being formed */
static u_char * lenptr;  /* pointer to length byte in SRR option */
static int ocnt;  /* count of # addresses */
u_char * inet_srcrt_init(void)
{/*分配一个最大为44字节的缓冲区并且将其初置为0,EOL选项的值为0,所以这将整个选项初始化为EOL字节。指向选项的指针返回给调用者以便作为第四个参数传给setsockopt */
  optr = Malloc(44); /* NOP, code, len, ptr, up to 10 addresses */
  bzero(optr, 44); /* guarantees EOLs at end */
  ocnt = 0;
  return(optr);  /* pointer for setsockopt() */
}

下一个函数inet_srcrt_add,把一个IPv4地址加到正在构造的源路径上

int inet_srcrt_add(char * hostptr, int type)
{/*第一个参数指向一个主机名或者点分十进制数形式的IP地址,第二个参数对宽松源路径为0,对严格源路径为非0.我们将看到,加到路径中的第一个地址的类型决定路径是松散的还是严格的 */
  int  len;
  struct addinfo * ai;
  struct sockaddr_in * sin;
  /*我们检查没有指定太多的地址,如果是第一个地址则进行初始化。我们已经提到过,源路径选项前总是放一个NOP。我们保存一个指向len字段的指针,当每个地址加入列表时,存入这个长度值 */
  if(ocnt > 9)
    err_quit("too many source routes with: %s", hostptr);
  if(ocnt == 0)
  {
    *optr++ = IPOPT_NOP;  /* NOP for alignment */
    *optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;
    lenptr = optr++; /* we fill in the length later */
    *optr++ = 4;  /* offset to first address */
  }
  /*我们的host_serv函数处理主机名或者点分十进制数串,并将最终的二进制地址存入列表。修改len字段的值,并返回缓冲区的大小(包括NOP)以便调用者将其传给setsockopt */
  ai = Host_serv(hostptr, " ", AF_INET, 0);
  sin = (struct sockaddr_in *) ai->ai_addr;
  memcpy(optr, &sin->sin_addr, sizeof(sturct in_addr));
  freeaddrinfo(ai);
  optr += sizeof(struct in_addr);
  ocnt++;
  len = 3 + ( ocnt * sizeof(struct in_addr) );
  * lenptr = len;
  return(len+1);  /* size for setsockopt() */
}

当一个收到的源路径通过getsockopt返回给应用进程时,其格式和图24.1不同,图24.4展示了这一格式

 首先,地址的顺序是收到的源路径被内核颠倒后的顺序。“颠倒”是值如果收到的源路由包括四个地址ABCD,则颠倒的顺序为D,C,B,A,前4字节是列表中的第一个IP地址,后面是1字节的NOP(为了对齐),再后面是3个字节源路径选项头部,最后跟以其余的IP地址。最多有9个IP地址可以跟在3字节的头后,返回的len域最大值为39.因为NOP总是出现的,所以getsockopt返回的长度总是4的倍数。

图24.4所示的格式在<netinet/ip_var.h>中定义为下述结构:

#define MAX_IPOPTLEN 40
struct ipoption
{
  struct in_addr ipopt_dst;  /* first_hop dst if source routed */
  char ipopt_list[MAX_IPOPTLEN]; /* Options proper */
};

这个返回的格式与我们传递给setsockopt的格式是不同的。如果我们想把图24.4中的格式转化为图24.1的格式,我们必须将头4个字节和随后的4个字节对换,再给len字段加4。所幸的是我们不必这样做,因为源自Berkeley的实现自动地使用在TCP套接口收到的源路径的倒序值。换句话说,图24.4展示的信息是getsockopt返回的纯粹信息,我们不必调用setsockopt高速内核使用该路径传送该TCP连接上的IP数据报,内核会自动这么做。我们很快就会在我们的TCP服务器程序例子中看到这一点。

下一个源路径函数是取得收到的图24.4格式的源路径,并输出该信息。图24.5给出的是我们的这个函数inet_srcrt_print

void inet_srcrt_print(u_char * ptr, int len)
{
  u_char c;
  char str[INET_ADDRSTRLEN];
  struct in_addr hop1;
  memcpy(&hop1, ptr, sizeof(struct in_addr)); /*将第1个IP地址存放在缓冲区,跳过任何后面的NOP */ 
  ptr += sizeof(struct in_addr);
  while( (c = * ptr++) == IPOPT_NOP );  /* skip any leading NOPs */
  if( c == IPOPT_LSRR) /*我们只输出源路径信息,从3字节头部中,我们检查code,取出len,并跳过ptr。然后我们输出3字节头部后的所有IP地址,最终的目的地址除外。 */
    printf("received LSRR:");
  else if (c == IPOPT_SSRR)
           printf("received SSRR:");
         else 
         {
           printf("received option type %d\n", c);
           return;  
         }
  printf("%s", inet_htop(AF_INET, &hop1, str, sizeof(str)));
  len = * ptr++ - sizeof(struct in_addr); /* subtract dest ip address */
  ptr++;  /* skip over pointer */
  while(len > 0)
  {
    printf("%s", inet_ntop(AF_INET, ptr, str, sizeof(str)));
    ptr += sizeof(struct in_addr);
    len -= sizeof(struct in_addr);
  }
  printf("\n");
}

现在我们修改TCP回射客户程序以说明源路径,修改TCP回射服务器程序以输出收到的源路径。图24.6是我们的客户程序。

#include "unp.h"
int main(int argc, char * * argv)
{
  int  c, sockfd, len = 0;
  u_char * ptr;
  struct addrinfo * ai;
  if( argc < 2 ) /*我们调用inet_srcrt_init函数初始化源路径,路径的每跳由-g选项(宽松)或-G选项(严格)指定。第1个IP地址的类型说明了源路径的类型。inet_srcrt_add函数将每个地址加到路径中。 */
    err_quit("usage: tcpcli01 [ -[gG] <hostname> ... ] <hostname>");
  ptr = inet_srcrt_init();
  opterr = 0; /* dont want getopt() writing to stderr */
  while( ( c = getopt(argc, argv, "g: G:")) != -1 )
  {
    swith(c)
    {
      case 'g':  /* loose soure route */
        len = inet_srcrt_add(optarg, 0);
        break;
      case 'G': /* strict source route */
        len = inet_srcrt_add(optarg, 1);
        break;
      case '?':
        err_quit("unrecognized optin: %c", c);
    }
  }
  if ( optind ! = argc - 1) /*最后一个命令行参数是主机名或者服务器的点分十进制数地址,由函数host_serv处理。我们不能够调用tcp_connect函数,因为在socket和connect调用之间必须指定源路径。connect启动了三路握手,但我们想要最初的SYN和后续的分组都是用这个源路径 */
    err_quit("missing <hostname>");
  ai = Host_serv(argv[optind], SERV_PORT_STR, AF_INET, SOCK_STREAM);
  sockfd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  if(len>0) /*如果指定一个源路径,我们必须将服务器的IP地址加到IP地址列表的末尾,setsockopt给套接口安装源路径,然后我们调用connect,紧接着是str_cli函数。 */
  {
    len = inet_srcrt_add(argv[optind], 0); /* des at end */
    Setsockopt(sockfd, IPPROTO_IP, IP_OPTIONS, ptr, len);
    free(ptr);
  }
  Connect(sockfd, ai->ai_addr, ai->ai_addrlen);
  str_cli(stdin, sockfd);  /* do it all */
  exit(0);
}

我们的TCP服务器程序几乎和图5.12给出的代码相同,只有以下几点变化。第一我们给选项分配空间:

int len;
u_char * opts;
opts = Malloc(44);

然后在调用accept之后,fork之前获取IP选项:

len = 44;
Getsockopt(connfd, IPPROTO_IP, IP_OPTIONS, opts, &len);
if (len > 0)
{
  printf("received IP options, len = %d\n", len);
  inet_srcrt_print(opts, len);
}

如果从客户收到的SYN未包括任何IP选项,从getsockopt返回的len变量将0(len是一个值-结果参数)。我们早先提到,我们不必做任何事情使TCP使用收到的源路径的倒序值,这是TCP自动做的。我们调用setsockopt所做的全部工作就是取得一份收到的源路径的拷贝。如果我们不想让TCP使用这条路径,我们可以在accept返回之后调用setsockopt,将第五个参数置为0,这将取消正在使用的IP选项。TCP在三路握手的第二个参数已经使用了源路径,但如果我们取消了该选项,IP将给送往客户的以后的分组使用计算出的任何路径。

 

24.4. IPv6扩展头部

1. 步跳(hop_by_hop)选项必须紧跟40字节的IPv6头部。目前没有定义这种可供应用程序使用的选项。

2. 目的(destination)选项:目前没有定义这种可供应用程序使用的选项。

3. 路由头部(routing header):这是一个源路由选项,在概念上类似于我们在24.3节中描述的IPv4源路径选项。

4. 分片头部(fragmentation hearder):该头部由将IPv6数据报分片的主机自动生成,有最终的目的主机重组片段时处理。

5. 认证头部(authentication header, AH):该头部的用法由RFC 1826[Atkinson 1995a]和[Kent and Atkinson 1997a]说明。

6. 封装安全有效负载(encapsulating security payload, ESP)头部:该头部的用法在RFC 1827[Atkinson 1995b]和[Kent and Atkinson 1997b]中说明。

我们说过分片头部全部由内核处理,[McDonald 1997]建议用套接口选项处理AH和ESP头部。这样只剩下了前三个选项,我们将在下两节中讨论。

 

24.5. IPv6步跳选项和目的选项

 

24.6. IPv6路由头部

 

24.7. IPv6粘附选项

 

 

24.8. 小结

posted on 2012-04-12 16:31  s7vens  阅读(1067)  评论(0编辑  收藏  举报