您的位置:首页 > 产品设计 > UI/UE

基于libuinet的IPv6调试

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

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

uinet_kern_jail.c:
#define	DEFAULT_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_SADDRSEL	PR_IP4_SADDRSEL|PR_IP6_SADDRSEL
#else
#define	_PR_IP_SADDRSEL	PR_IP4_SADDRSEL
#endif
#else /* !INET */
#ifdef INET6
#define	_PR_IP_SADDRSEL	PR_IP6_SADDRSEL
#else
#define	_PR_IP_SADDRSEL	0
#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.
*/
int
sa6_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?
*/
int
in6_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 DIAGNOSTIC
if (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 void
lock_rw(struct lock_object *lock, int how)
{
struct rwlock *rw;

rw = (struct rwlock *)lock;
if (how)
rw_wlock(rw);
else
rw_rlock(rw);
}

static int
unlock_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通也就基本没问题,不得不说打字真累!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息