Android DNS之gethostbyname()的實現
阿新 • • 發佈:2019-01-02
原型解讀
struct hostent *gethostbyname(const char *name);
入參
字串name可取的值分為三種類型:
- 十進位制數字格式的IPv4地址
- 十六進位制數字格式的IPv6地址
- 域名
返回值
返回值為指向struct hostent型別的指標,呼叫者顯然沒有提前分配它,那麼該結構一定是有內部實現分配的,所以該函式是不可重入的。struct hostent結構定義如下:
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
- h_name:代表正式名字
- h_aliases:指標陣列,每個成員指向的字串代表域名的別名,該陣列以NULL結尾
- h_addrtype:表示解析後的地址型別,可取的值為AF_INET和AF_INET6
- h_length:地址長度,對於IPv4地址為4,對於IPv6為16位元組
- h_addr_list:指標陣列,每個成員指向一個解析出來的IP地址,該陣列以NULL結尾
- h_addr:為了向後相容,也可以用h_addr引用第一個解析結果
入口
struct hostent *
gethostbyname(const char *name)
{
struct hostent *result = NULL;
//該結構非常重要,會貫穿整個DNS請求過程
res_static rs = __res_get_static (); /* Use res_static to provide thread-safety. */
//呼叫可重入版本繼續請求
gethostbyname_r(name, &rs->host, rs->hostbuf, sizeof(rs->hostbuf), &result, &h_errno);
return result;
}
呼叫可重入版本繼續請求
/* The prototype of gethostbyname_r is from glibc, not that in netbsd. */
int
gethostbyname_r(const char *name, struct hostent *hp, char *buf, size_t buflen,
struct hostent **result, int *errorp)
{
//獲取DNS解析器狀態結構
res_state res = __res_get_state();
if (res == NULL) {
*result = NULL;
*errorp = NETDB_INTERNAL;
return -1;
}
//檢查DNS請求的域名不能為空
_DIAGASSERT(name != NULL);
//根據res->options中是否設定了RES_USE_INET6選項,決定是否先發起4A請求
if (res->options & RES_USE_INET6) {
*result = gethostbyname_internal(name, AF_INET6, res, hp, buf, buflen, errorp,
&NETCONTEXT_UNSET);
//可見,如果4A請求成功返回,那麼就不會再發起A類請求
if (*result) {
__res_put_state(res);
return 0;
}
}
//4A請求查詢失敗,或者沒有設定RES_USE_INET6選項,那麼就會發起A型別請求
*result = gethostbyname_internal(name, AF_INET, res, hp, buf, buflen, errorp,
&NETCONTEXT_UNSET);
__res_put_state(res);
if (!*result && errno == ENOSPC) {
errno = ERANGE;
return ERANGE; /* Return error as in linux manual page. */
}
//返回0表示成功,-1為失敗
return (*result) ? 0 : -1;
}
從上面可以看出,gethostbyname()最終只會返回一個查詢結果,即使該域名實際上可能會由多個IP地址。
gethostbyname_internal()
static struct hostent *
gethostbyname_internal(const char *name, int af, res_state res, struct hostent *hp, char *hbuf,
size_t hbuflen, int *errorp, const struct android_net_context *netcontext)
{
//開啟代理,實際上是建立一條到DnsProxyListener的socket連線
FILE* proxy = android_open_proxy();
if (proxy == NULL) {
//當從netd呼叫進來時,由於netd設定了ANDROID_DNS_MODE,所以會進入這個條件
// Either we're not supposed to be using the proxy or the proxy is unavailable.
res_setnetcontext(res, netcontext);
//走這裡
return gethostbyname_internal_real(name, af, res, hp, hbuf, hbuflen, errorp);
}
//請求從哪個網絡卡出去,就是app_netid
unsigned netid = __netdClientDispatch.netIdForResolv(netcontext->app_netid);
//向netd傳送gethostbyname請求
// This is writing to system/netd/server/DnsProxyListener.cpp and changes
// here need to be matched there.
if (fprintf(proxy, "gethostbyname %u %s %d",
netid,
name == NULL ? "^" : name,
af) < 0) {
fclose(proxy);
return NULL;
}
//如果寫入出錯,直接返回
if (fputc(0, proxy) == EOF || fflush(proxy) != 0) {
fclose(proxy);
return NULL;
}
//讀取來自netd的查詢結果
struct hostent* result = android_read_hostent(proxy, hp, hbuf, hbuflen, errorp);
fclose(proxy);
return result;
}
從上面可以看出,呼叫gethostbyname()時,實際上最終的DNS查詢是由netd代理的。
netd處理
netd的處理都在DnsProxyListener.cpp中的GetHostByNameHandler類中,具體參考其run()方法,該函式最終會呼叫android_gethostbynamefornet(),但是要注意的一點是這時已經是在Netd的一個執行緒中運行了。
android_gethostbynamefornet()
struct hostent *
android_gethostbynamefornet(const char *name, int af, unsigned netid, unsigned mark)
{
const struct android_net_context netcontext = make_context(netid, mark);
return android_gethostbynamefornetcontext(name, af, &netcontext);
}
struct hostent *
android_gethostbynamefornetcontext(const char *name, int af,
const struct android_net_context *netcontext)
{
struct hostent *hp;
res_state res = __res_get_state();
if (res == NULL)
return NULL;
res_static rs = __res_get_static(); /* Use res_static to provide thread-safety. */
//是否非常熟悉,呼叫到了同一函式
hp = gethostbyname_internal(name, af, res, &rs->host, rs->hostbuf, sizeof(rs->hostbuf),
&h_errno, netcontext);
__res_put_state(res);
return hp;
}
gethostbyname_internal_real()
static struct hostent *
gethostbyname_internal_real(const char *name, int af, res_state res, struct hostent *hp, char *buf,
size_t buflen, int *he)
{
const char *cp;
struct getnamaddr info;
char hbuf[MAXHOSTNAMELEN];
size_t size;
//查詢表
static const ns_dtab dtab[] = {
NS_FILES_CB(_hf_gethtbyname, NULL)
{ NSSRC_DNS, _dns_gethtbyname, NULL }, /* force -DHESIOD */
NS_NIS_CB(_yp_gethtbyname, NULL)
NS_NULL_CB
};
_DIAGASSERT(name != NULL);
//根據地址族確定地址大小,AF_INET為4位元組,AF_INET6為16位元組
switch (af) {
case AF_INET:
size = NS_INADDRSZ;
break;
case AF_INET6:
size = NS_IN6ADDRSZ;
break;
default:
*he = NETDB_INTERNAL;
errno = EAFNOSUPPORT;
return NULL;
}
//儲存結果的buff太小,直接返回NOSPACE錯誤
if (buflen < size)
goto nospc;
//設定地址族和地址大小
hp->h_addrtype = af;
hp->h_length = (int)size;
/*
* if there aren't any dots, it could be a user-level alias.
* this is also done in res_nquery() since we are not the only
* function that looks up host names.
*/
if (!strchr(name, '.') && (cp = res_hostalias(res, name,
hbuf, sizeof(hbuf))))
name = cp;
/*
* disallow names consisting only of digits/dots, unless
* they end in a dot.
*/
//如果是純數字型別的主機名,那麼無需繼續查詢,跳轉到fake標籤處直接構造返回值
if (isdigit((u_char) name[0]))
for (cp = name;; ++cp) {
if (!*cp) {
if (*--cp == '.')
break;
/*
* All-numeric, no dot at the end.
* Fake up a hostent as if we'd actually
* done a lookup.
*/
goto fake;
}
if (!isdigit((u_char) *cp) && *cp != '.')
break;
}
if ((isxdigit((u_char) name[0]) && strchr(name, ':') != NULL) ||
name[0] == ':')
for (cp = name;; ++cp) {
if (!*cp) {
if (*--cp == '.')
break;
/*
* All-IPv6-legal, no dot at the end.
* Fake up a hostent as if we'd actually
* done a lookup.
*/
goto fake;
}
if (!isxdigit((u_char) *cp) && *cp != ':' && *cp != '.')
break;
}
//先將錯誤碼設定為NETDB_INTERNAL,表示是一種查詢網路資料庫內部錯誤
*he = NETDB_INTERNAL;
info.hp = hp;
info.buf = buf;
info.buflen = buflen;
info.he = he;
//按照搜尋源和查詢表的對應關係進行查詢,見https://blog.csdn.net/xiaoyu_750516366/article/details/82731634
if (nsdispatch(&info, dtab, NSDB_HOSTS, "gethostbyname",
default_dns_files, name, strlen(name), af) != NS_SUCCESS)
return NULL;
*he = NETDB_SUCCESS;
return hp;
nospc:
*he = NETDB_INTERNAL;
errno = ENOSPC;
return NULL;
fake:
//對於純數字形式表示的hostname,實際上不需要發起真正的DNS,只需要將其轉換成IP地址即可,
//這裡就是對於此種情況構造返回結果
//地址列表中只有一個成員,別名列表為空
HENT_ARRAY(hp->h_addr_list, 1, buf, buflen);
HENT_ARRAY(hp->h_aliases, 0, buf, buflen);
hp->h_aliases[0] = NULL;
if (size > buflen)
goto nospc;
//呼叫標準的介面進行轉換
if (inet_pton(af, name, buf) <= 0) {
*he = HOST_NOT_FOUND;
return NULL;
}
hp->h_addr_list[0] = buf;
hp->h_addr_list[1] = NULL;
buf += size;
buflen -= size;
HENT_SCOPY(hp->h_name, name, buf, buflen);
//如果需要處理IPv6,後面分析
if (res->options & RES_USE_INET6)
map_v4v6_hostent(hp, &buf, buf + buflen);
*he = NETDB_SUCCESS;
return hp;
}
查詢
由於搜尋源和查詢表的定義分別如下:
//搜尋源為檔案和DNS
static const ns_src default_dns_files[] = {
{ NSSRC_FILES, NS_SUCCESS },
{ NSSRC_DNS, NS_SUCCESS },
{ 0, 0 }
};
//查詢表
static const ns_dtab dtab[] = {
NS_FILES_CB(_hf_gethtbyname, NULL)
{ NSSRC_DNS, _dns_gethtbyname, NULL }, /* force -DHESIOD */
NS_NIS_CB(_yp_gethtbyname, NULL)
NS_NULL_CB
};
按照匹配規則,在上面呼叫nsdispatch()時,實際上會按照順序分別呼叫_hf_gethtbyname()和_dns_gethtbyname()進行域名查詢,前者是在/system/etc/hosts檔案中搜索,後者會想DNS伺服器查詢結果,基於檔案的查詢我們不關注,直接看對DNS伺服器進行查詢的處理。
基於DNS的查詢_dns_gethtbyname()
static int _dns_gethtbyname(void *rv, void *cb_data, va_list ap)
{
querybuf *buf;
int n, type;
struct hostent *hp;
const char *name;
res_state res;
struct getnamaddr *info = rv;
_DIAGASSERT(rv != NULL);
name = va_arg(ap, char *);
/* NOSTRICT skip string len */(void)va_arg(ap, int);
//傳進來的就是地址族AF_INET或者AF_INET6
info->hp->h_addrtype = va_arg(ap, int);
//根據地址族確定是A查詢還是4A查詢
switch (info->hp->h_addrtype) {
case AF_INET:
info->hp->h_length = NS_INADDRSZ;
type = T_A;
break;
case AF_INET6:
info->hp->h_length = NS_IN6ADDRSZ;
type = T_AAAA;
break;
default:
return NS_UNAVAIL;
}
//分配一個buf用於儲存查詢結果
buf = malloc(sizeof(*buf));
if (buf == NULL) {
*info->he = NETDB_INTERNAL;
return NS_NOTFOUND;
}
res = __res_get_state();
if (res == NULL) {
free(buf);
return NS_NOTFOUND;
}
//呼叫resolver的res_nsearch()介面進行DNS查詢,查詢結果儲存到buf->buf中
n = res_nsearch(res, name, C_IN, type, buf->buf, (int)sizeof(buf->buf));
if (n < 0) {
free(buf);
debugprintf("res_nsearch failed (%d)\n", res, n);
__res_put_state(res);
return NS_NOTFOUND;
}
//查詢成功,解析響應報文,如果解析成功,則將查詢結果儲存到info中,對於報文解析,實際上已經沒有必要
//去仔細分析了,無非就是按照標準協議處理即可
hp = getanswer(buf, n, name, type, res, info->hp, info->buf,
info->buflen, info->he);
free(buf);
__res_put_state(res);
//查詢失敗則返回錯誤碼
if (hp == NULL)
switch (*info->he) {
case HOST_NOT_FOUND:
return NS_NOTFOUND;
case TRY_AGAIN:
return NS_TRYAGAIN;
default:
return NS_UNAVAIL;
}
return NS_SUCCESS;
}