ios 客户端IPV6网络超时等相关问题

3,208 阅读4分钟

1、前序

首先,众所周知的原因,苹果从很早开始都要求开发者必须支持IPV6,刚开始由于更重原因,改造还是很慢,无论是作为开发者的我们还是运营商,但是随着时间的推进,企业和移动通信供应商在逐步部署IPv6 DNS64/NAT64网络。IPv6DNS64/NAT64是一个仅有IPv6的网络,且能通过转换继续支持IPv4。同时4G的普及,由于IPv4地址的限制,为了保证4G开发的扩展性,需要IPv6的支持。当然目前国内的三大运营商,他们的IPV6的发展规模现在各不相同。

通过这里就了解到了一些前提。这边再说下DNS64/NAT64转换流程

DNS64/NAT64转换流程

参考Apple官方文档 Supporting IPv6 DNS64/NAT64 Networks

运营商为了兼容问题,大多数的网络供应商实现了一个叫DNS64/NAT64的转换流程。这是个纯IPv6网络,并通过转换也可继续访问IPv4的内容。这样能保证所有的网络都能访问。上图: 就是目前运营商的一个标准解析流程

如果客户端向DNS64服务器发起一个DNS查询,当DNS找到一个基于IPv6的地址后,立刻返回客户端。如果无法找到对应的IPv6地址,DNS64服务器将请求IPv4地址,然后DNS64服务器将IPv4作为前缀合成一个IPv6地址,并且将其返回给客户端。这样,客户端将总是获得一个IPv6目标地址.详细流程见下图

而对于客户端如果本地网络同时提供IPv4和IPv6的环境时(称为双栈),访问IPv4服务器就直接创建IPv4 socket进行链接。

如果本地网络只提供了IPv6环境(称为IPv6-Only),访问IPv4服务器时就先转成IPv6地址,网关收到后再由NAT64服务解析成IPv4,走后面的IPv4网络。

2.由bug产生的思考🤔

bug:有客户反应,在4G状态下,上传图片会超时失败,但是在WiFi下没有这个问题。

非常奇怪的bug,因为经过我不停的复现就是复现不了,但是客户的客户端确实必现的,在尝试努力了几次后,险些放弃查找原因,认为是正常的网络不稳定的错误,但是在后期,陆续又有客户反馈有类似问题,而且都是在4G的网络下。通过本地日志查看,调试,大致猜测问题可能和IPV6网路有关。

3.突破口

真的是运气,在某个阴郁天气的日子,我又通过自己的手机尝试了下问题,问题复现,而且是必现,公司其他同事的手机都没问题,只有我的手机问题复现,哈哈哈哈,立刻打开电脑,开启xcode进行调试操作,发现问题所在是在底层的socket连接库的问题。灵机一动,把sim卡放入其他人的手机尝试,问题复现。。。看来问题和运营商也有关系。

问题定位到socket IPV6连接error错误。尝试验证自己的想法。 验证手机的网络地址

手机访问检测网站

问题复现的sim卡:

正常没有该问题的sim卡:

确实问题和IPV6网路有关。证实。

4.bug排故

通过调试发现,在判断网络时,该sim卡网络被判断为IPV6-only,但是实际上是IPV^/IPV4双栈的网络。发现原来是判断网络的方法实现有问题

int32 getdefaultgateway(in_addr* addr) 判断是否支持ipv4

百度或者谷歌能搜到很多相关实现 例如:

int getdefaultgateway(in_addr_t * addr)
{
    int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET,
        NET_RT_FLAGS, RTF_GATEWAY};
    size_t l;
    char * buf, * p;
    struct rt_msghdr * rt;
    struct sockaddr * sa;
    struct sockaddr * sa_tab[RTAX_MAX];
    int i;
    int r = -1;
    if(sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0) {
        return -1;
    }
    if(l>0) {
        buf = malloc(l);
        if(sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) {
            return -1;
        }
        for(p=buf; p<buf+l; p+=rt->rtm_msglen) {
            rt = (struct rt_msghdr *)p;
            sa = (struct sockaddr *)(rt + 1);
            for(i=0; i<RTAX_MAX; i++) {
                if(rt->rtm_addrs & (1 << i)) {
                    sa_tab[i] = sa;
                    sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len));
                } else {
                    sa_tab[i] = NULL;
                }
            }

            if( ((rt->rtm_addrs & (RTA_DST|RTA_GATEWAY)) == (RTA_DST|RTA_GATEWAY))
               && sa_tab[RTAX_DST]->sa_family == AF_INET
               && sa_tab[RTAX_GATEWAY]->sa_family == AF_INET) {


                if(((struct sockaddr_in *)sa_tab[RTAX_DST])->sin_addr.s_addr == 0) {
                    char ifName[128];
                    if_indextoname(rt->rtm_index,ifName);

                    if(strcmp("en0",ifName)==0){

                        *addr = ((struct sockaddr_in *)(sa_tab[RTAX_GATEWAY]))->sin_addr.s_addr;
                        r = 0;
                    }
                }
            }
        }
        free(buf);
    }
    return r;
}

为什么明明支持IPV6但是判断为IPV4呢。深入方法调试查看

关键就是en0的判断,en0表示的是wifi,也就是说只有在wifi状态下才能进入if逻辑中,4G状态下是进入不了判断语句的。

尝试把此判断去除,问题解决。。。 问题排除

此处可以进行兜底逻辑,如果是IPV^-only的地址,进行是否可以合成IPV6的判断。如果运营商支持IPV6-only就一定有IPV4合成IPV6的能力,用于支持IPV4。如果是双栈就不用去合成IPV6地址,可以直接走IPV4地址。