网络编程学习——IP选项

1 概述

  IPv4允许在20字节首部固定部分之后跟以最多共40个字节的选项。尽管已经定义的的IPv4选项共有10种,最常见的却是源路径选项。这些选项的访问途径是存取IP_OPTIONS套接字选项,我们将以一个使用源路由的例子展示这个访问方式。

  IPv6允许在固定长度的40个字节IPv6首部和传输层首部(例如ICMPv6、TCP或UDP)之间出现扩展首部(extension header)。目前定义了6种不同的扩展首部。不同于IPv6的是,IPv6扩展首部的访问途径是函数接口,而不是强求用户理解这些首部如何呈现在IPv6分组中的真是细节。

 

2 Ipv4选项

  我们在图1-1展示出IPv4的选项(options)字段跟在20字节IPv4首部固定部分之后。

  IP层提供无连接不可靠的数据报递送服务(RFC 791).它会尽最大努力把IP数据报递送到指定的目的地,然而并不保证它们一定到达,也不保证它们的到达顺序与发送顺序一致,还不保证每个IP数据报只到达一次。任何期望的可靠性(即无差错按顺序不重复地递送用户数据)必须由上层提供支持。对于TCP(或SCTP)应用程序而言,这由TCP(或SCTP)本身完成。对于UDP应用程序而言,这得由应用程序完成,因为UDP是不可靠的。

  IP层最重要的功能之一是路由(routing)。每个IP数据报包含一个源地址和一个目的地址。每个IP数据报包含一个源地址和一个目的地址。图1-1展示了IPv4数据报首部的格式。

202728_ETn5_2537915.jpg

