基于libuinet的IPv6调试

来源:互联网 发布:客服中心数据分析 编辑:程序博客网 时间:2024/05/21 10:10

基于libuinet的IPv6调试

标签: freebsdIPV6调试移植开源代码
269人阅读 评论(0)收藏举报
分类:
作者同类文章X
  • freeBSD的VNET_DEFINE跟SYSCTL_VNET_INT

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

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

[objc] view plain copy
  1. uinet_kern_jail.c:  
[objc] view plain copy
  1. #define DEFAULT_HOSTUUID    "00000000-0000-0000-0000-000000000000"  
  2.   
  3. /* Keep struct prison prison0 and some code in kern_jail_set() readable. */  
  4. #ifdef INET  
  5. #ifdef INET6  
  6. #define _PR_IP_SADDRSEL PR_IP4_SADDRSEL|PR_IP6_SADDRSEL  
  7. #else  
  8. #define _PR_IP_SADDRSEL PR_IP4_SADDRSEL  
  9. #endif  
  10. #else /* !INET */  
  11. #ifdef INET6  
  12. #define _PR_IP_SADDRSEL PR_IP6_SADDRSEL  
  13. #else  
  14. #define _PR_IP_SADDRSEL 0  
  15. #endif  
  16. #endif  
  17.   
  18. /* prison0 describes what is "real" about the system. */  
  19. struct prison prison0 = {  
  20.     .pr_id      = 0,  
  21.     .pr_name    = "0",  
  22.     .pr_ref     = 1,  
  23.     .pr_uref    = 1,  
  24.     .pr_path    = "/",  
  25.     .pr_securelevel = -1,  
  26.     .pr_devfs_rsnum = 0,  
  27.     .pr_childmax    = JAIL_MAX,  
  28.     .pr_hostuuid    = DEFAULT_HOSTUUID,  
  29.     .pr_children    = LIST_HEAD_INITIALIZER(prison0.pr_children),  
  30. #ifdef VIMAGE  
  31.     .pr_flags   = PR_HOST|PR_VNET|_PR_IP_SADDRSEL,  
  32. #else  
  33.     .pr_flags   = PR_HOST|_PR_IP_SADDRSEL,  
  34. #endif  
  35.     .pr_allow   = PR_ALLOW_ALL,  
  36. };  
  37. 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不相等,于是直接 

[objc] view plain copy
  1. goto badscope;  
[objc] view plain copy
  1. 既然知道是在这里出的问题,那就继续深入下去查,<pre name="code" class="objc">/* 
  2.  * generate standard sockaddr_in6 from embedded form. 
  3.  */  
  4. int  
  5. sa6_recoverscope(struct sockaddr_in6 *sin6)  
  6. {  
  7.     char ip6buf[INET6_ADDRSTRLEN];  
  8.     u_int32_t zoneid;  
  9.   
  10.     if (sin6->sin6_scope_id != 0) {  
  11.         log(LOG_NOTICE,  
  12.             "sa6_recoverscope: assumption failure (non 0 ID): %s%%%d\n",  
  13.             ip6_sprintf(ip6buf, &sin6->sin6_addr), sin6->sin6_scope_id);  
  14.         /* XXX: proceed anyway... */  
  15.     }  
  16.     if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr) ||  
  17.         IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr)) {  
  18.         /* 
  19.          * KAME assumption: link id == interface id 
  20.          */  
  21.         zoneid = ntohs(sin6->sin6_addr.s6_addr16[1]);  
  22.         if (zoneid) {  
  23.             /* sanity check */  
  24.             if (zoneid < 0 || V_if_index < zoneid)  
  25.                 return (ENXIO);  
  26.             if (!ifnet_byindex(zoneid))  
  27.                 return (ENXIO);  
  28.             sin6->sin6_addr.s6_addr16[1] = 0;  
  29.             sin6->sin6_scope_id = zoneid;  
  30.         }  
  31.     }  
  32.   
  33.     return 0;  
  34. }  
[objc] view plain copy
  1. 这个函数的主要作用是根据ipv6地址中的<span style="font-family: Arial, Helvetica, sans-serif;">s6_addr16[1]这二个字节来获取接口的ifindex,然后通过这个ifindex来找到ifnet这个结构体,但是很遗憾,肯定是找不到的,因为这里只使用了二个字节,而我们的ifindex时4个字节,这也是为什么我怀疑装有freeBSD的机子其ifindex如果大于65535这个数值,这个接口还能不能好使的理由所在。</span>  
