基于libuinet的IPv6调试

来源:互联网 发布:suse防火墙开放端口 编辑:程序博客网 时间:2024/06/06 19:50

进来由于工作上的需要,在IPv4之后,要加入IPv6的功能,但是让人蛋疼的是libuinet开源代码中,原作者根本就没有测试IPv6的相关功能,于是蛋疼的花了2个星期,看源码,定位问题。我在调IPv6的时候,主要遇到了3个问题:

1、在打开IPV6的宏,并修改一些编译上的问题时,等编过了之后,开始执行,进程就会一直挂在一个锁上,原因是这个锁是为NULL。(具体代码在哪行已经记不清了,也怪我没有记录问题的习惯,不过跟jail和prison有关,源文件是uinet_kern_jail.c,这个文件是由原作者在kern_jail.c的基础上移植出来的。)这就给了我一个信号,这把锁没有初始化或者说是prison这个模块没有初始化,既然知道原因了,那就简单了,只要到原文件中重新移植一份过来就行。以下是我一直过来的相关代码:

uinet_kern_jail.c:
#defineDEFAULT_HOSTUUID"00000000-0000-0000-0000-000000000000"/* Keep struct prison prison0 and some code in kern_jail_set() readable. */#ifdef INET#ifdef INET6#define_PR_IP_SADDRSELPR_IP4_SADDRSEL|PR_IP6_SADDRSEL#else#define_PR_IP_SADDRSELPR_IP4_SADDRSEL#endif#else /* !INET */#ifdef INET6#define_PR_IP_SADDRSELPR_IP6_SADDRSEL#else#define_PR_IP_SADDRSEL0#endif#endif/* prison0 describes what is "real" about the system. */struct prison prison0 = {.pr_id= 0,.pr_name= "0",.pr_ref= 1,.pr_uref= 1,.pr_path= "/",.pr_securelevel= -1,.pr_devfs_rsnum = 0,.pr_childmax= JAIL_MAX,.pr_hostuuid= DEFAULT_HOSTUUID,.pr_children= LIST_HEAD_INITIALIZER(prison0.pr_children),#ifdef VIMAGE.pr_flags= PR_HOST|PR_VNET|_PR_IP_SADDRSEL,#else.pr_flags= PR_HOST|_PR_IP_SADDRSEL,#endif.pr_allow= PR_ALLOW_ALL,};MTX_SYSINIT(prison0, &prison0.pr_mtx, "jail mutex", MTX_SPIN)
有了上面这段代码去初始化jail mutex这个模块,这个问题也就不在是问题了。

2、既然运行没有问题了,也不会挂了,那就应该创建一个IPv6的接口让他能ping通,创建IPv6的接口代码可以参照IPv4去写,这一块不难。在我创建了一个IPv6接口之后,因为接口会有ifindex,而我所碰到的应用场景是ifindex为32个位,这其实也没啥问题,因为freeBSD中所使用的ifindex也是32位,但是我不知道装有freeBSD的机子在给它配IPv6的接口时,如果给的ifindex大小大于2个字节,也就是65535,这个ipv6接口是不是还能正常ping通?装有freeBSD机子的同学可以试验一下。我为什么要这么说呢,因为在我给我的接口指定了70001这个ifindex时接口倒是创建成功了,但是在进行DAD检测的时候硬是没有把包发出来,既然如此只能一步一步的去定位了,最后发现数据包在ip6_output这个函数的707行,原因是zone跟dst_sa.sin6_scope_id不相等,于是直接 

goto badscope;
既然知道是在这里出的问题,那就继续深入下去查,<pre name="code" class="objc">/* * generate standard sockaddr_in6 from embedded form. */intsa6_recoverscope(struct sockaddr_in6 *sin6){char ip6buf[INET6_ADDRSTRLEN];u_int32_t zoneid;if (sin6->sin6_scope_id != 0) {log(LOG_NOTICE,    "sa6_recoverscope: assumption failure (non 0 ID): %s%%%d\n",    ip6_sprintf(ip6buf, &sin6->sin6_addr), sin6->sin6_scope_id);/* XXX: proceed anyway... */}if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr) ||    IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr)) {/* * KAME assumption: link id == interface id */zoneid = ntohs(sin6->sin6_addr.s6_addr16[1]);if (zoneid) {/* sanity check */if (zoneid < 0 || V_if_index < zoneid)return (ENXIO);if (!ifnet_byindex(zoneid))return (ENXIO);sin6->sin6_addr.s6_addr16[1] = 0;sin6->sin6_scope_id = zoneid;}}return 0;}

