9.1.3.2主機(jī)名獲取
apr_sockaddr_info_get函數(shù)用以完成從主機(jī)名到網(wǎng)絡(luò)地址的轉(zhuǎn)換,而APR中提供的apr_getnameinfo則可以實(shí)現(xiàn)從網(wǎng)絡(luò)地址到主機(jī)名的轉(zhuǎn)換,該函數(shù)定義如下:
APR_DECLARE(apr_status_t) apr_getnameinfo(char **hostname, apr_sockaddr_t *sa, apr_int32_t flags);
參數(shù)sa指定需要轉(zhuǎn)換的網(wǎng)絡(luò)地址,轉(zhuǎn)換后的主機(jī)名由hostname返回。fags是標(biāo)志位,用以控制內(nèi)部的轉(zhuǎn)換過程。
{
#if defined(HAVE_GETNAMEINFO)
int rc;
#if defined(NI_MAXHOST)
char tmphostname[NI_MAXHOST];
#else
char tmphostname[256];
#endif
SET_H_ERRNO(0);
#if APR_HAVE_IPV6
if (sockaddr->family == AF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&sockaddr->sa.sin6.sin6_addr)) {
struct sockaddr_in tmpsa;
tmpsa.sin_family = AF_INET;
tmpsa.sin_addr.s_addr = ((apr_uint32_t *)sockaddr->ipaddr_ptr)[3];
#ifdef SIN6_LEN
tmpsa.sin_len = sizeof(tmpsa);
#endif
rc = getnameinfo((const struct sockaddr *)&tmpsa, sizeof(tmpsa),
tmphostname, sizeof(tmphostname), NULL, 0,
flags != 0 ? flags : NI_NAMEREQD);
}
else
#endif
#endif
rc = getnameinfo((const struct sockaddr *)&sockaddr->sa, sockaddr->salen,
tmphostname, sizeof(tmphostname), NULL, 0,
flags != 0 ? flags : NI_NAMEREQD);
在函數(shù)的內(nèi)部實(shí)現(xiàn)從地址到主機(jī)名稱的解析是由函數(shù)getnameinfo完成的,該函數(shù)是getaddrinfo的互補(bǔ)函數(shù),它以一個(gè)套接口地址為參數(shù),返回描述其中的主機(jī)的一個(gè)字符串和描述其中的服務(wù)的另一個(gè)字符串。另外該函數(shù)以協(xié)議無(wú)關(guān)的方式提供這些信息,調(diào)用者必須關(guān)心存放在套接口地址結(jié)構(gòu)中的協(xié)議地址的類型,這些由函數(shù)自行處理。
需要轉(zhuǎn)換的地址到底是IPv4還是IPv6,這由地址結(jié)構(gòu)中的family參數(shù)決定。盡管理想中的做法是將apr_getnameinfo()中的參數(shù)直接傳遞給getnameinfo()函數(shù),但是在一些平臺(tái)上還是會(huì)出現(xiàn)一些問題。
MacOS X Panther has a lousy getnameinfo() implementation that doesn't fill the buffer when no DNS entry is found for a host and a numerical result wasn't explicitely asked. As a result, Pure-FTPd didn't even start on Panther (saying "bad IP address") . We now check for EAI_NONAME if available and we retry with NI_NUMERICHOST if this is what getnameinfo() returns. Thanks to Yann Bizeul for his valuable help on this issue. Will research it more and see if I can come up with a patch (I am NOT good at C!)
在一些操作系統(tǒng)中,比如老版本的Mac OS X,如果Ipv6地址是由Ipv4地址映射的結(jié)果,那么該地址在傳遞給getnameinfo函數(shù)的時(shí)候?qū)?huì)產(chǎn)生錯(cuò)誤,這是系統(tǒng)本身實(shí)現(xiàn)的BUG。因此對(duì)于這種情況,解決的方法就是將這種Ipv6地址重新轉(zhuǎn)換為Ipv4地址。Ipv6地址是否是由Ipv4地址進(jìn)行映射而成,通過宏IN6_IS_ADDR_V4MAPPED可以實(shí)現(xiàn)檢測(cè)。IPV4到IPV6地址的映射可以用下圖描述:
Ipv4地址通過在其十六進(jìn)制前面添加前導(dǎo)零的方式映射為IPV6地址。反之如果一個(gè)IPV6地址是由IPV4地址映射而成,則只要剔除前面的前導(dǎo)零即可,剔除后的地址通常為((apr_uint32_t *)sockaddr->ipaddr_ptr)[3];一旦獲取了實(shí)際的IPV4地址,則可以將其傳遞給getnameinfo函數(shù)。
對(duì)于其余的IP地址,包括普通的Ipv4地址,非Ipv4映射的Ipv6地址,由于不存在BUG,因此可以直接調(diào)用getnameinfo。
getnameinfo函數(shù)原型如下:
Int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flag);
函數(shù)的前面幾個(gè)參數(shù)都非常容易理解,只有最后一個(gè)參數(shù)flag,它用于控制getnameinfo的操作,它允許的值如下面所列:
NI_DGRAM
當(dāng)知道處理的是數(shù)據(jù)報(bào)套接口的時(shí)候,調(diào)用者應(yīng)該設(shè)置NI_DGRAM標(biāo)志,因?yàn)樵谔捉涌诘刂方Y(jié)構(gòu)中給出的僅僅是IP地址和端口號(hào),getnameinfo無(wú)法就此確定所用協(xié)議是TCP還是UDP。比如端口514,在TCP端口上提供rsh服務(wù),而在UDP端口上則提供syslog服務(wù)。
NI_NOFQDN
該標(biāo)志導(dǎo)致返回的主機(jī)名稱被截去第一個(gè)點(diǎn)號(hào)之后的內(nèi)容。比如假設(shè)套接口結(jié)構(gòu)中的IP地址為912.168.42.2,那么不設(shè)置該標(biāo)志返回的主機(jī)名為aix.unpbook.com,那么如果設(shè)置了該標(biāo)志后返回的主機(jī)名則為aix。
NI_NUMERICHOST,NI_NUMERICSERV,NI_NUMERICSCOPE
該標(biāo)志通知getnameinfo不要調(diào)用DNS,而是以數(shù)值表達(dá)格式作為字符串返回IP地址;類似的,NI_NUMERICSERV標(biāo)志指定以十進(jìn)制數(shù)格式作為字符串返回端口號(hào),以代替查找服務(wù)名;NI_NUMERICSCOPE則指定以數(shù)值格式作為字符串返回范圍標(biāo)識(shí),以代替其名字
NI_NAMEREQD
該標(biāo)志通知getnameinfo函數(shù)如果無(wú)法適用DNS反向解析出主機(jī)名,則直接返回一個(gè)錯(cuò)誤。需要把客戶的IP地址映射成主機(jī)名的那些服務(wù)器可以使用該特性。
如果flag沒有指定,即為零,那么NI_NAMEREQD將是Apache中默認(rèn)的標(biāo)志項(xiàng),如果不設(shè)置該標(biāo)志,那么在反向解析失敗的時(shí)候getnameinfo將返回一個(gè)數(shù)值地址字符串,顯然這并不是Apache所需要的結(jié)果。
if (rc != 0) {
*hostname = NULL;
#ifndef WIN32
if (rc == EAI_SYSTEM) {
if (h_errno) { /* for broken implementations which set h_errno */
return h_errno + APR_OS_START_SYSERR;
}
else { /* "normal" case */
return errno + APR_OS_START_SYSERR;
}
}
else
#endif
{
#if defined(NEGATIVE_EAI)
if (rc < 0) rc = -rc;w
#endif
return rc + APR_OS_START_EAIERR; /* return the EAI_ error */
}
}
*hostname = sockaddr->hostname = apr_pstrdup(sockaddr->pool, tmphostname);
return APR_SUCCESS;
上面的代碼是對(duì)getnameinfo發(fā)生錯(cuò)誤時(shí)候的處理(rc==0意味著成功,否則意味著轉(zhuǎn)換失敗)。此時(shí)將需要返回的主機(jī)名稱設(shè)置為NULL。當(dāng)getnameinfo發(fā)生錯(cuò)誤的時(shí)候通常會(huì)返回EAI_XXXX的錯(cuò)誤碼,在所有這些錯(cuò)誤碼中比較特殊的就是EAI_SYSTEM,它意味著同時(shí)在errno變量中有系統(tǒng)錯(cuò)誤返回,而其余的EAI_XXXX錯(cuò)誤并不會(huì)設(shè)置errno變量。


