Android DNSのgetaddrinfo()の実現
79390 ワード
このノートでは,ライブラリ関数getaddrinfo()のコード実装を解析した.
プロトタイプ解読 hostname:gethostbyname()の入参hostnameと同じ、クエリーするドメイン名; servname:クエリーするサービス名.指定すると、戻り値にサービス名に対応するポート番号も表示されます.つまり、この関数にはgetservbyname()の機能もあります. hints:表面は暗示を意味し、このパラメータを使用してクエリー結果を制約し、条件を満たすクエリー結果だけを返すことができます.このパラメータをフィルタと呼ぶ. res:このパラメータはクエリー結果アドレスへのポインタを保存するために使用され、クエリー結果を保存するメモリにはこの関数の内部割り当てがあり、使用が完了すると呼び出し者はfreeaddrinfo()クリアを呼び出す必要があり、呼び出し者はstruct addrinfo*を保存するアドレスを提供するだけでよい.
この関数の詳細についてはgetaddrinfo(3)を参照してください.
いりぐち
この2つの関数について説明する必要があります. getaddrinfo()は標準的なlibcインタフェースであり、システム内のFramework以下のC/C++コードはこのインタフェースを呼び出してDNSクエリーを実現することができる. android_getaddrinfofornet()はAndroidによるlibcの拡張であり、FramworkがInetAddressクラスから提供されるインタフェースを介してDNSクエリを行うと、最終的にJNIを介してこの関数、すなわちこのインタフェースはFrameworkに使用される.
android_getaddrinfofornetcontext()
この関数の役割は主に入力パラメータの正当性をチェックし、hostnameが空または純粋なデジタルフォーマットの場合を処理し、DNSリクエストを本当に開始する必要がある場合はexplore_を呼び出すことです.fqdn()が完了しました.
explore_fqdn()
この関数のコードを見ると、gethostbyname()の実装では、このようなクエリーテーブル方式が見られますが、ここでは説明しません.
_dns_getaddrinfo()
ファイルベースのクエリーには注目しません.files_getaddrinfo()の実装は,DNSサーバベースのクエリー実装のみを見る.
次のコードは分析を続行しません.resとsearch()の論理は非常に類似しており,興味のあるものは自分で分析することができる.
その他
MATCHマクロ
xとyが一致するかどうかを確認し、ブール値を返し、一致は2つの状況に分けられます. xとyの値は確かに等しいので、一致し、対応する式の(x)=(y) xおよびyは等しくないが、wパラメータは0ではなく、xおよびyのいずれかが共通であり、すなわち値はANY(0)であり、一致していると考えられる.実際,ここでのwの意味はwildcard,すなわちパスであり,wが0であればパスを考慮し,そうでなければxとyの真の値が等しいか否かでマッチングするか否かを判断することを示す.
プロトタイプ解読
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res);
この関数の詳細についてはgetaddrinfo(3)を参照してください.
いりぐち
int getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
// NETID_UNSET MARK_UNSET
return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
int android_getaddrinfofornet(const char *hostname, const char *servname,
const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
// netid mark DNS , ,
//netcontext
struct android_net_context netcontext = {
.app_netid = netid,
.app_mark = mark,
.dns_netid = netid,
.dns_mark = mark,
.uid = NET_CONTEXT_INVALID_UID,
};
return android_getaddrinfofornetcontext(hostname, servname, hints, &netcontext, res);
}
この2つの関数について説明する必要があります.
android_getaddrinfofornetcontext()
この関数の役割は主に入力パラメータの正当性をチェックし、hostnameが空または純粋なデジタルフォーマットの場合を処理し、DNSリクエストを本当に開始する必要がある場合はexplore_を呼び出すことです.fqdn()が完了しました.
/* faimly、socktype、protocol */
static const struct explore explore[] = {
#ifdef INET6
{ PF_INET6, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_INET6, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_INET6, SOCK_RAW, ANY, NULL, 0x05 },
#endif
{ PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_INET, SOCK_RAW, ANY, NULL, 0x05 },
{ PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
{ PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
{ PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05 },
{ -1, 0, 0, NULL, 0 },
};
int android_getaddrinfofornetcontext(const char *hostname, const char *servname,
const struct addrinfo *hints, const struct android_net_context *netcontext,
struct addrinfo **res)
{
// 3 addrinfo , ,
struct addrinfo sentinel;
struct addrinfo *cur;
int error = 0;
struct addrinfo ai;
struct addrinfo ai0;
struct addrinfo *pai;
const struct explore *ex;
/* hostname is allowed to be NULL */
/* servname is allowed to be NULL */
/* hints is allowed to be NULL */
assert(res != NULL);
assert(netcontext != NULL);
//sentinel , cur
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
//pai ai, ai
pai = &ai;
pai->ai_flags = 0;
pai->ai_family = PF_UNSPEC;
pai->ai_socktype = ANY;
pai->ai_protocol = ANY;
pai->ai_addrlen = 0;
pai->ai_canonname = NULL;
pai->ai_addr = NULL;
pai->ai_next = NULL;
//hostname servname ,
if (hostname == NULL && servname == NULL)
return EAI_NONAME;
// , , , addrinfo
if (hints) {
/* error check for hints */
// ,addrinfo , 0
if (hints->ai_addrlen || hints->ai_canonname ||
hints->ai_addr || hints->ai_next)
ERR(EAI_BADHINTS); /* xxx */
//ai_flags
if (hints->ai_flags & ~AI_MASK)
ERR(EAI_BADFLAGS);
//ai_faimly ,
switch (hints->ai_family) {
case PF_UNSPEC:
case PF_INET:
#ifdef INET6
case PF_INET6:
#endif
break;
default:
ERR(EAI_FAMILY);
}
// , ai , ai
memcpy(pai, hints, sizeof(*pai));
/*
* if both socktype/protocol are specified, check if they
* are meaningful combination.
*/
// hints ai_socktype ai_protocol ( 0),
// , socktype STREAM, protocol DGRAM
if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
//expore faimly、socktype、protocol
for (ex = explore; ex->e_af >= 0; ex++) {
if (pai->ai_family != ex->e_af)
continue;
if (ex->e_socktype == ANY)
continue;
if (ex->e_protocol == ANY)
continue;
// BADHINTS
if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) {
ERR(EAI_BADHINTS);
}
}
}
}//end of "if (hints)"
/*
* check for special cases. (1) numeric servname is disallowed if
* socktype/protocol are left unspecified. (2) servname is disallowed
* for raw and other inet{,6} sockets.
*/
// hints ( )
if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
|| MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
) {
//ai0 hints
ai0 = *pai; /* backup *pai */
if (pai->ai_family == PF_UNSPEC) {
#ifdef PF_INET6
pai->ai_family = PF_INET6;
#else
pai->ai_family = PF_INET;
#endif
}
error = get_portmatch(pai, servname);
if (error)
ERR(error);
// hints , hints
*pai = ai0;
}
ai0 = *pai;
// hostname , hostname
/* NULL hostname, or numeric hostname */
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* PF_UNSPEC entries are prepared for DNS queries only */
if (ex->e_af == PF_UNSPEC)
continue;
if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
continue;
if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex)))
continue;
if (pai->ai_family == PF_UNSPEC)
pai->ai_family = ex->e_af;
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
if (hostname == NULL)
// , cur->ai_next 。
// sentinel , ,
//sentinel.ai_next addrinfo , cur ,
// , while
error = explore_null(pai, servname, &cur->ai_next);
else
// hostname
error = explore_numeric_scope(pai, hostname, servname,
&cur->ai_next);
if (error)
goto free;
// cur ,
while (cur->ai_next)
cur = cur->ai_next;
}
/*
* XXX
* If numeric representation of AF1 can be interpreted as FQDN
* representation of AF2, we need to think again about the code below.
*/
// , for , hostname ,
// DNS ,
if (sentinel.ai_next)
goto good;
//hostname ,
if (hostname == NULL)
ERR(EAI_NODATA);
//hostname ,
if (pai->ai_flags & AI_NUMERICHOST)
ERR(EAI_NONAME);
// Android , DNS netd , netd ,
// gethostbyname() , gethostbyname()
#if defined(__ANDROID__)
// netd , netd
int gai_error = android_getaddrinfo_proxy(
hostname, servname, hints, res, netcontext->app_netid);
// netd , android_getaddrinfo_proxy() EAI_SYSTEM ,
if (gai_error != EAI_SYSTEM) {
return gai_error;
}
#endif
/*
* hostname as alphabetical name.
* we would like to prefer AF_INET6 than AF_INET, so we'll make a
* outer loop by AFs.
*/
// explore , explore_fqdn() DNS
for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;
/* require exact match for family field */
if (pai->ai_family != ex->e_af)
continue;
if (!MATCH(pai->ai_socktype, ex->e_socktype,
WILD_SOCKTYPE(ex))) {
continue;
}
if (!MATCH(pai->ai_protocol, ex->e_protocol,
WILD_PROTOCOL(ex))) {
continue;
}
// , explore ,socktype ,
if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;
if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;
// DNS
error = explore_fqdn(
pai, hostname, servname, &cur->ai_next, netcontext);
// cur
while (cur && cur->ai_next)
cur = cur->ai_next;
}
//sentinel.ai_next , , error 0
if (sentinel.ai_next)
error = 0;
// , freeaddrinfo()
if (error)
goto free;
if (error == 0) {
if (sentinel.ai_next) {
good:
// && ,
*res = sentinel.ai_next;
return SUCCESS;
} else
// (error=0), (sentinel.ai_next=NULL),
error = EAI_FAIL;
}
free:
bad:
// , ,
if (sentinel.ai_next)
freeaddrinfo(sentinel.ai_next);
*res = NULL;
return error;
}
explore_fqdn()
この関数のコードを見ると、gethostbyname()の実装では、このようなクエリーテーブル方式が見られますが、ここでは説明しません.
/*
* FQDN hostname, DNS lookup
*/
static int explore_fqdn(const struct addrinfo *pai, const char *hostname,
const char *servname, struct addrinfo **res,
const struct android_net_context *netcontext)
{
struct addrinfo *result;
struct addrinfo *cur;
int error = 0;
//_files_getaddrinfo() ;_dns_getaddrinfo() DNS ;
//_yp_getaddrinfo()
static const ns_dtab dtab[] = {
NS_FILES_CB(_files_getaddrinfo, NULL)
{ NSSRC_DNS, _dns_getaddrinfo, NULL }, /* force -DHESIOD */
NS_NIS_CB(_yp_getaddrinfo, NULL)
{ 0, 0, 0 }
};
assert(pai != NULL);
/* hostname may be NULL */
/* servname may be NULL */
assert(res != NULL);
result = NULL;
/*
* if the servname does not match socktype/protocol, ignore it.
*/
// servname, socktype protocol ,Android
if (get_portmatch(pai, servname) != 0)
return 0;
// nsdispatch()
switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
default_dns_files, hostname, pai, netcontext)) {
case NS_TRYAGAIN:
error = EAI_AGAIN;
goto free;
case NS_UNAVAIL:
error = EAI_FAIL;
goto free;
case NS_NOTFOUND:
error = EAI_NODATA;
goto free;
case NS_SUCCESS:
error = 0;
// hostname , servname, ,Android
for (cur = result; cur; cur = cur->ai_next) {
GET_PORT(cur, servname);
/* canonname should be filled already */
}
break;
}
*res = result;
return 0;
free:
if (result)
freeaddrinfo(result);
return error;
}
_dns_getaddrinfo()
ファイルベースのクエリーには注目しません.files_getaddrinfo()の実装は,DNSサーバベースのクエリー実装のみを見る.
static int _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
{
struct addrinfo *ai;
querybuf *buf, *buf2;
const char *name;
const struct addrinfo *pai;
struct addrinfo sentinel, *cur;
struct res_target q, q2;
res_state res;
const struct android_net_context *netcontext;
// explore_fqdn()
name = va_arg(ap, char *);
pai = va_arg(ap, const struct addrinfo *);
netcontext = va_arg(ap, const struct android_net_context *);
memset(&q, 0, sizeof(q));
memset(&q2, 0, sizeof(q2));
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
//buf buf2 , getanswer()
buf = malloc(sizeof(*buf));
if (buf == NULL) {
h_errno = NETDB_INTERNAL;
return NS_NOTFOUND;
}
buf2 = malloc(sizeof(*buf2));
if (buf2 == NULL) {
free(buf);
h_errno = NETDB_INTERNAL;
return NS_NOTFOUND;
}
// , AAAA A
switch (pai->ai_family) {
case AF_UNSPEC:
// failmy , IPv6
/* prefer IPv6 */
q.name = name;
q.qclass = C_IN;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);
int query_ipv6 = 1, query_ipv4 = 1;
// AI_ADDRCONFIG , , IP ,
//_have_ipv6() _have_ipv4() IPv4 IPv6
if (pai->ai_flags & AI_ADDRCONFIG) {
query_ipv6 = _have_ipv6(netcontext->app_mark, netcontext->uid);
query_ipv4 = _have_ipv4(netcontext->app_mark, netcontext->uid);
}
// : AAAA A , AAAA A
if (query_ipv6) {
q.qtype = T_AAAA;
if (query_ipv4) {
// q2 q , AAAA A
q.next = &q2;
q2.name = name;
q2.qclass = C_IN;
q2.qtype = T_A;
q2.answer = buf2->buf;
q2.anslen = sizeof(buf2->buf);
}
} else if (query_ipv4) {
q.qtype = T_A;
} else {
free(buf);
free(buf2);
return NS_NOTFOUND;
}
break
// faimy, failmy A AAAA
case AF_INET:
q.name = name;
q.qclass = C_IN;
//A
q.qtype = T_A;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);
break;
case AF_INET6:
q.name = name;
q.qclass = C_IN;
//AAAA
q.qtype = T_AAAA;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);
break;
default:
// faimy
free(buf);
free(buf2);
return NS_UNAVAIL;
}
// resolver
res = __res_get_state();
if (res == NULL) {
free(buf);
free(buf2);
return NS_NOTFOUND;
}
/* this just sets our netid val in the thread private data so we don't have to
* modify the api's all the way down to res_send.c's res_nsend. We could
* fully populate the thread private data here, but if we get down there
* and have a cache hit that would be wasted, so we do the rest there on miss
*/
// netid、mark qhook res_stats , res_nsend()
res_setnetcontext(res, netcontext);
//getaddrinfo() res_search.c , ( !!!)
if (res_searchN(name, &q, res) < 0) {
__res_put_state(res);
free(buf);
free(buf2);
return NS_NOTFOUND;
}
//
ai = getanswer(buf, q.n, q.name, q.qtype, pai);
if (ai) {
cur->ai_next = ai;
while (cur && cur->ai_next)
cur = cur->ai_next;
}
// A , A
if (q.next) {
ai = getanswer(buf2, q2.n, q2.name, q2.qtype, pai);
if (ai)
cur->ai_next = ai;
}
free(buf);
free(buf2);
// ,
if (sentinel.ai_next == NULL) {
__res_put_state(res);
switch (h_errno) {
case HOST_NOT_FOUND:
return NS_NOTFOUND;
case TRY_AGAIN:
return NS_TRYAGAIN;
default:
return NS_UNAVAIL;
}
}
// RFC 6762 ,
_rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);
__res_put_state(res);
*((struct addrinfo **)rv) = sentinel.ai_next;
return NS_SUCCESS;
}
次のコードは分析を続行しません.resとsearch()の論理は非常に類似しており,興味のあるものは自分で分析することができる.
その他
MATCHマクロ
#define MATCH(x, y, w) \
((x) == (y) || (/*CONSTCOND*/(w) && ((x) == ANY || (y) == ANY)))
xとyが一致するかどうかを確認し、ブール値を返し、一致は2つの状況に分けられます.