Removed coroutines from parts of the networking message processing.
parent
8d73a3018a
commit
5b18e16d89
|
@ -0,0 +1,6 @@
|
||||||
|
package org.handwerkszeug.chain;
|
||||||
|
|
||||||
|
public interface Chain<CTX, R extends ChainResult> {
|
||||||
|
|
||||||
|
R execute(CTX context);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.handwerkszeug.chain;
|
||||||
|
|
||||||
|
public interface ChainResult {
|
||||||
|
|
||||||
|
boolean hasNext();
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.handwerkszeug.chain.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
|
||||||
|
public class DefaultChainExecutor<CTX, R extends ChainResult> implements
|
||||||
|
Chain<CTX, R> {
|
||||||
|
|
||||||
|
protected List<Chain<CTX, R>> chains = new ArrayList<Chain<CTX, R>>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R execute(CTX context) {
|
||||||
|
R r = null;
|
||||||
|
for (Chain<CTX, R> c : this.chains) {
|
||||||
|
r = c.execute(context);
|
||||||
|
if (r.hasNext() == false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Chain<CTX, R> c) {
|
||||||
|
this.chains.add(c);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.handwerkszeug.chain.impl;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
|
||||||
|
public class SimpleChainResult implements ChainResult {
|
||||||
|
|
||||||
|
public static final ChainResult Continue = new SimpleChainResult(true);
|
||||||
|
|
||||||
|
public static final ChainResult Terminate = new SimpleChainResult();
|
||||||
|
|
||||||
|
protected boolean hasNext;
|
||||||
|
|
||||||
|
public SimpleChainResult() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleChainResult(boolean hasNext) {
|
||||||
|
this.hasNext = hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
public static final String BASE_NAME = "org.handwerkszeug.dns";
|
||||||
|
public static final String SYSTEM_PROPERTY_NAMESERVERS = BASE_NAME
|
||||||
|
+ ".nameservers";
|
||||||
|
|
||||||
|
public static final String SYSTEM_PROPERTY_ACTIVE_NAMESERVER_CONTAINER = BASE_NAME
|
||||||
|
+ ".container";
|
||||||
|
|
||||||
|
public static final int DEFAULT_PORT = 53;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.handwerkszeug.util.EnumUtil;
|
||||||
|
import org.handwerkszeug.util.VariableEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC1035 3.2.4. CLASS values
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public enum DNSClass implements VariableEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the Internet
|
||||||
|
*/
|
||||||
|
IN(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the CSNET class (Obsolete - used only for examples in some obsolete RFCs)
|
||||||
|
*/
|
||||||
|
CS(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the CHAOS class
|
||||||
|
*/
|
||||||
|
CH(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hesiod [Dyer 87]
|
||||||
|
*/
|
||||||
|
HS(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* any class
|
||||||
|
*/
|
||||||
|
ANY(255);
|
||||||
|
|
||||||
|
private int value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int value() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DNSClass(int i) {
|
||||||
|
this.value = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DNSClass valueOf(int value) {
|
||||||
|
return EnumUtil.find(DNSClass.values(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DNSClass find(String value) {
|
||||||
|
return EnumUtil.find(DNSClass.values(), value, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.record.AbstractRecord;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC1035 4. MESSAGES
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* +---------------------+
|
||||||
|
* | Header |
|
||||||
|
* +---------------------+
|
||||||
|
* | Question | the question for the name server
|
||||||
|
* +---------------------+
|
||||||
|
* | Answer | RRs answering the question
|
||||||
|
* +---------------------+
|
||||||
|
* | Authority | RRs pointing toward an authority
|
||||||
|
* +---------------------+
|
||||||
|
* | Additional | RRs holding additional information
|
||||||
|
* +---------------------+
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class DNSMessage {
|
||||||
|
|
||||||
|
protected Header header;
|
||||||
|
protected List<ResourceRecord> question;
|
||||||
|
protected List<ResourceRecord> answer;
|
||||||
|
protected List<ResourceRecord> authority;
|
||||||
|
protected List<ResourceRecord> additional;
|
||||||
|
|
||||||
|
protected int messageSize;
|
||||||
|
|
||||||
|
public DNSMessage(Header header) {
|
||||||
|
this.header(header);
|
||||||
|
this.question = new ArrayList<ResourceRecord>();
|
||||||
|
this.answer = new ArrayList<ResourceRecord>();
|
||||||
|
this.authority = new ArrayList<ResourceRecord>();
|
||||||
|
this.additional = new ArrayList<ResourceRecord>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DNSMessage() {
|
||||||
|
this(new Header());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DNSMessage(DNSMessage from) {
|
||||||
|
this(new Header(from.header()));
|
||||||
|
|
||||||
|
this.question().addAll(from.question());
|
||||||
|
this.answer().addAll(from.answer());
|
||||||
|
this.authority().addAll(from.authority());
|
||||||
|
this.additional().addAll(from.additional());
|
||||||
|
|
||||||
|
this.messageSize(from.messageSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DNSMessage(ByteBuf buffer) {
|
||||||
|
this.header = new Header(buffer);
|
||||||
|
if (this.header.rcode().equals(RCode.FormErr)) {
|
||||||
|
this.question = Collections.emptyList();
|
||||||
|
this.answer = Collections.emptyList();
|
||||||
|
this.authority = Collections.emptyList();
|
||||||
|
this.additional = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.parse(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parse(ByteBuf buffer) {
|
||||||
|
Header header = this.header();
|
||||||
|
|
||||||
|
int q = header.qdcount();
|
||||||
|
if (q < 1) {
|
||||||
|
this.question = Collections.emptyList();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.question(new ArrayList<ResourceRecord>(q));
|
||||||
|
for (int i = 0; i < q; i++) {
|
||||||
|
this.question()
|
||||||
|
.add(AbstractRecord.parseSection(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.answer(parse(buffer, header.ancount()));
|
||||||
|
this.authority(parse(buffer, header.nscount()));
|
||||||
|
this.additional(parse(buffer, header.arcount()));
|
||||||
|
|
||||||
|
this.messageSize(buffer.readerIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static
|
||||||
|
List<ResourceRecord> parse(ByteBuf buffer, int size) {
|
||||||
|
if (size < 1) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResourceRecord> result = new ArrayList<ResourceRecord>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ResourceRecord rr = AbstractRecord.parseSection(buffer);
|
||||||
|
rr.parse(buffer);
|
||||||
|
result.add(rr);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(ByteBuf buffer) {
|
||||||
|
header().qdcount(this.question().size());
|
||||||
|
header().ancount(this.answer().size());
|
||||||
|
header().nscount(this.authority().size());
|
||||||
|
header().arcount(this.additional().size());
|
||||||
|
|
||||||
|
header().write(buffer);
|
||||||
|
NameCompressor nc = new SimpleNameCompressor();
|
||||||
|
for (ResourceRecord rr : this.question()) {
|
||||||
|
AbstractRecord.writeSection(buffer, nc, rr);
|
||||||
|
}
|
||||||
|
write(buffer, nc, answer());
|
||||||
|
write(buffer, nc, authority());
|
||||||
|
write(buffer, nc, additional());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void write(ByteBuf buffer, NameCompressor compressor,
|
||||||
|
List<ResourceRecord> list) {
|
||||||
|
for (ResourceRecord rr : list) {
|
||||||
|
AbstractRecord.writeSection(buffer, compressor, rr);
|
||||||
|
rr.write(buffer, compressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header header() {
|
||||||
|
return this.header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void header(Header header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4.1.2. Question section format
|
||||||
|
*/
|
||||||
|
public List<ResourceRecord> question() {
|
||||||
|
return this.question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void question(List<ResourceRecord> list) {
|
||||||
|
this.question = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceRecord> answer() {
|
||||||
|
return this.answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void answer(List<ResourceRecord> list) {
|
||||||
|
this.answer = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceRecord> authority() {
|
||||||
|
return this.authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authority(List<ResourceRecord> list) {
|
||||||
|
this.authority = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceRecord> additional() {
|
||||||
|
return this.additional;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void additional(List<ResourceRecord> list) {
|
||||||
|
this.additional = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int messageSize() {
|
||||||
|
return this.messageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void messageSize(int size) {
|
||||||
|
this.messageSize = size;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,362 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC1035 4.1.1. Header section format
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1 1 1 1 1 1
|
||||||
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | ID |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* |QR| DnsOpCode |AA|TC|RD|RA| Z | RCODE |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | QDCOUNT |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | ANCOUNT |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | NSCOUNT |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | ARCOUNT |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class Header {
|
||||||
|
|
||||||
|
static final int MIN_USHORT = 0;
|
||||||
|
static final int MAX_USHORT = 0xFFFF;
|
||||||
|
|
||||||
|
static final int FLAGS_QR = 15;
|
||||||
|
static final int FLAGS_Opcode = 11;
|
||||||
|
static final int FLAGS_AA = 10;
|
||||||
|
static final int FLAGS_TC = 9;
|
||||||
|
static final int FLAGS_RD = 8;
|
||||||
|
static final int FLAGS_RA = 7;
|
||||||
|
static final int FLAGS_Z = 4;
|
||||||
|
static final int FLAGS_RCODE = 0;
|
||||||
|
|
||||||
|
static final int[] FLAGS_ALL = { FLAGS_QR, FLAGS_AA, FLAGS_TC, FLAGS_RD, FLAGS_RA };
|
||||||
|
static final String[] FLAGS_TXT = { "qr", "aa", "tc", "rd", "ra" };
|
||||||
|
|
||||||
|
static final SecureRandom RANDOM;
|
||||||
|
|
||||||
|
static {
|
||||||
|
RANDOM = new SecureRandom();
|
||||||
|
byte[] seed = RANDOM.generateSeed(20); // TODO more ? from config?
|
||||||
|
RANDOM.setSeed(seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int id;
|
||||||
|
// include there flags |QR| DnsOpCode |AA|TC|RD|RA| Z | RCODE|
|
||||||
|
protected int flags;
|
||||||
|
|
||||||
|
protected int qdcount;
|
||||||
|
protected int ancount;
|
||||||
|
protected int nscount;
|
||||||
|
protected int arcount;
|
||||||
|
|
||||||
|
public Header() {
|
||||||
|
this(RANDOM.nextInt(MAX_USHORT));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Header(int id) {
|
||||||
|
id(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header(ByteBuf in) {
|
||||||
|
this(in.readUnsignedShort());
|
||||||
|
flags(in.readUnsignedShort());
|
||||||
|
qdcount(in.readUnsignedShort());
|
||||||
|
ancount(in.readUnsignedShort());
|
||||||
|
nscount(in.readUnsignedShort());
|
||||||
|
arcount(in.readUnsignedShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header(Header from) {
|
||||||
|
this();
|
||||||
|
|
||||||
|
this.flags(from.flags());
|
||||||
|
this.qdcount(from.qdcount());
|
||||||
|
this.ancount(from.ancount());
|
||||||
|
this.nscount(from.nscount());
|
||||||
|
this.arcount(from.arcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(ByteBuf out) {
|
||||||
|
out.writeShort(id());
|
||||||
|
out.writeShort(flags());
|
||||||
|
out.writeShort(qdcount());
|
||||||
|
out.writeShort(ancount());
|
||||||
|
out.writeShort(nscount());
|
||||||
|
out.writeShort(arcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 16 bit identifier assigned by the program that generates any kind of
|
||||||
|
* query. This identifier is copied the corresponding reply and can be used
|
||||||
|
* by the requester to match up replies to outstanding queries.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public int id() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void id(int i) {
|
||||||
|
this.id = verify16bitValue("ID", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int verify16bitValue(String column, int i) {
|
||||||
|
if ((i < MIN_USHORT) || (MAX_USHORT < i)) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
Messages.Not16bitValue, column, i));
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int flags() {
|
||||||
|
return this.flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void flags(int flags) {
|
||||||
|
this.flags = verify16bitValue("Flags", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int flag(int shift, int mask) {
|
||||||
|
return (this.flags >> shift) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A one bit field that specifies whether this message is a query (0), or a
|
||||||
|
* response (1).
|
||||||
|
*/
|
||||||
|
public boolean qr() {
|
||||||
|
return flag(FLAGS_QR, 0x1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void qr(boolean is) {
|
||||||
|
flip(FLAGS_QR, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see OpCode
|
||||||
|
*/
|
||||||
|
public OpCode opcode() {
|
||||||
|
int code = flag(FLAGS_Opcode, 0xF);
|
||||||
|
return OpCode.valueOf(code); // TODO cache?
|
||||||
|
}
|
||||||
|
|
||||||
|
public void opcode(OpCode op) {
|
||||||
|
// clear current opcode
|
||||||
|
this.flags &= 0x87FF; // 1000 0111 1111 1111
|
||||||
|
// set opcode
|
||||||
|
this.flags |= op.value() << FLAGS_Opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authoritative Answer - this bit is valid in responses, and specifies that
|
||||||
|
* the responding name server is an authority for the domain name in
|
||||||
|
* question section.
|
||||||
|
*/
|
||||||
|
public boolean aa() {
|
||||||
|
return flag(FLAGS_AA, 0x1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void aa(boolean is) {
|
||||||
|
flip(FLAGS_AA, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flip(int index, boolean is) {
|
||||||
|
int i = 1 << index; // TODO move to caller ?
|
||||||
|
if (is) {
|
||||||
|
this.flags |= i;
|
||||||
|
} else {
|
||||||
|
this.flags &= i ^ 0xFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TrunCation - specifies that this message was truncated due to length
|
||||||
|
* greater than that permitted on the transmission channel.
|
||||||
|
*/
|
||||||
|
public boolean tc() {
|
||||||
|
return flag(FLAGS_TC, 0x1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tc(boolean is) {
|
||||||
|
flip(FLAGS_TC, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursion Desired - this bit may be set in a query and is copied into the
|
||||||
|
* response. If RD is set, it directs the name server to pursue the query
|
||||||
|
* recursively. Recursive query support is optional.
|
||||||
|
*/
|
||||||
|
public boolean rd() {
|
||||||
|
return flag(FLAGS_RD, 0x1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rd(boolean is) {
|
||||||
|
flip(FLAGS_RD, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursion Available - this be is set or cleared in a response, and
|
||||||
|
* denotes whether recursive query support is available in the name server.
|
||||||
|
*/
|
||||||
|
public boolean ra() {
|
||||||
|
return flag(FLAGS_RA, 0x1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ra(boolean is) {
|
||||||
|
flip(FLAGS_RA, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved for future use. Must be zero in all queries and responses.
|
||||||
|
*/
|
||||||
|
public int z() {
|
||||||
|
return flag(FLAGS_Z, 0x7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see RCode
|
||||||
|
*/
|
||||||
|
public RCode rcode() {
|
||||||
|
int code = flag(FLAGS_RCODE, 0xF);
|
||||||
|
return RCode.valueOf(code); // TODO cache ?
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rcode(RCode rc) {
|
||||||
|
// clear current response code
|
||||||
|
this.flags &= 0xFFF0; // 1111 1111 1111 0000
|
||||||
|
// set response code
|
||||||
|
this.flags |= rc.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an unsigned 16 bit integer specifying the number of entries in the
|
||||||
|
* question section.
|
||||||
|
*/
|
||||||
|
public int qdcount() {
|
||||||
|
return this.qdcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void qdcount(int value) {
|
||||||
|
this.qdcount = verify16bitValue("qdcount", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an unsigned 16 bit integer specifying the number of resource records in
|
||||||
|
* the answer section.
|
||||||
|
*/
|
||||||
|
public int ancount() {
|
||||||
|
return this.ancount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ancount(int value) {
|
||||||
|
this.ancount = verify16bitValue("ancount", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an unsigned 16 bit integer specifying the number of name server resource
|
||||||
|
* records in the authority records section.
|
||||||
|
*/
|
||||||
|
public int nscount() {
|
||||||
|
return this.nscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nscount(int value) {
|
||||||
|
this.nscount = verify16bitValue("nscount", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an unsigned 16 bit integer specifying the number of resource records in
|
||||||
|
* the additional records section.
|
||||||
|
*/
|
||||||
|
public int arcount() {
|
||||||
|
return this.arcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void arcount(int value) {
|
||||||
|
this.arcount = verify16bitValue("arcount", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
MessageFormat form = new MessageFormat(";; ->>HEADER<<- "
|
||||||
|
+ "opcode: {0}, rcode: {1}, id: {2,number,#}\n"
|
||||||
|
+ ";; flags: {3}; QUERY: {4,number,#}, "
|
||||||
|
+ "ANSWER: {5,number,#}, " + "AUTHORITY: {6,number,#}, "
|
||||||
|
+ "ADDITIONAL: {7,number,#}");
|
||||||
|
Object[] args = { opcode().name(), rcode().name(), id(),
|
||||||
|
toFlagsString(), qdcount(), ancount(), nscount(), arcount() };
|
||||||
|
return form.format(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String toFlagsString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
for (int i = 0, length = FLAGS_ALL.length; i < length; i++) {
|
||||||
|
if (flag(FLAGS_ALL[i], 0x1) != 0) {
|
||||||
|
stb.append(FLAGS_TXT[i]);
|
||||||
|
stb.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + this.id;
|
||||||
|
result = prime * result + this.flags;
|
||||||
|
result = prime * result + this.qdcount;
|
||||||
|
result = prime * result + this.ancount;
|
||||||
|
result = prime * result + this.nscount;
|
||||||
|
result = prime * result + this.arcount;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
} else if (obj instanceof Header) {
|
||||||
|
Header other = (Header) obj;
|
||||||
|
return equals(other);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Header other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.id != other.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.flags != other.flags) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.qdcount != other.qdcount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.ancount != other.ancount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.nscount != other.nscount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.arcount != other.arcount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.slf4j.Marker;
|
||||||
|
import org.slf4j.MarkerFactory;
|
||||||
|
|
||||||
|
public
|
||||||
|
class Markers {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maker prefixes.
|
||||||
|
*/
|
||||||
|
public static final String PREFIX_PKG = "org.handwerkszeug.dns";
|
||||||
|
|
||||||
|
public static final Marker MARKER_ROOT = MarkerFactory.getMarker(PREFIX_PKG);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* using for design decision. like plug-in or add-ins.
|
||||||
|
*/
|
||||||
|
public static final Marker DESIGN = MarkerFactory.getMarker(PREFIX_PKG + ".design");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* using for boundaries. like I/O or another library.
|
||||||
|
*/
|
||||||
|
public static final Marker BOUNDARY = MarkerFactory.getMarker(PREFIX_PKG + ".boundary");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* using for object lifecycle.
|
||||||
|
*/
|
||||||
|
public static final Marker LIFECYCLE = MarkerFactory.getMarker(PREFIX_PKG + ".lifecycle");
|
||||||
|
/**
|
||||||
|
* using for implementation details. primary purpose is debugging.
|
||||||
|
*/
|
||||||
|
public static final Marker DETAIL = MarkerFactory.getMarker(PREFIX_PKG + ".detail");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* using for profiling.
|
||||||
|
*/
|
||||||
|
public static final Marker PROFILE = MarkerFactory.getMarker(PREFIX_PKG + ".profile");
|
||||||
|
|
||||||
|
static {
|
||||||
|
Marker[] markers = {DESIGN, BOUNDARY, LIFECYCLE, DETAIL, PROFILE};
|
||||||
|
for (Marker m : markers) {
|
||||||
|
MARKER_ROOT.add(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,414 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
public class Name implements Comparable<Name> {
|
||||||
|
|
||||||
|
public static final Name NULL_NAME;
|
||||||
|
public static final Name WILDCARD;
|
||||||
|
|
||||||
|
static final byte[] NULL_ARRAY = new byte[0];
|
||||||
|
static final byte[] WILDCARD_ARRAY = new byte[] { '*' };
|
||||||
|
|
||||||
|
static {
|
||||||
|
NULL_NAME = create(NULL_ARRAY);
|
||||||
|
WILDCARD = create(WILDCARD_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Name create(byte[] array) {
|
||||||
|
List<byte[]> l = new ArrayList<byte[]>(1);
|
||||||
|
l.add(array);
|
||||||
|
return new Name(Collections.unmodifiableList(l));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4.1.4. DnsMessage compression
|
||||||
|
*/
|
||||||
|
public static final int MASK_POINTER = 0xC0; // 1100 0000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2.3.4. Size limits
|
||||||
|
*/
|
||||||
|
public static final int MAX_LABEL_SIZE = 63; // 0011 1111
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2.3.4. Size limits
|
||||||
|
*/
|
||||||
|
public static final int MAX_NAME_SIZE = 255;
|
||||||
|
|
||||||
|
protected final List<byte[]> name;
|
||||||
|
|
||||||
|
public Name(ByteBuf buffer) {
|
||||||
|
this.name = this.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name(String name) {
|
||||||
|
this.name = this.parse(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Name(List<byte[]> rawdata) {
|
||||||
|
this.name = rawdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<byte[]> parse(ByteBuf buffer) {
|
||||||
|
List<byte[]> list = new ArrayList<byte[]>();
|
||||||
|
boolean jumped = false;
|
||||||
|
|
||||||
|
int namesize = 0;
|
||||||
|
for (int length = buffer.readUnsignedByte(); -1 < length; length = buffer
|
||||||
|
.readUnsignedByte()) {
|
||||||
|
if (length == 0) {
|
||||||
|
list.add(NULL_ARRAY);
|
||||||
|
break;
|
||||||
|
} else if ((length & MASK_POINTER) != 0) {
|
||||||
|
int p = ((length ^ MASK_POINTER) << 8)
|
||||||
|
+ buffer.readUnsignedByte();
|
||||||
|
if (jumped == false) {
|
||||||
|
buffer.markReaderIndex();
|
||||||
|
jumped = true;
|
||||||
|
}
|
||||||
|
buffer.readerIndex(p);
|
||||||
|
} else if (length <= MAX_LABEL_SIZE) {
|
||||||
|
namesize += length;
|
||||||
|
if (MAX_NAME_SIZE < namesize) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
Messages.NamesMustBe255orLess, namesize));
|
||||||
|
}
|
||||||
|
byte[] ary = new byte[length];
|
||||||
|
buffer.readBytes(ary);
|
||||||
|
list.add(ary);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
Messages.InvalidCompressionMask, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jumped) {
|
||||||
|
buffer.resetReaderIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5.1. Format
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* \X where X is any character other than a digit (0-9), is
|
||||||
|
* used to quote that character so that its special meaning
|
||||||
|
* does not apply. For example, "\." can be used to place
|
||||||
|
* a dot character in a label.
|
||||||
|
*
|
||||||
|
* \DDD where each D is a digit is the octet corresponding to
|
||||||
|
* the decimal number described by DDD. The resulting
|
||||||
|
* octet is assumed to be text and is not checked for
|
||||||
|
* special meaning.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param namedata
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected List<byte[]> parse(String namedata) {
|
||||||
|
if (".".equals(namedata)) {
|
||||||
|
return NULL_NAME.name;
|
||||||
|
}
|
||||||
|
// TODO IDN support from RFC3490 RFC3491 RFC3492 RFC3454
|
||||||
|
List<byte[]> result = new ArrayList<byte[]>();
|
||||||
|
byte[] bytes = namedata.getBytes();
|
||||||
|
int namesize = 0;
|
||||||
|
ByteBuf buffer = Unpooled.buffer(MAX_LABEL_SIZE);
|
||||||
|
int current = 0;
|
||||||
|
int length = bytes.length;
|
||||||
|
|
||||||
|
boolean escape = false;
|
||||||
|
int digits = 0;
|
||||||
|
int value = 0;
|
||||||
|
for (; current < length; current++) {
|
||||||
|
byte b = bytes[current];
|
||||||
|
if (escape) {
|
||||||
|
if ((('0' <= b) && (b <= '9')) && (digits++ < 3)) {
|
||||||
|
value *= 10;
|
||||||
|
value += (b - '0');
|
||||||
|
if (255 < value) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
Messages.EscapedDecimalIsInvalid, value));
|
||||||
|
}
|
||||||
|
if (2 < digits) {
|
||||||
|
appendByte(namedata, buffer, (byte) value);
|
||||||
|
escape = false;
|
||||||
|
}
|
||||||
|
} else if (0 < digits) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format(
|
||||||
|
Messages.MixtureOfEscapedDigitAndNonDigit,
|
||||||
|
namedata));
|
||||||
|
} else {
|
||||||
|
appendByte(namedata, buffer, b);
|
||||||
|
escape = false;
|
||||||
|
}
|
||||||
|
} else if (b == '\\') {
|
||||||
|
escape = true;
|
||||||
|
digits = 0;
|
||||||
|
value = 0;
|
||||||
|
} else if (b == '.') {
|
||||||
|
namesize = namesize + addBytes(result, buffer) + 1;
|
||||||
|
} else {
|
||||||
|
appendByte(namedata, buffer, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
Messages.InvalidEscapeSequence, namedata));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.isReadable()) {
|
||||||
|
// relative domain name
|
||||||
|
namesize = namesize + addBytes(result, buffer);
|
||||||
|
} else {
|
||||||
|
// absolute domain name
|
||||||
|
result.add(NULL_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.release();
|
||||||
|
|
||||||
|
namesize += 1;
|
||||||
|
|
||||||
|
if (MAX_NAME_SIZE < namesize) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
Messages.NamesMustBe255orLess, namesize));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendByte(String namedata, ByteBuf buffer, byte b) {
|
||||||
|
if (buffer.isWritable()) {
|
||||||
|
buffer.writeByte(b);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(String.format(Messages.LabelsMustBe63orLess, namedata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int addBytes(List<byte[]> result, ByteBuf buffer) {
|
||||||
|
int size = buffer.readableBytes();
|
||||||
|
if (size < 1) {
|
||||||
|
throw new IllegalArgumentException(Messages.NullLabelIsNotValid);
|
||||||
|
}
|
||||||
|
byte[] newone = new byte[size];
|
||||||
|
buffer.readBytes(newone);
|
||||||
|
buffer.clear();
|
||||||
|
result.add(newone);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
// TODO DNAME and other non compress RR
|
||||||
|
// TODO need writing cache?
|
||||||
|
if (writePointer(buffer, compressor, this) == false) {
|
||||||
|
compressor.put(this, buffer.writerIndex());
|
||||||
|
for (int i = 0, size = this.name.size(); i < size; i++) {
|
||||||
|
byte[] current = this.name.get(i);
|
||||||
|
int cl = current.length;
|
||||||
|
buffer.writeByte(cl);
|
||||||
|
if (0 < cl) {
|
||||||
|
buffer.writeBytes(current);
|
||||||
|
if (i + 1 < size) {
|
||||||
|
Name n = new Name(this.name.subList(i + 1, size));
|
||||||
|
if (writePointer(buffer, compressor, n)) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
compressor.put(n, buffer.writerIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean writePointer(ByteBuf buffer,
|
||||||
|
NameCompressor compressor, Name n) {
|
||||||
|
int position = compressor.get(n);
|
||||||
|
if (-1 < position) {
|
||||||
|
int pointer = (MASK_POINTER << 8) | position;
|
||||||
|
buffer.writeShort(pointer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name toParent() {
|
||||||
|
int size = this.name.size();
|
||||||
|
if (1 < size) {
|
||||||
|
List<byte[]> newone = this.name.subList(1, size);
|
||||||
|
return new Name(newone);
|
||||||
|
}
|
||||||
|
return NULL_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name toWildcard() {
|
||||||
|
int size = this.name.size();
|
||||||
|
if (1 < size) {
|
||||||
|
List<byte[]> newone = new ArrayList<byte[]>(size);
|
||||||
|
newone.add(WILDCARD_ARRAY);
|
||||||
|
newone.addAll(this.name.subList(1, size));
|
||||||
|
return new Name(Collections.unmodifiableList(newone));
|
||||||
|
}
|
||||||
|
return WILDCARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Name other) {
|
||||||
|
notNull(other, "other");
|
||||||
|
int mySize = this.name.size();
|
||||||
|
int otherSize = other.name.size();
|
||||||
|
if (mySize < otherSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int diff = mySize - otherSize;
|
||||||
|
for (int i = 0; i < otherSize; i++) {
|
||||||
|
byte[] me = this.name.get(i + diff);
|
||||||
|
byte[] yu = other.name.get(i);
|
||||||
|
if (Arrays.equals(me, yu) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name replace(Name from, Name to) {
|
||||||
|
notNull(from, "from");
|
||||||
|
notNull(to, "to");
|
||||||
|
if (contains(from)) {
|
||||||
|
int diff = this.name.size() - from.name.size();
|
||||||
|
int newsize = diff + to.name.size();
|
||||||
|
List<byte[]> newone = new ArrayList<byte[]>(newsize);
|
||||||
|
newone.addAll(this.name.subList(0, diff));
|
||||||
|
newone.addAll(to.name);
|
||||||
|
int size = 0;
|
||||||
|
for (byte[] ary : newone) {
|
||||||
|
size += ary.length;
|
||||||
|
}
|
||||||
|
if (size < MAX_NAME_SIZE) {
|
||||||
|
return new Name(Collections.unmodifiableList(newone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
for (byte[] b : this.name) {
|
||||||
|
result = prime * result + Arrays.hashCode(b);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof Name) {
|
||||||
|
return equals(Name.class.cast(obj));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Name other) {
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int mySize = this.name.size();
|
||||||
|
int yrSize = other.name.size();
|
||||||
|
if (mySize != yrSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mySize; i++) {
|
||||||
|
byte[] me = this.name.get(i);
|
||||||
|
byte[] yu = other.name.get(i);
|
||||||
|
if (Arrays.equals(me, yu) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Name o) {
|
||||||
|
// TODO use more effective algorithm for red black tree.
|
||||||
|
int mySize = this.name.size();
|
||||||
|
int yrSize = o.name.size();
|
||||||
|
if (mySize != yrSize) {
|
||||||
|
return mySize - yrSize;
|
||||||
|
}
|
||||||
|
int minSize = Math.min(mySize, yrSize);
|
||||||
|
for (int i = minSize - 1; (-1 < i); i--) {
|
||||||
|
byte[] mine = this.name.get(i);
|
||||||
|
byte[] other = o.name.get(i);
|
||||||
|
if (Arrays.equals(mine, other) == false) {
|
||||||
|
int size = Math.min(mine.length, other.length);
|
||||||
|
for (int ii = size - 1; -1 < ii; i--) {
|
||||||
|
byte mb = mine[ii];
|
||||||
|
byte yb = other[ii];
|
||||||
|
if (mb != yb) {
|
||||||
|
if (mb < yb) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
DecimalFormat fmt = new DecimalFormat();
|
||||||
|
fmt.setMinimumIntegerDigits(3);
|
||||||
|
for (Iterator<byte[]> cursor = this.name.iterator(); cursor.hasNext();) {
|
||||||
|
byte[] ary = cursor.next();
|
||||||
|
for (int i = 0, l = ary.length; i < l; i++) {
|
||||||
|
int b = ary[i] & 0xFF;
|
||||||
|
if ((b < 0x21) || (0x7F < b)) {
|
||||||
|
stb.append('\\');
|
||||||
|
stb.append(fmt.format(b));
|
||||||
|
} else {
|
||||||
|
switch (b) {
|
||||||
|
case '"':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '.':
|
||||||
|
case ';':
|
||||||
|
case '\\':
|
||||||
|
case '@':
|
||||||
|
case '$':
|
||||||
|
stb.append('\\');
|
||||||
|
default:
|
||||||
|
stb.append((char) b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor.hasNext()) {
|
||||||
|
stb.append('.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3. Standard RRs
|
||||||
|
* <p>
|
||||||
|
* The following RR definitions are expected to occur, at least potentially, in
|
||||||
|
* all classes. In particular, NS, SOA, CNAME, and PTR will be used in all
|
||||||
|
* classes, and have the same format in all classes. Because their RDATA format
|
||||||
|
* is known, all domain names in the RDATA section of these RRs may be
|
||||||
|
* compressed.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* 4.1.4. DnsMessage compression
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface NameCompressor {
|
||||||
|
|
||||||
|
void put(Name name, int offset);
|
||||||
|
|
||||||
|
int get(Name name);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Disposable;
|
||||||
|
import werkzeugkasten.common.util.Initializable;
|
||||||
|
|
||||||
|
public interface NameServerContainer extends Initializable, Disposable {
|
||||||
|
|
||||||
|
String name();
|
||||||
|
|
||||||
|
List<String> nameservers();
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Disposable;
|
||||||
|
import werkzeugkasten.common.util.Initializable;
|
||||||
|
|
||||||
|
public class NameServerContainerProvider implements Initializable, Disposable {
|
||||||
|
|
||||||
|
public static final String DEFAULT_NAME = "default";
|
||||||
|
|
||||||
|
protected Map<String, NameServerContainer> containers = new HashMap<String, NameServerContainer>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
initialize(Thread.currentThread().getContextClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(ClassLoader classLoader) {
|
||||||
|
ServiceLoader<NameServerContainer> loader = ServiceLoader.load(
|
||||||
|
NameServerContainer.class, classLoader);
|
||||||
|
for (NameServerContainer nc : loader) {
|
||||||
|
this.containers.put(nc.name(), nc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
this.containers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameServerContainer getContainer() {
|
||||||
|
String name = System
|
||||||
|
.getProperty(Constants.SYSTEM_PROPERTY_ACTIVE_NAMESERVER_CONTAINER);
|
||||||
|
return getContainer(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameServerContainer getContainer(String name) {
|
||||||
|
NameServerContainer result = this.containers.get(name);
|
||||||
|
if (result == null) {
|
||||||
|
result = this.containers.get(DEFAULT_NAME);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
public class NullNameCompressor implements NameCompressor {
|
||||||
|
|
||||||
|
public static final NullNameCompressor INSTANCE = new NullNameCompressor();
|
||||||
|
|
||||||
|
private NullNameCompressor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Name name, int offset) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int get(Name name) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.handwerkszeug.util.EnumUtil;
|
||||||
|
import org.handwerkszeug.util.VariableEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A four bit field that specifies kind of query in this message. This value is
|
||||||
|
* set by the originator of a query and copied into the response. The values
|
||||||
|
* are:
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
|
||||||
|
* System (DNS) Parameters</a>
|
||||||
|
*/
|
||||||
|
public enum OpCode implements VariableEnum {
|
||||||
|
/**
|
||||||
|
* a standard query
|
||||||
|
*/
|
||||||
|
QUERY(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an inverse query
|
||||||
|
*/
|
||||||
|
IQUERY(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a server status request
|
||||||
|
*/
|
||||||
|
STATUS(2);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private OpCode(int i) {
|
||||||
|
this.code = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int value() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OpCode valueOf(int code) {
|
||||||
|
return EnumUtil.find(OpCode.values(), code);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.handwerkszeug.util.EnumUtil;
|
||||||
|
import org.handwerkszeug.util.VariableEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code - this 4 bit field is set as part of responses. The values have
|
||||||
|
* the following interpretation:
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
|
||||||
|
* System (DNS) Parameters</a>
|
||||||
|
*/
|
||||||
|
public enum RCode implements VariableEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No error condition
|
||||||
|
*/
|
||||||
|
NoError(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format error - The name server was unable to interpret the query.
|
||||||
|
*/
|
||||||
|
FormErr(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server failure - The name server was unable to process this query due to
|
||||||
|
* a problem with the name server.
|
||||||
|
*/
|
||||||
|
ServFail(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name Error - Meaningful only for responses from an authoritative name
|
||||||
|
* server, this code signifies that the domain name referenced in the query
|
||||||
|
* does not exist.
|
||||||
|
*/
|
||||||
|
NXDomain(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not Implemented - The name server does not support the requested kind of
|
||||||
|
* query.
|
||||||
|
*/
|
||||||
|
NotImp(4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refused - The name server refuses to perform the specified operation for
|
||||||
|
* policy reasons. For example, a name server may not wish to provide the
|
||||||
|
* information to the particular requester, or a name server may not wish to
|
||||||
|
* perform a particular operation (e.g., zone transfer) for particular data.
|
||||||
|
*/
|
||||||
|
Refused(5),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name Exists when it should not
|
||||||
|
*/
|
||||||
|
YXDomain(6),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RR Set Exists when it should not
|
||||||
|
*/
|
||||||
|
YXRRSet(7),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RR Set that should exist does not
|
||||||
|
*/
|
||||||
|
NXRRSet(8),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server Not Authoritative for zone
|
||||||
|
*/
|
||||||
|
NotAuth(9),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name not contained in zone
|
||||||
|
*/
|
||||||
|
NotZone(10);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int value() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RCode(int i) {
|
||||||
|
this.code = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RCode valueOf(int code) {
|
||||||
|
return EnumUtil.find(RCode.values(), code);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.handwerkszeug.dns.record.AAAARecord;
|
||||||
|
import org.handwerkszeug.dns.record.ARecord;
|
||||||
|
import org.handwerkszeug.dns.record.HINFORecord;
|
||||||
|
import org.handwerkszeug.dns.record.MINFORecord;
|
||||||
|
import org.handwerkszeug.dns.record.MXRecord;
|
||||||
|
import org.handwerkszeug.dns.record.NULLRecord;
|
||||||
|
import org.handwerkszeug.dns.record.SOARecord;
|
||||||
|
import org.handwerkszeug.dns.record.SingleNameRecord;
|
||||||
|
import org.handwerkszeug.dns.record.TXTRecord;
|
||||||
|
import org.handwerkszeug.dns.record.WKSRecord;
|
||||||
|
import org.handwerkszeug.util.EnumUtil;
|
||||||
|
import org.handwerkszeug.util.VariableEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.2.2. TYPE values
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
|
||||||
|
* System (DNS) Parameters</a>
|
||||||
|
*/
|
||||||
|
public enum RRType implements VariableEnum {
|
||||||
|
/**
|
||||||
|
* a host address
|
||||||
|
*/
|
||||||
|
A(1) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new ARecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* an authoritative name server
|
||||||
|
*/
|
||||||
|
NS(2) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a mail destination (Obsolete - use MX)
|
||||||
|
*/
|
||||||
|
MD(3) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a mail forwarder (Obsolete - use MX)
|
||||||
|
*/
|
||||||
|
MF(4) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* the canonical name for an alias
|
||||||
|
*/
|
||||||
|
CNAME(5) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* marks the start of a zone of authority
|
||||||
|
*/
|
||||||
|
SOA(6) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SOARecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a mailbox domain name (EXPERIMENTAL)
|
||||||
|
*/
|
||||||
|
MB(7) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a mail group member (EXPERIMENTAL)
|
||||||
|
*/
|
||||||
|
MG(8) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a mail rename domain name (EXPERIMENTAL)
|
||||||
|
*/
|
||||||
|
MR(9) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a null RR (EXPERIMENTAL)
|
||||||
|
*/
|
||||||
|
NULL(10) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new NULLRecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a well known service description
|
||||||
|
*/
|
||||||
|
WKS(11) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new WKSRecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* a domain name pointer
|
||||||
|
*/
|
||||||
|
PTR(12) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* host information
|
||||||
|
*/
|
||||||
|
HINFO(13) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new HINFORecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* mailbox or mail list information
|
||||||
|
*/
|
||||||
|
MINFO(14) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new MINFORecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* mail exchange
|
||||||
|
*/
|
||||||
|
MX(15) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new MXRecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* text strings
|
||||||
|
*/
|
||||||
|
TXT(16) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new TXTRecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* IP6 Address
|
||||||
|
*/
|
||||||
|
AAAA(28) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new AAAARecord();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Naming Authority Pointer
|
||||||
|
*
|
||||||
|
* @see http://tools.ietf.org/html/rfc3403#section-4
|
||||||
|
*/
|
||||||
|
NAPTR(35) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
// TODO not implemented...
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Terminal DNS Name Redirection
|
||||||
|
*
|
||||||
|
* @see http://www.ietf.org/rfc/rfc2672.txt
|
||||||
|
*/
|
||||||
|
DNAME(39) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// RFC6891 6.1.1 OPT values
|
||||||
|
/**
|
||||||
|
* An OPT pseudo-RR (sometimes called a meta-RR) MAY be added to the additional data section of a request.
|
||||||
|
*
|
||||||
|
* @see http://www.ietf.org/rfc/rfc6891.txt
|
||||||
|
*/
|
||||||
|
OPT(41) {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
ResourceRecord newRecord() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// RFC1035 3.2.3. QTYPE values
|
||||||
|
/**
|
||||||
|
* A request for a transfer of an entire zone
|
||||||
|
*/
|
||||||
|
AXFR(252) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
throw new UnsupportedOperationException(String.format(
|
||||||
|
Messages.NoResourceRecord, AXFR.name()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* A request for mailbox-related records (MB, MG or MR)
|
||||||
|
*/
|
||||||
|
MAILB(253) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
throw new UnsupportedOperationException(String.format(
|
||||||
|
Messages.NoResourceRecord, MAILB.name()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* A request for mail agent RRs (Obsolete - see MX)
|
||||||
|
*/
|
||||||
|
MAILA(254) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
throw new UnsupportedOperationException(String.format(
|
||||||
|
Messages.NoResourceRecord, MAILA.name()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* A request for all records
|
||||||
|
*/
|
||||||
|
ANY(255) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
throw new UnsupportedOperationException(String.format(
|
||||||
|
Messages.NoResourceRecord, ANY.name()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UNKNOWN(-1) {
|
||||||
|
@Override
|
||||||
|
public ResourceRecord newRecord() {
|
||||||
|
return new NULLRecord();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private RRType(int i) {
|
||||||
|
this.code = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ResourceRecord newRecord();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int value() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RRType valueOf(int code) {
|
||||||
|
return EnumUtil.find(RRType.values(), code, UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RRType find(String value) {
|
||||||
|
return EnumUtil.find(RRType.values(), value, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
public interface ResolveContext {
|
||||||
|
|
||||||
|
DNSMessage request();
|
||||||
|
|
||||||
|
DNSMessage response();
|
||||||
|
|
||||||
|
Response resolve(Name qname, RRType qtype);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.impl.SimpleChainResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public interface Resolver extends Chain<ResolveContext, SimpleChainResult> {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4.1.3. Resource record format<br/>
|
||||||
|
* The answer, authority, and additional sections all share the same format: a
|
||||||
|
* variable number of resource records, where the number of records is specified
|
||||||
|
* in the corresponding count field in the header. Each resource record has the
|
||||||
|
* following format:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1 1 1 1 1 1
|
||||||
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | |
|
||||||
|
* / /
|
||||||
|
* / NAME /
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | TYPE |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | CLASS |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | TTL |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | RDLENGTH |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||||
|
* / RDATA /
|
||||||
|
* / /
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
|
||||||
|
* System (DNS) Parameters</a>
|
||||||
|
*/
|
||||||
|
public interface ResourceRecord {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* two octets containing one of the RR type codes. This field specifies the
|
||||||
|
* meaning of the data in the RDATA field.
|
||||||
|
*/
|
||||||
|
RRType type();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a domain name to which this resource record pertains.
|
||||||
|
*/
|
||||||
|
Name name();
|
||||||
|
|
||||||
|
void name(Name name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* two octets which specify the class of the data in the RDATA field.
|
||||||
|
*/
|
||||||
|
DNSClass dnsClass();
|
||||||
|
|
||||||
|
void dnsClass(DNSClass dnsClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a 32 bit unsigned integer that specifies the time interval (in seconds)
|
||||||
|
* that the resource record may be cached before it should be discarded.
|
||||||
|
* Zero values are interpreted to mean that the RR can only be used for the
|
||||||
|
* transaction in progress, and should not be cached.
|
||||||
|
*/
|
||||||
|
long ttl();
|
||||||
|
|
||||||
|
void ttl(long ttl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an unsigned 16 bit integer that specifies the length in octets of the
|
||||||
|
* RDATA field.
|
||||||
|
*/
|
||||||
|
int rdlength();
|
||||||
|
|
||||||
|
void rdlength(int rdlength);
|
||||||
|
|
||||||
|
void setRDATA(List<String> list);
|
||||||
|
|
||||||
|
void parse(ByteBuf buffer);
|
||||||
|
|
||||||
|
void write(ByteBuf buffer, NameCompressor compressor);
|
||||||
|
|
||||||
|
ResourceRecord toQnameRecord(Name qname);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public interface Response {
|
||||||
|
|
||||||
|
RCode rcode();
|
||||||
|
|
||||||
|
void postProcess(ResolveContext context);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SimpleNameCompressor implements NameCompressor {
|
||||||
|
|
||||||
|
protected Map<Name, Integer> map = new HashMap<Name, Integer>();
|
||||||
|
|
||||||
|
public void put(Name name, int offset) {
|
||||||
|
this.map.put(name, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(Name name) {
|
||||||
|
Integer i = this.map.get(name);
|
||||||
|
if (i == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return i.intValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
public interface Zone {
|
||||||
|
Name name();
|
||||||
|
|
||||||
|
DNSClass dnsClass();
|
||||||
|
|
||||||
|
ZoneType type();
|
||||||
|
|
||||||
|
Response find(Name qname, RRType qtype);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.handwerkszeug.dns;
|
||||||
|
|
||||||
|
public enum ZoneType {
|
||||||
|
|
||||||
|
master, slave, stub, forward, rootHint;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
package org.handwerkszeug.dns.aaaa;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Constants;
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.Header;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainer;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainerProvider;
|
||||||
|
import org.handwerkszeug.dns.OpCode;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.client.WKPortNumbers;
|
||||||
|
import org.handwerkszeug.dns.client.WKProtocols;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.handwerkszeug.dns.record.WKSRecord;
|
||||||
|
import org.handwerkszeug.util.EnumUtil;
|
||||||
|
import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
|
import org.jboss.netty.channel.ChannelFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelFuture;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.ChannelStateEvent;
|
||||||
|
import org.jboss.netty.channel.ExceptionEvent;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
public class DNSClient extends SimpleChannelHandler {
|
||||||
|
|
||||||
|
protected static final String LINE_SEP = System.getProperty("line.separator");
|
||||||
|
protected NameServerContainer container;
|
||||||
|
protected WKProtocols wkProtocols = new WKProtocols();
|
||||||
|
protected WKPortNumbers wkPortNumbers = new WKPortNumbers();
|
||||||
|
|
||||||
|
protected List<String> names = new ArrayList<String>();
|
||||||
|
|
||||||
|
protected DNSClass dnsclass = DNSClass.IN;
|
||||||
|
|
||||||
|
protected RRType type = RRType.A;
|
||||||
|
|
||||||
|
protected OpCode opCode = OpCode.QUERY;
|
||||||
|
|
||||||
|
protected InetSocketAddress serverAddress;
|
||||||
|
|
||||||
|
protected int serverPort = Constants.DEFAULT_PORT; // default DNS port.
|
||||||
|
|
||||||
|
protected DNSMessage request;
|
||||||
|
|
||||||
|
protected long time;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
DNSClient client = new DNSClient();
|
||||||
|
client.initialize();
|
||||||
|
client.process(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initialize() {
|
||||||
|
NameServerContainerProvider provider = new NameServerContainerProvider();
|
||||||
|
provider.initialize();
|
||||||
|
this.container = provider.getContainer();
|
||||||
|
this.container.initialize();
|
||||||
|
this.wkProtocols.load();
|
||||||
|
this.wkPortNumbers.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void process(String[] args) throws Exception {
|
||||||
|
parseArgs(args);
|
||||||
|
setUpRequest();
|
||||||
|
sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseArgs(String[] args) throws Exception {
|
||||||
|
InetAddress address = null;
|
||||||
|
for (int i = 0, length = args.length; i < length; i++) {
|
||||||
|
String current = args[i];
|
||||||
|
if (current.startsWith("@")) {
|
||||||
|
String s = current.substring(1);
|
||||||
|
address = InetAddress.getByName(s);
|
||||||
|
} else if (current.startsWith("-") && (1 < current.length())) {
|
||||||
|
switch (current.charAt(1)) {
|
||||||
|
case 'x': {
|
||||||
|
this.opCode = OpCode.IQUERY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p': {
|
||||||
|
String next = args[++i];
|
||||||
|
if (Pattern.matches("\\p{Digit}+", next)) {
|
||||||
|
int num = Integer.parseInt(next);
|
||||||
|
if ((0 < num) && (num < 0xFFFF)) {
|
||||||
|
this.serverPort = num;
|
||||||
|
} else {
|
||||||
|
System.err.printf(Messages.InvalidPortNumber, num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
String s = current.toUpperCase();
|
||||||
|
DNSClass dc = EnumUtil.find(DNSClass.values(), s, null);
|
||||||
|
if (dc != null) {
|
||||||
|
this.dnsclass = dc;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RRType t = EnumUtil.find(RRType.values(), s, null);
|
||||||
|
if (t != null) {
|
||||||
|
this.type = t;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.names.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (address == null) {
|
||||||
|
address = findDNSServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serverAddress = new InetSocketAddress(address, this.serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InetAddress findDNSServer() throws Exception {
|
||||||
|
List<String> list = this.container.nameservers();
|
||||||
|
if (0 < list.size()) {
|
||||||
|
String host = list.get(0).toString();
|
||||||
|
return InetAddress.getByName(host);
|
||||||
|
}
|
||||||
|
return InetAddress.getLocalHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setUpRequest() {
|
||||||
|
this.request = new DNSMessage(new Header());
|
||||||
|
this.request.header().opcode(this.opCode);
|
||||||
|
this.request.header().rd(true);
|
||||||
|
for (String s : this.names) {
|
||||||
|
String name = s;
|
||||||
|
if ((OpCode.IQUERY.equals(this.opCode) == false)
|
||||||
|
&& (s.endsWith(".") == false)) {
|
||||||
|
name += ".";
|
||||||
|
}
|
||||||
|
ResourceRecord rr = this.type.newRecord();
|
||||||
|
rr.name(new Name(name));
|
||||||
|
this.request.question().add(rr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendRequest() {
|
||||||
|
// use UDP/IP
|
||||||
|
ChannelFactory factory = new NioDatagramChannelFactory(Executors.newSingleThreadExecutor());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ConnectionlessBootstrap bootstrap = new ConnectionlessBootstrap(factory);
|
||||||
|
|
||||||
|
bootstrap.getPipeline().addLast("handler", DNSClient.this);
|
||||||
|
|
||||||
|
bootstrap.setOption("broadcast", "false");
|
||||||
|
// bootstrap.setOption("sendBufferSize", 512);
|
||||||
|
// bootstrap.setOption("receiveBufferSize", 512);
|
||||||
|
|
||||||
|
ChannelFuture future = bootstrap.connect(this.serverAddress);
|
||||||
|
future.awaitUninterruptibly(30, TimeUnit.SECONDS);
|
||||||
|
if (future.isSuccess() == false) {
|
||||||
|
future.getCause().printStackTrace();
|
||||||
|
}
|
||||||
|
future.getChannel().getCloseFuture().awaitUninterruptibly();
|
||||||
|
} finally {
|
||||||
|
factory.releaseExternalResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
|
||||||
|
System.out.println("DNSClient#channelConnected");
|
||||||
|
System.out.printf("Local %s | Remote %s\n", e.getChannel()
|
||||||
|
.getLocalAddress(), e.getChannel().getRemoteAddress());
|
||||||
|
ChannelBuffer buffer = ChannelBuffers.buffer(512);
|
||||||
|
this.request.write(buffer);
|
||||||
|
this.time = System.currentTimeMillis();
|
||||||
|
e.getChannel().write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||||
|
System.out.println("DNSClient#messageReceived");
|
||||||
|
System.out.println(e.getChannel().getLocalAddress() + " | "
|
||||||
|
+ e.getChannel().getRemoteAddress());
|
||||||
|
this.time = System.currentTimeMillis() - this.time;
|
||||||
|
ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
|
||||||
|
DNSMessage msg = new DNSMessage(buffer);
|
||||||
|
printResult(this.time, msg);
|
||||||
|
e.getChannel().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
|
||||||
|
e.getCause().printStackTrace();
|
||||||
|
e.getChannel().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String formatMessage(long time, DNSMessage msg) {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(msg.header());
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
stb.append(";; QUESTION SECTION:");
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
for (ResourceRecord rr : msg.question()) {
|
||||||
|
stb.append(';');
|
||||||
|
int i = stb.length();
|
||||||
|
stb.append(rr.name().toString());
|
||||||
|
StringUtil.padRight(stb, ' ', i + 30);
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(rr.dnsClass().name());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(rr.type().name());
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
append(stb, ";; ANSWER SECTION:", msg.answer());
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
append(stb, ";; AUTHORITY SECTION:", msg.authority());
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
append(stb, ";; ADDITIONAL SECTION:", msg.additional());
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
|
||||||
|
stb.append(";; Query time: ");
|
||||||
|
stb.append(time);
|
||||||
|
stb.append(" msec");
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
stb.append(";; Server: ");
|
||||||
|
stb.append(this.serverAddress);
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
stb.append(";; When: ");
|
||||||
|
stb.append(DateFormat.getDateTimeInstance().format(new Date()));
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
stb.append(";; Message size: ");
|
||||||
|
stb.append(msg.messageSize());
|
||||||
|
stb.append(" bytes");
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void append(StringBuilder stb, String name, List<ResourceRecord> list) {
|
||||||
|
stb.append(name);
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
if (list != null) {
|
||||||
|
for (ResourceRecord rr : list) {
|
||||||
|
stb.append(rr.toString());
|
||||||
|
if (rr.type().equals(RRType.WKS)) {
|
||||||
|
append(stb, (WKSRecord) rr);
|
||||||
|
}
|
||||||
|
stb.append(LINE_SEP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void append(StringBuilder stb, WKSRecord record) {
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.wkProtocols.find(record.protocol()));
|
||||||
|
stb.append(' ');
|
||||||
|
this.wkPortNumbers.appendServices(record, stb);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void printResult(long time, DNSMessage msg) {
|
||||||
|
System.out.println(formatMessage(time, msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void printHelp() {
|
||||||
|
// TODO print help message.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package org.handwerkszeug.dns.aaaa;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.conf.ServerConfiguration;
|
||||||
|
import org.handwerkszeug.dns.conf.ServerConfigurationImpl;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelFactory;
|
||||||
|
import io.netty.channel.group.ChannelGroup;
|
||||||
|
import io.netty.channel.group.DefaultChannelGroup;
|
||||||
|
import werkzeugkasten.common.util.Disposable;
|
||||||
|
import werkzeugkasten.common.util.Initializable;
|
||||||
|
|
||||||
|
public class DNSServer implements Initializable, Disposable {
|
||||||
|
|
||||||
|
protected static Logger LOG = LoggerFactory.getLogger(DNSServer.class);
|
||||||
|
|
||||||
|
protected ServerConfiguration config;
|
||||||
|
protected ChannelFactory serverChannelFactory;
|
||||||
|
protected ChannelFactory clientChannelFactory;
|
||||||
|
protected ConnectionlessBootstrap bootstrap;
|
||||||
|
protected ChannelGroup group;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
ServerConfiguration conf = parseArgs(args);
|
||||||
|
final DNSServer server = new DNSServer(conf);
|
||||||
|
server.initialize();
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
server.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerConfiguration parseArgs(String[] args) throws Exception {
|
||||||
|
ServerConfigurationImpl config = new ServerConfigurationImpl();
|
||||||
|
|
||||||
|
URL from = null;
|
||||||
|
if ((args != null) && (0 < args.length)) {
|
||||||
|
from = readFrom(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == null) {
|
||||||
|
from = readFrom("named.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == null) {
|
||||||
|
LOG.info("read named.default.yml from ClassLoader");
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
from = cl.getResource("named.default.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == null) {
|
||||||
|
throw new IllegalStateException("configuration file is not found.");
|
||||||
|
}
|
||||||
|
config.load(from);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URL readFrom(String path) throws MalformedURLException {
|
||||||
|
File f = new File(path);
|
||||||
|
LOG.info("read from {}", f.getAbsolutePath());
|
||||||
|
if (f.exists()) {
|
||||||
|
if (f.canRead()) {
|
||||||
|
return f.toURI().toURL();
|
||||||
|
} else {
|
||||||
|
LOG.info("{} cannot read", f.getAbsolutePath());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("{} is not exists", f.getAbsolutePath());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DNSServer(ServerConfiguration config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
LOG.debug(Markers.LIFECYCLE, "initialize server");
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(this.config.getThreadPoolSize());
|
||||||
|
|
||||||
|
// TODO need TCP?
|
||||||
|
this.clientChannelFactory = new NioDatagramChannelFactory(executor);
|
||||||
|
|
||||||
|
// TODO TCP and/or UDP
|
||||||
|
this.serverChannelFactory = new NioDatagramChannelFactory(executor);
|
||||||
|
|
||||||
|
ChannelPipelineFactory pipelineFactory = new DNSServerPipelineFactory(this.config, this.clientChannelFactory);
|
||||||
|
|
||||||
|
this.bootstrap = new ConnectionlessBootstrap(this.serverChannelFactory);
|
||||||
|
this.bootstrap.setPipelineFactory(pipelineFactory);
|
||||||
|
|
||||||
|
this.group = new DefaultChannelGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process() {
|
||||||
|
for (SocketAddress sa : this.config.getBindingHosts()) {
|
||||||
|
LOG.info(Markers.BOUNDARY, "binding {}", sa);
|
||||||
|
this.group.add(this.bootstrap.bind(sa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
try {
|
||||||
|
this.group.close().awaitUninterruptibly();
|
||||||
|
} finally {
|
||||||
|
dispose(this.clientChannelFactory);
|
||||||
|
dispose(this.serverChannelFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dispose(ExternalResourceReleasable releasable) {
|
||||||
|
try {
|
||||||
|
releasable.releaseExternalResources();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.handwerkszeug.dns.aaaa;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.conf.ServerConfiguration;
|
||||||
|
import org.handwerkszeug.dns.server.DNSMessageDecoder;
|
||||||
|
import org.jboss.netty.channel.ChannelFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
|
||||||
|
public class DNSServerPipelineFactory implements ChannelPipelineFactory {
|
||||||
|
|
||||||
|
protected ServerConfiguration config;
|
||||||
|
protected ChannelFactory clientChannelFactory;
|
||||||
|
|
||||||
|
protected DNSMessageDecoder decoder = new DNSMessageDecoder();
|
||||||
|
|
||||||
|
public DNSServerPipelineFactory(ServerConfiguration config, ChannelFactory clientChannelFactory) {
|
||||||
|
this.config = config;
|
||||||
|
this.clientChannelFactory = clientChannelFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline getPipeline() throws Exception {
|
||||||
|
ChannelPipeline result = Channels.pipeline();
|
||||||
|
result.addLast("decoder", this.decoder);
|
||||||
|
result.addLast("fowarder", new ForwardingHandler(this.config, this.clientChannelFactory));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package org.handwerkszeug.dns.aaaa;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.conf.ServerConfiguration;
|
||||||
|
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelFuture;
|
||||||
|
import org.jboss.netty.channel.ChannelFutureListener;
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
import org.jboss.netty.channel.ExceptionEvent;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class ForwardingHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(ForwardingHandler.class);
|
||||||
|
|
||||||
|
protected ServerConfiguration config;
|
||||||
|
protected ChannelFactory clientChannelFactory;
|
||||||
|
|
||||||
|
public ForwardingHandler(ServerConfiguration config, ChannelFactory clientChannelFactory) {
|
||||||
|
this.config = config;
|
||||||
|
this.clientChannelFactory = clientChannelFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {
|
||||||
|
final DNSMessage original = DNSMessage.class.cast(e.getMessage());
|
||||||
|
|
||||||
|
ClientBootstrap cb = new ClientBootstrap(this.clientChannelFactory);
|
||||||
|
cb.setOption("broadcast", "false");
|
||||||
|
|
||||||
|
cb.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline getPipeline() throws Exception {
|
||||||
|
return Channels.pipeline(new ClientHanler(original, e.getChannel(), e.getRemoteAddress()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<SocketAddress> newlist = new ArrayList<SocketAddress>(this.config.getForwarders());
|
||||||
|
sendRequest(e, original, cb, newlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendRequest(final MessageEvent e, final DNSMessage original,
|
||||||
|
final ClientBootstrap bootstrap,
|
||||||
|
final List<SocketAddress> forwarders) {
|
||||||
|
|
||||||
|
if (0 < forwarders.size()) {
|
||||||
|
SocketAddress sa = forwarders.remove(0);
|
||||||
|
LOG.debug("send to {}", sa);
|
||||||
|
|
||||||
|
ChannelFuture f = bootstrap.connect(sa);
|
||||||
|
ChannelBuffer newone = ChannelBuffers.buffer(512);
|
||||||
|
DNSMessage msg = new DNSMessage(original);
|
||||||
|
msg.write(newone);
|
||||||
|
newone.resetReaderIndex();
|
||||||
|
final Channel c = f.getChannel();
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("STATUS : [isOpen/isConnected/isWritable {}] {} {}",
|
||||||
|
new Object[] {
|
||||||
|
new boolean[] { c.isOpen(), c.isConnected(), c.isWritable() }, c.getRemoteAddress(), c.getClass() });
|
||||||
|
}
|
||||||
|
|
||||||
|
c.write(newone, sa).addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future)
|
||||||
|
throws Exception {
|
||||||
|
LOG.debug("request complete isSuccess : {}",
|
||||||
|
future.isSuccess());
|
||||||
|
if (future.isSuccess() == false) {
|
||||||
|
if (0 < forwarders.size()) {
|
||||||
|
sendRequest(e, original, bootstrap, forwarders);
|
||||||
|
} else {
|
||||||
|
original.header().rcode(RCode.ServFail);
|
||||||
|
ChannelBuffer buffer = ChannelBuffers.buffer(512);
|
||||||
|
original.write(buffer);
|
||||||
|
// close inbound channel
|
||||||
|
e.getChannel().write(buffer)
|
||||||
|
.addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// f.awaitUninterruptibly(30, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
|
||||||
|
LOG.error("ForwardingHandler#exceptionCaught");
|
||||||
|
Throwable t = e.getCause();
|
||||||
|
LOG.error(t.getMessage(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ClientHanler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
protected DNSMessage original;
|
||||||
|
protected Channel originalChannel;
|
||||||
|
protected SocketAddress originalAddress;
|
||||||
|
|
||||||
|
public ClientHanler(DNSMessage msg, Channel c, SocketAddress sa) {
|
||||||
|
this.original = msg;
|
||||||
|
this.originalChannel = c;
|
||||||
|
this.originalAddress = sa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception {
|
||||||
|
LOG.debug("ClientHanler#messageReceived");
|
||||||
|
ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
|
||||||
|
DNSMessage msg = new DNSMessage(buffer);
|
||||||
|
msg.header().id(this.original.header().id());
|
||||||
|
|
||||||
|
ChannelBuffer newone = ChannelBuffers.buffer(buffer.capacity());
|
||||||
|
msg.write(newone);
|
||||||
|
newone.resetReaderIndex();
|
||||||
|
|
||||||
|
this.originalChannel.write(newone, this.originalAddress)
|
||||||
|
.addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future)
|
||||||
|
throws Exception {
|
||||||
|
e.getChannel().close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
|
||||||
|
LOG.error("ClientHanler#exceptionCaught");
|
||||||
|
Throwable t = e.getCause();
|
||||||
|
LOG.error(t.getMessage(), t);
|
||||||
|
e.getFuture().setFailure(t);
|
||||||
|
e.getChannel().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,178 @@
|
||||||
|
package org.handwerkszeug.dns.client;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.record.WKSRecord;
|
||||||
|
import org.handwerkszeug.util.ClassUtil;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Streams;
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://www.iana.org/assignments/port-numbers">PORT NUMBERS</a>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class WKPortNumbers {
|
||||||
|
|
||||||
|
public static final String UNKNOWN_PORT = "unknown";
|
||||||
|
public static final String PATH = ClassUtil.toPackagePath(WKPortNumbers.class) + "/PortNumbers.txt";
|
||||||
|
|
||||||
|
protected static final Set<String> skipWords = new HashSet<String>();
|
||||||
|
static {
|
||||||
|
skipWords.add("Reserved");
|
||||||
|
skipWords.add("Unassigned");
|
||||||
|
skipWords.add("Discard");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<Integer, String> ports = new HashMap<Integer, String>();
|
||||||
|
protected Map<String, Integer> keywords = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
public WKPortNumbers() {}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
load(PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String path) {
|
||||||
|
try {
|
||||||
|
InputStream fin = getClass().getResource("/" + path).openStream();
|
||||||
|
load(fin);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Resource '" + path + "' was not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(final InputStream in) {
|
||||||
|
new Streams.using<BufferedReader, Exception>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedReader open() throws Exception {
|
||||||
|
return new BufferedReader(new InputStreamReader(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(BufferedReader stream) throws Exception {
|
||||||
|
WKPortNumbers.this.parse(stream);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void happen(Exception exception) {
|
||||||
|
throw new IllegalStateException(exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parse(BufferedReader br) throws IOException {
|
||||||
|
while (br.ready()) {
|
||||||
|
parse(br.readLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parse(String line) {
|
||||||
|
if (line.startsWith("#")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] ary = line.split("\\p{Space}+");
|
||||||
|
if ((ary.length < 3) || skipWords.contains(ary[2])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = ary[1].indexOf('/');
|
||||||
|
String port = ary[1].substring(0, index);
|
||||||
|
add(Integer.valueOf(port), ary[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Integer port, String keyword) {
|
||||||
|
this.ports.put(port, keyword);
|
||||||
|
this.keywords.put(keyword, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String find(Integer port) {
|
||||||
|
if (port == null) {
|
||||||
|
return UNKNOWN_PORT;
|
||||||
|
}
|
||||||
|
String keyword = this.ports.get(port);
|
||||||
|
if (StringUtil.isEmpty(keyword)) {
|
||||||
|
return UNKNOWN_PORT;
|
||||||
|
}
|
||||||
|
return keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer find(String keyword) {
|
||||||
|
if (StringUtil.isEmpty(keyword)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.keywords.get(keyword.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Pattern isDigit = Pattern.compile("\\d+");
|
||||||
|
|
||||||
|
public void setServices(WKSRecord record, String[] services) {
|
||||||
|
List<Integer> list = new ArrayList<Integer>();
|
||||||
|
for (String s : services) {
|
||||||
|
if (isDigit.matcher(s).matches()) {
|
||||||
|
list.add(Integer.valueOf(s));
|
||||||
|
} else {
|
||||||
|
Integer i = find(s);
|
||||||
|
if (i != null) {
|
||||||
|
list.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int[] ary = new int[list.size()];
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
ary[i] = list.get(i);
|
||||||
|
}
|
||||||
|
WKPortNumbers.setServices(record, ary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.4.2. WKS RDATA format
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* @param services
|
||||||
|
*/
|
||||||
|
public static void setServices(WKSRecord record, int[] services) {
|
||||||
|
Arrays.sort(services);
|
||||||
|
int last = services[services.length - 1];
|
||||||
|
byte[] bitmap = new byte[last / 8 + 1];
|
||||||
|
for (int i : services) {
|
||||||
|
bitmap[i / 8] |= (1 << (7 - i % 8));
|
||||||
|
}
|
||||||
|
record.bitmap(bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Integer> getServices(WKSRecord record) {
|
||||||
|
byte[] bitmap = record.bitmap();
|
||||||
|
List<Integer> result = new ArrayList<Integer>();
|
||||||
|
for (int i = 0, length = bitmap.length; i < length; i++) {
|
||||||
|
int octets = bitmap[i] & 0xFF;
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if ((octets & (1 << (7 - j))) != 0) {
|
||||||
|
result.add(Integer.valueOf(i * 8 + j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendServices(WKSRecord record, StringBuilder stb) {
|
||||||
|
for (Integer i : getServices(record)) {
|
||||||
|
stb.append(find(i));
|
||||||
|
stb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package org.handwerkszeug.dns.client;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.xml.stream.XMLStreamException;
|
||||||
|
import javax.xml.stream.XMLStreamReader;
|
||||||
|
|
||||||
|
import org.handwerkszeug.util.ClassUtil;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Streams;
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
import werkzeugkasten.common.util.XMLEventParser;
|
||||||
|
import werkzeugkasten.common.util.XMLEventParser.DefaultHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* <a href="http://www.iana.org/assignments/protocol-numbers/">Protocol
|
||||||
|
* Numbers</a>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class WKProtocols {
|
||||||
|
|
||||||
|
public static final String UNKNOWN_PROTOCOL = "UNKNOWN";
|
||||||
|
public static final String PATH = ClassUtil.toPackagePath(WKProtocols.class) + "/ProtocolNumbers.xml";
|
||||||
|
|
||||||
|
protected Map<Short, String> protocols = new HashMap<Short, String>();
|
||||||
|
|
||||||
|
public WKProtocols() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
load(PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String path) {
|
||||||
|
try {
|
||||||
|
InputStream fin = getClass().getResource("/" + path).openStream();
|
||||||
|
load(fin);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Resource '" + path + "' was not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(final InputStream in) {
|
||||||
|
new Streams.using<BufferedInputStream, Exception>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedInputStream open() throws Exception {
|
||||||
|
return new BufferedInputStream(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(BufferedInputStream stream) throws Exception {
|
||||||
|
XMLEventParser parser = new XMLEventParser(stream);
|
||||||
|
parser.add(new DefaultHandler("registry"));
|
||||||
|
parser.add(new RecordHandler());
|
||||||
|
parser.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void happen(Exception exception) {
|
||||||
|
throw new IllegalStateException(exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Record {
|
||||||
|
String value;
|
||||||
|
String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RecordHandler extends DefaultHandler {
|
||||||
|
Pattern isDigit = Pattern.compile("\\p{Digit}+");
|
||||||
|
|
||||||
|
public RecordHandler() {
|
||||||
|
super("record");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(XMLStreamReader reader) throws XMLStreamException {
|
||||||
|
XMLEventParser parser = new XMLEventParser(reader);
|
||||||
|
Record r = new Record();
|
||||||
|
parser.add(new ValueHandler(r));
|
||||||
|
parser.add(new NameHandler(r));
|
||||||
|
parser.parse(getTagName());
|
||||||
|
if (this.isDigit.matcher(r.value).matches()) {
|
||||||
|
WKProtocols.this.add(Short.valueOf(r.value), r.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ValueHandler extends DefaultHandler {
|
||||||
|
Record r;
|
||||||
|
|
||||||
|
public ValueHandler(Record r) {
|
||||||
|
super("value");
|
||||||
|
this.r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(XMLStreamReader reader) throws XMLStreamException {
|
||||||
|
this.r.value = reader.getElementText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NameHandler extends DefaultHandler {
|
||||||
|
Record r;
|
||||||
|
|
||||||
|
public NameHandler(Record r) {
|
||||||
|
super("name");
|
||||||
|
this.r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(XMLStreamReader reader) throws XMLStreamException {
|
||||||
|
this.r.name = reader.getElementText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void add(Short value, String name) {
|
||||||
|
this.protocols.put(value, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String find(short ubyte) {
|
||||||
|
String s = this.protocols.get(ubyte);
|
||||||
|
if (StringUtil.isEmpty(s)) {
|
||||||
|
return UNKNOWN_PROTOCOL;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
import org.handwerkszeug.chain.impl.DefaultChainExecutor;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainer;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainerProvider;
|
||||||
|
|
||||||
|
public
|
||||||
|
class DefaultNameServerContainer implements NameServerContainer {
|
||||||
|
|
||||||
|
protected Chain<List<String>, ChainResult> executor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String name() {
|
||||||
|
return NameServerContainerProvider.DEFAULT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
List<String> nameservers() {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
this.executor.execute(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void initialize() {
|
||||||
|
// FIXME this code run only sun JRE.
|
||||||
|
// find from IBM JDK. JRockit.
|
||||||
|
DefaultChainExecutor<List<String>, ChainResult> dce = new DefaultChainExecutor<List<String>, ChainResult>();
|
||||||
|
dce.add(new SystemProperties());
|
||||||
|
dce.add(new SunJRE());
|
||||||
|
dce.add(new ResolvConf());
|
||||||
|
this.executor = dce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void dispose() {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
public interface MasterDataHandler {
|
||||||
|
// lifecycle methods
|
||||||
|
void initialize(ServerConfiguration conf);
|
||||||
|
|
||||||
|
void commit();
|
||||||
|
|
||||||
|
void rollback();
|
||||||
|
|
||||||
|
void dispose();
|
||||||
|
|
||||||
|
// directives
|
||||||
|
// void do$origin(Name origin);
|
||||||
|
|
||||||
|
// process parser
|
||||||
|
// void do$include(String line);
|
||||||
|
|
||||||
|
// void do$ttl(long ttl);
|
||||||
|
|
||||||
|
// void do$unknown(String line);
|
||||||
|
|
||||||
|
void add(ResourceRecord record);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
public interface MasterDataResource {
|
||||||
|
|
||||||
|
void initialize(ServerConfiguration conf);
|
||||||
|
|
||||||
|
void process(MasterDataHandler processor);
|
||||||
|
|
||||||
|
void dispose();
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Constants;
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.handwerkszeug.util.AddressUtil;
|
||||||
|
import org.handwerkszeug.yaml.DefaultHandler;
|
||||||
|
import org.handwerkszeug.yaml.SequenceHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlNodeHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.nodes.NodeId;
|
||||||
|
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||||
|
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* convert Node to Address
|
||||||
|
* </p>
|
||||||
|
* you can use following examples.
|
||||||
|
* <p>
|
||||||
|
* <blockquote>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* #sequence to addresses
|
||||||
|
* - address : 127.0.0.1
|
||||||
|
* port : 53
|
||||||
|
* - 127.0.0.1 : 53
|
||||||
|
* - 127.0.0.1:53
|
||||||
|
* #mapping to address
|
||||||
|
* 127.0.0.1 : 53
|
||||||
|
* address : 127.0.0.1
|
||||||
|
* port : 53
|
||||||
|
* #scalar to address
|
||||||
|
* 127.0.0.1:53
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* </blockquote>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class NodeToAddress extends DefaultHandler<Set<SocketAddress>> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(NodeToAddress.class);
|
||||||
|
|
||||||
|
Map<NodeId, YamlNodeHandler<Set<SocketAddress>>> converters = new HashMap<NodeId, YamlNodeHandler<Set<SocketAddress>>>();
|
||||||
|
|
||||||
|
public NodeToAddress() {
|
||||||
|
this.converters.put(NodeId.scalar, new ScalarToAddress());
|
||||||
|
this.converters.put(NodeId.mapping, new MappingToAddress());
|
||||||
|
this.converters.put(NodeId.sequence, new SequenceHandler<Set<SocketAddress>>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, Set<SocketAddress> context) {
|
||||||
|
YamlNodeHandler<Set<SocketAddress>> handler = this.converters.get(node
|
||||||
|
.getNodeId());
|
||||||
|
if (handler == null) {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.UnsupportedNode, node);
|
||||||
|
} else {
|
||||||
|
handler.handle(node, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ScalarToAddress extends DefaultHandler<Set<SocketAddress>> {
|
||||||
|
protected int defaultPort = Constants.DEFAULT_PORT;
|
||||||
|
|
||||||
|
public ScalarToAddress() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScalarToAddress(int port) {
|
||||||
|
this.defaultPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, Set<SocketAddress> context) {
|
||||||
|
if (node instanceof ScalarNode) {
|
||||||
|
ScalarNode sn = (ScalarNode) node;
|
||||||
|
SocketAddress addr = AddressUtil.convertTo(sn.getValue(), this.defaultPort);
|
||||||
|
if (addr != null) {
|
||||||
|
context.add(addr);
|
||||||
|
} else {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.InvalidAddressFormat,
|
||||||
|
sn.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MappingToAddress extends DefaultHandler<Set<SocketAddress>> {
|
||||||
|
|
||||||
|
protected int defaultPort = Constants.DEFAULT_PORT;
|
||||||
|
|
||||||
|
public MappingToAddress() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingToAddress(int port) {
|
||||||
|
this.defaultPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, Set<SocketAddress> context) {
|
||||||
|
if (node instanceof MappingNode) {
|
||||||
|
MappingNode mn = (MappingNode) node;
|
||||||
|
String[] ary = new String[2];
|
||||||
|
for (NodeTuple nt : mn.getValue()) {
|
||||||
|
String key = YamlUtil.getStringValue(nt.getKeyNode());
|
||||||
|
String value = YamlUtil.getStringValue(nt.getValueNode());
|
||||||
|
if ("address".equalsIgnoreCase(key)) {
|
||||||
|
ary[0] = value;
|
||||||
|
} else if ("port".equalsIgnoreCase(key)) {
|
||||||
|
ary[1] = value;
|
||||||
|
} else {
|
||||||
|
ary[0] = key;
|
||||||
|
ary[1] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtil.isEmpty(ary[0]) == false) {
|
||||||
|
SocketAddress addr = AddressUtil.convertTo(ary[0],
|
||||||
|
AddressUtil.toInt(ary[1], this.defaultPort));
|
||||||
|
if (addr != null) {
|
||||||
|
context.add(addr);
|
||||||
|
} else {
|
||||||
|
LOG.debug(Markers.DETAIL,
|
||||||
|
Messages.InvalidAddressFormat, mn.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Constants;
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainer;
|
||||||
|
import org.handwerkszeug.dns.NameServerContainerProvider;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.handwerkszeug.yaml.DefaultHandler;
|
||||||
|
import org.handwerkszeug.yaml.SequenceHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlNodeHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.nodes.NodeId;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
public class NodeToForwarders extends DefaultHandler<ServerConfiguration> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(NodeToForwarders.class);
|
||||||
|
|
||||||
|
protected NodeToAddress nodeToAddress;
|
||||||
|
protected Map<NodeId, YamlNodeHandler<ServerConfiguration>> converters = new HashMap<NodeId, YamlNodeHandler<ServerConfiguration>>();
|
||||||
|
|
||||||
|
public NodeToForwarders(NodeToAddress nodeToAddress) {
|
||||||
|
super("forwarders");
|
||||||
|
this.converters.put(NodeId.scalar,
|
||||||
|
new ScalarToForwarders(nodeToAddress));
|
||||||
|
this.converters.put(NodeId.sequence,
|
||||||
|
new SequenceHandler<ServerConfiguration>(this));
|
||||||
|
this.converters.put(NodeId.mapping, new MappingToForwarders(
|
||||||
|
nodeToAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, ServerConfiguration context) {
|
||||||
|
YamlNodeHandler<ServerConfiguration> handler = this.converters.get(node.getNodeId());
|
||||||
|
if (handler == null) {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.UnsupportedNode, node);
|
||||||
|
} else {
|
||||||
|
handler.handle(node, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MappingToForwarders extends
|
||||||
|
DefaultHandler<ServerConfiguration> {
|
||||||
|
NodeToAddress nodeToAddress;
|
||||||
|
|
||||||
|
public MappingToForwarders(NodeToAddress nodeToAddress) {
|
||||||
|
this.nodeToAddress = nodeToAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, ServerConfiguration context) {
|
||||||
|
this.nodeToAddress.handle(node, context.getForwarders());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ScalarToForwarders extends DefaultHandler<ServerConfiguration> {
|
||||||
|
NodeToAddress nodeToAddress;
|
||||||
|
Pattern isAutoDetect = Pattern.compile("auto[_]?detect",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
public ScalarToForwarders(NodeToAddress nodeToAddress) {
|
||||||
|
this.nodeToAddress = nodeToAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, ServerConfiguration context) {
|
||||||
|
String value = YamlUtil.getStringValue(node);
|
||||||
|
if ((StringUtil.isEmpty(value) == false)
|
||||||
|
&& this.isAutoDetect.matcher(value).matches()) {
|
||||||
|
NameServerContainerProvider provider = new NameServerContainerProvider();
|
||||||
|
provider.initialize();
|
||||||
|
NameServerContainer container = provider.getContainer();
|
||||||
|
container.initialize();
|
||||||
|
for (String s : container.nameservers()) {
|
||||||
|
LOG.info(Markers.BOUNDARY,
|
||||||
|
Messages.DetectedForwardingServer, s);
|
||||||
|
context.getForwarders().add(new InetSocketAddress(s,
|
||||||
|
Constants.DEFAULT_PORT));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.nodeToAddress.handle(node, context.getForwarders());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
import org.handwerkszeug.chain.impl.SimpleChainResult;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Streams;
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
public
|
||||||
|
class ResolvConf implements Chain<List<String>, ChainResult> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(ResolvConf.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
ChainResult execute(List<String> context) {
|
||||||
|
handle("/etc/resolv.conf", context);
|
||||||
|
return SimpleChainResult.Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
void handle(String path, final List<String> list) {
|
||||||
|
final File file = new File(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
new Streams.using<BufferedReader, Exception>() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
BufferedReader open() throws Exception {
|
||||||
|
return new BufferedReader(new InputStreamReader(new FileInputStream(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void handle(BufferedReader stream) throws Exception {
|
||||||
|
while (stream.ready()) {
|
||||||
|
parse(stream.readLine(), list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void happen(Exception e) {
|
||||||
|
LOG.error(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
void parse(String line, List<String> list) {
|
||||||
|
if (StringUtil.isEmpty(line) == false) {
|
||||||
|
String[] ary = line.split("\\s");
|
||||||
|
if ((1 < ary.length) && ary[0].equalsIgnoreCase("nameserver")) {
|
||||||
|
list.add(ary[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface ServerConfiguration {
|
||||||
|
|
||||||
|
Set<SocketAddress> getBindingHosts();
|
||||||
|
|
||||||
|
int getThreadPoolSize();
|
||||||
|
|
||||||
|
Set<SocketAddress> getForwarders();
|
||||||
|
|
||||||
|
void setThreadPoolSize(int threadPoolSize);
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Zone;
|
||||||
|
import org.handwerkszeug.util.AddressUtil;
|
||||||
|
import org.handwerkszeug.yaml.DefaultHandler;
|
||||||
|
import org.handwerkszeug.yaml.MappingHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlNodeAccepter;
|
||||||
|
import org.handwerkszeug.yaml.YamlNodeHandler;
|
||||||
|
import org.handwerkszeug.yaml.YamlUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.Streams;
|
||||||
|
|
||||||
|
public class ServerConfigurationImpl implements ServerConfiguration {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory
|
||||||
|
.getLogger(ServerConfigurationImpl.class);
|
||||||
|
|
||||||
|
protected Set<SocketAddress> bindingHosts = new HashSet<SocketAddress>();
|
||||||
|
|
||||||
|
protected Set<SocketAddress> forwarders = new HashSet<SocketAddress>();
|
||||||
|
|
||||||
|
protected List<Zone> zones = new ArrayList<Zone>();
|
||||||
|
|
||||||
|
protected int threadPoolSize = 10;
|
||||||
|
|
||||||
|
public ServerConfigurationImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(final URL url) {
|
||||||
|
new Streams.using<BufferedInputStream, Exception>() {
|
||||||
|
@Override
|
||||||
|
public BufferedInputStream open() throws Exception {
|
||||||
|
return new BufferedInputStream(url.openStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(BufferedInputStream stream) throws Exception {
|
||||||
|
load(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void happen(Exception exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(InputStream in) {
|
||||||
|
YamlNodeHandler<ServerConfiguration> root = createRootHandler();
|
||||||
|
YamlNodeAccepter<ServerConfiguration> accepter = new YamlNodeAccepter<ServerConfiguration>(
|
||||||
|
root);
|
||||||
|
accepter.accept(in, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected YamlNodeHandler<ServerConfiguration> createRootHandler() {
|
||||||
|
MappingHandler<ServerConfiguration> root = new MappingHandler<ServerConfiguration>();
|
||||||
|
final NodeToAddress node2addr = new NodeToAddress();
|
||||||
|
root.add(new DefaultHandler<ServerConfiguration>("bindingHosts") {
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, ServerConfiguration context) {
|
||||||
|
node2addr.handle(node, context.getBindingHosts());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
root.add(new NodeToForwarders(node2addr));
|
||||||
|
// TODO logging
|
||||||
|
root.add(new DefaultHandler<ServerConfiguration>("threadPoolSize") {
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, ServerConfiguration conf) {
|
||||||
|
String value = YamlUtil.getStringValue(node);
|
||||||
|
conf.setThreadPoolSize(AddressUtil.toInt(value, 10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SocketAddress> getBindingHosts() {
|
||||||
|
return this.bindingHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThreadPoolSize() {
|
||||||
|
return this.threadPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SocketAddress> getForwarders() {
|
||||||
|
return this.forwarders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThreadPoolSize(int threadPoolSize) {
|
||||||
|
this.threadPoolSize = threadPoolSize;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
import org.handwerkszeug.chain.impl.SimpleChainResult;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class SunJRE implements Chain<List<String>, ChainResult> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(SunJRE.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainResult execute(List<String> context) {
|
||||||
|
try {
|
||||||
|
Class<?> clazz = Class.forName("sun.net.dns.ResolverConfiguration");
|
||||||
|
Method open = clazz.getDeclaredMethod("open");
|
||||||
|
Method nameservers = clazz.getDeclaredMethod("nameservers");
|
||||||
|
Object conf = open.invoke(null);
|
||||||
|
Object maybelist = nameservers.invoke(conf);
|
||||||
|
for (Object o : List.class.cast(maybelist)) {
|
||||||
|
context.add(o.toString());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
return SimpleChainResult.Continue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.handwerkszeug.dns.conf;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.chain.Chain;
|
||||||
|
import org.handwerkszeug.chain.ChainResult;
|
||||||
|
import org.handwerkszeug.chain.impl.SimpleChainResult;
|
||||||
|
import org.handwerkszeug.dns.Constants;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
public class SystemProperties implements Chain<List<String>, ChainResult> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainResult execute(List<String> context) {
|
||||||
|
String servers = System.getProperty(Constants.SYSTEM_PROPERTY_NAMESERVERS);
|
||||||
|
if (StringUtil.isEmpty(servers) == false) {
|
||||||
|
for (String s : servers.split(",")) {
|
||||||
|
context.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SimpleChainResult.Continue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
RFC4741 NETCONF Configuration Protocol
|
||||||
|
RFC5381 Experience of Implementing NETCONF over SOAP
|
||||||
|
|
||||||
|
そのものを利用する事は無くても、方向性として考慮する必要はありそう。
|
||||||
|
ここに書いてある程度の事は出来なければ、別解を用意する意味は無い。
|
|
@ -0,0 +1,229 @@
|
||||||
|
package org.handwerkszeug.dns.conf.masterfile;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.conf.MasterDataHandler;
|
||||||
|
import org.handwerkszeug.dns.conf.MasterDataResource;
|
||||||
|
import org.handwerkszeug.dns.conf.ServerConfiguration;
|
||||||
|
import org.handwerkszeug.dns.conf.masterfile.Partition.PartitionType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.FileUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC1035 5. MASTER FILES
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class MasterFileParser implements MasterDataResource {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(MasterFileParser.class);
|
||||||
|
|
||||||
|
static final int MAX_INCLUDE_DEPTH = 10; // TODO from configuration ?
|
||||||
|
|
||||||
|
final Partitioner partitioner;
|
||||||
|
|
||||||
|
ServerConfiguration conf;
|
||||||
|
|
||||||
|
Name origin;
|
||||||
|
long ttl;
|
||||||
|
|
||||||
|
IncludeContext includeContext;
|
||||||
|
|
||||||
|
int currentLine = 1; // TODO for error messages.
|
||||||
|
|
||||||
|
class IncludeContext {
|
||||||
|
int includeDepth = 0;
|
||||||
|
Set<String> includedPath = new HashSet<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterFileParser(String origin, File master) {
|
||||||
|
this(origin, FileUtil.open(master));
|
||||||
|
this.includeContext.includedPath.add(master.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterFileParser(String origin, InputStream in) {
|
||||||
|
this.includeContext = new IncludeContext();
|
||||||
|
this.partitioner = new Partitioner(in);
|
||||||
|
this.origin = new Name(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MasterFileParser(Name origin, File file, IncludeContext context) {
|
||||||
|
this.includeContext = context;
|
||||||
|
this.partitioner = new Partitioner(FileUtil.open(file));
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(ServerConfiguration conf) {
|
||||||
|
notNull(conf, "conf");
|
||||||
|
if (LOG.isInfoEnabled()) {
|
||||||
|
LOG.info("initialize");
|
||||||
|
}
|
||||||
|
this.conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (LOG.isInfoEnabled()) {
|
||||||
|
LOG.info("dispose");
|
||||||
|
}
|
||||||
|
this.partitioner.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(MasterDataHandler handler) {
|
||||||
|
notNull(handler, "processor");
|
||||||
|
try {
|
||||||
|
handler.initialize(this.conf);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
handler.rollback();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
handler.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void internalProcess(MasterDataHandler handler) {
|
||||||
|
Name currentName = null;
|
||||||
|
long currentTTL = 0L;
|
||||||
|
DNSClass currentClass = DNSClass.IN;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Iterator<Partition> line = readLine();
|
||||||
|
if (line.hasNext()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Partition first = line.next();
|
||||||
|
|
||||||
|
if (isDirective(first)) {
|
||||||
|
String directive = first.getString().toUpperCase();
|
||||||
|
if ("$INCLUDE".equals(directive)) {
|
||||||
|
String path = null;
|
||||||
|
Name newOrigin = this.origin;
|
||||||
|
if (line.hasNext()) {
|
||||||
|
path = line.next().getString();
|
||||||
|
} else {
|
||||||
|
// TODO parser error
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (line.hasNext()) {
|
||||||
|
String s = line.next().getString();
|
||||||
|
newOrigin = new Name(s);
|
||||||
|
}
|
||||||
|
processInclude(path, newOrigin, handler);
|
||||||
|
} else if ("$ORIGIN".equals(directive)) {
|
||||||
|
if (line.hasNext()) {
|
||||||
|
String origin = line.next().getString();
|
||||||
|
this.origin = new Name(origin);
|
||||||
|
}
|
||||||
|
} else if ("$TTL".equals(directive)) {
|
||||||
|
if (line.hasNext()) {
|
||||||
|
String num = line.next().getString();
|
||||||
|
if (isTTL(num)) {
|
||||||
|
this.ttl = Long.parseLong(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn("unknown directive {}", directive);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (first.type().equals(PartitionType.Default)) {
|
||||||
|
currentName = new Name(first.getString());
|
||||||
|
}
|
||||||
|
if (line.hasNext()) {
|
||||||
|
String second = line.next().getString();
|
||||||
|
// ttl class type
|
||||||
|
// ttl type
|
||||||
|
// class ttl type
|
||||||
|
// class type
|
||||||
|
// type
|
||||||
|
if (isTTL(second)) {
|
||||||
|
currentTTL = Long.parseLong(second);
|
||||||
|
if (line.hasNext()) {
|
||||||
|
String third = line.next().getString();
|
||||||
|
if (isDNSClass(third)) {
|
||||||
|
|
||||||
|
} else if (isRRType(third)) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// TODO parser error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO parser error
|
||||||
|
}
|
||||||
|
} else if (isDNSClass(second)) {
|
||||||
|
currentClass = DNSClass.valueOf(second.toUpperCase());
|
||||||
|
} else if (isRRType(second)) {
|
||||||
|
RRType type = RRType.valueOf(second.toUpperCase());
|
||||||
|
} else {
|
||||||
|
// TODO parser error.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO parser error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processInclude(String path, Name origin,
|
||||||
|
MasterDataHandler handler) {
|
||||||
|
if (MAX_INCLUDE_DEPTH < ++this.includeContext.includeDepth) {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
File file = new File(path);
|
||||||
|
if (this.includeContext.includedPath.add(file.getAbsolutePath()) == false) {
|
||||||
|
// TODO error message. cyclic include.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterFileParser newone = new MasterFileParser(origin, file,
|
||||||
|
this.includeContext);
|
||||||
|
try {
|
||||||
|
newone.initialize(this.conf);
|
||||||
|
newone.process(handler);
|
||||||
|
} finally {
|
||||||
|
newone.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Iterator<Partition> readLine() {
|
||||||
|
// 改行のみ 空白のみ コメントのみ は読み飛ばす。
|
||||||
|
// 先頭のWhitespaceは読み飛ばさないが、他のWhitespaceは読み飛ばす。
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isDirective(Partition p) {
|
||||||
|
byte[] b = p.division();
|
||||||
|
return b != null && 0 < b.length && b[0] == '$';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isWhitespace(Partition p) {
|
||||||
|
return PartitionType.Whitespace.equals(p.type());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isTTL(String p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isDNSClass(String p) {
|
||||||
|
DNSClass dc = DNSClass.find(p);
|
||||||
|
return dc != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isRRType(String p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.handwerkszeug.dns.conf.masterfile;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class Partition {
|
||||||
|
public enum PartitionType {
|
||||||
|
Default, Quoted, LP, RP, Comment, Whitespace, EOL, EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Partition EOF = new Partition(PartitionType.EOF);
|
||||||
|
|
||||||
|
public static final Partition EOL = new Partition(PartitionType.EOL);
|
||||||
|
|
||||||
|
public static final Partition LP = new Partition(PartitionType.LP);
|
||||||
|
|
||||||
|
public static final Partition RP = new Partition(PartitionType.RP);
|
||||||
|
|
||||||
|
final PartitionType type;
|
||||||
|
final byte[] division;
|
||||||
|
|
||||||
|
public Partition(PartitionType type) {
|
||||||
|
this(type, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Partition(PartitionType type, byte[] buffer) {
|
||||||
|
this.type = type;
|
||||||
|
this.division = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartitionType type() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] division() {
|
||||||
|
return this.division;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return new String(this.division);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result
|
||||||
|
+ ((this.type == null) ? 0 : this.type.hashCode());
|
||||||
|
result = prime * result + Arrays.hashCode(this.division);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Partition other = (Partition) obj;
|
||||||
|
if (this.type != other.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(this.division, other.division)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append("[");
|
||||||
|
stb.append(this.type);
|
||||||
|
stb.append("]");
|
||||||
|
if (this.division != null) {
|
||||||
|
stb.append("<");
|
||||||
|
stb.append(new String(this.division));
|
||||||
|
stb.append(">");
|
||||||
|
}
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
package org.handwerkszeug.dns.conf.masterfile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.conf.masterfile.Partition.PartitionType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
public class Partitioner {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(Partitioner.class);
|
||||||
|
|
||||||
|
final InputStream source;
|
||||||
|
|
||||||
|
static final int DEFAULT_BUFFER_SIZE = 2000;
|
||||||
|
protected ByteBuf working;
|
||||||
|
protected Partition next;
|
||||||
|
|
||||||
|
public Partitioner(InputStream in) {
|
||||||
|
this(in, DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Partitioner(InputStream in, int size) {
|
||||||
|
this.source = in;
|
||||||
|
this.working = Unpooled.buffer(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Partition partition() {
|
||||||
|
Partition result = this.next;
|
||||||
|
if (result != null) {
|
||||||
|
this.next = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
byte ch = readByte();
|
||||||
|
if (ch == -1) {
|
||||||
|
if (0 < this.working.readerIndex()) {
|
||||||
|
result = makePartition(PartitionType.Default, 0);
|
||||||
|
discardBefore(0);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch == '\r') {
|
||||||
|
byte n = readByte();
|
||||||
|
if (n == '\n') {
|
||||||
|
if (this.working.readerIndex() < 3) {
|
||||||
|
this.working.discardReadBytes();
|
||||||
|
return Partition.EOL;
|
||||||
|
}
|
||||||
|
this.next = Partition.EOL;
|
||||||
|
result = makePartition(PartitionType.Default, 2);
|
||||||
|
discardBefore(0);
|
||||||
|
} else {
|
||||||
|
this.working.readerIndex(this.working.readerIndex() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch == '\n') {
|
||||||
|
if (this.working.readerIndex() < 2) {
|
||||||
|
this.working.discardReadBytes();
|
||||||
|
return Partition.EOL;
|
||||||
|
}
|
||||||
|
this.next = Partition.EOL;
|
||||||
|
result = makePartition(PartitionType.Default, 1);
|
||||||
|
discardBefore(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == ';') {
|
||||||
|
result = readTo(partitionBefore(), PartitionType.Comment, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '(') {
|
||||||
|
result = currentOrNext(partitionBefore(), Partition.LP);
|
||||||
|
}
|
||||||
|
if (ch == ')') {
|
||||||
|
result = currentOrNext(partitionBefore(), Partition.RP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ch == '"')) {
|
||||||
|
result = readTo(partitionBefore(), PartitionType.Quoted, '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((ch == ' ') || (ch == '\t'))) {
|
||||||
|
result = partitionBefore();
|
||||||
|
int begin = this.working.readerIndex() - 1;
|
||||||
|
while (true) {
|
||||||
|
byte c = readByte();
|
||||||
|
if ((c != ' ') && (c != '\t')) {
|
||||||
|
int end = this.working.readerIndex() - 1;
|
||||||
|
Partition ws = makePartition(PartitionType.Whitespace,
|
||||||
|
begin, end);
|
||||||
|
result = currentOrNext(result, ws, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Partition.EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition currentOrNext(Partition before, Partition p) {
|
||||||
|
return currentOrNext(before, p, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition currentOrNext(Partition before, Partition p, int discard) {
|
||||||
|
Partition result = before;
|
||||||
|
if (before == null) {
|
||||||
|
result = p;
|
||||||
|
} else {
|
||||||
|
this.next = p;
|
||||||
|
}
|
||||||
|
discardBefore(discard);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition readTo(Partition back, PartitionType type, char stop) {
|
||||||
|
Partition result = back;
|
||||||
|
int begin = this.working.readerIndex() - 1;
|
||||||
|
while (true) {
|
||||||
|
byte c = readByte();
|
||||||
|
if ((c == stop) || (c == -1)) {
|
||||||
|
int end = this.working.readerIndex();
|
||||||
|
Partition p = makePartition(type, begin, end);
|
||||||
|
result = currentOrNext(back, p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte readByte() {
|
||||||
|
try {
|
||||||
|
if (this.working.isReadable() == false) {
|
||||||
|
if (0 < this.source.available()) {
|
||||||
|
this.working.writeBytes(this.source, DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.working.isReadable()) {
|
||||||
|
return this.working.readByte();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition makePartition(PartitionType type, int begin, int end) {
|
||||||
|
byte[] newone = new byte[end - begin];
|
||||||
|
this.working.getBytes(begin, newone);
|
||||||
|
return new Partition(type, newone);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition makePartition(PartitionType type, int stripSize) {
|
||||||
|
int newsize = this.working.readerIndex() - stripSize;
|
||||||
|
byte[] newone = new byte[newsize];
|
||||||
|
this.working.getBytes(0, newone);
|
||||||
|
return new Partition(type, newone);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Partition partitionBefore() {
|
||||||
|
if (1 < this.working.readerIndex()) {
|
||||||
|
return makePartition(PartitionType.Default, 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void discardBefore(int backSize) {
|
||||||
|
this.working.readerIndex(this.working.readerIndex() - backSize);
|
||||||
|
this.working.discardReadBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
this.source.close();
|
||||||
|
this.working.release();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.handwerkszeug.dns.nls;
|
||||||
|
|
||||||
|
public class Messages {
|
||||||
|
|
||||||
|
public static String InvalidParameter = "{} needs {} but {}";
|
||||||
|
|
||||||
|
public static String ComposeNode = "compose yaml node";
|
||||||
|
|
||||||
|
public static String UnsupportedAttribute = "unsupported attribute {}";
|
||||||
|
|
||||||
|
public static String UnsupportedNode = "unsupported node {}";
|
||||||
|
|
||||||
|
public static String InvalidAddressFormat = "invalid address format {}";
|
||||||
|
|
||||||
|
public static String DetectedForwardingServer = "detected forwarding server {}";
|
||||||
|
|
||||||
|
public static String NullLabelIsNotValid = "null label is not valid";
|
||||||
|
|
||||||
|
public static String InvalidCompressionMask = "Invalid compression mask %s";
|
||||||
|
|
||||||
|
public static String LabelsMustBe63orLess = "Labels must be 63 characters or less. current input=%s";
|
||||||
|
|
||||||
|
public static String NamesMustBe255orLess = "Labels must be 255 characters or less. current size=%s";
|
||||||
|
|
||||||
|
public static String EscapedDecimalIsInvalid = "escaped decimal is invalid value=%s";
|
||||||
|
|
||||||
|
public static String MixtureOfEscapedDigitAndNonDigit = "mixture of escaped digit and non-digit namedata=%s";
|
||||||
|
|
||||||
|
public static String InvalidEscapeSequence = "invalid escape sequence namedata=%s";
|
||||||
|
|
||||||
|
public static String StringMustBe255orLess = "String must be 255 characters or less. current length=%s";
|
||||||
|
|
||||||
|
public static String DataMustBe65535orLess = "Data must be 65535 characters or less. current length=%s";
|
||||||
|
|
||||||
|
public static String InvalidPortNumber = "invalid port number %s%n";
|
||||||
|
|
||||||
|
public static String NoResourceRecord = "%s has no resource record.";
|
||||||
|
|
||||||
|
public static String Not16bitValue = "%s is not 16bit value. current value=%s";
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.AddressUtil;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://datatracker.ietf.org/doc/rfc3596/">RFC3596</a>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class AAAARecord extends AbstractRecord<AAAARecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 128 bit IPv6 address is encoded in the data portion of an AAAA resource
|
||||||
|
* record in network byte order (high-order byte first).
|
||||||
|
*/
|
||||||
|
protected byte[] address;
|
||||||
|
|
||||||
|
public
|
||||||
|
AAAARecord() {
|
||||||
|
super(RRType.AAAA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
AAAARecord(AAAARecord from) {
|
||||||
|
super(from);
|
||||||
|
byte[] ary = from.address;
|
||||||
|
if (ary != null) {
|
||||||
|
this.address = Arrays.copyOf(ary, ary.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
byte[] newone = new byte[16];
|
||||||
|
buffer.readBytes(newone);
|
||||||
|
this.address = newone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeBytes(this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new AAAARecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(AAAARecord o) {
|
||||||
|
if ((this != o) && (super.compareTo(o) == 0)) {
|
||||||
|
return CompareUtil.compare(this.address, o.address);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
InetAddress ia = address();
|
||||||
|
if (ia == null) {
|
||||||
|
stb.append("null");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stb.append(ia.getHostAddress());
|
||||||
|
}
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
InetAddress address() {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(this.address);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (0 < list.size()) {
|
||||||
|
String s = list.get(0);
|
||||||
|
if (AddressUtil.v6Address.matcher(s)
|
||||||
|
.matches()) {
|
||||||
|
InetAddress addr = AddressUtil.getByName(s);
|
||||||
|
this.address = addr.getAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void address(Inet6Address v6address) {
|
||||||
|
this.address = v6address.getAddress();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.AddressUtil;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.4.1. A RDATA format
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class ARecord extends AbstractRecord<ARecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32 bit Internet address.
|
||||||
|
*/
|
||||||
|
protected long address;
|
||||||
|
|
||||||
|
public
|
||||||
|
ARecord() {
|
||||||
|
super(RRType.A);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
ARecord(ARecord from) {
|
||||||
|
super(from);
|
||||||
|
this.address = from.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.address = buffer.readUnsignedInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeInt((int) this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new ARecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(ARecord o) {
|
||||||
|
if ((this != o) && (super.compareTo(o) == 0)) {
|
||||||
|
return CompareUtil.compare(this.address, o.address);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = prime * result + (int) (this.address ^ (this.address >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!super.equals(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ARecord other = (ARecord) obj;
|
||||||
|
if (this.address != other.address) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.address()
|
||||||
|
.getHostAddress());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
InetAddress address() {
|
||||||
|
return AddressUtil.getByAddress(this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (0 < list.size()) {
|
||||||
|
String s = list.get(0);
|
||||||
|
if (AddressUtil.v4Address.matcher(s)
|
||||||
|
.matches()) {
|
||||||
|
InetAddress addr = AddressUtil.getByName(s);
|
||||||
|
this.address = AddressUtil.toLong(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void address(InetAddress v4address) {
|
||||||
|
this.address = AddressUtil.toLong(v4address);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,361 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import werkzeugkasten.common.util.StringUtil;
|
||||||
|
|
||||||
|
public abstract
|
||||||
|
class AbstractRecord<T extends ResourceRecord> implements ResourceRecord, Comparable<T> {
|
||||||
|
|
||||||
|
public static byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
|
||||||
|
public static int MAX_STRING_LENGTH = 255;
|
||||||
|
|
||||||
|
protected RRType type;
|
||||||
|
|
||||||
|
protected Name name;
|
||||||
|
|
||||||
|
protected DNSClass dnsClass = DNSClass.IN;
|
||||||
|
|
||||||
|
protected long ttl;
|
||||||
|
|
||||||
|
protected int rdlength;
|
||||||
|
|
||||||
|
public
|
||||||
|
AbstractRecord(RRType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
AbstractRecord(AbstractRecord<T> from) {
|
||||||
|
this.type = from.type();
|
||||||
|
this.name(from.name());
|
||||||
|
this.dnsClass(from.dnsClass());
|
||||||
|
this.ttl(from.ttl());
|
||||||
|
this.rdlength(from.rdlength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
RRType type() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Name name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void name(Name name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
DNSClass dnsClass() {
|
||||||
|
return this.dnsClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void dnsClass(DNSClass dnsClass) {
|
||||||
|
this.dnsClass = dnsClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
long ttl() {
|
||||||
|
return this.ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void ttl(long ttl) {
|
||||||
|
this.ttl = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int rdlength() {
|
||||||
|
return this.rdlength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void rdlength(int value) {
|
||||||
|
this.rdlength = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void parse(ByteBuf buffer) {
|
||||||
|
this.ttl(buffer.readUnsignedInt());
|
||||||
|
this.rdlength(buffer.readUnsignedShort());
|
||||||
|
|
||||||
|
parseRDATA(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4.1.3. Resource record format<br/>
|
||||||
|
* <b>RDATA</b> a variable length string of octets that describes the
|
||||||
|
* resource. The format of this information varies according to the TYPE and
|
||||||
|
* CLASS of the resource record.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
*/
|
||||||
|
protected abstract
|
||||||
|
void parseRDATA(ByteBuf buffer);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void write(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeInt((int) this.ttl());
|
||||||
|
int rdlengthIndex = buffer.writerIndex();
|
||||||
|
buffer.writeShort(0); // at first, write zero.
|
||||||
|
|
||||||
|
writeRDATA(buffer, compressor);
|
||||||
|
|
||||||
|
int rdlength = (buffer.writerIndex() - rdlengthIndex - 2) & 0xFFFF;
|
||||||
|
buffer.setShort(rdlengthIndex, rdlength);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
ResourceRecord toQnameRecord(Name qname) {
|
||||||
|
ResourceRecord newone = newInstance();
|
||||||
|
newone.name(qname);
|
||||||
|
return newone;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract
|
||||||
|
ResourceRecord newInstance();
|
||||||
|
|
||||||
|
public static
|
||||||
|
ResourceRecord parseSection(ByteBuf buffer) {
|
||||||
|
Name n = new Name(buffer);
|
||||||
|
RRType t = RRType.valueOf(buffer.readUnsignedShort());
|
||||||
|
DNSClass dc = DNSClass.valueOf(buffer.readUnsignedShort());
|
||||||
|
ResourceRecord result = t.newRecord();
|
||||||
|
result.name(n);
|
||||||
|
result.dnsClass(dc);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
void writeSection(ByteBuf buffer, NameCompressor compressor, ResourceRecord rr) {
|
||||||
|
rr.name()
|
||||||
|
.write(buffer, compressor);
|
||||||
|
buffer.writeShort(rr.type()
|
||||||
|
.value());
|
||||||
|
buffer.writeShort(rr.dnsClass()
|
||||||
|
.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3. Standard RRs
|
||||||
|
* <p>
|
||||||
|
* <character-string> is a single length octet followed by that number
|
||||||
|
* of characters. <character-string> is treated as binary information,
|
||||||
|
* and can be up to 256 characters in length (including the length octet).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
*/
|
||||||
|
protected
|
||||||
|
byte[] readString(ByteBuf buffer) {
|
||||||
|
short length = buffer.readUnsignedByte();
|
||||||
|
if (MAX_STRING_LENGTH < length) {
|
||||||
|
throw new IllegalStateException(String.format(Messages.StringMustBe255orLess, length));
|
||||||
|
}
|
||||||
|
byte[] newone = new byte[length];
|
||||||
|
buffer.readBytes(newone);
|
||||||
|
return newone;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
void writeString(ByteBuf buffer, byte[] ary) {
|
||||||
|
int length = ary.length;
|
||||||
|
buffer.writeByte(length);
|
||||||
|
buffer.writeBytes(ary);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
StringBuilder toQuoteString(byte[] ary) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
result.append('"');
|
||||||
|
result.append(toString(ary));
|
||||||
|
result.append('"');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5.1. Format
|
||||||
|
*
|
||||||
|
* @param ary
|
||||||
|
*/
|
||||||
|
protected
|
||||||
|
StringBuilder toString(byte[] ary) {
|
||||||
|
DecimalFormat fmt = new DecimalFormat("###");
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (byte b : ary) {
|
||||||
|
int i = b & 0xFF;
|
||||||
|
if ((i < 0x20) || (0x7E < i)) { // control code
|
||||||
|
result.append('\\');
|
||||||
|
result.append(fmt.format(i));
|
||||||
|
}
|
||||||
|
else if ((i == '"') || (i == '\\')) {
|
||||||
|
result.append('\\');
|
||||||
|
result.append((char) i);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.append((char) i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
byte[] toArrayFromQuoted(String string) {
|
||||||
|
if (StringUtil.isEmpty(string)) {
|
||||||
|
return EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
if (string.length() < 3) {
|
||||||
|
return EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
return toArray(string.substring(1, string.length() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected
|
||||||
|
byte[] toArray(String string) {
|
||||||
|
if (StringUtil.isEmpty(string)) {
|
||||||
|
return EMPTY_BYTE_ARRAY;
|
||||||
|
}
|
||||||
|
byte[] bytes = string.getBytes();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
for (int i = 0, length = bytes.length; i < length; i++) {
|
||||||
|
byte b = bytes[i];
|
||||||
|
if (b == '\\') {
|
||||||
|
byte next = bytes[++i];
|
||||||
|
if (Character.isDigit(next)) {
|
||||||
|
int value = ((next - '0') * 100) + ((bytes[++i] - '0') * 10) + bytes[++i] - '0';
|
||||||
|
out.write(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.write(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(T o) {
|
||||||
|
if (o == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int result = this.type()
|
||||||
|
.compareTo(o.type());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = this.name()
|
||||||
|
.compareTo(o.name());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return this.dnsClass()
|
||||||
|
.compareTo(o.dnsClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.dnsClass == null) ? 0 : this.dnsClass.hashCode());
|
||||||
|
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
|
||||||
|
result = prime * result + this.rdlength;
|
||||||
|
result = prime * result + (int) (this.ttl ^ (this.ttl >>> 32));
|
||||||
|
result = prime * result + ((this.type == null) ? 0 : this.type.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof AbstractRecord) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
AbstractRecord<T> other = (AbstractRecord<T>) obj;
|
||||||
|
return equals(other);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
boolean equals(AbstractRecord<T> other) {
|
||||||
|
if (this.dnsClass != other.dnsClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.name == null) {
|
||||||
|
if (other.name != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.name.equals(other.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.rdlength != other.rdlength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.ttl != other.ttl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.type != other.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(this.name()
|
||||||
|
.toString());
|
||||||
|
StringUtil.padRight(stb, ' ', 23);
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.ttl());
|
||||||
|
StringUtil.padRight(stb, ' ', 31);
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.dnsClass()
|
||||||
|
.name());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.type()
|
||||||
|
.name());
|
||||||
|
StringUtil.padRight(stb, ' ', 39);
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.2. HINFO RDATA format
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* Standard values for CPU and OS can be found in [RFC-1010].
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class HINFORecord extends AbstractRecord<HINFORecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <character-string> which specifies the CPU type.
|
||||||
|
*/
|
||||||
|
protected byte[] cpu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <character-string> which specifies the operating system type.
|
||||||
|
*/
|
||||||
|
protected byte[] os;
|
||||||
|
|
||||||
|
public
|
||||||
|
HINFORecord() {
|
||||||
|
super(RRType.HINFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
HINFORecord(HINFORecord from) {
|
||||||
|
super(from);
|
||||||
|
byte[] c = from.cpu;
|
||||||
|
if (c != null) {
|
||||||
|
this.cpu = Arrays.copyOf(c, c.length);
|
||||||
|
}
|
||||||
|
byte[] o = from.os;
|
||||||
|
if (o != null) {
|
||||||
|
this.os = Arrays.copyOf(o, o.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.cpu = readString(buffer);
|
||||||
|
this.os = readString(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
writeString(buffer, this.cpu);
|
||||||
|
writeString(buffer, this.os);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new HINFORecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(HINFORecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result == 0) {
|
||||||
|
result = CompareUtil.compare(this.cpu, o.cpu);
|
||||||
|
if (result == 0) {
|
||||||
|
result = CompareUtil.compare(this.os, o.os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.cpu());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.os());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String cpu() {
|
||||||
|
return new String(this.cpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String os() {
|
||||||
|
return new String(this.os);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (list.size() == 2) {
|
||||||
|
this.cpu(list.get(0));
|
||||||
|
this.os(list.get(1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void cpu(String cpu) {
|
||||||
|
this.cpu = cpu.getBytes(); // TODO encoding ?
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void os(String os) {
|
||||||
|
this.os = os.getBytes(); // TODO encoding ?
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.7. MINFO RDATA format (EXPERIMENTAL)
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class MINFORecord extends AbstractRecord<MINFORecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <domain-name> which specifies a mailbox which is responsible for the
|
||||||
|
* mailing list or mailbox. If this domain name names the root, the owner of
|
||||||
|
* the MINFO RR is responsible for itself. Note that many existing mailing
|
||||||
|
* lists use a mailbox X-request for the RMAILBX field of mailing list X,
|
||||||
|
* e.g., Msgroup-request for Msgroup. This field provides a more general
|
||||||
|
* mechanism.
|
||||||
|
*/
|
||||||
|
protected Name rmailbx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <domain-name> which specifies a mailbox which is to receive error
|
||||||
|
* messages related to the mailing list or mailbox specified by the owner of
|
||||||
|
* the MINFO RR (similar to the ERRORS-TO: field which has been proposed).
|
||||||
|
* If this domain name names the root, errors should be returned to the
|
||||||
|
* sender of the message.
|
||||||
|
*/
|
||||||
|
protected Name emailbx;
|
||||||
|
|
||||||
|
public
|
||||||
|
MINFORecord() {
|
||||||
|
super(RRType.MINFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
MINFORecord(MINFORecord from) {
|
||||||
|
super(from);
|
||||||
|
this.rmailbx(from.rmailbx());
|
||||||
|
this.emailbx(from.emailbx());
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name rmailbx() {
|
||||||
|
return this.rmailbx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void rmailbx(Name name) {
|
||||||
|
this.rmailbx = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name emailbx() {
|
||||||
|
return this.emailbx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void emailbx(Name name) {
|
||||||
|
this.emailbx = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.rmailbx = new Name(buffer);
|
||||||
|
this.emailbx = new Name(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
this.rmailbx()
|
||||||
|
.write(buffer, compressor);
|
||||||
|
this.emailbx()
|
||||||
|
.write(buffer, compressor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new MINFORecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(MINFORecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result == 0) {
|
||||||
|
result = this.rmailbx()
|
||||||
|
.compareTo(o.rmailbx());
|
||||||
|
if (result == 0) {
|
||||||
|
result = this.emailbx()
|
||||||
|
.compareTo(o.emailbx());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.rmailbx());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.emailbx());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (2 == list.size()) {
|
||||||
|
this.rmailbx(new Name(list.get(0)));
|
||||||
|
this.emailbx(new Name(list.get(1)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.9. MX RDATA format
|
||||||
|
* <p>
|
||||||
|
* <pre>
|
||||||
|
* 1 1 1 1 1 1
|
||||||
|
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | PREFERENCE |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* / EXCHANGE /
|
||||||
|
* / /
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class MXRecord extends AbstractRecord<MXRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 16 bit integer which specifies the preference given to this RR among
|
||||||
|
* others at the same owner. Lower values are preferred.
|
||||||
|
*/
|
||||||
|
protected int preference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RFC 974 the name of a host.
|
||||||
|
*/
|
||||||
|
protected Name exchange;
|
||||||
|
|
||||||
|
public
|
||||||
|
MXRecord() {
|
||||||
|
super(RRType.MX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
MXRecord(MXRecord from) {
|
||||||
|
super(from);
|
||||||
|
this.preference = from.preference();
|
||||||
|
this.exchange = from.exchange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
int preference() {
|
||||||
|
return this.preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name exchange() {
|
||||||
|
return this.exchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.preference = buffer.readUnsignedShort();
|
||||||
|
this.exchange = new Name(buffer);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeShort(this.preference);
|
||||||
|
this.exchange.write(buffer, compressor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new MXRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(MXRecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result == 0) {
|
||||||
|
result = CompareUtil.compare(this.preference(), o.preference());
|
||||||
|
if (result == 0) {
|
||||||
|
result = this.exchange()
|
||||||
|
.compareTo(o.exchange());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.preference());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.exchange());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (2 == list.size()) {
|
||||||
|
int pref = Integer.parseInt(list.get(0));
|
||||||
|
if (-1 < pref && pref < 65536) {
|
||||||
|
this.preference = pref;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
this.exchange = new Name(list.get(1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.10. NULL RDATA format (EXPERIMENTAL)
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class NULLRecord extends AbstractRecord<NULLRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anything at all may be in the RDATA field so long as it is 65535 octets
|
||||||
|
* or less.
|
||||||
|
*/
|
||||||
|
protected byte[] anything;
|
||||||
|
|
||||||
|
public
|
||||||
|
NULLRecord() {
|
||||||
|
super(RRType.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
NULLRecord(NULLRecord from) {
|
||||||
|
super(from);
|
||||||
|
byte[] ary = from.anything();
|
||||||
|
if (ary != null) {
|
||||||
|
this.anything(Arrays.copyOf(ary, ary.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
byte[] anything() {
|
||||||
|
return this.anything;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void anything(byte[] data) {
|
||||||
|
if (65535 < data.length) {
|
||||||
|
throw new IllegalArgumentException(String.format(Messages.DataMustBe65535orLess, data.length));
|
||||||
|
}
|
||||||
|
this.anything = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.anything = new byte[rdlength()];
|
||||||
|
buffer.readBytes(this.anything);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeBytes(anything());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new NULLRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(NULLRecord o) {
|
||||||
|
if ((this != o) && (super.compareTo(o) == 0)) {
|
||||||
|
return CompareUtil.compare(this.anything(), o.anything());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <ul>
|
||||||
|
* <li>3.3.1. CNAME RDATA format</li>
|
||||||
|
* <li>3.3.11. NS RDATA format</li>
|
||||||
|
* <li>3.3.12. PTR RDATA format</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class OPTNameRecord extends AbstractRecord<OPTNameRecord> {
|
||||||
|
|
||||||
|
protected Name oneName;
|
||||||
|
|
||||||
|
public
|
||||||
|
OPTNameRecord(RRType type) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
OPTNameRecord(RRType type, Name oneName) {
|
||||||
|
super(type);
|
||||||
|
this.oneName = oneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
OPTNameRecord(OPTNameRecord from) {
|
||||||
|
super(from);
|
||||||
|
this.oneName = from.oneName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name oneName() {
|
||||||
|
return this.oneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.oneName = new Name(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
this.oneName.write(buffer, compressor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new OPTNameRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(OPTNameRecord o) {
|
||||||
|
if ((this != o) && (super.compareTo(o) == 0)) {
|
||||||
|
return this.oneName()
|
||||||
|
.compareTo(o.oneName());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.oneName());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (0 < list.size()) {
|
||||||
|
this.oneName = new Name(list.get(0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,280 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.13. SOA RDATA format
|
||||||
|
* <p>
|
||||||
|
* <pre>
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* / MNAME /
|
||||||
|
* / /
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* / RNAME /
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | SERIAL |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | REFRESH |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | RETRY |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | EXPIRE |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | MINIMUM |
|
||||||
|
* | |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class SOARecord extends AbstractRecord<SOARecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The <domain-name> of the name server that was the original or primary
|
||||||
|
* source of data for this zone.
|
||||||
|
*/
|
||||||
|
protected Name mname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A <domain-name> which specifies the mailbox of the person responsible for
|
||||||
|
* this zone.
|
||||||
|
*/
|
||||||
|
protected Name rname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unsigned 32 bit version number of the original copy of the zone. Zone
|
||||||
|
* transfers preserve this value. This value wraps and should be compared
|
||||||
|
* using sequence space arithmetic.
|
||||||
|
*/
|
||||||
|
protected long serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32 bit time interval before the zone should be refreshed.
|
||||||
|
*/
|
||||||
|
protected long refresh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32 bit time interval that should elapse before a failed refresh should
|
||||||
|
* be retried.
|
||||||
|
*/
|
||||||
|
protected long retry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 32 bit time value that specifies the upper limit on the time interval
|
||||||
|
* that can elapse before the zone is no longer authoritative.
|
||||||
|
*/
|
||||||
|
protected long expire;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unsigned 32 bit minimum TTL field that should be exported with any RR
|
||||||
|
* from this zone.
|
||||||
|
*/
|
||||||
|
protected long minimum;
|
||||||
|
|
||||||
|
public
|
||||||
|
SOARecord() {
|
||||||
|
super(RRType.SOA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
SOARecord(SOARecord from) {
|
||||||
|
super(from);
|
||||||
|
this.mname(from.mname());
|
||||||
|
this.rname(from.rname());
|
||||||
|
this.serial(from.serial());
|
||||||
|
this.refresh(from.refresh());
|
||||||
|
this.retry(from.retry());
|
||||||
|
this.expire(from.expire());
|
||||||
|
this.minimum(from.minimum());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The <domain-name> of the name server that was the original or primary
|
||||||
|
* source of data for this zone.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Name mname() {
|
||||||
|
return this.mname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void mname(Name name) {
|
||||||
|
this.mname = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name rname() {
|
||||||
|
return this.rname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void rname(Name name) {
|
||||||
|
this.rname = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
long serial() {
|
||||||
|
return this.serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void serial(long uint) {
|
||||||
|
this.serial = uint & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
long refresh() {
|
||||||
|
return this.refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void refresh(long uint) {
|
||||||
|
this.refresh = uint & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
long retry() {
|
||||||
|
return this.retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void retry(long uint) {
|
||||||
|
this.retry = uint & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
long expire() {
|
||||||
|
return this.expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void expire(long uint) {
|
||||||
|
this.expire = uint & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
long minimum() {
|
||||||
|
return this.minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void minimum(long uint) {
|
||||||
|
this.minimum = uint & 0xFFFFFFFFL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.mname(new Name(buffer));
|
||||||
|
this.rname(new Name(buffer));
|
||||||
|
this.serial(buffer.readUnsignedInt());
|
||||||
|
this.refresh(buffer.readUnsignedInt());
|
||||||
|
this.retry(buffer.readUnsignedInt());
|
||||||
|
this.expire(buffer.readUnsignedInt());
|
||||||
|
this.minimum(buffer.readUnsignedInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
this.mname()
|
||||||
|
.write(buffer, compressor);
|
||||||
|
this.rname()
|
||||||
|
.write(buffer, compressor);
|
||||||
|
buffer.writeInt((int) this.serial());
|
||||||
|
buffer.writeInt((int) this.refresh());
|
||||||
|
buffer.writeInt((int) this.retry());
|
||||||
|
buffer.writeInt((int) this.expire());
|
||||||
|
buffer.writeInt((int) this.minimum());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new SOARecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(SOARecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = this.mname()
|
||||||
|
.compareTo(o.mname());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = this.rname()
|
||||||
|
.compareTo(o.rname());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.serial(), o.serial());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.refresh(), o.refresh());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.retry(), o.retry());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.expire(), o.expire());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return CompareUtil.compare(this.minimum(), o.minimum());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.mname());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.rname());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.serial());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.refresh());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.retry());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.expire());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.minimum());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (6 < list.size()) {
|
||||||
|
// XXX
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO error message.
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <ul>
|
||||||
|
* <li>3.3.1. CNAME RDATA format</li>
|
||||||
|
* <li>3.3.11. NS RDATA format</li>
|
||||||
|
* <li>3.3.12. PTR RDATA format</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class SingleNameRecord extends AbstractRecord<SingleNameRecord> {
|
||||||
|
|
||||||
|
protected Name oneName;
|
||||||
|
|
||||||
|
public
|
||||||
|
SingleNameRecord(RRType type) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
SingleNameRecord(RRType type, Name oneName) {
|
||||||
|
super(type);
|
||||||
|
this.oneName = oneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
SingleNameRecord(SingleNameRecord from) {
|
||||||
|
super(from);
|
||||||
|
this.oneName = from.oneName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Name oneName() {
|
||||||
|
return this.oneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.oneName = new Name(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
this.oneName.write(buffer, compressor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new SingleNameRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(SingleNameRecord o) {
|
||||||
|
if ((this != o) && (super.compareTo(o) == 0)) {
|
||||||
|
return this.oneName()
|
||||||
|
.compareTo(o.oneName());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.oneName());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
if (0 < list.size()) {
|
||||||
|
this.oneName = new Name(list.get(0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.3.14. TXT RDATA format
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class TXTRecord extends AbstractRecord<TXTRecord> {
|
||||||
|
|
||||||
|
protected List<byte[]> strings;
|
||||||
|
|
||||||
|
public
|
||||||
|
TXTRecord() {
|
||||||
|
super(RRType.TXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
TXTRecord(TXTRecord from) {
|
||||||
|
super(from);
|
||||||
|
if (from.strings != null) {
|
||||||
|
List<byte[]> newone = new ArrayList<byte[]>(from.strings.size());
|
||||||
|
for (byte[] b : from.strings) {
|
||||||
|
newone.add(Arrays.copyOf(b, b.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.strings = new ArrayList<byte[]>();
|
||||||
|
ByteBuf part = buffer.readSlice(rdlength());
|
||||||
|
while (part.isReadable()) {
|
||||||
|
this.strings.add(readString(part));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
for (byte[] ary : this.strings) {
|
||||||
|
writeString(buffer, ary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new TXTRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(TXTRecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int mySize = this.strings.size();
|
||||||
|
int yrSize = o.strings.size();
|
||||||
|
int min = Math.min(mySize, yrSize);
|
||||||
|
for (int i = 0; i < min; i++) {
|
||||||
|
result = CompareUtil.compare(this.strings.get(i), o.strings.get(i));
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mySize - yrSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
for (Iterator<byte[]> i = this.strings.iterator(); i.hasNext(); ) {
|
||||||
|
stb.append(toQuoteString(i.next()));
|
||||||
|
if (i.hasNext()) {
|
||||||
|
stb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
for (String s : list) {
|
||||||
|
this.strings.add(s.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String txt() {
|
||||||
|
return this.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package org.handwerkszeug.dns.record;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.NameCompressor;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.client.WKPortNumbers;
|
||||||
|
import org.handwerkszeug.dns.client.WKProtocols;
|
||||||
|
import org.handwerkszeug.util.AddressUtil;
|
||||||
|
import org.handwerkszeug.util.CompareUtil;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3.4.2. WKS RDATA format
|
||||||
|
* <p>
|
||||||
|
* <pre>
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | ADDRESS |
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* | PROTOCOL | |
|
||||||
|
* +--+--+--+--+--+--+--+--+ |
|
||||||
|
* | |
|
||||||
|
* / <BIT MAP> /
|
||||||
|
* / /
|
||||||
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* The WKS record is used to describe the well known services supported by a
|
||||||
|
* particular protocol on a particular internet address. The PROTOCOL field
|
||||||
|
* specifies an IP protocol number, and the bit map has one bit per port of the
|
||||||
|
* specified protocol. The first bit corresponds to port 0, the second to port
|
||||||
|
* 1, etc. If the bit map does not include a bit for a protocol of interest,
|
||||||
|
* that bit is assumed zero. The appropriate values and mnemonics for ports and
|
||||||
|
* protocols are specified in [RFC-1010].
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class WKSRecord extends AbstractRecord<WKSRecord> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An 32 bit Internet address
|
||||||
|
*/
|
||||||
|
protected long address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An 8 bit IP protocol number
|
||||||
|
*/
|
||||||
|
protected short protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variable length bit map. The bit map must be a multiple of 8 bits long.
|
||||||
|
*/
|
||||||
|
protected byte[] bitmap;
|
||||||
|
|
||||||
|
public
|
||||||
|
WKSRecord() {
|
||||||
|
super(RRType.WKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
WKSRecord(WKSRecord from) {
|
||||||
|
super(from);
|
||||||
|
this.address = from.address;
|
||||||
|
this.protocol = from.protocol();
|
||||||
|
byte[] ary = from.bitmap();
|
||||||
|
if (ary != null) {
|
||||||
|
this.bitmap(Arrays.copyOf(ary, ary.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
short protocol() {
|
||||||
|
return this.protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
byte[] bitmap() {
|
||||||
|
return this.bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void bitmap(byte[] bytes) {
|
||||||
|
this.bitmap = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void parseRDATA(ByteBuf buffer) {
|
||||||
|
this.address = buffer.readUnsignedInt();
|
||||||
|
this.protocol = buffer.readUnsignedByte();
|
||||||
|
this.bitmap = new byte[rdlength() - 5];// (32bit + 8bit) / 8bit
|
||||||
|
buffer.readBytes(this.bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
|
||||||
|
buffer.writeInt((int) this.address);
|
||||||
|
buffer.writeByte(this.protocol);
|
||||||
|
buffer.writeBytes(this.bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
ResourceRecord newInstance() {
|
||||||
|
return new WKSRecord(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(WKSRecord o) {
|
||||||
|
if (this == o) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = super.compareTo(o);
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.address, o.address);
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = CompareUtil.compare(this.protocol(), o.protocol());
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return CompareUtil.compare(this.bitmap(), o.bitmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see WKProtocols
|
||||||
|
* @see WKPortNumbers
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder stb = new StringBuilder();
|
||||||
|
stb.append(super.toString());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.address()
|
||||||
|
.getHostAddress());
|
||||||
|
stb.append(' ');
|
||||||
|
stb.append(this.protocol());
|
||||||
|
return stb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
InetAddress address() {
|
||||||
|
return AddressUtil.getByAddress(this.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setRDATA(List<String> list) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void protocol(short no) {
|
||||||
|
this.protocol = no;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.*;
|
||||||
|
import org.handwerkszeug.dns.record.SingleNameRecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
class CNAMEResponse extends DefaultResponse {
|
||||||
|
final SingleNameRecord cname;
|
||||||
|
final RRType qtype;
|
||||||
|
|
||||||
|
public
|
||||||
|
CNAMEResponse(ResourceRecord cname, RRType qtype) {
|
||||||
|
super(RCode.NoError);
|
||||||
|
this.cname = SingleNameRecord.class.cast(cname);
|
||||||
|
this.qtype = qtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void postProcess(ResolveContext context) {
|
||||||
|
context.response()
|
||||||
|
.answer()
|
||||||
|
.add(this.cname);
|
||||||
|
Response r = context.resolve(this.cname.oneName(), this.qtype);
|
||||||
|
r.postProcess(context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.*;
|
||||||
|
import org.handwerkszeug.dns.record.SingleNameRecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
class DNAMEResponse extends DefaultResponse {
|
||||||
|
|
||||||
|
final SingleNameRecord dname;
|
||||||
|
final Name qname;
|
||||||
|
final RRType qtype;
|
||||||
|
|
||||||
|
public
|
||||||
|
DNAMEResponse(ResourceRecord dname, Name qname, RRType qtype) {
|
||||||
|
super(RCode.NoError);
|
||||||
|
notNull(dname, "dname");
|
||||||
|
notNull(qname, "qname");
|
||||||
|
notNull(qtype, "qtype");
|
||||||
|
this.dname = SingleNameRecord.class.cast(dname);
|
||||||
|
this.qname = qname;
|
||||||
|
this.qtype = qtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void postProcess(ResolveContext context) {
|
||||||
|
DNSMessage res = context.response();
|
||||||
|
res.answer()
|
||||||
|
.add(this.dname);
|
||||||
|
Name name = this.qname.replace(this.dname.name(), this.dname.oneName());
|
||||||
|
if (name == null) {
|
||||||
|
context.response()
|
||||||
|
.header()
|
||||||
|
.rcode(RCode.YXDomain);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SingleNameRecord cname = new SingleNameRecord(RRType.CNAME, name);
|
||||||
|
cname.name(this.qname);
|
||||||
|
res.answer()
|
||||||
|
.add(cname);
|
||||||
|
res.header()
|
||||||
|
.aa(true);
|
||||||
|
Response r = context.resolve(name, this.qtype);
|
||||||
|
r.postProcess(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
|
||||||
|
public
|
||||||
|
class DNSCacheEntry {
|
||||||
|
|
||||||
|
protected long expiration;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.util.Validation;
|
||||||
|
|
||||||
|
|
||||||
|
public
|
||||||
|
class DNSCacheKey {
|
||||||
|
Name name;
|
||||||
|
RRType t;
|
||||||
|
DNSClass c;
|
||||||
|
|
||||||
|
public
|
||||||
|
DNSCacheKey(Name name, RRType t, DNSClass c) {
|
||||||
|
super();
|
||||||
|
Validation.notNull(name, "name");
|
||||||
|
Validation.notNull(t, "t");
|
||||||
|
Validation.notNull(c, "c");
|
||||||
|
this.name = name;
|
||||||
|
this.t = t;
|
||||||
|
this.c = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.c == null) ? 0 : this.c.hashCode());
|
||||||
|
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
|
||||||
|
result = prime * result + ((this.t == null) ? 0 : this.t.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (other instanceof DNSCacheKey) {
|
||||||
|
return equals(DNSCacheKey.class.cast(other));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
boolean equals(DNSCacheKey other) {
|
||||||
|
boolean ne = this.name.equals(other.name);
|
||||||
|
|
||||||
|
boolean t = (this.t.equals(RRType.ANY) || other.t.equals(RRType.ANY) || this.t.equals(other.t));
|
||||||
|
|
||||||
|
boolean c = (this.c.equals(DNSClass.ANY) || other.c.equals(DNSClass.ANY) || this.c.equals(other.c));
|
||||||
|
|
||||||
|
return ne && t && c;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class DNSMessageCache {
|
||||||
|
|
||||||
|
public
|
||||||
|
DNSMessage lookup(Name name, RRType t, DNSClass c) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void store(DNSMessage message) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.records.DnsMessage;
|
||||||
|
import dorkbox.util.NamedThreadFactory;
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.PooledByteBufAllocator;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.oio.OioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||||
|
import io.netty.channel.socket.oio.OioDatagramChannel;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
@ChannelHandler.Sharable
|
||||||
|
public
|
||||||
|
class DNSMessageDecoder extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is what is called whenever a DNS packet is received. Currently only support UDP packets.
|
||||||
|
* <p>
|
||||||
|
* Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
|
||||||
|
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
|
||||||
|
* <p>
|
||||||
|
* Sub-classes may override this method to change behavior.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof io.netty.channel.socket.DatagramPacket) {
|
||||||
|
ByteBuf content = ((DatagramPacket) msg).content();
|
||||||
|
|
||||||
|
if (content.readableBytes() == 0) {
|
||||||
|
// we can't read this message, there's nothing there!
|
||||||
|
System.err.println("NO CONTENT ");
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsMessage msg1 = new DnsMessage(content);
|
||||||
|
|
||||||
|
// should get one from a pool!
|
||||||
|
|
||||||
|
Bootstrap dnsBootstrap = new Bootstrap();
|
||||||
|
|
||||||
|
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
|
||||||
|
SecurityManager s = System.getSecurityManager();
|
||||||
|
ThreadGroup nettyGroup = new ThreadGroup(s != null
|
||||||
|
? s.getThreadGroup()
|
||||||
|
: Thread.currentThread()
|
||||||
|
.getThreadGroup(), "DnsClient (Netty)");
|
||||||
|
|
||||||
|
EventLoopGroup group;
|
||||||
|
if (PlatformDependent.isAndroid()) {
|
||||||
|
group = new OioEventLoopGroup(0, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
|
||||||
|
dnsBootstrap.channel(OioDatagramChannel.class);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
group = new NioEventLoopGroup(2, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
|
||||||
|
dnsBootstrap.channel(NioDatagramChannel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsBootstrap.group(group);
|
||||||
|
dnsBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
|
||||||
|
// dnsBootstrap.handler(new DnsHandler());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// sending the question
|
||||||
|
final ChannelFuture future = dnsBootstrap.connect(new InetSocketAddress("8.8.8.8", 53));
|
||||||
|
try {
|
||||||
|
future.await();
|
||||||
|
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
// woo, connected!
|
||||||
|
System.err.println("CONNECTED");
|
||||||
|
// this.dnsServer = dnsServer;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.err.println("CANNOT CONNECT!");
|
||||||
|
// this.dnsServer = null;
|
||||||
|
// Logger logger2 = this.logger;
|
||||||
|
// if (logger2.isDebugEnabled()) {
|
||||||
|
// logger2.error("Could not connect to the DNS server.", this.future.cause());
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// logger2.error("Could not connect to the DNS server.");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
// Logger logger2 = this.logger;
|
||||||
|
// if (logger2.isDebugEnabled()) {
|
||||||
|
// logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort(), e.getCause());
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// ClientBootstrap cb = new ClientBootstrap(this.clientChannelFactory);
|
||||||
|
// cb.setOption("broadcast", "false");
|
||||||
|
//
|
||||||
|
// cb.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
// @Override
|
||||||
|
// public
|
||||||
|
// ChannelPipeline getPipeline() throws Exception {
|
||||||
|
// return Channels.pipeline(new ClientHanler(original, e.getChannel(), e.getRemoteAddress()));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// List<SocketAddress> newlist = new ArrayList<SocketAddress>(this.config.getForwarders());
|
||||||
|
// sendRequest(e, original, cb, newlist);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected
|
||||||
|
// void sendRequest(final MessageEvent e, final DNSMessage original, final ClientBootstrap bootstrap, final List<SocketAddress> forwarders) {
|
||||||
|
//
|
||||||
|
// if (0 < forwarders.size()) {
|
||||||
|
// SocketAddress sa = forwarders.remove(0);
|
||||||
|
// LOG.debug("send to {}", sa);
|
||||||
|
//
|
||||||
|
// ChannelFuture f = bootstrap.connect(sa);
|
||||||
|
// ChannelBuffer newone = ChannelBuffers.buffer(512);
|
||||||
|
// DNSMessage msg = new DNSMessage(original);
|
||||||
|
// msg.write(newone);
|
||||||
|
// newone.resetReaderIndex();
|
||||||
|
// final Channel c = f.getChannel();
|
||||||
|
//
|
||||||
|
// if (LOG.isDebugEnabled()) {
|
||||||
|
// LOG.debug("STATUS : [isOpen/isConnected/isWritable {}] {} {}",
|
||||||
|
// new Object[] {new boolean[] {c.isOpen(), c.isConnected(), c.isWritable()}, c.getRemoteAddress(), c.getClass()});
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// c.write(newone, sa).addListener(new ChannelFutureListener() {
|
||||||
|
// @Override
|
||||||
|
// public
|
||||||
|
// void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
// LOG.debug("request complete isSuccess : {}", future.isSuccess());
|
||||||
|
// if (future.isSuccess() == false) {
|
||||||
|
// if (0 < forwarders.size()) {
|
||||||
|
// sendRequest(e, original, bootstrap, forwarders);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// original.header().rcode(RCode.ServFail);
|
||||||
|
// ChannelBuffer buffer = ChannelBuffers.buffer(512);
|
||||||
|
// original.write(buffer);
|
||||||
|
// // close inbound channel
|
||||||
|
// e.getChannel().write(buffer).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// // f.awaitUninterruptibly(30, TimeUnit.SECONDS);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.*;
|
||||||
|
|
||||||
|
public
|
||||||
|
class DefaultResolveContext implements ResolveContext {
|
||||||
|
|
||||||
|
final DNSMessage request;
|
||||||
|
final DNSMessage response;
|
||||||
|
|
||||||
|
public
|
||||||
|
DefaultResolveContext(DNSMessage request) {
|
||||||
|
this(request, new DNSMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
DefaultResolveContext(DNSMessage request, DNSMessage response) {
|
||||||
|
notNull(request, "request");
|
||||||
|
notNull(response, "response");
|
||||||
|
this.request = request;
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
DNSMessage request() {
|
||||||
|
return this.request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
DNSMessage response() {
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Response resolve(Name qname, RRType qtype) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.Response;
|
||||||
|
|
||||||
|
public abstract
|
||||||
|
class DefaultResponse implements Response {
|
||||||
|
final RCode rcode;
|
||||||
|
|
||||||
|
protected
|
||||||
|
DefaultResponse(RCode rcode) {
|
||||||
|
this.rcode = rcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
RCode rcode() {
|
||||||
|
return this.rcode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.ResolveContext;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
class NoErrorResponse extends DefaultResponse {
|
||||||
|
final Set<ResourceRecord> records;
|
||||||
|
final boolean aa;
|
||||||
|
|
||||||
|
public
|
||||||
|
NoErrorResponse(Set<ResourceRecord> records) {
|
||||||
|
this(records, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
NoErrorResponse(Set<ResourceRecord> records, boolean aa) {
|
||||||
|
super(RCode.NoError);
|
||||||
|
this.records = records;
|
||||||
|
this.aa = aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void postProcess(ResolveContext context) {
|
||||||
|
DNSMessage res = context.response();
|
||||||
|
res.header()
|
||||||
|
.rcode(this.rcode());
|
||||||
|
res.header()
|
||||||
|
.aa(this.aa);
|
||||||
|
res.answer()
|
||||||
|
.addAll(this.records);
|
||||||
|
// TODO additional section ?
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.ResolveContext;
|
||||||
|
import org.handwerkszeug.dns.record.SOARecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
class NotFoundResponse extends DefaultResponse {
|
||||||
|
final SOARecord soaRecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
NotFoundResponse(RCode rcode, SOARecord soaRecord) {
|
||||||
|
super(rcode);
|
||||||
|
this.soaRecord = soaRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void postProcess(ResolveContext context) {
|
||||||
|
DNSMessage res = context.response();
|
||||||
|
res.header()
|
||||||
|
.rcode(this.rcode());
|
||||||
|
res.authority()
|
||||||
|
.add(this.soaRecord);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.handwerkszeug.dns.server;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSMessage;
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.ResolveContext;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
public
|
||||||
|
class ReferralResponse extends DefaultResponse {
|
||||||
|
final Set<ResourceRecord> nsRecords;
|
||||||
|
|
||||||
|
public
|
||||||
|
ReferralResponse(Set<ResourceRecord> records) {
|
||||||
|
super(RCode.NoError);
|
||||||
|
this.nsRecords = records;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void postProcess(ResolveContext context) {
|
||||||
|
DNSMessage res = context.response();
|
||||||
|
res.header()
|
||||||
|
.rcode(this.rcode());
|
||||||
|
res.authority()
|
||||||
|
.addAll(this.nsRecords);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.Zone;
|
||||||
|
import org.handwerkszeug.dns.ZoneType;
|
||||||
|
|
||||||
|
public abstract class AbstractZone implements Zone {
|
||||||
|
|
||||||
|
protected ZoneType type;
|
||||||
|
|
||||||
|
protected DNSClass dnsClass;
|
||||||
|
|
||||||
|
protected Name name;
|
||||||
|
|
||||||
|
public AbstractZone(ZoneType type, Name name) {
|
||||||
|
this(type, DNSClass.IN, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractZone(ZoneType type, DNSClass dnsClass, Name name) {
|
||||||
|
this.type = type;
|
||||||
|
this.dnsClass = dnsClass;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ZoneType type() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DNSClass dnsClass() {
|
||||||
|
return this.dnsClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Name name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.Response;
|
||||||
|
import org.handwerkszeug.dns.ZoneType;
|
||||||
|
|
||||||
|
public class ForwardZone extends AbstractZone {
|
||||||
|
|
||||||
|
protected List<InetAddress> forwarders = new ArrayList<InetAddress>();
|
||||||
|
|
||||||
|
public ForwardZone(Name name) {
|
||||||
|
super(ZoneType.forward, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForwardZone(DNSClass dnsclass, Name name) {
|
||||||
|
super(ZoneType.forward, dnsclass, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addForwardHost(InetAddress host) {
|
||||||
|
this.forwarders.add(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response find(Name qname, RRType type) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.RCode;
|
||||||
|
import org.handwerkszeug.dns.RRType;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.Response;
|
||||||
|
import org.handwerkszeug.dns.ZoneType;
|
||||||
|
import org.handwerkszeug.dns.record.SOARecord;
|
||||||
|
import org.handwerkszeug.dns.server.CNAMEResponse;
|
||||||
|
import org.handwerkszeug.dns.server.DNAMEResponse;
|
||||||
|
import org.handwerkszeug.dns.server.NoErrorResponse;
|
||||||
|
import org.handwerkszeug.dns.server.NotFoundResponse;
|
||||||
|
import org.handwerkszeug.dns.server.ReferralResponse;
|
||||||
|
|
||||||
|
public class MasterZone extends AbstractZone {
|
||||||
|
|
||||||
|
final ConcurrentMap<Name, ConcurrentMap<RRType, NavigableSet<ResourceRecord>>> records = new ConcurrentSkipListMap<Name, ConcurrentMap<RRType, NavigableSet<ResourceRecord>>>();
|
||||||
|
final Response nxDomain;
|
||||||
|
final Response nxRRSet;
|
||||||
|
|
||||||
|
public MasterZone(Name name, SOARecord soaRecord) {
|
||||||
|
super(ZoneType.master, name);
|
||||||
|
this.nxDomain = new NotFoundResponse(RCode.NXDomain, soaRecord);
|
||||||
|
this.nxRRSet = new NotFoundResponse(RCode.NXRRSet, soaRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response find(Name qname, RRType qtype) {
|
||||||
|
notNull(qname, "qname");
|
||||||
|
notNull(qtype, "qtype");
|
||||||
|
|
||||||
|
if (qname.contains(this.name()) == false) {
|
||||||
|
return this.nxDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> exactMatch = this.records
|
||||||
|
.get(qname);
|
||||||
|
if (exactMatch != null) {
|
||||||
|
NavigableSet<ResourceRecord> rrs = exactMatch.get(qtype);
|
||||||
|
if (rrs != null) {
|
||||||
|
synchronized (rrs) {
|
||||||
|
if (rrs.isEmpty() == false) {
|
||||||
|
return new NoErrorResponse(rrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (RRType.ANY.equals(qtype)) {
|
||||||
|
Set<ResourceRecord> newset = new HashSet<ResourceRecord>();
|
||||||
|
for (RRType type : exactMatch.keySet()) {
|
||||||
|
Set<ResourceRecord> s = exactMatch.get(type);
|
||||||
|
if (s != null) {
|
||||||
|
synchronized (s) {
|
||||||
|
newset.addAll(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newset.isEmpty() == false) {
|
||||||
|
return new NoErrorResponse(newset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (RRType.CNAME.equals(qtype) == false) {
|
||||||
|
rrs = exactMatch.get(RRType.CNAME);
|
||||||
|
if (rrs != null) {
|
||||||
|
synchronized (rrs) {
|
||||||
|
if (rrs.isEmpty() == false) {
|
||||||
|
return new CNAMEResponse(rrs.first(), qtype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.nxRRSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Name qn = qname.toParent(); this.name().equals(qn) == false; qn = qn
|
||||||
|
.toParent()) {
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> match = this.records
|
||||||
|
.get(qn);
|
||||||
|
if (match != null) {
|
||||||
|
synchronized (match) {
|
||||||
|
if (match.isEmpty() == false) {
|
||||||
|
NavigableSet<ResourceRecord> set = match.get(RRType.NS);
|
||||||
|
if ((set != null) && (set.isEmpty() == false)) {
|
||||||
|
return new ReferralResponse(set);
|
||||||
|
}
|
||||||
|
set = match.get(RRType.DNAME);
|
||||||
|
if ((set != null) && (set.isEmpty() == false)) {
|
||||||
|
return new DNAMEResponse(set.first(), qname, qtype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Name qn = qname; this.name().equals(qn) == false; qn = qn
|
||||||
|
.toParent()) {
|
||||||
|
Name wild = qn.toWildcard();
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> match = this.records
|
||||||
|
.get(wild);
|
||||||
|
if (match != null) {
|
||||||
|
synchronized (match) {
|
||||||
|
if (match.isEmpty() == false) {
|
||||||
|
Set<ResourceRecord> matchSet = match.get(qtype);
|
||||||
|
if (matchSet.isEmpty() == false) {
|
||||||
|
Set<ResourceRecord> set = new HashSet<ResourceRecord>(
|
||||||
|
matchSet.size());
|
||||||
|
for (ResourceRecord rr : matchSet) {
|
||||||
|
set.add(rr.toQnameRecord(qname));
|
||||||
|
}
|
||||||
|
return new NoErrorResponse(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.nxDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add and remove needs queuing?
|
||||||
|
// if modify operations works on single thread, not conflict.
|
||||||
|
public void add(ResourceRecord rr) {
|
||||||
|
notNull(rr, "rr");
|
||||||
|
for (;;) {
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> current = this.records
|
||||||
|
.get(rr.name());
|
||||||
|
if (current == null) {
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> newone = new ConcurrentSkipListMap<RRType, NavigableSet<ResourceRecord>>();
|
||||||
|
NavigableSet<ResourceRecord> newset = new ConcurrentSkipListSet<ResourceRecord>();
|
||||||
|
newset.add(rr);
|
||||||
|
newone.put(rr.type(), newset);
|
||||||
|
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> prevTypes = this.records
|
||||||
|
.putIfAbsent(rr.name(), newone);
|
||||||
|
if (prevTypes == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
synchronized (prevTypes) {
|
||||||
|
Set<ResourceRecord> prevRecs = prevTypes.putIfAbsent(
|
||||||
|
rr.type(), newset);
|
||||||
|
if (prevRecs == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevRecs.add(rr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
synchronized (current) {
|
||||||
|
Set<ResourceRecord> rrs = current.get(rr.type());
|
||||||
|
if (rrs == null) {
|
||||||
|
NavigableSet<ResourceRecord> newset = new ConcurrentSkipListSet<ResourceRecord>();
|
||||||
|
newset.add(rr);
|
||||||
|
current.put(rr.type(), newset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rrs.isEmpty() == false) {
|
||||||
|
rrs.add(rr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(ResourceRecord rr, boolean checkSets, boolean checkMap) {
|
||||||
|
notNull(rr, "rr");
|
||||||
|
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> current = this.records
|
||||||
|
.get(rr.name());
|
||||||
|
if (current != null) {
|
||||||
|
synchronized (current) {
|
||||||
|
NavigableSet<ResourceRecord> sets = current.get(rr.type());
|
||||||
|
sets.remove(rr);
|
||||||
|
if (checkSets && sets.isEmpty()) {
|
||||||
|
current.remove(rr.type());
|
||||||
|
if (checkMap && current.isEmpty()) {
|
||||||
|
this.records.remove(rr.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.Zone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class Query {
|
||||||
|
|
||||||
|
protected Name origin;
|
||||||
|
|
||||||
|
protected Name current;
|
||||||
|
|
||||||
|
protected DNSClass dnsClass;
|
||||||
|
|
||||||
|
protected Zone target;
|
||||||
|
|
||||||
|
protected ZoneDatabase database;
|
||||||
|
|
||||||
|
public Query(Name origin, Name current, DNSClass dnsClass, Zone target,
|
||||||
|
ZoneDatabase database) {
|
||||||
|
super();
|
||||||
|
notNull(origin, "origin");
|
||||||
|
notNull(current, "current");
|
||||||
|
notNull(dnsClass, "dnsClass");
|
||||||
|
notNull(target, "target");
|
||||||
|
notNull(database, "database");
|
||||||
|
|
||||||
|
this.origin = origin;
|
||||||
|
this.current = current;
|
||||||
|
this.dnsClass = dnsClass;
|
||||||
|
this.target = target;
|
||||||
|
this.database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public SearchResult execute(/* ResolveContext? */) {
|
||||||
|
// List<ResourceRecord> rrs = this.target.resolve(this.current,
|
||||||
|
// this.dnsClass);
|
||||||
|
// SearchResult result = new SearchResult(rrs);
|
||||||
|
// if (rrs.isEmpty()) {
|
||||||
|
// result.status = Status.NXDOMAIN;
|
||||||
|
// return result;
|
||||||
|
// } else if (contains(rrs)) {
|
||||||
|
// result.status = Status.SUCCESS;
|
||||||
|
// return result;
|
||||||
|
// } else {
|
||||||
|
// Query q = this.database.prepare(this.origin, this.dnsClass);
|
||||||
|
// SearchResult sr = q.execute();
|
||||||
|
// result.rrs.addAll(sr.rrs);
|
||||||
|
// result.status = sr.status;
|
||||||
|
// return sr;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
protected boolean contains(List<ResourceRecord> rrs) {
|
||||||
|
for (ResourceRecord rr : rrs) {
|
||||||
|
if (this.origin.equals(rr.name())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
|
||||||
|
public class SearchResult {
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
NXDOMAIN, SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ResourceRecord> rrs;
|
||||||
|
|
||||||
|
Status status = Status.NXDOMAIN;
|
||||||
|
|
||||||
|
public SearchResult(List<ResourceRecord> rrs) {
|
||||||
|
super();
|
||||||
|
this.rrs = rrs;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package org.handwerkszeug.dns.zone;
|
||||||
|
|
||||||
|
import static org.handwerkszeug.util.Validation.notNull;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.DNSClass;
|
||||||
|
import org.handwerkszeug.dns.Name;
|
||||||
|
import org.handwerkszeug.dns.ResourceRecord;
|
||||||
|
import org.handwerkszeug.dns.Zone;
|
||||||
|
|
||||||
|
public class ZoneDatabase {
|
||||||
|
|
||||||
|
protected Map<ZoneDatabaseKey, Zone> zones = new ConcurrentSkipListMap<ZoneDatabaseKey, Zone>();
|
||||||
|
|
||||||
|
public Query prepare(Name name, DNSClass dnsClass) {
|
||||||
|
notNull(name, "name");
|
||||||
|
notNull(dnsClass, "dnsClass");
|
||||||
|
ZoneDatabaseKey zk = new ZoneDatabaseKey(name, dnsClass);
|
||||||
|
Zone found = this.zones.get(zk);
|
||||||
|
if (found != null) {
|
||||||
|
// exact match
|
||||||
|
return new Query(name, name, dnsClass, found, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Name child = name;
|
||||||
|
// partial match
|
||||||
|
for (int i = 0, size = this.zones.size(); i < size; i++) {
|
||||||
|
Name p = child.toParent();
|
||||||
|
zk.name(p);
|
||||||
|
found = this.zones.get(zk);
|
||||||
|
if (found == null) {
|
||||||
|
child = p;
|
||||||
|
} else {
|
||||||
|
return new Query(name, p, dnsClass, found, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not found.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Zone zone/* TODO ZoneConfig? */) {
|
||||||
|
notNull(zone, "zone");
|
||||||
|
this.zones.put(new ZoneDatabaseKey(zone), zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ZoneDatabaseKey implements Comparable<ZoneDatabaseKey> {
|
||||||
|
Name name;
|
||||||
|
DNSClass dnsclass;
|
||||||
|
|
||||||
|
public ZoneDatabaseKey(Zone z) {
|
||||||
|
this(z.name(), z.dnsClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoneDatabaseKey(ResourceRecord rr) {
|
||||||
|
this(rr.name(), rr.dnsClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoneDatabaseKey(Name name, DNSClass dnsclass) {
|
||||||
|
notNull(name, "name");
|
||||||
|
notNull(dnsclass, "dnsclass");
|
||||||
|
this.name = name;
|
||||||
|
this.dnsclass = dnsclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name name() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void name(Name name) {
|
||||||
|
notNull(name, "name");
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + this.dnsclass.hashCode();
|
||||||
|
result = prime * result + this.name.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (other == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != other.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return equals((ZoneDatabaseKey) other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(ZoneDatabaseKey other) {
|
||||||
|
return (this.dnsclass == other.dnsclass)
|
||||||
|
&& this.name.equals(other.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ZoneDatabaseKey o) {
|
||||||
|
if (o == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (equals(o)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this.hashCode() - o.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="http://tools.ietf.org/html/rfc4291">[RFC4291] IP Version 6
|
||||||
|
* Addressing Architecture</a>
|
||||||
|
* @see <a href="http://tools.ietf.org/html/rfc5952">[RFC5952] A Recommendation
|
||||||
|
* for IPv6 Address Text Representation</a>
|
||||||
|
* @see http://www.intermapper.com/ipv6validator
|
||||||
|
* @see http://download.dartware.com/thirdparty/test-ipv6-regex.pl
|
||||||
|
* @author taichi
|
||||||
|
*/
|
||||||
|
public class AddressUtil {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(AddressUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="http://tools.ietf.org/html/rfc952">[RFC952] DOD INTERNET
|
||||||
|
* HOST TABLE SPECIFICATION</a>
|
||||||
|
*/
|
||||||
|
public static final Pattern hostname = Pattern
|
||||||
|
.compile("([a-zA-Z][\\w-]*[\\w]*(\\.[a-zA-Z][\\w-]*[\\w]*)*)");
|
||||||
|
|
||||||
|
public static final Pattern hostWithPort = withPortNumber(hostname);
|
||||||
|
|
||||||
|
public static final String under65536 = "(6553[0-5]|6(55[012]|(5[0-4]|[0-4]\\d)\\d)\\d|[1-5]?\\d{1,4})";
|
||||||
|
public static final Pattern v4Address = Pattern
|
||||||
|
.compile("((25[0-5]|(2[0-4]|1\\d|[1-9]?)\\d)(\\.|\\b)){4}(?<!\\.)");
|
||||||
|
public static final Pattern v4withPort = withPortNumber(v4Address);
|
||||||
|
|
||||||
|
protected static Pattern withPortNumber(Pattern p) {
|
||||||
|
return Pattern.compile("(" + p.pattern() + ")(:" + under65536 + ")?");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final String internal_v6address = "((((?=(?>.*?::)(?!.*::)))(::)?([0-9a-f]{1,4}::?){0,5}|([0-9a-f]{1,4}:){6})(((25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])(\\.|\\b)){4}|\\3([0-9a-f]{1,4}(::?|\\b)){0,2}|[0-9a-f]{1,4}:[0-9a-f]{1,4})(?<![^:]:)(?<!\\.))";
|
||||||
|
public static final Pattern v6Address = Pattern.compile(internal_v6address
|
||||||
|
+ "$");
|
||||||
|
public static final Pattern v6withSuffixPort = Pattern
|
||||||
|
.compile(internal_v6address + "(?:(#|\\.)" + under65536 + ")?$");
|
||||||
|
|
||||||
|
public static final Pattern v6withBracketPort = Pattern.compile("\\["
|
||||||
|
+ internal_v6address + "\\](:" + under65536 + ")?$"); // 1 15
|
||||||
|
|
||||||
|
protected static final List<SocketAddressConverter> CONVERTERS = new ArrayList<SocketAddressConverter>();
|
||||||
|
static {
|
||||||
|
CONVERTERS.add(new FromV4Address());
|
||||||
|
CONVERTERS.add(new FromBracketV6Address());
|
||||||
|
CONVERTERS.add(new FromV6Address());
|
||||||
|
CONVERTERS.add(new FromHostname());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InetSocketAddress convertTo(String addressWithPort,
|
||||||
|
int defaultPort) {
|
||||||
|
InetSocketAddress result = null;
|
||||||
|
for (SocketAddressConverter sac : CONVERTERS) {
|
||||||
|
result = sac.to(addressWithPort, defaultPort);
|
||||||
|
if (result != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SocketAddressConverter {
|
||||||
|
InetSocketAddress to(String addr, int defaultPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FromHostname implements SocketAddressConverter {
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress to(String addr, int defaultPort) {
|
||||||
|
return toSocketAddress(addr, defaultPort, hostWithPort, 1, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FromV4Address implements SocketAddressConverter {
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress to(String addr, int defaultPort) {
|
||||||
|
return toSocketAddress(addr, defaultPort, v4withPort, 1, 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FromBracketV6Address implements SocketAddressConverter {
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress to(String addr, int defaultPort) {
|
||||||
|
return toSocketAddress(addr, defaultPort, v6withBracketPort, 1, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FromV6Address implements SocketAddressConverter {
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress to(String addr, int defaultPort) {
|
||||||
|
return toSocketAddress(addr, defaultPort, v6withSuffixPort, 1, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static InetSocketAddress toSocketAddress(String addressWithPort,
|
||||||
|
int defaultPort, Pattern p, int HOST_INDEX, int PORT_INDEX) {
|
||||||
|
Matcher m = p.matcher(addressWithPort);
|
||||||
|
if (m.matches() && m.reset().find()) {
|
||||||
|
int port = toInt(m.group(PORT_INDEX), defaultPort);
|
||||||
|
return new InetSocketAddress(m.group(HOST_INDEX), port);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toInt(String s, int defaultValue) {
|
||||||
|
int result = defaultValue;
|
||||||
|
try {
|
||||||
|
if ((s != null) && (s.isEmpty() == false)) {
|
||||||
|
result = Integer.parseInt(s);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InetAddress getByAddress(long v4address) {
|
||||||
|
byte[] a = new byte[4];
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
a[i] = (byte) ((v4address >>> ((3 - i) * 8)) & 0xFF);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(a);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
LOG.error(e.getLocalizedMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long toLong(InetAddress v4address) {
|
||||||
|
byte[] a = v4address.getAddress();
|
||||||
|
long result = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
result |= (long) ((a[i] & 0xFF)) << ((3 - i) * 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InetAddress getByName(String host) {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(host);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
LOG.error(host, e);
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
public class ClassUtil {
|
||||||
|
|
||||||
|
public static String toPackagePath(Class<?> clazz) {
|
||||||
|
String result = clazz.getName();
|
||||||
|
int index = result.lastIndexOf('.');
|
||||||
|
if (index < 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
result = result.substring(0, index);
|
||||||
|
result = result.replace('.', '/');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
import werkzeugkasten.common.util.ArrayUtil;
|
||||||
|
|
||||||
|
public class CompareUtil {
|
||||||
|
|
||||||
|
public static int compare(long left, long right) {
|
||||||
|
if (left < right) {
|
||||||
|
return -1;
|
||||||
|
} else if (left > right) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int compare(byte[] lefts, byte[] rights) {
|
||||||
|
return ArrayUtil.compare(lefts, rights);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
public class EnumUtil {
|
||||||
|
|
||||||
|
public static <E extends Enum<E> & VariableEnum> E find(E[] values,
|
||||||
|
int value) {
|
||||||
|
E result = find(values, value, null);
|
||||||
|
if (result == null) {
|
||||||
|
throw new IllegalArgumentException("value=" + value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E> & VariableEnum> E find(E[] values,
|
||||||
|
int value, E defaultValue) {
|
||||||
|
for (E e : values) {
|
||||||
|
if (e.value() == value) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> E find(E[] values, String value,
|
||||||
|
E defaultValue) {
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
String key = value.toUpperCase();
|
||||||
|
for (E e : values) {
|
||||||
|
if (e.name().equals(key)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Validation {
|
||||||
|
|
||||||
|
static final Pattern isPositiveNumber = Pattern.compile("\\d+");
|
||||||
|
|
||||||
|
public static void notNull(Object o, String name) {
|
||||||
|
if (o == null) {
|
||||||
|
throw new IllegalArgumentException(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPositiveNumber(String s) {
|
||||||
|
return s != null && isPositiveNumber.matcher(s).matches();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.handwerkszeug.util;
|
||||||
|
|
||||||
|
public interface VariableEnum {
|
||||||
|
|
||||||
|
int value();
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
public abstract class DefaultHandler<CTX> implements YamlNodeHandler<CTX> {
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
public DefaultHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultHandler(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNodeName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||||
|
|
||||||
|
public class MappingHandler<CTX> extends DefaultHandler<CTX> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(MappingHandler.class);
|
||||||
|
|
||||||
|
Map<String, YamlNodeHandler<CTX>> handlers = new HashMap<String, YamlNodeHandler<CTX>>();
|
||||||
|
|
||||||
|
public MappingHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingHandler(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(YamlNodeHandler<CTX> handler) {
|
||||||
|
if (handler != null) {
|
||||||
|
this.handlers.put(handler.getNodeName().toLowerCase(), handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, CTX context) {
|
||||||
|
if (node instanceof MappingNode) {
|
||||||
|
MappingNode mn = (MappingNode) node;
|
||||||
|
for (NodeTuple nt : mn.getValue()) {
|
||||||
|
String key = YamlUtil.getStringValue(nt.getKeyNode());
|
||||||
|
YamlNodeHandler<CTX> h = this.handlers.get(key.toLowerCase());
|
||||||
|
if (h != null) {
|
||||||
|
Node value = nt.getValueNode();
|
||||||
|
h.handle(value, context);
|
||||||
|
} else {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.UnsupportedAttribute,
|
||||||
|
key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.InvalidParameter, new Object[] {
|
||||||
|
"MappingHandler#handle", MappingNode.class, node });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||||
|
|
||||||
|
public class SequenceHandler<CTX> extends DefaultHandler<CTX> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(SequenceHandler.class);
|
||||||
|
|
||||||
|
protected YamlNodeHandler<CTX> handler;
|
||||||
|
|
||||||
|
public SequenceHandler(YamlNodeHandler<CTX> handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SequenceHandler(String name, YamlNodeHandler<CTX> handler) {
|
||||||
|
super(name);
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Node node, CTX context) {
|
||||||
|
if (node instanceof SequenceNode) {
|
||||||
|
SequenceNode sn = (SequenceNode) node;
|
||||||
|
for (Node n : sn.getValue()) {
|
||||||
|
this.handler.handle(n, context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug(Markers.DETAIL, Messages.InvalidParameter, new Object[] {
|
||||||
|
"SequenceHandler#handle", SequenceNode.class, node });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import org.handwerkszeug.dns.Markers;
|
||||||
|
import org.handwerkszeug.dns.nls.Messages;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.yaml.snakeyaml.composer.Composer;
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.parser.ParserImpl;
|
||||||
|
import org.yaml.snakeyaml.reader.StreamReader;
|
||||||
|
import org.yaml.snakeyaml.resolver.Resolver;
|
||||||
|
|
||||||
|
public class YamlNodeAccepter<CTX> {
|
||||||
|
|
||||||
|
static final Logger LOG = LoggerFactory.getLogger(YamlNodeAccepter.class);
|
||||||
|
|
||||||
|
protected final YamlNodeHandler<CTX> rootHandler;
|
||||||
|
|
||||||
|
public YamlNodeAccepter(YamlNodeHandler<CTX> root) {
|
||||||
|
this.rootHandler = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept(InputStream in, CTX context) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.trace(Markers.BOUNDARY, Messages.ComposeNode);
|
||||||
|
}
|
||||||
|
Composer composer = new Composer(new ParserImpl(new StreamReader(
|
||||||
|
new InputStreamReader(in))), new Resolver());
|
||||||
|
Node node = composer.getSingleNode();
|
||||||
|
this.rootHandler.handle(node, context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
|
||||||
|
public interface YamlNodeHandler<CTX> {
|
||||||
|
|
||||||
|
String getNodeName();
|
||||||
|
|
||||||
|
void handle(Node node, CTX context);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.handwerkszeug.yaml;
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.nodes.Node;
|
||||||
|
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||||
|
|
||||||
|
public class YamlUtil {
|
||||||
|
|
||||||
|
public static String getStringValue(Node node) {
|
||||||
|
if (node instanceof ScalarNode) {
|
||||||
|
ScalarNode sn = (ScalarNode) node;
|
||||||
|
return sn.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.channels.SelectableChannel;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.utils.Options;
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
|
||||||
|
protected long endTime;
|
||||||
|
protected SelectionKey key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet logger, if available.
|
||||||
|
*/
|
||||||
|
private static PacketLogger packetLogger = null;
|
||||||
|
|
||||||
|
protected
|
||||||
|
Client(SelectableChannel channel, long endTime) throws IOException {
|
||||||
|
boolean done = false;
|
||||||
|
Selector selector = null;
|
||||||
|
this.endTime = endTime;
|
||||||
|
try {
|
||||||
|
selector = Selector.open();
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
key = channel.register(selector, SelectionKey.OP_READ);
|
||||||
|
done = true;
|
||||||
|
} finally {
|
||||||
|
if (!done && selector != null) {
|
||||||
|
selector.close();
|
||||||
|
}
|
||||||
|
if (!done) {
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected
|
||||||
|
void blockUntil(SelectionKey key, long endTime) throws IOException {
|
||||||
|
long timeout = endTime - System.currentTimeMillis();
|
||||||
|
int nkeys = 0;
|
||||||
|
if (timeout > 0) {
|
||||||
|
nkeys = key.selector()
|
||||||
|
.select(timeout);
|
||||||
|
}
|
||||||
|
else if (timeout == 0) {
|
||||||
|
nkeys = key.selector()
|
||||||
|
.selectNow();
|
||||||
|
}
|
||||||
|
if (nkeys == 0) {
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected
|
||||||
|
void verboseLog(String prefix, SocketAddress local, SocketAddress remote, byte[] data) {
|
||||||
|
if (Options.check("verbosemsg")) {
|
||||||
|
System.err.println(hexdump.dump(prefix, data));
|
||||||
|
}
|
||||||
|
if (packetLogger != null) {
|
||||||
|
packetLogger.log(prefix, local, remote, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup() throws IOException {
|
||||||
|
key.selector()
|
||||||
|
.close();
|
||||||
|
key.channel()
|
||||||
|
.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void setPacketLogger(PacketLogger logger) {
|
||||||
|
packetLogger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.exceptions.InvalidTypeException;
|
||||||
|
import dorkbox.network.dns.exceptions.TextParseException;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.utils.Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a $GENERATE statement in a master file.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class Generator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start of the range.
|
||||||
|
*/
|
||||||
|
public long start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end of the range.
|
||||||
|
*/
|
||||||
|
public long end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The step value of the range.
|
||||||
|
*/
|
||||||
|
public long step;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pattern to use for generating record names.
|
||||||
|
*/
|
||||||
|
public final String namePattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the generated records.
|
||||||
|
*/
|
||||||
|
public final int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class of the generated records.
|
||||||
|
*/
|
||||||
|
public final int dclass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ttl of the generated records.
|
||||||
|
*/
|
||||||
|
public final long ttl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pattern to use for generating record data.
|
||||||
|
*/
|
||||||
|
public final String rdataPattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The origin to append to relative names.
|
||||||
|
*/
|
||||||
|
public final Name origin;
|
||||||
|
|
||||||
|
private long current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a specification for generating records, as a $GENERATE
|
||||||
|
* statement in a master file.
|
||||||
|
*
|
||||||
|
* @param start The start of the range.
|
||||||
|
* @param end The end of the range.
|
||||||
|
* @param step The step value of the range.
|
||||||
|
* @param namePattern The pattern to use for generating record names.
|
||||||
|
* @param type The type of the generated records. The supported types are
|
||||||
|
* PTR, CNAME, DNAME, A, AAAA, and NS.
|
||||||
|
* @param dclass The class of the generated records.
|
||||||
|
* @param ttl The ttl of the generated records.
|
||||||
|
* @param rdataPattern The pattern to use for generating record data.
|
||||||
|
* @param origin The origin to append to relative names.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The range is invalid.
|
||||||
|
* @throws IllegalArgumentException The type does not support generation.
|
||||||
|
* @throws IllegalArgumentException The dclass is not a valid class.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Generator(long start, long end, long step, String namePattern, int type, int dclass, long ttl, String rdataPattern, Name origin) {
|
||||||
|
if (start < 0 || end < 0 || start > end || step <= 0) {
|
||||||
|
throw new IllegalArgumentException("invalid range specification");
|
||||||
|
}
|
||||||
|
if (!supportedType(type)) {
|
||||||
|
throw new IllegalArgumentException("unsupported type");
|
||||||
|
}
|
||||||
|
DnsClass.check(dclass);
|
||||||
|
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.step = step;
|
||||||
|
this.namePattern = namePattern;
|
||||||
|
this.type = type;
|
||||||
|
this.dclass = dclass;
|
||||||
|
this.ttl = ttl;
|
||||||
|
this.rdataPattern = rdataPattern;
|
||||||
|
this.origin = origin;
|
||||||
|
this.current = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether generation is supported for this type.
|
||||||
|
*
|
||||||
|
* @throws InvalidTypeException The type is out of range.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean supportedType(int type) {
|
||||||
|
DnsRecordType.check(type);
|
||||||
|
return (type == DnsRecordType.PTR || type == DnsRecordType.CNAME || type == DnsRecordType.DNAME || type == DnsRecordType.A || type == DnsRecordType.AAAA || type == DnsRecordType.NS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs and returns the next record in the expansion.
|
||||||
|
*
|
||||||
|
* @throws IOException The name or rdata was invalid after substitutions were
|
||||||
|
* performed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord nextRecord() throws IOException {
|
||||||
|
if (current > end) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String namestr = substitute(namePattern, current);
|
||||||
|
Name name = Name.Companion.fromString(namestr, origin);
|
||||||
|
String rdata = substitute(rdataPattern, current);
|
||||||
|
current += step;
|
||||||
|
return DnsRecord.fromString(name, type, dclass, ttl, rdata, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
String substitute(String spec, long n) throws IOException {
|
||||||
|
boolean escaped = false;
|
||||||
|
byte[] str = spec.getBytes();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < str.length; i++) {
|
||||||
|
char c = (char) (str[i] & 0xFF);
|
||||||
|
if (escaped) {
|
||||||
|
sb.append(c);
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
else if (c == '\\') {
|
||||||
|
if (i + 1 == str.length) {
|
||||||
|
throw new TextParseException("invalid escape character");
|
||||||
|
}
|
||||||
|
escaped = true;
|
||||||
|
}
|
||||||
|
else if (c == '$') {
|
||||||
|
boolean negative = false;
|
||||||
|
long offset = 0;
|
||||||
|
long width = 0;
|
||||||
|
long base = 10;
|
||||||
|
boolean wantUpperCase = false;
|
||||||
|
if (i + 1 < str.length && str[i + 1] == '$') {
|
||||||
|
// '$$' == literal '$' for backwards
|
||||||
|
// compatibility with old versions of BIND.
|
||||||
|
c = (char) (str[++i] & 0xFF);
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (i + 1 < str.length && str[i + 1] == '{') {
|
||||||
|
// It's a substitution with modifiers.
|
||||||
|
i++;
|
||||||
|
if (i + 1 < str.length && str[i + 1] == '-') {
|
||||||
|
negative = true;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
while (i + 1 < str.length) {
|
||||||
|
c = (char) (str[++i] & 0xFF);
|
||||||
|
if (c == ',' || c == '}') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
throw new TextParseException("invalid offset");
|
||||||
|
}
|
||||||
|
c -= '0';
|
||||||
|
offset *= 10;
|
||||||
|
offset += c;
|
||||||
|
}
|
||||||
|
if (negative) {
|
||||||
|
offset = -offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ',') {
|
||||||
|
while (i + 1 < str.length) {
|
||||||
|
c = (char) (str[++i] & 0xFF);
|
||||||
|
if (c == ',' || c == '}') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
throw new TextParseException("invalid width");
|
||||||
|
}
|
||||||
|
c -= '0';
|
||||||
|
width *= 10;
|
||||||
|
width += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ',') {
|
||||||
|
if (i + 1 == str.length) {
|
||||||
|
throw new TextParseException("invalid base");
|
||||||
|
}
|
||||||
|
c = (char) (str[++i] & 0xFF);
|
||||||
|
if (c == 'o') {
|
||||||
|
base = 8;
|
||||||
|
}
|
||||||
|
else if (c == 'x') {
|
||||||
|
base = 16;
|
||||||
|
}
|
||||||
|
else if (c == 'X') {
|
||||||
|
base = 16;
|
||||||
|
wantUpperCase = true;
|
||||||
|
}
|
||||||
|
else if (c != 'd') {
|
||||||
|
throw new TextParseException("invalid base");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 == str.length || str[i + 1] != '}') {
|
||||||
|
throw new TextParseException("invalid modifiers");
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
long v = n + offset;
|
||||||
|
if (v < 0) {
|
||||||
|
throw new TextParseException("invalid offset expansion");
|
||||||
|
}
|
||||||
|
String number;
|
||||||
|
if (base == 8) {
|
||||||
|
number = Long.toOctalString(v);
|
||||||
|
}
|
||||||
|
else if (base == 16) {
|
||||||
|
number = Long.toHexString(v);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
number = Long.toString(v);
|
||||||
|
}
|
||||||
|
if (wantUpperCase) {
|
||||||
|
number = number.toUpperCase();
|
||||||
|
}
|
||||||
|
if (width != 0 && width > number.length()) {
|
||||||
|
int zeros = (int) width - number.length();
|
||||||
|
while (zeros-- > 0) {
|
||||||
|
sb.append('0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(number);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs and returns all records in the expansion.
|
||||||
|
*
|
||||||
|
* @throws IOException The name or rdata of a record was invalid after
|
||||||
|
* substitutions were performed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord[] expand() throws IOException {
|
||||||
|
List list = new ArrayList();
|
||||||
|
for (long i = start; i < end; i += step) {
|
||||||
|
String namestr = substitute(namePattern, current);
|
||||||
|
Name name = Name.Companion.fromString(namestr, origin);
|
||||||
|
String rdata = substitute(rdataPattern, current);
|
||||||
|
list.add(DnsRecord.fromString(name, type, dclass, ttl, rdata, origin));
|
||||||
|
}
|
||||||
|
return (DnsRecord[]) list.toArray(new DnsRecord[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the generate specification to a string containing the corresponding
|
||||||
|
* $GENERATE statement.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("$GENERATE ");
|
||||||
|
sb.append(start + "-" + end);
|
||||||
|
if (step > 1) {
|
||||||
|
sb.append("/" + step);
|
||||||
|
}
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(namePattern + " ");
|
||||||
|
sb.append(ttl + " ");
|
||||||
|
if (dclass != DnsClass.IN || !Options.check("noPrintIN")) {
|
||||||
|
sb.append(DnsClass.string(dclass) + " ");
|
||||||
|
}
|
||||||
|
sb.append(DnsRecordType.string(type) + " ");
|
||||||
|
sb.append(rdataPattern + " ");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,766 @@
|
||||||
|
// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.xbill.DNS2.resolver.Cache;
|
||||||
|
import org.xbill.DNS2.resolver.Credibility;
|
||||||
|
import org.xbill.DNS2.resolver.ExtendedResolver;
|
||||||
|
import org.xbill.DNS2.resolver.Resolver;
|
||||||
|
import org.xbill.DNS2.resolver.ResolverConfig;
|
||||||
|
import org.xbill.DNS2.resolver.SetResponse;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Mnemonic;
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.constants.DnsResponseCode;
|
||||||
|
import dorkbox.network.dns.exceptions.NameTooLongException;
|
||||||
|
import dorkbox.network.dns.exceptions.TextParseException;
|
||||||
|
import dorkbox.network.dns.records.CNAMERecord;
|
||||||
|
import dorkbox.network.dns.records.DNAMERecord;
|
||||||
|
import dorkbox.network.dns.records.DnsMessage;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.records.RRset;
|
||||||
|
import dorkbox.network.dns.utils.Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Lookup object issues queries to caching DNS servers. The input consists
|
||||||
|
* of a name, an optional type, and an optional class. Caching is enabled
|
||||||
|
* by default and used when possible to reduce the number of DNS requests.
|
||||||
|
* A Resolver, which defaults to an ExtendedResolver initialized with the
|
||||||
|
* resolvers located by the ResolverConfig class, performs the queries. A
|
||||||
|
* search path of domain suffixes is used to resolve relative names, and is
|
||||||
|
* also determined by the ResolverConfig class.
|
||||||
|
* <p>
|
||||||
|
* A Lookup object may be reused, but should not be used by multiple threads.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
* @see Cache
|
||||||
|
* @see Resolver
|
||||||
|
* @see ResolverConfig
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final
|
||||||
|
class Lookup {
|
||||||
|
|
||||||
|
private static Resolver defaultResolver;
|
||||||
|
private static Name[] defaultSearchPath;
|
||||||
|
private static Map defaultCaches;
|
||||||
|
private static int defaultNdots;
|
||||||
|
|
||||||
|
private Resolver resolver;
|
||||||
|
private Name[] searchPath;
|
||||||
|
private Cache cache;
|
||||||
|
private boolean temporary_cache;
|
||||||
|
private int credibility;
|
||||||
|
private Name name;
|
||||||
|
private int type;
|
||||||
|
private int dclass;
|
||||||
|
private boolean verbose;
|
||||||
|
private int iterations;
|
||||||
|
private boolean foundAlias;
|
||||||
|
private boolean done;
|
||||||
|
private boolean doneCurrent;
|
||||||
|
private List aliases;
|
||||||
|
private DnsRecord[] answers;
|
||||||
|
private int result;
|
||||||
|
private String error;
|
||||||
|
private boolean nxdomain;
|
||||||
|
private boolean badresponse;
|
||||||
|
private String badresponse_error;
|
||||||
|
private boolean networkerror;
|
||||||
|
private boolean timedout;
|
||||||
|
private boolean nametoolong;
|
||||||
|
private boolean referral;
|
||||||
|
|
||||||
|
private static final Name[] noAliases = new Name[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lookup was successful.
|
||||||
|
*/
|
||||||
|
public static final int SUCCESSFUL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lookup failed due to a data or server error. Repeating the lookup
|
||||||
|
* would not be helpful.
|
||||||
|
*/
|
||||||
|
public static final int UNRECOVERABLE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lookup failed due to a network error. Repeating the lookup may be
|
||||||
|
* helpful.
|
||||||
|
*/
|
||||||
|
public static final int TRY_AGAIN = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host does not exist.
|
||||||
|
*/
|
||||||
|
public static final int HOST_NOT_FOUND = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host exists, but has no records associated with the queried type.
|
||||||
|
*/
|
||||||
|
public static final int TYPE_NOT_FOUND = 4;
|
||||||
|
|
||||||
|
public static synchronized
|
||||||
|
void refreshDefault() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
defaultResolver = new ExtendedResolver();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException("Failed to initialize resolver");
|
||||||
|
}
|
||||||
|
defaultSearchPath = ResolverConfig.getCurrentConfig()
|
||||||
|
.searchPath();
|
||||||
|
defaultCaches = new HashMap();
|
||||||
|
defaultNdots = ResolverConfig.getCurrentConfig()
|
||||||
|
.ndots();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
refreshDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Cache to be used as the default for the specified class by future
|
||||||
|
* Lookups.
|
||||||
|
*
|
||||||
|
* @param cache The default cache for the specified class.
|
||||||
|
* @param dclass The class whose cache is being set.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
void setDefaultCache(Cache cache, int dclass) {
|
||||||
|
DnsClass.check(dclass);
|
||||||
|
defaultCaches.put(Mnemonic.toInteger(dclass), cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the search path to be used as the default by future Lookups.
|
||||||
|
*
|
||||||
|
* @param domains The default search path.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
void setDefaultSearchPath(Name[] domains) {
|
||||||
|
defaultSearchPath = domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a custom logger that will be used to log the send and received packets.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
void setPacketLogger(PacketLogger logger) {
|
||||||
|
Client.setPacketLogger(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void reset() {
|
||||||
|
iterations = 0;
|
||||||
|
foundAlias = false;
|
||||||
|
done = false;
|
||||||
|
doneCurrent = false;
|
||||||
|
aliases = null;
|
||||||
|
answers = null;
|
||||||
|
result = -1;
|
||||||
|
error = null;
|
||||||
|
nxdomain = false;
|
||||||
|
badresponse = false;
|
||||||
|
badresponse_error = null;
|
||||||
|
networkerror = false;
|
||||||
|
timedout = false;
|
||||||
|
nametoolong = false;
|
||||||
|
referral = false;
|
||||||
|
if (temporary_cache) {
|
||||||
|
cache.clearCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of the given name and type
|
||||||
|
* in the IN class.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
* @param type The type of the desired records
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The type is a meta type other than ANY.
|
||||||
|
* @see #Lookup(Name, int, int)
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(Name name, int type) {
|
||||||
|
this(name, type, DnsClass.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of the given name, type,
|
||||||
|
* and class. The lookup will use the default cache, resolver, and search
|
||||||
|
* path, and look for records that are reasonably credible.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
* @param type The type of the desired records
|
||||||
|
* @param dclass The class of the desired records
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The type is a meta type other than ANY.
|
||||||
|
* @see Cache
|
||||||
|
* @see Resolver
|
||||||
|
* @see Credibility
|
||||||
|
* @see Name
|
||||||
|
* @see DnsRecordType
|
||||||
|
* @see DnsClass
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(Name name, int type, int dclass) {
|
||||||
|
DnsRecordType.check(type);
|
||||||
|
DnsClass.check(dclass);
|
||||||
|
if (!DnsRecordType.isRR(type) && type != DnsRecordType.ANY) {
|
||||||
|
throw new IllegalArgumentException("Cannot query for " + "meta-types other than ANY");
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
this.dclass = dclass;
|
||||||
|
synchronized (Lookup.class) {
|
||||||
|
this.resolver = getDefaultResolver();
|
||||||
|
this.searchPath = getDefaultSearchPath();
|
||||||
|
this.cache = getDefaultCache(dclass);
|
||||||
|
}
|
||||||
|
this.credibility = Credibility.NORMAL;
|
||||||
|
this.verbose = Options.check("verbose");
|
||||||
|
this.result = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Resolver that will be used as the default by future Lookups.
|
||||||
|
*
|
||||||
|
* @return The default resolver.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
Resolver getDefaultResolver() {
|
||||||
|
return defaultResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default Resolver to be used as the default by future Lookups.
|
||||||
|
*
|
||||||
|
* @param resolver The default resolver.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
void setDefaultResolver(Resolver resolver) {
|
||||||
|
defaultResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Cache that will be used as the default for the specified
|
||||||
|
* class by future Lookups.
|
||||||
|
*
|
||||||
|
* @param dclass The class whose cache is being retrieved.
|
||||||
|
*
|
||||||
|
* @return The default cache for the specified class.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
Cache getDefaultCache(int dclass) {
|
||||||
|
DnsClass.check(dclass);
|
||||||
|
Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
|
||||||
|
if (c == null) {
|
||||||
|
c = new Cache(dclass);
|
||||||
|
defaultCaches.put(Mnemonic.toInteger(dclass), c);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the search path that will be used as the default by future Lookups.
|
||||||
|
*
|
||||||
|
* @return The default search path.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
Name[] getDefaultSearchPath() {
|
||||||
|
return defaultSearchPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the search path that will be used as the default by future Lookups.
|
||||||
|
*
|
||||||
|
* @param domains The default search path.
|
||||||
|
*
|
||||||
|
* @throws TextParseException A name in the array is not a valid DNS name.
|
||||||
|
*/
|
||||||
|
public static synchronized
|
||||||
|
void setDefaultSearchPath(String[] domains) throws TextParseException {
|
||||||
|
if (domains == null) {
|
||||||
|
defaultSearchPath = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Name[] newdomains = new Name[domains.length];
|
||||||
|
for (int i = 0; i < domains.length; i++) {
|
||||||
|
newdomains[i] = Name.Companion.fromString(domains[i], Name.root);
|
||||||
|
}
|
||||||
|
defaultSearchPath = newdomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of type A at the given name
|
||||||
|
* in the IN class.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
*
|
||||||
|
* @see #Lookup(Name, int, int)
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(Name name) {
|
||||||
|
this(name, DnsRecordType.A, DnsClass.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of the given name, type,
|
||||||
|
* and class.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
* @param type The type of the desired records
|
||||||
|
* @param dclass The class of the desired records
|
||||||
|
*
|
||||||
|
* @throws TextParseException The name is not a valid DNS name
|
||||||
|
* @throws IllegalArgumentException The type is a meta type other than ANY.
|
||||||
|
* @see #Lookup(Name, int, int)
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(String name, int type, int dclass) throws TextParseException {
|
||||||
|
this(Name.Companion.fromString(name), type, dclass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of the given name and type
|
||||||
|
* in the IN class.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
* @param type The type of the desired records
|
||||||
|
*
|
||||||
|
* @throws TextParseException The name is not a valid DNS name
|
||||||
|
* @throws IllegalArgumentException The type is a meta type other than ANY.
|
||||||
|
* @see #Lookup(Name, int, int)
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(String name, int type) throws TextParseException {
|
||||||
|
this(Name.Companion.fromString(name), type, DnsClass.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Lookup object that will find records of type A at the given name
|
||||||
|
* in the IN class.
|
||||||
|
*
|
||||||
|
* @param name The name of the desired records
|
||||||
|
*
|
||||||
|
* @throws TextParseException The name is not a valid DNS name
|
||||||
|
* @see #Lookup(Name, int, int)
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Lookup(String name) throws TextParseException {
|
||||||
|
this(Name.Companion.fromString(name), DnsRecordType.A, DnsClass.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resolver to use when performing this lookup. This overrides the
|
||||||
|
* default value.
|
||||||
|
*
|
||||||
|
* @param resolver The resolver to use.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setResolver(Resolver resolver) {
|
||||||
|
this.resolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the search path to use when performing this lookup. This overrides the
|
||||||
|
* default value.
|
||||||
|
*
|
||||||
|
* @param domains An array of names containing the search path.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setSearchPath(Name[] domains) {
|
||||||
|
this.searchPath = domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the search path to use when performing this lookup. This overrides the
|
||||||
|
* default value.
|
||||||
|
*
|
||||||
|
* @param domains An array of names containing the search path.
|
||||||
|
*
|
||||||
|
* @throws TextParseException A name in the array is not a valid DNS name.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setSearchPath(String[] domains) throws TextParseException {
|
||||||
|
if (domains == null) {
|
||||||
|
this.searchPath = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Name[] newdomains = new Name[domains.length];
|
||||||
|
for (int i = 0; i < domains.length; i++) {
|
||||||
|
newdomains[i] = Name.Companion.fromString(domains[i], Name.root);
|
||||||
|
}
|
||||||
|
this.searchPath = newdomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cache to use when performing this lookup. This overrides the
|
||||||
|
* default value. If the results of this lookup should not be permanently
|
||||||
|
* cached, null can be provided here.
|
||||||
|
*
|
||||||
|
* @param cache The cache to use.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setCache(Cache cache) {
|
||||||
|
if (cache == null) {
|
||||||
|
this.cache = new Cache(dclass);
|
||||||
|
this.temporary_cache = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.cache = cache;
|
||||||
|
this.temporary_cache = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets ndots to use when performing this lookup, overriding the default value.
|
||||||
|
* Specifically, this refers to the number of "dots" which, if present in a
|
||||||
|
* name, indicate that a lookup for the absolute name should be attempted
|
||||||
|
* before appending any search path elements.
|
||||||
|
*
|
||||||
|
* @param ndots The ndots value to use, which must be greater than or equal to
|
||||||
|
* 0.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setNdots(int ndots) {
|
||||||
|
if (ndots < 0) {
|
||||||
|
throw new IllegalArgumentException("Illegal ndots value: " + ndots);
|
||||||
|
}
|
||||||
|
defaultNdots = ndots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum credibility level that will be accepted when performing
|
||||||
|
* the lookup. This defaults to Credibility.NORMAL.
|
||||||
|
*
|
||||||
|
* @param credibility The minimum credibility level.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setCredibility(int credibility) {
|
||||||
|
this.credibility = credibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void follow(Name name, Name oldname) {
|
||||||
|
foundAlias = true;
|
||||||
|
badresponse = false;
|
||||||
|
networkerror = false;
|
||||||
|
timedout = false;
|
||||||
|
nxdomain = false;
|
||||||
|
referral = false;
|
||||||
|
iterations++;
|
||||||
|
if (iterations >= 10 || name.equals(oldname)) {
|
||||||
|
result = UNRECOVERABLE;
|
||||||
|
error = "CNAME loop";
|
||||||
|
done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (aliases == null) {
|
||||||
|
aliases = new ArrayList();
|
||||||
|
}
|
||||||
|
aliases.add(oldname);
|
||||||
|
lookup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void processResponse(Name name, SetResponse response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
RRset[] rrsets = response.answers();
|
||||||
|
List l = new ArrayList();
|
||||||
|
Iterator it;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < rrsets.length; i++) {
|
||||||
|
it = rrsets[i].rrs();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
l.add(it.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = SUCCESSFUL;
|
||||||
|
answers = (DnsRecord[]) l.toArray(new DnsRecord[l.size()]);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (response.isNXDOMAIN()) {
|
||||||
|
nxdomain = true;
|
||||||
|
doneCurrent = true;
|
||||||
|
if (iterations > 0) {
|
||||||
|
result = HOST_NOT_FOUND;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (response.isNXRRSET()) {
|
||||||
|
result = TYPE_NOT_FOUND;
|
||||||
|
answers = null;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (response.isCNAME()) {
|
||||||
|
CNAMERecord cname = response.getCNAME();
|
||||||
|
follow(cname.getTarget(), name);
|
||||||
|
}
|
||||||
|
else if (response.isDNAME()) {
|
||||||
|
DNAMERecord dname = response.getDNAME();
|
||||||
|
try {
|
||||||
|
follow(name.fromDNAME(dname), name);
|
||||||
|
} catch (NameTooLongException e) {
|
||||||
|
result = UNRECOVERABLE;
|
||||||
|
error = "Invalid DNAME target";
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (response.isDelegation()) {
|
||||||
|
// We shouldn't get a referral. Ignore it.
|
||||||
|
referral = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void lookup(Name current) {
|
||||||
|
SetResponse sr = cache.lookupRecords(current, type, credibility);
|
||||||
|
if (verbose) {
|
||||||
|
System.err.println("lookup " + current + " " + DnsRecordType.string(type));
|
||||||
|
System.err.println(sr);
|
||||||
|
}
|
||||||
|
processResponse(current, sr);
|
||||||
|
if (done || doneCurrent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsRecord question = DnsRecord.newRecord(current, type, dclass);
|
||||||
|
DnsMessage query = DnsMessage.newQuery(question);
|
||||||
|
DnsMessage response = null;
|
||||||
|
try {
|
||||||
|
response = resolver.send(query);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// A network error occurred. Press on.
|
||||||
|
if (e instanceof InterruptedIOException) {
|
||||||
|
timedout = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
networkerror = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int rcode = response.getHeader()
|
||||||
|
.getRcode();
|
||||||
|
if (rcode != DnsResponseCode.NOERROR && rcode != DnsResponseCode.NXDOMAIN) {
|
||||||
|
// The server we contacted is broken or otherwise unhelpful.
|
||||||
|
// Press on.
|
||||||
|
badresponse = true;
|
||||||
|
badresponse_error = DnsResponseCode.string(rcode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.getQuestion()
|
||||||
|
.equals(response.getQuestion())) {
|
||||||
|
// The answer doesn't match the question. That's not good.
|
||||||
|
badresponse = true;
|
||||||
|
badresponse_error = "response does not match query";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sr = cache.addMessage(response);
|
||||||
|
if (sr == null) {
|
||||||
|
sr = cache.lookupRecords(current, type, credibility);
|
||||||
|
}
|
||||||
|
if (verbose) {
|
||||||
|
System.err.println("queried " + current + " " + DnsRecordType.string(type));
|
||||||
|
System.err.println(sr);
|
||||||
|
}
|
||||||
|
processResponse(current, sr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void resolve(Name current, Name suffix) {
|
||||||
|
doneCurrent = false;
|
||||||
|
Name tname = null;
|
||||||
|
if (suffix == null) {
|
||||||
|
tname = current;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
tname = Name.concatenate(current, suffix);
|
||||||
|
} catch (NameTooLongException e) {
|
||||||
|
nametoolong = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lookup(tname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the lookup, using the specified Cache, Resolver, and search path.
|
||||||
|
*
|
||||||
|
* @return The answers, or null if none are found.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord[] run() {
|
||||||
|
if (done) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
if (name.isAbsolute()) {
|
||||||
|
resolve(name, null);
|
||||||
|
}
|
||||||
|
else if (searchPath == null) {
|
||||||
|
resolve(name, Name.root);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (name.labels() > defaultNdots) {
|
||||||
|
resolve(name, Name.root);
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < searchPath.length; i++) {
|
||||||
|
resolve(name, searchPath[i]);
|
||||||
|
if (done) {
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
else if (foundAlias) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) {
|
||||||
|
if (badresponse) {
|
||||||
|
result = TRY_AGAIN;
|
||||||
|
error = badresponse_error;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (timedout) {
|
||||||
|
result = TRY_AGAIN;
|
||||||
|
error = "timed out";
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (networkerror) {
|
||||||
|
result = TRY_AGAIN;
|
||||||
|
error = "network error";
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (nxdomain) {
|
||||||
|
result = HOST_NOT_FOUND;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (referral) {
|
||||||
|
result = UNRECOVERABLE;
|
||||||
|
error = "referral";
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else if (nametoolong) {
|
||||||
|
result = UNRECOVERABLE;
|
||||||
|
error = "name too long";
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the answers from the lookup.
|
||||||
|
*
|
||||||
|
* @return The answers, or null if none are found.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException The lookup has not completed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord[] getAnswers() {
|
||||||
|
checkDone();
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void checkDone() {
|
||||||
|
if (done && result != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder("Lookup of " + name + " ");
|
||||||
|
|
||||||
|
if (dclass != DnsClass.IN) {
|
||||||
|
sb.append(DnsClass.string(dclass))
|
||||||
|
.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(DnsRecordType.string(type))
|
||||||
|
.append(" isn't done");
|
||||||
|
throw new IllegalStateException(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all known aliases for this name. Whenever a CNAME/DNAME is
|
||||||
|
* followed, an alias is added to this array. The last element in this
|
||||||
|
* array will be the owner name for records in the answer, if there are any.
|
||||||
|
*
|
||||||
|
* @return The aliases.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException The lookup has not completed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Name[] getAliases() {
|
||||||
|
checkDone();
|
||||||
|
if (aliases == null) {
|
||||||
|
return noAliases;
|
||||||
|
}
|
||||||
|
return (Name[]) aliases.toArray(new Name[aliases.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result code of the lookup.
|
||||||
|
*
|
||||||
|
* @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
|
||||||
|
* HOST_NOT_FOUND, or TYPE_NOT_FOUND.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException The lookup has not completed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getResult() {
|
||||||
|
checkDone();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an error string describing the result code of this lookup.
|
||||||
|
*
|
||||||
|
* @return A string, which may either directly correspond the result code
|
||||||
|
* or be more specific.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException The lookup has not completed.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
String getErrorString() {
|
||||||
|
checkDone();
|
||||||
|
if (error != null) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
switch (result) {
|
||||||
|
case SUCCESSFUL:
|
||||||
|
return "successful";
|
||||||
|
case UNRECOVERABLE:
|
||||||
|
return "unrecoverable error";
|
||||||
|
case TRY_AGAIN:
|
||||||
|
return "try again";
|
||||||
|
case HOST_NOT_FOUND:
|
||||||
|
return "host not found";
|
||||||
|
case TYPE_NOT_FOUND:
|
||||||
|
return "type not found";
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("unknown result");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,478 @@
|
||||||
|
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.exceptions.RelativeNameException;
|
||||||
|
import dorkbox.network.dns.exceptions.TextParseException;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.records.SOARecord;
|
||||||
|
import dorkbox.network.dns.records.TTL;
|
||||||
|
import dorkbox.network.dns.utils.Tokenizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DNS master file parser. This incrementally parses the file, returning
|
||||||
|
* one record at a time. When directives are seen, they are added to the
|
||||||
|
* state and used when parsing future records.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class Master {
|
||||||
|
|
||||||
|
private Name origin;
|
||||||
|
private File file;
|
||||||
|
private DnsRecord last = null;
|
||||||
|
private long defaultTTL;
|
||||||
|
private Master included = null;
|
||||||
|
private Tokenizer st;
|
||||||
|
private int currentType;
|
||||||
|
private int currentDClass;
|
||||||
|
private long currentTTL;
|
||||||
|
private boolean needSOATTL;
|
||||||
|
|
||||||
|
private Generator generator;
|
||||||
|
private List generators;
|
||||||
|
private boolean noExpandGenerate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader and opens the specified master file.
|
||||||
|
*
|
||||||
|
* @param filename The master file.
|
||||||
|
* @param origin The initial origin to append to relative names.
|
||||||
|
* @param ttl The initial default TTL.
|
||||||
|
*
|
||||||
|
* @throws IOException The master file could not be opened.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(String filename, Name origin, long ttl) throws IOException {
|
||||||
|
this(new File(filename), origin, ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
Master(File file, Name origin, long initialTTL) throws IOException {
|
||||||
|
if (origin != null && !origin.isAbsolute()) {
|
||||||
|
throw new RelativeNameException(origin);
|
||||||
|
}
|
||||||
|
this.file = file;
|
||||||
|
st = new Tokenizer(file);
|
||||||
|
this.origin = origin;
|
||||||
|
defaultTTL = initialTTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader and opens the specified master file.
|
||||||
|
*
|
||||||
|
* @param filename The master file.
|
||||||
|
* @param origin The initial origin to append to relative names.
|
||||||
|
*
|
||||||
|
* @throws IOException The master file could not be opened.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(String filename, Name origin) throws IOException {
|
||||||
|
this(new File(filename), origin, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader and opens the specified master file.
|
||||||
|
*
|
||||||
|
* @param filename The master file.
|
||||||
|
*
|
||||||
|
* @throws IOException The master file could not be opened.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(String filename) throws IOException {
|
||||||
|
this(new File(filename), null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader.
|
||||||
|
*
|
||||||
|
* @param in The input stream containing a master file.
|
||||||
|
* @param origin The initial origin to append to relative names.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(InputStream in, Name origin) {
|
||||||
|
this(in, origin, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader.
|
||||||
|
*
|
||||||
|
* @param in The input stream containing a master file.
|
||||||
|
* @param origin The initial origin to append to relative names.
|
||||||
|
* @param ttl The initial default TTL.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(InputStream in, Name origin, long ttl) {
|
||||||
|
if (origin != null && !origin.isAbsolute()) {
|
||||||
|
throw new RelativeNameException(origin);
|
||||||
|
}
|
||||||
|
st = new Tokenizer(in);
|
||||||
|
this.origin = origin;
|
||||||
|
defaultTTL = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the master file reader.
|
||||||
|
*
|
||||||
|
* @param in The input stream containing a master file.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Master(InputStream in) {
|
||||||
|
this(in, null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
Name parseName(String s, Name origin) throws TextParseException {
|
||||||
|
try {
|
||||||
|
return Name.Companion.fromString(s, origin);
|
||||||
|
} catch (TextParseException e) {
|
||||||
|
throw st.exception(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void parseTTLClassAndType() throws IOException {
|
||||||
|
String s;
|
||||||
|
boolean seen_class = false;
|
||||||
|
|
||||||
|
|
||||||
|
// This is a bit messy, since any of the following are legal:
|
||||||
|
// class ttl type
|
||||||
|
// ttl class type
|
||||||
|
// class type
|
||||||
|
// ttl type
|
||||||
|
// type
|
||||||
|
seen_class = false;
|
||||||
|
s = st.getString();
|
||||||
|
if ((currentDClass = DnsClass.value(s)) >= 0) {
|
||||||
|
s = st.getString();
|
||||||
|
seen_class = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTTL = -1;
|
||||||
|
try {
|
||||||
|
currentTTL = TTL.parseTTL(s);
|
||||||
|
s = st.getString();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
if (defaultTTL >= 0) {
|
||||||
|
currentTTL = defaultTTL;
|
||||||
|
}
|
||||||
|
else if (last != null) {
|
||||||
|
currentTTL = last.getTTL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seen_class) {
|
||||||
|
if ((currentDClass = DnsClass.value(s)) >= 0) {
|
||||||
|
s = st.getString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentDClass = DnsClass.IN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((currentType = DnsRecordType.value(s)) < 0) {
|
||||||
|
throw st.exception("Invalid type '" + s + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// BIND allows a missing TTL for the initial SOA record, and uses
|
||||||
|
// the SOA minimum value. If the SOA is not the first record,
|
||||||
|
// this is an error.
|
||||||
|
if (currentTTL < 0) {
|
||||||
|
if (currentType != DnsRecordType.SOA) {
|
||||||
|
throw st.exception("missing TTL");
|
||||||
|
}
|
||||||
|
needSOATTL = true;
|
||||||
|
currentTTL = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
long parseUInt32(String s) {
|
||||||
|
if (!Character.isDigit(s.charAt(0))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
long l = Long.parseLong(s);
|
||||||
|
if (l < 0 || l > 0xFFFFFFFFL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void startGenerate() throws IOException {
|
||||||
|
String s;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
// The first field is of the form start-end[/step]
|
||||||
|
// Regexes would be useful here.
|
||||||
|
s = st.getIdentifier();
|
||||||
|
n = s.indexOf("-");
|
||||||
|
if (n < 0) {
|
||||||
|
throw st.exception("Invalid $GENERATE range specifier: " + s);
|
||||||
|
}
|
||||||
|
String startstr = s.substring(0, n);
|
||||||
|
String endstr = s.substring(n + 1);
|
||||||
|
String stepstr = null;
|
||||||
|
n = endstr.indexOf("/");
|
||||||
|
if (n >= 0) {
|
||||||
|
stepstr = endstr.substring(n + 1);
|
||||||
|
endstr = endstr.substring(0, n);
|
||||||
|
}
|
||||||
|
long start = parseUInt32(startstr);
|
||||||
|
long end = parseUInt32(endstr);
|
||||||
|
long step;
|
||||||
|
if (stepstr != null) {
|
||||||
|
step = parseUInt32(stepstr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
step = 1;
|
||||||
|
}
|
||||||
|
if (start < 0 || end < 0 || start > end || step <= 0) {
|
||||||
|
throw st.exception("Invalid $GENERATE range specifier: " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next field is the name specification.
|
||||||
|
String nameSpec = st.getIdentifier();
|
||||||
|
|
||||||
|
// Then the ttl/class/type, in the same form as a normal record.
|
||||||
|
// Only some types are supported.
|
||||||
|
parseTTLClassAndType();
|
||||||
|
if (!Generator.supportedType(currentType)) {
|
||||||
|
throw st.exception("$GENERATE does not support " + DnsRecordType.string(currentType) + " records");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next comes the rdata specification.
|
||||||
|
String rdataSpec = st.getIdentifier();
|
||||||
|
|
||||||
|
// That should be the end. However, we don't want to move past the
|
||||||
|
// line yet, so put back the EOL after reading it.
|
||||||
|
st.getEOL();
|
||||||
|
st.unget();
|
||||||
|
|
||||||
|
generator = new Generator(start, end, step, nameSpec, currentType, currentDClass, currentTTL, rdataSpec, origin);
|
||||||
|
if (generators == null) {
|
||||||
|
generators = new ArrayList(1);
|
||||||
|
}
|
||||||
|
generators.add(generator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void endGenerate() throws IOException {
|
||||||
|
// Read the EOL that we put back before.
|
||||||
|
st.getEOL();
|
||||||
|
|
||||||
|
generator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
DnsRecord nextGenerated() throws IOException {
|
||||||
|
try {
|
||||||
|
return generator.nextRecord();
|
||||||
|
} catch (Tokenizer.TokenizerException e) {
|
||||||
|
throw st.exception("Parsing $GENERATE: " + e.getBaseMessage());
|
||||||
|
} catch (TextParseException e) {
|
||||||
|
throw st.exception("Parsing $GENERATE: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next record in the master file. This will process any
|
||||||
|
* directives before the next record.
|
||||||
|
*
|
||||||
|
* @return The next record.
|
||||||
|
*
|
||||||
|
* @throws IOException The master file could not be read, or was syntactically
|
||||||
|
* invalid.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord _nextRecord() throws IOException {
|
||||||
|
Tokenizer.Token token;
|
||||||
|
String s;
|
||||||
|
|
||||||
|
if (included != null) {
|
||||||
|
DnsRecord rec = included.nextRecord();
|
||||||
|
if (rec != null) {
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
included = null;
|
||||||
|
}
|
||||||
|
if (generator != null) {
|
||||||
|
DnsRecord rec = nextGenerated();
|
||||||
|
if (rec != null) {
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
endGenerate();
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
Name name;
|
||||||
|
|
||||||
|
token = st.get(true, false);
|
||||||
|
if (token.type == Tokenizer.WHITESPACE) {
|
||||||
|
Tokenizer.Token next = st.get();
|
||||||
|
if (next.type == Tokenizer.EOL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (next.type == Tokenizer.EOF) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
st.unget();
|
||||||
|
}
|
||||||
|
if (last == null) {
|
||||||
|
throw st.exception("no owner");
|
||||||
|
}
|
||||||
|
name = last.getName();
|
||||||
|
}
|
||||||
|
else if (token.type == Tokenizer.EOL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (token.type == Tokenizer.EOF) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (((String) token.value).charAt(0) == '$') {
|
||||||
|
s = token.value;
|
||||||
|
|
||||||
|
if (s.equalsIgnoreCase("$ORIGIN")) {
|
||||||
|
origin = st.getName(Name.root);
|
||||||
|
st.getEOL();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (s.equalsIgnoreCase("$TTL")) {
|
||||||
|
defaultTTL = st.getTTL();
|
||||||
|
st.getEOL();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (s.equalsIgnoreCase("$INCLUDE")) {
|
||||||
|
String filename = st.getString();
|
||||||
|
File newfile;
|
||||||
|
if (file != null) {
|
||||||
|
String parent = file.getParent();
|
||||||
|
newfile = new File(parent, filename);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newfile = new File(filename);
|
||||||
|
}
|
||||||
|
Name incorigin = origin;
|
||||||
|
token = st.get();
|
||||||
|
if (token.isString()) {
|
||||||
|
incorigin = parseName(token.value, Name.root);
|
||||||
|
st.getEOL();
|
||||||
|
}
|
||||||
|
included = new Master(newfile, incorigin, defaultTTL);
|
||||||
|
/*
|
||||||
|
* If we continued, we wouldn't be looking in
|
||||||
|
* the new file. Recursing works better.
|
||||||
|
*/
|
||||||
|
return nextRecord();
|
||||||
|
}
|
||||||
|
else if (s.equalsIgnoreCase("$GENERATE")) {
|
||||||
|
if (generator != null) {
|
||||||
|
throw new IllegalStateException("cannot nest $GENERATE");
|
||||||
|
}
|
||||||
|
startGenerate();
|
||||||
|
if (noExpandGenerate) {
|
||||||
|
endGenerate();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return nextGenerated();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw st.exception("Invalid directive: " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s = token.value;
|
||||||
|
name = parseName(s, origin);
|
||||||
|
if (last != null && name.equals(last.getName())) {
|
||||||
|
name = last.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTTLClassAndType();
|
||||||
|
last = DnsRecord.fromString(name, currentType, currentDClass, currentTTL, st, origin);
|
||||||
|
if (needSOATTL) {
|
||||||
|
long ttl = ((SOARecord) last).getMinimum();
|
||||||
|
last.setTTL(ttl);
|
||||||
|
defaultTTL = ttl;
|
||||||
|
needSOATTL = false;
|
||||||
|
}
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next record in the master file. This will process any
|
||||||
|
* directives before the next record.
|
||||||
|
*
|
||||||
|
* @return The next record.
|
||||||
|
*
|
||||||
|
* @throws IOException The master file could not be read, or was syntactically
|
||||||
|
* invalid.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
DnsRecord nextRecord() throws IOException {
|
||||||
|
DnsRecord rec = null;
|
||||||
|
try {
|
||||||
|
rec = _nextRecord();
|
||||||
|
} finally {
|
||||||
|
if (rec == null) {
|
||||||
|
st.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether $GENERATE statements should be expanded. Whether
|
||||||
|
* expanded or not, the specifications for generated records are available
|
||||||
|
* by calling {@link #generators}. This must be called before a $GENERATE
|
||||||
|
* statement is seen during iteration to have an effect.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void expandGenerate(boolean wantExpand) {
|
||||||
|
noExpandGenerate = !wantExpand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator over the generators specified in the master file; that
|
||||||
|
* is, the parsed contents of $GENERATE statements.
|
||||||
|
*
|
||||||
|
* @see Generator
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Iterator generators() {
|
||||||
|
if (generators != null) {
|
||||||
|
return Collections.unmodifiableList(generators)
|
||||||
|
.iterator();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Collections.EMPTY_LIST.iterator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
void finalize() {
|
||||||
|
if (st != null) {
|
||||||
|
st.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom logger that can log all the packets that were send or received.
|
||||||
|
*
|
||||||
|
* @author Damian Minkov
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface PacketLogger {
|
||||||
|
public
|
||||||
|
void log(String prefix, SocketAddress local, SocketAddress remote, byte[] data);
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions for doing serial arithmetic. These should be used when
|
||||||
|
* setting/checking SOA serial numbers. SOA serial number arithmetic is
|
||||||
|
* defined in RFC 1982.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final
|
||||||
|
class Serial {
|
||||||
|
|
||||||
|
private static final long MAX32 = 0xFFFFFFFFL;
|
||||||
|
|
||||||
|
private
|
||||||
|
Serial() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two numbers using serial arithmetic. The numbers are assumed
|
||||||
|
* to be 32 bit unsigned integers stored in longs.
|
||||||
|
*
|
||||||
|
* @param serial1 The first integer
|
||||||
|
* @param serial2 The second integer
|
||||||
|
*
|
||||||
|
* @return 0 if the 2 numbers are equal, a positive number if serial1 is greater
|
||||||
|
* than serial2, and a negative number if serial2 is greater than serial1.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException serial1 or serial2 is out of range
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
int compare(long serial1, long serial2) {
|
||||||
|
if (serial1 < 0 || serial1 > MAX32) {
|
||||||
|
throw new IllegalArgumentException(serial1 + " out of range");
|
||||||
|
}
|
||||||
|
if (serial2 < 0 || serial2 > MAX32) {
|
||||||
|
throw new IllegalArgumentException(serial2 + " out of range");
|
||||||
|
}
|
||||||
|
long diff = serial1 - serial2;
|
||||||
|
if (diff >= MAX32) {
|
||||||
|
diff -= (MAX32 + 1);
|
||||||
|
}
|
||||||
|
else if (diff < -MAX32) {
|
||||||
|
diff += (MAX32 + 1);
|
||||||
|
}
|
||||||
|
return (int) diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments a serial number. The number is assumed to be a 32 bit unsigned
|
||||||
|
* integer stored in a long. This basically adds 1 and resets the value to
|
||||||
|
* 0 if it is 2^32.
|
||||||
|
*
|
||||||
|
* @param serial The serial number
|
||||||
|
*
|
||||||
|
* @return The incremented serial number
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException serial is out of range
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
long increment(long serial) {
|
||||||
|
if (serial < 0 || serial > MAX32) {
|
||||||
|
throw new IllegalArgumentException(serial + " out of range");
|
||||||
|
}
|
||||||
|
if (serial == MAX32) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return serial + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
|
||||||
|
public final
|
||||||
|
class TCPClient extends Client {
|
||||||
|
|
||||||
|
public
|
||||||
|
TCPClient(long endTime) throws IOException {
|
||||||
|
super(SocketChannel.open(), endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
byte[] sendrecv(SocketAddress addr, byte[] data, long endTime) throws IOException {
|
||||||
|
return sendrecv(null, addr, data, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
byte[] sendrecv(SocketAddress local, SocketAddress remote, byte[] data, long endTime) throws IOException {
|
||||||
|
TCPClient client = new TCPClient(endTime);
|
||||||
|
try {
|
||||||
|
if (local != null) {
|
||||||
|
client.bind(local);
|
||||||
|
}
|
||||||
|
client.connect(remote);
|
||||||
|
client.send(data);
|
||||||
|
return client.recv();
|
||||||
|
} finally {
|
||||||
|
client.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(SocketAddress addr) throws IOException {
|
||||||
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
|
channel.socket()
|
||||||
|
.bind(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect(SocketAddress addr) throws IOException {
|
||||||
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
|
if (channel.connect(addr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
key.interestOps(SelectionKey.OP_CONNECT);
|
||||||
|
try {
|
||||||
|
while (!channel.finishConnect()) {
|
||||||
|
if (!key.isConnectable()) {
|
||||||
|
blockUntil(key, endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (key.isValid()) {
|
||||||
|
key.interestOps(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(byte[] data) throws IOException {
|
||||||
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
|
verboseLog("TCP write",
|
||||||
|
channel.socket()
|
||||||
|
.getLocalSocketAddress(),
|
||||||
|
channel.socket()
|
||||||
|
.getRemoteSocketAddress(),
|
||||||
|
data);
|
||||||
|
byte[] lengthArray = new byte[2];
|
||||||
|
lengthArray[0] = (byte) (data.length >>> 8);
|
||||||
|
lengthArray[1] = (byte) (data.length & 0xFF);
|
||||||
|
ByteBuffer[] buffers = new ByteBuffer[2];
|
||||||
|
buffers[0] = ByteBuffer.wrap(lengthArray);
|
||||||
|
buffers[1] = ByteBuffer.wrap(data);
|
||||||
|
int nsent = 0;
|
||||||
|
key.interestOps(SelectionKey.OP_WRITE);
|
||||||
|
try {
|
||||||
|
while (nsent < data.length + 2) {
|
||||||
|
if (key.isWritable()) {
|
||||||
|
long n = channel.write(buffers);
|
||||||
|
if (n < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
nsent += (int) n;
|
||||||
|
if (nsent < data.length + 2 && System.currentTimeMillis() > endTime) {
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
blockUntil(key, endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (key.isValid()) {
|
||||||
|
key.interestOps(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] recv() throws IOException {
|
||||||
|
byte[] buf = _recv(2);
|
||||||
|
int length = ((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF);
|
||||||
|
byte[] data = _recv(length);
|
||||||
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
|
verboseLog("TCP read",
|
||||||
|
channel.socket()
|
||||||
|
.getLocalSocketAddress(),
|
||||||
|
channel.socket()
|
||||||
|
.getRemoteSocketAddress(),
|
||||||
|
data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
byte[] _recv(int length) throws IOException {
|
||||||
|
SocketChannel channel = (SocketChannel) key.channel();
|
||||||
|
int nrecvd = 0;
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||||
|
key.interestOps(SelectionKey.OP_READ);
|
||||||
|
try {
|
||||||
|
while (nrecvd < length) {
|
||||||
|
if (key.isReadable()) {
|
||||||
|
long n = channel.read(buffer);
|
||||||
|
if (n < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
nrecvd += (int) n;
|
||||||
|
if (nrecvd < length && System.currentTimeMillis() > endTime) {
|
||||||
|
throw new SocketTimeoutException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
blockUntil(key, endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (key.isValid()) {
|
||||||
|
key.interestOps(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public final
|
||||||
|
class UDPClient extends Client {
|
||||||
|
|
||||||
|
private static final int EPHEMERAL_START = 1024;
|
||||||
|
private static final int EPHEMERAL_STOP = 65535;
|
||||||
|
private static final int EPHEMERAL_RANGE = EPHEMERAL_STOP - EPHEMERAL_START;
|
||||||
|
|
||||||
|
private static SecureRandom prng = new SecureRandom();
|
||||||
|
private static volatile boolean prng_initializing = true;
|
||||||
|
private boolean bound = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On some platforms (Windows), the SecureRandom module initialization involves
|
||||||
|
* a call to InetAddress.getLocalHost(), which can end up here if using a
|
||||||
|
* dnsjava name service provider.
|
||||||
|
*
|
||||||
|
* This can cause problems in multiple ways.
|
||||||
|
* - If the SecureRandom seed generation process calls into here, and this
|
||||||
|
* module attempts to seed the local SecureRandom object, the thread hangs.
|
||||||
|
* - If something else calls InetAddress.getLocalHost(), and that causes this
|
||||||
|
* module to seed the local SecureRandom object, the thread hangs.
|
||||||
|
*
|
||||||
|
* To avoid both of these, check at initialization time to see if InetAddress
|
||||||
|
* is in the call chain. If so, initialize the SecureRandom object in a new
|
||||||
|
* thread, and disable port randomization until it completes.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
int n = prng.nextInt();
|
||||||
|
prng_initializing = false;
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
UDPClient(long endTime) throws IOException {
|
||||||
|
super(DatagramChannel.open(), endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
byte[] sendrecv(SocketAddress addr, byte[] data, int max, long endTime) throws IOException {
|
||||||
|
return sendrecv(null, addr, data, max, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
byte[] sendrecv(SocketAddress local, SocketAddress remote, byte[] data, int max, long endTime) throws IOException {
|
||||||
|
UDPClient client = new UDPClient(endTime);
|
||||||
|
try {
|
||||||
|
client.bind(local);
|
||||||
|
client.connect(remote);
|
||||||
|
client.send(data);
|
||||||
|
return client.recv(max);
|
||||||
|
} finally {
|
||||||
|
client.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(SocketAddress addr) throws IOException {
|
||||||
|
if (addr == null || (addr instanceof InetSocketAddress && ((InetSocketAddress) addr).getPort() == 0)) {
|
||||||
|
bind_random((InetSocketAddress) addr);
|
||||||
|
if (bound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr != null) {
|
||||||
|
DatagramChannel channel = (DatagramChannel) key.channel();
|
||||||
|
channel.socket()
|
||||||
|
.bind(addr);
|
||||||
|
bound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void bind_random(InetSocketAddress addr) throws IOException {
|
||||||
|
if (prng_initializing) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
if (prng_initializing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DatagramChannel channel = (DatagramChannel) key.channel();
|
||||||
|
InetSocketAddress temp;
|
||||||
|
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
try {
|
||||||
|
int port = prng.nextInt(EPHEMERAL_RANGE) + EPHEMERAL_START;
|
||||||
|
if (addr != null) {
|
||||||
|
temp = new InetSocketAddress(addr.getAddress(), port);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temp = new InetSocketAddress(port);
|
||||||
|
}
|
||||||
|
channel.socket()
|
||||||
|
.bind(temp);
|
||||||
|
bound = true;
|
||||||
|
return;
|
||||||
|
} catch (SocketException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect(SocketAddress addr) throws IOException {
|
||||||
|
if (!bound) {
|
||||||
|
bind(null);
|
||||||
|
}
|
||||||
|
DatagramChannel channel = (DatagramChannel) key.channel();
|
||||||
|
channel.connect(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(byte[] data) throws IOException {
|
||||||
|
DatagramChannel channel = (DatagramChannel) key.channel();
|
||||||
|
verboseLog("UDP write",
|
||||||
|
channel.socket()
|
||||||
|
.getLocalSocketAddress(),
|
||||||
|
channel.socket()
|
||||||
|
.getRemoteSocketAddress(),
|
||||||
|
data);
|
||||||
|
channel.write(ByteBuffer.wrap(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] recv(int max) throws IOException {
|
||||||
|
DatagramChannel channel = (DatagramChannel) key.channel();
|
||||||
|
byte[] temp = new byte[max];
|
||||||
|
key.interestOps(SelectionKey.OP_READ);
|
||||||
|
try {
|
||||||
|
while (!key.isReadable()) {
|
||||||
|
blockUntil(key, endTime);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (key.isValid()) {
|
||||||
|
key.interestOps(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long ret = channel.read(ByteBuffer.wrap(temp));
|
||||||
|
if (ret <= 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
int len = (int) ret;
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
System.arraycopy(temp, 0, data, 0, len);
|
||||||
|
verboseLog("UDP read",
|
||||||
|
channel.socket()
|
||||||
|
.getLocalSocketAddress(),
|
||||||
|
channel.socket()
|
||||||
|
.getRemoteSocketAddress(),
|
||||||
|
data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,655 @@
|
||||||
|
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.xbill.DNS2.resolver.SetResponse;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.exceptions.ZoneTransferException;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.records.RRset;
|
||||||
|
import dorkbox.network.dns.records.SOARecord;
|
||||||
|
import dorkbox.util.OS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DNS Zone. This encapsulates all data related to a Zone, and provides
|
||||||
|
* convenient lookup methods.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class Zone implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -9220510891189510942L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A primary zone
|
||||||
|
*/
|
||||||
|
public static final int PRIMARY = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A secondary zone
|
||||||
|
*/
|
||||||
|
public static final int SECONDARY = 2;
|
||||||
|
|
||||||
|
private Map data;
|
||||||
|
private Name origin;
|
||||||
|
private Object originNode;
|
||||||
|
private int dclass = DnsClass.IN;
|
||||||
|
private RRset NS;
|
||||||
|
private SOARecord SOA;
|
||||||
|
private boolean hasWild;
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneIterator implements Iterator {
|
||||||
|
private Iterator zentries;
|
||||||
|
private RRset[] current;
|
||||||
|
private int count;
|
||||||
|
private boolean wantLastSOA;
|
||||||
|
|
||||||
|
ZoneIterator(boolean axfr) {
|
||||||
|
synchronized (Zone.this) {
|
||||||
|
zentries = data.entrySet()
|
||||||
|
.iterator();
|
||||||
|
}
|
||||||
|
wantLastSOA = axfr;
|
||||||
|
RRset[] sets = allRRsets(originNode);
|
||||||
|
current = new RRset[sets.length];
|
||||||
|
for (int i = 0, j = 2; i < sets.length; i++) {
|
||||||
|
int type = sets[i].getType();
|
||||||
|
if (type == DnsRecordType.SOA) {
|
||||||
|
current[0] = sets[i];
|
||||||
|
}
|
||||||
|
else if (type == DnsRecordType.NS) {
|
||||||
|
current[1] = sets[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current[j++] = sets[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean hasNext() {
|
||||||
|
return (current != null || wantLastSOA);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Object next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
if (current == null) {
|
||||||
|
wantLastSOA = false;
|
||||||
|
return oneRRset(originNode, DnsRecordType.SOA);
|
||||||
|
}
|
||||||
|
Object set = current[count++];
|
||||||
|
if (count == current.length) {
|
||||||
|
current = null;
|
||||||
|
while (zentries.hasNext()) {
|
||||||
|
Map.Entry entry = (Map.Entry) zentries.next();
|
||||||
|
if (entry.getKey()
|
||||||
|
.equals(origin)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RRset[] sets = allRRsets(entry.getValue());
|
||||||
|
if (sets.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current = sets;
|
||||||
|
count = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Zone from the records in the specified master file.
|
||||||
|
*
|
||||||
|
* @param zone The name of the zone.
|
||||||
|
* @param file The master file to read from.
|
||||||
|
*
|
||||||
|
* @see Master
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Zone(Name zone, String file) throws IOException {
|
||||||
|
data = new TreeMap();
|
||||||
|
|
||||||
|
if (zone == null) {
|
||||||
|
throw new IllegalArgumentException("no zone name specified");
|
||||||
|
}
|
||||||
|
Master m = new Master(file, zone);
|
||||||
|
DnsRecord record;
|
||||||
|
|
||||||
|
origin = zone;
|
||||||
|
while ((record = m.nextRecord()) != null) {
|
||||||
|
maybeAddRecord(record);
|
||||||
|
}
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void validate() throws IOException {
|
||||||
|
originNode = exactName(origin);
|
||||||
|
if (originNode == null) {
|
||||||
|
throw new IOException(origin + ": no data specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
RRset rrset = oneRRset(originNode, DnsRecordType.SOA);
|
||||||
|
if (rrset == null || rrset.size() != 1) {
|
||||||
|
throw new IOException(origin + ": exactly 1 SOA must be specified");
|
||||||
|
}
|
||||||
|
Iterator it = rrset.rrs();
|
||||||
|
SOA = (SOARecord) it.next();
|
||||||
|
|
||||||
|
NS = oneRRset(originNode, DnsRecordType.NS);
|
||||||
|
if (NS == null) {
|
||||||
|
throw new IOException(origin + ": no NS set specified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void maybeAddRecord(DnsRecord record) throws IOException {
|
||||||
|
int rtype = record.getType();
|
||||||
|
Name name = record.getName();
|
||||||
|
|
||||||
|
if (rtype == DnsRecordType.SOA && !name.equals(origin)) {
|
||||||
|
throw new IOException("SOA owner " + name + " does not match zone origin " + origin);
|
||||||
|
}
|
||||||
|
if (name.subdomain(origin)) {
|
||||||
|
addRecord(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Record to the Zone
|
||||||
|
*
|
||||||
|
* @param r The record to be added
|
||||||
|
*
|
||||||
|
* @see DnsRecord
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void addRecord(DnsRecord r) {
|
||||||
|
Name name = r.getName();
|
||||||
|
int rtype = r.getRRsetType();
|
||||||
|
synchronized (this) {
|
||||||
|
RRset rrset = findRRset(name, rtype);
|
||||||
|
if (rrset == null) {
|
||||||
|
rrset = new RRset(r);
|
||||||
|
addRRset(name, rrset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rrset.addRR(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
RRset findRRset(Name name, int type) {
|
||||||
|
Object types = exactName(name);
|
||||||
|
if (types == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return oneRRset(types, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
Object exactName(Name name) {
|
||||||
|
return data.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
RRset oneRRset(Object types, int type) {
|
||||||
|
if (type == DnsRecordType.ANY) {
|
||||||
|
throw new IllegalArgumentException("oneRRset(ANY)");
|
||||||
|
}
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
RRset set = (RRset) list.get(i);
|
||||||
|
if (set.getType() == type) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RRset set = (RRset) types;
|
||||||
|
if (set.getType() == type) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
void addRRset(Name name, RRset rrset) {
|
||||||
|
if (!hasWild && name.isWild()) {
|
||||||
|
hasWild = true;
|
||||||
|
}
|
||||||
|
Object types = data.get(name);
|
||||||
|
if (types == null) {
|
||||||
|
data.put(name, rrset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int rtype = rrset.getType();
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
RRset set = (RRset) list.get(i);
|
||||||
|
if (set.getType() == rtype) {
|
||||||
|
list.set(i, rrset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(rrset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RRset set = (RRset) types;
|
||||||
|
if (set.getType() == rtype) {
|
||||||
|
data.put(name, rrset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LinkedList list = new LinkedList();
|
||||||
|
list.add(set);
|
||||||
|
list.add(rrset);
|
||||||
|
data.put(name, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Zone from an array of records.
|
||||||
|
*
|
||||||
|
* @param zone The name of the zone.
|
||||||
|
* @param records The records to add to the zone.
|
||||||
|
*
|
||||||
|
* @see Master
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Zone(Name zone, DnsRecord[] records) throws IOException {
|
||||||
|
data = new TreeMap();
|
||||||
|
|
||||||
|
if (zone == null) {
|
||||||
|
throw new IllegalArgumentException("no zone name specified");
|
||||||
|
}
|
||||||
|
origin = zone;
|
||||||
|
for (int i = 0; i < records.length; i++) {
|
||||||
|
maybeAddRecord(records[i]);
|
||||||
|
}
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Zone by doing the specified zone transfer.
|
||||||
|
*
|
||||||
|
* @param xfrin The incoming zone transfer to execute.
|
||||||
|
*
|
||||||
|
* @see ZoneTransferIn
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
|
||||||
|
fromXFR(xfrin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
|
||||||
|
data = new TreeMap();
|
||||||
|
|
||||||
|
origin = xfrin.getName();
|
||||||
|
List records = xfrin.run();
|
||||||
|
for (Iterator it = records.iterator(); it.hasNext(); ) {
|
||||||
|
DnsRecord record = (DnsRecord) it.next();
|
||||||
|
maybeAddRecord(record);
|
||||||
|
}
|
||||||
|
if (!xfrin.isAXFR()) {
|
||||||
|
throw new IllegalArgumentException("zones can only be " + "created from AXFRs");
|
||||||
|
}
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Zone by performing a zone transfer to the specified host.
|
||||||
|
*
|
||||||
|
* @see ZoneTransferIn
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Zone(Name zone, int dclass, String remote) throws IOException, ZoneTransferException {
|
||||||
|
ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null);
|
||||||
|
xfrin.setDClass(dclass);
|
||||||
|
fromXFR(xfrin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Zone's origin
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Name getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Zone origin's NS records
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
RRset getNS() {
|
||||||
|
return NS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Zone's SOA record
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
SOARecord getSOA() {
|
||||||
|
return SOA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Zone's class
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getDClass() {
|
||||||
|
return dclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up Records in the Zone. This follows CNAMEs and wildcards.
|
||||||
|
*
|
||||||
|
* @param name The name to look up
|
||||||
|
* @param type The type to look up
|
||||||
|
*
|
||||||
|
* @return A SetResponse object
|
||||||
|
*
|
||||||
|
* @see SetResponse
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
SetResponse findRecords(Name name, int type) {
|
||||||
|
return lookup(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
SetResponse lookup(Name name, int type) {
|
||||||
|
int labels;
|
||||||
|
int olabels;
|
||||||
|
int tlabels;
|
||||||
|
RRset rrset;
|
||||||
|
Name tname;
|
||||||
|
Object types;
|
||||||
|
SetResponse sr;
|
||||||
|
|
||||||
|
if (!name.subdomain(origin)) {
|
||||||
|
return SetResponse.ofType(SetResponse.NXDOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = name.labels();
|
||||||
|
olabels = origin.labels();
|
||||||
|
|
||||||
|
for (tlabels = olabels; tlabels <= labels; tlabels++) {
|
||||||
|
boolean isOrigin = (tlabels == olabels);
|
||||||
|
boolean isExact = (tlabels == labels);
|
||||||
|
|
||||||
|
if (isOrigin) {
|
||||||
|
tname = origin;
|
||||||
|
}
|
||||||
|
else if (isExact) {
|
||||||
|
tname = name;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tname = new Name(name, labels - tlabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
types = exactName(tname);
|
||||||
|
if (types == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this is a delegation, return that. */
|
||||||
|
if (!isOrigin) {
|
||||||
|
RRset ns = oneRRset(types, DnsRecordType.NS);
|
||||||
|
if (ns != null) {
|
||||||
|
return new SetResponse(SetResponse.DELEGATION, ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this is an ANY lookup, return everything. */
|
||||||
|
if (isExact && type == DnsRecordType.ANY) {
|
||||||
|
sr = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
RRset[] sets = allRRsets(types);
|
||||||
|
for (int i = 0; i < sets.length; i++) {
|
||||||
|
sr.addRRset(sets[i]);
|
||||||
|
}
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this is the name, look for the actual type or a CNAME.
|
||||||
|
* Otherwise, look for a DNAME.
|
||||||
|
*/
|
||||||
|
if (isExact) {
|
||||||
|
rrset = oneRRset(types, type);
|
||||||
|
if (rrset != null) {
|
||||||
|
sr = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
sr.addRRset(rrset);
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
rrset = oneRRset(types, DnsRecordType.CNAME);
|
||||||
|
if (rrset != null) {
|
||||||
|
return new SetResponse(SetResponse.CNAME, rrset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rrset = oneRRset(types, DnsRecordType.DNAME);
|
||||||
|
if (rrset != null) {
|
||||||
|
return new SetResponse(SetResponse.DNAME, rrset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We found the name, but not the type. */
|
||||||
|
if (isExact) {
|
||||||
|
return SetResponse.ofType(SetResponse.NXRRSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasWild) {
|
||||||
|
for (int i = 0; i < labels - olabels; i++) {
|
||||||
|
tname = name.wild(i + 1);
|
||||||
|
|
||||||
|
types = exactName(tname);
|
||||||
|
if (types == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rrset = oneRRset(types, type);
|
||||||
|
if (rrset != null) {
|
||||||
|
sr = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
sr.addRRset(rrset);
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetResponse.ofType(SetResponse.NXDOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
RRset[] allRRsets(Object types) {
|
||||||
|
if (types instanceof List) {
|
||||||
|
List typelist = (List) types;
|
||||||
|
return (RRset[]) typelist.toArray(new RRset[typelist.size()]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RRset set = (RRset) types;
|
||||||
|
return new RRset[] {set};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up Records in the zone, finding exact matches only.
|
||||||
|
*
|
||||||
|
* @param name The name to look up
|
||||||
|
* @param type The type to look up
|
||||||
|
*
|
||||||
|
* @return The matching RRset
|
||||||
|
*
|
||||||
|
* @see RRset
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
RRset findExactMatch(Name name, int type) {
|
||||||
|
Object types = exactName(name);
|
||||||
|
if (types == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return oneRRset(types, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an RRset to the Zone
|
||||||
|
*
|
||||||
|
* @param rrset The RRset to be added
|
||||||
|
*
|
||||||
|
* @see RRset
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void addRRset(RRset rrset) {
|
||||||
|
Name name = rrset.getName();
|
||||||
|
addRRset(name, rrset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a record from the Zone
|
||||||
|
*
|
||||||
|
* @param r The record to be removed
|
||||||
|
*
|
||||||
|
* @see DnsRecord
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void removeRecord(DnsRecord r) {
|
||||||
|
Name name = r.getName();
|
||||||
|
int rtype = r.getRRsetType();
|
||||||
|
synchronized (this) {
|
||||||
|
RRset rrset = findRRset(name, rtype);
|
||||||
|
if (rrset == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rrset.size() == 1 && rrset.first()
|
||||||
|
.equals(r)) {
|
||||||
|
removeRRset(name, rtype);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rrset.deleteRR(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
void removeRRset(Name name, int type) {
|
||||||
|
Object types = data.get(name);
|
||||||
|
if (types == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
RRset set = (RRset) list.get(i);
|
||||||
|
if (set.getType() == type) {
|
||||||
|
list.remove(i);
|
||||||
|
if (list.size() == 0) {
|
||||||
|
data.remove(name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RRset set = (RRset) types;
|
||||||
|
if (set.getType() != type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an Iterator over the RRsets in the zone.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Iterator iterator() {
|
||||||
|
return new ZoneIterator(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an Iterator over the RRsets in the zone that can be used to
|
||||||
|
* construct an AXFR response. This is identical to {@link #iterator} except
|
||||||
|
* that the SOA is returned at the end as well as the beginning.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Iterator AXFR() {
|
||||||
|
return new ZoneIterator(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of the Zone as a string (in master file format).
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
return toMasterFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of the Zone in master file format.
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
String toMasterFile() {
|
||||||
|
Iterator zentries = data.entrySet()
|
||||||
|
.iterator();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
nodeToString(sb, originNode);
|
||||||
|
while (zentries.hasNext()) {
|
||||||
|
Map.Entry entry = (Map.Entry) zentries.next();
|
||||||
|
if (!origin.equals(entry.getKey())) {
|
||||||
|
nodeToString(sb, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void nodeToString(StringBuilder sb, Object node) {
|
||||||
|
RRset[] sets = allRRsets(node);
|
||||||
|
for (int i = 0; i < sets.length; i++) {
|
||||||
|
RRset rrset = sets[i];
|
||||||
|
Iterator it = rrset.rrs();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
sb.append(it.next())
|
||||||
|
.append(OS.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
it = rrset.sigs();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
sb.append(it.next())
|
||||||
|
.append(OS.LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,749 @@
|
||||||
|
// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
// Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright
|
||||||
|
// notice follows.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 1999-2001 Internet Software Consortium.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
|
||||||
|
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||||
|
* INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||||
|
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.xbill.DNS2.resolver.SimpleResolver;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsOpCode;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.constants.DnsResponseCode;
|
||||||
|
import dorkbox.network.dns.constants.DnsSection;
|
||||||
|
import dorkbox.network.dns.exceptions.NameTooLongException;
|
||||||
|
import dorkbox.network.dns.exceptions.WireParseException;
|
||||||
|
import dorkbox.network.dns.exceptions.ZoneTransferException;
|
||||||
|
import dorkbox.network.dns.records.DnsMessage;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.records.SOARecord;
|
||||||
|
import dorkbox.network.dns.records.TSIG;
|
||||||
|
import dorkbox.network.dns.records.TSIGRecord;
|
||||||
|
import dorkbox.network.dns.utils.Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An incoming DNS Zone Transfer. To use this class, first initialize an
|
||||||
|
* object, then call the run() method. If run() doesn't throw an exception
|
||||||
|
* the result will either be an IXFR-style response, an AXFR-style response,
|
||||||
|
* or an indication that the zone is up to date.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class ZoneTransferIn {
|
||||||
|
|
||||||
|
private static final int INITIALSOA = 0;
|
||||||
|
private static final int FIRSTDATA = 1;
|
||||||
|
private static final int IXFR_DELSOA = 2;
|
||||||
|
private static final int IXFR_DEL = 3;
|
||||||
|
private static final int IXFR_ADDSOA = 4;
|
||||||
|
private static final int IXFR_ADD = 5;
|
||||||
|
private static final int AXFR = 6;
|
||||||
|
private static final int END = 7;
|
||||||
|
|
||||||
|
private Name zname;
|
||||||
|
private int qtype;
|
||||||
|
private int dclass;
|
||||||
|
private long ixfr_serial;
|
||||||
|
private boolean want_fallback;
|
||||||
|
private ZoneTransferHandler handler;
|
||||||
|
|
||||||
|
private SocketAddress localAddress;
|
||||||
|
private SocketAddress address;
|
||||||
|
private TCPClient client;
|
||||||
|
private TSIG tsig;
|
||||||
|
private TSIG.StreamVerifier verifier;
|
||||||
|
private long timeout = 900 * 1000;
|
||||||
|
|
||||||
|
private int state;
|
||||||
|
private long end_serial;
|
||||||
|
private long current_serial;
|
||||||
|
private DnsRecord initialsoa;
|
||||||
|
|
||||||
|
private int rtype;
|
||||||
|
|
||||||
|
|
||||||
|
public static
|
||||||
|
class Delta {
|
||||||
|
/**
|
||||||
|
* All changes between two versions of a zone in an IXFR response.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The starting serial number of this delta.
|
||||||
|
*/
|
||||||
|
public long start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ending serial number of this delta.
|
||||||
|
*/
|
||||||
|
public long end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of records added between the start and end versions
|
||||||
|
*/
|
||||||
|
public List adds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of records deleted between the start and end versions
|
||||||
|
*/
|
||||||
|
public List deletes;
|
||||||
|
|
||||||
|
private
|
||||||
|
Delta() {
|
||||||
|
adds = new ArrayList();
|
||||||
|
deletes = new ArrayList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static
|
||||||
|
interface ZoneTransferHandler {
|
||||||
|
/**
|
||||||
|
* Handles a Zone Transfer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an AXFR transfer begins.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void startAXFR() throws ZoneTransferException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an IXFR transfer begins.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void startIXFR() throws ZoneTransferException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a series of IXFR deletions begins.
|
||||||
|
*
|
||||||
|
* @param soa The starting SOA.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void startIXFRDeletes(DnsRecord soa) throws ZoneTransferException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a series of IXFR adds begins.
|
||||||
|
*
|
||||||
|
* @param soa The starting SOA.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void startIXFRAdds(DnsRecord soa) throws ZoneTransferException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for each content record in an AXFR.
|
||||||
|
*
|
||||||
|
* @param r The DNS record.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void handleRecord(DnsRecord r) throws ZoneTransferException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
class BasicHandler implements ZoneTransferHandler {
|
||||||
|
private List axfr;
|
||||||
|
private List ixfr;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void startAXFR() {
|
||||||
|
axfr = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void startIXFR() {
|
||||||
|
ixfr = new ArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void startIXFRDeletes(DnsRecord soa) {
|
||||||
|
Delta delta = new Delta();
|
||||||
|
delta.deletes.add(soa);
|
||||||
|
delta.start = getSOASerial(soa);
|
||||||
|
ixfr.add(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void startIXFRAdds(DnsRecord soa) {
|
||||||
|
Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
|
||||||
|
delta.adds.add(soa);
|
||||||
|
delta.end = getSOASerial(soa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void handleRecord(DnsRecord r) {
|
||||||
|
List list;
|
||||||
|
if (ixfr != null) {
|
||||||
|
Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
|
||||||
|
if (delta.adds.size() > 0) {
|
||||||
|
list = delta.adds;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list = delta.deletes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list = axfr;
|
||||||
|
}
|
||||||
|
list.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
ZoneTransferIn() {}
|
||||||
|
|
||||||
|
private
|
||||||
|
ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback, SocketAddress address, TSIG key) {
|
||||||
|
this.address = address;
|
||||||
|
this.tsig = key;
|
||||||
|
if (zone.isAbsolute()) {
|
||||||
|
zname = zone;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
zname = Name.concatenate(zone, Name.root);
|
||||||
|
} catch (NameTooLongException e) {
|
||||||
|
throw new IllegalArgumentException("ZoneTransferIn: " + "name too long");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qtype = xfrtype;
|
||||||
|
dclass = DnsClass.IN;
|
||||||
|
ixfr_serial = serial;
|
||||||
|
want_fallback = fallback;
|
||||||
|
state = INITIALSOA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param host The host from which to transfer the zone.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*
|
||||||
|
* @throws UnknownHostException The host does not exist.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newAXFR(Name zone, String host, TSIG key) throws UnknownHostException {
|
||||||
|
return newAXFR(zone, host, 0, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param host The host from which to transfer the zone.
|
||||||
|
* @param port The port to connect to on the server, or 0 for the default.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*
|
||||||
|
* @throws UnknownHostException The host does not exist.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newAXFR(Name zone, String host, int port, TSIG key) throws UnknownHostException {
|
||||||
|
if (port == 0) {
|
||||||
|
port = SimpleResolver.DEFAULT_PORT;
|
||||||
|
}
|
||||||
|
return newAXFR(zone, new InetSocketAddress(host, port), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param address The host/port from which to transfer the zone.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newAXFR(Name zone, SocketAddress address, TSIG key) {
|
||||||
|
return new ZoneTransferIn(zone, DnsRecordType.AXFR, 0, false, address, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
|
||||||
|
* transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param serial The existing serial number.
|
||||||
|
* @param fallback If true, fall back to AXFR if IXFR is not supported.
|
||||||
|
* @param host The host from which to transfer the zone.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*
|
||||||
|
* @throws UnknownHostException The host does not exist.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key) throws UnknownHostException {
|
||||||
|
return newIXFR(zone, serial, fallback, host, 0, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
|
||||||
|
* transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param serial The existing serial number.
|
||||||
|
* @param fallback If true, fall back to AXFR if IXFR is not supported.
|
||||||
|
* @param host The host from which to transfer the zone.
|
||||||
|
* @param port The port to connect to on the server, or 0 for the default.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*
|
||||||
|
* @throws UnknownHostException The host does not exist.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newIXFR(Name zone, long serial, boolean fallback, String host, int port, TSIG key) throws UnknownHostException {
|
||||||
|
if (port == 0) {
|
||||||
|
port = SimpleResolver.DEFAULT_PORT;
|
||||||
|
}
|
||||||
|
return newIXFR(zone, serial, fallback, new InetSocketAddress(host, port), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
|
||||||
|
* transfer).
|
||||||
|
*
|
||||||
|
* @param zone The zone to transfer.
|
||||||
|
* @param serial The existing serial number.
|
||||||
|
* @param fallback If true, fall back to AXFR if IXFR is not supported.
|
||||||
|
* @param address The host/port from which to transfer the zone.
|
||||||
|
* @param key The TSIG key used to authenticate the transfer, or null.
|
||||||
|
*
|
||||||
|
* @return The ZoneTransferIn object.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
ZoneTransferIn newIXFR(Name zone, long serial, boolean fallback, SocketAddress address, TSIG key) {
|
||||||
|
return new ZoneTransferIn(zone, DnsRecordType.IXFR, serial, fallback, address, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the zone being transferred.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Name getName() {
|
||||||
|
return zname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the type of zone transfer (either AXFR or IXFR).
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getType() {
|
||||||
|
return qtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a timeout on this zone transfer. The default is 900 seconds (15
|
||||||
|
* minutes).
|
||||||
|
*
|
||||||
|
* @param secs The maximum amount of time that this zone transfer can take.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setTimeout(int secs) {
|
||||||
|
if (secs < 0) {
|
||||||
|
throw new IllegalArgumentException("timeout cannot be " + "negative");
|
||||||
|
}
|
||||||
|
timeout = 1000L * secs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an alternate DNS class for this zone transfer.
|
||||||
|
*
|
||||||
|
* @param dclass The class to use instead of class IN.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setDClass(int dclass) {
|
||||||
|
DnsClass.check(dclass);
|
||||||
|
this.dclass = dclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the local address to bind to when sending messages.
|
||||||
|
*
|
||||||
|
* @param addr The local address to send messages from.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setLocalAddress(SocketAddress addr) {
|
||||||
|
this.localAddress = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void openConnection() throws IOException {
|
||||||
|
long endTime = System.currentTimeMillis() + timeout;
|
||||||
|
client = new TCPClient(endTime);
|
||||||
|
if (localAddress != null) {
|
||||||
|
client.bind(localAddress);
|
||||||
|
}
|
||||||
|
client.connect(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void sendQuery() throws IOException {
|
||||||
|
DnsRecord question = DnsRecord.newRecord(zname, qtype, dclass);
|
||||||
|
|
||||||
|
DnsMessage query = new DnsMessage();
|
||||||
|
query.getHeader()
|
||||||
|
.setOpcode(DnsOpCode.QUERY);
|
||||||
|
query.addRecord(question, DnsSection.QUESTION);
|
||||||
|
if (qtype == DnsRecordType.IXFR) {
|
||||||
|
DnsRecord soa = new SOARecord(zname, dclass, 0, Name.root, Name.root, ixfr_serial, 0, 0, 0, 0);
|
||||||
|
query.addRecord(soa, DnsSection.AUTHORITY);
|
||||||
|
}
|
||||||
|
if (tsig != null) {
|
||||||
|
tsig.apply(query, null);
|
||||||
|
verifier = new TSIG.StreamVerifier(tsig, query.getTSIG());
|
||||||
|
}
|
||||||
|
byte[] out = query.toWire(DnsMessage.MAXLENGTH);
|
||||||
|
client.send(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
long getSOASerial(DnsRecord rec) {
|
||||||
|
SOARecord soa = (SOARecord) rec;
|
||||||
|
return soa.getSerial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void logxfr(String s) {
|
||||||
|
if (Options.check("verbose")) {
|
||||||
|
System.out.println(zname + ": " + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void fail(String s) throws ZoneTransferException {
|
||||||
|
throw new ZoneTransferException(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void fallback() throws ZoneTransferException {
|
||||||
|
if (!want_fallback) {
|
||||||
|
fail("server doesn't support IXFR");
|
||||||
|
}
|
||||||
|
|
||||||
|
logxfr("falling back to AXFR");
|
||||||
|
qtype = DnsRecordType.AXFR;
|
||||||
|
state = INITIALSOA;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void parseRR(DnsRecord rec) throws ZoneTransferException {
|
||||||
|
int type = rec.getType();
|
||||||
|
Delta delta;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case INITIALSOA:
|
||||||
|
if (type != DnsRecordType.SOA) {
|
||||||
|
fail("missing initial SOA");
|
||||||
|
}
|
||||||
|
initialsoa = rec;
|
||||||
|
// Remember the serial number in the initial SOA; we need it
|
||||||
|
// to recognize the end of an IXFR.
|
||||||
|
end_serial = getSOASerial(rec);
|
||||||
|
if (qtype == DnsRecordType.IXFR && Serial.compare(end_serial, ixfr_serial) <= 0) {
|
||||||
|
logxfr("up to date");
|
||||||
|
state = END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state = FIRSTDATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FIRSTDATA:
|
||||||
|
// If the transfer begins with 1 SOA, it's an AXFR.
|
||||||
|
// If it begins with 2 SOAs, it's an IXFR.
|
||||||
|
if (qtype == DnsRecordType.IXFR && type == DnsRecordType.SOA && getSOASerial(rec) == ixfr_serial) {
|
||||||
|
rtype = DnsRecordType.IXFR;
|
||||||
|
handler.startIXFR();
|
||||||
|
logxfr("got incremental response");
|
||||||
|
state = IXFR_DELSOA;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rtype = DnsRecordType.AXFR;
|
||||||
|
handler.startAXFR();
|
||||||
|
handler.handleRecord(initialsoa);
|
||||||
|
logxfr("got nonincremental response");
|
||||||
|
state = AXFR;
|
||||||
|
}
|
||||||
|
parseRR(rec); // Restart...
|
||||||
|
return;
|
||||||
|
|
||||||
|
case IXFR_DELSOA:
|
||||||
|
handler.startIXFRDeletes(rec);
|
||||||
|
state = IXFR_DEL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IXFR_DEL:
|
||||||
|
if (type == DnsRecordType.SOA) {
|
||||||
|
current_serial = getSOASerial(rec);
|
||||||
|
state = IXFR_ADDSOA;
|
||||||
|
parseRR(rec); // Restart...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.handleRecord(rec);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IXFR_ADDSOA:
|
||||||
|
handler.startIXFRAdds(rec);
|
||||||
|
state = IXFR_ADD;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IXFR_ADD:
|
||||||
|
if (type == DnsRecordType.SOA) {
|
||||||
|
long soa_serial = getSOASerial(rec);
|
||||||
|
if (soa_serial == end_serial) {
|
||||||
|
state = END;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (soa_serial != current_serial) {
|
||||||
|
fail("IXFR out of sync: expected serial " + current_serial + " , got " + soa_serial);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state = IXFR_DELSOA;
|
||||||
|
parseRR(rec); // Restart...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handler.handleRecord(rec);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AXFR:
|
||||||
|
// Old BINDs sent cross class A records for non IN classes.
|
||||||
|
if (type == DnsRecordType.A && rec.getDClass() != dclass) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
handler.handleRecord(rec);
|
||||||
|
if (type == DnsRecordType.SOA) {
|
||||||
|
state = END;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case END:
|
||||||
|
fail("extra data");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fail("invalid state");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void closeConnection() {
|
||||||
|
try {
|
||||||
|
if (client != null) {
|
||||||
|
client.cleanup();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
DnsMessage parseMessage(byte[] b) throws WireParseException {
|
||||||
|
try {
|
||||||
|
return new DnsMessage(b);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (e instanceof WireParseException) {
|
||||||
|
throw (WireParseException) e;
|
||||||
|
}
|
||||||
|
throw new WireParseException("Error parsing message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void doxfr() throws IOException, ZoneTransferException {
|
||||||
|
sendQuery();
|
||||||
|
while (state != END) {
|
||||||
|
byte[] in = client.recv();
|
||||||
|
DnsMessage response = parseMessage(in);
|
||||||
|
if (response.getHeader()
|
||||||
|
.getRcode() == DnsResponseCode.NOERROR && verifier != null) {
|
||||||
|
TSIGRecord tsigrec = response.getTSIG();
|
||||||
|
|
||||||
|
int error = verifier.verify(response, in);
|
||||||
|
if (error != DnsResponseCode.NOERROR) {
|
||||||
|
fail("TSIG failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsRecord[] answers = response.getSectionArray(DnsSection.ANSWER);
|
||||||
|
|
||||||
|
if (state == INITIALSOA) {
|
||||||
|
int rcode = response.getRcode();
|
||||||
|
if (rcode != DnsResponseCode.NOERROR) {
|
||||||
|
if (qtype == DnsRecordType.IXFR && rcode == DnsResponseCode.NOTIMP) {
|
||||||
|
fallback();
|
||||||
|
doxfr();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail(DnsResponseCode.string(rcode));
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsRecord question = response.getQuestion();
|
||||||
|
if (question != null && question.getType() != qtype) {
|
||||||
|
fail("invalid question section");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answers.length == 0 && qtype == DnsRecordType.IXFR) {
|
||||||
|
fallback();
|
||||||
|
doxfr();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < answers.length; i++) {
|
||||||
|
parseRR(answers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == END && verifier != null && !response.isVerified()) {
|
||||||
|
fail("last message must be signed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the zone transfer.
|
||||||
|
*
|
||||||
|
* @param handler The callback object that handles the zone transfer data.
|
||||||
|
*
|
||||||
|
* @throws IOException The zone transfer failed to due an IO problem.
|
||||||
|
* @throws ZoneTransferException The zone transfer failed to due a problem
|
||||||
|
* with the zone transfer itself.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void run(ZoneTransferHandler handler) throws IOException, ZoneTransferException {
|
||||||
|
this.handler = handler;
|
||||||
|
try {
|
||||||
|
openConnection();
|
||||||
|
doxfr();
|
||||||
|
} finally {
|
||||||
|
closeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the zone transfer.
|
||||||
|
*
|
||||||
|
* @return A list, which is either an AXFR-style response (List of Records),
|
||||||
|
* and IXFR-style response (List of Deltas), or null, which indicates that
|
||||||
|
* an IXFR was performed and the zone is up to date.
|
||||||
|
*
|
||||||
|
* @throws IOException The zone transfer failed to due an IO problem.
|
||||||
|
* @throws ZoneTransferException The zone transfer failed to due a problem
|
||||||
|
* with the zone transfer itself.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
List run() throws IOException, ZoneTransferException {
|
||||||
|
BasicHandler handler = new BasicHandler();
|
||||||
|
run(handler);
|
||||||
|
if (handler.axfr != null) {
|
||||||
|
return handler.axfr;
|
||||||
|
}
|
||||||
|
return handler.ixfr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the response is an AXFR-style response (List of Records).
|
||||||
|
* This will be true if either an IXFR was performed, an IXFR was performed
|
||||||
|
* and the server provided a full zone transfer, or an IXFR failed and
|
||||||
|
* fallback to AXFR occurred.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean isAXFR() {
|
||||||
|
return (rtype == DnsRecordType.AXFR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the AXFR-style response.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The transfer used the callback interface,
|
||||||
|
* so the response was not stored.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
List getAXFR() {
|
||||||
|
BasicHandler handler = getBasicHandler();
|
||||||
|
return handler.axfr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
BasicHandler getBasicHandler() throws IllegalArgumentException {
|
||||||
|
if (handler instanceof BasicHandler) {
|
||||||
|
return (BasicHandler) handler;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("ZoneTransferIn used callback " + "interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the response is an IXFR-style response (List of Deltas).
|
||||||
|
* This will be true only if an IXFR was performed and the server provided
|
||||||
|
* an incremental zone transfer.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean isIXFR() {
|
||||||
|
return (rtype == DnsRecordType.IXFR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the IXFR-style response.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The transfer used the callback interface,
|
||||||
|
* so the response was not stored.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
List getIXFR() {
|
||||||
|
BasicHandler handler = getBasicHandler();
|
||||||
|
return handler.ixfr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the response indicates that the zone is up to date.
|
||||||
|
* This will be true only if an IXFR was performed.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException The transfer used the callback interface,
|
||||||
|
* so the response was not stored.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean isCurrent() {
|
||||||
|
BasicHandler handler = getBasicHandler();
|
||||||
|
return (handler.axfr == null && handler.ixfr == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.clients;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A routine to produce a nice looking hex dump
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class hexdump {
|
||||||
|
|
||||||
|
private static final char[] hex = "0123456789ABCDEF".toCharArray();
|
||||||
|
|
||||||
|
public static
|
||||||
|
String dump(String s, byte[] b) {
|
||||||
|
return dump(s, b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps a byte array into hex format.
|
||||||
|
*
|
||||||
|
* @param description If not null, a description of the data.
|
||||||
|
* @param b The data to be printed.
|
||||||
|
* @param offset The start of the data in the array.
|
||||||
|
* @param length The length of the data in the array.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String dump(String description, byte[] b, int offset, int length) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(length)
|
||||||
|
.append("b");
|
||||||
|
|
||||||
|
if (description != null) {
|
||||||
|
sb.append(" (")
|
||||||
|
.append(description)
|
||||||
|
.append(")");
|
||||||
|
}
|
||||||
|
sb.append(':');
|
||||||
|
|
||||||
|
int prefixlen = sb.toString()
|
||||||
|
.length();
|
||||||
|
prefixlen = (prefixlen + 8) & ~7;
|
||||||
|
sb.append('\t');
|
||||||
|
|
||||||
|
int perline = (80 - prefixlen) / 3;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
if (i != 0 && i % perline == 0) {
|
||||||
|
sb.append('\n');
|
||||||
|
for (int j = 0; j < prefixlen / 8; j++) {
|
||||||
|
sb.append('\t');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int value = (int) (b[i + offset]) & 0xFF;
|
||||||
|
sb.append(hex[(value >> 4)]);
|
||||||
|
sb.append(hex[(value & 0xF)]);
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,970 @@
|
||||||
|
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
|
||||||
|
|
||||||
|
package org.xbill.DNS2.resolver;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.xbill.DNS2.clients.Master;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.constants.DnsClass;
|
||||||
|
import dorkbox.network.dns.constants.DnsRecordType;
|
||||||
|
import dorkbox.network.dns.constants.DnsResponseCode;
|
||||||
|
import dorkbox.network.dns.constants.DnsSection;
|
||||||
|
import dorkbox.network.dns.constants.Flags;
|
||||||
|
import dorkbox.network.dns.exceptions.NameTooLongException;
|
||||||
|
import dorkbox.network.dns.records.CNAMERecord;
|
||||||
|
import dorkbox.network.dns.records.DNAMERecord;
|
||||||
|
import dorkbox.network.dns.records.DnsMessage;
|
||||||
|
import dorkbox.network.dns.records.DnsRecord;
|
||||||
|
import dorkbox.network.dns.records.RRset;
|
||||||
|
import dorkbox.network.dns.records.SOARecord;
|
||||||
|
import dorkbox.network.dns.utils.Options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache of DNS records. The cache obeys TTLs, so items are purged after
|
||||||
|
* their validity period is complete. Negative answers are cached, to
|
||||||
|
* avoid repeated failed DNS queries. The credibility of each RRset is
|
||||||
|
* maintained, so that more credible records replace less credible records,
|
||||||
|
* and lookups can specify the minimum credibility of data they are requesting.
|
||||||
|
*
|
||||||
|
* @author Brian Wellington
|
||||||
|
* @see RRset
|
||||||
|
* @see Credibility
|
||||||
|
*/
|
||||||
|
|
||||||
|
public
|
||||||
|
class Cache {
|
||||||
|
|
||||||
|
private CacheMap data;
|
||||||
|
private int maxncache = -1;
|
||||||
|
private int maxcache = -1;
|
||||||
|
private int dclass;
|
||||||
|
private static final int defaultMaxEntries = 50000;
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
interface Element {
|
||||||
|
public
|
||||||
|
boolean expired();
|
||||||
|
|
||||||
|
public
|
||||||
|
int compareCredibility(int cred);
|
||||||
|
|
||||||
|
public
|
||||||
|
int getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
int limitExpire(long ttl, long maxttl) {
|
||||||
|
if (maxttl >= 0 && maxttl < ttl) {
|
||||||
|
ttl = maxttl;
|
||||||
|
}
|
||||||
|
long expire = (System.currentTimeMillis() / 1000) + ttl;
|
||||||
|
if (expire < 0 || expire > Integer.MAX_VALUE) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return (int) expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
class CacheRRset extends RRset implements Element {
|
||||||
|
private static final long serialVersionUID = 5971755205903597024L;
|
||||||
|
|
||||||
|
int credibility;
|
||||||
|
int expire;
|
||||||
|
|
||||||
|
public
|
||||||
|
CacheRRset(DnsRecord rec, int cred, long maxttl) {
|
||||||
|
super();
|
||||||
|
this.credibility = cred;
|
||||||
|
this.expire = limitExpire(rec.getTTL(), maxttl);
|
||||||
|
addRR(rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
CacheRRset(RRset rrset, int cred, long maxttl) {
|
||||||
|
super(rrset);
|
||||||
|
this.credibility = cred;
|
||||||
|
this.expire = limitExpire(rrset.getTTL(), maxttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final
|
||||||
|
boolean expired() {
|
||||||
|
int now = (int) (System.currentTimeMillis() / 1000);
|
||||||
|
return (now >= expire);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(super.toString());
|
||||||
|
sb.append(" cl = ");
|
||||||
|
sb.append(credibility);
|
||||||
|
return sb.toString();
|
||||||
|
} @Override
|
||||||
|
public final
|
||||||
|
int compareCredibility(int cred) {
|
||||||
|
return credibility - cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
class NegativeElement implements Element {
|
||||||
|
int type;
|
||||||
|
Name name;
|
||||||
|
int credibility;
|
||||||
|
int expire;
|
||||||
|
|
||||||
|
public
|
||||||
|
NegativeElement(Name name, int type, SOARecord soa, int cred, long maxttl) {
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
long cttl = 0;
|
||||||
|
if (soa != null) {
|
||||||
|
cttl = soa.getMinimum();
|
||||||
|
}
|
||||||
|
this.credibility = cred;
|
||||||
|
this.expire = limitExpire(cttl, maxttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (type == 0) {
|
||||||
|
sb.append("NXDOMAIN ")
|
||||||
|
.append(name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append("NXRRSET ")
|
||||||
|
.append(name)
|
||||||
|
.append(" ")
|
||||||
|
.append(DnsRecordType.string(type));
|
||||||
|
}
|
||||||
|
sb.append(" cl = ");
|
||||||
|
sb.append(credibility);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final
|
||||||
|
boolean expired() {
|
||||||
|
int now = (int) (System.currentTimeMillis() / 1000);
|
||||||
|
return (now >= expire);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final
|
||||||
|
int compareCredibility(int cred) {
|
||||||
|
return credibility - cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
class CacheMap extends LinkedHashMap {
|
||||||
|
private int maxsize = -1;
|
||||||
|
|
||||||
|
CacheMap(int maxsize) {
|
||||||
|
super(16, (float) 0.75, true);
|
||||||
|
this.maxsize = maxsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getMaxSize() {
|
||||||
|
return maxsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMaxSize(int maxsize) {
|
||||||
|
/*
|
||||||
|
* Note that this doesn't shrink the size of the map if
|
||||||
|
* the maximum size is lowered, but it should shrink as
|
||||||
|
* entries expire.
|
||||||
|
*/
|
||||||
|
this.maxsize = maxsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
boolean removeEldestEntry(Map.Entry eldest) {
|
||||||
|
return maxsize >= 0 && size() > maxsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty Cache for class IN.
|
||||||
|
*
|
||||||
|
* @see DnsClass
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Cache() {
|
||||||
|
this(DnsClass.IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty Cache
|
||||||
|
*
|
||||||
|
* @param dclass The DNS class of this cache
|
||||||
|
*
|
||||||
|
* @see DnsClass
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Cache(int dclass) {
|
||||||
|
this.dclass = dclass;
|
||||||
|
data = new CacheMap(defaultMaxEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Cache which initially contains all records in the specified file.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
Cache(String file) throws IOException {
|
||||||
|
data = new CacheMap(defaultMaxEntries);
|
||||||
|
Master m = new Master(file);
|
||||||
|
DnsRecord record;
|
||||||
|
while ((record = m.nextRecord()) != null) {
|
||||||
|
addRecord(record, Credibility.HINT, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
Object exactName(Name name) {
|
||||||
|
return data.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
Element findElement(Name name, int type, int minCred) {
|
||||||
|
Object types = exactName(name);
|
||||||
|
if (types == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return oneElement(name, types, type, minCred);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
void addElement(Name name, Element element) {
|
||||||
|
Object types = data.get(name);
|
||||||
|
if (types == null) {
|
||||||
|
data.put(name, element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int type = element.getType();
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
Element elt = (Element) list.get(i);
|
||||||
|
if (elt.getType() == type) {
|
||||||
|
list.set(i, element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(element);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Element elt = (Element) types;
|
||||||
|
if (elt.getType() == type) {
|
||||||
|
data.put(name, element);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LinkedList list = new LinkedList();
|
||||||
|
list.add(elt);
|
||||||
|
list.add(element);
|
||||||
|
data.put(name, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empties the Cache.
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
void clearCache() {
|
||||||
|
data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a record to the Cache.
|
||||||
|
*
|
||||||
|
* @param r The record to be added
|
||||||
|
* @param cred The credibility of the record
|
||||||
|
* @param o The source of the record (this could be a DnsMessage, for example)
|
||||||
|
*
|
||||||
|
* @see DnsRecord
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
void addRecord(DnsRecord r, int cred, Object o) {
|
||||||
|
Name name = r.getName();
|
||||||
|
int type = r.getRRsetType();
|
||||||
|
if (!DnsRecordType.isRR(type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Element element = findElement(name, type, cred);
|
||||||
|
if (element == null) {
|
||||||
|
CacheRRset crrset = new CacheRRset(r, cred, maxcache);
|
||||||
|
addRRset(crrset, cred);
|
||||||
|
}
|
||||||
|
else if (element.compareCredibility(cred) == 0) {
|
||||||
|
if (element instanceof CacheRRset) {
|
||||||
|
CacheRRset crrset = (CacheRRset) element;
|
||||||
|
crrset.addRR(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an RRset to the Cache.
|
||||||
|
*
|
||||||
|
* @param rrset The RRset to be added
|
||||||
|
* @param cred The credibility of these records
|
||||||
|
*
|
||||||
|
* @see RRset
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
void addRRset(RRset rrset, int cred) {
|
||||||
|
long ttl = rrset.getTTL();
|
||||||
|
Name name = rrset.getName();
|
||||||
|
int type = rrset.getType();
|
||||||
|
Element element = findElement(name, type, 0);
|
||||||
|
if (ttl == 0) {
|
||||||
|
if (element != null && element.compareCredibility(cred) <= 0) {
|
||||||
|
removeElement(name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (element != null && element.compareCredibility(cred) <= 0) {
|
||||||
|
element = null;
|
||||||
|
}
|
||||||
|
if (element == null) {
|
||||||
|
CacheRRset crrset;
|
||||||
|
if (rrset instanceof CacheRRset) {
|
||||||
|
crrset = (CacheRRset) rrset;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
crrset = new CacheRRset(rrset, cred, maxcache);
|
||||||
|
}
|
||||||
|
addElement(name, crrset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a negative entry to the Cache.
|
||||||
|
*
|
||||||
|
* @param name The name of the negative entry
|
||||||
|
* @param type The type of the negative entry
|
||||||
|
* @param soa The SOA record to add to the negative cache entry, or null.
|
||||||
|
* The negative cache ttl is derived from the SOA.
|
||||||
|
* @param cred The credibility of the negative entry
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
void addNegative(Name name, int type, SOARecord soa, int cred) {
|
||||||
|
long ttl = 0;
|
||||||
|
if (soa != null) {
|
||||||
|
ttl = soa.getTTL();
|
||||||
|
}
|
||||||
|
Element element = findElement(name, type, 0);
|
||||||
|
if (ttl == 0) {
|
||||||
|
if (element != null && element.compareCredibility(cred) <= 0) {
|
||||||
|
removeElement(name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (element != null && element.compareCredibility(cred) <= 0) {
|
||||||
|
element = null;
|
||||||
|
}
|
||||||
|
if (element == null) {
|
||||||
|
addElement(name, new NegativeElement(name, type, soa, cred, maxncache));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up credible Records in the Cache (a wrapper around lookupRecords).
|
||||||
|
* Unlike lookupRecords, this given no indication of why failure occurred.
|
||||||
|
*
|
||||||
|
* @param name The name to look up
|
||||||
|
* @param type The type to look up
|
||||||
|
*
|
||||||
|
* @return An array of RRsets, or null
|
||||||
|
*
|
||||||
|
* @see Credibility
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
RRset[] findRecords(Name name, int type) {
|
||||||
|
return findRecords(name, type, Credibility.NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
RRset[] findRecords(Name name, int type, int minCred) {
|
||||||
|
SetResponse cr = lookupRecords(name, type, minCred);
|
||||||
|
if (cr.isSuccessful()) {
|
||||||
|
return cr.answers();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up Records in the Cache. This follows CNAMEs and handles negatively
|
||||||
|
* cached data.
|
||||||
|
*
|
||||||
|
* @param name The name to look up
|
||||||
|
* @param type The type to look up
|
||||||
|
* @param minCred The minimum acceptable credibility
|
||||||
|
*
|
||||||
|
* @return A SetResponse object
|
||||||
|
*
|
||||||
|
* @see SetResponse
|
||||||
|
* @see Credibility
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
SetResponse lookupRecords(Name name, int type, int minCred) {
|
||||||
|
return lookup(name, type, minCred);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all matching sets or something that causes the lookup to stop.
|
||||||
|
*/
|
||||||
|
protected synchronized
|
||||||
|
SetResponse lookup(Name name, int type, int minCred) {
|
||||||
|
int labels;
|
||||||
|
int tlabels;
|
||||||
|
Element element;
|
||||||
|
Name tname;
|
||||||
|
Object types;
|
||||||
|
SetResponse sr;
|
||||||
|
|
||||||
|
labels = name.labels();
|
||||||
|
|
||||||
|
for (tlabels = labels; tlabels >= 1; tlabels--) {
|
||||||
|
boolean isRoot = (tlabels == 1);
|
||||||
|
boolean isExact = (tlabels == labels);
|
||||||
|
|
||||||
|
if (isRoot) {
|
||||||
|
tname = Name.root;
|
||||||
|
}
|
||||||
|
else if (isExact) {
|
||||||
|
tname = name;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tname = new Name(name, labels - tlabels);
|
||||||
|
}
|
||||||
|
|
||||||
|
types = data.get(tname);
|
||||||
|
if (types == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If this is the name, look for the actual type or a CNAME
|
||||||
|
* (unless it's an ANY query, where we return everything).
|
||||||
|
* Otherwise, look for a DNAME.
|
||||||
|
*/
|
||||||
|
if (isExact && type == DnsRecordType.ANY) {
|
||||||
|
sr = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
Element[] elements = allElements(types);
|
||||||
|
int added = 0;
|
||||||
|
for (int i = 0; i < elements.length; i++) {
|
||||||
|
element = elements[i];
|
||||||
|
if (element.expired()) {
|
||||||
|
removeElement(tname, element.getType());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(element instanceof CacheRRset)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (element.compareCredibility(minCred) < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sr.addRRset((CacheRRset) element);
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
/* There were positive entries */
|
||||||
|
if (added > 0) {
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isExact) {
|
||||||
|
element = oneElement(tname, types, type, minCred);
|
||||||
|
if (element != null && element instanceof CacheRRset) {
|
||||||
|
sr = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
sr.addRRset((CacheRRset) element);
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
else if (element != null) {
|
||||||
|
sr = new SetResponse(SetResponse.NXRRSET);
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
|
||||||
|
element = oneElement(tname, types, DnsRecordType.CNAME, minCred);
|
||||||
|
if (element != null && element instanceof CacheRRset) {
|
||||||
|
return new SetResponse(SetResponse.CNAME, (CacheRRset) element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
element = oneElement(tname, types, DnsRecordType.DNAME, minCred);
|
||||||
|
if (element != null && element instanceof CacheRRset) {
|
||||||
|
return new SetResponse(SetResponse.DNAME, (CacheRRset) element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for an NS */
|
||||||
|
element = oneElement(tname, types, DnsRecordType.NS, minCred);
|
||||||
|
if (element != null && element instanceof CacheRRset) {
|
||||||
|
return new SetResponse(SetResponse.DELEGATION, (CacheRRset) element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for the special NXDOMAIN element. */
|
||||||
|
if (isExact) {
|
||||||
|
element = oneElement(tname, types, 0, minCred);
|
||||||
|
if (element != null) {
|
||||||
|
return SetResponse.ofType(SetResponse.NXDOMAIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return SetResponse.ofType(SetResponse.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
Element[] allElements(Object types) {
|
||||||
|
if (types instanceof List) {
|
||||||
|
List typelist = (List) types;
|
||||||
|
int size = typelist.size();
|
||||||
|
return (Element[]) typelist.toArray(new Element[size]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Element set = (Element) types;
|
||||||
|
return new Element[] {set};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
Element oneElement(Name name, Object types, int type, int minCred) {
|
||||||
|
Element found = null;
|
||||||
|
|
||||||
|
if (type == DnsRecordType.ANY) {
|
||||||
|
throw new IllegalArgumentException("oneElement(ANY)");
|
||||||
|
}
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
Element set = (Element) list.get(i);
|
||||||
|
if (set.getType() == type) {
|
||||||
|
found = set;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Element set = (Element) types;
|
||||||
|
if (set.getType() == type) {
|
||||||
|
found = set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (found.expired()) {
|
||||||
|
removeElement(name, type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (found.compareCredibility(minCred) < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
void removeElement(Name name, int type) {
|
||||||
|
Object types = data.get(name);
|
||||||
|
if (types == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (types instanceof List) {
|
||||||
|
List list = (List) types;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
Element elt = (Element) list.get(i);
|
||||||
|
if (elt.getType() == type) {
|
||||||
|
list.remove(i);
|
||||||
|
if (list.size() == 0) {
|
||||||
|
data.remove(name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Element elt = (Element) types;
|
||||||
|
if (elt.getType() != type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up Records in the Cache (a wrapper around lookupRecords). Unlike
|
||||||
|
* lookupRecords, this given no indication of why failure occurred.
|
||||||
|
*
|
||||||
|
* @param name The name to look up
|
||||||
|
* @param type The type to look up
|
||||||
|
*
|
||||||
|
* @return An array of RRsets, or null
|
||||||
|
*
|
||||||
|
* @see Credibility
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
RRset[] findAnyRecords(Name name, int type) {
|
||||||
|
return findRecords(name, type, Credibility.GLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
int getCred(int section, boolean isAuth) {
|
||||||
|
if (section == DnsSection.ANSWER) {
|
||||||
|
if (isAuth) {
|
||||||
|
return Credibility.AUTH_ANSWER;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Credibility.NONAUTH_ANSWER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (section == DnsSection.AUTHORITY) {
|
||||||
|
if (isAuth) {
|
||||||
|
return Credibility.AUTH_AUTHORITY;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Credibility.NONAUTH_AUTHORITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (section == DnsSection.ADDITIONAL) {
|
||||||
|
return Credibility.ADDITIONAL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException("getCred: invalid section");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
void markAdditional(RRset rrset, Set names) {
|
||||||
|
DnsRecord first = rrset.first();
|
||||||
|
if (first.getAdditionalName() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator it = rrset.rrs();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
DnsRecord r = (DnsRecord) it.next();
|
||||||
|
Name name = r.getAdditionalName();
|
||||||
|
if (name != null) {
|
||||||
|
names.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all data from a DnsMessage into the Cache. Each record is added with
|
||||||
|
* the appropriate credibility, and negative answers are cached as such.
|
||||||
|
*
|
||||||
|
* @param in The DnsMessage to be added
|
||||||
|
*
|
||||||
|
* @return A SetResponse that reflects what would be returned from a cache
|
||||||
|
* lookup, or null if nothing useful could be cached from the message.
|
||||||
|
*
|
||||||
|
* @see DnsMessage
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
SetResponse addMessage(DnsMessage in) {
|
||||||
|
boolean isAuth = in.getHeader()
|
||||||
|
.getFlag(Flags.AA);
|
||||||
|
DnsRecord question = in.getQuestion();
|
||||||
|
Name qname;
|
||||||
|
Name curname;
|
||||||
|
int qtype;
|
||||||
|
int qclass;
|
||||||
|
int cred;
|
||||||
|
int rcode = in.getHeader()
|
||||||
|
.getRcode();
|
||||||
|
boolean completed = false;
|
||||||
|
RRset[] answers, auth, addl;
|
||||||
|
SetResponse response = null;
|
||||||
|
boolean verbose = Options.check("verbosecache");
|
||||||
|
HashSet additionalNames;
|
||||||
|
|
||||||
|
if ((rcode != DnsResponseCode.NOERROR && rcode != DnsResponseCode.NXDOMAIN) || question == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
qname = question.getName();
|
||||||
|
qtype = question.getType();
|
||||||
|
qclass = question.getDClass();
|
||||||
|
|
||||||
|
curname = qname;
|
||||||
|
|
||||||
|
additionalNames = new HashSet();
|
||||||
|
|
||||||
|
answers = in.getSectionRRsets(DnsSection.ANSWER);
|
||||||
|
for (int i = 0; i < answers.length; i++) {
|
||||||
|
if (answers[i].getDClass() != qclass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int type = answers[i].getType();
|
||||||
|
Name name = answers[i].getName();
|
||||||
|
cred = getCred(DnsSection.ANSWER, isAuth);
|
||||||
|
if ((type == qtype || qtype == DnsRecordType.ANY) && name.equals(curname)) {
|
||||||
|
addRRset(answers[i], cred);
|
||||||
|
completed = true;
|
||||||
|
if (curname == qname) {
|
||||||
|
if (response == null) {
|
||||||
|
response = new SetResponse(SetResponse.SUCCESSFUL);
|
||||||
|
}
|
||||||
|
response.addRRset(answers[i]);
|
||||||
|
}
|
||||||
|
markAdditional(answers[i], additionalNames);
|
||||||
|
}
|
||||||
|
else if (type == DnsRecordType.CNAME && name.equals(curname)) {
|
||||||
|
CNAMERecord cname;
|
||||||
|
addRRset(answers[i], cred);
|
||||||
|
if (curname == qname) {
|
||||||
|
response = new SetResponse(SetResponse.CNAME, answers[i]);
|
||||||
|
}
|
||||||
|
cname = (CNAMERecord) answers[i].first();
|
||||||
|
curname = cname.getTarget();
|
||||||
|
}
|
||||||
|
else if (type == DnsRecordType.DNAME && curname.subdomain(name)) {
|
||||||
|
DNAMERecord dname;
|
||||||
|
addRRset(answers[i], cred);
|
||||||
|
if (curname == qname) {
|
||||||
|
response = new SetResponse(SetResponse.DNAME, answers[i]);
|
||||||
|
}
|
||||||
|
dname = (DNAMERecord) answers[i].first();
|
||||||
|
try {
|
||||||
|
curname = curname.fromDNAME(dname);
|
||||||
|
} catch (NameTooLongException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = in.getSectionRRsets(DnsSection.AUTHORITY);
|
||||||
|
RRset soa = null, ns = null;
|
||||||
|
for (int i = 0; i < auth.length; i++) {
|
||||||
|
if (auth[i].getType() == DnsRecordType.SOA && curname.subdomain(auth[i].getName())) {
|
||||||
|
soa = auth[i];
|
||||||
|
}
|
||||||
|
else if (auth[i].getType() == DnsRecordType.NS && curname.subdomain(auth[i].getName())) {
|
||||||
|
ns = auth[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!completed) {
|
||||||
|
/* This is a negative response or a referral. */
|
||||||
|
int cachetype = (rcode == DnsResponseCode.NXDOMAIN) ? 0 : qtype;
|
||||||
|
if (rcode == DnsResponseCode.NXDOMAIN || soa != null || ns == null) {
|
||||||
|
/* Negative response */
|
||||||
|
cred = getCred(DnsSection.AUTHORITY, isAuth);
|
||||||
|
SOARecord soarec = null;
|
||||||
|
if (soa != null) {
|
||||||
|
soarec = (SOARecord) soa.first();
|
||||||
|
}
|
||||||
|
addNegative(curname, cachetype, soarec, cred);
|
||||||
|
if (response == null) {
|
||||||
|
int responseType;
|
||||||
|
if (rcode == DnsResponseCode.NXDOMAIN) {
|
||||||
|
responseType = SetResponse.NXDOMAIN;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
responseType = SetResponse.NXRRSET;
|
||||||
|
}
|
||||||
|
response = SetResponse.ofType(responseType);
|
||||||
|
}
|
||||||
|
/* DNSSEC records are not cached. */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Referral response */
|
||||||
|
cred = getCred(DnsSection.AUTHORITY, isAuth);
|
||||||
|
addRRset(ns, cred);
|
||||||
|
markAdditional(ns, additionalNames);
|
||||||
|
if (response == null) {
|
||||||
|
response = new SetResponse(SetResponse.DELEGATION, ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rcode == DnsResponseCode.NOERROR && ns != null) {
|
||||||
|
/* Cache the NS set from a positive response. */
|
||||||
|
cred = getCred(DnsSection.AUTHORITY, isAuth);
|
||||||
|
addRRset(ns, cred);
|
||||||
|
markAdditional(ns, additionalNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
addl = in.getSectionRRsets(DnsSection.ADDITIONAL);
|
||||||
|
for (int i = 0; i < addl.length; i++) {
|
||||||
|
int type = addl[i].getType();
|
||||||
|
if (type != DnsRecordType.A && type != DnsRecordType.AAAA && type != DnsRecordType.A6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Name name = addl[i].getName();
|
||||||
|
if (!additionalNames.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cred = getCred(DnsSection.ADDITIONAL, isAuth);
|
||||||
|
addRRset(addl[i], cred);
|
||||||
|
}
|
||||||
|
if (verbose) {
|
||||||
|
System.out.println("addMessage: " + response);
|
||||||
|
}
|
||||||
|
return (response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes an RRset from the cache
|
||||||
|
*
|
||||||
|
* @param name The name of the records to be flushed
|
||||||
|
* @param type The type of the records to be flushed
|
||||||
|
*
|
||||||
|
* @see RRset
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void flushSet(Name name, int type) {
|
||||||
|
removeElement(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes all RRsets with a given name from the cache
|
||||||
|
*
|
||||||
|
* @param name The name of the records to be flushed
|
||||||
|
*
|
||||||
|
* @see RRset
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void flushName(Name name) {
|
||||||
|
removeName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized
|
||||||
|
void removeName(Name name) {
|
||||||
|
data.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum length of time that a negative response will be stored
|
||||||
|
* in this Cache. A negative value indicates no limit.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getMaxNCache() {
|
||||||
|
return maxncache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum length of time that a negative response will be stored
|
||||||
|
* in this Cache. A negative value disables this feature (that is, sets
|
||||||
|
* no limit).
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setMaxNCache(int seconds) {
|
||||||
|
maxncache = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum length of time that records will be stored
|
||||||
|
* in this Cache. A negative value indicates no limit.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getMaxCache() {
|
||||||
|
return maxcache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum length of time that records will be stored in this
|
||||||
|
* Cache. A negative value disables this feature (that is, sets no limit).
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setMaxCache(int seconds) {
|
||||||
|
maxcache = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current number of entries in the Cache, where an entry consists
|
||||||
|
* of all records with a specific Name.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getSize() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum number of entries in the Cache, where an entry consists
|
||||||
|
* of all records with a specific Name. A negative value is treated as an
|
||||||
|
* infinite limit.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getMaxEntries() {
|
||||||
|
return data.getMaxSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of entries in the Cache, where an entry consists
|
||||||
|
* of all records with a specific Name. A negative value is treated as an
|
||||||
|
* infinite limit.
|
||||||
|
* <p>
|
||||||
|
* Note that setting this to a value lower than the current number
|
||||||
|
* of entries will not cause the Cache to shrink immediately.
|
||||||
|
* <p>
|
||||||
|
* The default maximum number of entries is 50000.
|
||||||
|
*
|
||||||
|
* @param entries The maximum number of entries in the Cache.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setMaxEntries(int entries) {
|
||||||
|
data.setMaxSize(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DNS class of this cache.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
int getDClass() {
|
||||||
|
return dclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of the Cache as a string.
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
synchronized (this) {
|
||||||
|
Iterator it = data.values()
|
||||||
|
.iterator();
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Element[] elements = allElements(it.next());
|
||||||
|
for (int i = 0; i < elements.length; i++) {
|
||||||
|
sb.append(elements[i]);
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue