1. 程式人生 > >Android DNS之gethostbyname()的實現

Android DNS之gethostbyname()的實現

原型解讀

struct hostent *gethostbyname(const char *name);

入參

字串name可取的值分為三種類型:

  1. 十進位制數字格式的IPv4地址
  2. 十六進位制數字格式的IPv6地址
  3. 域名

返回值

返回值為指向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;
}