從實現代碼可以看出,函數的內部實際的地址轉換過程是由函數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;
}
}