基于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这个模块没有初始化,既然知道原因了,那就简单了,只要到原文件中重新移植一份过来就行。以下是我一直过来的相关代码:
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不相等,于是直接
/* XXX */return (0);}
这个函数的其中一个作用是:如果穿进来的这个IP是linklocal类型的地址,那么它会取出ifp中的ifindex并把这个ifindex赋值给地址的2、3字节,
(1)
,然后在sa6_recoverscope函数里注释掉通过scope去查ifp这个步骤的代码,这个方法我没仔细的往下面去调,我用的是第二种方法,
(2)在in6_setscope中设置ifindex的时候我直接采用4个字节去代替2个字节这种做法,如下
-= scope这个操作,为什么要这样做呢?因为现在的ip6里藏有ifindex这4个字节,因此要把它移除掉,因为getscope现在返回的是4个字节,原先我的做法是
看到这里,想必你也明白了为什么。因此要解决cksum的问题,那只需要以2个字节为单位来操作,在减去scope的时候,scope高2字节跟低2字节分开来操作就好了。
(3)继续说下第三个问题,这个问题描述是在当时ping通了以后,连续ping了4-5个,就会在定时器那里挂掉了,还是空指针,这表明定时器在注册的时候信息不全,这个问题只要在uinet_kern_rwlock.c中加入一下代码即可:
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通也就基本没问题,不得不说打字真累!!!
相关文章推荐
- Linux 自检和 SystemTap
- 喜欢 Netflix 么?你应该感谢 FreeBSD
- Python 七步捉虫法
- freebsd 安装rzsz出错处理
- freebsd 下程序随系统开机启动的一种方法(GNU)
- FreeBSD下解决Nagios不发报警邮件的问题
- FREEBSD安装POSTGRESQL笔记
- 路由器的配置与调试
- FreeBSD 6.2 安装全程图解教程
- 对于技术人员的出现了运行时间错误,是否要进行调试的解决方法
- 讲解WordPress开发中一些常用的debug技巧
- JavaScript程序设计之JS调试
- 可以用来调试JavaScript错误的解决方案
- 如何调试异步加载页面里包含的js文件
- jQuery下的Ajax调试步骤
- freebsd网卡安装与dhcp配置方法
- 调试一段PHP程序时遇到的三个问题
- JavaScript高级程序设计 错误处理与调试学习笔记
- Javascript调试脚本的经验之谈第1/2页
- 在IE,Firefox,Safari,Chrome,Opera浏览器上调试javascript