Android okhttp3 DNS 底层实现追踪(一)

需求:Android 4.4 + okhttp 3.2;非root,在应用层,拿到DNS维度底层数据

方案:jni + hook libc.so中DNS关键getaddrinfo

分析:

1.人为制造DNS异常,抛出调用链路:


即:

java.net.InetAddress.lookupHostByName(InetAddress.java:424)
java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)
java.net.InetAddress.getAllByName(InetAddress.java:214)
okhttp3.Dns$1.lookup(Dns.java:39)
okhttp3.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:173)
okhttp3.internal.http.RouteSelector.nextProxy(RouteSelector.java:139)
okhttp3.internal.http.RouteSelector.next(RouteSelector.java:81)
okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:174)
okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:127)
okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:289)
okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
okhttp3.RealCall.getResponse(RealCall.java:240)
okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198)
com.baidu.uaq.agent.android.instrumentation.okhttp3util.OkHttp3Interceptor.intercept(OkHttp3Interceptor.java:52)
okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:187)
okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)
okhttp3.RealCall.execute(RealCall.java:57)


2.okhttp3.Dns$1.lookup(Dns.java:39)

Dns SYSTEM = new Dns() {
  @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
    if (hostname == null) throw new UnknownHostException("hostname == null");
    return Arrays.asList(InetAddress.getAllByName(hostname));
  }
};


3.

java.net.InetAddress.getAllByName(InetAddress.java:214)

java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)

java.net.InetAddress.lookupHostByName(InetAddress.java:424)

public static InetAddress[] getAllByName(String host) throws UnknownHostException {
    return getAllByNameImpl(host).clone();
}
 
/**
 * Returns the InetAddresses for {@code host}. The returned array is shared
 * and must be cloned before it is returned to application code.
 */
private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
    if (host == null || host.isEmpty()) {
        return loopbackAddresses();
    }
 
    // Is it a numeric address?
    InetAddress result = parseNumericAddressNoThrow(host);
    if (result != null) {
        result = disallowDeprecatedFormats(host, result);
        if (result == null) {
            throw new UnknownHostException("Deprecated IPv4 address format: " + host);
        }
        return new InetAddress[] { result };
    }
 
    return lookupHostByName(host).clone();
}
 
private static InetAddress parseNumericAddressNoThrow(String address) {
    // Accept IPv6 addresses (only) in square brackets for compatibility.
    if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {
        address = address.substring(1, address.length() - 1);
    }
    StructAddrinfo hints = new StructAddrinfo();
    hints.ai_flags = AI_NUMERICHOST;
    InetAddress[] addresses = null;
    try {
        addresses = Libcore.os.getaddrinfo(address, hints);
    } catch (GaiException ignored) {
    }
    return (addresses != null) ? addresses[0] : null;
}
 
private static InetAddress[] lookupHostByName(String host) throws UnknownHostException {
    BlockGuard.getThreadPolicy().onNetwork();
    // Do we have a result cached?
    Object cachedResult = addressCache.get(host);
    if (cachedResult != null) {
        if (cachedResult instanceof InetAddress[]) {
            // A cached positive result.
            return (InetAddress[]) cachedResult;
        } else {
            // A cached negative result.
            throw new UnknownHostException((String) cachedResult);
        }
    }
    try {
        StructAddrinfo hints = new StructAddrinfo();
        hints.ai_flags = AI_ADDRCONFIG;
        hints.ai_family = AF_UNSPEC;
        // If we don't specify a socket type, every address will appear twice, once
        // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
        // anyway, just pick one.
        hints.ai_socktype = SOCK_STREAM;
        InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
        // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
        for (InetAddress address : addresses) {
            address.hostName = host;
        }
        addressCache.put(host, addresses);
        return addresses;
    } catch (GaiException gaiException) {
        // If the failure appears to have been a lack of INTERNET permission, throw a clear
        // SecurityException to aid in debugging this common mistake.
        // http://code.google.com/p/android/issues/detail?id=15722
        if (gaiException.getCause() instanceof ErrnoException) {
            if (((ErrnoException) gaiException.getCause()).errno == EACCES) {
                throw new SecurityException("Permission denied (missing INTERNET permission?)", gaiException);
            }
        }
        // Otherwise, throw an UnknownHostException.
        String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
        addressCache.putUnknownHost(host, detailMessage);
        throw gaiException.rethrowAsUnknownHostException(detailMessage);
    }
}


4.Libcore.os.getaddrinfo

对于应用层

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url(ipaddr)
        .build();
Response response = client.newCall(request).execute();

会调用Libcore.os.getaddrinfo(host, hints)两次

第一次:

hints.ai_flags = AI_NUMERICHOST;

第二次:

hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

所以:在c层,可以用ai_flags和ai_socktype来区别这两个,从而过滤出真正的DNS请求


5. / libcore / luni / src / main / java / libcore / io / ForwardingOs.java

public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException {
    return os.getaddrinfo(node, hints);
}

6. / libcore / luni / src / main / java / libcore / io / Posix.java

public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;

7. / libcore / luni / src / main / native / libcore_io_Posix.cpp     JNI层,此方法为java的代理

