//选项格式: // 1.type中指示该选项在分片时是否需要被拷贝 // 2.ptr从1算起,1为type的位置 // 3.len不包括type字段,其余都包括(len,ptr,选项内容)
//type字段:
ip选项type字段的常见代码值:
//inet_addr_type(addr)返回l3 addr的路由类型: // 1.RTN_LOCAL 该ip地址属于一个本地接口 // 2.RTN_UNICAST 根据路由表,该ip地址可以抵达,而且是单播地址 // 3.RTN_MULTICAST 该地址是多播地址 // 4.RTN_BROADCAST 该地址是广播地址
//此函数分析ip报文中的如下选项,并设置到skb->cb中 // 1.IPOPT_END 处理办法,使用IPOPT_END覆盖之后出现的所有选项,并标示ip头被更改过 // 2.IPOPT_NOOP 处理办法,跳过 // 3.IPOPT_SSRR IPOPT_LSRR 处理办法,设置opt->ss为选项相对ip头的偏移量,在之后对ip报文的处理上,填充该选项,该选项只能出现一次 // 4.IPOPT_RR 处理办法,拷贝路由缓存中的首选源地址到选项中,更新选项的ptr字段,使其指向下一个空闲位置 // 5.IPOPT_TIMESTAMP 处理办法,分析子选项 // 5.1 IPOPT_TS_TSONLY 只记录时间戳 // 5.2 IPOPT_TS_TSANDADDR 记录时间戳和ip地址 // 5.3 IPOPT_TS_PRESPEC 本机ip等于ptr当前所指的ip时,填入本机时间,否则更新ptr到下一个ip地址 // 6.IPOPT_SEC IPOPT_SID 处理办法,不处理 // 3,4在处理时,如果选项空间不足够,则通过icmp向发送主机报告错误并丢弃封包 // 5在处理时,如果选项空间不足够,则递增溢出次数,如果溢出次数已达15次,则通过icmp项主机发送报告错误并丢弃封包 //调用路径ip_rcv->ip_rcv_finish->ip_options_compile //opt = NULL 1.1 int ip_options_compile(struct ip_options * opt, struct sk_buff * skb) { int l; unsigned char * iph; unsigned char * optptr; int optlen; unsigned char * pp_ptr = NULL; struct rtable *rt = skb ? (struct rtable*)skb->dst : NULL; if (!opt) {//在接收路径上opt=null opt = &(IPCB(skb)->opt);//skb->cb强转成struct ip_options结构 memset(opt, 0, sizeof(struct ip_options)); iph = skb->nh.raw; opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr);//ip选项的长度 = ihl*4 - 20(ip报头长度) optptr = iph + sizeof(struct iphdr);//选项的第一个字节 opt->is_data = 0; } else { optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]); iph = optptr - sizeof(struct iphdr); } for (l = opt->optlen; l > 0; ) { switch (*optptr) { case IPOPT_END://在IPOPT_END之后的所有选项,均会被IPOP_END覆盖 for (optptr++, l--; l>0; optptr++, l--) { if (*optptr != IPOPT_END) { *optptr = IPOPT_END; opt->is_changed = 1;//记录报头被修改 } } goto eol; case IPOPT_NOOP://IPOPT_NOOP用于填补选项之间的空白 l--; optptr++; continue; } //非单字节选项 //1.通过第二个字节optptr[1]指示选项长度 //2.通过第三个字节optptr[2]指示选项内容的指针,起始值为1,表示type字段 optlen = optptr[1]; if (optlen<2 || optlen>l) {//选项的健康性检查,非单字节选项长度至少为2,该选项长度不能超过选项剩余的总长度 pp_ptr = optptr; goto error; } switch (*optptr) { case IPOPT_SSRR://严格源路由选项,发送者列出沿途上的每一台路由器ip地址,并且沿途不能修改 case IPOPT_LSRR://宽松源路由选项,中间路由器可以使用另一台不在列表中的路由器,作为通向列表中下一个路由器的路径,发送者指定的路由器必须按照指定的次序使用 if (optlen < 3) {//健康性检查 pp_ptr = optptr + 1; goto error; } if (optptr[2] < 4) { pp_ptr = optptr + 2; goto error; } if (opt->srr) {//opt->srr记录源路由选项相对于ip头的起始位置;只能有一个该选项 pp_ptr = optptr; goto error; } //输入路径上,skb!=NULL 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); } opt->is_strictroute = (optptr[0] == IPOPT_SSRR);//is_strictroute指示是否为严源路由选项 opt->srr = optptr - iph;//源路由选项的起始位置 break; case IPOPT_RR://record route选项 if (opt->rr) {//opt->rr记录record route选项相对于ip头的偏移量;只能有一个该选项 pp_ptr = optptr; goto error; } if (optlen < 3) { pp_ptr = optptr + 1; goto error; } if (optptr[2] < 4) { pp_ptr = optptr + 2; goto error; } if (optptr[2] <= optlen) {//说明有空闲空间 if (optptr[2]+3 > optlen) {//不足4字节 pp_ptr = optptr + 2; goto error; } if (skb) { memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);//入口路径上skb->dst在处理ip选项之前被初始化 opt->is_changed = 1;//复制rt中的首选源地址,首选源地址在ip_route_input_slow中,根据被路由封包的目的地址被设置 } optptr[2] += 4;//指向下一个可用的空闲位置 opt->rr_needaddr = 1; } opt->rr = optptr - iph; break; case IPOPT_TIMESTAMP://时间戳选项 if (opt->ts) {//opt->ts记录time stamp选项相对于ip头的偏移量;只能有一个该选项 pp_ptr = optptr; goto error; } if (optlen < 4) { pp_ptr = optptr + 1; goto error; } if (optptr[2] < 5) {//IPOPT_TIMESTAMP选项头格式为:[type len ptr (overflow:4 | flag:4)],因此ptr指示位置至少为5,(type默认为1) pp_ptr = optptr + 2; goto error; } if (optptr[2] <= optlen) { __u32 * timeptr = NULL; if (optptr[2]+3 > optptr[1]) { pp_ptr = optptr + 2; goto error; } //处理子选项,flag字段,指示子选项 switch (optptr[3]&0xF) { case IPOPT_TS_TSONLY://记录时间戳 opt->ts = optptr - iph;//opt->ts指示time stamp选项相对于ip头的偏移量 if (skb) timeptr = (__u32*)&optptr[optptr[2]-1];//本机记录time stamp的位置 opt->ts_needtime = 1;//告诉本机,需要记录time stamp optptr[2] += 4;//选项指针移动4个字节,下一个主机记录time stamp的起始位置 break; case IPOPT_TS_TSANDADDR://记录时间戳和地址 if (optptr[2]+7 > optptr[1]) {//不足8字节 pp_ptr = optptr + 2; goto error; } opt->ts = optptr - iph; if (skb) { memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);//首选源地址 timeptr = (__u32*)&optptr[optptr[2]+3];//time stamp填充位置,之后填充 } opt->ts_needaddr = 1;//指示time stamp子选项要求位置和时间 opt->ts_needtime = 1; optptr[2] += 8;//长度更新8字节 break; case IPOPT_TS_PRESPEC://只针对发送者指定的ip地址,记录time stamp选项 if (optptr[2]+7 > optptr[1]) { pp_ptr = optptr + 2; goto error; } opt->ts = optptr - iph; { u32 addr; memcpy(&addr, &optptr[optptr[2]-1], 4);//检查该地址的路由类型 if (inet_addr_type(addr) == RTN_UNICAST)//非本机ip地址,但是该ip可达,而且是单播地址 break; if (skb) timeptr = (__u32*)&optptr[optptr[2]+3]; } opt->ts_needtime = 1; optptr[2] += 8;//如果指定的ip非本机地址,也会掉过该选项位置,后续的主机,从下一个选项位置开始 break; default: if (!skb && !capable(CAP_NET_RAW)) { pp_ptr = optptr + 3; goto error; } break; } if (timeptr) {//填充time stamp的位置 struct timeval tv; __u32 midtime; do_gettimeofday(&tv);//获取系统时间 midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);//在一天内,已经过多少秒 memcpy(timeptr, &midtime, sizeof(__u32)); opt->is_changed = 1;//ip报头被修改,因此需要重新计算校验和 } } else {//time stamp选项,空闲空间不够 unsigned overflow = optptr[3]>>4;//溢出的次数 if (overflow == 15) {//已经达到最大的溢出次数 pp_ptr = optptr + 3; goto error; } opt->ts = optptr - iph; if (skb) { optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);//递增溢出次数 opt->is_changed = 1; } } break; case IPOPT_RA: if (optlen < 4) { pp_ptr = optptr + 1; goto error; } if (optptr[2] == 0 && optptr[3] == 0) opt->router_alert = optptr - iph; break; case IPOPT_SEC://security选项 case IPOPT_SID://stream id选项 default://不处理这两种选项 if (!skb && !capable(CAP_NET_RAW)) { pp_ptr = optptr; goto error; } break; } l -= optlen; optptr += optlen; } eol: if (!pp_ptr) return 0; error: if (skb) {//通过icmp,返回错误 icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24)); } return -EINVAL; }