图1-1 IPv4首部格式

  • 4位版本(version)字段值为4.这是自20世纪80世纪早期以来一直在使用的IP版本。

  • 首部长度(header length)字段是包括任何选项在内的整个IP首部的32位字长度。这个4位字段的最大取值为15,因而IP首部的最大长度为60字节。扣除首部固定部分所占据的20字节外,它最多允许40个字节的选项。

  • 历史性的8位服务类型(type-of-server,TOS)字段(RFC 1349)已被替换为两个字段:6位区分服务码点(Differentiated Services Code Point,DSCP,RFC 2474)和2位显式拥塞通知(Explicit Congestion Notification,ECN,RFC3168).我们可以使用IP_TOS套接字选项设置该字段,虽然内盒可能覆盖为了实施Diffserv策略或实现ECN而设置的值。

  • 16位总长度(total length)字段是包括IPv4首部在内的整个IP数据报的字节长度。数据报中的数据量就是本字段减掉4乘以首部长度(首部长度都是32位或4字节的整数倍)。本字段是必需的,因为有些数据链路要求把帧垫补成某个最小长度(例如以太网),因而有效IP数据报的大小有可能小于数据链路的最小长度。

  • 16位标志(identification)字段由IP模块为每个IP数据报设置成不同的值,用于分片和重组。该字段必须就源IPv4地址、目的IPv4地址和协议这三个字段至少在数据报的网络存活期唯一标识每个IP数据报。如果分组不会被分片(但如设置了DF位),那么就不需要设置此字段。

  • DF(表示don't fragment,不要分片)位、MF(表示more fragment,还有片段)位和13位片段偏移(fragment offset)字段也用于分片和重组。DF位还用于路径MTU发现。

  • 8位存活时间(time-to-live,TTL)字段由本IP数据报的发送者设置,并由转发它的每个路由器递减(即减去1).当被减到0时,相应路由器就丢弃该数据报。任何IP数据报的生命期限定为最多255跳。本字段的常用默认值为64,不过我们可以使用套接字选项IP_TTL和IP_MULTICAST_TTL查询和修改这个默认值。

  • 8位协议(protocol)字段指定包含在本IP数据报中的数据类型。它的典型值有(ICMPv4)、2(IGMPv4)、6(TCP)和17(UDP)。这些值由IANA的“Protocol Numbers”注册处登记并提供查询。

  • 16位首部检验和(header checksum)字段只对IP首部(包括任何选项)进行计算。其算法是标准的网际校验和算法,即简单的16位反码加法(16-bit ones-complement addition)。

  • 源IPv4地址(source IPv4 address)和目的IPv4地址(destination IPv4 address)都是32位字段。

  • 选项(options)字段。

  IPv4定义了10种不同的选项。

  1. NOP:no-operation。单字节选项,典型的用途是为后续选项落在4字节边界上提供填充。

  2. EOL:end-of-list。单字节选项,终止选项的处理。既然各个IP选项的总长度必须为4字节的倍数,因此最后一个有效选项之后可能跟以0~3个EOL字节。

  3. LSRR:loose source and record route。

  4. SSRR:strict source and record route。

  5. Timestamp。

  6. Record route。

  7. Basic security(已作废)。

  8. Extended security(已作废)。

  9. Stream identifier(已作废)。

  10. Router alert。包含该选项的IP数据报要求所有转发路由器都查看其内容。

3 Ipv4源路径选项

  源路径(source route)是由IP数据报的发送者指定的一个IP地址列表。如果源路径是严格的(strict),那么数据报必须且只能逐一经过所列的节点。也就是说列在源路径中的所有节点必须前后互为邻居。如果源路径是宽松的(loose),那么数据报必须逐一经过所列的节点,不过也可以经过未列在源路径中的其他节点。

  IPv4源路径成为源和记录路径(source and record routes,SRR,其中LSRR表示宽松的选项,SSRR表示严格的选项),因为随着数据报逐一经过所列的节点,每个节点都把列在源路径中的自己的地址替换为外出接口的地址。SRR允许接收者逆转新的列表的顺序,得到沿反方向回到发送者的路径。

  我们把源路径指定为一个IPv4地址数组,并冠以3个单字节字段,如图1-2所示。

134301_bJPS_2537915.jpg

图1-2 向内核传递的源路径

  我们在源路径选项之前放置一个NOP选项,使得所有IP地址在各自的4字节边界对齐。这么做并非必须,不过无须占用额外空间(IP选项总是填充为4字节的倍数),还对齐了地址。

  code字段对于LSRR为0x83,对于SSRR为0x89。len字段用于指定选项的字节长度,包括3字节选项首部和处于末尾的额外的最终目的地址(该地址不属于源路径)。对于由1个IP地址构成的源路径len为11,对于由2个IP地址构成的源路径len为15,以此类推,直到9个IP地址构成的源路径len为最大值43.NOP不属于本SSR选项(它自成一个单字节IP选项),因而不包括在len字段的涵盖范围之内,不过包括在给setsockopt指定的缓冲区大小之中。当源路径地址列表中的第一个地址被移走并置于IP首部的目的地址字段时,这个len值被减去4。ptr是一个指针,也就是路径中下一个待处理IP地址的偏移量,初始值为4,表示指向第一个IP地址。该字段的值随着IP数据报被每个所列节点处理而逐次加上4。

  我们现在开发3个函数,分别初始化、创建和处理一个源路径选项。这个函数只是处理源路径IP选项。尽管源路径结合其他IP选项(例如路由器警告)也是可能的,但这样的组合很少使用。

  第一个函数inet_strct_init以及用于构造选项内容的一些静态变量。

#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

uchar * inet_srcrt_init( int type )
{
  // 分配一个最大长度(44字节)的缓冲区并将它清零。EOL选项的值为0,因此清零操作把整个选项缓冲区初始化为EOL
  // 字节。接着按照图1-2设置源路径选项首部,包括用于对齐的NOP、源路径类型(LSRR或SSRR)、长度和指针。保存
  // 指向len字段的一个指针,以后每往地址列表中加入一个地址,就在该字段中存入新值。把指向选项缓冲区的指针返
  // 回给调用者,以便作为第四个参数传递给setsockopt。
  optr = malloc( 44 ); // NOP,code.len,up to 10 addresses
  bzero( optr, 44 ); // guaratees EOLs at end
  ocnt = 0;
  *optr++ = IPOPT_NOP; // NOP for alignment
  *optr++ = type ? IPOPT_SSRR : IPOPT_LSRR;
  lenptr = optr++; // we fill in length later
  *optr++ = 4; // offset to first address
  
  return( optr - 4 ); // pointer for setsockopt()
} 

// 参数指向一个主机名或一个点分十进制数串IP地址
int inet_stcrt_add( char *hostptr )
{
  int len;
  struct addrinfo *ai;
  strcut sockaddr_in *sin;
  
  // 我们检查尚未指定过多的地址,如果这是第一个地址则将其初始化
  if( ocnt > 9 )
    err_quit( " too many source routes whit : %s ", hostptr );
    
  // 调用我们的host_serv函数转换主机或点分十进制数串,并把最终的二进制地址存如路径地址列表。更改len字段的值,
  // 返回缓冲区的总长度(包括NOP),以便调用者把它作为第五个参数传递给setsockopt。
  ai = host_serv( hostptr, NULL, AF_INET, 0 );
  sin = ( strcut sockaddr_in * ) ai->ai_addr;
  memcpy( optr, &sin->sin_sin_addr, sizeof( struct in_addr ) );
  freeaddrinfo( ai );
  
  optr += sizeof( struct in_addr );
  ocnt++;
  len = 3 + ( ocnt * sizeof( struct in_addr ) );
  *lenptr = len;
  return( lem + 1 ); // size of setsockopt()
}

  通过getsockopt调用返回给应用进程的接收源路径格式不同于图1-2所示的发送源路径。图1-3展示了接收格式。

144528_CBGD_2537915.jpg

图1-3 getsockopt返回的源路径选项格式

  首先,地址的顺序是所收取的源路径被内核逆转后的顺序。这里“逆转”指的是如果所收取的源路径按顺序包括A,B,C和D四个地址,该路径的逆转顺序就是D,C,B和A。头4个字节是该列表的第一个IP地址,后跟一个单字节NOP(为了对齐),再跟以3字节源路径选项首部,最后跟以其余的IP地址。3字节选项首部之后最多可以跟以9个地址,所返回首部中len字段相应的最大值为39.NOP始终存在,因此由getsockopt返回的长度于是总为4字节的倍数。

  下一个源路径函数取得图1-3所示格式的一个接收源路径并显示该信息。

void inet_srcrt_print( u_char *ptr, int len )
{
  u_char c;
  char str[ INET_ADDRSTRLEN ];
  strcut in_addr hop1;
  
  // 保存缓冲区中的第一个IP地址,跳过后跟的任何NOP
  memcpy( &hop1, ptr, sizeof( struct in_addr ) );
  ptr += sizeof( strcut in_addr );
  
  while( ( c = *ptr++ ) == IPOPT_NOP ); // skip any leading NOPS
  
  //我们只显示源路径,从3字节首部中,我们检查code,取出len,并跳过ptr。我们接着显示跟在3字节首部之后的
  // 所有IP地址,不过末尾那个目的IP地址除外。
  if( c == OPOPT_LSRR )
    printf( " received LSRR: " );
  else if ( c == IPOPT_SSRR )
    printf( " received SSRR: " );
  else
  {
    printf( " received option type %d\n ", c );
    return;
  }
  printf( " %s ", inet_ntop( AF_INET,&hop1, str, sizeof( str ) ) );
  
  len = *ptr++ - sizeof( struct in_addr ); // subtract dest IP addr
  ptr++; // skipoverpointer
  while( len > 0 )
  {
    printf( " %s ", inet_ntop( AF_INET, ptr, str, sizeof( str ) ) );
    ptr += sizeof( struct in_addr );
    lem -= sizeof( struct in_addr );
  }
  printf( " \n " );
}

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/u/2537915/blog/668290

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值