从实现代码可以看出,函数的内部实际的地址转换过程是由函数find_address完成的。不过在调用find_address之前,函数进行了相关检查和预处理,这些检查和预处理包括:
1、APR_IPV4_ADDR_OK标记只有在hostname为NULL,同时family为APR_UNSPEC的时候才会有效,而APR_IPV6_ADDR_OK和APR_IPV4_ADDR_OK是相互排斥的,一旦定义了APR_IPV4_ADDR_OK,就不能使用APR_IPV6_ADDR_OK,反之亦然。只有在hostname为NULL,同时family为APR_UNSPEC并且没有定义APR_IPV4_ADDR_OK的时候APR_IPV6_ADDR_OK才会有效。
2、如果操作系统平台并不支持IPV6,同时并没有限定获取的地址族,那么此时将默认为IPV6。如果指定必须获取IPV6的地址信息,但系统并不提供支持,此时返回APR_EINVAL。
一般情况下,在IPV4中从主机名到网络地址的解析可以通过gethostbyname()函数完成,不过该API不允许调用者指定所需地址类型的任何信息,这意味着它仅返回包含IPV4地址的信息,对于目前新的IPV6则无能为力。一些平台中为了支持IPV6地址的解析,提供了新的地址解析函数getaddrinfo()以及新的地址描述结构struct addrinfo。APR中通过宏HAVE_GETADDRINFO判断是否支持IPV6地址的解析。目前Window 2000/XP以上的操作系统都能支持新特性。为此APR中根据系统平台的特性采取不同的方法完成地址解析。
首先我们来看支持IPV6地址解析平台下的实现代码,find_address函数的实现如下:
static apr_status_t find_addresses(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
if (flags & APR_IPV4_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET, port, flags, p);
#if APR_HAVE_IPV6
if (error) {
family = AF_INET6; /* try again */ u
}
else
#endif
return error;
}
#if APR_HAVE_IPV6
else if (flags & APR_IPV6_ADDR_OK) {
apr_status_t error = call_resolver(sa, hostname, AF_INET6, port, flags, p);
if (error) { v
family = AF_INET; /* try again */
}
else {
return APR_SUCCESS;
}
}
#endif
return call_resolver(sa, hostname, family, port, flags, p); w
}
从上面的代码可以清晰的看到APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK的含义:对于前者,函数内部首先查询对应主机的IPV4地址,只有在IPV4查询失败的时候才会继续查询IPV6地址;而后者则与之相反,对于给定的主机名称,首先查询IPV6地址,只有在查询失败的时候才会查询IPV4。因此APR_IPV4_ADDR_OK和APR_IPV6_ADDR_OK决定了查询的优先性,任何时候一旦查询成功都不会继续查询另外协议地址,即使被查询主机具有该协议地址。
查询的核心代码封装在内部函数call_resolve中,该函数的参数和apr_sockaddr_info_get函数的参数完全相同且对应,call_resolve中的宏处理比较的多,因此我们将分开描述:
static apr_status_t call_resolver(apr_sockaddr_t **sa,
const char *hostname, apr_int32_t family,
apr_port_t port, apr_int32_t flags,
apr_pool_t *p)
{
struct addrinfo hints, *ai, *ai_list;
apr_sockaddr_t *prev_sa;
int error;
char *servname = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_GAI_ADDRCONFIG
if (family == APR_UNSPEC) {
hints.ai_flags = AI_ADDRCONFIG;
}
#endif
在了解上面的代码之前我们首先简要的了解一些getaddrinfo函数的用法,该函数定义如下:
int getaddrinfo(const char *hostname, const char *service, const struct addinfo *hints,struct addrinfo **result);
hostname是需要进行地址解析的主机名称或者是二进制的地址串(IPV4的点分十进制或者Ipv6的十六进制数串),service则是一个服务名或者是一个十进制的端口号数串。其中hints是addfinfo结构,该结构定义如下:
struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
int ai_family; /* PF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
size_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* canonical name for nodename */
struct sockaddr *ai_addr; /* binary address */
struct addrinfo *ai_next; /* next structure in linked list */
};
hints参数可以是一个空置针,也可以是一个指向某个addrinfo结构的指针,调用者在该结构中填入关于期望返回的信息类型的暗示,这些暗示将控制内部的转换细节。比如,如果指定的服务器既支持TCP,也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置为SOCK_DGRAM,使得返回的仅仅是适用于数据报套接口的信息。
hints结构中调用者可以设置的成员包括ai_flags,ai_family,ai_socktype和ai_protocol。
其中,ai_flags成员可用的标志值及含义如下:
标志名称 标志含义
AI_PASSIVE 套接口将用于被动打开
AI_CANONNAME 告知getaddrinfo函数返回主机的规范名称
AI_NUMERICHOST 防止任何类型的名字到地址的映射;hostname必须是一个地址串
AI_NUMERICSERV 防止任何类型的名字到服务的映射,service参数必须是一个十进制端口号数串
AI_V4MAPPED 如果同时指定ai_family成员的值为AF_INET6和AF_INET,那么如果没有可用的AAAA记录就返回与A记录对应得Ipv4映射的IPV6地址
AI_ALL 如果同时指定AI_V4MAPPED标志,那么除了返回与AAAA对应得IPV6地址之外,还会返回与A记录对应的IPV4映射的Ipv6地址。
AI_ADDRCONFIG 按照所在主机的配置选择返回的地址类型,也就是只查找与所在主机回馈接口以外的网络接口配置的IP地址版本一直的地址。只有当本地系统中配置仅仅配置了IPV4地址才会将主机名称转换位IPV4地址;同样只有当本地系统中仅配置了IPV6地址的时候才会返回IPV6地址。Loopback地址并不在这种限制之中。
ai_family参数指定调用者期待返回的套接口地址结构的类型。它的值包括三种:AF_INET,AF_INET6和AF_UNSPEC。如果指定AF_INET,那么函数九不能返回任何IPV6相关的地址信息;如果仅指定了AF_INET6,则就不能返回任何IPV4地址信息。AF_UNSPEC则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某个主机既有AAAA记录(IPV6)地址,同时又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A记录则作为sockaddr_in结构返回。
if(hostname == NULL) {
#ifdef AI_PASSIVE
hints.ai_flags |= AI_PASSIVE;
#endif
#ifdef OSF1
hostname = family == AF_INET6 ? "::" : "0.0.0.0";
servname = NULL;
#ifdef AI_NUMERICHOST
hints.ai_flags |= AI_NUMERICHOST;
#endif
#else
#ifdef _AIX
if (!port) {
servname = "1";
}
else
#endif /* _AIX */
servname = apr_itoa(p, port);
#endif /* OSF1 */
}
#ifdef HAVE_GAI_ADDRCONFIG
if (error == EAI_BADFLAGS && family == APR_UNSPEC) {
hints.ai_flags = 0;
error = getaddrinfo(hostname, servname, &hints, &ai_list);
}
#endif
if (error) {
#ifndef WIN32
if (error == EAI_SYSTEM) {
return errno;
}
else
#endif
{
#if defined(NEGATIVE_EAI)
error = -error;
#endif
return error + APR_OS_START_EAIERR;
}
}