static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) {
    ScopedUtfChars node(env, javaNode);
    if (node.c_str() == NULL) {
        return NULL;
    }
 
    static jfieldID flagsFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_flags", "I");
    static jfieldID familyFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_family", "I");
    static jfieldID socktypeFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_socktype", "I");
    static jfieldID protocolFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_protocol", "I");
 
    addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = env->GetIntField(javaHints, flagsFid);
    hints.ai_family = env->GetIntField(javaHints, familyFid);
    hints.ai_socktype = env->GetIntField(javaHints, socktypeFid);
    hints.ai_protocol = env->GetIntField(javaHints, protocolFid);
 
    addrinfo* addressList = NULL;
    errno = 0;
    int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);
    UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
    if (rc != 0) {
        throwGaiException(env, "getaddrinfo", rc);
        return NULL;
    }
 
    // Count results so we know how to size the output array.
    int addressCount = 0;
    for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
        if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
            ++addressCount;
        } else {
            ALOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
        }
    }
    if (addressCount == 0) {
        return NULL;
    }
 
    // Prepare output array.
    jobjectArray result = env->NewObjectArray(addressCount, JniConstants::inetAddressClass, NULL);
    if (result == NULL) {
        return NULL;
    }
 
    // Examine returned addresses one by one, save them in the output array.
    int index = 0;
    for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
        if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
            // Unknown address family. Skip this address.
            ALOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
            continue;
        }
 
        // Convert each IP address into a Java byte array.
        sockaddr_storage& address = *reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
        ScopedLocalRef<jobject> inetAddress(env, sockaddrToInetAddress(env, address, NULL));
        if (inetAddress.get() == NULL) {
            return NULL;
        }
        env->SetObjectArrayElement(result, index, inetAddress.get());
        ++index;
    }
    return result;
}

8.int rc = getaddrinfo(node.c_str(),NULL, &hints, &addressList);    调用BIONIC的libc标准库

====>>> /bionic/libc/netbsd/net/getaddrinfo.c

int getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{
	return android_getaddrinfoforiface(hostname, servname, hints, NULL, 0, res);
}

int android_getaddrinfoforiface(const char *hostname, const char *servname,
    const struct addrinfo *hints, const char *iface, int mark, struct addrinfo **res)
{
	struct addrinfo sentinel;
	struct addrinfo *cur;
	int error = 0;
	struct addrinfo ai;
	struct addrinfo ai0;
	struct addrinfo *pai;
	const struct explore *ex;
	const char* cache_mode = getenv("ANDROID_DNS_MODE");

	/* hostname is allowed to be NULL */
	/* servname is allowed to be NULL */
	/* hints is allowed to be NULL */
	assert(res != NULL);
	memset(&sentinel, 0, sizeof(sentinel));
	cur = &sentinel;
	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;

	if (hostname == NULL && servname == NULL)
		return EAI_NONAME;
	if (hints) {
		/* error check for hints */
		if (hints->ai_addrlen || hints->ai_canonname ||
		    hints->ai_addr || hints->ai_next)
			ERR(EAI_BADHINTS); /* xxx */
		if (hints->ai_flags & ~AI_MASK)
			ERR(EAI_BADFLAGS);
		switch (hints->ai_family) {
		case PF_UNSPEC:
		case PF_INET:
#ifdef INET6
		case PF_INET6:
#endif
			break;
		default:
			ERR(EAI_FAMILY);
		}
		memcpy(pai, hints, sizeof(*pai));

		/*
		 * if both socktype/protocol are specified, check if they
		 * are meaningful combination.
		 */
		if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) {
			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;
				if (pai->ai_socktype == ex->e_socktype
				 && pai->ai_protocol != ex->e_protocol) {
					ERR(EAI_BADHINTS);
				}
			}
		}
	}

	/*
	 * 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.
	 */
	if (MATCH_FAMILY(pai->ai_family, PF_INET, 1)
#ifdef PF_INET6
	 || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)
#endif
	    ) {
		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);

		*pai = ai0;
	}

	ai0 = *pai;

	/* 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)
			error = explore_null(pai, servname, &cur->ai_next);
		else
			error = explore_numeric_scope(pai, hostname, servname,
			    &cur->ai_next);

		if (error)
			goto free;

		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.
	 */
	if (sentinel.ai_next)
		goto good;

	if (hostname == NULL)
		ERR(EAI_NODATA);
	if (pai->ai_flags & AI_NUMERICHOST)
		ERR(EAI_NONAME);

        /*
         * BEGIN ANDROID CHANGES; proxying to the cache
         */
	if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
		// we're not the proxy - pass the request to them
		return android_getaddrinfo_proxy(hostname, servname, hints, res, iface);
	}

	/*
	 * hostname as alphabetical name.
	 * we would like to prefer AF_INET6 than AF_INET, so we'll make a
	 * outer loop by AFs.
	 */
	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;
		}

		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;

		error = explore_fqdn(pai, hostname, servname,
			&cur->ai_next, iface, mark);

		while (cur && cur->ai_next)
			cur = cur->ai_next;
	}

	/* XXX */
	if (sentinel.ai_next)
		error = 0;

	if (error)
		goto free;
	if (error == 0) {
		if (sentinel.ai_next) {
 good:
			*res = sentinel.ai_next;
			return SUCCESS;
		} else
			error = EAI_FAIL;
	}
 free:
 bad:
	if (sentinel.ai_next)
		freeaddrinfo(sentinel.ai_next);
	*res = NULL;
	return error;
}


总结:

hook libc.so中的getaddrinfo就能拿到DNS维度的数据,包括DNS时间、域名解析到哪几个ip,是否命中缓存、解析错误信息等。





  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello2mao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值