2009年3月5日星期四

IPV4和IPV6在APR中的用法

由于APR中仅仅使用apr_sockaddr_t结构描述套接字地址,因此其余的各类描述信息最终都要
转换为该结构,APR中提供apr_sockaddr_info_get函数实现该功能:
APR_DECLARE(apr_status_t) apr_sockaddr_info_get(apr_sockaddr_t **sa,
const char *hostname,
apr_int32_t family,
apr_port_t port,
apr_int32_t flags,
apr_pool_t *p);
该函数允许从主机名hostname,地址协议族family和端口port创建新的apr_sockaddr_t地址,
并由sa返回。
hostname参数允许是实际的主机名称,或者也可以是字符串类型的IP地址,比如
”127.0.0.1”,甚至可以是NULL,此时默认的地址是”0.0.0.0”。
family的值可以是AF_INET,AF_UNIX等系统定义类型,也可以是APR_UNSPEC类型,此时,地
址协议族由系统决定。
flags参数用以指定Ipv4和Ipv6处理的 优先级,它的取值包括两种:APR_IPV4_ADDR_OK和
APR_IPV6_ADDR_OK。这两个标志并不是在所有的情况下都有效,这可以从函数的实现中看出它的
用法:
{
apr_int32_t masked;
*sa = NULL;
if ((masked = flags & (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK))) {
if (!hostname ||
family != APR_UNSPEC ||
masked == (APR_IPV4_ADDR_OK | APR_IPV6_ADDR_OK)) {
return APR_EINVAL;u
}
#if !APR_HAVE_IPV6
if (flags & APR_IPV6_ADDR_OK) {
return APR_ENOTIMPL;
}
#endif
}
#if !APR_HAVE_IPV6
if (family == APR_UNSPEC) {
family = APR_INET; v
}
#endif
return find_addresses(sa, hostname, family, port, flags, p); w
}
从实现代码可以看出,函数的内部实际的地址转换过程是由函数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_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;
}
}

没有评论: