目前在测试IPV6 DNS解析的时候发现一个问题,这里记录一下
问题是:当dhcpv6服务器分配的IPV6 dns是fe80类型的dns时,无法发送dns请求,抓包看不到有目标地址为fe80::1的dns报文
首先我这边使用dumpsys netd查看了下是否有fe80::1这个dns设置到系统中
发现是有设置到系统中的,而且可以看到dns请求的数据统计都是internal errors,那么表示错误产生在内部,并未实际发出dns报文
接下来只能查dns解析接口getaddrinfo了,看其中是否有会挡住fe80::1为目标地址的请求的地方
分析getaddrinfo流程时,发现一段代码的注释挺可疑的(注释写的好真的很有帮助),就让我锁定了目标
cp = strchr(hostname, SCOPE_DELIMITER);
if (cp == NULL)
return explore_numeric(pai, hostname, servname, res, hostname);
/*
* Handle special case of <scoped_address><delimiter><scope id>
*/
hostname2 = strdup(hostname);
if (hostname2 == NULL)
return EAI_MEMORY;
/* terminate at the delimiter */
hostname2[cp - hostname] = '\0';
addr = hostname2;
scope = cp + 1;
error = explore_numeric(pai, addr, servname, res, hostname);
if (error == 0) {
u_int32_t scopeid;
for (cur = *res; cur; cur = cur->ai_next) {
if (cur->ai_family != AF_INET6)
continue;
sin6 = (struct sockaddr_in6 *)(void *)cur->ai_addr;
if (ip6_str2scopeid(scope, sin6, &scopeid) == -1) {
free(hostname2);
return(EAI_NODATA); /* XXX: is return OK? */
}
sin6->sin6_scope_id = scopeid;
}
}
在系统设置dns的时候会对dns地址调用一次getaddrinfo(这个大家可以去追一下android 设置dns的流程),比如说getaddrinfo(fe80::1,......),这个时候走到这里我看到这么一句注释
这里可以理解成对特殊的dns地址进行处理,这个地址由scoped_address+delimiter+scope id组成,让我比较奇怪的地址就是一串字符串,为什么还会有特殊的地址需要处理,解析来我就看看到底是什么样的地址被认为是特殊地址
cp = strchr(hostname, SCOPE_DELIMITER);
if (cp == NULL)
return explore_numeric(pai, hostname, servname, res, hostname);
这段代码的意思就是查看hostname字符串种是否包含SCOPE_DELIMITER,不包含就会return,也就是说包含了SCOPE_DELIMITER的就是特殊dns地址
/*
* Scope delimit character
*/
#define SCOPE_DELIMITER '%'
SCOPE_DELIMITER就是%,也就是说特殊dns地址是需要包含%的,到这里还不是知道特殊地址为什么要带%,就假设目前是特殊地址了,会怎么处理
/* terminate at the delimiter */
hostname2[cp - hostname] = '\0';
addr = hostname2;
scope = cp + 1;
通过这里的字符串处理,特殊地址被分成两部分,%前的和%后面的,addr就是%前的,scope就是%后的,%前面的就是dns地址,这里主要看下%后的是什么,有什么作用
for (cur = *res; cur; cur = cur->ai_next) {
if (cur->ai_family != AF_INET6)
continue;
sin6 = (struct sockaddr_in6 *)(void *)cur->ai_addr;
if (ip6_str2scopeid(scope, sin6, &scopeid) == -1) {
free(hostname2);
return(EAI_NODATA); /* XXX: is return OK? */
}
sin6->sin6_scope_id = scopeid;
}
这里调用ip6_str2scopeid对上面的scope参数进行了处理,也就是我们说的%后面的部分,然后将scopeid赋值给了sin6->sin6_scope_id(sin6_scope_id的作用这里就不写了,可以自行研究内核)
/* convert a string to a scope identifier. XXX: IPv6 specific */
static int
ip6_str2scopeid(char *scope, struct sockaddr_in6 *sin6, u_int32_t *scopeid)
{
u_long lscopeid;
struct in6_addr *a6;
char *ep;
assert(scope != NULL);
assert(sin6 != NULL);
assert(scopeid != NULL);
a6 = &sin6->sin6_addr;
/* empty scopeid portion is invalid */
if (*scope == '\0')
return -1;
if (IN6_IS_ADDR_LINKLOCAL(a6) || IN6_IS_ADDR_MC_LINKLOCAL(a6)) {
/*
* We currently assume a one-to-one mapping between links
* and interfaces, so we simply use interface indices for
* like-local scopes.
*/
*scopeid = if_nametoindex(scope);
if (*scopeid == 0)
goto trynumeric;
return 0;
}
......
}
这个函数的作用就是将scope转换成scopeid,看看这里的注释写的应该挺明显了,将interface转换成索引值
看到这里明白了,如果是fe80这种link local地址的dns,应该是需要携带%后面跟interface,也就是fe80::1%eth0设置到系统中,在这里进行特殊处理后确定scope id,否则内核会找不到网口索引,导致报文发不出去
至于内核对于link local地址为什么需要scope id,这里贴一段代码(net/ipv6/af_inet6.c),大家可以研究一下
if (__ipv6_addr_needs_scope_id(addr_type)) {
if (addr_len >= sizeof(struct sockaddr_in6) &&
addr->sin6_scope_id) {
/* Override any existing binding, if another one
* is supplied by user.
*/
sk->sk_bound_dev_if = addr->sin6_scope_id;
}
/* Binding to link-local address requires an interface */
if (!sk->sk_bound_dev_if) {
err = -EINVAL;
goto out_unlock;
}
}