linux内核学习笔记------ip选项处理(二)

来源:互联网 发布:虫虫大作战刷气球软件 编辑:程序博客网 时间:2024/05/17 22:13

在以前的笔记中讲过ip数据报的处理,里面提到过ip_rcv_finish这个函数,这个函数会调用ip_rcv_options来解析并处理iP首部中的ip选项。

if (iph->ihl > 5 && ip_rcv_options(skb))goto drop;
在ip_rcv_finish中会判断ip首部长度是否大于5,只有首部长度大于20的情况下才会有ip选项,并调用ip_rcv_options处理;

static inline int ip_rcv_options(struct sk_buff *skb){......if (skb_cow(skb, skb_headroom(skb))) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);goto drop;}opt = &(IPCB(skb)->opt);opt->optlen = iph->ihl*4 - sizeof(struct iphdr);if (ip_options_compile(dev_net(dev), opt, skb)) {IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);goto drop;}if (unlikely(opt->srr)) {struct in_device *in_dev = in_dev_get(dev);if (in_dev) {if (!IN_DEV_SOURCE_ROUTE(in_dev)) {if (IN_DEV_LOG_MARTIANS(in_dev) &&    net_ratelimit())printk(KERN_INFO "source route option %pI4 -> %pI4\n",       &iph->saddr, &iph->daddr);in_dev_put(in_dev);goto drop;}in_dev_put(in_dev);}if (ip_options_rcv_srr(skb))goto drop;}return 0;drop:return -1;}
首先会确保数据报足够的头部空间

紧接着会获取skb中ip选项,以及ip选项的长度

接着调用ip_options_compile()解析skb的IP首部中IP选项到skb的cb中,IP层的私有数据cb为一个IP选项信息块。

如果存在源路由选项,并且系统允许接收带源路由选项的数据包就会调用ip_options_rcv_srr处理,否则就会丢弃该报文;


下面来看下ip_options_compile是怎么解析ip选项报文的

 ip_options_compile()会被两个函数调用:ip_options_get_finish()和 ip_rcv_options(),分别对应了发送和接收两个方向。注意: 在发送时的调用方式是ip_option_compile(opt,NULL),而在接收 时其调用语句是ip_option_compile(NULL,skb),这是因为发送 和接收时,待解析的IP选项以及解析后的IP选项信息块 * 所存储的位置是不同的--发送时IP选项时存储在参数opt 的__data字段起始的区域中,解析得到的信息会保存在opt中;接收时IP选项存储在参数skb中指向的IP首部中, 解析得到的信息则保存在SKB的cb中。因此,opt和skb两者不能同时为NULL

