Merge changes from topic "Java async dns" am: 31ff0616d1
am: a8e16262f1
Change-Id: I305cf0ae791f9503519884bdf382863002357ac7
This commit is contained in:
@@ -27210,6 +27210,28 @@ package android.net {
|
||||
field public int serverAddress;
|
||||
}
|
||||
|
||||
public final class DnsResolver {
|
||||
method public static android.net.DnsResolver getInstance();
|
||||
method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
|
||||
method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
|
||||
method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
|
||||
field public static final int CLASS_IN = 1; // 0x1
|
||||
field public static final int FLAG_EMPTY = 0; // 0x0
|
||||
field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
|
||||
field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
|
||||
field public static final int FLAG_NO_RETRY = 1; // 0x1
|
||||
field public static final int TYPE_A = 1; // 0x1
|
||||
field public static final int TYPE_AAAA = 28; // 0x1c
|
||||
}
|
||||
|
||||
public static interface DnsResolver.InetAddressAnswerListener {
|
||||
method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
|
||||
}
|
||||
|
||||
public static interface DnsResolver.RawAnswerListener {
|
||||
method public void onAnswer(@Nullable byte[]);
|
||||
}
|
||||
|
||||
public class InetAddresses {
|
||||
method public static boolean isNumericAddress(String);
|
||||
method public static java.net.InetAddress parseNumericAddress(String);
|
||||
|
||||
235
core/java/android/net/DnsPacket.java
Normal file
235
core/java/android/net/DnsPacket.java
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.util.BitUtils;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Defines basic data for DNS protocol based on RFC 1035.
|
||||
* Subclasses create the specific format used in DNS packet.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class DnsPacket {
|
||||
public class DnsHeader {
|
||||
private static final String TAG = "DnsHeader";
|
||||
public final int id;
|
||||
public final int flags;
|
||||
public final int rcode;
|
||||
private final int[] mSectionCount;
|
||||
|
||||
/**
|
||||
* Create a new DnsHeader from a positioned ByteBuffer.
|
||||
*
|
||||
* The ByteBuffer must be in network byte order (which is the default).
|
||||
* Reads the passed ByteBuffer from its current position and decodes a DNS header.
|
||||
* When this constructor returns, the reading position of the ByteBuffer has been
|
||||
* advanced to the end of the DNS header record.
|
||||
* This is meant to chain with other methods reading a DNS response in sequence.
|
||||
*
|
||||
*/
|
||||
DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
|
||||
id = BitUtils.uint16(buf.getShort());
|
||||
flags = BitUtils.uint16(buf.getShort());
|
||||
rcode = flags & 0xF;
|
||||
mSectionCount = new int[NUM_SECTIONS];
|
||||
for (int i = 0; i < NUM_SECTIONS; ++i) {
|
||||
mSectionCount[i] = BitUtils.uint16(buf.getShort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section count by section type.
|
||||
*/
|
||||
public int getSectionCount(int sectionType) {
|
||||
return mSectionCount[sectionType];
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsSection {
|
||||
private static final int MAXNAMESIZE = 255;
|
||||
private static final int MAXLABELSIZE = 63;
|
||||
private static final int MAXLABELCOUNT = 128;
|
||||
private static final int NAME_NORMAL = 0;
|
||||
private static final int NAME_COMPRESSION = 0xC0;
|
||||
private final DecimalFormat byteFormat = new DecimalFormat();
|
||||
private final FieldPosition pos = new FieldPosition(0);
|
||||
|
||||
private static final String TAG = "DnsSection";
|
||||
|
||||
public final String dName;
|
||||
public final int nsType;
|
||||
public final int nsClass;
|
||||
public final long ttl;
|
||||
private final byte[] mRR;
|
||||
|
||||
/**
|
||||
* Create a new DnsSection from a positioned ByteBuffer.
|
||||
*
|
||||
* The ByteBuffer must be in network byte order (which is the default).
|
||||
* Reads the passed ByteBuffer from its current position and decodes a DNS section.
|
||||
* When this constructor returns, the reading position of the ByteBuffer has been
|
||||
* advanced to the end of the DNS header record.
|
||||
* This is meant to chain with other methods reading a DNS response in sequence.
|
||||
*
|
||||
*/
|
||||
DnsSection(int sectionType, @NonNull ByteBuffer buf)
|
||||
throws BufferUnderflowException, ParseException {
|
||||
dName = parseName(buf, 0 /* Parse depth */);
|
||||
if (dName.length() > MAXNAMESIZE) {
|
||||
throw new ParseException("Parse name fail, name size is too long");
|
||||
}
|
||||
nsType = BitUtils.uint16(buf.getShort());
|
||||
nsClass = BitUtils.uint16(buf.getShort());
|
||||
|
||||
if (sectionType != QDSECTION) {
|
||||
ttl = BitUtils.uint32(buf.getInt());
|
||||
final int length = BitUtils.uint16(buf.getShort());
|
||||
mRR = new byte[length];
|
||||
buf.get(mRR);
|
||||
} else {
|
||||
ttl = 0;
|
||||
mRR = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of rr.
|
||||
*/
|
||||
@Nullable public byte[] getRR() {
|
||||
return (mRR == null) ? null : mRR.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert label from {@code byte[]} to {@code String}
|
||||
*
|
||||
* It follows the same converting rule as native layer.
|
||||
* (See ns_name.c in libc)
|
||||
*
|
||||
*/
|
||||
private String labelToString(@NonNull byte[] label) {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < label.length; ++i) {
|
||||
int b = BitUtils.uint8(label[i]);
|
||||
// Control characters and non-ASCII characters.
|
||||
if (b <= 0x20 || b >= 0x7f) {
|
||||
sb.append('\\');
|
||||
byteFormat.format(b, sb, pos);
|
||||
} else if (b == '"' || b == '.' || b == ';' || b == '\\'
|
||||
|| b == '(' || b == ')' || b == '@' || b == '$') {
|
||||
sb.append('\\');
|
||||
sb.append((char) b);
|
||||
} else {
|
||||
sb.append((char) b);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String parseName(@NonNull ByteBuffer buf, int depth) throws
|
||||
BufferUnderflowException, ParseException {
|
||||
if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels");
|
||||
final int len = BitUtils.uint8(buf.get());
|
||||
final int mask = len & NAME_COMPRESSION;
|
||||
if (0 == len) {
|
||||
return "";
|
||||
} else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
|
||||
throw new ParseException("Parse name fail, bad label type");
|
||||
} else if (mask == NAME_COMPRESSION) {
|
||||
// Name compression based on RFC 1035 - 4.1.4 Message compression
|
||||
final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
|
||||
final int oldPos = buf.position();
|
||||
if (offset >= oldPos - 2) {
|
||||
throw new ParseException("Parse compression name fail, invalid compression");
|
||||
}
|
||||
buf.position(offset);
|
||||
final String pointed = parseName(buf, depth + 1);
|
||||
buf.position(oldPos);
|
||||
return pointed;
|
||||
} else {
|
||||
final byte[] label = new byte[len];
|
||||
buf.get(label);
|
||||
final String head = labelToString(label);
|
||||
if (head.length() > MAXLABELSIZE) {
|
||||
throw new ParseException("Parse name fail, invalid label length");
|
||||
}
|
||||
final String tail = parseName(buf, depth + 1);
|
||||
return TextUtils.isEmpty(tail) ? head : head + "." + tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final int QDSECTION = 0;
|
||||
public static final int ANSECTION = 1;
|
||||
public static final int NSSECTION = 2;
|
||||
public static final int ARSECTION = 3;
|
||||
private static final int NUM_SECTIONS = ARSECTION + 1;
|
||||
|
||||
private static final String TAG = DnsPacket.class.getSimpleName();
|
||||
|
||||
protected final DnsHeader mHeader;
|
||||
protected final List<DnsSection>[] mSections;
|
||||
|
||||
public static class ParseException extends Exception {
|
||||
public ParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ParseException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
protected DnsPacket(@NonNull byte[] data) throws ParseException {
|
||||
if (null == data) throw new ParseException("Parse header failed, null input data");
|
||||
final ByteBuffer buffer;
|
||||
try {
|
||||
buffer = ByteBuffer.wrap(data);
|
||||
mHeader = new DnsHeader(buffer);
|
||||
} catch (BufferUnderflowException e) {
|
||||
throw new ParseException("Parse Header fail, bad input data", e);
|
||||
}
|
||||
|
||||
mSections = new ArrayList[NUM_SECTIONS];
|
||||
|
||||
for (int i = 0; i < NUM_SECTIONS; ++i) {
|
||||
final int count = mHeader.getSectionCount(i);
|
||||
if (count > 0) {
|
||||
mSections[i] = new ArrayList(count);
|
||||
}
|
||||
for (int j = 0; j < count; ++j) {
|
||||
try {
|
||||
mSections[i].add(new DnsSection(i, buffer));
|
||||
} catch (BufferUnderflowException e) {
|
||||
throw new ParseException("Parse section fail", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
core/java/android/net/DnsResolver.java
Normal file
289
core/java/android/net/DnsResolver.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import android.system.ErrnoException;
|
||||
|
||||
/**
|
||||
* Native methods for managing network interfaces.
|
||||
*
|
||||
@@ -132,6 +134,32 @@ public class NetworkUtils {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
#define LOG_TAG "NetUtils"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jni.h"
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <nativehelper/ScopedLocalRef.h>
|
||||
#include "NetdClient.h"
|
||||
#include <utils/misc.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 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)
|
||||
{
|
||||
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 },
|
||||
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
|
||||
{ "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)
|
||||
|
||||
159
tests/net/java/android/net/DnsPacketTest.java
Normal file
159
tests/net/java/android/net/DnsPacketTest.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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 org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DnsPacketTest {
|
||||
private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag,
|
||||
int qCount, int aCount, int nsCount, int arCount) {
|
||||
assertEquals(header.id, id);
|
||||
assertEquals(header.flags, flag);
|
||||
assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount);
|
||||
assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount);
|
||||
assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount);
|
||||
assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount);
|
||||
}
|
||||
|
||||
private void assertSectionParses(DnsPacket.DnsSection section, String dname,
|
||||
int dtype, int dclass, int ttl, byte[] rr) {
|
||||
assertEquals(section.dName, dname);
|
||||
assertEquals(section.nsType, dtype);
|
||||
assertEquals(section.nsClass, dclass);
|
||||
assertEquals(section.ttl, ttl);
|
||||
assertTrue(Arrays.equals(section.getRR(), rr));
|
||||
}
|
||||
|
||||
class TestDnsPacket extends DnsPacket {
|
||||
TestDnsPacket(byte[] data) throws ParseException {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public DnsHeader getHeader() {
|
||||
return mHeader;
|
||||
}
|
||||
public List<DnsSection> getSectionList(int secType) {
|
||||
return mSections[secType];
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullDisallowed() {
|
||||
try {
|
||||
new TestDnsPacket(null);
|
||||
fail("Exception not thrown for null byte array");
|
||||
} catch (DnsPacket.ParseException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV4Answer() throws Exception {
|
||||
final byte[] v4blob = new byte[] {
|
||||
/* Header */
|
||||
0x55, 0x66, /* Transaction ID */
|
||||
(byte) 0x81, (byte) 0x80, /* Flags */
|
||||
0x00, 0x01, /* Questions */
|
||||
0x00, 0x01, /* Answer RRs */
|
||||
0x00, 0x00, /* Authority RRs */
|
||||
0x00, 0x00, /* Additional RRs */
|
||||
/* Queries */
|
||||
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
|
||||
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
|
||||
0x00, 0x01, /* Type */
|
||||
0x00, 0x01, /* Class */
|
||||
/* Answers */
|
||||
(byte) 0xc0, 0x0c, /* Name */
|
||||
0x00, 0x01, /* Type */
|
||||
0x00, 0x01, /* Class */
|
||||
0x00, 0x00, 0x01, 0x2b, /* TTL */
|
||||
0x00, 0x04, /* Data length */
|
||||
(byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
|
||||
};
|
||||
TestDnsPacket packet = new TestDnsPacket(v4blob);
|
||||
|
||||
// Header part
|
||||
assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
|
||||
|
||||
// Section part
|
||||
List<DnsPacket.DnsSection> qdSectionList =
|
||||
packet.getSectionList(DnsPacket.QDSECTION);
|
||||
assertEquals(qdSectionList.size(), 1);
|
||||
assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null);
|
||||
|
||||
List<DnsPacket.DnsSection> anSectionList =
|
||||
packet.getSectionList(DnsPacket.ANSECTION);
|
||||
assertEquals(anSectionList.size(), 1);
|
||||
assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b,
|
||||
new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV6Answer() throws Exception {
|
||||
final byte[] v6blob = new byte[] {
|
||||
/* Header */
|
||||
0x77, 0x22, /* Transaction ID */
|
||||
(byte) 0x81, (byte) 0x80, /* Flags */
|
||||
0x00, 0x01, /* Questions */
|
||||
0x00, 0x01, /* Answer RRs */
|
||||
0x00, 0x00, /* Authority RRs */
|
||||
0x00, 0x00, /* Additional RRs */
|
||||
/* Queries */
|
||||
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
|
||||
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
|
||||
0x00, 0x1c, /* Type */
|
||||
0x00, 0x01, /* Class */
|
||||
/* Answers */
|
||||
(byte) 0xc0, 0x0c, /* Name */
|
||||
0x00, 0x1c, /* Type */
|
||||
0x00, 0x01, /* Class */
|
||||
0x00, 0x00, 0x00, 0x37, /* TTL */
|
||||
0x00, 0x10, /* Data length */
|
||||
0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */
|
||||
};
|
||||
TestDnsPacket packet = new TestDnsPacket(v6blob);
|
||||
|
||||
// Header part
|
||||
assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
|
||||
|
||||
// Section part
|
||||
List<DnsPacket.DnsSection> qdSectionList =
|
||||
packet.getSectionList(DnsPacket.QDSECTION);
|
||||
assertEquals(qdSectionList.size(), 1);
|
||||
assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null);
|
||||
|
||||
List<DnsPacket.DnsSection> anSectionList =
|
||||
packet.getSectionList(DnsPacket.ANSECTION);
|
||||
assertEquals(anSectionList.size(), 1);
|
||||
assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37,
|
||||
new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user