这里还得说下另一个函数,有get那么就八九不离十的有set函数
[objc] view plain copy
  1. /* 
  2.  * Determine the appropriate scope zone ID for in6 and ifp.  If ret_id is 
  3.  * non NULL, it is set to the zone ID.  If the zone ID needs to be embedded 
  4.  * in the in6_addr structure, in6 will be modified. 
  5.  * 
  6.  * ret_id - unnecessary? 
  7.  */  
  8. int  
  9. in6_setscope(struct in6_addr *in6struct ifnet *ifp, u_int32_t *ret_id)  
  10. {  
  11.     int scope;  
  12.     u_int32_t zoneid = 0;  
  13.     struct scope6_id *sid;  
  14.   
  15.     IF_AFDATA_LOCK(ifp);  
  16.   
  17.     sid = SID(ifp);  
  18.   
  19. #ifdef DIAGNOSTIC  
  20.     if (sid == NULL) { /* should not happen */  
  21.         panic("in6_setscope: scope array is NULL");  
  22.         /* NOTREACHED */  
  23.     }  
  24. #endif  
  25.   
  26.     /* 
  27.      * special case: the loopback address can only belong to a loopback 
  28.      * interface. 
  29.      */  
  30.     if (IN6_IS_ADDR_LOOPBACK(in6)) {  
  31.         if (!(ifp->if_flags & IFF_LOOPBACK)) {  
  32.             IF_AFDATA_UNLOCK(ifp);  
  33.             return (EINVAL);  
  34.         } else {  
  35.             if (ret_id != NULL)  
  36.                 *ret_id = 0/* there's no ambiguity */  
  37.             IF_AFDATA_UNLOCK(ifp);  
  38.             return (0);  
  39.         }  
  40.     }  
  41.   
  42.     scope = in6_addrscope(in6);  
  43.   
  44.     SCOPE6_LOCK();  
  45.     switch (scope) {  
  46.     case IPV6_ADDR_SCOPE_INTFACELOCAL/* should be interface index */  
  47.         zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL];  
  48.         break;  
  49.   
  50.     case IPV6_ADDR_SCOPE_LINKLOCAL:  
  51.         zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL];  
  52.         break;  
  53.   
  54.     case IPV6_ADDR_SCOPE_SITELOCAL:  
  55.         zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_SITELOCAL];  
  56.         break;  
  57.   
  58.     case IPV6_ADDR_SCOPE_ORGLOCAL:  
  59.         zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL];  
  60.         break;  
  61.   
  62.     default:  
  63.         zoneid = 0/* XXX: treat as global. */  
  64.         break;  
  65.     }  
  66.     SCOPE6_UNLOCK();  
  67.     IF_AFDATA_UNLOCK(ifp);  
  68.   
  69.     if (ret_id != NULL)  
  70.         *ret_id = zoneid;  
  71.   
  72.     if (IN6_IS_SCOPE_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6))  
  73.         <pre name="code" class="objc">in6->s6_addr16[1] = htons(zoneid & 0xffff);  

/* XXX */return (0);}这个函数的其中一个作用是:如果穿进来的这个IP是linklocal类型的地址,那么它会取出ifp中的ifindex并把这个ifindex赋值给地址的2、3字节,
[objc] view plain copy
  1. 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个字节这种做法,如下

[objc] view plain copy
  1. 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这个函数里去看看:
[objc] view plain copy
  1. ip6 = mtod(m, struct ip6_hdr *);  
  2.   
  3.     /* IPv6 source address. */  
  4.     scope = in6_getscope(&ip6->ip6_src);/*!< 通过getscope可知,只有linklocal或者intfacelocal才能获得scope */  
  5.     w = (u_int16_t *)&ip6->ip6_src;  
  6.     sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];  
  7.     sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];  
  8.     if (scope != 0)  
  9.         sum -= scope;/*!< 因为ipv6中的scope字段是要清除掉的,因此在计算sum的时候就要事先去除 */  
  10.   
  11.     /* IPv6 destination address. */  
  12.     scope = in6_getscope(&ip6->ip6_dst);  
  13.     w = (u_int16_t *)&ip6->ip6_dst;  
  14.     sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];  
  15.     sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];  
  16.     if (scope != 0)  
  17.         sum -= scope;  
这里有一个getscope函数,还有一个sum -= scope这个操作,为什么要这样做呢?因为现在的ip6里藏有ifindex这4个字节,因此要把它移除掉,因为getscope现在返回的是4个字节,原先我的做法是
[objc] view plain copy
  1. w0 = (u_int32_t *)&ip6->ip6_src;  
直接以32位为单位进行操作,结果悲催的发现只要scope不为0的在对端cksum的时候都是bad。好吧只能继续绞尽脑汁想原因了,在这里就不继续关外抹角了,直接说原因:

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