int ip_options_compile(struct net *net,       struct ip_options * opt, struct sk_buff * skb){......if (skb != NULL) {rt = skb_rtable(skb);optptr = (unsigned char *)&(ip_hdr(skb)[1]);} elseoptptr = opt->__data;......for (l = opt->optlen; l > 0; ) {switch (*optptr) {      case IPOPT_END:for (optptr++, l--; l>0; optptr++, l--) {if (*optptr != IPOPT_END) {*optptr = IPOPT_END;opt->is_changed = 1;}}goto eol;      case IPOPT_NOOP:l--;optptr++;continue;}switch (*optptr) {      case IPOPT_SSRR:      case IPOPT_LSRR:/* * 校验待处理源路由选项的 * 长度是否有效。 */if (optlen < 3) {pp_ptr = optptr + 1;goto error;}/* * 校验待处理源路由选项的指针值 * 是否有效 */if (optptr[2] < 4) {pp_ptr = optptr + 2;goto error;}/* NB: cf RFC-1812 5.2.4.1 *//* * IP选项信息块opt中源路由选项 * 若已处理过,则无需再处理。 */if (opt->srr) {pp_ptr = optptr;goto error;}/* * 显然这是针对发送的,先再次校验选项中 * 的指针及长度的有效性:对于选项指针值其 * 最小值为4,对于选项长度值,除了选项 * 类型、选项长度以及选项指针的三字节外, * 至少应该可以容纳一个IP地址,且扣除了 * 前面的三字节4字节对齐。作为发送方, * 应取出第一个地址作为下一跳地址,并在 * 路径列表中多于一个地址时,将剩余的所有 * 地址往前移动一个位置。 */if (!skb) {if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {pp_ptr = optptr + 1;goto error;}memcpy(&opt->faddr, &optptr[3], 4);if (optlen > 7)memmove(&optptr[3], &optptr[7], optlen-7);}/* * 根据选项类型标识是不是严格源路由 * 选项,并记录源路由选项在IP首部中 * 的偏移量。 */opt->is_strictroute = (optptr[0] == IPOPT_SSRR);opt->srr = optptr - iph;break;......}
这里只列出了源路由选项的处理。

在判断skb是否为空的时候就指定这是为发送构建ip选项还是接收ip数据报时处理ip选项。如果skb为空说明是发送,这时是构建ip选项到参数opt的_data中。否则就是解析skb的cb;

紧接着就是一个循环处理ip选项,这个for循环是为接收ip报文解析skb报文中的ip选项进行处理;发送会在eol处退出这个函数。

解析ip选项的时候如果如果检测到选项列表结束符,则将后面所剩余的全部空间都设置为结束符,因为修改了选项内容,从而需要计算校验和,因此设置opt->is_changed为1,然后结束解析返回。如果检测到空操作符,则修改循环变量l,并将指针移动到下一个选项处后,直接跳入下一次循环。

而后面处理源路由选项的时候就需要对照源路由选项的格式看。这样容易理解点。源码中的注释也讲的比较清楚,这里就不罗嗦了。


ip_options_rcv_srr函数检查输入数据报中的源路由信息,并根据源路由选项更新ip数据报的下一跳地址

int ip_options_rcv_srr(struct sk_buff *skb){......       struct ip_options *opt = &(IPCB(skb)->opt);unsigned char *optptr = skb_network_header(skb) + opt->srr;struct rtable *rt = skb_rtable(skb);......if (!opt->srr)return 0;......for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {/* * 校验源路由选项的路径列表是否还能至少容纳 * 下一个IP地址值,如果不能,则给发送方发送 * 一个参数问题ICMP差错报文。 */if (srrptr + 3 > srrspace) {icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));return -EINVAL;}/* * 通过输入路由方式来判断是否抵达源路由选项中 * 的某一站,一旦确定本地为源路由选项中的某一 * 站,则获取下一跳的IP地址作为IP数据包的目的地址, * 并设置is_changed,表示该IP数据包作了修改。 */memcpy(&nexthop, &optptr[srrptr-1], 4);rt = skb_rtable(skb);skb_dst_set(skb, NULL);err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev);rt2 = skb_rtable(skb);if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {ip_rt_put(rt2);skb_dst_set(skb, &rt->u.dst);return -EINVAL;}ip_rt_put(rt);if (rt2->rt_type != RTN_LOCAL)break;/* Superfast 8) loopback forward */memcpy(&iph->daddr, &optptr[srrptr-1], 4);opt->is_changed = 1;}/* * 如果源路由选项的路径列表没有遍历完,则 * 说明该IP数据包的目的地址是从源路由选项 * 选出的,因此需设置srr_is_hit标志,待转发时 * 需要进一步处理。同时还需要设置is_changed * 标志,标识需重新计算IP数据包的首部校验和。 */if (srrptr <= srrspace) {opt->srr_is_hit = 1;opt->is_changed = 1;}}
首先ip_options_rcv_srr会进行一些有效性的检查,最后会通过一个for循环来查找源路由选项,如果找到了。就根据找到的地址信息去路由表和路由缓存信息中查找,如果查找到就设置下一跳目的地址为源路由地址

udp和raw套接口都是通过控制信息来生成ip选项。通过ip_options_get函数完成这个功能,最后还是调用ip_options_compile来处理。



0 0
原创粉丝点击