Fixed DNS FQDN parsing

This commit is contained in:
Ziver Koc 2021-08-27 01:22:34 +02:00
parent f55473ced0
commit 600a4b648f
9 changed files with 86 additions and 64 deletions

View file

@ -16,7 +16,7 @@ public class PositionalInputStream extends FilterInputStream {
/**
* @param in the underlying input stream.
*/
protected PositionalInputStream(InputStream in) {
public PositionalInputStream(InputStream in) {
super(in);
}
@ -60,8 +60,8 @@ public class PositionalInputStream extends FilterInputStream {
}
@Override
public void mark(int readlimit) {
super.mark(readlimit);
public void mark(int readLimit) {
super.mark(readLimit);
synchronized(this) {
mark = pos;

View file

@ -78,7 +78,6 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
int id = 0; // Needs to be zero when doing multicast
activeProbes.add(id);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer);
DnsPacket dnsPacket = new DnsPacket();
dnsPacket.getHeader().id = id;
@ -87,7 +86,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
domain,
DnsConstants.TYPE.SRV,
DnsConstants.CLASS.IN));
dnsPacket.write(out);
dnsPacket.write(buffer);
DatagramPacket udpPacket = new DatagramPacket(
buffer.toByteArray(), buffer.size(),
@ -106,8 +105,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
try {
ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(),
packet.getOffset(), packet.getLength());
BinaryStructInputStream in = new BinaryStructInputStream(buffer);
DnsPacket dnsPacket = DnsPacket.read(in);
DnsPacket dnsPacket = DnsPacket.read(buffer);
//System.out.println("Received:\n" +ByteUtil.toFormattedString(packet.getData(), packet.getOffset(), packet.getLength()));
MultiPrintStream.out.dump(dnsPacket,3);

View file

@ -112,8 +112,7 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD
try {
ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(),
packet.getOffset(), packet.getLength());
BinaryStructInputStream in = new BinaryStructInputStream(buffer);
DnsPacket dnsPacket = DnsPacket.read(in);
DnsPacket dnsPacket = DnsPacket.read(buffer);
// Just handle queries and no responses
if (! dnsPacket.getHeader().flagQueryResponse) {
@ -121,9 +120,8 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD
if (response != null) {
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(outBuffer);
response.write(out);
out.close();
response.write(outBuffer);
outBuffer.close();
DatagramPacket outPacket = new DatagramPacket(
outBuffer.toByteArray(), outBuffer.size(),

View file

@ -24,10 +24,13 @@
package zutil.net.dns.packet;
import zutil.io.PositionalInputStream;
import zutil.parser.binary.BinaryStructInputStream;
import zutil.parser.binary.BinaryStructOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -94,7 +97,9 @@ public class DnsPacket {
}
public static DnsPacket read(BinaryStructInputStream structIn) throws IOException {
public static DnsPacket read(InputStream in) throws IOException {
BinaryStructInputStream structIn = new BinaryStructInputStream(new PositionalInputStream(in));
DnsPacket packet = new DnsPacket();
structIn.read(packet.header);
@ -116,7 +121,9 @@ public class DnsPacket {
}
}
public void write(BinaryStructOutputStream structOut) throws IOException {
public void write(OutputStream out) throws IOException {
BinaryStructOutputStream structOut = new BinaryStructOutputStream(out);
structOut.write(header);
for (DnsPacketQuestion question : questions)

View file

@ -24,40 +24,64 @@
package zutil.net.dns.packet;
import zutil.io.PositionalInputStream;
import zutil.parser.binary.BinaryFieldData;
import zutil.parser.binary.BinaryFieldSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
/**
* A serializer class that can read and write a DNS FQDN in binary format.
*/
public class FQDNStringSerializer implements BinaryFieldSerializer<String> {
private HashMap<Integer, String> stringCache = new HashMap<>();
public String read(InputStream in, BinaryFieldData field) throws IOException {
StringBuilder str = new StringBuilder();
StringBuilder buffer = new StringBuilder();
int pos = (int) ((PositionalInputStream) in).getPosition();
int c;
while ((c=in.read()) > 0) {
if (str.length() > 0) // Don't add dot to first loop
str.append('.');
if (buffer.length() > 0) // Don't add dot to first loop
buffer.append('.');
if ((c & 0b1100_0000) == 0b1100_0000) {
// This a offset pointer to the String data
// This is an offset pointer to the String data
int offset = (c & 0b0011_1111) << 8;
offset |= in.read() & 0b1111_1111;
str.append(offset);
if (stringCache.containsKey(offset))
buffer.append(stringCache.get(offset));
else
buffer.append('<').append(offset).append('>');
break; // PTR is always the last part of the FQDN
} else {
// Normal String data
// Read normal String data
for (int i = 0; i < c; ++i) {
str.append((char) in.read());
buffer.append((char) in.read());
}
}
}
return str.toString();
String output = buffer.toString();
// Populate cache
if (in instanceof PositionalInputStream) {
stringCache.put(pos, output);
for (int index = 0; index >= 0;) {
index = buffer.indexOf(".", index);
if (index >= 0) {
++index;
stringCache.put(pos + index, buffer.substring(index));
}
}
}
return output;
}
public void write(OutputStream out, String domain, BinaryFieldData field) throws IOException {
@ -70,5 +94,4 @@ public class FQDNStringSerializer implements BinaryFieldSerializer<String> {
}
out.write(0);
}
}

View file

@ -29,15 +29,13 @@ import java.io.InputStream;
import java.io.OutputStream;
/**
* An Interface where custom field parser and writer can be implemented.
* An Interface defining a custom field parser and writer.
* <p></p>
* One instance of the serializer and will have the scope of the methods
* {@link BinaryStructInputStream#read(BinaryStruct)} and {@link BinaryStructOutputStream#write(BinaryStruct)}
* where as it will be deallocated after the methods have returned.
* One singleton instance of the serializer will be instantiated for the lifetime of the
* {@link BinaryStructInputStream} and {@link BinaryStructOutputStream} objects.
* <p></p>
* NOTE: Partial octet serializing not supported.
*
* Created by Ziver on 2016-04-11.
*/
public interface BinaryFieldSerializer<T> {

View file

@ -35,7 +35,7 @@ import java.util.Map;
/**
* A stream class that parses a byte stream into binary struct objects.
* <p><p/>
* <p></p>
* Limitations:<br>
* - Does not support sub binary objects.<br>
*
@ -47,6 +47,9 @@ public class BinaryStructInputStream {
private byte data;
private int dataBitIndex = -1;
private Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
public BinaryStructInputStream(InputStream in) {
this.in = in;
}
@ -78,7 +81,6 @@ public class BinaryStructInputStream {
*/
public int read(BinaryStruct struct) throws IOException {
List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass());
Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
int totalReadLength = 0;
for (BinaryFieldData field : structDataList) {

View file

@ -36,7 +36,7 @@ import java.util.Map;
/**
* A stream class that generates a byte stream from a binary struct objects.
* <p><p/>
* <p></p>
* Limitations:<br>
* - Does not support sub binary objects.<br>
*
@ -48,6 +48,8 @@ public class BinaryStructOutputStream {
private byte rest;
private int restBitLength; // length from Most Significant Bit
private Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
public BinaryStructOutputStream(OutputStream out) {
this.out = out;
@ -88,7 +90,6 @@ public class BinaryStructOutputStream {
*/
public void write(BinaryStruct struct) throws IOException {
List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass());
Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
for (BinaryFieldData field : structDataList) {
if (field.hasSerializer()) {

View file

@ -75,8 +75,7 @@ public class DnsPacketTest {
packet.getHeader().setDefaultQueryData();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer);
packet.write(out);
packet.write(buffer);
byte[] data = buffer.toByteArray();
byte[] expected = {
@ -97,8 +96,7 @@ public class DnsPacketTest {
"appletv.local", DnsConstants.TYPE.A, DnsConstants.CLASS.IN));
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer);
packet.write(out);
packet.write(buffer);
byte[] data = buffer.toByteArray();
byte[] expected = {
@ -155,8 +153,7 @@ public class DnsPacketTest {
0xc0, 0x0c, 0x00, 0x04, 0x40, 0x00, 0x00, 0x08 // NSEC
};
ByteArrayInputStream buffer = new ByteArrayInputStream(Converter.toBytes(input));
BinaryStructInputStream in = new BinaryStructInputStream(buffer);
DnsPacket packet = DnsPacket.read(in);
DnsPacket packet = DnsPacket.read(buffer);
// Assert Header
assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -217,10 +214,9 @@ Domain Name System (query)
question.clazz = DnsConstants.CLASS.IN;
packet.addQuestion(question);
ByteArrayOutputStream buff = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buff);
packet.write(out);
byte[] data = buff.toByteArray();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
packet.write(buffer);
byte[] data = buffer.toByteArray();
assertEquals((
"241a 01 00 00 01 00 00 00 00 00 00 " +
"03 77 77 77 06 67 6f 6f 67 6c 65 03 63 6f 6d 00 0001 0001"
@ -297,8 +293,7 @@ Domain Name System (response)
"c02c 0001 0001 000000e3 0004 42 f9 59 68" // Answer3
).replace(" ", ""));
ByteArrayInputStream buffer = new ByteArrayInputStream(input);
BinaryStructInputStream in = new BinaryStructInputStream(buffer);
DnsPacket packet = DnsPacket.read(in);
DnsPacket packet = DnsPacket.read(buffer);
assertEquals("id", 0x241a, packet.getHeader().id);
assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -315,19 +310,20 @@ Domain Name System (response)
// Answer
DnsPacketResource answer = packet.getAnswerRecords().get(0);
assertEquals("NAME", "12", answer.name);
assertEquals("NAME", "www.google.com", answer.name);
//assertEquals("NAME", "<12>", answer.name);
assertEquals("TYPE", DnsConstants.TYPE.CNAME, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 337977, answer.ttl);
answer = packet.getAnswerRecords().get(1);
assertEquals("NAME", "44", answer.name);
assertEquals("NAME", "<44>", answer.name);
assertEquals("TYPE", DnsConstants.TYPE.A, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 227, answer.ttl);
answer = packet.getAnswerRecords().get(2);
assertEquals("NAME", "44", answer.name);
assertEquals("NAME", "<44>", answer.name);
assertEquals("TYPE", DnsConstants.TYPE.A, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 227, answer.ttl);
@ -415,17 +411,16 @@ Multicast Domain Name System (query)
byte[] input = Converter.hexToByte((
"00 00 00 00 00 06 00 00 00 00 00 01" + // Header
"0f 5f 63 6f 6d 70 61 6e 69 6f 6e 2d 6c 69 6e 6b 04 5f 74 63 70 05 6c 6f 63 61 6c 00 00 0c 00 01" + // Query PTR: _companion-link._tcp.local
"08 5f 68 6f 6d 65 6b 69 74 c0 1c 00 0c 00 01" + // Query PTR: _homekit._tcp.local
"03 68 61 6c c0 21 00 41 00 01" + // Query Unknown: hal.local
"c0 3b 00 1c 00 01" + // Query IPv6: hal.local
"c0 3b 00 01 00 01" + // Query IPv4: hal.local
"0f 5f 63 6f 6d 70 61 6e 69 6f 6e 2d 6c 69 6e 6b 04 5f 74 63 70 05 6c 6f 63 61 6c 00 00 0c 00 01" + // (offset 12) Query PTR: _companion-link._tcp.local
"08 5f 68 6f 6d 65 6b 69 74 c0 1c 00 0c 00 01" + // (offset 44) Query PTR: _homekit._tcp.local
"03 68 61 6c c0 21 00 41 00 01" + // (offset 59) Query Unknown: hal.local
"c0 3b 00 1c 00 01" + // (offset 69) Query IPv6: hal.local
"c0 3b 00 01 00 01" + // (offset 75) Query IPv4: hal.local
"0c 5f 73 6c 65 65 70 2d 70 72 6f 78 79 04 5f 75 64 70 c0 21 00 0c 00 01" + // Query PTR: _sleep-proxy._udp.local
"00 00 29 05 a0 00 00 11 94 00 12 00 04 00 0e 00 65 7a e6 ba 29 34 00 d6 07 3a f2 2e e7" // Additional records
).replace(" ", ""));
ByteArrayInputStream buffer = new ByteArrayInputStream(input);
BinaryStructInputStream in = new BinaryStructInputStream(buffer);
DnsPacket packet = DnsPacket.read(in);
DnsPacket packet = DnsPacket.read(buffer);
assertEquals("id", 0x00, packet.getHeader().id);
assertFalse("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -441,31 +436,31 @@ Multicast Domain Name System (query)
assertEquals("clazz", DnsConstants.CLASS.IN, question1.clazz);
DnsPacketQuestion question2 = packet.getQuestions().get(1);
//assertEquals("qNAME", "_homekit._tcp.local", question2.name);
assertEquals("qNAME", "_homekit.28", question2.name); // TODO: Fix support for string pointers
assertEquals("qNAME", "_homekit._tcp.local", question2.name);
//assertEquals("qNAME", "_homekit.<28>", question2.name); // TODO: Fix support for string pointers
assertEquals("type", DnsConstants.TYPE.PTR, question2.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question2.clazz);
DnsPacketQuestion question3 = packet.getQuestions().get(2);
//assertEquals("qNAME", "hal.local", question3.name);
assertEquals("qNAME", "hal.33", question3.name);
assertEquals("qNAME", "hal.local", question3.name);
//assertEquals("qNAME", "hal.<33>", question3.name);
assertEquals("clazz", DnsConstants.CLASS.IN, question3.clazz);
DnsPacketQuestion question4 = packet.getQuestions().get(3);
//assertEquals("qNAME", "hal.local", question4.name);
assertEquals("qNAME", "59", question4.name);
assertEquals("qNAME", "hal.local", question4.name);
//assertEquals("qNAME", "<59>", question4.name);
assertEquals("type", DnsConstants.TYPE.AAAA, question4.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question4.clazz);
DnsPacketQuestion question5 = packet.getQuestions().get(4);
//assertEquals("qNAME", "hal.local", question5.name);
assertEquals("qNAME", "59", question5.name);
assertEquals("qNAME", "hal.local", question5.name);
//assertEquals("qNAME", "<59>", question5.name);
assertEquals("type", DnsConstants.TYPE.A, question5.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question5.clazz);
DnsPacketQuestion question6 = packet.getQuestions().get(5);
//assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name);
assertEquals("qNAME", "_sleep-proxy._udp.33", question6.name);
assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name);
//assertEquals("qNAME", "_sleep-proxy._udp.<33>", question6.name);
assertEquals("type", DnsConstants.TYPE.PTR, question6.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question6.clazz);
}