需求: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 lookup(String hostname) throws UnknownHostException {
if (hostname == null) throw new UnknownHostException("hostname == null");
return Arrays.asList(InetAddress.getAllByName(hostname));
}
};
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);
}
}
对于应用层
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请求
public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException {
return os.getaddrinfo(node, hints);
}
public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
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 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(ai->ai_addr);
ScopedLocalRef 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,是否命中缓存、解析错误信息等。