Android DNSのgetaddrinfo()の実現


このノートでは,ライブラリ関数getaddrinfo()のコード実装を解析した.
プロトタイプ解読
int getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res);
  • hostname:gethostbyname()の入参hostnameと同じ、クエリーするドメイン名;
  • servname:クエリーするサービス名.指定すると、戻り値にサービス名に対応するポート番号も表示されます.つまり、この関数にはgetservbyname()の機能もあります.
  • hints:表面は暗示を意味し、このパラメータを使用してクエリー結果を制約し、条件を満たすクエリー結果だけを返すことができます.このパラメータをフィルタと呼ぶ.
  • res:このパラメータはクエリー結果アドレスへのポインタを保存するために使用され、クエリー結果を保存するメモリにはこの関数の内部割り当てがあり、使用が完了すると呼び出し者はfreeaddrinfo()クリアを呼び出す必要があり、呼び出し者はstruct addrinfo*を保存するアドレスを提供するだけでよい.

  • この関数の詳細については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つの関数について説明する必要があります.
  • getaddrinfo()は標準的なlibcインタフェースであり、システム内のFramework以下のC/C++コードはこのインタフェースを呼び出してDNSクエリーを実現することができる.
  • android_getaddrinfofornet()はAndroidによるlibcの拡張であり、FramworkがInetAddressクラスから提供されるインタフェースを介してDNSクエリを行うと、最終的にJNIを介してこの関数、すなわちこのインタフェースはFrameworkに使用される.

  • 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つの状況に分けられます.
  • xとyの値は確かに等しいので、一致し、対応する式の(x)=(y)
  • xおよびyは等しくないが、wパラメータは0ではなく、xおよびyのいずれかが共通であり、すなわち値はANY(0)であり、一致していると考えられる.実際,ここでのwの意味はwildcard,すなわちパスであり,wが0であればパスを考慮し,そうでなければxとyの真の値が等しいか否かでマッチングするか否かを判断することを示す.