Merge "Minor changes to the async DNS query JAVA API"

This commit is contained in:
Luke Huang
2019-03-08 03:19:55 +00:00
committed by Gerrit Code Review
3 changed files with 81 additions and 69 deletions

View File

@@ -28,7 +28,6 @@ import java.text.DecimalFormat;
import java.text.FieldPosition; import java.text.FieldPosition;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
/** /**
* Defines basic data for DNS protocol based on RFC 1035. * Defines basic data for DNS protocol based on RFC 1035.
@@ -42,7 +41,7 @@ public abstract class DnsPacket {
public final int id; public final int id;
public final int flags; public final int flags;
public final int rcode; public final int rcode;
private final int[] mSectionCount; private final int[] mRecordCount;
/** /**
* Create a new DnsHeader from a positioned ByteBuffer. * Create a new DnsHeader from a positioned ByteBuffer.
@@ -52,27 +51,32 @@ public abstract class DnsPacket {
* When this constructor returns, the reading position of the ByteBuffer has been * When this constructor returns, the reading position of the ByteBuffer has been
* advanced to the end of the DNS header record. * advanced to the end of the DNS header record.
* This is meant to chain with other methods reading a DNS response in sequence. * This is meant to chain with other methods reading a DNS response in sequence.
*
*/ */
DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
id = BitUtils.uint16(buf.getShort()); id = BitUtils.uint16(buf.getShort());
flags = BitUtils.uint16(buf.getShort()); flags = BitUtils.uint16(buf.getShort());
rcode = flags & 0xF; rcode = flags & 0xF;
mSectionCount = new int[NUM_SECTIONS]; mRecordCount = new int[NUM_SECTIONS];
for (int i = 0; i < NUM_SECTIONS; ++i) { for (int i = 0; i < NUM_SECTIONS; ++i) {
mSectionCount[i] = BitUtils.uint16(buf.getShort()); mRecordCount[i] = BitUtils.uint16(buf.getShort());
} }
} }
/** /**
* Get section count by section type. * Get record count by type.
*/ */
public int getSectionCount(int sectionType) { public int getRecordCount(int type) {
return mSectionCount[sectionType]; return mRecordCount[type];
} }
} }
public class DnsSection { /**
* It's used both for DNS questions and DNS resource records.
*
* DNS questions (No TTL/RDATA)
* DNS resource records (With TTL/RDATA)
*/
public class DnsRecord {
private static final int MAXNAMESIZE = 255; private static final int MAXNAMESIZE = 255;
private static final int MAXLABELSIZE = 63; private static final int MAXLABELSIZE = 63;
private static final int MAXLABELCOUNT = 128; private static final int MAXLABELCOUNT = 128;
@@ -81,57 +85,57 @@ public abstract class DnsPacket {
private final DecimalFormat byteFormat = new DecimalFormat(); private final DecimalFormat byteFormat = new DecimalFormat();
private final FieldPosition pos = new FieldPosition(0); private final FieldPosition pos = new FieldPosition(0);
private static final String TAG = "DnsSection"; private static final String TAG = "DnsRecord";
public final String dName; public final String dName;
public final int nsType; public final int nsType;
public final int nsClass; public final int nsClass;
public final long ttl; public final long ttl;
private final byte[] mRR; private final byte[] mRdata;
/** /**
* Create a new DnsSection from a positioned ByteBuffer. * Create a new DnsRecord from a positioned ByteBuffer.
* *
* The ByteBuffer must be in network byte order (which is the default). * @param ByteBuffer input of record, must be in network byte order
* Reads the passed ByteBuffer from its current position and decodes a DNS section. * (which is the default).
* Reads the passed ByteBuffer from its current position and decodes a DNS record.
* When this constructor returns, the reading position of the ByteBuffer has been * When this constructor returns, the reading position of the ByteBuffer has been
* advanced to the end of the DNS header record. * advanced to the end of the DNS header record.
* This is meant to chain with other methods reading a DNS response in sequence. * This is meant to chain with other methods reading a DNS response in sequence.
*
*/ */
DnsSection(int sectionType, @NonNull ByteBuffer buf) DnsRecord(int recordType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException { throws BufferUnderflowException, ParseException {
dName = parseName(buf, 0 /* Parse depth */); dName = parseName(buf, 0 /* Parse depth */);
if (dName.length() > MAXNAMESIZE) { if (dName.length() > MAXNAMESIZE) {
throw new ParseException("Parse name fail, name size is too long"); throw new ParseException(
"Parse name fail, name size is too long: " + dName.length());
} }
nsType = BitUtils.uint16(buf.getShort()); nsType = BitUtils.uint16(buf.getShort());
nsClass = BitUtils.uint16(buf.getShort()); nsClass = BitUtils.uint16(buf.getShort());
if (sectionType != QDSECTION) { if (recordType != QDSECTION) {
ttl = BitUtils.uint32(buf.getInt()); ttl = BitUtils.uint32(buf.getInt());
final int length = BitUtils.uint16(buf.getShort()); final int length = BitUtils.uint16(buf.getShort());
mRR = new byte[length]; mRdata = new byte[length];
buf.get(mRR); buf.get(mRdata);
} else { } else {
ttl = 0; ttl = 0;
mRR = null; mRdata = null;
} }
} }
/** /**
* Get a copy of rr. * Get a copy of rdata.
*/ */
@Nullable public byte[] getRR() { @Nullable
return (mRR == null) ? null : mRR.clone(); public byte[] getRR() {
return (mRdata == null) ? null : mRdata.clone();
} }
/** /**
* Convert label from {@code byte[]} to {@code String} * Convert label from {@code byte[]} to {@code String}
* *
* It follows the same converting rule as native layer. * Follows the same conversion rules of the native code (ns_name.c in libc)
* (See ns_name.c in libc)
*
*/ */
private String labelToString(@NonNull byte[] label) { private String labelToString(@NonNull byte[] label) {
final StringBuffer sb = new StringBuffer(); final StringBuffer sb = new StringBuffer();
@@ -139,13 +143,16 @@ public abstract class DnsPacket {
int b = BitUtils.uint8(label[i]); int b = BitUtils.uint8(label[i]);
// Control characters and non-ASCII characters. // Control characters and non-ASCII characters.
if (b <= 0x20 || b >= 0x7f) { if (b <= 0x20 || b >= 0x7f) {
// Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
sb.append('\\'); sb.append('\\');
byteFormat.format(b, sb, pos); byteFormat.format(b, sb, pos);
} else if (b == '"' || b == '.' || b == ';' || b == '\\' } else if (b == '"' || b == '.' || b == ';' || b == '\\'
|| b == '(' || b == ')' || b == '@' || b == '$') { || b == '(' || b == ')' || b == '@' || b == '$') {
// Append the byte as an escaped character, e.g., "\:" for 0x3a.
sb.append('\\'); sb.append('\\');
sb.append((char) b); sb.append((char) b);
} else { } else {
// Append the byte as a character, e.g., "a" for 0x61.
sb.append((char) b); sb.append((char) b);
} }
} }
@@ -154,7 +161,9 @@ public abstract class DnsPacket {
private String parseName(@NonNull ByteBuffer buf, int depth) throws private String parseName(@NonNull ByteBuffer buf, int depth) throws
BufferUnderflowException, ParseException { BufferUnderflowException, ParseException {
if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); if (depth > MAXLABELCOUNT) {
throw new ParseException("Failed to parse name, too many labels");
}
final int len = BitUtils.uint8(buf.get()); final int len = BitUtils.uint8(buf.get());
final int mask = len & NAME_COMPRESSION; final int mask = len & NAME_COMPRESSION;
if (0 == len) { if (0 == len) {
@@ -194,7 +203,7 @@ public abstract class DnsPacket {
private static final String TAG = DnsPacket.class.getSimpleName(); private static final String TAG = DnsPacket.class.getSimpleName();
protected final DnsHeader mHeader; protected final DnsHeader mHeader;
protected final List<DnsSection>[] mSections; protected final List<DnsRecord>[] mRecords;
public static class ParseException extends Exception { public static class ParseException extends Exception {
public ParseException(String msg) { public ParseException(String msg) {
@@ -216,18 +225,18 @@ public abstract class DnsPacket {
throw new ParseException("Parse Header fail, bad input data", e); throw new ParseException("Parse Header fail, bad input data", e);
} }
mSections = new ArrayList[NUM_SECTIONS]; mRecords = new ArrayList[NUM_SECTIONS];
for (int i = 0; i < NUM_SECTIONS; ++i) { for (int i = 0; i < NUM_SECTIONS; ++i) {
final int count = mHeader.getSectionCount(i); final int count = mHeader.getRecordCount(i);
if (count > 0) { if (count > 0) {
mSections[i] = new ArrayList(count); mRecords[i] = new ArrayList(count);
} }
for (int j = 0; j < count; ++j) { for (int j = 0; j < count; ++j) {
try { try {
mSections[i].add(new DnsSection(i, buffer)); mRecords[i].add(new DnsRecord(i, buffer));
} catch (BufferUnderflowException e) { } catch (BufferUnderflowException e) {
throw new ParseException("Parse section fail", e); throw new ParseException("Parse record fail", e);
} }
} }
} }

View File

@@ -43,6 +43,9 @@ import java.util.function.Consumer;
/** /**
* Dns resolver class for asynchronous dns querying * Dns resolver class for asynchronous dns querying
* *
* Note that if a client sends a query with more than 1 record in the question section but
* the remote dns server does not support this, it may not respond at all, leading to a timeout.
*
*/ */
public final class DnsResolver { public final class DnsResolver {
private static final String TAG = "DnsResolver"; private static final String TAG = "DnsResolver";
@@ -226,19 +229,19 @@ public final class DnsResolver {
if (mHeader.rcode != 0) { if (mHeader.rcode != 0) {
throw new ParseException("Response error, rcode:" + mHeader.rcode); throw new ParseException("Response error, rcode:" + mHeader.rcode);
} }
if (mHeader.getSectionCount(ANSECTION) == 0) { if (mHeader.getRecordCount(ANSECTION) == 0) {
throw new ParseException("No available answer"); throw new ParseException("No available answer");
} }
if (mHeader.getSectionCount(QDSECTION) == 0) { if (mHeader.getRecordCount(QDSECTION) == 0) {
throw new ParseException("No question found"); throw new ParseException("No question found");
} }
// Assume only one question per answer packet. (RFC1035) // Expect only one question in question section.
mQueryType = mSections[QDSECTION].get(0).nsType; mQueryType = mRecords[QDSECTION].get(0).nsType;
} }
public @NonNull List<InetAddress> getAddresses() { public @NonNull List<InetAddress> getAddresses() {
final List<InetAddress> results = new ArrayList<InetAddress>(); final List<InetAddress> results = new ArrayList<InetAddress>();
for (final DnsSection ansSec : mSections[ANSECTION]) { for (final DnsRecord ansSec : mRecords[ANSECTION]) {
// Only support A and AAAA, also ignore answers if query type != answer type. // Only support A and AAAA, also ignore answers if query type != answer type.
int nsType = ansSec.nsType; int nsType = ansSec.nsType;
if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {

View File

@@ -36,19 +36,19 @@ public class DnsPacketTest {
int qCount, int aCount, int nsCount, int arCount) { int qCount, int aCount, int nsCount, int arCount) {
assertEquals(header.id, id); assertEquals(header.id, id);
assertEquals(header.flags, flag); assertEquals(header.flags, flag);
assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount); assertEquals(header.getRecordCount(DnsPacket.QDSECTION), qCount);
assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount); assertEquals(header.getRecordCount(DnsPacket.ANSECTION), aCount);
assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount); assertEquals(header.getRecordCount(DnsPacket.NSSECTION), nsCount);
assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount); assertEquals(header.getRecordCount(DnsPacket.ARSECTION), arCount);
} }
private void assertSectionParses(DnsPacket.DnsSection section, String dname, private void assertRecordParses(DnsPacket.DnsRecord record, String dname,
int dtype, int dclass, int ttl, byte[] rr) { int dtype, int dclass, int ttl, byte[] rr) {
assertEquals(section.dName, dname); assertEquals(record.dName, dname);
assertEquals(section.nsType, dtype); assertEquals(record.nsType, dtype);
assertEquals(section.nsClass, dclass); assertEquals(record.nsClass, dclass);
assertEquals(section.ttl, ttl); assertEquals(record.ttl, ttl);
assertTrue(Arrays.equals(section.getRR(), rr)); assertTrue(Arrays.equals(record.getRR(), rr));
} }
class TestDnsPacket extends DnsPacket { class TestDnsPacket extends DnsPacket {
@@ -59,8 +59,8 @@ public class DnsPacketTest {
public DnsHeader getHeader() { public DnsHeader getHeader() {
return mHeader; return mHeader;
} }
public List<DnsSection> getSectionList(int secType) { public List<DnsRecord> getRecordList(int secType) {
return mSections[secType]; return mRecords[secType];
} }
} }
@@ -101,16 +101,16 @@ public class DnsPacketTest {
// Header part // Header part
assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0);
// Section part // Record part
List<DnsPacket.DnsSection> qdSectionList = List<DnsPacket.DnsRecord> qdRecordList =
packet.getSectionList(DnsPacket.QDSECTION); packet.getRecordList(DnsPacket.QDSECTION);
assertEquals(qdSectionList.size(), 1); assertEquals(qdRecordList.size(), 1);
assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null); assertRecordParses(qdRecordList.get(0), "www.google.com", 1, 1, 0, null);
List<DnsPacket.DnsSection> anSectionList = List<DnsPacket.DnsRecord> anRecordList =
packet.getSectionList(DnsPacket.ANSECTION); packet.getRecordList(DnsPacket.ANSECTION);
assertEquals(anSectionList.size(), 1); assertEquals(anRecordList.size(), 1);
assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b, assertRecordParses(anRecordList.get(0), "www.google.com", 1, 1, 0x12b,
new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 });
} }
@@ -143,16 +143,16 @@ public class DnsPacketTest {
// Header part // Header part
assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0);
// Section part // Record part
List<DnsPacket.DnsSection> qdSectionList = List<DnsPacket.DnsRecord> qdRecordList =
packet.getSectionList(DnsPacket.QDSECTION); packet.getRecordList(DnsPacket.QDSECTION);
assertEquals(qdSectionList.size(), 1); assertEquals(qdRecordList.size(), 1);
assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null); assertRecordParses(qdRecordList.get(0), "www.google.com", 28, 1, 0, null);
List<DnsPacket.DnsSection> anSectionList = List<DnsPacket.DnsRecord> anRecordList =
packet.getSectionList(DnsPacket.ANSECTION); packet.getRecordList(DnsPacket.ANSECTION);
assertEquals(anSectionList.size(), 1); assertEquals(anRecordList.size(), 1);
assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37, assertRecordParses(anRecordList.get(0), "www.google.com", 28, 1, 0x37,
new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 });
} }