Merge changes from topic "Java async dns"

am: 6fd9d58242

Change-Id: Ia08a8300887315d673f398b1340b2f44ce501b14
This commit is contained in:
Luke Huang
2019-01-23 01:06:19 -08:00
committed by android-build-merger
3 changed files with 405 additions and 0 deletions

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import static android.net.NetworkUtils.resNetworkQuery;
import static android.net.NetworkUtils.resNetworkResult;
import static android.net.NetworkUtils.resNetworkSend;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.MessageQueue;
import android.system.ErrnoException;
import android.util.Log;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Dns resolver class for asynchronous dns querying
*
*/
public final class DnsResolver {
private static final String TAG = "DnsResolver";
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
private static final int MAXPACKET = 8 * 1024;
@IntDef(prefix = { "CLASS_" }, value = {
CLASS_IN
})
@Retention(RetentionPolicy.SOURCE)
@interface QueryClass {}
public static final int CLASS_IN = 1;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_A,
TYPE_AAAA
})
@Retention(RetentionPolicy.SOURCE)
@interface QueryType {}
public static final int TYPE_A = 1;
public static final int TYPE_AAAA = 28;
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_EMPTY,
FLAG_NO_RETRY,
FLAG_NO_CACHE_STORE,
FLAG_NO_CACHE_LOOKUP
})
@Retention(RetentionPolicy.SOURCE)
@interface QueryFlag {}
public static final int FLAG_EMPTY = 0;
public static final int FLAG_NO_RETRY = 1 << 0;
public static final int FLAG_NO_CACHE_STORE = 1 << 1;
public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
private static final int DNS_RAW_RESPONSE = 1;
private static final int NETID_UNSET = 0;
private static final DnsResolver sInstance = new DnsResolver();
/**
* listener for receiving raw answers
*/
public interface RawAnswerListener {
/**
* {@code byte[]} is {@code null} if query timed out
*/
void onAnswer(@Nullable byte[] answer);
}
/**
* listener for receiving parsed answers
*/
public interface InetAddressAnswerListener {
/**
* Will be called exactly once with all the answers to the query.
* size of addresses will be zero if no available answer could be parsed.
*/
void onAnswer(@NonNull List<InetAddress> addresses);
}
/**
* Get instance for DnsResolver
*/
public static DnsResolver getInstance() {
return sInstance;
}
private DnsResolver() {}
/**
* Pass in a blob and corresponding setting,
* get a blob back asynchronously with the entire raw answer.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
* @param query blob message
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link RawAnswerListener} will be invoked.
* @param listener a {@link RawAnswerListener} which will be called to notify the caller
* of the result of dns query.
*/
public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
@NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
final FileDescriptor queryfd = resNetworkSend((network != null
? network.netId : NETID_UNSET), query, query.length, flags);
registerFDListener(handler.getLooper().getQueue(), queryfd,
answerbuf -> listener.onAnswer(answerbuf));
}
/**
* Pass in a domain name and corresponding setting,
* get a blob back asynchronously with the entire raw answer.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
* @param domain domain name for querying
* @param nsClass dns class as one of the CLASS_* constants
* @param nsType dns resource record (RR) type as one of the TYPE_* constants
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link RawAnswerListener} will be invoked.
* @param listener a {@link RawAnswerListener} which will be called to notify the caller
* of the result of dns query.
*/
public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
@QueryType int nsType, @QueryFlag int flags,
@NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
final FileDescriptor queryfd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
registerFDListener(handler.getLooper().getQueue(), queryfd,
answerbuf -> listener.onAnswer(answerbuf));
}
/**
* Pass in a domain name and corresponding setting,
* get back a set of InetAddresses asynchronously.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
* @param domain domain name for querying
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link InetAddressAnswerListener} will be invoked.
* @param listener an {@link InetAddressAnswerListener} which will be called to
* notify the caller of the result of dns query.
*
*/
public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
@NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
throws ErrnoException {
final FileDescriptor v4fd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
final FileDescriptor v6fd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
final InetAddressAnswerAccumulator accmulator =
new InetAddressAnswerAccumulator(2, listener);
final Consumer<byte[]> consumer = answerbuf ->
accmulator.accumulate(parseAnswers(answerbuf));
registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
}
private void registerFDListener(@NonNull MessageQueue queue,
@NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
queue.addOnFileDescriptorEventListener(
queryfd,
FD_EVENTS,
(fd, events) -> {
byte[] answerbuf = null;
try {
// TODO: Implement result function in Java side instead of using JNI
// Because JNI method close fd prior than unregistering fd on
// event listener.
answerbuf = resNetworkResult(fd);
} catch (ErrnoException e) {
Log.e(TAG, "resNetworkResult:" + e.toString());
}
answerConsumer.accept(answerbuf);
// Unregister this fd listener
return 0;
});
}
private class DnsAddressAnswer extends DnsPacket {
private static final String TAG = "DnsResolver.DnsAddressAnswer";
private static final boolean DBG = false;
private final int mQueryType;
DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
super(data);
if ((mHeader.flags & (1 << 15)) == 0) {
throw new ParseException("Not an answer packet");
}
if (mHeader.rcode != 0) {
throw new ParseException("Response error, rcode:" + mHeader.rcode);
}
if (mHeader.getSectionCount(ANSECTION) == 0) {
throw new ParseException("No available answer");
}
if (mHeader.getSectionCount(QDSECTION) == 0) {
throw new ParseException("No question found");
}
// Assume only one question per answer packet. (RFC1035)
mQueryType = mSections[QDSECTION].get(0).nsType;
}
public @NonNull List<InetAddress> getAddresses() {
final List<InetAddress> results = new ArrayList<InetAddress>();
for (final DnsSection ansSec : mSections[ANSECTION]) {
// Only support A and AAAA, also ignore answers if query type != answer type.
int nsType = ansSec.nsType;
if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
continue;
}
try {
results.add(InetAddress.getByAddress(ansSec.getRR()));
} catch (UnknownHostException e) {
if (DBG) {
Log.w(TAG, "rr to address fail");
}
}
}
return results;
}
}
private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
try {
return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
} catch (DnsPacket.ParseException e) {
Log.e(TAG, "Parse answer fail " + e.getMessage());
return null;
}
}
private class InetAddressAnswerAccumulator {
private final List<InetAddress> mAllAnswers;
private final InetAddressAnswerListener mAnswerListener;
private final int mTargetAnswerCount;
private int mReceivedAnswerCount = 0;
InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
mTargetAnswerCount = size;
mAllAnswers = new ArrayList<>();
mAnswerListener = listener;
}
public void accumulate(@Nullable List<InetAddress> answer) {
if (null != answer) {
mAllAnswers.addAll(answer);
}
if (++mReceivedAnswerCount == mTargetAnswerCount) {
mAnswerListener.onAnswer(mAllAnswers);
}
}
}
}

View File

@@ -34,6 +34,8 @@ import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import java.util.TreeSet; import java.util.TreeSet;
import android.system.ErrnoException;
/** /**
* Native methods for managing network interfaces. * Native methods for managing network interfaces.
* *
@@ -132,6 +134,32 @@ public class NetworkUtils {
*/ */
public native static boolean queryUserAccess(int uid, int netId); public native static boolean queryUserAccess(int uid, int netId);
/**
* DNS resolver series jni method.
* Issue the query {@code msg} on the network designated by {@code netId}.
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
public static native FileDescriptor resNetworkSend(
int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
* Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
* with Domain Name {@code dname} on the network designated by {@code netId}.
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
public static native FileDescriptor resNetworkQuery(
int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
* Read a result for the query associated with the {@code fd}.
* @return a byte array containing blob answer
*/
public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
/** /**
* Add an entry into the ARP cache. * Add an entry into the ARP cache.
*/ */

View File

@@ -16,8 +16,11 @@
#define LOG_TAG "NetUtils" #define LOG_TAG "NetUtils"
#include <vector>
#include "jni.h" #include "jni.h"
#include <nativehelper/JNIHelp.h> #include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include "NetdClient.h" #include "NetdClient.h"
#include <utils/misc.h> #include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h> #include <android_runtime/AndroidRuntime.h>
@@ -55,6 +58,31 @@ static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udp
static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
static const uint16_t kDhcpClientPort = 68; static const uint16_t kDhcpClientPort = 68;
constexpr int MAXPACKETSIZE = 8 * 1024;
// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
constexpr int MAXCMDSIZE = 1024;
static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
if (detailMessage.get() == NULL) {
// Not really much we can do here. We're probably dead in the water,
// but let's try to stumble on...
env->ExceptionClear();
}
static jclass errnoExceptionClass =
MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
static jmethodID errnoExceptionCtor =
GetMethodIDOrDie(env, errnoExceptionClass,
"<init>", "(Ljava/lang/String;I)V");
jobject exception = env->NewObject(errnoExceptionClass,
errnoExceptionCtor,
detailMessage.get(),
error);
env->Throw(reinterpret_cast<jthrowable>(exception));
}
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{ {
struct sock_filter filter_code[] = { struct sock_filter filter_code[] = {
@@ -372,6 +400,63 @@ static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray
} }
} }
static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
jstring dname, jint ns_class, jint ns_type, jint flags) {
const jsize javaCharsCount = env->GetStringLength(dname);
const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
// Only allow dname which could be simply formatted to UTF8.
// In native layer, res_mkquery would re-format the input char array to packet.
std::vector<char> queryname(byteCountUTF8 + 1, 0);
env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
throwErrnoException(env, "resNetworkQuery", -fd);
return nullptr;
}
return jniCreateFileDescriptor(env, fd);
}
static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
jbyteArray msg, jint msgLen, jint flags) {
uint8_t data[MAXCMDSIZE];
checkLenAndCopy(env, msg, msgLen, data);
int fd = resNetworkSend(netId, data, msgLen, flags);
if (fd < 0) {
throwErrnoException(env, "resNetworkSend", -fd);
return nullptr;
}
return jniCreateFileDescriptor(env, fd);
}
static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
int rcode;
std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
if (res < 0) {
throwErrnoException(env, "resNetworkResult", -res);
return nullptr;
}
jbyteArray answer = env->NewByteArray(res);
if (answer == nullptr) {
throwErrnoException(env, "resNetworkResult", ENOMEM);
return nullptr;
} else {
env->SetByteArrayRegion(answer, 0, res,
reinterpret_cast<jbyte*>(buf.data()));
}
return answer;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -391,6 +476,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
}; };
int register_android_net_NetworkUtils(JNIEnv* env) int register_android_net_NetworkUtils(JNIEnv* env)