这个函数的主要作用是根据ipv6地址中的<span style="font-family: Arial, Helvetica, sans-serif;">s6_addr16[1]这二个字节来获取接口的ifindex,然后通过这个ifindex来找到ifnet这个结构体,但是很遗憾,肯定是找不到的,因为这里只使用了二个字节,而我们的ifindex时4个字节,这也是为什么我怀疑装有freeBSD的机子其ifindex如果大于65535这个数值,这个接口还能不能好使的理由所在。</span>
这里还得说下另一个函数,有get那么就八九不离十的有set函数
/* * Determine the appropriate scope zone ID for in6 and ifp.  If ret_id is * non NULL, it is set to the zone ID.  If the zone ID needs to be embedded * in the in6_addr structure, in6 will be modified. * * ret_id - unnecessary? */intin6_setscope(struct in6_addr *in6, struct ifnet *ifp, u_int32_t *ret_id){int scope;u_int32_t zoneid = 0;struct scope6_id *sid;IF_AFDATA_LOCK(ifp);sid = SID(ifp);#ifdef DIAGNOSTICif (sid == NULL) { /* should not happen */panic("in6_setscope: scope array is NULL");/* NOTREACHED */}#endif/* * special case: the loopback address can only belong to a loopback * interface. */if (IN6_IS_ADDR_LOOPBACK(in6)) {if (!(ifp->if_flags & IFF_LOOPBACK)) {IF_AFDATA_UNLOCK(ifp);return (EINVAL);} else {if (ret_id != NULL)*ret_id = 0; /* there's no ambiguity */IF_AFDATA_UNLOCK(ifp);return (0);}}scope = in6_addrscope(in6);SCOPE6_LOCK();switch (scope) {case IPV6_ADDR_SCOPE_INTFACELOCAL: /* should be interface index */zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL];break;case IPV6_ADDR_SCOPE_LINKLOCAL:zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL];break;case IPV6_ADDR_SCOPE_SITELOCAL:zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_SITELOCAL];break;case IPV6_ADDR_SCOPE_ORGLOCAL:zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL];break;default:zoneid = 0;/* XXX: treat as global. */break;}SCOPE6_UNLOCK();IF_AFDATA_UNLOCK(ifp);if (ret_id != NULL)*ret_id = zoneid;if (IN6_IS_SCOPE_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6))<pre name="code" class="objc">in6->s6_addr16[1] = htons(zoneid & 0xffff);

/* XXX */return (0);}
这个函数的其中一个作用是:如果穿进来的这个IP是linklocal类型的地址,那么它会取出ifp中的ifindex并把这个ifindex赋值给地址的2、3字节,
in6->s6_addr16[1] = htons(zoneid & 0xffff);
注意这里只取了ifindex的后2个字节,因此70001这个ifindex就会被砍掉2个字节,现在终于明白了为什么在sa6_recoverscope的时候出现异常了,因为v6地址中所保存的这个scope根本就不全,那怎么可能会获取出正确的ifp呢。这个问题我感觉应该有二种解决方案:

(1),然后在sa6_recoverscope函数里注释掉通过scope去查ifp这个步骤的代码,这个方法我没仔细的往下面去调,我用的是第二种方法,

(2)在in6_setscope中设置ifindex的时候我直接采用4个字节去代替2个字节这种做法,如下

in6->s6_addr32[1] = htonl(zoneid);
因此只要一同修改跟scope扯上关系的所有相关代码就行了,到了这里ipv6的linklocal地址在插入scope跟去除scope的时候就能满足要求了,在这里你如果给这个接口配IP并且给它指定ifindex,在另一台机子上抓包的时候,额竟然ICMPv6 cksum bad,好吧,解决一个问题又引入了另一个问题,那就继续去查in6_cksum这个函数了,在分析这个函数之前,要先交代一下在IPv6的地址里加ifindex,在发出去之前是会调用in6_clearscope这个函数把这四个字节又从IPv6地址中移除。有了这个概念,那就进入到in6_cksum这个函数里去看看:
ip6 = mtod(m, struct ip6_hdr *);/* IPv6 source address. */scope = in6_getscope(&ip6->ip6_src);/*!< 通过getscope可知,只有linklocal或者intfacelocal才能获得scope */w = (u_int16_t *)&ip6->ip6_src;sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];if (scope != 0)sum -= scope;/*!< 因为ipv6中的scope字段是要清除掉的,因此在计算sum的时候就要事先去除 *//* IPv6 destination address. */scope = in6_getscope(&ip6->ip6_dst);w = (u_int16_t *)&ip6->ip6_dst;sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];if (scope != 0)sum -= scope;
这里有一个getscope函数,还有一个sum -= scope这个操作,为什么要这样做呢?因为现在的ip6里藏有ifindex这4个字节,因此要把它移除掉,因为getscope现在返回的是4个字节,原先我的做法是
w0 = (u_int32_t *)&ip6->ip6_src;
直接以32位为单位进行操作,结果悲催的发现只要scope不为0的在对端cksum的时候都是bad。好吧只能继续绞尽脑汁想原因了,在这里就不继续关外抹角了,直接说原因:

看到这里,想必你也明白了为什么。因此要解决cksum的问题,那只需要以2个字节为单位来操作,在减去scope的时候,scope高2字节跟低2字节分开来操作就好了。
sum -= ((scope & 0xffff) + (scope >> 16));
只需要把这条语句替换原先那二条就行了,这样ifindex也就不在是问题了。
(3)继续说下第三个问题,这个问题描述是在当时ping通了以后,连续ping了4-5个,就会在定时器那里挂掉了,还是空指针,这表明定时器在注册的时候信息不全,这个问题只要在uinet_kern_rwlock.c中加入一下代码即可:
static voidlock_rw(struct lock_object *lock, int how){struct rwlock *rw;rw = (struct rwlock *)lock;if (how)rw_wlock(rw);elserw_rlock(rw);}static intunlock_rw(struct lock_object *lock){struct rwlock *rw;rw = (struct rwlock *)lock;rw_assert(rw, RA_LOCKED | LA_NOTRECURSED);if (1) {rw_runlock(rw);return (0);} else {rw_wunlock(rw);return (1);}}struct lock_class lock_class_rw = {.lc_name = "rw",.lc_flags = LC_SLEEPLOCK | LC_RECURSABLE | LC_UPGRADABLE,.lc_assert = assert_rw,#ifdef DDB.lc_ddb_show = db_show_rwlock,#endif   <span style="white-space:pre"></span> .lc_lock = lock_rw,.lc_unlock = unlock_rw,#ifdef KDTRACE_HOOKS.lc_owner = owner_rw,#endif};
好了,在解决了上面这三个问题之后,ipv6 ping通也就基本没问题,不得不说打字真累!!!




0 0
原创粉丝点击