Removed coroutines from parts of the networking message processing.

connection_type_change
Robinson 2022-07-27 00:20:34 +02:00
parent 8d73a3018a
commit 5b18e16d89
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
163 changed files with 25902 additions and 0 deletions

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.chain;
public interface Chain<CTX, R extends ChainResult> {
R execute(CTX context);
}

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.chain;
public interface ChainResult {
boolean hasNext();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
package org.handwerkszeug.dns;
public interface ResolveContext {
DNSMessage request();
DNSMessage response();
Response resolve(Name qname, RRType qtype);
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -0,0 +1,11 @@
package org.handwerkszeug.dns;
/**
* @author taichi
*/
public interface Response {
RCode rcode();
void postProcess(ResolveContext context);
}

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
package org.handwerkszeug.dns;
public interface Zone {
Name name();
DNSClass dnsClass();
ZoneType type();
Response find(Name qname, RRType qtype);
}

View File

@ -0,0 +1,7 @@
package org.handwerkszeug.dns;
public enum ZoneType {
master, slave, stub, forward, rootHint;
}

View File

@ -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.
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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(' ');
}
}
}

View File

@ -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;
}
}

View File

@ -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.
}
}

View File

@ -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);
}

View File

@ -0,0 +1,10 @@
package org.handwerkszeug.dns.conf;
public interface MasterDataResource {
void initialize(ServerConfiguration conf);
void process(MasterDataHandler processor);
void dispose();
}

View File

@ -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());
}
}
}
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -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]);
}
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
RFC4741 NETCONF Configuration Protocol
RFC5381 Experience of Implementing NETCONF over SOAP
そのものを利用する事は無くても、方向性として考慮する必要はありそう。
ここに書いてある程度の事は出来なければ、別解を用意する意味は無い。

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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";
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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>
* &lt;character-string&gt; is a single length octet followed by that number
* of characters. &lt;character-string&gt; 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();
}
}

View File

@ -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 ?
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,9 @@
package org.handwerkszeug.dns.server;
public
class DNSCacheEntry {
protected long expiration;
}

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -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);
// }
// }
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 ?
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.util;
public interface VariableEnum {
int value();
}

View File

@ -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;
}
}

View File

@ -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 });
}
}
}

View File

@ -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 });
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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