9.1.3.2主機名獲取
apr_sockaddr_info_get函數用以完成從主機名到網絡地址的轉換,而APR中提供的apr_getnameinfo則可以實現從網絡地址到主機名的轉換,該函數定義如下:
APR_DECLARE(apr_status_t) apr_getnameinfo(char **hostname, apr_sockaddr_t *sa, apr_int32_t flags);
參數sa指定需要轉換的網絡地址,轉換后的主機名由hostname返回。fags是標志位,用以控制內部的轉換過程。
{
#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);
在函數的內部實現從地址到主機名稱的解析是由函數getnameinfo完成的,該函數是getaddrinfo的互補函數,它以一個套接口地址為參數,返回描述其中的主機的一個字符串和描述其中的服務的另一個字符串。另外該函數以協議無關的方式提供這些信息,調用者必須關心存放在套接口地址結構中的協議地址的類型,這些由函數自行處理。
需要轉換的地址到底是IPv4還是IPv6,這由地址結構中的family參數決定。盡管理想中的做法是將apr_getnameinfo()中的參數直接傳遞給getnameinfo()函數,但是在一些平臺上還是會出現一些問題。
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!)
在一些操作系統中,比如老版本的Mac OS X,如果Ipv6地址是由Ipv4地址映射的結果,那么該地址在傳遞給getnameinfo函數的時候將會產生錯誤,這是系統本身實現的BUG。因此對于這種情況,解決的方法就是將這種Ipv6地址重新轉換為Ipv4地址。Ipv6地址是否是由Ipv4地址進行映射而成,通過宏IN6_IS_ADDR_V4MAPPED可以實現檢測。IPV4到IPV6地址的映射可以用下圖描述:
Ipv4地址通過在其十六進制前面添加前導零的方式映射為IPV6地址。反之如果一個IPV6地址是由IPV4地址映射而成,則只要剔除前面的前導零即可,剔除后的地址通常為((apr_uint32_t *)sockaddr->ipaddr_ptr)[3];一旦獲取了實際的IPV4地址,則可以將其傳遞給getnameinfo函數。
對于其余的IP地址,包括普通的Ipv4地址,非Ipv4映射的Ipv6地址,由于不存在BUG,因此可以直接調用getnameinfo。
getnameinfo函數原型如下:
Int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flag);
函數的前面幾個參數都非常容易理解,只有最后一個參數flag,它用于控制getnameinfo的操作,它允許的值如下面所列:
NI_DGRAM
當知道處理的是數據報套接口的時候,調用者應該設置NI_DGRAM標志,因為在套接口地址結構中給出的僅僅是IP地址和端口號,getnameinfo無法就此確定所用協議是TCP還是UDP。比如端口514,在TCP端口上提供rsh服務,而在UDP端口上則提供syslog服務。
NI_NOFQDN
該標志導致返回的主機名稱被截去第一個點號之后的內容。比如假設套接口結構中的IP地址為912.168.42.2,那么不設置該標志返回的主機名為aix.unpbook.com,那么如果設置了該標志后返回的主機名則為aix。
NI_NUMERICHOST,NI_NUMERICSERV,NI_NUMERICSCOPE
該標志通知getnameinfo不要調用DNS,而是以數值表達格式作為字符串返回IP地址;類似的,NI_NUMERICSERV標志指定以十進制數格式作為字符串返回端口號,以代替查找服務名;NI_NUMERICSCOPE則指定以數值格式作為字符串返回范圍標識,以代替其名字
NI_NAMEREQD
該標志通知getnameinfo函數如果無法適用DNS反向解析出主機名,則直接返回一個錯誤。需要把客戶的IP地址映射成主機名的那些服務器可以使用該特性。
如果flag沒有指定,即為零,那么NI_NAMEREQD將是Apache中默認的標志項,如果不設置該標志,那么在反向解析失敗的時候getnameinfo將返回一個數值地址字符串,顯然這并不是Apache所需要的結果。
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;
上面的代碼是對getnameinfo發生錯誤時候的處理(rc==0意味著成功,否則意味著轉換失敗)。此時將需要返回的主機名稱設置為NULL。當getnameinfo發生錯誤的時候通常會返回EAI_XXXX的錯誤碼,在所有這些錯誤碼中比較特殊的就是EAI_SYSTEM,它意味著同時在errno變量中有系統錯誤返回,而其余的EAI_XXXX錯誤并不會設置errno變量。