diff --git a/src-wip/org/handwerkszeug/chain/Chain.java b/src-wip/org/handwerkszeug/chain/Chain.java new file mode 100755 index 0000000..940c110 --- /dev/null +++ b/src-wip/org/handwerkszeug/chain/Chain.java @@ -0,0 +1,6 @@ +package org.handwerkszeug.chain; + +public interface Chain { + + R execute(CTX context); +} diff --git a/src-wip/org/handwerkszeug/chain/ChainResult.java b/src-wip/org/handwerkszeug/chain/ChainResult.java new file mode 100755 index 0000000..34dc25e --- /dev/null +++ b/src-wip/org/handwerkszeug/chain/ChainResult.java @@ -0,0 +1,6 @@ +package org.handwerkszeug.chain; + +public interface ChainResult { + + boolean hasNext(); +} diff --git a/src-wip/org/handwerkszeug/chain/impl/DefaultChainExecutor.java b/src-wip/org/handwerkszeug/chain/impl/DefaultChainExecutor.java new file mode 100755 index 0000000..ba1efd5 --- /dev/null +++ b/src-wip/org/handwerkszeug/chain/impl/DefaultChainExecutor.java @@ -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 implements + Chain { + + protected List> chains = new ArrayList>(); + + @Override + public R execute(CTX context) { + R r = null; + for (Chain c : this.chains) { + r = c.execute(context); + if (r.hasNext() == false) { + break; + } + } + return r; + } + + public void add(Chain c) { + this.chains.add(c); + } +} diff --git a/src-wip/org/handwerkszeug/chain/impl/SimpleChainResult.java b/src-wip/org/handwerkszeug/chain/impl/SimpleChainResult.java new file mode 100755 index 0000000..8889155 --- /dev/null +++ b/src-wip/org/handwerkszeug/chain/impl/SimpleChainResult.java @@ -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; + } + +} diff --git a/src-wip/org/handwerkszeug/dns/Constants.java b/src-wip/org/handwerkszeug/dns/Constants.java new file mode 100755 index 0000000..bcb0a93 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Constants.java @@ -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; +} diff --git a/src-wip/org/handwerkszeug/dns/DNSClass.java b/src-wip/org/handwerkszeug/dns/DNSClass.java new file mode 100755 index 0000000..479eeb2 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/DNSClass.java @@ -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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/DNSMessage.java b/src-wip/org/handwerkszeug/dns/DNSMessage.java new file mode 100755 index 0000000..ea52175 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/DNSMessage.java @@ -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 + * + *
+ *     +---------------------+
+ *     |        Header       |
+ *     +---------------------+
+ *     |       Question      | the question for the name server
+ *     +---------------------+
+ *     |        Answer       | RRs answering the question
+ *     +---------------------+
+ *     |      Authority      | RRs pointing toward an authority
+ *     +---------------------+
+ *     |      Additional     | RRs holding additional information
+ *     +---------------------+
+ * 
+ * + * @author taichi + */ +public class DNSMessage { + + protected Header header; + protected List question; + protected List answer; + protected List authority; + protected List additional; + + protected int messageSize; + + public DNSMessage(Header header) { + this.header(header); + this.question = new ArrayList(); + this.answer = new ArrayList(); + this.authority = new ArrayList(); + this.additional = new ArrayList(); + } + + 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(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 parse(ByteBuf buffer, int size) { + if (size < 1) { + return Collections.emptyList(); + } + + List result = new ArrayList(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 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 question() { + return this.question; + } + + public void question(List list) { + this.question = list; + } + + public List answer() { + return this.answer; + } + + public void answer(List list) { + this.answer = list; + } + + public List authority() { + return this.authority; + } + + public void authority(List list) { + this.authority = list; + } + + public List additional() { + return this.additional; + } + + public void additional(List list) { + this.additional = list; + } + + public int messageSize() { + return this.messageSize; + } + + public void messageSize(int size) { + this.messageSize = size; + } +} diff --git a/src-wip/org/handwerkszeug/dns/Header.java b/src-wip/org/handwerkszeug/dns/Header.java new file mode 100755 index 0000000..e259409 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Header.java @@ -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 + * + *
+ *                                   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                    |
+ *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * 
+ */ +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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/Markers.java b/src-wip/org/handwerkszeug/dns/Markers.java new file mode 100755 index 0000000..82ebd30 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Markers.java @@ -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); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/Name.java b/src-wip/org/handwerkszeug/dns/Name.java new file mode 100755 index 0000000..94da8b1 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Name.java @@ -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 { + + 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 l = new ArrayList(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 name; + + public Name(ByteBuf buffer) { + this.name = this.parse(buffer); + } + + public Name(String name) { + this.name = this.parse(name); + } + + protected Name(List rawdata) { + this.name = rawdata; + } + + protected List parse(ByteBuf buffer) { + List list = new ArrayList(); + 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 + * + *
+	 * \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.
+	 * 
+ * + * @param namedata + * @return + */ + protected List parse(String namedata) { + if (".".equals(namedata)) { + return NULL_NAME.name; + } + // TODO IDN support from RFC3490 RFC3491 RFC3492 RFC3454 + List result = new ArrayList(); + 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 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 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 newone = new ArrayList(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 newone = new ArrayList(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 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(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/NameCompressor.java b/src-wip/org/handwerkszeug/dns/NameCompressor.java new file mode 100755 index 0000000..44e539b --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/NameCompressor.java @@ -0,0 +1,25 @@ +package org.handwerkszeug.dns; + + +/** + * 3.3. Standard RRs + *

+ * 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. + *

+ * + * 4.1.4. DnsMessage compression + * + * @author taichi + * + */ +public interface NameCompressor { + + void put(Name name, int offset); + + int get(Name name); + +} diff --git a/src-wip/org/handwerkszeug/dns/NameServerContainer.java b/src-wip/org/handwerkszeug/dns/NameServerContainer.java new file mode 100755 index 0000000..da75a34 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/NameServerContainer.java @@ -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 nameservers(); +} diff --git a/src-wip/org/handwerkszeug/dns/NameServerContainerProvider.java b/src-wip/org/handwerkszeug/dns/NameServerContainerProvider.java new file mode 100755 index 0000000..e92b8ba --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/NameServerContainerProvider.java @@ -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 containers = new HashMap(); + + @Override + public void initialize() { + initialize(Thread.currentThread().getContextClassLoader()); + } + + public void initialize(ClassLoader classLoader) { + ServiceLoader 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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/NullNameCompressor.java b/src-wip/org/handwerkszeug/dns/NullNameCompressor.java new file mode 100755 index 0000000..5c17e3b --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/NullNameCompressor.java @@ -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; + } + +} diff --git a/src-wip/org/handwerkszeug/dns/OpCode.java b/src-wip/org/handwerkszeug/dns/OpCode.java new file mode 100755 index 0000000..f033ad3 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/OpCode.java @@ -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 Domain Name + * System (DNS) Parameters + */ +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); + } +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/dns/RCode.java b/src-wip/org/handwerkszeug/dns/RCode.java new file mode 100755 index 0000000..194addd --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/RCode.java @@ -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 Domain Name + * System (DNS) Parameters + */ +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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/RRType.java b/src-wip/org/handwerkszeug/dns/RRType.java new file mode 100755 index 0000000..d20c76f --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/RRType.java @@ -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 Domain Name + * System (DNS) Parameters + */ +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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/ResolveContext.java b/src-wip/org/handwerkszeug/dns/ResolveContext.java new file mode 100755 index 0000000..012201e --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/ResolveContext.java @@ -0,0 +1,10 @@ +package org.handwerkszeug.dns; + +public interface ResolveContext { + + DNSMessage request(); + + DNSMessage response(); + + Response resolve(Name qname, RRType qtype); +} diff --git a/src-wip/org/handwerkszeug/dns/Resolver.java b/src-wip/org/handwerkszeug/dns/Resolver.java new file mode 100755 index 0000000..5b974b1 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Resolver.java @@ -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 { + +} diff --git a/src-wip/org/handwerkszeug/dns/ResourceRecord.java b/src-wip/org/handwerkszeug/dns/ResourceRecord.java new file mode 100755 index 0000000..6fd64af --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/ResourceRecord.java @@ -0,0 +1,88 @@ +package org.handwerkszeug.dns; + +import java.util.List; + +import io.netty.buffer.ByteBuf; + +/** + * 4.1.3. Resource record format
+ * 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: + * + *
+ *                                     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                     /
+ *     /                                               /
+ *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * 
+ * + * @author taichi + * @see Domain Name + * System (DNS) Parameters + */ +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 list); + + void parse(ByteBuf buffer); + + void write(ByteBuf buffer, NameCompressor compressor); + + ResourceRecord toQnameRecord(Name qname); +} diff --git a/src-wip/org/handwerkszeug/dns/Response.java b/src-wip/org/handwerkszeug/dns/Response.java new file mode 100755 index 0000000..9ffb0d0 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Response.java @@ -0,0 +1,11 @@ +package org.handwerkszeug.dns; + +/** + * @author taichi + */ +public interface Response { + + RCode rcode(); + + void postProcess(ResolveContext context); +} diff --git a/src-wip/org/handwerkszeug/dns/SimpleNameCompressor.java b/src-wip/org/handwerkszeug/dns/SimpleNameCompressor.java new file mode 100755 index 0000000..7d7c98b --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/SimpleNameCompressor.java @@ -0,0 +1,21 @@ +package org.handwerkszeug.dns; + +import java.util.HashMap; +import java.util.Map; + +public class SimpleNameCompressor implements NameCompressor { + + protected Map map = new HashMap(); + + 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(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/Zone.java b/src-wip/org/handwerkszeug/dns/Zone.java new file mode 100755 index 0000000..59d8b7d --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/Zone.java @@ -0,0 +1,11 @@ +package org.handwerkszeug.dns; + +public interface Zone { + Name name(); + + DNSClass dnsClass(); + + ZoneType type(); + + Response find(Name qname, RRType qtype); +} diff --git a/src-wip/org/handwerkszeug/dns/ZoneType.java b/src-wip/org/handwerkszeug/dns/ZoneType.java new file mode 100755 index 0000000..f5585c0 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/ZoneType.java @@ -0,0 +1,7 @@ +package org.handwerkszeug.dns; + +public enum ZoneType { + + master, slave, stub, forward, rootHint; + +} diff --git a/src-wip/org/handwerkszeug/dns/aaaa/DNSClient.java b/src-wip/org/handwerkszeug/dns/aaaa/DNSClient.java new file mode 100755 index 0000000..6d95b49 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/aaaa/DNSClient.java @@ -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 names = new ArrayList(); + + 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 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 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. + } +} diff --git a/src-wip/org/handwerkszeug/dns/aaaa/DNSServer.java b/src-wip/org/handwerkszeug/dns/aaaa/DNSServer.java new file mode 100755 index 0000000..7b496d1 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/aaaa/DNSServer.java @@ -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); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/aaaa/DNSServerPipelineFactory.java b/src-wip/org/handwerkszeug/dns/aaaa/DNSServerPipelineFactory.java new file mode 100755 index 0000000..f9125e8 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/aaaa/DNSServerPipelineFactory.java @@ -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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/aaaa/ForwardingHandler.java b/src-wip/org/handwerkszeug/dns/aaaa/ForwardingHandler.java new file mode 100755 index 0000000..a9119b0 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/aaaa/ForwardingHandler.java @@ -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 newlist = new ArrayList(this.config.getForwarders()); + sendRequest(e, original, cb, newlist); + } + + protected void sendRequest(final MessageEvent e, final DNSMessage original, + final ClientBootstrap bootstrap, + final List 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(); + } + } + +} diff --git a/src-wip/org/handwerkszeug/dns/client/PortNumbers.txt b/src-wip/org/handwerkszeug/dns/client/PortNumbers.txt new file mode 100755 index 0000000..1f05ae1 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/client/PortNumbers.txt @@ -0,0 +1,2122 @@ +#(last updated 2010-09-21) + 0/tcp Reserved + 0/udp Reserved +# Jon Postel +spr-itunes 0/tcp Shirt Pocket netTunes +spl-itunes 0/tcp Shirt Pocket launchTunes +# David Nanian 28 September 2007 +tcpmux 1/tcp TCP Port Service Multiplexer +tcpmux 1/udp TCP Port Service Multiplexer +# Mark Lottor +compressnet 2/tcp Management Utility +compressnet 2/udp Management Utility +compressnet 3/tcp Compression Process +compressnet 3/udp Compression Process +# Bernie Volz +# 4/tcp Unassigned +# 4/udp Unassigned +rje 5/tcp Remote Job Entry +rje 5/udp Remote Job Entry +# Jon Postel +# 6/tcp Unassigned +# 6/udp Unassigned +echo 7/tcp Echo +echo 7/udp Echo +# Jon Postel +# 8/tcp Unassigned +# 8/udp Unassigned +discard 9/tcp Discard +discard 9/udp Discard +# Jon Postel +discard 9/sctp Discard +# IETF TSVWG +# Randall Stewart +# [RFC4960] +discard 9/dccp Discard SC:DISC +# IETF dccp WG, Eddie Kohler , [RFC4340] +# 10/tcp Unassigned +# 10/udp Unassigned +systat 11/tcp Active Users +systat 11/udp Active Users +# Jon Postel +# 12/tcp Unassigned +# 12/udp Unassigned +daytime 13/tcp Daytime (RFC 867) +daytime 13/udp Daytime (RFC 867) +# Jon Postel +# 14/tcp Unassigned +# 14/udp Unassigned +# 15/tcp Unassigned [was netstat] +# 15/udp Unassigned +# 16/tcp Unassigned +# 16/udp Unassigned +qotd 17/tcp Quote of the Day +qotd 17/udp Quote of the Day +# Jon Postel +msp 18/tcp Message Send Protocol +msp 18/udp Message Send Protocol +# Rina Nethaniel <---none---> +chargen 19/tcp Character Generator +chargen 19/udp Character Generator +ftp-data 20/tcp File Transfer [Default Data] +ftp-data 20/udp File Transfer [Default Data] +# Jon Postel +ftp-data 20/sctp FTP +# IETF TSVWG +# Randall Stewart +# [RFC4960] +ftp 21/tcp File Transfer [Control] +ftp 21/udp File Transfer [Control] +# Jon Postel +ftp 21/sctp FTP +# IETF TSVWG +# Randall Stewart +# [RFC4960] +ssh 22/tcp The Secure Shell (SSH) Protocol +ssh 22/udp The Secure Shell (SSH) Protocol +# [RFC4251] +ssh 22/sctp SSH +# IETF TSVWG +# Randall Stewart +# [RFC4960] +telnet 23/tcp Telnet +telnet 23/udp Telnet +# Jon Postel + 24/tcp any private mail system + 24/udp any private mail system +# Rick Adams +smtp 25/tcp Simple Mail Transfer +smtp 25/udp Simple Mail Transfer +# Jon Postel +# 26/tcp Unassigned +# 26/udp Unassigned +nsw-fe 27/tcp NSW User System FE +nsw-fe 27/udp NSW User System FE +# Robert Thomas +# 28/tcp Unassigned +# 28/udp Unassigned +msg-icp 29/tcp MSG ICP +msg-icp 29/udp MSG ICP +# Robert Thomas +# 30/tcp Unassigned +# 30/udp Unassigned +msg-auth 31/tcp MSG Authentication +msg-auth 31/udp MSG Authentication +# Robert Thomas +# 32/tcp Unassigned +# 32/udp Unassigned +dsp 33/tcp Display Support Protocol +dsp 33/udp Display Support Protocol +# Ed Cain +# 34/tcp Unassigned +# 34/udp Unassigned + 35/tcp any private printer server + 35/udp any private printer server +# Jon Postel +# 36/tcp Unassigned +# 36/udp Unassigned +time 37/tcp Time +time 37/udp Time +# Jon Postel +rap 38/tcp Route Access Protocol +rap 38/udp Route Access Protocol +# Robert Ullmann +rlp 39/tcp Resource Location Protocol +rlp 39/udp Resource Location Protocol +# Mike Accetta +# 40/tcp Unassigned +# 40/udp Unassigned +graphics 41/tcp Graphics +graphics 41/udp Graphics +name 42/tcp Host Name Server +name 42/udp Host Name Server +nameserver 42/tcp Host Name Server +nameserver 42/udp Host Name Server +nicname 43/tcp Who Is +nicname 43/udp Who Is +mpm-flags 44/tcp MPM FLAGS Protocol +mpm-flags 44/udp MPM FLAGS Protocol +mpm 45/tcp Message Processing Module [recv] +mpm 45/udp Message Processing Module [recv] +mpm-snd 46/tcp MPM [default send] +mpm-snd 46/udp MPM [default send] +# Jon Postel +ni-ftp 47/tcp NI FTP +ni-ftp 47/udp NI FTP +# Steve Kille +auditd 48/tcp Digital Audit Daemon +auditd 48/udp Digital Audit Daemon +# Larry Scott +tacacs 49/tcp Login Host Protocol (TACACS) +tacacs 49/udp Login Host Protocol (TACACS) +# Pieter Ditmars +re-mail-ck 50/tcp Remote Mail Checking Protocol +re-mail-ck 50/udp Remote Mail Checking Protocol +# Steve Dorner +la-maint 51/tcp IMP Logical Address Maintenance +la-maint 51/udp IMP Logical Address Maintenance +# Andy Malis +xns-time 52/tcp XNS Time Protocol +xns-time 52/udp XNS Time Protocol +# Susie Armstrong +domain 53/tcp Domain Name Server +domain 53/udp Domain Name Server +# Paul Mockapetris +xns-ch 54/tcp XNS Clearinghouse +xns-ch 54/udp XNS Clearinghouse +# Susie Armstrong +isi-gl 55/tcp ISI Graphics Language +isi-gl 55/udp ISI Graphics Language +xns-auth 56/tcp XNS Authentication +xns-auth 56/udp XNS Authentication +# Susie Armstrong + 57/tcp any private terminal access + 57/udp any private terminal access +# Jon Postel +xns-mail 58/tcp XNS Mail +xns-mail 58/udp XNS Mail +# Susie Armstrong + 59/tcp any private file service + 59/udp any private file service +# Jon Postel +# 60/tcp Unassigned +# 60/udp Unassigned +ni-mail 61/tcp NI MAIL +ni-mail 61/udp NI MAIL +# Steve Kille +acas 62/tcp ACA Services +acas 62/udp ACA Services +# E. Wald +whois++ 63/tcp whois++ +whois++ 63/udp whois++ +# Rickard Schoultz +covia 64/tcp Communications Integrator (CI) +covia 64/udp Communications Integrator (CI) +# Dan Smith +tacacs-ds 65/tcp TACACS-Database Service +tacacs-ds 65/udp TACACS-Database Service +# Kathy Huber +sql*net 66/tcp Oracle SQL*NET +sql*net 66/udp Oracle SQL*NET +# Jack Haverty +bootps 67/tcp Bootstrap Protocol Server +bootps 67/udp Bootstrap Protocol Server +bootpc 68/tcp Bootstrap Protocol Client +bootpc 68/udp Bootstrap Protocol Client +# Bill Croft +tftp 69/tcp Trivial File Transfer +tftp 69/udp Trivial File Transfer +# David Clark +gopher 70/tcp Gopher +gopher 70/udp Gopher +# Mark McCahill +netrjs-1 71/tcp Remote Job Service +netrjs-1 71/udp Remote Job Service +netrjs-2 72/tcp Remote Job Service +netrjs-2 72/udp Remote Job Service +netrjs-3 73/tcp Remote Job Service +netrjs-3 73/udp Remote Job Service +netrjs-4 74/tcp Remote Job Service +netrjs-4 74/udp Remote Job Service +# Bob Braden + 75/tcp any private dial out service + 75/udp any private dial out service +# Jon Postel +deos 76/tcp Distributed External Object Store +deos 76/udp Distributed External Object Store +# Robert Ullmann + 77/tcp any private RJE service + 77/udp any private RJE service +# Jon Postel +vettcp 78/tcp vettcp +vettcp 78/udp vettcp +# Christopher Leong +finger 79/tcp Finger +finger 79/udp Finger +# David Zimmerman +# Unauthorized use by some mail users (see [RFC4146] for details) +http 80/tcp World Wide Web HTTP +http 80/udp World Wide Web HTTP +www 80/tcp World Wide Web HTTP +www 80/udp World Wide Web HTTP +www-http 80/tcp World Wide Web HTTP +www-http 80/udp World Wide Web HTTP +# Tim Berners-Lee +http 80/sctp HTTP +# IETF TSVWG +# Randall Stewart +# [RFC4960] +# 81 Unassigned (Removed on 2007-09-06) +xfer 82/tcp XFER Utility +xfer 82/udp XFER Utility +# Thomas M. Smith +mit-ml-dev 83/tcp MIT ML Device +mit-ml-dev 83/udp MIT ML Device +# David Reed <--none---> +ctf 84/tcp Common Trace Facility +ctf 84/udp Common Trace Facility +# Hugh Thomas +mit-ml-dev 85/tcp MIT ML Device +mit-ml-dev 85/udp MIT ML Device +# David Reed <--none---> +mfcobol 86/tcp Micro Focus Cobol +mfcobol 86/udp Micro Focus Cobol +# Simon Edwards <--none---> + 87/tcp any private terminal link + 87/udp any private terminal link +# Jon Postel +kerberos 88/tcp Kerberos +kerberos 88/udp Kerberos +# B. Clifford Neuman +su-mit-tg 89/tcp SU/MIT Telnet Gateway +su-mit-tg 89/udp SU/MIT Telnet Gateway +# Mark Crispin +########### PORT 90 also being used unofficially by Pointcast ######### +dnsix 90/tcp DNSIX Securit Attribute Token Map +dnsix 90/udp DNSIX Securit Attribute Token Map +# Charles Watt +mit-dov 91/tcp MIT Dover Spooler +mit-dov 91/udp MIT Dover Spooler +# Eliot Moss +npp 92/tcp Network Printing Protocol +npp 92/udp Network Printing Protocol +# Louis Mamakos +dcp 93/tcp Device Control Protocol +dcp 93/udp Device Control Protocol +# Daniel Tappan +objcall 94/tcp Tivoli Object Dispatcher +objcall 94/udp Tivoli Object Dispatcher +# Tom Bereiter <--none---> +supdup 95/tcp SUPDUP +supdup 95/udp SUPDUP +# Mark Crispin +dixie 96/tcp DIXIE Protocol Specification +dixie 96/udp DIXIE Protocol Specification +# Tim Howes +swift-rvf 97/tcp Swift Remote Virtural File Protocol +swift-rvf 97/udp Swift Remote Virtural File Protocol +# Maurice R. Turcotte +# +tacnews 98/tcp TAC News +tacnews 98/udp TAC News +# Jon Postel +metagram 99/tcp Metagram Relay +metagram 99/udp Metagram Relay +# Geoff Goodfellow +newacct 100/tcp [unauthorized use] +hostname 101/tcp NIC Host Name Server +hostname 101/udp NIC Host Name Server +# Jon Postel +iso-tsap 102/tcp ISO-TSAP Class 0 +iso-tsap 102/udp ISO-TSAP Class 0 +# Marshall Rose +gppitnp 103/tcp Genesis Point-to-Point Trans Net +gppitnp 103/udp Genesis Point-to-Point Trans Net +acr-nema 104/tcp ACR-NEMA Digital Imag. & Comm. 300 +acr-nema 104/udp ACR-NEMA Digital Imag. & Comm. 300 +# Patrick McNamee <--none---> +cso 105/tcp CCSO name server protocol +cso 105/udp CCSO name server protocol +# Martin Hamilton +csnet-ns 105/tcp Mailbox Name Nameserver +csnet-ns 105/udp Mailbox Name Nameserver +# Marvin Solomon +3com-tsmux 106/tcp 3COM-TSMUX +3com-tsmux 106/udp 3COM-TSMUX +# Jeremy Siegel +########## 106 Unauthorized use by insecure poppassd protocol +rtelnet 107/tcp Remote Telnet Service +rtelnet 107/udp Remote Telnet Service +# Jon Postel +snagas 108/tcp SNA Gateway Access Server +snagas 108/udp SNA Gateway Access Server +# Kevin Murphy +pop2 109/tcp Post Office Protocol - Version 2 +pop2 109/udp Post Office Protocol - Version 2 +# Joyce K. Reynolds +pop3 110/tcp Post Office Protocol - Version 3 +pop3 110/udp Post Office Protocol - Version 3 +# Marshall Rose +sunrpc 111/tcp SUN Remote Procedure Call +sunrpc 111/udp SUN Remote Procedure Call +# Chuck McManis +mcidas 112/tcp McIDAS Data Transmission Protocol +mcidas 112/udp McIDAS Data Transmission Protocol +# Glenn Davis +ident 113/tcp +auth 113/tcp Authentication Service +auth 113/udp Authentication Service +# Mike St. Johns +# 114 Deprecated June 2004 +sftp 115/tcp Simple File Transfer Protocol +sftp 115/udp Simple File Transfer Protocol +# Mark Lottor +ansanotify 116/tcp ANSA REX Notify +ansanotify 116/udp ANSA REX Notify +# Nicola J. Howarth +uucp-path 117/tcp UUCP Path Service +uucp-path 117/udp UUCP Path Service +sqlserv 118/tcp SQL Services +sqlserv 118/udp SQL Services +# Larry Barnes +nntp 119/tcp Network News Transfer Protocol +nntp 119/udp Network News Transfer Protocol +# Phil Lapsley +cfdptkt 120/tcp CFDPTKT +cfdptkt 120/udp CFDPTKT +# John Ioannidis +erpc 121/tcp Encore Expedited Remote Pro.Call +erpc 121/udp Encore Expedited Remote Pro.Call +# Jack O'Neil <---none---> +smakynet 122/tcp SMAKYNET +smakynet 122/udp SMAKYNET +# Pierre Arnaud +ntp 123/tcp Network Time Protocol +ntp 123/udp Network Time Protocol +# Dave Mills +# [RFC5905] +ansatrader 124/tcp ANSA REX Trader +ansatrader 124/udp ANSA REX Trader +# Nicola J. Howarth +locus-map 125/tcp Locus PC-Interface Net Map Ser +locus-map 125/udp Locus PC-Interface Net Map Ser +# Eric Peterson +nxedit 126/tcp NXEdit +nxedit 126/udp NXEdit +# Don Payette +###########Port 126 Previously assigned to application below####### +#unitary 126/tcp Unisys Unitary Login +#unitary 126/udp Unisys Unitary Login +# +###########Port 126 Previously assigned to application above####### +locus-con 127/tcp Locus PC-Interface Conn Server +locus-con 127/udp Locus PC-Interface Conn Server +# Eric Peterson +gss-xlicen 128/tcp GSS X License Verification +gss-xlicen 128/udp GSS X License Verification +# John Light +pwdgen 129/tcp Password Generator Protocol +pwdgen 129/udp Password Generator Protocol +# Frank J. Wacho +cisco-fna 130/tcp cisco FNATIVE +cisco-fna 130/udp cisco FNATIVE +cisco-tna 131/tcp cisco TNATIVE +cisco-tna 131/udp cisco TNATIVE +cisco-sys 132/tcp cisco SYSMAINT +cisco-sys 132/udp cisco SYSMAINT +statsrv 133/tcp Statistics Service +statsrv 133/udp Statistics Service +# Dave Mills +ingres-net 134/tcp INGRES-NET Service +ingres-net 134/udp INGRES-NET Service +# Mike Berrow <---none---> +epmap 135/tcp DCE endpoint resolution +epmap 135/udp DCE endpoint resolution +# Joe Pato +profile 136/tcp PROFILE Naming System +profile 136/udp PROFILE Naming System +# Larry Peterson +netbios-ns 137/tcp NETBIOS Name Service +netbios-ns 137/udp NETBIOS Name Service +netbios-dgm 138/tcp NETBIOS Datagram Service +netbios-dgm 138/udp NETBIOS Datagram Service +netbios-ssn 139/tcp NETBIOS Session Service +netbios-ssn 139/udp NETBIOS Session Service +# Jon Postel +emfis-data 140/tcp EMFIS Data Service +emfis-data 140/udp EMFIS Data Service +emfis-cntl 141/tcp EMFIS Control Service +emfis-cntl 141/udp EMFIS Control Service +# Gerd Beling +bl-idm 142/tcp Britton-Lee IDM +bl-idm 142/udp Britton-Lee IDM +# Susie Snitzer <---none---> +imap 143/tcp Internet Message Access Protocol +imap 143/udp Internet Message Access Protocol +# Mark Crispin +uma 144/tcp Universal Management Architecture +uma 144/udp Universal Management Architecture +# Jay Whitney +uaac 145/tcp UAAC Protocol +uaac 145/udp UAAC Protocol +# David A. Gomberg +iso-tp0 146/tcp ISO-IP0 +iso-tp0 146/udp ISO-IP0 +iso-ip 147/tcp ISO-IP +iso-ip 147/udp ISO-IP +# Marshall Rose +jargon 148/tcp Jargon +jargon 148/udp Jargon +# Bill Weinman +aed-512 149/tcp AED 512 Emulation Service +aed-512 149/udp AED 512 Emulation Service +# Albert G. Broscius +sql-net 150/tcp SQL-NET +sql-net 150/udp SQL-NET +# Martin Picard <<---none---> +hems 151/tcp HEMS +hems 151/udp HEMS +bftp 152/tcp Background File Transfer Program +bftp 152/udp Background File Transfer Program +# Annette DeSchon +sgmp 153/tcp SGMP +sgmp 153/udp SGMP +# Marty Schoffstahl +netsc-prod 154/tcp NETSC +netsc-prod 154/udp NETSC +netsc-dev 155/tcp NETSC +netsc-dev 155/udp NETSC +# Sergio Heker +sqlsrv 156/tcp SQL Service +sqlsrv 156/udp SQL Service +# Craig Rogers +knet-cmp 157/tcp KNET/VM Command/Message Protocol +knet-cmp 157/udp KNET/VM Command/Message Protocol +# Gary S. Malkin +pcmail-srv 158/tcp PCMail Server +pcmail-srv 158/udp PCMail Server +# Mark L. Lambert +nss-routing 159/tcp NSS-Routing +nss-routing 159/udp NSS-Routing +# Yakov Rekhter +sgmp-traps 160/tcp SGMP-TRAPS +sgmp-traps 160/udp SGMP-TRAPS +# Marty Schoffstahl +snmp 161/tcp SNMP +snmp 161/udp SNMP +snmptrap 162/tcp SNMPTRAP +snmptrap 162/udp SNMPTRAP +# Marshall Rose +cmip-man 163/tcp CMIP/TCP Manager +cmip-man 163/udp CMIP/TCP Manager +cmip-agent 164/tcp CMIP/TCP Agent +cmip-agent 164/udp CMIP/TCP Agent +# Amatzia Ben-Artzi <---none---> +xns-courier 165/tcp Xerox +xns-courier 165/udp Xerox +# Susie Armstrong +s-net 166/tcp Sirius Systems +s-net 166/udp Sirius Systems +# Brian Lloyd +namp 167/tcp NAMP +namp 167/udp NAMP +# Marty Schoffstahl +rsvd 168/tcp RSVD +rsvd 168/udp RSVD +# Previous contact: Neil Todd +# Current contact: Alan Sandell 01 May 2008 +send 169/tcp SEND +send 169/udp SEND +# William D. Wisner +print-srv 170/tcp Network PostScript +print-srv 170/udp Network PostScript +# Brian Reid +multiplex 171/tcp Network Innovations Multiplex +multiplex 171/udp Network Innovations Multiplex +cl/1 172/tcp Network Innovations CL/1 +cl/1 172/udp Network Innovations CL/1 +# Kevin DeVault <<---none---> +xyplex-mux 173/tcp Xyplex +xyplex-mux 173/udp Xyplex +# Bob Stewart +mailq 174/tcp MAILQ +mailq 174/udp MAILQ +# Rayan Zachariassen +vmnet 175/tcp VMNET +vmnet 175/udp VMNET +# Christopher Tengi +genrad-mux 176/tcp GENRAD-MUX +genrad-mux 176/udp GENRAD-MUX +# Ron Thornton +xdmcp 177/tcp X Display Manager Control Protocol +xdmcp 177/udp X Display Manager Control Protocol +# Robert W. Scheifler +nextstep 178/tcp NextStep Window Server +nextstep 178/udp NextStep Window Server +# Leo Hourvitz +bgp 179/tcp Border Gateway Protocol +bgp 179/udp Border Gateway Protocol +# Kirk Lougheed +bgp 179/sctp BGP +# IETF TSVWG +# Randall Stewart +# [RFC4960] +ris 180/tcp Intergraph +ris 180/udp Intergraph +# Dave Buehmann +unify 181/tcp Unify +unify 181/udp Unify +# Mark Ainsley +audit 182/tcp Unisys Audit SITP +audit 182/udp Unisys Audit SITP +# Gil Greenbaum +ocbinder 183/tcp OCBinder +ocbinder 183/udp OCBinder +ocserver 184/tcp OCServer +ocserver 184/udp OCServer +# Jerrilynn Okamura <--none---> +remote-kis 185/tcp Remote-KIS +remote-kis 185/udp Remote-KIS +kis 186/tcp KIS Protocol +kis 186/udp KIS Protocol +# Ralph Droms +aci 187/tcp Application Communication Interface +aci 187/udp Application Communication Interface +# Rick Carlos +mumps 188/tcp Plus Five's MUMPS +mumps 188/udp Plus Five's MUMPS +# Hokey Stenn +qft 189/tcp Queued File Transport +qft 189/udp Queued File Transport +# Wayne Schroeder +gacp 190/tcp Gateway Access Control Protocol +gacp 190/udp Gateway Access Control Protocol +# C. Philip Wood +prospero 191/tcp Prospero Directory Service +prospero 191/udp Prospero Directory Service +# B. Clifford Neuman +osu-nms 192/tcp OSU Network Monitoring System +osu-nms 192/udp OSU Network Monitoring System +# Doug Karl +srmp 193/tcp Spider Remote Monitoring Protocol +srmp 193/udp Spider Remote Monitoring Protocol +# Ted J. Socolofsky +irc 194/tcp Internet Relay Chat Protocol +irc 194/udp Internet Relay Chat Protocol +# Jarkko Oikarinen +dn6-nlm-aud 195/tcp DNSIX Network Level Module Audit +dn6-nlm-aud 195/udp DNSIX Network Level Module Audit +dn6-smm-red 196/tcp DNSIX Session Mgt Module Audit Redir +dn6-smm-red 196/udp DNSIX Session Mgt Module Audit Redir +# Lawrence Lebahn +dls 197/tcp Directory Location Service +dls 197/udp Directory Location Service +dls-mon 198/tcp Directory Location Service Monitor +dls-mon 198/udp Directory Location Service Monitor +# Scott Bellew +smux 199/tcp SMUX +smux 199/udp SMUX +# Marshall Rose +src 200/tcp IBM System Resource Controller +src 200/udp IBM System Resource Controller +# Gerald McBrearty <---none---> +at-rtmp 201/tcp AppleTalk Routing Maintenance +at-rtmp 201/udp AppleTalk Routing Maintenance +at-nbp 202/tcp AppleTalk Name Binding +at-nbp 202/udp AppleTalk Name Binding +at-3 203/tcp AppleTalk Unused +at-3 203/udp AppleTalk Unused +at-echo 204/tcp AppleTalk Echo +at-echo 204/udp AppleTalk Echo +at-5 205/tcp AppleTalk Unused +at-5 205/udp AppleTalk Unused +at-zis 206/tcp AppleTalk Zone Information +at-zis 206/udp AppleTalk Zone Information +at-7 207/tcp AppleTalk Unused +at-7 207/udp AppleTalk Unused +at-8 208/tcp AppleTalk Unused +at-8 208/udp AppleTalk Unused +# Rob Chandhok +qmtp 209/tcp The Quick Mail Transfer Protocol +qmtp 209/udp The Quick Mail Transfer Protocol +# Dan Bernstein +z39.50 210/tcp ANSI Z39.50 +z39.50 210/udp ANSI Z39.50 +# Mark H. Needleman +914c/g 211/tcp Texas Instruments 914C/G Terminal +914c/g 211/udp Texas Instruments 914C/G Terminal +# Bill Harrell <---none---> +anet 212/tcp ATEXSSTR +anet 212/udp ATEXSSTR +# Jim Taylor +ipx 213/tcp IPX +ipx 213/udp IPX +# Don Provan +vmpwscs 214/tcp VM PWSCS +vmpwscs 214/udp VM PWSCS +# Dan Shia +softpc 215/tcp Insignia Solutions +softpc 215/udp Insignia Solutions +# Martyn Thomas <---none---> +CAIlic 216/tcp Computer Associates Int'l License Server +CAIlic 216/udp Computer Associates Int'l License Server +# Chuck Spitz +dbase 217/tcp dBASE Unix +dbase 217/udp dBASE Unix +# Don Gibson +# +mpp 218/tcp Netix Message Posting Protocol +mpp 218/udp Netix Message Posting Protocol +# Shannon Yeh +uarps 219/tcp Unisys ARPs +uarps 219/udp Unisys ARPs +# Ashok Marwaha <---none---> +imap3 220/tcp Interactive Mail Access Protocol v3 +imap3 220/udp Interactive Mail Access Protocol v3 +# James Rice +fln-spx 221/tcp Berkeley rlogind with SPX auth +fln-spx 221/udp Berkeley rlogind with SPX auth +rsh-spx 222/tcp Berkeley rshd with SPX auth +rsh-spx 222/udp Berkeley rshd with SPX auth +cdc 223/tcp Certificate Distribution Center +cdc 223/udp Certificate Distribution Center +# Kannan Alagappan +########### Possible Conflict of Port 222 with "Masqdialer"############## +### Contact for Masqdialer is Charles Wright ### +masqdialer 224/tcp masqdialer +masqdialer 224/udp masqdialer +# Charles Wright +# 225-241 Reserved +# Jon Postel +direct 242/tcp Direct +direct 242/udp Direct +# Herb Sutter +sur-meas 243/tcp Survey Measurement +sur-meas 243/udp Survey Measurement +# Dave Clark +inbusiness 244/tcp inbusiness +inbusiness 244/udp inbusiness +# Derrick Hisatake +link 245/tcp LINK +link 245/udp LINK +dsp3270 246/tcp Display Systems Protocol +dsp3270 246/udp Display Systems Protocol +# Weldon J. Showalter +subntbcst_tftp 247/tcp SUBNTBCST_TFTP +subntbcst_tftp 247/udp SUBNTBCST_TFTP +# John Fake +bhfhs 248/tcp bhfhs +bhfhs 248/udp bhfhs +# John Kelly +# 249-255 Reserved +# Jon Postel +rap 256/tcp RAP +rap 256/udp RAP +# J.S. Greenfield +set 257/tcp Secure Electronic Transaction +set 257/udp Secure Electronic Transaction +# Donald Eastlake +# 258 Unassigned (Removed 2006-09-13) +esro-gen 259/tcp Efficient Short Remote Operations +esro-gen 259/udp Efficient Short Remote Operations +# Mohsen Banan +openport 260/tcp Openport +openport 260/udp Openport +# John Marland +nsiiops 261/tcp IIOP Name Service over TLS/SSL +nsiiops 261/udp IIOP Name Service over TLS/SSL +# Jeff Stewart +arcisdms 262/tcp Arcisdms +arcisdms 262/udp Arcisdms +# Russell Crook (rmc&sni.ca> +hdap 263/tcp HDAP +hdap 263/udp HDAP +# Troy Gau +bgmp 264/tcp BGMP +bgmp 264/udp BGMP +# Dave Thaler +x-bone-ctl 265/tcp X-Bone CTL +x-bone-ctl 265/udp X-Bone CTL +# Joe Touch +sst 266/tcp SCSI on ST +sst 266/udp SCSI on ST +# Donald D. Woelz +td-service 267/tcp Tobit David Service Layer +td-service 267/udp Tobit David Service Layer +td-replica 268/tcp Tobit David Replica +td-replica 268/udp Tobit David Replica +# Franz-Josef Leuders +manet 269/tcp MANET Protocols +manet 269/udp MANET Protocols +# [RFC5498] +# 270/tcp Reserved +gist 270/udp Q-mode encapsulation for GIST messages +# [RFC-ietf-nsis-ntlp-20.txt] +# 271-279 Unassigned +http-mgmt 280/tcp http-mgmt +http-mgmt 280/udp http-mgmt +# Adrian Pell +# +personal-link 281/tcp Personal Link +personal-link 281/udp Personal Link +# Dan Cummings +cableport-ax 282/tcp Cable Port A/X +cableport-ax 282/udp Cable Port A/X +# Craig Langfahl +rescap 283/tcp rescap +rescap 283/udp rescap +# Paul Hoffman +corerjd 284/tcp corerjd +corerjd 284/udp corerjd +# Chris Thornhill +# 285 Unassigned +fxp 286/tcp FXP Communication +fxp 286/udp FXP Communication +# James Darnall +k-block 287/tcp K-BLOCK +k-block 287/udp K-BLOCK +# Simon P Jackson +# 288-307 Unassigned +novastorbakcup 308/tcp Novastor Backup +novastorbakcup 308/udp Novastor Backup +# Brian Dickman +entrusttime 309/tcp EntrustTime +entrusttime 309/udp EntrustTime +# Peter Whittaker +bhmds 310/tcp bhmds +bhmds 310/udp bhmds +# John Kelly +asip-webadmin 311/tcp AppleShare IP WebAdmin +asip-webadmin 311/udp AppleShare IP WebAdmin +# Ann Huang +vslmp 312/tcp VSLMP +vslmp 312/udp VSLMP +# Gerben Wierda +magenta-logic 313/tcp Magenta Logic +magenta-logic 313/udp Magenta Logic +# Karl Rousseau +opalis-robot 314/tcp Opalis Robot +opalis-robot 314/udp Opalis Robot +# Laurent Domenech, Opalis +dpsi 315/tcp DPSI +dpsi 315/udp DPSI +# Tony Scamurra +decauth 316/tcp decAuth +decauth 316/udp decAuth +# Michael Agishtein +zannet 317/tcp Zannet +zannet 317/udp Zannet +# Zan Oliphant +pkix-timestamp 318/tcp PKIX TimeStamp +pkix-timestamp 318/udp PKIX TimeStamp +# Robert Zuccherato +ptp-event 319/tcp PTP Event +ptp-event 319/udp PTP Event +ptp-general 320/tcp PTP General +ptp-general 320/udp PTP General +# Previous contact: John Eidson +# Current contact: I&M Society TC-9 (Kang Lee) 27 July 2010 +pip 321/tcp PIP +pip 321/udp PIP +# Gordon Mohr +rtsps 322/tcp RTSPS +rtsps 322/udp RTSPS +# Anders Klemets +# 323-332 Unassigned +texar 333/tcp Texar Security Port +texar 333/udp Texar Security Port +# Eugen Bacic +# 334-343 Unassigned +pdap 344/tcp Prospero Data Access Protocol +pdap 344/udp Prospero Data Access Protocol +# B. Clifford Neuman +pawserv 345/tcp Perf Analysis Workbench +pawserv 345/udp Perf Analysis Workbench +zserv 346/tcp Zebra server +zserv 346/udp Zebra server +fatserv 347/tcp Fatmen Server +fatserv 347/udp Fatmen Server +csi-sgwp 348/tcp Cabletron Management Protocol +csi-sgwp 348/udp Cabletron Management Protocol +mftp 349/tcp mftp +mftp 349/udp mftp +# Dave Feinleib +matip-type-a 350/tcp MATIP Type A +matip-type-a 350/udp MATIP Type A +# Alain Robert +# [RFC2351] +matip-type-b 351/tcp MATIP Type B +matip-type-b 351/udp MATIP Type B +# Alain Robert +# [RFC2351] +# The following entry records an unassigned but widespread use +bhoetty 351/tcp bhoetty (added 5/21/97) +bhoetty 351/udp bhoetty +# John Kelly +dtag-ste-sb 352/tcp DTAG (assigned long ago) +dtag-ste-sb 352/udp DTAG +# Ruediger Wald +# The following entry records an unassigned but widespread use +bhoedap4 352/tcp bhoedap4 (added 5/21/97) +bhoedap4 352/udp bhoedap4 +# John Kelly +ndsauth 353/tcp NDSAUTH +ndsauth 353/udp NDSAUTH +# Jayakumar Ramalingam +bh611 354/tcp bh611 +bh611 354/udp bh611 +# John Kelly +datex-asn 355/tcp DATEX-ASN +datex-asn 355/udp DATEX-ASN +# Kenneth Vaughn +cloanto-net-1 356/tcp Cloanto Net 1 +cloanto-net-1 356/udp Cloanto Net 1 +# Previous contact: Michael Battilana +# Current contact: Michael Battilana 30 April 2010 +bhevent 357/tcp bhevent +bhevent 357/udp bhevent +# John Kelly +shrinkwrap 358/tcp Shrinkwrap +shrinkwrap 358/udp Shrinkwrap +# Bill Simpson +nsrmp 359/tcp Network Security Risk Management Protocol +nsrmp 359/udp Network Security Risk Management Protocol +# Eric Jacksch +scoi2odialog 360/tcp scoi2odialog +scoi2odialog 360/udp scoi2odialog +# Keith Petley +semantix 361/tcp Semantix +semantix 361/udp Semantix +# Semantix +srssend 362/tcp SRS Send +srssend 362/udp SRS Send +# Curt Mayer +rsvp_tunnel 363/tcp RSVP Tunnel +rsvp_tunnel 363/udp RSVP Tunnel +# Andreas Terzis +aurora-cmgr 364/tcp Aurora CMGR +aurora-cmgr 364/udp Aurora CMGR +# Philip Budne +dtk 365/tcp DTK +dtk 365/udp DTK +# Fred Cohen +odmr 366/tcp ODMR +odmr 366/udp ODMR +# Randall Gellens +mortgageware 367/tcp MortgageWare +mortgageware 367/udp MortgageWare +# Ole Hellevik +qbikgdp 368/tcp QbikGDP +qbikgdp 368/udp QbikGDP +# Adrien de Croy +rpc2portmap 369/tcp rpc2portmap +rpc2portmap 369/udp rpc2portmap +codaauth2 370/tcp codaauth2 +codaauth2 370/udp codaauth2 +# Robert Watson +clearcase 371/tcp Clearcase +clearcase 371/udp Clearcase +# Dave LeBlang +ulistproc 372/tcp ListProcessor +ulistproc 372/udp ListProcessor +# Anastasios Kotsikonas +legent-1 373/tcp Legent Corporation +legent-1 373/udp Legent Corporation +legent-2 374/tcp Legent Corporation +legent-2 374/udp Legent Corporation +# Keith Boyce <---none---> +hassle 375/tcp Hassle +hassle 375/udp Hassle +# Reinhard Doelz +nip 376/tcp Amiga Envoy Network Inquiry Proto +nip 376/udp Amiga Envoy Network Inquiry Proto +# Heinz Wrobel +tnETOS 377/tcp NEC Corporation +tnETOS 377/udp NEC Corporation +dsETOS 378/tcp NEC Corporation +dsETOS 378/udp NEC Corporation +# Tomoo Fujita +is99c 379/tcp TIA/EIA/IS-99 modem client +is99c 379/udp TIA/EIA/IS-99 modem client +is99s 380/tcp TIA/EIA/IS-99 modem server +is99s 380/udp TIA/EIA/IS-99 modem server +# Frank Quick +hp-collector 381/tcp hp performance data collector +hp-collector 381/udp hp performance data collector +hp-managed-node 382/tcp hp performance data managed node +hp-managed-node 382/udp hp performance data managed node +hp-alarm-mgr 383/tcp hp performance data alarm manager +hp-alarm-mgr 383/udp hp performance data alarm manager +# Frank Blakely +arns 384/tcp A Remote Network Server System +arns 384/udp A Remote Network Server System +# David Hornsby +ibm-app 385/tcp IBM Application +ibm-app 385/udp IBM Application +# Lisa Tomita <---none---> +asa 386/tcp ASA Message Router Object Def. +asa 386/udp ASA Message Router Object Def. +# Steve Laitinen +aurp 387/tcp Appletalk Update-Based Routing Pro. +aurp 387/udp Appletalk Update-Based Routing Pro. +# Chris Ranch +unidata-ldm 388/tcp Unidata LDM +unidata-ldm 388/udp Unidata LDM +# Glenn Davis +ldap 389/tcp Lightweight Directory Access Protocol +ldap 389/udp Lightweight Directory Access Protocol +# Tim Howes +uis 390/tcp UIS +uis 390/udp UIS +# Ed Barron <---none---> +synotics-relay 391/tcp SynOptics SNMP Relay Port +synotics-relay 391/udp SynOptics SNMP Relay Port +synotics-broker 392/tcp SynOptics Port Broker Port +synotics-broker 392/udp SynOptics Port Broker Port +# Illan Raab +meta5 393/tcp Meta5 +meta5 393/udp Meta5 +# Jim Kanzler +embl-ndt 394/tcp EMBL Nucleic Data Transfer +embl-ndt 394/udp EMBL Nucleic Data Transfer +# Peter Gad +netcp 395/tcp NetScout Control Protocol +netcp 395/udp NetScout Control Protocol +# Old contact: Anil Singhal <---none---> +# Current contact: Ashwani Singhal 07 April 2010 +netware-ip 396/tcp Novell Netware over IP +netware-ip 396/udp Novell Netware over IP +mptn 397/tcp Multi Protocol Trans. Net. +mptn 397/udp Multi Protocol Trans. Net. +# Soumitra Sarkar +kryptolan 398/tcp Kryptolan +kryptolan 398/udp Kryptolan +# Peter de Laval +iso-tsap-c2 399/tcp ISO Transport Class 2 Non-Control over TCP +iso-tsap-c2 399/udp ISO Transport Class 2 Non-Control over UDP +# Yanick Pouffary +osb-sd 400/tcp Oracle Secure Backup +osb-sd 400/udp Oracle Secure Backup +# Formerly was Workstation Solutions +# Previous Contact: Jim Ward +# Current Contact: Joseph Dziedzic 06 June 2008 +ups 401/tcp Uninterruptible Power Supply +ups 401/udp Uninterruptible Power Supply +# Previous Contact: Charles Bennett +# Current Contact: Charles Bennett 29 August 2008 +genie 402/tcp Genie Protocol +genie 402/udp Genie Protocol +# Mark Hankin <---none---> +decap 403/tcp decap +decap 403/udp decap +nced 404/tcp nced +nced 404/udp nced +ncld 405/tcp ncld +ncld 405/udp ncld +# Richard Jones <---none---> +imsp 406/tcp Interactive Mail Support Protocol +imsp 406/udp Interactive Mail Support Protocol +# John Myers +timbuktu 407/tcp Timbuktu +timbuktu 407/udp Timbuktu +# Marc Epard +prm-sm 408/tcp Prospero Resource Manager Sys. Man. +prm-sm 408/udp Prospero Resource Manager Sys. Man. +prm-nm 409/tcp Prospero Resource Manager Node Man. +prm-nm 409/udp Prospero Resource Manager Node Man. +# B. Clifford Neuman +decladebug 410/tcp DECLadebug Remote Debug Protocol +decladebug 410/udp DECLadebug Remote Debug Protocol +# Hewlett-Packard <-TBA-> +rmt 411/tcp Remote MT Protocol +rmt 411/udp Remote MT Protocol +# Peter Eriksson +synoptics-trap 412/tcp Trap Convention Port +synoptics-trap 412/udp Trap Convention Port +# Illan Raab +smsp 413/tcp Storage Management Services Protocol +smsp 413/udp Storage Management Services Protocol +# Murthy Srinivas +infoseek 414/tcp InfoSeek +infoseek 414/udp InfoSeek +# Steve Kirsch +bnet 415/tcp BNet +bnet 415/udp BNet +# Jim Mertz +silverplatter 416/tcp Silverplatter +silverplatter 416/udp Silverplatter +# Peter Ciuffetti +onmux 417/tcp Onmux +onmux 417/udp Onmux +# Stephen Hanna +hyper-g 418/tcp Hyper-G +hyper-g 418/udp Hyper-G +# Frank Kappe +ariel1 419/tcp Ariel 1 +ariel1 419/udp Ariel 1 +# Joel Karafin +smpte 420/tcp SMPTE +smpte 420/udp SMPTE +# Si Becker <71362.22&CompuServe.COM> +ariel2 421/tcp Ariel 2 +ariel2 421/udp Ariel 2 +ariel3 422/tcp Ariel 3 +ariel3 422/udp Ariel 3 +# Joel Karafin +opc-job-start 423/tcp IBM Operations Planning and Control Start +opc-job-start 423/udp IBM Operations Planning and Control Start +opc-job-track 424/tcp IBM Operations Planning and Control Track +opc-job-track 424/udp IBM Operations Planning and Control Track +# Conny Larsson +icad-el 425/tcp ICAD +icad-el 425/udp ICAD +# Larry Stone +smartsdp 426/tcp smartsdp +smartsdp 426/udp smartsdp +# Marie-Pierre Belanger +svrloc 427/tcp Server Location +svrloc 427/udp Server Location +# +ocs_cmu 428/tcp OCS_CMU +ocs_cmu 428/udp OCS_CMU +ocs_amu 429/tcp OCS_AMU +ocs_amu 429/udp OCS_AMU +# Florence Wyman +utmpsd 430/tcp UTMPSD +utmpsd 430/udp UTMPSD +utmpcd 431/tcp UTMPCD +utmpcd 431/udp UTMPCD +iasd 432/tcp IASD +iasd 432/udp IASD +# Nir Baroz +nnsp 433/tcp NNSP +nnsp 433/udp NNSP +# Rob Robertson +mobileip-agent 434/tcp MobileIP-Agent +mobileip-agent 434/udp MobileIP-Agent +mobilip-mn 435/tcp MobilIP-MN +mobilip-mn 435/udp MobilIP-MN +# Kannan Alagappan +dna-cml 436/tcp DNA-CML +dna-cml 436/udp DNA-CML +# Dan Flowers +comscm 437/tcp comscm +comscm 437/udp comscm +# Jim Teague +dsfgw 438/tcp dsfgw +dsfgw 438/udp dsfgw +# Andy McKeen +dasp 439/tcp dasp Thomas Obermair +dasp 439/udp dasp tommy&inlab.m.eunet.de +# Thomas Obermair +sgcp 440/tcp sgcp +sgcp 440/udp sgcp +# Marshall Rose +decvms-sysmgt 441/tcp decvms-sysmgt +decvms-sysmgt 441/udp decvms-sysmgt +# Lee Barton +cvc_hostd 442/tcp cvc_hostd +cvc_hostd 442/udp cvc_hostd +# Bill Davidson +https 443/tcp http protocol over TLS/SSL +https 443/udp http protocol over TLS/SSL +# Kipp E.B. Hickman +https 443/sctp HTTPS +# IETF TSVWG +# Randall Stewart +# [RFC4960] +snpp 444/tcp Simple Network Paging Protocol +snpp 444/udp Simple Network Paging Protocol +# [RFC1568] +microsoft-ds 445/tcp Microsoft-DS +microsoft-ds 445/udp Microsoft-DS +# Pradeep Bahl +ddm-rdb 446/tcp DDM-Remote Relational Database Access +ddm-rdb 446/udp DDM-Remote Relational Database Access +ddm-dfm 447/tcp DDM-Distributed File Management +ddm-dfm 447/udp DDM-Distributed File Management +# Steven Ritland +ddm-ssl 448/tcp DDM-Remote DB Access Using Secure Sockets +ddm-ssl 448/udp DDM-Remote DB Access Using Secure Sockets +# Steven Ritland +as-servermap 449/tcp AS Server Mapper +as-servermap 449/udp AS Server Mapper +# Barbara Foss +tserver 450/tcp Computer Supported Telecomunication Applications +tserver 450/udp Computer Supported Telecomunication Applications +# Harvey S. Schultz +sfs-smp-net 451/tcp Cray Network Semaphore server +sfs-smp-net 451/udp Cray Network Semaphore server +sfs-config 452/tcp Cray SFS config server +sfs-config 452/udp Cray SFS config server +# Walter Poxon +creativeserver 453/tcp CreativeServer +creativeserver 453/udp CreativeServer +contentserver 454/tcp ContentServer +contentserver 454/udp ContentServer +creativepartnr 455/tcp CreativePartnr +creativepartnr 455/udp CreativePartnr +# Jesus Ortiz +macon-tcp 456/tcp macon-tcp +macon-udp 456/udp macon-udp +# Yoshinobu Inoue +# +scohelp 457/tcp scohelp +scohelp 457/udp scohelp +# Faith Zack +appleqtc 458/tcp apple quick time +appleqtc 458/udp apple quick time +# Murali Ranganathan +# +ampr-rcmd 459/tcp ampr-rcmd +ampr-rcmd 459/udp ampr-rcmd +# Rob Janssen +skronk 460/tcp skronk +skronk 460/udp skronk +# Henry Strickland +datasurfsrv 461/tcp DataRampSrv +datasurfsrv 461/udp DataRampSrv +datasurfsrvsec 462/tcp DataRampSrvSec +datasurfsrvsec 462/udp DataRampSrvSec +# Diane Downie +alpes 463/tcp alpes +alpes 463/udp alpes +# Alain Durand +kpasswd 464/tcp kpasswd +kpasswd 464/udp kpasswd +# Theodore Ts'o +urd 465/tcp URL Rendesvous Directory for SSM +igmpv3lite 465/udp IGMP over UDP for SSM +# Toerless Eckert +digital-vrc 466/tcp digital-vrc +digital-vrc 466/udp digital-vrc +# Peter Higginson +mylex-mapd 467/tcp mylex-mapd +mylex-mapd 467/udp mylex-mapd +# Gary Lewis +photuris 468/tcp proturis +photuris 468/udp proturis +# Bill Simpson +rcp 469/tcp Radio Control Protocol +rcp 469/udp Radio Control Protocol +# Jim Jennings +1-708-538-7241 +scx-proxy 470/tcp scx-proxy +scx-proxy 470/udp scx-proxy +# Scott Narveson +mondex 471/tcp Mondex +mondex 471/udp Mondex +# Bill Reding +ljk-login 472/tcp ljk-login +ljk-login 472/udp ljk-login +# LJK Software, Cambridge, Massachusetts +# +hybrid-pop 473/tcp hybrid-pop +hybrid-pop 473/udp hybrid-pop +# Rami Rubin +tn-tl-w1 474/tcp tn-tl-w1 +tn-tl-w2 474/udp tn-tl-w2 +# Ed Kress +tcpnethaspsrv 475/tcp tcpnethaspsrv +tcpnethaspsrv 475/udp tcpnethaspsrv +# Previous contact: Charlie Hava +# Current contact: Michael Zunke 23 July 2010 +tn-tl-fd1 476/tcp tn-tl-fd1 +tn-tl-fd1 476/udp tn-tl-fd1 +# Ed Kress +ss7ns 477/tcp ss7ns +ss7ns 477/udp ss7ns +# Jean-Michel URSCH +spsc 478/tcp spsc +spsc 478/udp spsc +# Mike Rieker +iafserver 479/tcp iafserver +iafserver 479/udp iafserver +iafdbase 480/tcp iafdbase +iafdbase 480/udp iafdbase +# ricky&solect.com +ph 481/tcp Ph service +ph 481/udp Ph service +# Roland Hedberg +bgs-nsi 482/tcp bgs-nsi +bgs-nsi 482/udp bgs-nsi +# Jon Saperia +ulpnet 483/tcp ulpnet +ulpnet 483/udp ulpnet +# Kevin Mooney +integra-sme 484/tcp Integra Software Management Environment +integra-sme 484/udp Integra Software Management Environment +# Randall Dow +powerburst 485/tcp Air Soft Power Burst +powerburst 485/udp Air Soft Power Burst +# +avian 486/tcp avian +avian 486/udp avian +# Robert Ullmann +# +saft 487/tcp saft Simple Asynchronous File Transfer +saft 487/udp saft Simple Asynchronous File Transfer +# Ulli Horlacher +gss-http 488/tcp gss-http +gss-http 488/udp gss-http +# Doug Rosenthal +nest-protocol 489/tcp nest-protocol +nest-protocol 489/udp nest-protocol +# Gilles Gameiro +micom-pfs 490/tcp micom-pfs +micom-pfs 490/udp micom-pfs +# David Misunas +go-login 491/tcp go-login +go-login 491/udp go-login +# Troy Morrison +ticf-1 492/tcp Transport Independent Convergence for FNA +ticf-1 492/udp Transport Independent Convergence for FNA +ticf-2 493/tcp Transport Independent Convergence for FNA +ticf-2 493/udp Transport Independent Convergence for FNA +# Mamoru Ito +pov-ray 494/tcp POV-Ray +pov-ray 494/udp POV-Ray +# POV-Team Co-ordinator +# +intecourier 495/tcp intecourier +intecourier 495/udp intecourier +# Steve Favor +pim-rp-disc 496/tcp PIM-RP-DISC +pim-rp-disc 496/udp PIM-RP-DISC +# Dino Farinacci +dantz 497/tcp dantz +dantz 497/udp dantz +# Richard Zulch +siam 498/tcp siam +siam 498/udp siam +# Philippe Gilbert +iso-ill 499/tcp ISO ILL Protocol +iso-ill 499/udp ISO ILL Protocol +# Mark H. Needleman +isakmp 500/tcp isakmp +isakmp 500/udp isakmp +# Mark Schertler +stmf 501/tcp STMF +stmf 501/udp STMF +# Alan Ungar +asa-appl-proto 502/tcp asa-appl-proto +asa-appl-proto 502/udp asa-appl-proto +# Dennis Dube +intrinsa 503/tcp Intrinsa +intrinsa 503/udp Intrinsa +# Robert Ford +citadel 504/tcp citadel +citadel 504/udp citadel +# Art Cancro +mailbox-lm 505/tcp mailbox-lm +mailbox-lm 505/udp mailbox-lm +# Beverly Moody +ohimsrv 506/tcp ohimsrv +ohimsrv 506/udp ohimsrv +# Scott Powell +crs 507/tcp crs +crs 507/udp crs +# Brad Wright +xvttp 508/tcp xvttp +xvttp 508/udp xvttp +# Keith J. Alphonso +snare 509/tcp snare +snare 509/udp snare +# Dennis Batchelder +fcp 510/tcp FirstClass Protocol +fcp 510/udp FirstClass Protocol +# Mike Marshburn +passgo 511/tcp PassGo +passgo 511/udp PassGo +# John Rainford +exec 512/tcp remote process execution; +# authentication performed using +# passwords and UNIX login names +comsat 512/udp +biff 512/udp used by mail system to notify users +# of new mail received; currently +# receives messages only from +# processes on the same machine +login 513/tcp remote login a la telnet; +# automatic authentication performed +# based on priviledged port numbers +# and distributed data bases which +# identify "authentication domains" +who 513/udp maintains data bases showing who's +# logged in to machines on a local +# net and the load average of the +# machine +shell 514/tcp cmd +# like exec, but automatic authentication +# is performed as for login server +syslog 514/udp +printer 515/tcp spooler +printer 515/udp spooler +videotex 516/tcp videotex +videotex 516/udp videotex +# Daniel Mavrakis +talk 517/tcp like tenex link, but across +# machine - unfortunately, doesn't +# use link protocol (this is actually +# just a rendezvous port from which a +# tcp connection is established) +talk 517/udp like tenex link, but across +# machine - unfortunately, doesn't +# use link protocol (this is actually +# just a rendezvous port from which a +# tcp connection is established) +ntalk 518/tcp +ntalk 518/udp +utime 519/tcp unixtime +utime 519/udp unixtime +efs 520/tcp extended file name server +router 520/udp local routing process (on site); +# uses variant of Xerox NS routing +# information protocol - RIP +ripng 521/tcp ripng +ripng 521/udp ripng +# Robert E. Minnear +ulp 522/tcp ULP +ulp 522/udp ULP +# Max Morris +ibm-db2 523/tcp IBM-DB2 +ibm-db2 523/udp IBM-DB2 +# Juliana Hsu +ncp 524/tcp NCP +ncp 524/udp NCP +# Don Provan +timed 525/tcp timeserver +timed 525/udp timeserver +tempo 526/tcp newdate +tempo 526/udp newdate +# Unknown +stx 527/tcp Stock IXChange +stx 527/udp Stock IXChange +custix 528/tcp Customer IXChange +custix 528/udp Customer IXChange +# Ferdi Ladeira +irc-serv 529/tcp IRC-SERV +irc-serv 529/udp IRC-SERV +# Brian Tackett +courier 530/tcp rpc +courier 530/udp rpc +conference 531/tcp chat +conference 531/udp chat +netnews 532/tcp readnews +netnews 532/udp readnews +netwall 533/tcp for emergency broadcasts +netwall 533/udp for emergency broadcasts +# Andreas Heidemann +windream 534/tcp windream Admin +windream 534/udp windream Admin +# Uwe Honermann +iiop 535/tcp iiop +iiop 535/udp iiop +# Jeff M.Michaud +opalis-rdv 536/tcp opalis-rdv +opalis-rdv 536/udp opalis-rdv +# Laurent Domenech +nmsp 537/tcp Networked Media Streaming Protocol +nmsp 537/udp Networked Media Streaming Protocol +# Paul Santinelli Jr. +gdomap 538/tcp gdomap +gdomap 538/udp gdomap +# Richard Frith-Macdonald +apertus-ldp 539/tcp Apertus Technologies Load Determination +apertus-ldp 539/udp Apertus Technologies Load Determination +uucp 540/tcp uucpd +uucp 540/udp uucpd +uucp-rlogin 541/tcp uucp-rlogin +uucp-rlogin 541/udp uucp-rlogin +# Stuart Lynne +commerce 542/tcp commerce +commerce 542/udp commerce +# Randy Epstein +klogin 543/tcp +klogin 543/udp +kshell 544/tcp krcmd +kshell 544/udp krcmd +appleqtcsrvr 545/tcp appleqtcsrvr +appleqtcsrvr 545/udp appleqtcsrvr +# Murali Ranganathan +# +dhcpv6-client 546/tcp DHCPv6 Client +dhcpv6-client 546/udp DHCPv6 Client +dhcpv6-server 547/tcp DHCPv6 Server +dhcpv6-server 547/udp DHCPv6 Server +# Jim Bound +afpovertcp 548/tcp AFP over TCP +afpovertcp 548/udp AFP over TCP +# Leland Wallace +idfp 549/tcp IDFP +idfp 549/udp IDFP +# Ramana Kovi +new-rwho 550/tcp new-who +new-rwho 550/udp new-who +cybercash 551/tcp cybercash +cybercash 551/udp cybercash +# Donald E. Eastlake 3rd +devshr-nts 552/tcp DeviceShare +devshr-nts 552/udp DeviceShare +# Benjamin Rosenberg +pirp 553/tcp pirp +pirp 553/udp pirp +# D. J. Bernstein +rtsp 554/tcp Real Time Streaming Protocol (RTSP) +rtsp 554/udp Real Time Streaming Protocol (RTSP) +# Rob Lanphier +dsf 555/tcp +dsf 555/udp +remotefs 556/tcp rfs server +remotefs 556/udp rfs server +openvms-sysipc 557/tcp openvms-sysipc +openvms-sysipc 557/udp openvms-sysipc +# Alan Potter +sdnskmp 558/tcp SDNSKMP +sdnskmp 558/udp SDNSKMP +teedtap 559/tcp TEEDTAP +teedtap 559/udp TEEDTAP +# Charlie Limoges +rmonitor 560/tcp rmonitord +rmonitor 560/udp rmonitord +monitor 561/tcp +monitor 561/udp +chshell 562/tcp chcmd +chshell 562/udp chcmd +nntps 563/tcp nntp protocol over TLS/SSL (was snntp) +nntps 563/udp nntp protocol over TLS/SSL (was snntp) +# Kipp E.B. Hickman +9pfs 564/tcp plan 9 file service +9pfs 564/udp plan 9 file service +whoami 565/tcp whoami +whoami 565/udp whoami +streettalk 566/tcp streettalk +streettalk 566/udp streettalk +banyan-rpc 567/tcp banyan-rpc +banyan-rpc 567/udp banyan-rpc +# Tom Lemaire +ms-shuttle 568/tcp microsoft shuttle +ms-shuttle 568/udp microsoft shuttle +# Rudolph Balaz +ms-rome 569/tcp microsoft rome +ms-rome 569/udp microsoft rome +# Rudolph Balaz +meter 570/tcp demon +meter 570/udp demon +meter 571/tcp udemon +meter 571/udp udemon +sonar 572/tcp sonar +sonar 572/udp sonar +# Keith Moore +banyan-vip 573/tcp banyan-vip +banyan-vip 573/udp banyan-vip +# Denis Leclerc +ftp-agent 574/tcp FTP Software Agent System +ftp-agent 574/udp FTP Software Agent System +# Michael S. Greenberg +vemmi 575/tcp VEMMI +vemmi 575/udp VEMMI +# Daniel Mavrakis +ipcd 576/tcp ipcd +ipcd 576/udp ipcd +vnas 577/tcp vnas +vnas 577/udp vnas +ipdd 578/tcp ipdd +ipdd 578/udp ipdd +# Jay Farhat +decbsrv 579/tcp decbsrv +decbsrv 579/udp decbsrv +# Rudi Martin +sntp-heartbeat 580/tcp SNTP HEARTBEAT +sntp-heartbeat 580/udp SNTP HEARTBEAT +# Louis Mamakos +bdp 581/tcp Bundle Discovery Protocol +bdp 581/udp Bundle Discovery Protocol +# Gary Malkin +scc-security 582/tcp SCC Security +scc-security 582/udp SCC Security +# Prashant Dholakia +philips-vc 583/tcp Philips Video-Conferencing +philips-vc 583/udp Philips Video-Conferencing +# Janna Chang +keyserver 584/tcp Key Server +keyserver 584/udp Key Server +# Gary Howland +# 585 De-registered (25 April 2006) +# Use of 585 is not recommended, use 993 instead +password-chg 586/tcp Password Change +password-chg 586/udp Password Change +submission 587/tcp Submission +submission 587/udp Submission +# [RFC4409] +cal 588/tcp CAL +cal 588/udp CAL +# Myron Hattig +eyelink 589/tcp EyeLink +eyelink 589/udp EyeLink +# Dave Stampe +tns-cml 590/tcp TNS CML +tns-cml 590/udp TNS CML +# Jerome Albin +http-alt 591/tcp FileMaker, Inc. - HTTP Alternate (see Port 80) +http-alt 591/udp FileMaker, Inc. - HTTP Alternate (see Port 80) +# Clay Maeckel +eudora-set 592/tcp Eudora Set +eudora-set 592/udp Eudora Set +# Randall Gellens +http-rpc-epmap 593/tcp HTTP RPC Ep Map +http-rpc-epmap 593/udp HTTP RPC Ep Map +# Edward Reus +tpip 594/tcp TPIP +tpip 594/udp TPIP +# Brad Spear +cab-protocol 595/tcp CAB Protocol +cab-protocol 595/udp CAB Protocol +# Winston Hetherington +smsd 596/tcp SMSD +smsd 596/udp SMSD +# Wayne Barlow +ptcnameservice 597/tcp PTC Name Service +ptcnameservice 597/udp PTC Name Service +# Yuri Machkasov +sco-websrvrmg3 598/tcp SCO Web Server Manager 3 +sco-websrvrmg3 598/udp SCO Web Server Manager 3 +# Simon Baldwin +acp 599/tcp Aeolon Core Protocol +acp 599/udp Aeolon Core Protocol +# Michael Alyn Miller +ipcserver 600/tcp Sun IPC server +ipcserver 600/udp Sun IPC server +# Bill Schiefelbein +syslog-conn 601/tcp Reliable Syslog Service +syslog-conn 601/udp Reliable Syslog Service +# RFC 3195 +xmlrpc-beep 602/tcp XML-RPC over BEEP +xmlrpc-beep 602/udp XML-RPC over BEEP +# RFC3529 March 2003 +idxp 603/tcp IDXP +idxp 603/udp IDXP +# RFC4767 +tunnel 604/tcp TUNNEL +tunnel 604/udp TUNNEL +# RFC3620 +soap-beep 605/tcp SOAP over BEEP +soap-beep 605/udp SOAP over BEEP +# RFC4227 April 2002 +urm 606/tcp Cray Unified Resource Manager +urm 606/udp Cray Unified Resource Manager +nqs 607/tcp nqs +nqs 607/udp nqs +# Bill Schiefelbein +sift-uft 608/tcp Sender-Initiated/Unsolicited File Transfer +sift-uft 608/udp Sender-Initiated/Unsolicited File Transfer +# Rick Troth +npmp-trap 609/tcp npmp-trap +npmp-trap 609/udp npmp-trap +npmp-local 610/tcp npmp-local +npmp-local 610/udp npmp-local +npmp-gui 611/tcp npmp-gui +npmp-gui 611/udp npmp-gui +# John Barnes +hmmp-ind 612/tcp HMMP Indication +hmmp-ind 612/udp HMMP Indication +hmmp-op 613/tcp HMMP Operation +hmmp-op 613/udp HMMP Operation +# Andrew Sinclair +sshell 614/tcp SSLshell +sshell 614/udp SSLshell +# Simon J. Gerraty +sco-inetmgr 615/tcp Internet Configuration Manager +sco-inetmgr 615/udp Internet Configuration Manager +sco-sysmgr 616/tcp SCO System Administration Server +sco-sysmgr 616/udp SCO System Administration Server +sco-dtmgr 617/tcp SCO Desktop Administration Server +sco-dtmgr 617/udp SCO Desktop Administration Server +# Christopher Durham +dei-icda 618/tcp DEI-ICDA +dei-icda 618/udp DEI-ICDA +# David Turner +compaq-evm 619/tcp Compaq EVM +compaq-evm 619/udp Compaq EVM +# Jem Treadwell +sco-websrvrmgr 620/tcp SCO WebServer Manager +sco-websrvrmgr 620/udp SCO WebServer Manager +# Christopher Durham +escp-ip 621/tcp ESCP +escp-ip 621/udp ESCP +# Lai Zit Seng +collaborator 622/tcp Collaborator +collaborator 622/udp Collaborator +# Johnson Davis +oob-ws-http 623/tcp DMTF out-of-band web services management protocol +# Jim Davis June 2007 +asf-rmcp 623/udp ASF Remote Management and Control Protocol +# Carl First +cryptoadmin 624/tcp Crypto Admin +cryptoadmin 624/udp Crypto Admin +# Tony Walker +dec_dlm 625/tcp DEC DLM +dec_dlm 625/udp DEC DLM +# Rudi Martin +asia 626/tcp ASIA +asia 626/udp ASIA +# Michael Dasenbrock +passgo-tivoli 627/tcp PassGo Tivoli +passgo-tivoli 627/udp PassGo Tivoli +# John Rainford +qmqp 628/tcp QMQP +qmqp 628/udp QMQP +# Dan Bernstein +3com-amp3 629/tcp 3Com AMP3 +3com-amp3 629/udp 3Com AMP3 +# Prakash Banthia +rda 630/tcp RDA +rda 630/udp RDA +# John Hadjioannou +ipp 631/tcp IPP (Internet Printing Protocol) +ipp 631/udp IPP (Internet Printing Protocol) +# Carl-Uno Manros +bmpp 632/tcp bmpp +bmpp 632/udp bmpp +# Troy Rollo +servstat 633/tcp Service Status update (Sterling Software) +servstat 633/udp Service Status update (Sterling Software) +# Greg Rose +ginad 634/tcp ginad +ginad 634/udp ginad +# Mark Crother +rlzdbase 635/tcp RLZ DBase +rlzdbase 635/udp RLZ DBase +# Michael Ginn +ldaps 636/tcp ldap protocol over TLS/SSL (was sldap) +ldaps 636/udp ldap protocol over TLS/SSL (was sldap) +# Pat Richard +lanserver 637/tcp lanserver +lanserver 637/udp lanserver +# Chris Larsson +mcns-sec 638/tcp mcns-sec +mcns-sec 638/udp mcns-sec +# Kaz Ozawa +msdp 639/tcp MSDP +msdp 639/udp MSDP +# Dino Farinacci +entrust-sps 640/tcp entrust-sps +entrust-sps 640/udp entrust-sps +# Marek Buchler +repcmd 641/tcp repcmd +repcmd 641/udp repcmd +# Scott Dale +esro-emsdp 642/tcp ESRO-EMSDP V1.3 +esro-emsdp 642/udp ESRO-EMSDP V1.3 +# Mohsen Banan +sanity 643/tcp SANity +sanity 643/udp SANity +# Peter Viscarola +dwr 644/tcp dwr +dwr 644/udp dwr +# Bill Fenner +pssc 645/tcp PSSC +pssc 645/udp PSSC +# Egon Meier-Engelen +ldp 646/tcp LDP +ldp 646/udp LDP +# Bob Thomas +dhcp-failover 647/tcp DHCP Failover +dhcp-failover 647/udp DHCP Failover +# Bernard Volz +rrp 648/tcp Registry Registrar Protocol (RRP) +rrp 648/udp Registry Registrar Protocol (RRP) +# Scott Hollenbeck +cadview-3d 649/tcp Cadview-3d - streaming 3d models over the internet +cadview-3d 649/udp Cadview-3d - streaming 3d models over the internet +# David Cooper +obex 650/tcp OBEX +obex 650/udp OBEX +# Jeff Garbers +ieee-mms 651/tcp IEEE MMS +ieee-mms 651/udp IEEE MMS +# Curtis Anderson +hello-port 652/tcp HELLO_PORT +hello-port 652/udp HELLO_PORT +# Patrick Cipiere +repscmd 653/tcp RepCmd +repscmd 653/udp RepCmd +# Scott Dale +aodv 654/tcp AODV +aodv 654/udp AODV +# Charles Perkins +tinc 655/tcp TINC +tinc 655/udp TINC +# Ivo Timmermans +spmp 656/tcp SPMP +spmp 656/udp SPMP +# Jakob Kaivo +rmc 657/tcp RMC +rmc 657/udp RMC +# Michael Schmidt +tenfold 658/tcp TenFold +tenfold 658/udp TenFold +# Louis Olszyk +# 659 Removed (2001-06-06) +mac-srvr-admin 660/tcp MacOS Server Admin +mac-srvr-admin 660/udp MacOS Server Admin +# Forest Hill +hap 661/tcp HAP +hap 661/udp HAP +# Igor Plotnikov +pftp 662/tcp PFTP +pftp 662/udp PFTP +# Ben Schluricke +purenoise 663/tcp PureNoise +purenoise 663/udp PureNoise +# Sam Osa +oob-ws-https 664/tcp DMTF out-of-band secure web services management protocol +# Jim Davis June 2007 +asf-secure-rmcp 664/udp ASF Secure Remote Management and Control Protocol +# Carl First +sun-dr 665/tcp Sun DR +sun-dr 665/udp Sun DR +# Harinder Bhasin +mdqs 666/tcp +mdqs 666/udp +doom 666/tcp doom Id Software +doom 666/udp doom Id Software +# +disclose 667/tcp campaign contribution disclosures - SDR Technologies +disclose 667/udp campaign contribution disclosures - SDR Technologies +# Jim Dixon +mecomm 668/tcp MeComm +mecomm 668/udp MeComm +meregister 669/tcp MeRegister +meregister 669/udp MeRegister +# Armin Sawusch +vacdsm-sws 670/tcp VACDSM-SWS +vacdsm-sws 670/udp VACDSM-SWS +vacdsm-app 671/tcp VACDSM-APP +vacdsm-app 671/udp VACDSM-APP +vpps-qua 672/tcp VPPS-QUA +vpps-qua 672/udp VPPS-QUA +cimplex 673/tcp CIMPLEX +cimplex 673/udp CIMPLEX +# Ulysses G. Smith Jr. +acap 674/tcp ACAP +acap 674/udp ACAP +# Chris Newman +dctp 675/tcp DCTP +dctp 675/udp DCTP +# Andre Kramer +vpps-via 676/tcp VPPS Via +vpps-via 676/udp VPPS Via +# Ulysses G. Smith Jr. +vpp 677/tcp Virtual Presence Protocol +vpp 677/udp Virtual Presence Protocol +# Klaus Wolf +ggf-ncp 678/tcp GNU Generation Foundation NCP +ggf-ncp 678/udp GNU Generation Foundation NCP +# Noah Paul +mrm 679/tcp MRM +mrm 679/udp MRM +# Liming Wei +entrust-aaas 680/tcp entrust-aaas +entrust-aaas 680/udp entrust-aaas +entrust-aams 681/tcp entrust-aams +entrust-aams 681/udp entrust-aams +# Adrian Mancini +xfr 682/tcp XFR +xfr 682/udp XFR +# Noah Paul +corba-iiop 683/tcp CORBA IIOP +corba-iiop 683/udp CORBA IIOP +corba-iiop-ssl 684/tcp CORBA IIOP SSL +corba-iiop-ssl 684/udp CORBA IIOP SSL +# Andrew Watson +mdc-portmapper 685/tcp MDC Port Mapper +mdc-portmapper 685/udp MDC Port Mapper +# Noah Paul +hcp-wismar 686/tcp Hardware Control Protocol Wismar +hcp-wismar 686/udp Hardware Control Protocol Wismar +# David Merchant +asipregistry 687/tcp asipregistry +asipregistry 687/udp asipregistry +# Erik Sea +realm-rusd 688/tcp ApplianceWare managment protocol +realm-rusd 688/udp ApplianceWare managment protocol +# Stacy Kenworthy +nmap 689/tcp NMAP +nmap 689/udp NMAP +# Peter Dennis Bartok +vatp 690/tcp Velazquez Application Transfer Protocol +vatp 690/udp Velazquez Application Transfer Protocol +# Velneo +msexch-routing 691/tcp MS Exchange Routing +msexch-routing 691/udp MS Exchange Routing +# David Lemson +hyperwave-isp 692/tcp Hyperwave-ISP +hyperwave-isp 692/udp Hyperwave-ISP +# Gerald Mesaric +connendp 693/tcp almanid Connection Endpoint +connendp 693/udp almanid Connection Endpoint +# Ronny Bremer +ha-cluster 694/tcp ha-cluster +ha-cluster 694/udp ha-cluster +# Alan Robertson +ieee-mms-ssl 695/tcp IEEE-MMS-SSL +ieee-mms-ssl 695/udp IEEE-MMS-SSL +# Curtis Anderson +rushd 696/tcp RUSHD +rushd 696/udp RUSHD +# Greg Ercolano +uuidgen 697/tcp UUIDGEN +uuidgen 697/udp UUIDGEN +# James Falkner +olsr 698/tcp OLSR +olsr 698/udp OLSR +# Thomas Clausen +accessnetwork 699/tcp Access Network +accessnetwork 699/udp Access Network +# Yingchun Xu +epp 700/tcp Extensible Provisioning Protocol +epp 700/udp Extensible Provisioning Protocol +# [RFC5734] +lmp 701/tcp Link Management Protocol (LMP) +lmp 701/udp Link Management Protocol (LMP) +# [RFC4204] +iris-beep 702/tcp IRIS over BEEP +iris-beep 702/udp IRIS over BEEP +# [RFC3983] +# 703 Unassigned +elcsd 704/tcp errlog copy/server daemon +elcsd 704/udp errlog copy/server daemon +agentx 705/tcp AgentX +agentx 705/udp AgentX +# Bob Natale +silc 706/tcp SILC +silc 706/udp SILC +# Pekka Riikonen +borland-dsj 707/tcp Borland DSJ +borland-dsj 707/udp Borland DSJ +# Gerg Cole +# 708 Unassigned +entrust-kmsh 709/tcp Entrust Key Management Service Handler +entrust-kmsh 709/udp Entrust Key Management Service Handler +entrust-ash 710/tcp Entrust Administration Service Handler +entrust-ash 710/udp Entrust Administration Service Handler +# Peter Whittaker +cisco-tdp 711/tcp Cisco TDP +cisco-tdp 711/udp Cisco TDP +# Bruce Davie +tbrpf 712/tcp TBRPF +tbrpf 712/udp TBRPF +# RFC3684 +iris-xpc 713/tcp IRIS over XPC +iris-xpc 713/udp IRIS over XPC +iris-xpcs 714/tcp IRIS over XPCS +iris-xpcs 714/udp IRIS over XPCS +# [RFC4992] +iris-lwz 715/tcp IRIS-LWZ +iris-lwz 715/udp IRIS-LWZ +# [RFC4993] +pana 716/udp PANA Messages +# [RFC5191] +# 717-728 Unassigned +netviewdm1 729/tcp IBM NetView DM/6000 Server/Client +netviewdm1 729/udp IBM NetView DM/6000 Server/Client +netviewdm2 730/tcp IBM NetView DM/6000 send/tcp +netviewdm2 730/udp IBM NetView DM/6000 send/tcp +netviewdm3 731/tcp IBM NetView DM/6000 receive/tcp +netviewdm3 731/udp IBM NetView DM/6000 receive/tcp +# Philippe Binet (phbinet&vnet.IBM.COM) +# 732-740 Unassigned +netgw 741/tcp netGW +netgw 741/udp netGW +# Oliver Korfmacher (okorf&netcs.com) +netrcs 742/tcp Network based Rev. Cont. Sys. +netrcs 742/udp Network based Rev. Cont. Sys. +# Gordon C. Galligher +# 743 Unassigned +flexlm 744/tcp Flexible License Manager +flexlm 744/udp Flexible License Manager +# Matt Christiano +# +# 745-746 Unassigned +fujitsu-dev 747/tcp Fujitsu Device Control +fujitsu-dev 747/udp Fujitsu Device Control +ris-cm 748/tcp Russell Info Sci Calendar Manager +ris-cm 748/udp Russell Info Sci Calendar Manager +kerberos-adm 749/tcp kerberos administration +kerberos-adm 749/udp kerberos administration +rfile 750/tcp +loadav 750/udp +kerberos-iv 750/udp kerberos version iv +# Martin Hamilton +pump 751/tcp +pump 751/udp +qrh 752/tcp +qrh 752/udp +rrh 753/tcp +rrh 753/udp +tell 754/tcp send +tell 754/udp send +# Josyula R. Rao +# 755-756 Unassigned +nlogin 758/tcp +nlogin 758/udp +con 759/tcp +con 759/udp +ns 760/tcp +ns 760/udp +rxe 761/tcp +rxe 761/udp +quotad 762/tcp +quotad 762/udp +cycleserv 763/tcp +cycleserv 763/udp +omserv 764/tcp +omserv 764/udp +webster 765/tcp +webster 765/udp +# Josyula R. Rao +# 766 Unassigned +phonebook 767/tcp phone +phonebook 767/udp phone +# Josyula R. Rao +# 768 Unassigned +vid 769/tcp +vid 769/udp +cadlock 770/tcp +cadlock 770/udp +rtip 771/tcp +rtip 771/udp +cycleserv2 772/tcp +cycleserv2 772/udp +submit 773/tcp +notify 773/udp +rpasswd 774/tcp +acmaint_dbd 774/udp +entomb 775/tcp +acmaint_transd 775/udp +wpages 776/tcp +wpages 776/udp +# Josyula R. Rao +multiling-http 777/tcp Multiling HTTP +multiling-http 777/udp Multiling HTTP +# Alejandro Bonet +# 778-779 Unassigned +wpgs 780/tcp +wpgs 780/udp +# Josyula R. Rao +# 781-785 Unassigned +# 786 Unassigned (Removed 2002-05-08) +# 787 Unassigned (Removed 2002-10-08) +# 788-799 Unassigned +mdbs_daemon 800/tcp +mdbs_daemon 800/udp +device 801/tcp +device 801/udp +# 802-809 Unassigned +fcp-udp 810/tcp FCP +fcp-udp 810/udp FCP Datagram +# Paul Whittemore +# 811-827 Unassigned +itm-mcell-s 828/tcp itm-mcell-s +itm-mcell-s 828/udp itm-mcell-s +# Portnoy Boxman +pkix-3-ca-ra 829/tcp PKIX-3 CA/RA +pkix-3-ca-ra 829/udp PKIX-3 CA/RA +# Carlisle Adams +netconf-ssh 830/tcp NETCONF over SSH +netconf-ssh 830/udp NETCONF over SSH +# [RFC4742] +netconf-beep 831/tcp NETCONF over BEEP +netconf-beep 831/udp NETCONF over BEEP +# [RFC4744] +netconfsoaphttp 832/tcp NETCONF for SOAP over HTTPS +netconfsoaphttp 832/udp NETCONF for SOAP over HTTPS +# [RFC4743] +netconfsoapbeep 833/tcp NETCONF for SOAP over BEEP +netconfsoapbeep 833/udp NETCONF for SOAP over BEEP +# [RFC4743] +# 834-846 Unassigned +dhcp-failover2 847/tcp dhcp-failover 2 +dhcp-failover2 847/udp dhcp-failover 2 +# Bernard Volz +gdoi 848/tcp GDOI +gdoi 848/udp GDOI +# [RFC3547] +# 849-859 Unassigned +iscsi 860/tcp iSCSI +iscsi 860/udp iSCSI +# RFC3720 +owamp-control 861/tcp OWAMP-Control +owamp-control 861/udp OWAMP-Control +# [RFC4656] +twamp-control 862/tcp Two-way Active Measurement Protocol (TWAMP) Control +twamp-control 862/udp Two-way Active Measurement Protocol (TWAMP) Control +# [RFC5357] +# 863-872 Unassigned +rsync 873/tcp rsync +rsync 873/udp rsync +# Andrew Tridgell +# 874-885 Unassigned +iclcnet-locate 886/tcp ICL coNETion locate server +iclcnet-locate 886/udp ICL coNETion locate server +# Bob Lyon +iclcnet_svinfo 887/tcp ICL coNETion server info +iclcnet_svinfo 887/udp ICL coNETion server info +# Bob Lyon +accessbuilder 888/tcp AccessBuilder +accessbuilder 888/udp AccessBuilder +# Steve Sweeney +# The following entry records an unassigned but widespread use +cddbp 888/tcp CD Database Protocol +# Steve Scherf +# +# 889-899 Unassigned +omginitialrefs 900/tcp OMG Initial Refs +omginitialrefs 900/udp OMG Initial Refs +# Christian Callsen +smpnameres 901/tcp SMPNAMERES +smpnameres 901/udp SMPNAMERES +# Leif Ekblad +ideafarm-door 902/tcp self documenting Telnet Door +ideafarm-door 902/udp self documenting Door: send 0x00 for info +ideafarm-panic 903/tcp self documenting Telnet Panic Door +ideafarm-panic 903/udp self documenting Panic Door: send 0x00 for info +# Wo'o Ideafarm +# 904-909 Unassigned +kink 910/tcp Kerberized Internet Negotiation of Keys (KINK) +kink 910/udp Kerberized Internet Negotiation of Keys (KINK) +# [RFC4430] +xact-backup 911/tcp xact-backup +xact-backup 911/udp xact-backup +# Bill Carroll +apex-mesh 912/tcp APEX relay-relay service +apex-mesh 912/udp APEX relay-relay service +apex-edge 913/tcp APEX endpoint-relay service +apex-edge 913/udp APEX endpoint-relay service +# [RFC3340] +# 914-988 Unassigned +ftps-data 989/tcp ftp protocol, data, over TLS/SSL +ftps-data 989/udp ftp protocol, data, over TLS/SSL +ftps 990/tcp ftp protocol, control, over TLS/SSL +ftps 990/udp ftp protocol, control, over TLS/SSL +# Christopher Allen +nas 991/tcp Netnews Administration System +nas 991/udp Netnews Administration System +# Vera Heinau +# Heiko Schlichting +telnets 992/tcp telnet protocol over TLS/SSL +telnets 992/udp telnet protocol over TLS/SSL +imaps 993/tcp imap4 protocol over TLS/SSL +imaps 993/udp imap4 protocol over TLS/SSL +ircs 994/tcp irc protocol over TLS/SSL +ircs 994/udp irc protocol over TLS/SSL +# Christopher Allen +pop3s 995/tcp pop3 protocol over TLS/SSL (was spop3) +pop3s 995/udp pop3 protocol over TLS/SSL (was spop3) +# Gordon Mangione +vsinet 996/tcp vsinet +vsinet 996/udp vsinet +# Rob Juergens +maitrd 997/tcp +maitrd 997/udp +busboy 998/tcp +puparp 998/udp +garcon 999/tcp +applix 999/udp Applix ac +puprouter 999/tcp +puprouter 999/udp +cadlock2 1000/tcp +cadlock2 1000/udp +# 1001-1009 Unassigned +# 1008/udp Possibly used by Sun Solaris???? +surf 1010/tcp surf +surf 1010/udp surf +# Joseph Geer +# 1011-1020 Reserved +exp1 1021/tcp RFC3692-style Experiment 1 (*) [RFC4727] +exp1 1021/udp RFC3692-style Experiment 1 (*) [RFC4727] +exp2 1022/tcp RFC3692-style Experiment 2 (*) [RFC4727] +exp2 1022/udp RFC3692-style Experiment 2 (*) [RFC4727] + 1023/tcp Reserved + 1023/udp Reserved +# IANA diff --git a/src-wip/org/handwerkszeug/dns/client/ProtocolNumbers.xml b/src-wip/org/handwerkszeug/dns/client/ProtocolNumbers.xml new file mode 100755 index 0000000..1e89089 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/client/ProtocolNumbers.xml @@ -0,0 +1,1262 @@ + + + + + Protocol Numbers + 2010-05-27 + + Assigned Internet Protocol Numbers + + IESG Approval or Standards Action + In the Internet Protocol version 4 (IPv4) there is a field +called "Protocol" to identify the next level protocol. This is an 8 +bit field. In Internet Protocol version 6 (IPv6) , this field +is called the "Next Header" field. + + 0 + HOPOPT + IPv6 Hop-by-Hop Option + + + + 1 + ICMP + Internet Control Message + + + + 2 + IGMP + Internet Group Management + + + + 3 + GGP + Gateway-to-Gateway + + + + 4 + IPv4 + IPv4 encapsulation + + + + 5 + ST + Stream + + + + + 6 + TCP + Transmission Control + + + + 7 + CBT + CBT + + + + 8 + EGP + Exterior Gateway Protocol + + + + + 9 + IGP + any private interior gateway +(used by Cisco for their IGRP) + + + + 10 + BBN-RCC-MON + BBN RCC Monitoring + + + + 11 + NVP-II + Network Voice Protocol + + + + + 12 + PUP + PUP + Boggs, D., J. Shoch, E. Taft, and R. Metcalfe, "PUP: An +Internetwork Architecture", XEROX Palo Alto Research Center, +CSL-79-10, July 1979; also in IEEE Transactions on +Communication, Volume COM-28, Number 4, April 1980. + [XEROX] + + + 13 + ARGUS + ARGUS + + + + 14 + EMCON + EMCON + <mystery contact> + + + 15 + XNET + Cross Net Debugger + Haverty, J., "XNET Formats for Internet Protocol Version 4", +IEN 158, October 1980. + + + + 16 + CHAOS + Chaos + + + + 17 + UDP + User Datagram + + + + + 18 + MUX + Multiplexing + Cohen, D. and J. Postel, "Multiplexing Protocol", IEN 90, +USC/Information Sciences Institute, May 1979. + + + + 19 + DCN-MEAS + DCN Measurement Subsystems + + + + 20 + HMP + Host Monitoring + + + + + 21 + PRM + Packet Radio Measurement + + + + 22 + XNS-IDP + XEROX NS IDP + "The Ethernet, A Local Area Network: Data Link Layer and +Physical Layer Specification", AA-K759B-TK, Digital +Equipment Corporation, Maynard, MA. Also as: "The +Ethernet - A Local Area Network", Version 1.0, Digital +Equipment Corporation, Intel Corporation, Xerox +Corporation, September 1980. And: "The Ethernet, A Local +Area Network: Data Link Layer and Physical Layer +Specifications", Digital, Intel and Xerox, November 1982. +And: XEROX, "The Ethernet, A Local Area Network: Data Link +Layer and Physical Layer Specification", X3T51/80-50, +Xerox Corporation, Stamford, CT., October 1980. + [XEROX] + + + 23 + TRUNK-1 + Trunk-1 + + + + 24 + TRUNK-2 + Trunk-2 + + + + 25 + LEAF-1 + Leaf-1 + + + + 26 + LEAF-2 + Leaf-2 + + + + 27 + RDP + Reliable Data Protocol + + + + + 28 + IRTP + Internet Reliable Transaction + + + + + 29 + ISO-TP4 + ISO Transport Protocol Class 4 + + <mystery contact> + + + 30 + NETBLT + Bulk Data Transfer Protocol + + + + + 31 + MFE-NSP + MFE Network Services Protocol + Shuttleworth, B., "A Documentary of MFENet, a National +Computer Network", UCRL-52317, Lawrence Livermore Labs, +Livermore, California, June 1977. + + + + 32 + MERIT-INP + MERIT Internodal Protocol + + + + 33 + DCCP + Datagram Congestion Control Protocol + + + + 34 + 3PC + Third Party Connect Protocol + + + + 35 + IDPR + Inter-Domain Policy Routing Protocol + + + + 36 + XTP + XTP + + + + 37 + DDP + Datagram Delivery Protocol + + + + 38 + IDPR-CMTP + IDPR Control Message Transport Proto + + + + 39 + TP++ + TP++ Transport Protocol + + + + 40 + IL + IL Transport Protocol + + + + 41 + IPv6 + IPv6 encapsulation + + + + 42 + SDRP + Source Demand Routing Protocol + + + + 43 + IPv6-Route + Routing Header for IPv6 + + + + 44 + IPv6-Frag + Fragment Header for IPv6 + + + + 45 + IDRP + Inter-Domain Routing Protocol + + + + 46 + RSVP + Reservation Protocol + + + + 47 + GRE + General Routing Encapsulation + + + + 48 + DSR + Dynamic Source Routing Protocol + + + + 49 + BNA + BNA + Gary Salamon + + + 50 + ESP + Encap Security Payload + + + + 51 + AH + Authentication Header + + + + 52 + I-NLSP + Integrated Net Layer Security TUBA + + + + 53 + SWIPE + IP with Encryption + + + + 54 + NARP + NBMA Address Resolution Protocol + + + + 55 + MOBILE + IP Mobility + + + + 56 + TLSP + Transport Layer Security Protocol +using Kryptonet key management + + + + 57 + SKIP + SKIP + + + + 58 + IPv6-ICMP + ICMP for IPv6 + + + + 59 + IPv6-NoNxt + No Next Header for IPv6 + + + + 60 + IPv6-Opts + Destination Options for IPv6 + + + + 61 + any host internal protocol + + + + 62 + CFTP + CFTP + Forsdick, H., "CFTP", Network Message, Bolt Beranek and +Newman, January 1982. + + + + 63 + any local network + + + + 64 + SAT-EXPAK + SATNET and Backroom EXPAK + + + + 65 + KRYPTOLAN + Kryptolan + Paul Liu + + + 66 + RVD + MIT Remote Virtual Disk Protocol + + + + 67 + IPPC + Internet Pluribus Packet Core + + + + 68 + any distributed file system + + + + 69 + SAT-MON + SATNET Monitoring + + + + 70 + VISA + VISA Protocol + + + + 71 + IPCV + Internet Packet Core Utility + + + + 72 + CPNX + Computer Protocol Network Executive + David Mittnacht + + + 73 + CPHB + Computer Protocol Heart Beat + David Mittnacht + + + 74 + WSN + Wang Span Network + Victor Dafoulas + + + 75 + PVP + Packet Video Protocol + + + + 76 + BR-SAT-MON + Backroom SATNET Monitoring + + + + 77 + SUN-ND + SUN ND PROTOCOL-Temporary + + + + 78 + WB-MON + WIDEBAND Monitoring + + + + 79 + WB-EXPAK + WIDEBAND EXPAK + + + + 80 + ISO-IP + ISO Internet Protocol + + + + 81 + VMTP + VMTP + + + + 82 + SECURE-VMTP + SECURE-VMTP + + + + 83 + VINES + VINES + Brian Horn + + + 84 + TTP + TTP + + + + 85 + NSFNET-IGP + NSFNET-IGP + + + + 86 + DGP + Dissimilar Gateway Protocol + M/A-COM Government Systems, "Dissimilar Gateway Protocol +Specification, Draft Version", Contract no. CS901145, +November 16, 1987. + + + + 87 + TCF + TCF + + + + 88 + EIGRP + EIGRP + Cisco Systems, "Gateway Server Reference Manual", Manual +Revision B, January 10, 1988. + + + + 89 + OSPFIGP + OSPFIGP + + + + + 90 + Sprite-RPC + Sprite RPC Protocol + Welch, B., "The Sprite Remote Procedure Call System", +Technical Report, UCB/Computer Science Dept., 86/302, +University of California at Berkeley, June 1986. + Bruce Willins + + + 91 + LARP + Locus Address Resolution Protocol + Brian Horn + + + 92 + MTP + Multicast Transport Protocol + + + + 93 + AX.25 + AX.25 Frames + + + + 94 + IPIP + IP-within-IP Encapsulation Protocol + + + + 95 + MICP + Mobile Internetworking Control Pro. + + + + 96 + SCC-SP + Semaphore Communications Sec. Pro. + + + + 97 + ETHERIP + Ethernet-within-IP Encapsulation + + + + 98 + ENCAP + Encapsulation Header + + + + + 99 + any private encryption scheme + + + + 100 + GMTP + GMTP + [RXB5] + + + 101 + IFMP + Ipsilon Flow Management Protocol + + November 1995, 1997. + + + 102 + PNNI + PNNI over IP + + + + 103 + PIM + Protocol Independent Multicast + + + + 104 + ARIS + ARIS + + + + 105 + SCPS + SCPS + + + + 106 + QNX + QNX + + + + 107 + A/N + Active Networks + + + + 108 + IPComp + IP Payload Compression Protocol + + + + 109 + SNP + Sitara Networks Protocol + + + + 110 + Compaq-Peer + Compaq Peer Protocol + + + + 111 + IPX-in-IP + IPX in IP + + + + 112 + VRRP + Virtual Router Redundancy Protocol + + + + + 113 + PGM + PGM Reliable Transport Protocol + + + + 114 + any 0-hop protocol + + + + 115 + L2TP + Layer Two Tunneling Protocol + + + + 116 + DDX + D-II Data Exchange (DDX) + + + + 117 + IATP + Interactive Agent Transfer Protocol + + + + 118 + STP + Schedule Transfer Protocol + + + + 119 + SRP + SpectraLink Radio Protocol + + + + 120 + UTI + UTI + + + + 121 + SMP + Simple Message Protocol + + + + 122 + SM + SM + + + + 123 + PTP + Performance Transparency Protocol + + + + 124 + ISIS over IPv4 + + + + 125 + FIRE + + + + 126 + CRTP + Combat Radio Transport Protocol + + + + 127 + CRUDP + Combat Radio User Datagram + + + + 128 + SSCOPMCE + + + + 129 + IPLT + [Hollbach] + + + 130 + SPS + Secure Packet Shield + + + + 131 + PIPE + Private IP Encapsulation within IP + + + + 132 + SCTP + Stream Control Transmission Protocol + + + + 133 + FC + Fibre Channel + + + + 134 + RSVP-E2E-IGNORE + + + + 135 + Mobility Header + + + + 136 + UDPLite + + + + 137 + MPLS-in-IP + + + + 138 + manet + MANET Protocols + + + + 139 + HIP + Host Identity Protocol + + + + 140 + Shim6 + Shim6 Protocol + + + + 141 + WESP + Wrapped Encapsulating Security Payload + + + + 142 + ROHC + Robust Header Compression + + + + 143-252 + Unassigned + + + + 253 + Use for experimentation and testing + + + + 254 + Use for experimentation and testing + + + + 255 + Reserved + + + + + + Barry Boehm + mailto:boehm&arpa.mil + + + Barry Howard + mailto:Howard&nmfecc.llnl.gov + + + Bernard Aboba + mailto:bernarda&microsoft.com + 1998-04 + + + Bernhard Petri + mailto:bernhard.petri&nsn.com + 2000-03 + + + Bill McIntosh + mailto:BMcIntosh&fortresstech.com + + + Bob Braden + mailto:braden&isi.edu + 1997-07 + + + Bob Hinden + mailto:hinden&ipsilon.com + + + Brian Kantor + mailto:brian&ucsd.edu + + + CJ Lee + mailto:cj_lee&novell.com + 1997-10 + + + Charlie Perkins + mailto:perk&watson.ibm.com + 1994-10 + + + Christer Oberg + mailto:chg&bull.se + 1994-10 + + + Criag Partridge + mailto:craig&bbn.com + 1999-08 + + + Dave Cheriton + mailto:cheriton&pescadero.stanford.edu + + + Dave Presotto + mailto:presotto&plan9.att.com + 1995-07 + + + David Clark + mailto:ddc&lcs.mit.edu + + + David Mills + mailto:Mills&huey.udel.edu + + + Deborah Estrin + mailto:estrin&usc.edu + + + Dino Farinacci + mailto:dino&cisco.com + 1996-03 + + + Dirk Fromhein + mailto:df&watershed.com + + + Gene Tsudik + mailto:tsudik&usc.edu + + + Greg Chesson + mailto:Greg&sgi.com + + + Guenther Schreiner + mailto:snmp-admin&ira.uka.de + + + Guillermo A. Loyola + mailto:LOYOLA&ibm.com + + + Hans-Werner Braun + mailto:HWB&mcr.umich.edu + + + Harry Forsdick + mailto:Forsdick&bbn.com + + + Howard Hart + mailto:hch&hybrid.com + + + Internet Assigned Numbers Authority + mailto:iana&iana.org + 1995-06 + + + J. Noel Chiappa + mailto:JNC&xx.lcs.mit.edu + + + Jack Haverty + mailto:jhaverty&oracle.com + + + Jean-Michel Pittet + mailto:jmp&gandalf.engr.sgi.com + 1998-11 + + + Jim Stevens + mailto:Stevens&isi.edu + + + John Ioannidis + mailto:ji&cs.columbia.edu + + + John Moy + mailto:jmoy&proteon.com + + + John Murphy + mailto:john.m.murphy&mci.com + 1998-10 + + + John Worley + mailto:worley&milehigh.net + 1998-06 + + + Jon Crowcroft + mailto:jon&cs.ucl.ac.uk + 1999-06 + + + Jon Postel + mailto:postel&isi.edu + + + K. Robert Glenn + mailto:glenn&osi.ncsl.nist.gov + + + Kurt Waber + mailto:kurt.waber&swisscom.com + 1999-08 + + + Leif Ekblad + mailto:leif.ekblad&eslov.mail.telia.com + 1999-03 + + + Manickam R. Sridhar + mailto:msridhar&sitaranetworks.com + 1997-09 + + + Mark Hamilton + mailto:mah&spectralink.com + 1998-11 + + + Marshall T. Rose + mailto:mrose&dbc.mtview.ca.us + + + Martha Steenstrup + mailto:MSteenst&bbn.com + + + Michael Greenwald + mailto:Greenwald&scrc-stony-brook.symbolics.com + + + Michael Hunter + mailto:mphunter&qnx.com + 1997-07 + + + Michael Welzl + mailto:michael&tk.uni-linz.ac.at + 1999-08 + + + Mike Little + mailto:little&macom4.arpa + + + Murali Rajagopal + mailto:murali&gadzoox.com + 2000-05 + + + Nancy Feldman + mailto:nkf&vnet.ibm.com + 1997-01 + + + Peter Lothberg + mailto:roll&stupi.se + 1999-03 + + + Randall R. Stewart + mailto:rrs&lakerest.net + 2000-04 + + + Robert Durst + mailto:durst&mitre.org + 1997-03 + + + Robert Hinden + mailto:Hinden&eng.sun.com + + + Robert Sautter + mailto:rsautter&acdnj.itt.com + 1999-08 + + + Robert W. Scheifler + mailto:RWS&xx.lcs.mit.edu + + + Robert Woodburn + mailto:woody&cseic.saic.com + + + Ross Callon + mailto:rcallon&baynetworks.com + 1995-12 + + + Steve Casner + mailto:casner&isi.edu + + + Steve Chipman + mailto:Chipman&f.bbn.com + + + Steve Deering + mailto:deering&parc.xerox.com + 1995-03 + + + Steven Blumenthal + mailto:BLUMENTHAL&vax.bbn.com + + + Stuart A. Friedberg + mailto:stuart&cs.wisc.edu + + + Sue Hares + mailto:skh&merit.edu + + + Susie Armstrong + mailto:Armstrong.wbst128&xerox.com + + + Tom Markson + mailto:markson&osmosys.ingog.com + 1995-09 + + + Tony Ballardie + mailto:A.Ballardie&cs.ucl.ac.uk + + + Tony Li + mailto:tli&cisco.com + + + Tony Przygienda + mailto:prz&siara.com + 1999-08 + + + Tony Speakman + mailto:speakman&cisco.com + 1998-01 + + + Trudy Miller + mailto:Trudy&acc.com + + + Victor Volpe + mailto:vvolpe&smtp.microcom.com + 1997-10 + + + Wesley Craig + mailto:Wesley.Craig&terminator.cc.umich.edu + + + William Melohn + mailto:Melohn&sun.com + + + Zaw-Sing Su + mailto:ZSu&tsca.istc.sri. + + + diff --git a/src-wip/org/handwerkszeug/dns/client/WKPortNumbers.java b/src-wip/org/handwerkszeug/dns/client/WKPortNumbers.java new file mode 100755 index 0000000..719ffa9 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/client/WKPortNumbers.java @@ -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; + +/** + * PORT NUMBERS + * + * @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 skipWords = new HashSet(); + static { + skipWords.add("Reserved"); + skipWords.add("Unassigned"); + skipWords.add("Discard"); + } + + protected Map ports = new HashMap(); + protected Map keywords = new HashMap(); + + 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() { + + @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 list = new ArrayList(); + 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 getServices(WKSRecord record) { + byte[] bitmap = record.bitmap(); + List result = new ArrayList(); + 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(' '); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/client/WKProtocols.java b/src-wip/org/handwerkszeug/dns/client/WKProtocols.java new file mode 100755 index 0000000..9b0c83a --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/client/WKProtocols.java @@ -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; + +/** + * + * Protocol + * Numbers + * + * @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 protocols = new HashMap(); + + 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() { + + @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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/DefaultNameServerContainer.java b/src-wip/org/handwerkszeug/dns/conf/DefaultNameServerContainer.java new file mode 100755 index 0000000..5191107 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/DefaultNameServerContainer.java @@ -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, ChainResult> executor; + + @Override + public + String name() { + return NameServerContainerProvider.DEFAULT_NAME; + } + + @Override + public + List nameservers() { + List result = new ArrayList(); + this.executor.execute(result); + return result; + } + + @Override + public + void initialize() { + // FIXME this code run only sun JRE. + // find from IBM JDK. JRockit. + DefaultChainExecutor, ChainResult> dce = new DefaultChainExecutor, ChainResult>(); + dce.add(new SystemProperties()); + dce.add(new SunJRE()); + dce.add(new ResolvConf()); + this.executor = dce; + } + + @Override + public + void dispose() { + // do nothing. + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/MasterDataHandler.java b/src-wip/org/handwerkszeug/dns/conf/MasterDataHandler.java new file mode 100755 index 0000000..5bf4ba5 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/MasterDataHandler.java @@ -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); + +} diff --git a/src-wip/org/handwerkszeug/dns/conf/MasterDataResource.java b/src-wip/org/handwerkszeug/dns/conf/MasterDataResource.java new file mode 100755 index 0000000..d21fca8 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/MasterDataResource.java @@ -0,0 +1,10 @@ +package org.handwerkszeug.dns.conf; + +public interface MasterDataResource { + + void initialize(ServerConfiguration conf); + + void process(MasterDataHandler processor); + + void dispose(); +} diff --git a/src-wip/org/handwerkszeug/dns/conf/NodeToAddress.java b/src-wip/org/handwerkszeug/dns/conf/NodeToAddress.java new file mode 100755 index 0000000..5c958ba --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/NodeToAddress.java @@ -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; + +/** + *

+ * convert Node to Address + *

+ * you can use following examples. + *

+ *

+ * + *
+ * #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
+ * 
+ * + *
+ *

+ * + * @author taichi + */ +public class NodeToAddress extends DefaultHandler> { + + static final Logger LOG = LoggerFactory.getLogger(NodeToAddress.class); + + Map>> converters = new HashMap>>(); + + public NodeToAddress() { + this.converters.put(NodeId.scalar, new ScalarToAddress()); + this.converters.put(NodeId.mapping, new MappingToAddress()); + this.converters.put(NodeId.sequence, new SequenceHandler>(this)); + } + + @Override + public void handle(Node node, Set context) { + YamlNodeHandler> 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> { + protected int defaultPort = Constants.DEFAULT_PORT; + + public ScalarToAddress() { + } + + public ScalarToAddress(int port) { + this.defaultPort = port; + } + + @Override + public void handle(Node node, Set 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> { + + protected int defaultPort = Constants.DEFAULT_PORT; + + public MappingToAddress() { + } + + public MappingToAddress(int port) { + this.defaultPort = port; + } + + @Override + public void handle(Node node, Set 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()); + } + } + } + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/NodeToForwarders.java b/src-wip/org/handwerkszeug/dns/conf/NodeToForwarders.java new file mode 100755 index 0000000..947588d --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/NodeToForwarders.java @@ -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 { + + static final Logger LOG = LoggerFactory.getLogger(NodeToForwarders.class); + + protected NodeToAddress nodeToAddress; + protected Map> converters = new HashMap>(); + + public NodeToForwarders(NodeToAddress nodeToAddress) { + super("forwarders"); + this.converters.put(NodeId.scalar, + new ScalarToForwarders(nodeToAddress)); + this.converters.put(NodeId.sequence, + new SequenceHandler(this)); + this.converters.put(NodeId.mapping, new MappingToForwarders( + nodeToAddress)); + } + + @Override + public void handle(Node node, ServerConfiguration context) { + YamlNodeHandler 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 { + 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 { + 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()); + } + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/ResolvConf.java b/src-wip/org/handwerkszeug/dns/conf/ResolvConf.java new file mode 100755 index 0000000..b066409 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/ResolvConf.java @@ -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, ChainResult> { + + static final Logger LOG = LoggerFactory.getLogger(ResolvConf.class); + + @Override + public + ChainResult execute(List context) { + handle("/etc/resolv.conf", context); + return SimpleChainResult.Continue; + } + + protected + void handle(String path, final List list) { + final File file = new File(path); + if (file.exists()) { + new Streams.using() { + @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 list) { + if (StringUtil.isEmpty(line) == false) { + String[] ary = line.split("\\s"); + if ((1 < ary.length) && ary[0].equalsIgnoreCase("nameserver")) { + list.add(ary[1]); + } + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/ServerConfiguration.java b/src-wip/org/handwerkszeug/dns/conf/ServerConfiguration.java new file mode 100755 index 0000000..b1076bf --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/ServerConfiguration.java @@ -0,0 +1,15 @@ +package org.handwerkszeug.dns.conf; + +import java.net.SocketAddress; +import java.util.Set; + +public interface ServerConfiguration { + + Set getBindingHosts(); + + int getThreadPoolSize(); + + Set getForwarders(); + + void setThreadPoolSize(int threadPoolSize); +} diff --git a/src-wip/org/handwerkszeug/dns/conf/ServerConfigurationImpl.java b/src-wip/org/handwerkszeug/dns/conf/ServerConfigurationImpl.java new file mode 100755 index 0000000..78c1e96 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/ServerConfigurationImpl.java @@ -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 bindingHosts = new HashSet(); + + protected Set forwarders = new HashSet(); + + protected List zones = new ArrayList(); + + protected int threadPoolSize = 10; + + public ServerConfigurationImpl() { + } + + public void load(final URL url) { + new Streams.using() { + @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 root = createRootHandler(); + YamlNodeAccepter accepter = new YamlNodeAccepter( + root); + accepter.accept(in, this); + } + + protected YamlNodeHandler createRootHandler() { + MappingHandler root = new MappingHandler(); + final NodeToAddress node2addr = new NodeToAddress(); + root.add(new DefaultHandler("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("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 getBindingHosts() { + return this.bindingHosts; + } + + @Override + public int getThreadPoolSize() { + return this.threadPoolSize; + } + + @Override + public Set getForwarders() { + return this.forwarders; + } + + @Override + public void setThreadPoolSize(int threadPoolSize) { + this.threadPoolSize = threadPoolSize; + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/SunJRE.java b/src-wip/org/handwerkszeug/dns/conf/SunJRE.java new file mode 100755 index 0000000..195e1e3 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/SunJRE.java @@ -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, ChainResult> { + + static final Logger LOG = LoggerFactory.getLogger(SunJRE.class); + + @Override + public ChainResult execute(List 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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/SystemProperties.java b/src-wip/org/handwerkszeug/dns/conf/SystemProperties.java new file mode 100755 index 0000000..23a4ee5 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/SystemProperties.java @@ -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, ChainResult> { + + @Override + public ChainResult execute(List 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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/TODO b/src-wip/org/handwerkszeug/dns/conf/TODO new file mode 100755 index 0000000..9634f45 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/TODO @@ -0,0 +1,5 @@ +RFC4741 NETCONF Configuration Protocol +RFC5381 Experience of Implementing NETCONF over SOAP + +そのものを利用する事は無くても、方向性として考慮する必要はありそう。 +ここに書いてある程度の事は出来なければ、別解を用意する意味は無い。 \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/dns/conf/masterfile/MasterFileParser.java b/src-wip/org/handwerkszeug/dns/conf/masterfile/MasterFileParser.java new file mode 100755 index 0000000..852adf6 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/masterfile/MasterFileParser.java @@ -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 includedPath = new HashSet(); + } + + 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 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 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; + } + +} diff --git a/src-wip/org/handwerkszeug/dns/conf/masterfile/Partition.java b/src-wip/org/handwerkszeug/dns/conf/masterfile/Partition.java new file mode 100755 index 0000000..1bf4945 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/masterfile/Partition.java @@ -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(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/conf/masterfile/Partitioner.java b/src-wip/org/handwerkszeug/dns/conf/masterfile/Partitioner.java new file mode 100755 index 0000000..595ac17 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/conf/masterfile/Partitioner.java @@ -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); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/nls/Messages.java b/src-wip/org/handwerkszeug/dns/nls/Messages.java new file mode 100755 index 0000000..0dbf410 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/nls/Messages.java @@ -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"; +} diff --git a/src-wip/org/handwerkszeug/dns/record/AAAARecord.java b/src-wip/org/handwerkszeug/dns/record/AAAARecord.java new file mode 100755 index 0000000..c929ee0 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/AAAARecord.java @@ -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; + +/** + * RFC3596 + * + * @author taichi + */ +public +class AAAARecord extends AbstractRecord { + + /** + * 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 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(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/ARecord.java b/src-wip/org/handwerkszeug/dns/record/ARecord.java new file mode 100755 index 0000000..d9ad7f5 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/ARecord.java @@ -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 { + + /** + * 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 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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/AbstractRecord.java b/src-wip/org/handwerkszeug/dns/record/AbstractRecord.java new file mode 100755 index 0000000..294ba4c --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/AbstractRecord.java @@ -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 implements ResourceRecord, Comparable { + + 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 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
+ * RDATA 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 + *

+ * <character-string> is a single length octet followed by that number + * of characters. <character-string> is treated as binary information, + * and can be up to 256 characters in length (including the length octet). + *

+ * + * @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 other = (AbstractRecord) obj; + return equals(other); + } + return false; + } + + public + boolean equals(AbstractRecord 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(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/HINFORecord.java b/src-wip/org/handwerkszeug/dns/record/HINFORecord.java new file mode 100755 index 0000000..5480736 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/HINFORecord.java @@ -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 + *

+ *

+ * Standard values for CPU and OS can be found in [RFC-1010]. + *

+ * + * @author taichi + */ +public +class HINFORecord extends AbstractRecord { + + /** + * A which specifies the CPU type. + */ + protected byte[] cpu; + + /** + * A 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 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 ? + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/MINFORecord.java b/src-wip/org/handwerkszeug/dns/record/MINFORecord.java new file mode 100755 index 0000000..4132784 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/MINFORecord.java @@ -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 { + + /** + * A 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 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 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(); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/MXRecord.java b/src-wip/org/handwerkszeug/dns/record/MXRecord.java new file mode 100755 index 0000000..c68bcb9 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/MXRecord.java @@ -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 + *

+ *

+ *                                   1  1  1  1  1  1
+ *     0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
+ *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *    |                  PREFERENCE                   |
+ *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *    /                   EXCHANGE                    /
+ *    /                                               /
+ *    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * 
+ * + * @author taichi + */ +public +class MXRecord extends AbstractRecord { + + /** + * 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 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(); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/NULLRecord.java b/src-wip/org/handwerkszeug/dns/record/NULLRecord.java new file mode 100755 index 0000000..37eced9 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/NULLRecord.java @@ -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 { + + /** + * 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 list) { + throw new UnsupportedOperationException(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/OPTNameRecord.java b/src-wip/org/handwerkszeug/dns/record/OPTNameRecord.java new file mode 100755 index 0000000..886a491 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/OPTNameRecord.java @@ -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; + +/** + *
    + *
  • 3.3.1. CNAME RDATA format
  • + *
  • 3.3.11. NS RDATA format
  • + *
  • 3.3.12. PTR RDATA format
  • + *
+ * + * @author taichi + */ +public +class OPTNameRecord extends AbstractRecord { + + 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 list) { + if (0 < list.size()) { + this.oneName = new Name(list.get(0)); + } + else { + throw new IllegalArgumentException(); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/SOARecord.java b/src-wip/org/handwerkszeug/dns/record/SOARecord.java new file mode 100755 index 0000000..c7140fb --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/SOARecord.java @@ -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 + *

+ *

+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * /                     MNAME                     /
+ * /                                               /
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * /                     RNAME                     /
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * |                    SERIAL                     |
+ * |                                               |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * |                    REFRESH                    |
+ * |                                               |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * |                     RETRY                     |
+ * |                                               |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * |                    EXPIRE                     |
+ * |                                               |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * |                    MINIMUM                    |
+ * |                                               |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * 
+ */ +public +class SOARecord extends AbstractRecord { + + /** + * The of the name server that was the original or primary + * source of data for this zone. + */ + protected Name mname; + + /** + * A 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 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 list) { + if (6 < list.size()) { + // XXX + } + else { + // TODO error message. + throw new IllegalArgumentException(); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/SingleNameRecord.java b/src-wip/org/handwerkszeug/dns/record/SingleNameRecord.java new file mode 100755 index 0000000..7900aec --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/SingleNameRecord.java @@ -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; + +/** + *
    + *
  • 3.3.1. CNAME RDATA format
  • + *
  • 3.3.11. NS RDATA format
  • + *
  • 3.3.12. PTR RDATA format
  • + *
+ * + * @author taichi + */ +public +class SingleNameRecord extends AbstractRecord { + + 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 list) { + if (0 < list.size()) { + this.oneName = new Name(list.get(0)); + } + else { + throw new IllegalArgumentException(); + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/TXTRecord.java b/src-wip/org/handwerkszeug/dns/record/TXTRecord.java new file mode 100755 index 0000000..6770c67 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/TXTRecord.java @@ -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 { + + protected List strings; + + public + TXTRecord() { + super(RRType.TXT); + } + + public + TXTRecord(TXTRecord from) { + super(from); + if (from.strings != null) { + List newone = new ArrayList(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(); + 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 i = this.strings.iterator(); i.hasNext(); ) { + stb.append(toQuoteString(i.next())); + if (i.hasNext()) { + stb.append(' '); + } + } + return stb.toString(); + } + + @Override + public + void setRDATA(List list) { + for (String s : list) { + this.strings.add(s.getBytes()); + } + } + + public + String txt() { + return this.toString(); + } +} diff --git a/src-wip/org/handwerkszeug/dns/record/WKSRecord.java b/src-wip/org/handwerkszeug/dns/record/WKSRecord.java new file mode 100755 index 0000000..53617b4 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/record/WKSRecord.java @@ -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 + *

+ *

+ *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *     |                    ADDRESS                    |
+ *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ *     |       PROTOCOL        |                       |
+ *     +--+--+--+--+--+--+--+--+                       |
+ *     |                                               |
+ *     /                                      /
+ *     /                                               /
+ *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * 
+ *

+ * 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]. + *

+ * + * @author taichi + */ +public +class WKSRecord extends AbstractRecord { + + /** + * 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 list) { + // TODO Auto-generated method stub + + } + + public + void protocol(short no) { + this.protocol = no; + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/CNAMEResponse.java b/src-wip/org/handwerkszeug/dns/server/CNAMEResponse.java new file mode 100755 index 0000000..7f0ee29 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/CNAMEResponse.java @@ -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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/DNAMEResponse.java b/src-wip/org/handwerkszeug/dns/server/DNAMEResponse.java new file mode 100755 index 0000000..504da06 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DNAMEResponse.java @@ -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); + } + } + +} diff --git a/src-wip/org/handwerkszeug/dns/server/DNSCacheEntry.java b/src-wip/org/handwerkszeug/dns/server/DNSCacheEntry.java new file mode 100755 index 0000000..bf6c336 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DNSCacheEntry.java @@ -0,0 +1,9 @@ +package org.handwerkszeug.dns.server; + + +public +class DNSCacheEntry { + + protected long expiration; + +} diff --git a/src-wip/org/handwerkszeug/dns/server/DNSCacheKey.java b/src-wip/org/handwerkszeug/dns/server/DNSCacheKey.java new file mode 100755 index 0000000..7b09933 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DNSCacheKey.java @@ -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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/DNSMessageCache.java b/src-wip/org/handwerkszeug/dns/server/DNSMessageCache.java new file mode 100755 index 0000000..6d1b2c8 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DNSMessageCache.java @@ -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) { + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/DNSMessageDecoder.java b/src-wip/org/handwerkszeug/dns/server/DNSMessageDecoder.java new file mode 100755 index 0000000..5203463 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DNSMessageDecoder.java @@ -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. + *

+ * Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward + * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}. + *

+ * 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 newlist = new ArrayList(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 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); + // } + // } +} diff --git a/src-wip/org/handwerkszeug/dns/server/DefaultResolveContext.java b/src-wip/org/handwerkszeug/dns/server/DefaultResolveContext.java new file mode 100755 index 0000000..2353800 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DefaultResolveContext.java @@ -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; + } + +} diff --git a/src-wip/org/handwerkszeug/dns/server/DefaultResponse.java b/src-wip/org/handwerkszeug/dns/server/DefaultResponse.java new file mode 100755 index 0000000..3ce7883 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/DefaultResponse.java @@ -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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/NoErrorResponse.java b/src-wip/org/handwerkszeug/dns/server/NoErrorResponse.java new file mode 100755 index 0000000..485b187 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/NoErrorResponse.java @@ -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 records; + final boolean aa; + + public + NoErrorResponse(Set records) { + this(records, true); + } + + public + NoErrorResponse(Set 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 ? + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/NotFoundResponse.java b/src-wip/org/handwerkszeug/dns/server/NotFoundResponse.java new file mode 100755 index 0000000..56ec825 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/NotFoundResponse.java @@ -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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/server/ReferralResponse.java b/src-wip/org/handwerkszeug/dns/server/ReferralResponse.java new file mode 100755 index 0000000..93678a5 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/server/ReferralResponse.java @@ -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 nsRecords; + + public + ReferralResponse(Set 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); + } +} diff --git a/src-wip/org/handwerkszeug/dns/zone/AbstractZone.java b/src-wip/org/handwerkszeug/dns/zone/AbstractZone.java new file mode 100755 index 0000000..cae25f9 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/AbstractZone.java @@ -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; + } + +} diff --git a/src-wip/org/handwerkszeug/dns/zone/ForwardZone.java b/src-wip/org/handwerkszeug/dns/zone/ForwardZone.java new file mode 100755 index 0000000..9d82c26 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/ForwardZone.java @@ -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 forwarders = new ArrayList(); + + 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; + } +} diff --git a/src-wip/org/handwerkszeug/dns/zone/MasterZone.java b/src-wip/org/handwerkszeug/dns/zone/MasterZone.java new file mode 100755 index 0000000..8367761 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/MasterZone.java @@ -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>> records = new ConcurrentSkipListMap>>(); + 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> exactMatch = this.records + .get(qname); + if (exactMatch != null) { + NavigableSet rrs = exactMatch.get(qtype); + if (rrs != null) { + synchronized (rrs) { + if (rrs.isEmpty() == false) { + return new NoErrorResponse(rrs); + } + } + } + if (RRType.ANY.equals(qtype)) { + Set newset = new HashSet(); + for (RRType type : exactMatch.keySet()) { + Set 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> match = this.records + .get(qn); + if (match != null) { + synchronized (match) { + if (match.isEmpty() == false) { + NavigableSet 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> match = this.records + .get(wild); + if (match != null) { + synchronized (match) { + if (match.isEmpty() == false) { + Set matchSet = match.get(qtype); + if (matchSet.isEmpty() == false) { + Set set = new HashSet( + 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> current = this.records + .get(rr.name()); + if (current == null) { + ConcurrentMap> newone = new ConcurrentSkipListMap>(); + NavigableSet newset = new ConcurrentSkipListSet(); + newset.add(rr); + newone.put(rr.type(), newset); + + ConcurrentMap> prevTypes = this.records + .putIfAbsent(rr.name(), newone); + if (prevTypes == null) { + break; + } + synchronized (prevTypes) { + Set prevRecs = prevTypes.putIfAbsent( + rr.type(), newset); + if (prevRecs == null) { + break; + } + prevRecs.add(rr); + break; + } + } else { + synchronized (current) { + Set rrs = current.get(rr.type()); + if (rrs == null) { + NavigableSet newset = new ConcurrentSkipListSet(); + 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> current = this.records + .get(rr.name()); + if (current != null) { + synchronized (current) { + NavigableSet 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()); + } + } + } + } + } +} diff --git a/src-wip/org/handwerkszeug/dns/zone/Query.java b/src-wip/org/handwerkszeug/dns/zone/Query.java new file mode 100755 index 0000000..a7ccbb0 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/Query.java @@ -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 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 rrs) { + for (ResourceRecord rr : rrs) { + if (this.origin.equals(rr.name())) { + return true; + } + } + return false; + } +} diff --git a/src-wip/org/handwerkszeug/dns/zone/SearchResult.java b/src-wip/org/handwerkszeug/dns/zone/SearchResult.java new file mode 100755 index 0000000..e41e643 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/SearchResult.java @@ -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 rrs; + + Status status = Status.NXDOMAIN; + + public SearchResult(List rrs) { + super(); + this.rrs = rrs; + } +} diff --git a/src-wip/org/handwerkszeug/dns/zone/ZoneDatabase.java b/src-wip/org/handwerkszeug/dns/zone/ZoneDatabase.java new file mode 100755 index 0000000..33549a7 --- /dev/null +++ b/src-wip/org/handwerkszeug/dns/zone/ZoneDatabase.java @@ -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 zones = new ConcurrentSkipListMap(); + + 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 { + 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(); + } + } +} diff --git a/src-wip/org/handwerkszeug/util/AddressUtil.java b/src-wip/org/handwerkszeug/util/AddressUtil.java new file mode 100755 index 0000000..d5ae13b --- /dev/null +++ b/src-wip/org/handwerkszeug/util/AddressUtil.java @@ -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 [RFC4291] IP Version 6 + * Addressing Architecture + * @see [RFC5952] A Recommendation + * for IPv6 Address Text Representation + * @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 [RFC952] DOD INTERNET + * HOST TABLE SPECIFICATION + */ + 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}(?.*?::)(?!.*::)))(::)?([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})(? CONVERTERS = new ArrayList(); + 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); + } + } +} diff --git a/src-wip/org/handwerkszeug/util/ClassUtil.java b/src-wip/org/handwerkszeug/util/ClassUtil.java new file mode 100755 index 0000000..567982f --- /dev/null +++ b/src-wip/org/handwerkszeug/util/ClassUtil.java @@ -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; + } +} diff --git a/src-wip/org/handwerkszeug/util/CompareUtil.java b/src-wip/org/handwerkszeug/util/CompareUtil.java new file mode 100755 index 0000000..902f08f --- /dev/null +++ b/src-wip/org/handwerkszeug/util/CompareUtil.java @@ -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); + } +} diff --git a/src-wip/org/handwerkszeug/util/EnumUtil.java b/src-wip/org/handwerkszeug/util/EnumUtil.java new file mode 100755 index 0000000..64ecdd5 --- /dev/null +++ b/src-wip/org/handwerkszeug/util/EnumUtil.java @@ -0,0 +1,38 @@ +package org.handwerkszeug.util; + +public class EnumUtil { + + public static & 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 & VariableEnum> E find(E[] values, + int value, E defaultValue) { + for (E e : values) { + if (e.value() == value) { + return e; + } + } + return defaultValue; + } + + public static > 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; + } + +} diff --git a/src-wip/org/handwerkszeug/util/Validation.java b/src-wip/org/handwerkszeug/util/Validation.java new file mode 100755 index 0000000..61c0de5 --- /dev/null +++ b/src-wip/org/handwerkszeug/util/Validation.java @@ -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(); + } +} diff --git a/src-wip/org/handwerkszeug/util/VariableEnum.java b/src-wip/org/handwerkszeug/util/VariableEnum.java new file mode 100755 index 0000000..1793b9d --- /dev/null +++ b/src-wip/org/handwerkszeug/util/VariableEnum.java @@ -0,0 +1,6 @@ +package org.handwerkszeug.util; + +public interface VariableEnum { + + int value(); +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/yaml/DefaultHandler.java b/src-wip/org/handwerkszeug/yaml/DefaultHandler.java new file mode 100755 index 0000000..cbd5a36 --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/DefaultHandler.java @@ -0,0 +1,17 @@ +package org.handwerkszeug.yaml; + +public abstract class DefaultHandler implements YamlNodeHandler { + protected String name; + + public DefaultHandler() { + } + + public DefaultHandler(String name) { + this.name = name; + } + + @Override + public String getNodeName() { + return this.name; + } +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/yaml/MappingHandler.java b/src-wip/org/handwerkszeug/yaml/MappingHandler.java new file mode 100755 index 0000000..ef0d096 --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/MappingHandler.java @@ -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 extends DefaultHandler { + + static final Logger LOG = LoggerFactory.getLogger(MappingHandler.class); + + Map> handlers = new HashMap>(); + + public MappingHandler() { + } + + public MappingHandler(String name) { + super(name); + } + + public void add(YamlNodeHandler 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 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 }); + } + } +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/yaml/SequenceHandler.java b/src-wip/org/handwerkszeug/yaml/SequenceHandler.java new file mode 100755 index 0000000..0bc9840 --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/SequenceHandler.java @@ -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 extends DefaultHandler { + + static final Logger LOG = LoggerFactory.getLogger(SequenceHandler.class); + + protected YamlNodeHandler handler; + + public SequenceHandler(YamlNodeHandler handler) { + this.handler = handler; + } + + public SequenceHandler(String name, YamlNodeHandler 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 }); + } + } +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/yaml/YamlNodeAccepter.java b/src-wip/org/handwerkszeug/yaml/YamlNodeAccepter.java new file mode 100755 index 0000000..55c4ac4 --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/YamlNodeAccepter.java @@ -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 { + + static final Logger LOG = LoggerFactory.getLogger(YamlNodeAccepter.class); + + protected final YamlNodeHandler rootHandler; + + public YamlNodeAccepter(YamlNodeHandler 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); + } +} diff --git a/src-wip/org/handwerkszeug/yaml/YamlNodeHandler.java b/src-wip/org/handwerkszeug/yaml/YamlNodeHandler.java new file mode 100755 index 0000000..1f6452f --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/YamlNodeHandler.java @@ -0,0 +1,10 @@ +package org.handwerkszeug.yaml; + +import org.yaml.snakeyaml.nodes.Node; + +public interface YamlNodeHandler { + + String getNodeName(); + + void handle(Node node, CTX context); +} \ No newline at end of file diff --git a/src-wip/org/handwerkszeug/yaml/YamlUtil.java b/src-wip/org/handwerkszeug/yaml/YamlUtil.java new file mode 100755 index 0000000..d263fd6 --- /dev/null +++ b/src-wip/org/handwerkszeug/yaml/YamlUtil.java @@ -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; + } +} diff --git a/src-wip/org/xbill/DNS2/clients/Client.java b/src-wip/org/xbill/DNS2/clients/Client.java new file mode 100755 index 0000000..9a207ce --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Client.java @@ -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; + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/Generator.java b/src-wip/org/xbill/DNS2/clients/Generator.java new file mode 100755 index 0000000..2797608 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Generator.java @@ -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(); + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/Lookup.java b/src-wip/org/xbill/DNS2/clients/Lookup.java new file mode 100755 index 0000000..64df8df --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Lookup.java @@ -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. + *

+ * 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"); + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/Master.java b/src-wip/org/xbill/DNS2/clients/Master.java new file mode 100755 index 0000000..a61e199 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Master.java @@ -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(); + } + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/PacketLogger.java b/src-wip/org/xbill/DNS2/clients/PacketLogger.java new file mode 100755 index 0000000..d74530f --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/PacketLogger.java @@ -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); +} diff --git a/src-wip/org/xbill/DNS2/clients/Serial.java b/src-wip/org/xbill/DNS2/clients/Serial.java new file mode 100755 index 0000000..7c39f13 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Serial.java @@ -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; + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/TCPClient.java b/src-wip/org/xbill/DNS2/clients/TCPClient.java new file mode 100755 index 0000000..b4676a8 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/TCPClient.java @@ -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; + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/UDPClient.java b/src-wip/org/xbill/DNS2/clients/UDPClient.java new file mode 100755 index 0000000..ff242f9 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/UDPClient.java @@ -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; + } +} diff --git a/src-wip/org/xbill/DNS2/clients/Zone.java b/src-wip/org/xbill/DNS2/clients/Zone.java new file mode 100755 index 0000000..ab5857e --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/Zone.java @@ -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); + } + } + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/ZoneTransferIn.java b/src-wip/org/xbill/DNS2/clients/ZoneTransferIn.java new file mode 100755 index 0000000..5905780 --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/ZoneTransferIn.java @@ -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); + } + +} diff --git a/src-wip/org/xbill/DNS2/clients/hexdump.java b/src-wip/org/xbill/DNS2/clients/hexdump.java new file mode 100755 index 0000000..4d7efad --- /dev/null +++ b/src-wip/org/xbill/DNS2/clients/hexdump.java @@ -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(); + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/Cache.java b/src-wip/org/xbill/DNS2/resolver/Cache.java new file mode 100755 index 0000000..13c174a --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/Cache.java @@ -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. + *

+ * Note that setting this to a value lower than the current number + * of entries will not cause the Cache to shrink immediately. + *

+ * 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(); + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/Credibility.java b/src-wip/org/xbill/DNS2/resolver/Credibility.java new file mode 100755 index 0000000..dc7f146 --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/Credibility.java @@ -0,0 +1,64 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import dorkbox.network.dns.constants.DnsSection; + +/** + * Constants relating to the credibility of cached data, which is based on + * the data's source. The constants NORMAL and ANY should be used by most + * callers. + * + * @author Brian Wellington + * @see Cache + * @see DnsSection + */ + +public final +class Credibility { + + /** + * A hint or cache file on disk. + */ + public static final int HINT = 0; + /** + * The additional section of a response. + */ + public static final int ADDITIONAL = 1; + /** + * The additional section of a response. + */ + public static final int GLUE = 2; + /** + * The authority section of a nonauthoritative response. + */ + public static final int NONAUTH_AUTHORITY = 3; + /** + * The answer section of a nonauthoritative response. + */ + public static final int NONAUTH_ANSWER = 3; + /** + * The authority section of an authoritative response. + */ + public static final int AUTH_AUTHORITY = 4; + /** + * The answer section of a authoritative response. + */ + public static final int AUTH_ANSWER = 4; + /** + * A zone. + */ + public static final int ZONE = 5; + /** + * Credible data. + */ + public static final int NORMAL = 3; + /** + * Data not required to be credible. + */ + public static final int ANY = 1; + + private + Credibility() {} + +} diff --git a/src-wip/org/xbill/DNS2/resolver/ExtendedResolver.java b/src-wip/org/xbill/DNS2/resolver/ExtendedResolver.java new file mode 100755 index 0000000..65e2f8c --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/ExtendedResolver.java @@ -0,0 +1,490 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import dorkbox.network.dns.records.DnsMessage; +import dorkbox.network.dns.records.TSIG; +import dorkbox.network.dns.utils.Options; + +/** + * An implementation of Resolver that can send queries to multiple servers, + * sending the queries multiple times if necessary. + * + * @author Brian Wellington + * @see Resolver + */ + +public +class ExtendedResolver implements Resolver { + + private static final int quantum = 5; + private List resolvers; + private boolean loadBalance = false; + private int lbStart = 0; + private int retries = 3; + + + private static + class Resolution implements ResolverListener { + Resolver[] resolvers; + int[] sent; + Object[] inprogress; + int retries; + int outstanding; + boolean done; + DnsMessage query; + DnsMessage response; + Throwable thrown; + ResolverListener listener; + + public + Resolution(ExtendedResolver eres, DnsMessage query) { + List l = eres.resolvers; + resolvers = (Resolver[]) l.toArray(new Resolver[l.size()]); + if (eres.loadBalance) { + int nresolvers = resolvers.length; + /* + * Note: this is not synchronized, since the + * worst thing that can happen is a random + * ordering, which is ok. + */ + int start = eres.lbStart++ % nresolvers; + if (eres.lbStart > nresolvers) { + eres.lbStart %= nresolvers; + } + if (start > 0) { + Resolver[] shuffle = new Resolver[nresolvers]; + for (int i = 0; i < nresolvers; i++) { + int pos = (i + start) % nresolvers; + shuffle[i] = resolvers[pos]; + } + resolvers = shuffle; + } + } + sent = new int[resolvers.length]; + inprogress = new Object[resolvers.length]; + retries = eres.retries; + this.query = query; + } + + /* Start a synchronous resolution */ + public + DnsMessage start() throws IOException { + try { + /* + * First, try sending synchronously. If this works, + * we're done. Otherwise, we'll get an exception + * and continue. It would be easier to call send(0), + * but this avoids a thread creation. If and when + * SimpleResolver.sendAsync() can be made to not + * create a thread, this could be changed. + */ + sent[0]++; + outstanding++; + inprogress[0] = new Object(); + return resolvers[0].send(query); + } catch (Exception e) { + /* + * This will either cause more queries to be sent + * asynchronously or will set the 'done' flag. + */ + handleException(inprogress[0], e); + } + /* + * Wait for a successful response or for each + * subresolver to fail. + */ + synchronized (this) { + while (!done) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + /* Return the response or throw an exception */ + if (response != null) { + return response; + } + else if (thrown instanceof IOException) { + throw (IOException) thrown; + } + else if (thrown instanceof RuntimeException) { + throw (RuntimeException) thrown; + } + else if (thrown instanceof Error) { + throw (Error) thrown; + } + else { + throw new IllegalStateException("ExtendedResolver failure"); + } + } + + /* Start an asynchronous resolution */ + public + void startAsync(ResolverListener listener) { + this.listener = listener; + send(0); + } + + /* + * Receive a response. If the resolution hasn't been completed, + * either wake up the blocking thread or call the callback. + */ + @Override + public + void receiveMessage(Object id, DnsMessage m) { + if (Options.check("verbose")) { + System.err.println("ExtendedResolver: " + "received message"); + } + synchronized (this) { + if (done) { + return; + } + response = m; + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + listener.receiveMessage(this, response); + } + + /* + * Receive an exception. If the resolution has been completed, + * do nothing. Otherwise make progress. + */ + @Override + public + void handleException(Object id, Exception e) { + if (Options.check("verbose")) { + System.err.println("ExtendedResolver: got " + e); + } + synchronized (this) { + outstanding--; + if (done) { + return; + } + int n; + for (n = 0; n < inprogress.length; n++) { + if (inprogress[n] == id) { + break; + } + } + /* If we don't know what this is, do nothing. */ + if (n == inprogress.length) { + return; + } + boolean startnext = false; + /* + * If this is the first response from server n, + * we should start sending queries to server n + 1. + */ + if (sent[n] == 1 && n < resolvers.length - 1) { + startnext = true; + } + if (e instanceof InterruptedIOException) { + /* Got a timeout; resend */ + if (sent[n] < retries) { + send(n); + } + if (thrown == null) { + thrown = e; + } + } + else if (e instanceof SocketException) { + /* + * Problem with the socket; don't resend + * on it + */ + if (thrown == null || thrown instanceof InterruptedIOException) { + thrown = e; + } + } + else { + /* + * Problem with the response; don't resend + * on the same socket. + */ + thrown = e; + } + if (done) { + return; + } + if (startnext) { + send(n + 1); + } + if (done) { + return; + } + if (outstanding == 0) { + /* + * If we're done and this is synchronous, + * wake up the blocking thread. + */ + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + if (!done) { + return; + } + } + /* If we're done and this is asynchronous, call the callback. */ + if (!(thrown instanceof Exception)) { + thrown = new RuntimeException(thrown.getMessage()); + } + listener.handleException(this, (Exception) thrown); + } + + /* Asynchronously sends a message. */ + public + void send(int n) { + sent[n]++; + outstanding++; + try { + inprogress[n] = resolvers[n].sendAsync(query, this); + } catch (Throwable t) { + synchronized (this) { + thrown = t; + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + } + } + } + + /** + * Creates a new Extended Resolver. The default ResolverConfig is used to + * determine the servers for which SimpleResolver contexts should be + * initialized. + * + * @throws UnknownHostException Failure occured initializing SimpleResolvers + * @see SimpleResolver + * @see ResolverConfig + */ + public + ExtendedResolver() throws UnknownHostException { + init(); + String[] servers = ResolverConfig.getCurrentConfig() + .servers(); + if (servers != null) { + for (int i = 0; i < servers.length; i++) { + Resolver r = new SimpleResolver(servers[i]); + r.setTimeout(quantum); + resolvers.add(r); + } + } + else { + resolvers.add(new SimpleResolver()); + } + } + + private + void init() { + resolvers = new ArrayList(); + } + + /** + * Creates a new Extended Resolver + * + * @param servers An array of server names for which SimpleResolver + * contexts should be initialized. + * + * @throws UnknownHostException Failure occured initializing SimpleResolvers + * @see SimpleResolver + */ + public + ExtendedResolver(String[] servers) throws UnknownHostException { + init(); + for (int i = 0; i < servers.length; i++) { + Resolver r = new SimpleResolver(servers[i]); + r.setTimeout(quantum); + resolvers.add(r); + } + } + + /** + * Creates a new Extended Resolver + * + * @param res An array of pre-initialized Resolvers is provided. + * + * @throws UnknownHostException Failure occured initializing SimpleResolvers + * @see SimpleResolver + */ + public + ExtendedResolver(Resolver[] res) throws UnknownHostException { + init(); + for (int i = 0; i < res.length; i++) { + resolvers.add(res[i]); + } + } + + @Override + public + void setPort(int port) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setPort(port); + } + } + + @Override + public + void setTCP(boolean flag) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setTCP(flag); + } + } + + @Override + public + void setIgnoreTruncation(boolean flag) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setIgnoreTruncation(flag); + } + } + + @Override + public + void setEDNS(int level) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setEDNS(level); + } + } + + @Override + public + void setEDNS(int level, int payloadSize, int flags, List options) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setEDNS(level, payloadSize, flags, options); + } + } + + @Override + public + void setTSIGKey(TSIG key) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setTSIGKey(key); + } + } + + @Override + public + void setTimeout(int secs, int msecs) { + for (int i = 0; i < resolvers.size(); i++) { + ((Resolver) resolvers.get(i)).setTimeout(secs, msecs); + } + } + + @Override + public + void setTimeout(int secs) { + setTimeout(secs, 0); + } + + /** + * Sends a message and waits for a response. Multiple servers are queried, + * and queries are sent multiple times until either a successful response + * is received, or it is clear that there is no successful response. + * + * @param query The query to send. + * + * @return The response. + * + * @throws IOException An error occurred while sending or receiving. + */ + @Override + public + DnsMessage send(DnsMessage query) throws IOException { + Resolution res = new Resolution(this, query); + return res.start(); + } + + /** + * Asynchronously sends a message to multiple servers, potentially multiple + * times, registering a listener to receive a callback on success or exception. + * Multiple asynchronous lookups can be performed in parallel. Since the + * callback may be invoked before the function returns, external + * synchronization is necessary. + * + * @param query The query to send + * @param listener The object containing the callbacks. + * + * @return An identifier, which is also a parameter in the callback + */ + @Override + public + Object sendAsync(final DnsMessage query, final ResolverListener listener) { + Resolution res = new Resolution(this, query); + res.startAsync(listener); + return res; + } + + /** + * Returns the nth resolver used by this ExtendedResolver + */ + public + Resolver getResolver(int n) { + if (n < resolvers.size()) { + return (Resolver) resolvers.get(n); + } + return null; + } + + /** + * Returns all resolvers used by this ExtendedResolver + */ + public + Resolver[] getResolvers() { + return (Resolver[]) resolvers.toArray(new Resolver[resolvers.size()]); + } + + /** + * Adds a new resolver to be used by this ExtendedResolver + */ + public + void addResolver(Resolver r) { + resolvers.add(r); + } + + /** + * Deletes a resolver used by this ExtendedResolver + */ + public + void deleteResolver(Resolver r) { + resolvers.remove(r); + } + + /** + * Sets whether the servers should be load balanced. + * + * @param flag If true, servers will be tried in round-robin order. If false, + * servers will always be queried in the same order. + */ + public + void setLoadBalance(boolean flag) { + loadBalance = flag; + } + + /** + * Sets the number of retries sent to each server per query + */ + public + void setRetries(int retries) { + this.retries = retries; + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/ResolveThread.java b/src-wip/org/xbill/DNS2/resolver/ResolveThread.java new file mode 100755 index 0000000..8e91bfd --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/ResolveThread.java @@ -0,0 +1,47 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import dorkbox.network.dns.records.DnsMessage; + +/** + * A special-purpose thread used by Resolvers (both SimpleResolver and + * ExtendedResolver) to perform asynchronous queries. + * + * @author Brian Wellington + */ + +class ResolveThread extends Thread { + + private DnsMessage query; + private Object id; + private ResolverListener listener; + private Resolver res; + + /** + * Creates a new ResolveThread + */ + public + ResolveThread(Resolver res, DnsMessage query, Object id, ResolverListener listener) { + this.res = res; + this.query = query; + this.id = id; + this.listener = listener; + } + + + /** + * Performs the query, and executes the callback. + */ + @Override + public + void run() { + try { + DnsMessage response = res.send(query); + listener.receiveMessage(id, response); + } catch (Exception e) { + listener.handleException(id, e); + } + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/Resolver.java b/src-wip/org/xbill/DNS2/resolver/Resolver.java new file mode 100755 index 0000000..e1733ff --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/Resolver.java @@ -0,0 +1,115 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.io.IOException; +import java.util.List; + +import dorkbox.network.dns.records.DnsMessage; +import dorkbox.network.dns.records.OPTRecord; +import dorkbox.network.dns.records.TSIG; + +/** + * Interface describing a resolver. + * + * @author Brian Wellington + */ + +public +interface Resolver { + + /** + * Sets the port to communicate with on the server + * + * @param port The port to send messages to + */ + void setPort(int port); + + /** + * Sets whether TCP connections will be sent by default + * + * @param flag Indicates whether TCP connections are made + */ + void setTCP(boolean flag); + + /** + * Sets whether truncated responses will be ignored. If not, a truncated + * response over UDP will cause a retransmission over TCP. + * + * @param flag Indicates whether truncated responses should be ignored. + */ + void setIgnoreTruncation(boolean flag); + + /** + * Sets the EDNS version used on outgoing messages. + * + * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no + * EDNS. + * + * @throws IllegalArgumentException An invalid level was indicated. + */ + void setEDNS(int level); + + /** + * Sets the EDNS information on outgoing messages. + * + * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no + * EDNS. + * @param payloadSize The maximum DNS packet size that this host is capable + * of receiving over UDP. If 0 is specified, the default (1280) is used. + * @param flags EDNS extended flags to be set in the OPT record. + * @param options EDNS options to be set in the OPT record, specified as a + * List of OPTRecord.Option elements. + * + * @throws IllegalArgumentException An invalid field was specified. + * @see OPTRecord + */ + void setEDNS(int level, int payloadSize, int flags, List options); + + /** + * Specifies the TSIG key that messages will be signed with + * + * @param key The key + */ + void setTSIGKey(TSIG key); + + /** + * Sets the amount of time to wait for a response before giving up. + * + * @param secs The number of seconds to wait. + * @param msecs The number of milliseconds to wait. + */ + void setTimeout(int secs, int msecs); + + /** + * Sets the amount of time to wait for a response before giving up. + * + * @param secs The number of seconds to wait. + */ + void setTimeout(int secs); + + /** + * Sends a message and waits for a response. + * + * @param query The query to send. + * + * @return The response + * + * @throws IOException An error occurred while sending or receiving. + */ + DnsMessage send(DnsMessage query) throws IOException; + + /** + * Asynchronously sends a message registering a listener to receive a callback + * on success or exception. Multiple asynchronous lookups can be performed + * in parallel. Since the callback may be invoked before the function returns, + * external synchronization is necessary. + * + * @param query The query to send + * @param listener The object containing the callbacks. + * + * @return An identifier, which is also a parameter in the callback + */ + Object sendAsync(final DnsMessage query, final ResolverListener listener); + +} diff --git a/src-wip/org/xbill/DNS2/resolver/ResolverConfig.java b/src-wip/org/xbill/DNS2/resolver/ResolverConfig.java new file mode 100755 index 0000000..451c848 --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/ResolverConfig.java @@ -0,0 +1,555 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Options; + +/** + * A class that tries to locate name servers and the search path to + * be appended to unqualified names. + *

+ * The following are attempted, in order, until one succeeds. + *

    + *
  • The properties 'dns.server' and 'dns.search' (comma delimited lists) + * are checked. The servers can either be IP addresses or hostnames + * (which are resolved using Java's built in DNS support). + *
  • The sun.net.dns.ResolverConfiguration class is queried. + *
  • On Unix, /etc/resolv.conf is parsed. + *
  • On Windows, ipconfig/winipcfg is called and its output parsed. This + * may fail for non-English versions on Windows. + *
  • "localhost" is used as the nameserver, and the search path is empty. + *
+ *

+ * These routines will be called internally when creating Resolvers/Lookups + * without explicitly specifying server names, and can also be called + * directly if desired. + * + * @author Brian Wellington + * @author Yannick Meudal + * @author Arnt Gulbrandsen + */ + +public +class ResolverConfig { + + private String[] servers = null; + private Name[] searchlist = null; + private int ndots = -1; + + private static ResolverConfig currentConfig; + + static { + refresh(); + } + + public + ResolverConfig() { + if (findProperty()) { + return; + } + if (findSunJVM()) { + return; + } + if (servers == null || searchlist == null) { + String OS = System.getProperty("os.name"); + String vendor = System.getProperty("java.vendor"); + if (OS.indexOf("Windows") != -1) { + if (OS.indexOf("95") != -1 || OS.indexOf("98") != -1 || OS.indexOf("ME") != -1) { + find95(); + } + else { + findNT(); + } + } + else if (OS.indexOf("NetWare") != -1) { + findNetware(); + } + else if (vendor.indexOf("Android") != -1) { + findAndroid(); + } + else { + findUnix(); + } + } + } + + private + void addServer(String server, List list) { + if (list.contains(server)) { + return; + } + if (Options.check("verbose")) { + System.out.println("adding server " + server); + } + list.add(server); + } + + private + void addSearch(String search, List list) { + Name name; + if (Options.check("verbose")) { + System.out.println("adding search " + search); + } + try { + name = Name.Companion.fromString(search, Name.root); + } catch (TextParseException e) { + return; + } + if (list.contains(name)) { + return; + } + list.add(name); + } + + private + int parseNdots(String token) { + token = token.substring(6); + try { + int ndots = Integer.parseInt(token); + if (ndots >= 0) { + if (Options.check("verbose")) { + System.out.println("setting ndots " + token); + } + return ndots; + } + } catch (NumberFormatException e) { + } + return -1; + } + + private + void configureFromLists(List lserver, List lsearch) { + if (servers == null && lserver.size() > 0) { + servers = (String[]) lserver.toArray(new String[0]); + } + if (searchlist == null && lsearch.size() > 0) { + searchlist = (Name[]) lsearch.toArray(new Name[0]); + } + } + + private + void configureNdots(int lndots) { + if (ndots < 0 && lndots > 0) { + ndots = lndots; + } + } + + /** + * Looks in the system properties to find servers and a search path. + * Servers are defined by dns.server=server1,server2... + * The search path is defined by dns.search=domain1,domain2... + */ + private + boolean findProperty() { + String prop; + List lserver = new ArrayList(0); + List lsearch = new ArrayList(0); + StringTokenizer st; + + prop = System.getProperty("dns.server"); + if (prop != null) { + st = new StringTokenizer(prop, ","); + while (st.hasMoreTokens()) { + addServer(st.nextToken(), lserver); + } + } + + prop = System.getProperty("dns.search"); + if (prop != null) { + st = new StringTokenizer(prop, ","); + while (st.hasMoreTokens()) { + addSearch(st.nextToken(), lsearch); + } + } + configureFromLists(lserver, lsearch); + return (servers != null && searchlist != null); + } + + /** + * Uses the undocumented Sun DNS implementation to determine the configuration. + * This doesn't work or even compile with all JVMs (gcj, for example). + */ + private + boolean findSunJVM() { + List lserver = new ArrayList(0); + List lserver_tmp; + List lsearch = new ArrayList(0); + List lsearch_tmp; + + try { + Class[] noClasses = new Class[0]; + Object[] noObjects = new Object[0]; + String resConfName = "sun.net.dns.ResolverConfiguration"; + Class resConfClass = Class.forName(resConfName); + Object resConf; + + // ResolverConfiguration resConf = ResolverConfiguration.open(); + Method open = resConfClass.getDeclaredMethod("open", noClasses); + resConf = open.invoke(null, noObjects); + + // lserver_tmp = resConf.nameservers(); + Method nameservers = resConfClass.getMethod("nameservers", noClasses); + lserver_tmp = (List) nameservers.invoke(resConf, noObjects); + + // lsearch_tmp = resConf.searchlist(); + Method searchlist = resConfClass.getMethod("searchlist", noClasses); + lsearch_tmp = (List) searchlist.invoke(resConf, noObjects); + } catch (Exception e) { + return false; + } + + if (lserver_tmp.size() == 0) { + return false; + } + + if (lserver_tmp.size() > 0) { + Iterator it = lserver_tmp.iterator(); + while (it.hasNext()) { + addServer((String) it.next(), lserver); + } + } + + if (lsearch_tmp.size() > 0) { + Iterator it = lsearch_tmp.iterator(); + while (it.hasNext()) { + addSearch((String) it.next(), lsearch); + } + } + configureFromLists(lserver, lsearch); + return true; + } + + /** + * Looks in /etc/resolv.conf to find servers and a search path. + * "nameserver" lines specify servers. "domain" and "search" lines + * define the search path. + */ + private + void findResolvConf(String file) { + InputStream in = null; + try { + in = new FileInputStream(file); + } catch (FileNotFoundException e) { + return; + } + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + List lserver = new ArrayList(0); + List lsearch = new ArrayList(0); + int lndots = -1; + try { + String line; + while ((line = br.readLine()) != null) { + if (line.startsWith("nameserver")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip nameserver */ + addServer(st.nextToken(), lserver); + } + else if (line.startsWith("domain")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip domain */ + if (!st.hasMoreTokens()) { + continue; + } + if (lsearch.isEmpty()) { + addSearch(st.nextToken(), lsearch); + } + } + else if (line.startsWith("search")) { + if (!lsearch.isEmpty()) { + lsearch.clear(); + } + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip search */ + while (st.hasMoreTokens()) { + addSearch(st.nextToken(), lsearch); + } + } + else if (line.startsWith("options")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip options */ + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (token.startsWith("ndots:")) { + lndots = parseNdots(token); + } + } + } + } + br.close(); + } catch (IOException e) { + } + + configureFromLists(lserver, lsearch); + configureNdots(lndots); + } + + private + void findUnix() { + findResolvConf("/etc/resolv.conf"); + } + + private + void findNetware() { + findResolvConf("sys:/etc/resolv.cfg"); + } + + /** + * Parses the output of winipcfg or ipconfig. + */ + private + void findWin(InputStream in, Locale locale) { + String packageName = ResolverConfig.class.getPackage() + .getName(); + String resPackageName = packageName + ".windows.DNSServer"; + ResourceBundle res; + if (locale != null) { + res = ResourceBundle.getBundle(resPackageName, locale); + } + else { + res = ResourceBundle.getBundle(resPackageName); + } + + String host_name = res.getString("host_name"); + String primary_dns_suffix = res.getString("primary_dns_suffix"); + String dns_suffix = res.getString("dns_suffix"); + String dns_servers = res.getString("dns_servers"); + + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + try { + List lserver = new ArrayList(); + List lsearch = new ArrayList(); + String line = null; + boolean readingServers = false; + boolean readingSearches = false; + while ((line = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line); + if (!st.hasMoreTokens()) { + readingServers = false; + readingSearches = false; + continue; + } + String s = st.nextToken(); + if (line.indexOf(":") != -1) { + readingServers = false; + readingSearches = false; + } + + if (line.indexOf(host_name) != -1) { + while (st.hasMoreTokens()) { + s = st.nextToken(); + } + Name name; + try { + name = Name.Companion.fromString(s, null); + } catch (TextParseException e) { + continue; + } + if (name.labels() == 1) { + continue; + } + addSearch(s, lsearch); + } + else if (line.indexOf(primary_dns_suffix) != -1) { + while (st.hasMoreTokens()) { + s = st.nextToken(); + } + if (s.equals(":")) { + continue; + } + addSearch(s, lsearch); + readingSearches = true; + } + else if (readingSearches || line.indexOf(dns_suffix) != -1) { + while (st.hasMoreTokens()) { + s = st.nextToken(); + } + if (s.equals(":")) { + continue; + } + addSearch(s, lsearch); + readingSearches = true; + } + else if (readingServers || line.indexOf(dns_servers) != -1) { + while (st.hasMoreTokens()) { + s = st.nextToken(); + } + if (s.equals(":")) { + continue; + } + addServer(s, lserver); + readingServers = true; + } + } + + configureFromLists(lserver, lsearch); + } catch (IOException e) { + } + return; + } + + private + void findWin(InputStream in) { + String property = "org.xbill.DNS.windows.parse.buffer"; + final int defaultBufSize = 8 * 1024; + int bufSize = Integer.getInteger(property, defaultBufSize) + .intValue(); + BufferedInputStream b = new BufferedInputStream(in, bufSize); + b.mark(bufSize); + findWin(b, null); + if (servers == null) { + try { + b.reset(); + } catch (IOException e) { + return; + } + findWin(b, new Locale("", "")); + } + } + + /** + * Calls winipcfg and parses the result to find servers and a search path. + */ + private + void find95() { + String s = "winipcfg.out"; + try { + Process p; + p = Runtime.getRuntime() + .exec("winipcfg /all /batch " + s); + p.waitFor(); + File f = new File(s); + findWin(new FileInputStream(f)); + new File(s).delete(); + } catch (Exception e) { + return; + } + } + + /** + * Calls ipconfig and parses the result to find servers and a search path. + */ + private + void findNT() { + try { + Process p; + p = Runtime.getRuntime() + .exec("ipconfig /all"); + findWin(p.getInputStream()); + p.destroy(); + } catch (Exception e) { + return; + } + } + + /** + * Parses the output of getprop, which is the only way to get DNS + * info on Android. getprop might disappear in future releases, so + * this code comes with a use-by date. + */ + private + void findAndroid() { + // This originally looked for all lines containing .dns; but + // http://code.google.com/p/android/issues/detail?id=2207#c73 + // indicates that net.dns* should always be the active nameservers, so + // we use those. + final String re1 = "^\\d+(\\.\\d+){3}$"; + final String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$"; + ArrayList lserver = new ArrayList(); + ArrayList lsearch = new ArrayList(); + try { + Class SystemProperties = Class.forName("android.os.SystemProperties"); + Method method = SystemProperties.getMethod("get", new Class[] {String.class}); + final String[] netdns = new String[] {"net.dns1", "net.dns2", "net.dns3", "net.dns4"}; + for (int i = 0; i < netdns.length; i++) { + Object[] args = new Object[] {netdns[i]}; + String v = (String) method.invoke(null, args); + if (v != null && (v.matches(re1) || v.matches(re2)) && !lserver.contains(v)) { + lserver.add(v); + } + } + } catch (Exception e) { + // ignore resolutely + } + configureFromLists(lserver, lsearch); + } + + /** + * Returns all located servers + */ + public + String[] servers() { + return servers; + } + + /** + * Returns the first located server + */ + public + String server() { + if (servers == null) { + return null; + } + return servers[0]; + } + + /** + * Returns all entries in the located search path + */ + public + Name[] searchPath() { + return searchlist; + } + + /** + * Returns the located ndots value, or the default (1) if not configured. + * Note that ndots can only be configured in a resolv.conf file, and will only + * take effect if ResolverConfig uses resolv.conf directly (that is, if the + * JVM does not include the sun.net.dns.ResolverConfiguration class). + */ + public + int ndots() { + if (ndots < 0) { + return 1; + } + return ndots; + } + + /** + * Gets the current configuration + */ + public static synchronized + ResolverConfig getCurrentConfig() { + return currentConfig; + } + + /** + * Gets the current configuration + */ + public static + void refresh() { + ResolverConfig newConfig = new ResolverConfig(); + synchronized (ResolverConfig.class) { + currentConfig = newConfig; + } + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/ResolverListener.java b/src-wip/org/xbill/DNS2/resolver/ResolverListener.java new file mode 100755 index 0000000..a2c65ff --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/ResolverListener.java @@ -0,0 +1,35 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.util.EventListener; + +import dorkbox.network.dns.records.DnsMessage; + +/** + * An interface to the asynchronous resolver. + * + * @author Brian Wellington + * @see Resolver + */ + +public +interface ResolverListener extends EventListener { + + /** + * The callback used by an asynchronous resolver + * + * @param id The identifier returned by Resolver.sendAsync() + * @param m The response message as returned by the Resolver + */ + void receiveMessage(Object id, DnsMessage m); + + /** + * The callback used by an asynchronous resolver when an exception is thrown + * + * @param id The identifier returned by Resolver.sendAsync() + * @param e The thrown exception + */ + void handleException(Object id, Exception e); + +} diff --git a/src-wip/org/xbill/DNS2/resolver/SetResponse.java b/src-wip/org/xbill/DNS2/resolver/SetResponse.java new file mode 100755 index 0000000..052952d --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/SetResponse.java @@ -0,0 +1,244 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.util.ArrayList; +import java.util.List; + +import org.xbill.DNS2.clients.Zone; + +import dorkbox.network.dns.records.CNAMERecord; +import dorkbox.network.dns.records.DNAMERecord; +import dorkbox.network.dns.records.RRset; + +/** + * The Response from a query to Cache.lookupRecords() or Zone.findRecords() + * + * @author Brian Wellington + * @see Cache + * @see Zone + */ + +public +class SetResponse { + + /** + * The Cache contains no information about the requested name/type + */ + public static final int UNKNOWN = 0; + + /** + * The Zone does not contain the requested name, or the Cache has + * determined that the name does not exist. + */ + public static final int NXDOMAIN = 1; + + /** + * The Zone contains the name, but no data of the requested type, + * or the Cache has determined that the name exists and has no data + * of the requested type. + */ + public static final int NXRRSET = 2; + + /** + * A delegation enclosing the requested name was found. + */ + public static final int DELEGATION = 3; + + /** + * The Cache/Zone found a CNAME when looking for the name. + * + * @see CNAMERecord + */ + public static final int CNAME = 4; + + /** + * The Cache/Zone found a DNAME when looking for the name. + * + * @see DNAMERecord + */ + public static final int DNAME = 5; + + /** + * The Cache/Zone has successfully answered the question for the + * requested name/type/class. + */ + public static final int SUCCESSFUL = 6; + + private static final SetResponse unknown = new SetResponse(UNKNOWN); + private static final SetResponse nxdomain = new SetResponse(NXDOMAIN); + private static final SetResponse nxrrset = new SetResponse(NXRRSET); + + private int type; + private Object data; + + private + SetResponse() {} + + public + SetResponse(int type, RRset rrset) { + if (type < 0 || type > 6) { + throw new IllegalArgumentException("invalid type"); + } + this.type = type; + this.data = rrset; + } + + public + SetResponse(int type) { + if (type < 0 || type > 6) { + throw new IllegalArgumentException("invalid type"); + } + this.type = type; + this.data = null; + } + + public static + SetResponse ofType(int type) { + switch (type) { + case UNKNOWN: + return unknown; + case NXDOMAIN: + return nxdomain; + case NXRRSET: + return nxrrset; + case DELEGATION: + case CNAME: + case DNAME: + case SUCCESSFUL: + SetResponse sr = new SetResponse(); + sr.type = type; + sr.data = null; + return sr; + default: + throw new IllegalArgumentException("invalid type"); + } + } + + public + void addRRset(RRset rrset) { + if (data == null) { + data = new ArrayList(); + } + List l = (List) data; + l.add(rrset); + } + + /** + * Is the answer to the query unknown? + */ + public + boolean isUnknown() { + return (type == UNKNOWN); + } + + /** + * Is the answer to the query that the name does not exist? + */ + public + boolean isNXDOMAIN() { + return (type == NXDOMAIN); + } + + /** + * Is the answer to the query that the name exists, but the type does not? + */ + public + boolean isNXRRSET() { + return (type == NXRRSET); + } + + /** + * Is the result of the lookup that the name is below a delegation? + */ + public + boolean isDelegation() { + return (type == DELEGATION); + } + + /** + * Is the result of the lookup a CNAME? + */ + public + boolean isCNAME() { + return (type == CNAME); + } + + /** + * Is the result of the lookup a DNAME? + */ + public + boolean isDNAME() { + return (type == DNAME); + } + + /** + * Was the query successful? + */ + public + boolean isSuccessful() { + return (type == SUCCESSFUL); + } + + /** + * If the query was successful, return the answers + */ + public + RRset[] answers() { + if (type != SUCCESSFUL) { + return null; + } + List l = (List) data; + return (RRset[]) l.toArray(new RRset[l.size()]); + } + + /** + * If the query encountered a CNAME, return it. + */ + public + CNAMERecord getCNAME() { + return (CNAMERecord) ((RRset) data).first(); + } + + /** + * If the query encountered a DNAME, return it. + */ + public + DNAMERecord getDNAME() { + return (DNAMERecord) ((RRset) data).first(); + } + + /** + * If the query hit a delegation point, return the NS set. + */ + public + RRset getNS() { + return (RRset) data; + } + + /** + * Prints the value of the SetResponse + */ + public + String toString() { + switch (type) { + case UNKNOWN: + return "unknown"; + case NXDOMAIN: + return "NXDOMAIN"; + case NXRRSET: + return "NXRRSET"; + case DELEGATION: + return "delegation: " + data; + case CNAME: + return "CNAME: " + data; + case DNAME: + return "DNAME: " + data; + case SUCCESSFUL: + return "successful"; + default: + throw new IllegalStateException(); + } + } + +} diff --git a/src-wip/org/xbill/DNS2/resolver/SimpleResolver.java b/src-wip/org/xbill/DNS2/resolver/SimpleResolver.java new file mode 100755 index 0000000..c0e21ad --- /dev/null +++ b/src-wip/org/xbill/DNS2/resolver/SimpleResolver.java @@ -0,0 +1,421 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.resolver; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.List; + +import org.xbill.DNS2.clients.TCPClient; +import org.xbill.DNS2.clients.UDPClient; +import org.xbill.DNS2.clients.ZoneTransferIn; + +import dorkbox.network.dns.Name; +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.constants.Flags; +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.Header; +import dorkbox.network.dns.records.OPTRecord; +import dorkbox.network.dns.records.TSIG; +import dorkbox.network.dns.utils.Options; + +/** + * An implementation of Resolver that sends one query to one server. + * SimpleResolver handles TCP retries, transaction security (TSIG), and + * EDNS 0. + * + * @author Brian Wellington + * @see Resolver + * @see TSIG + * @see OPTRecord + */ + + +public +class SimpleResolver implements Resolver { + + /** + * The default port to send queries to + */ + public static final int DEFAULT_PORT = 53; + + /** + * The default EDNS payload size + */ + public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; + + private InetSocketAddress address; + private InetSocketAddress localAddress; + private boolean useTCP, ignoreTruncation; + private OPTRecord queryOPT; + private TSIG tsig; + private long timeoutValue = 10 * 1000; + + private static final short DEFAULT_UDPSIZE = 512; + + private static String defaultResolver = "localhost"; + private static int uniqueID = 0; + + /** + * Creates a SimpleResolver. The host to query is either found by using + * ResolverConfig, or the default host is used. + * + * @throws UnknownHostException Failure occurred while finding the host + * @see ResolverConfig + */ + public + SimpleResolver() throws UnknownHostException { + this(null); + } + + /** + * Creates a SimpleResolver that will query the specified host + * + * @throws UnknownHostException Failure occurred while finding the host + */ + public + SimpleResolver(String hostname) throws UnknownHostException { + if (hostname == null) { + hostname = ResolverConfig.getCurrentConfig() + .server(); + if (hostname == null) { + hostname = defaultResolver; + } + } + InetAddress addr; + if (hostname.equals("0")) { + addr = InetAddress.getLocalHost(); + } + else { + addr = InetAddress.getByName(hostname); + } + address = new InetSocketAddress(addr, DEFAULT_PORT); + } + + /** + * Gets the destination address associated with this SimpleResolver. + * Messages sent using this SimpleResolver will be sent to this address. + * + * @return The destination address associated with this SimpleResolver. + */ + public + InetSocketAddress getAddress() { + return address; + } + + /** + * Sets the address of the server to communicate with (on the default + * DNS port) + * + * @param addr The address of the DNS server + */ + public + void setAddress(InetAddress addr) { + address = new InetSocketAddress(addr, address.getPort()); + } + + /** + * Sets the default host (initially localhost) to query + */ + public static + void setDefaultResolver(String hostname) { + defaultResolver = hostname; + } + + @Override + public + void setPort(int port) { + address = new InetSocketAddress(address.getAddress(), port); + } + + @Override + public + void setTCP(boolean flag) { + this.useTCP = flag; + } + + @Override + public + void setIgnoreTruncation(boolean flag) { + this.ignoreTruncation = flag; + } + + @Override + public + void setEDNS(int level) { + setEDNS(level, 0, 0, null); + } + + @Override + public + void setEDNS(int level, int payloadSize, int flags, List options) { + if (level != 0 && level != -1) { + throw new IllegalArgumentException("invalid EDNS level - " + "must be 0 or -1"); + } + if (payloadSize == 0) { + payloadSize = DEFAULT_EDNS_PAYLOADSIZE; + } + queryOPT = new OPTRecord(payloadSize, 0, level, flags, options); + } + + /** + * Sets the address of the server to communicate with. + * + * @param addr The address of the DNS server + */ + public + void setAddress(InetSocketAddress addr) { + address = addr; + } + + /** + * Sets the local address to bind to when sending messages. + * + * @param addr The local address to send messages from. + */ + public + void setLocalAddress(InetSocketAddress addr) { + localAddress = addr; + } + + /** + * Sets the local address to bind to when sending messages. A random port + * will be used. + * + * @param addr The local address to send messages from. + */ + public + void setLocalAddress(InetAddress addr) { + localAddress = new InetSocketAddress(addr, 0); + } + + TSIG getTSIGKey() { + return tsig; + } + + @Override + public + void setTSIGKey(TSIG key) { + tsig = key; + } + + @Override + public + void setTimeout(int secs, int msecs) { + timeoutValue = (long) secs * 1000 + msecs; + } + + @Override + public + void setTimeout(int secs) { + setTimeout(secs, 0); + } + + long getTimeout() { + return timeoutValue; + } + + private + DnsMessage parseMessage(byte[] b) throws WireParseException { + try { + return (new DnsMessage(b)); + } catch (IOException e) { + if (Options.check("verbose")) { + e.printStackTrace(); + } + if (!(e instanceof WireParseException)) { + e = new WireParseException("Error parsing message"); + } + throw (WireParseException) e; + } + } + + private + void verifyTSIG(DnsMessage query, DnsMessage response, byte[] b, TSIG tsig) { + if (tsig == null) { + return; + } + int error = tsig.verify(response, b, query.getTSIG()); + if (Options.check("verbose")) { + System.err.println("TSIG verify: " + DnsResponseCode.TSIGstring(error)); + } + } + + private + void applyEDNS(DnsMessage query) { + if (queryOPT == null || query.getOPT() != null) { + return; + } + query.addRecord(queryOPT, DnsSection.ADDITIONAL); + } + + private + int maxUDPSize(DnsMessage query) { + OPTRecord opt = query.getOPT(); + if (opt == null) { + return DEFAULT_UDPSIZE; + } + else { + return opt.getPayloadSize(); + } + } + + /** + * Sends a message to a single server and waits for a response. No checking + * is done to ensure that the response is associated with the query. + * + * @param query The query to send. + * + * @return The response. + * + * @throws IOException An error occurred while sending or receiving. + */ + @Override + public + DnsMessage send(DnsMessage query) throws IOException { + if (Options.check("verbose")) { + System.err.println("Sending to " + address.getAddress() + .getHostAddress() + ":" + address.getPort()); + } + + if (query.getHeader() + .getOpcode() == DnsOpCode.QUERY) { + DnsRecord question = query.getQuestion(); + if (question != null && question.getType() == DnsRecordType.AXFR) { + return sendAXFR(query); + } + } + + query = (DnsMessage) query.clone(); + applyEDNS(query); + if (tsig != null) { + tsig.apply(query, null); + } + + byte[] out = query.toWire(DnsMessage.MAXLENGTH); + int udpSize = maxUDPSize(query); + boolean tcp = false; + long endTime = System.currentTimeMillis() + timeoutValue; + do { + byte[] in; + + if (useTCP || out.length > udpSize) { + tcp = true; + } + if (tcp) { + in = TCPClient.sendrecv(localAddress, address, out, endTime); + } + else { + in = UDPClient.sendrecv(localAddress, address, out, udpSize, endTime); + } + + /* + * Check that the response is long enough. + */ + if (in.length < Header.LENGTH) { + throw new WireParseException("invalid DNS header - " + "too short"); + } + /* + * Check that the response ID matches the query ID. We want + * to check this before actually parsing the message, so that + * if there's a malformed response that's not ours, it + * doesn't confuse us. + */ + int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF); + int qid = query.getHeader() + .getID(); + if (id != qid) { + String error = "invalid message id: expected " + qid + "; got id " + id; + if (tcp) { + throw new WireParseException(error); + } + else { + if (Options.check("verbose")) { + System.err.println(error); + } + continue; + } + } + DnsMessage response = parseMessage(in); + verifyTSIG(query, response, in, tsig); + if (!tcp && !ignoreTruncation && response.getHeader() + .getFlag(Flags.TC)) { + tcp = true; + continue; + } + return response; + } while (true); + } + + /** + * Asynchronously sends a message to a single server, registering a listener + * to receive a callback on success or exception. Multiple asynchronous + * lookups can be performed in parallel. Since the callback may be invoked + * before the function returns, external synchronization is necessary. + * + * @param query The query to send + * @param listener The object containing the callbacks. + * + * @return An identifier, which is also a parameter in the callback + */ + @Override + public + Object sendAsync(final DnsMessage query, final ResolverListener listener) { + final Object id; + synchronized (this) { + id = new Integer(uniqueID++); + } + DnsRecord question = query.getQuestion(); + String qname; + if (question != null) { + qname = question.getName() + .toString(); + } + else { + qname = "(none)"; + } + String name = this.getClass() + ": " + qname; + Thread thread = new ResolveThread(this, query, id, listener); + thread.setName(name); + thread.setDaemon(true); + thread.start(); + return id; + } + + private + DnsMessage sendAXFR(DnsMessage query) throws IOException { + Name qname = query.getQuestion() + .getName(); + ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig); + xfrin.setTimeout((int) (getTimeout() / 1000)); + xfrin.setLocalAddress(localAddress); + try { + xfrin.run(); + } catch (ZoneTransferException e) { + throw new WireParseException(e.getMessage()); + } + List records = xfrin.getAXFR(); + DnsMessage response = new DnsMessage(query.getHeader() + .getID()); + response.getHeader() + .setFlag(Flags.AA); + response.getHeader() + .setFlag(Flags.QR); + response.addRecord(query.getQuestion(), DnsSection.QUESTION); + Iterator it = records.iterator(); + while (it.hasNext()) { + response.addRecord((DnsRecord) it.next(), DnsSection.ANSWER); + } + return response; + } + +} diff --git a/src-wip/org/xbill/DNS2/spi/DNSJavaNameService.java b/src-wip/org/xbill/DNS2/spi/DNSJavaNameService.java new file mode 100755 index 0000000..0b7351e --- /dev/null +++ b/src-wip/org/xbill/DNS2/spi/DNSJavaNameService.java @@ -0,0 +1,190 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.spi; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.StringTokenizer; + +import org.xbill.DNS2.clients.Lookup; +import org.xbill.DNS2.resolver.ExtendedResolver; +import org.xbill.DNS2.resolver.Resolver; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.records.AAAARecord; +import dorkbox.network.dns.records.ARecord; +import dorkbox.network.dns.records.DnsRecord; +import dorkbox.network.dns.records.PTRRecord; +import dorkbox.network.dns.utils.ReverseMap; + +/** + * This class implements a Name Service Provider, which Java can use + * (starting with version 1.4), to perform DNS resolutions instead of using + * the standard calls. + *

+ * This Name Service Provider uses dnsjava. + *

+ * To use this provider, you must set the following system property: + * sun.net.spi.nameservice.provider.1=dns,dnsjava + * + * @author Brian Wellington + * @author Paul Cowan (pwc21@yahoo.com) + */ + +public +class DNSJavaNameService implements InvocationHandler { + + private static final String nsProperty = "sun.net.spi.nameservice.nameservers"; + private static final String domainProperty = "sun.net.spi.nameservice.domain"; + private static final String v6Property = "java.net.preferIPv6Addresses"; + + private boolean preferV6 = false; + + /** + * Creates a DNSJavaNameService instance. + *

+ * Uses the + * sun.net.spi.nameservice.nameservers, + * sun.net.spi.nameservice.domain, and + * java.net.preferIPv6Addresses properties for configuration. + */ + protected + DNSJavaNameService() { + String nameServers = System.getProperty(nsProperty); + String domain = System.getProperty(domainProperty); + String v6 = System.getProperty(v6Property); + + if (nameServers != null) { + StringTokenizer st = new StringTokenizer(nameServers, ","); + String[] servers = new String[st.countTokens()]; + int n = 0; + while (st.hasMoreTokens()) { + servers[n++] = st.nextToken(); + } + try { + Resolver res = new ExtendedResolver(servers); + Lookup.setDefaultResolver(res); + } catch (UnknownHostException e) { + System.err.println("DNSJavaNameService: invalid " + nsProperty); + } + } + + if (domain != null) { + try { + Lookup.setDefaultSearchPath(new String[] {domain}); + } catch (TextParseException e) { + System.err.println("DNSJavaNameService: invalid " + domainProperty); + } + } + + if (v6 != null && v6.equalsIgnoreCase("true")) { + preferV6 = true; + } + } + + + @Override + public + Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + if (method.getName() + .equals("getHostByAddr")) { + return this.getHostByAddr((byte[]) args[0]); + } + else if (method.getName() + .equals("lookupAllHostAddr")) { + InetAddress[] addresses; + addresses = this.lookupAllHostAddr((String) args[0]); + Class returnType = method.getReturnType(); + if (returnType.equals(InetAddress[].class)) { + // method for Java >= 1.6 + return addresses; + } + else if (returnType.equals(byte[][].class)) { + // method for Java <= 1.5 + int naddrs = addresses.length; + byte[][] byteAddresses = new byte[naddrs][]; + byte[] addr; + for (int i = 0; i < naddrs; i++) { + addr = addresses[i].getAddress(); + byteAddresses[i] = addr; + } + return byteAddresses; + } + } + } catch (Throwable e) { + System.err.println("DNSJavaNameService: Unexpected error."); + e.printStackTrace(); + throw e; + } + throw new IllegalArgumentException("Unknown function name or arguments."); + } + + /** + * Performs a forward DNS lookup for the host name. + * + * @param host The host name to resolve. + * + * @return All the ip addresses found for the host name. + */ + public + InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException { + Name name = null; + + try { + name = new Name(host); + } catch (TextParseException e) { + throw new UnknownHostException(host); + } + + DnsRecord[] records = null; + if (preferV6) { + records = new Lookup(name, DnsRecordType.AAAA).run(); + } + if (records == null) { + records = new Lookup(name, DnsRecordType.A).run(); + } + if (records == null && !preferV6) { + records = new Lookup(name, DnsRecordType.AAAA).run(); + } + if (records == null) { + throw new UnknownHostException(host); + } + + InetAddress[] array = new InetAddress[records.length]; + for (int i = 0; i < records.length; i++) { + DnsRecord record = records[i]; + if (records[i] instanceof ARecord) { + ARecord a = (ARecord) records[i]; + array[i] = a.getAddress(); + } + else { + AAAARecord aaaa = (AAAARecord) records[i]; + array[i] = aaaa.getAddress(); + } + } + return array; + } + + /** + * Performs a reverse DNS lookup. + * + * @param addr The ip address to lookup. + * + * @return The host name found for the ip address. + */ + public + String getHostByAddr(byte[] addr) throws UnknownHostException { + Name name = ReverseMap.fromAddress(InetAddress.getByAddress(addr)); + DnsRecord[] records = new Lookup(name, DnsRecordType.PTR).run(); + if (records == null) { + throw new UnknownHostException(); + } + return ((PTRRecord) records[0]).getTarget() + .toString(); + } +} diff --git a/src-wip/org/xbill/DNS2/spi/DNSJavaNameServiceDescriptor.java b/src-wip/org/xbill/DNS2/spi/DNSJavaNameServiceDescriptor.java new file mode 100755 index 0000000..daa1073 --- /dev/null +++ b/src-wip/org/xbill/DNS2/spi/DNSJavaNameServiceDescriptor.java @@ -0,0 +1,48 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.spi; + +import java.lang.reflect.Proxy; + +import sun.net.spi.nameservice.NameService; +import sun.net.spi.nameservice.NameServiceDescriptor; + +/** + * The descriptor class for the dnsjava name service provider. + * + * @author Brian Wellington + * @author Paul Cowan (pwc21@yahoo.com) + */ + +public +class DNSJavaNameServiceDescriptor implements NameServiceDescriptor { + + private static NameService nameService; + + static { + ClassLoader loader = NameService.class.getClassLoader(); + nameService = (NameService) Proxy.newProxyInstance(loader, new Class[] {NameService.class}, new DNSJavaNameService()); + } + + /** + * Returns a reference to a dnsjava name server provider. + */ + @Override + public + NameService createNameService() { + return nameService; + } + + @Override + public + String getProviderName() { + return "dnsjava"; + } + + @Override + public + String getType() { + return "dns"; + } + +} diff --git a/src-wip/org/xbill/DNS2/spi/services/sun.net.spi.nameservice.NameServiceDescriptor b/src-wip/org/xbill/DNS2/spi/services/sun.net.spi.nameservice.NameServiceDescriptor new file mode 100755 index 0000000..1ca895c --- /dev/null +++ b/src-wip/org/xbill/DNS2/spi/services/sun.net.spi.nameservice.NameServiceDescriptor @@ -0,0 +1 @@ +org.xbill.DNS.spi.DNSJavaNameServiceDescriptor diff --git a/src-wip/org/xbill/DNS2/tests/SerialTest.java b/src-wip/org/xbill/DNS2/tests/SerialTest.java new file mode 100755 index 0000000..8db336a --- /dev/null +++ b/src-wip/org/xbill/DNS2/tests/SerialTest.java @@ -0,0 +1,160 @@ +// -*- Java -*- +// +// Copyright (c) 2005, Matthew J. Rutherford +// Copyright (c) 2005, University of Colorado at Boulder +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the University of Colorado at Boulder nor the +// names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +package org.xbill.DNS2.tests; + +import org.xbill.DNS2.clients.Serial; + +import junit.framework.TestCase; + +public +class SerialTest extends TestCase { + public + void test_compare_NegativeArg1() { + long arg1 = -1; + long arg2 = 1; + try { + Serial.compare(arg1, arg2); + fail("compare accepted negative argument 1"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_compare_OOBArg1() { + long arg1 = 0xFFFFFFFFL + 1; + long arg2 = 1; + try { + Serial.compare(arg1, arg2); + fail("compare accepted out-of-bounds argument 1"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_compare_NegativeArg2() { + long arg1 = 1; + long arg2 = -1; + try { + Serial.compare(arg1, arg2); + fail("compare accepted negative argument 2"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_compare_OOBArg2() { + long arg1 = 1; + long arg2 = 0xFFFFFFFFL + 1; + try { + Serial.compare(arg1, arg2); + fail("compare accepted out-of-bounds argument 1"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_compare_Arg1Greater() { + long arg1 = 10; + long arg2 = 9; + int ret = Serial.compare(arg1, arg2); + assertTrue(ret > 0); + } + + public + void test_compare_Arg2Greater() { + long arg1 = 9; + long arg2 = 10; + int ret = Serial.compare(arg1, arg2); + assertTrue(ret < 0); + } + + public + void test_compare_ArgsEqual() { + long arg1 = 10; + long arg2 = 10; + int ret = Serial.compare(arg1, arg2); + assertEquals(ret, 0); + } + + public + void test_compare_boundary() { + long arg1 = 0xFFFFFFFFL; + long arg2 = 0; + int ret = Serial.compare(arg1, arg2); + assertEquals(-1, ret); + ret = Serial.compare(arg2, arg1); + assertEquals(1, ret); + } + + public + void test_increment_NegativeArg() { + long arg = -1; + try { + Serial.increment(arg); + fail("increment accepted negative argument"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_increment_OOBArg() { + long arg = 0xFFFFFFFFL + 1; + try { + Serial.increment(arg); + fail("increment accepted out-of-bounds argument"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public + void test_increment_reset() { + long arg = 0xFFFFFFFFL; + long ret = Serial.increment(arg); + assertEquals(0, ret); + } + + public + void test_increment_normal() { + long arg = 10; + long ret = Serial.increment(arg); + assertEquals(arg + 1, ret); + } +} diff --git a/src-wip/org/xbill/DNS2/tests/SetResponseTest.java b/src-wip/org/xbill/DNS2/tests/SetResponseTest.java new file mode 100755 index 0000000..4b78d16 --- /dev/null +++ b/src-wip/org/xbill/DNS2/tests/SetResponseTest.java @@ -0,0 +1,254 @@ +// -*- Java -*- +// +// Copyright (c) 2005, Matthew J. Rutherford +// Copyright (c) 2005, University of Colorado at Boulder +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the University of Colorado at Boulder nor the +// names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +package org.xbill.DNS2.tests; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +import org.xbill.DNS2.resolver.SetResponse; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.records.ARecord; +import dorkbox.network.dns.records.CNAMERecord; +import dorkbox.network.dns.records.DNAMERecord; +import dorkbox.network.dns.records.RRset; +import junit.framework.TestCase; + +public +class SetResponseTest extends TestCase { + public + void test_ctor_1arg() { + final int[] types = new int[] {SetResponse.UNKNOWN, SetResponse.NXDOMAIN, SetResponse.NXRRSET, SetResponse.DELEGATION, + SetResponse.CNAME, SetResponse.DNAME, SetResponse.SUCCESSFUL}; + + for (int i = 0; i < types.length; ++i) { + SetResponse sr = new SetResponse(types[i]); + assertNull(sr.getNS()); + assertEquals(types[i] == SetResponse.UNKNOWN, sr.isUnknown()); + assertEquals(types[i] == SetResponse.NXDOMAIN, sr.isNXDOMAIN()); + assertEquals(types[i] == SetResponse.NXRRSET, sr.isNXRRSET()); + assertEquals(types[i] == SetResponse.DELEGATION, sr.isDelegation()); + assertEquals(types[i] == SetResponse.CNAME, sr.isCNAME()); + assertEquals(types[i] == SetResponse.DNAME, sr.isDNAME()); + assertEquals(types[i] == SetResponse.SUCCESSFUL, sr.isSuccessful()); + } + } + + public + void test_ctor_1arg_toosmall() { + try { + new SetResponse(-1); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException a) { + } + } + + public + void test_ctor_1arg_toobig() { + try { + new SetResponse(7); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException a) { + } + } + + public + void test_ctor_2arg() { + final int[] types = new int[] {SetResponse.UNKNOWN, SetResponse.NXDOMAIN, SetResponse.NXRRSET, SetResponse.DELEGATION, + SetResponse.CNAME, SetResponse.DNAME, SetResponse.SUCCESSFUL}; + + for (int i = 0; i < types.length; ++i) { + RRset rs = new RRset(); + SetResponse sr = new SetResponse(types[i], rs); + assertSame(rs, sr.getNS()); + assertEquals(types[i] == SetResponse.UNKNOWN, sr.isUnknown()); + assertEquals(types[i] == SetResponse.NXDOMAIN, sr.isNXDOMAIN()); + assertEquals(types[i] == SetResponse.NXRRSET, sr.isNXRRSET()); + assertEquals(types[i] == SetResponse.DELEGATION, sr.isDelegation()); + assertEquals(types[i] == SetResponse.CNAME, sr.isCNAME()); + assertEquals(types[i] == SetResponse.DNAME, sr.isDNAME()); + assertEquals(types[i] == SetResponse.SUCCESSFUL, sr.isSuccessful()); + } + } + + public + void test_ctor_2arg_toosmall() { + try { + new SetResponse(-1, new RRset()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException a) { + } + } + + public + void test_ctor_2arg_toobig() { + try { + new SetResponse(7, new RRset()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException a) { + } + } + + public + void test_ofType_basic() { + final int[] types = new int[] {SetResponse.DELEGATION, SetResponse.CNAME, SetResponse.DNAME, SetResponse.SUCCESSFUL}; + + for (int i = 0; i < types.length; ++i) { + SetResponse sr = SetResponse.ofType(types[i]); + assertNull(sr.getNS()); + assertEquals(types[i] == SetResponse.UNKNOWN, sr.isUnknown()); + assertEquals(types[i] == SetResponse.NXDOMAIN, sr.isNXDOMAIN()); + assertEquals(types[i] == SetResponse.NXRRSET, sr.isNXRRSET()); + assertEquals(types[i] == SetResponse.DELEGATION, sr.isDelegation()); + assertEquals(types[i] == SetResponse.CNAME, sr.isCNAME()); + assertEquals(types[i] == SetResponse.DNAME, sr.isDNAME()); + assertEquals(types[i] == SetResponse.SUCCESSFUL, sr.isSuccessful()); + + SetResponse sr2 = SetResponse.ofType(types[i]); + assertNotSame(sr, sr2); + } + } + + public + void test_ofType_singleton() { + final int[] types = new int[] {SetResponse.UNKNOWN, SetResponse.NXDOMAIN, SetResponse.NXRRSET}; + + for (int i = 0; i < types.length; ++i) { + SetResponse sr = SetResponse.ofType(types[i]); + assertNull(sr.getNS()); + assertEquals(types[i] == SetResponse.UNKNOWN, sr.isUnknown()); + assertEquals(types[i] == SetResponse.NXDOMAIN, sr.isNXDOMAIN()); + assertEquals(types[i] == SetResponse.NXRRSET, sr.isNXRRSET()); + assertEquals(types[i] == SetResponse.DELEGATION, sr.isDelegation()); + assertEquals(types[i] == SetResponse.CNAME, sr.isCNAME()); + assertEquals(types[i] == SetResponse.DNAME, sr.isDNAME()); + assertEquals(types[i] == SetResponse.SUCCESSFUL, sr.isSuccessful()); + + SetResponse sr2 = SetResponse.ofType(types[i]); + assertSame(sr, sr2); + } + } + + public + void test_ofType_toosmall() { + try { + SetResponse.ofType(-1); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + public + void test_ofType_toobig() { + try { + SetResponse.ofType(7); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + public + void test_addRRset() throws TextParseException, UnknownHostException { + RRset rrs = new RRset(); + rrs.addRR(new ARecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1"))); + rrs.addRR(new ARecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, InetAddress.getByName("192.168.0.2"))); + SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL); + sr.addRRset(rrs); + + RRset[] exp = new RRset[] {rrs}; + assertTrue(Arrays.equals(exp, sr.answers())); + } + + public + void test_addRRset_multiple() throws TextParseException, UnknownHostException { + RRset rrs = new RRset(); + rrs.addRR(new ARecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1"))); + rrs.addRR(new ARecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, InetAddress.getByName("192.168.0.2"))); + + RRset rrs2 = new RRset(); + rrs2.addRR(new ARecord(Name.Companion.fromString("The.Other.Name."), DnsClass.IN, 0xABCE, InetAddress.getByName("192.168.1.1"))); + rrs2.addRR(new ARecord(Name.Companion.fromString("The.Other.Name."), DnsClass.IN, 0xABCE, InetAddress.getByName("192.168.1.2"))); + + SetResponse sr = new SetResponse(SetResponse.SUCCESSFUL); + sr.addRRset(rrs); + sr.addRRset(rrs2); + + RRset[] exp = new RRset[] {rrs, rrs2}; + assertTrue(Arrays.equals(exp, sr.answers())); + } + + public + void test_answers_nonSUCCESSFUL() { + SetResponse sr = new SetResponse(SetResponse.UNKNOWN, new RRset()); + assertNull(sr.answers()); + } + + public + void test_getCNAME() throws TextParseException, UnknownHostException { + RRset rrs = new RRset(); + CNAMERecord cr = new CNAMERecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, Name.Companion.fromString("The.Alias.")); + rrs.addRR(cr); + SetResponse sr = new SetResponse(SetResponse.CNAME, rrs); + assertEquals(cr, sr.getCNAME()); + } + + public + void test_getDNAME() throws TextParseException, UnknownHostException { + RRset rrs = new RRset(); + DNAMERecord dr = new DNAMERecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, Name.Companion.fromString("The.Alias.")); + rrs.addRR(dr); + SetResponse sr = new SetResponse(SetResponse.DNAME, rrs); + assertEquals(dr, sr.getDNAME()); + } + + public + void test_toString() throws TextParseException, UnknownHostException { + final int[] types = new int[] {SetResponse.UNKNOWN, SetResponse.NXDOMAIN, SetResponse.NXRRSET, SetResponse.DELEGATION, + SetResponse.CNAME, SetResponse.DNAME, SetResponse.SUCCESSFUL}; + RRset rrs = new RRset(); + rrs.addRR(new ARecord(Name.Companion.fromString("The.Name."), DnsClass.IN, 0xABCD, InetAddress.getByName("192.168.0.1"))); + + final String[] labels = new String[] {"unknown", "NXDOMAIN", "NXRRSET", "delegation: " + rrs, "CNAME: " + rrs, "DNAME: " + rrs, + "successful"}; + + for (int i = 0; i < types.length; ++i) { + SetResponse sr = new SetResponse(types[i], rrs); + assertEquals(labels[i], sr.toString()); + } + } +} diff --git a/src-wip/org/xbill/DNS2/tests/primary.java b/src-wip/org/xbill/DNS2/tests/primary.java new file mode 100755 index 0000000..f7961c0 --- /dev/null +++ b/src-wip/org/xbill/DNS2/tests/primary.java @@ -0,0 +1,70 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.tests; + +import java.util.Iterator; + +import org.xbill.DNS2.clients.Zone; + +import dorkbox.network.dns.Name; + +public +class primary { + + public static + void main(String[] args) throws Exception { + boolean time = false; + boolean axfr = false; + boolean iterator = false; + int arg = 0; + + if (args.length < 2) { + usage(); + } + + while (args.length - arg > 2) { + if (args[0].equals("-t")) { + time = true; + } + else if (args[0].equals("-a")) { + axfr = true; + } + else if (args[0].equals("-i")) { + iterator = true; + } + arg++; + } + + Name origin = Name.Companion.fromString(args[arg++], Name.root); + String file = args[arg++]; + + long start = System.currentTimeMillis(); + Zone zone = new Zone(origin, file); + long end = System.currentTimeMillis(); + if (axfr) { + Iterator it = zone.AXFR(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } + else if (iterator) { + Iterator it = zone.iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } + else { + System.out.println(zone); + } + if (time) { + System.out.println("; Load time: " + (end - start) + " ms"); + } + } + + private static + void usage() { + System.out.println("usage: primary [-t] [-a | -i] origin file"); + System.exit(1); + } + +} diff --git a/src-wip/org/xbill/DNS2/tests/xfrin.java b/src-wip/org/xbill/DNS2/tests/xfrin.java new file mode 100755 index 0000000..26d339a --- /dev/null +++ b/src-wip/org/xbill/DNS2/tests/xfrin.java @@ -0,0 +1,136 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS2.tests; + +import java.util.Iterator; +import java.util.List; + +import org.xbill.DNS2.clients.Lookup; +import org.xbill.DNS2.clients.ZoneTransferIn; +import org.xbill.DNS2.resolver.SimpleResolver; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.records.DnsRecord; +import dorkbox.network.dns.records.TSIG; + +public +class xfrin { + + public static + void main(String[] args) throws Exception { + ZoneTransferIn xfrin; + TSIG key = null; + int ixfr_serial = -1; + String server = null; + int port = SimpleResolver.DEFAULT_PORT; + boolean fallback = false; + Name zname; + + int arg = 0; + while (arg < args.length) { + if (args[arg].equals("-i")) { + ixfr_serial = Integer.parseInt(args[++arg]); + if (ixfr_serial < 0) { + usage("invalid serial number"); + } + } + else if (args[arg].equals("-k")) { + String s = args[++arg]; + int index = s.indexOf('/'); + if (index < 0) { + usage("invalid key"); + } + key = new TSIG(s.substring(0, index), s.substring(index + 1)); + } + else if (args[arg].equals("-s")) { + server = args[++arg]; + } + else if (args[arg].equals("-p")) { + port = Integer.parseInt(args[++arg]); + if (port < 0 || port > 0xFFFF) { + usage("invalid port"); + } + } + else if (args[arg].equals("-f")) { + fallback = true; + } + else if (args[arg].startsWith("-")) { + usage("invalid option"); + } + else { + break; + } + arg++; + } + if (arg >= args.length) { + usage("no zone name specified"); + } + zname = Name.Companion.fromString(args[arg]); + + if (server == null) { + Lookup l = new Lookup(zname, DnsRecordType.NS); + DnsRecord[] ns = l.run(); + if (ns == null) { + System.out.println("failed to look up NS record: " + l.getErrorString()); + System.exit(1); + } + StringBuilder stringBuilder = new StringBuilder(); + ns[0].rdataToString(stringBuilder); + + server = stringBuilder.toString(); + System.out.println("sending to server '" + server + "'"); + } + + if (ixfr_serial >= 0) { + xfrin = ZoneTransferIn.newIXFR(zname, ixfr_serial, fallback, server, port, key); + } + else { + xfrin = ZoneTransferIn.newAXFR(zname, server, port, key); + } + + List response = xfrin.run(); + if (xfrin.isAXFR()) { + if (ixfr_serial >= 0) { + System.out.println("AXFR-like IXFR response"); + } + else { + System.out.println("AXFR response"); + } + Iterator it = response.iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } + else if (xfrin.isIXFR()) { + System.out.println("IXFR response"); + Iterator it = response.iterator(); + while (it.hasNext()) { + ZoneTransferIn.Delta delta; + delta = (ZoneTransferIn.Delta) it.next(); + System.out.println("delta from " + delta.start + " to " + delta.end); + System.out.println("deletes"); + Iterator it2 = delta.deletes.iterator(); + while (it2.hasNext()) { + System.out.println(it2.next()); + } + System.out.println("adds"); + it2 = delta.adds.iterator(); + while (it2.hasNext()) { + System.out.println(it2.next()); + } + } + } + else if (xfrin.isCurrent()) { + System.out.println("up to date"); + } + } + + private static + void usage(String s) { + System.out.println("Error: " + s); + System.out.println("usage: xfrin [-i serial] [-k keyname/secret] " + "[-s server] [-p port] [-f] zone"); + System.exit(1); + } + +} diff --git a/src-wip/org/xbill/DNS2/windows/DNSServer.properties b/src-wip/org/xbill/DNS2/windows/DNSServer.properties new file mode 100755 index 0000000..25342f9 --- /dev/null +++ b/src-wip/org/xbill/DNS2/windows/DNSServer.properties @@ -0,0 +1,4 @@ +host_name=Host Name +primary_dns_suffix=Primary Dns Suffix +dns_suffix=DNS Suffix +dns_servers=DNS Servers diff --git a/src-wip/org/xbill/DNS2/windows/DNSServer_de.properties b/src-wip/org/xbill/DNS2/windows/DNSServer_de.properties new file mode 100755 index 0000000..aa3f4a6 --- /dev/null +++ b/src-wip/org/xbill/DNS2/windows/DNSServer_de.properties @@ -0,0 +1,4 @@ +host_name=Hostname +primary_dns_suffix=Prim\u00E4res DNS-Suffix +dns_suffix=DNS-Suffixsuchliste +dns_servers=DNS-Server diff --git a/src-wip/org/xbill/DNS2/windows/DNSServer_fr.properties b/src-wip/org/xbill/DNS2/windows/DNSServer_fr.properties new file mode 100755 index 0000000..7c87a25 --- /dev/null +++ b/src-wip/org/xbill/DNS2/windows/DNSServer_fr.properties @@ -0,0 +1,4 @@ +host_name=Nom de l'h\u00F4te +primary_dns_suffix=Suffixe DNS principal +dns_suffix=Suffixe DNS propre \u00E0 la connexion +dns_servers=Serveurs DNS diff --git a/src-wip/org/xbill/DNS2/windows/DNSServer_ja.properties b/src-wip/org/xbill/DNS2/windows/DNSServer_ja.properties new file mode 100755 index 0000000..f873164 --- /dev/null +++ b/src-wip/org/xbill/DNS2/windows/DNSServer_ja.properties @@ -0,0 +1,4 @@ +host_name=\u30db\u30b9\u30c8\u540d +primary_dns_suffix=\u30d7\u30e9\u30a4\u30de\u30ea DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 +dns_suffix=DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 +dns_servers=DNS \u30b5\u30fc\u30d0\u30fc diff --git a/src-wip/org/xbill/DNS2/windows/DNSServer_pl.properties b/src-wip/org/xbill/DNS2/windows/DNSServer_pl.properties new file mode 100755 index 0000000..eab5774 --- /dev/null +++ b/src-wip/org/xbill/DNS2/windows/DNSServer_pl.properties @@ -0,0 +1,4 @@ +host_name=Nazwa hosta +primary_dns_suffix=Sufiks podstawowej domeny DNS +dns_suffix=Sufiks DNS konkretnego po\u0142\u0105czenia +dns_servers=Serwery DNS diff --git a/src-wip/werkzeugkasten/common/exception/IORuntimeException.java b/src-wip/werkzeugkasten/common/exception/IORuntimeException.java new file mode 100755 index 0000000..44ef0c8 --- /dev/null +++ b/src-wip/werkzeugkasten/common/exception/IORuntimeException.java @@ -0,0 +1,12 @@ +package werkzeugkasten.common.exception; + +import java.io.IOException; + +public class IORuntimeException extends RuntimeException { + + private static final long serialVersionUID = -7720465648565468997L; + + public IORuntimeException(IOException e) { + super(e); + } +} diff --git a/src-wip/werkzeugkasten/common/exception/XMLStreamRuntimeException.java b/src-wip/werkzeugkasten/common/exception/XMLStreamRuntimeException.java new file mode 100755 index 0000000..4977aae --- /dev/null +++ b/src-wip/werkzeugkasten/common/exception/XMLStreamRuntimeException.java @@ -0,0 +1,12 @@ +package werkzeugkasten.common.exception; + +import javax.xml.stream.XMLStreamException; + +public class XMLStreamRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 1975130756272851096L; + + public XMLStreamRuntimeException(XMLStreamException e) { + super(e); + } +} diff --git a/src-wip/werkzeugkasten/common/util/ArrayUtil.java b/src-wip/werkzeugkasten/common/util/ArrayUtil.java new file mode 100755 index 0000000..4bb1fe9 --- /dev/null +++ b/src-wip/werkzeugkasten/common/util/ArrayUtil.java @@ -0,0 +1,262 @@ +package werkzeugkasten.common.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * 配列に対するユーティリティクラスです。 + * + * @author taichi + * + */ +public class ArrayUtil { + + /** + * 配列にオブジェクトを追加します。 + * + * @param array + * @param obj + * @return オブジェクトが追加された結果の配列 + */ + @SuppressWarnings("unchecked") + public static T[] add(T[] array, T obj) { + if (array == null) { + throw new IllegalStateException("array"); + } + T[] newArray = (T[]) Array.newInstance(array.getClass() + .getComponentType(), array.length + 1); + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = obj; + return newArray; + } + + /** + * 配列に配列を追加します。 + * + * @param a + * @param b + * @return 配列が追加された結果の配列 + */ + public static Object[] add(final Object[] a, final Object[] b) { + if ((a != null) && (b != null)) { + if ((a.length != 0) && (b.length != 0)) { + Object[] array = (Object[]) Array.newInstance(a.getClass() + .getComponentType(), a.length + b.length); + System.arraycopy(a, 0, array, 0, a.length); + System.arraycopy(b, 0, array, a.length, b.length); + return array; + } else if (b.length == 0) { + return a; + } else { + return b; + } + } else if (b == null) { + return a; + } else { + return b; + } + } + + /** + * 配列中のオブジェクトのindexを返します。 + * + * @param array + * @param obj + * @return 配列中のオブジェクトのindex + */ + public static int indexOf(Object[] array, Object obj) { + if (array != null) { + for (int i = 0; i < array.length; ++i) { + Object o = array[i]; + if (o != null) { + if (o.equals(obj)) { + return i; + } + } else if (obj == null) { + return i; + + } + } + } + return -1; + } + + /** + * 配列中のcharのindexを返します。 + * + * @param array + * @param ch + * @return 配列中のcharのindex + * @see Arrays#binarySearch(char[], char) + */ + public static int indexOf(char[] array, char ch) { + if (array != null) { + for (int i = 0; i < array.length; ++i) { + char c = array[i]; + if (ch == c) { + return i; + } + } + } + return -1; + } + + /** + * 配列中から対象のオブジェクトを削除します。 + * + * @param array + * @param obj + * @return 削除後の配列 + */ + public static Object[] remove(Object[] array, Object obj) { + int index = indexOf(array, obj); + if (index < 0) { + return array; + } + Object[] newArray = (Object[]) Array.newInstance(array.getClass() + .getComponentType(), array.length - 1); + if (index > 0) { + System.arraycopy(array, 0, newArray, 0, index); + } + if (index < array.length - 1) { + System.arraycopy(array, index + 1, newArray, index, newArray.length + - index); + } + return newArray; + } + + /** + * 配列が空かどうかを返します。 + * + * @param arrays + * @return 配列が空かどうか + */ + public static boolean isEmpty(Object[] arrays) { + return ((arrays == null) || (arrays.length == 0)); + } + + /** + * 配列にオブジェクトが含まれているかどうかを返します。 + * + * @param array + * @param obj + * @return 配列にオブジェクトが含まれているかどうか + */ + public static boolean contains(Object[] array, Object obj) { + return -1 < indexOf(array, obj); + } + + /** + * 配列にcharが含まれているかどうかを返します。 + * + * @param array + * @param ch + * @return 配列にcharが含まれているかどうか + */ + public static boolean contains(char[] array, char ch) { + return -1 < indexOf(array, ch); + } + + /** + * 順番は無視して2つの配列が等しいかどうかを返します。 + * + * @param array1 + * @param array2 + * @return 順番は無視して2つの配列が等しいかどうか + */ + public static boolean equalsIgnoreSequence(Object[] array1, Object[] array2) { + if ((array1 == null) && (array2 == null)) { + return true; + } else if ((array1 == null) || (array2 == null)) { + return false; + } + if (array1.length != array2.length) { + return false; + } + List list = Arrays.asList(array2); + for (int i = 0; i < array1.length; i++) { + Object o1 = array1[i]; + if (!list.contains(o1)) { + return false; + } + } + return true; + } + + /** + * 配列を文字列に変換します。 + * + * @param array + * @return 配列の文字列表現 + * @see Arrays#toString(Object[]) + */ + @Deprecated + public static String toString(Object[] array) { + if (array == null) { + return "null"; + } + if (array.length == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (i == 0) { + sb.append('['); + } else { + sb.append(", "); + } + sb.append(String.valueOf(array[i])); + } + sb.append("]"); + return sb.toString(); + } + + /** + * 配列をオブジェクトの配列に変換します。 + * + * @param obj + * @return オブジェクトの配列 + */ + public static Object[] toObjectArray(Object obj) { + int length = Array.getLength(obj); + Object[] array = new Object[length]; + for (int i = 0; i < length; i++) { + array[i] = Array.get(obj, i); + } + return array; + } + + /** + * {@link Comparable} や {@link Comparator}の実装を行う際に使用するbyte[]の比較処理です。 + * + * @see Comparable#compareTo(Object) + * @see Comparator#compare(Object, Object) + */ + public static int compare(byte[] lefts, byte[] rights) { + if (lefts == rights) { + return 0; + } + if ((lefts != null) && (rights == null)) { + return 1; + } + if ((lefts == null) && (rights != null)) { + return -1; + } + int ll = lefts.length; + int rl = rights.length; + int min = Math.min(ll, rl); + for (int i = 0; i < min; i++) { + byte lb = lefts[i]; + byte rb = rights[i]; + if (lb < rb) { + return -1; + } + if (lb > rb) { + return 1; + } + } + return ll - rl; + } +} diff --git a/src-wip/werkzeugkasten/common/util/Base64Util.java b/src-wip/werkzeugkasten/common/util/Base64Util.java new file mode 100755 index 0000000..34511c0 --- /dev/null +++ b/src-wip/werkzeugkasten/common/util/Base64Util.java @@ -0,0 +1,454 @@ +package werkzeugkasten.common.util; + +/** + * Base64 utility. + * + * @author shot + * + */ +public class Base64Util { + + /** + * Chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts + * all other characters, including any equal signs. + *

+ * + * @see RFC 2045 section 6.8< + * /a> + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1< + * /a> + */ + static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes(); + + /** + * The base length. + */ + static final int BASELENGTH = 255; + + /** + * Lookup length. + */ + static final int LOOKUPLENGTH = 64; + + /** + * Used to calculate the number of bits in a byte. + */ + static final int EIGHTBIT = 8; + + /** + * Used when encoding something which has fewer than 24 bits. + */ + static final int SIXTEENBIT = 16; + + /** + * Used to determine how many bits data contains. + */ + static final int TWENTYFOURBITGROUP = 24; + + /** + * Used to get the number of Quadruples. + */ + static final int FOURBYTE = 4; + + /** + * Used to test the sign of a byte. + */ + static final int SIGN = -128; + + /** + * Byte used to pad output. + */ + static final byte PAD = (byte) '='; + + private static byte[] base64Alphabet = new byte[BASELENGTH]; + + private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH]; + + // Populating the lookup and character arrays + static { + for (int i = 0; i < BASELENGTH; i++) { + base64Alphabet[i] = (byte) -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) { + lookUpBase64Alphabet[i] = (byte) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('0' + j); + } + + lookUpBase64Alphabet[62] = (byte) '+'; + lookUpBase64Alphabet[63] = (byte) '/'; + } + + private static boolean isBase64(byte octect) { + if (octect == PAD) { + return true; + } else if (base64Alphabet[octect] == -1) { + return false; + } else { + return true; + } + } + + /** + * Tests a given byte array to see if it contains only valid characters + * within the Base64 alphabet. + * + * @param arrayOctect + * byte array to test + * @return true if all bytes are valid characters in the Base64 alphabet or + * if the byte array is empty; false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctect) { + + arrayOctect = discardWhitespace(arrayOctect); + + int length = arrayOctect.length; + if (length == 0) { + // shouldn't a 0 length array be valid base64 data? + // return false; + return true; + } + for (int i = 0; i < length; i++) { + if (!isBase64(arrayOctect[i])) { + return false; + } + } + return true; + } + + /** + * Encodes an Object using the base64 algorithm. This method is provided in + * order to satisfy the requirements of the Encoder interface, and will + * throw an EncoderException if the supplied object is not of type byte[]. + * + * @param pObject + * Object to encode + * @return An object (of type byte[]) containing the base64 encoded data + * which corresponds to the byte[] supplied. + * @throws EncoderException + * if the parameter supplied is not of type byte[] + */ + public Object encode(Object pObject) { + if (!(pObject instanceof byte[])) { + throw new RuntimeException( + "Parameter supplied to Base64 encode is not a byte[]"); + } + return encode((byte[]) pObject); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing + * characters in the Base64 alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public static String encode(byte[] pArray) { + return encode(pArray, false); + } + + public static String encode(byte[] pArray, boolean isChunked) { + return encodeBase64(pArray, isChunked); + } + + /** + * Decodes a byte[] containing containing characters in the Base64 alphabet. + * + * @param pArray + * A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public static byte[] decode(String encodedStr) { + return decodeBase64(encodedStr.getBytes()); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the + * output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if isChunked is true this encoder will chunk the base64 output + * into 76 character blocks + * @return Base64-encoded data. + */ + protected static String encodeBase64(byte[] binaryData, boolean isChunked) { + int lengthDataBits = binaryData.length * EIGHTBIT; + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + byte encodedData[] = null; + int encodedDataLength = 0; + int nbrChunks = 0; + + if (fewerThan24bits != 0) { + // data not divisible by 24 bit + encodedDataLength = (numberTriplets + 1) * 4; + } else { + // 16 or 8 bit + encodedDataLength = numberTriplets * 4; + } + + // If the output is to be "chunked" into 76 character sections, + // for compliance with RFC 2045 MIME, then it is important to + // allow for extra length to account for the separator(s) + if (isChunked) { + + nbrChunks = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math + .ceil((float) encodedDataLength / CHUNK_SIZE)); + encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length; + } + + encodedData = new byte[encodedDataLength]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + int i = 0; + int nextSeparatorIndex = CHUNK_SIZE; + int chunksSoFar = 0; + + // log.debug("number of triplets = " + numberTriplets); + for (i = 0; i < numberTriplets; i++) { + dataIndex = i * 3; + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + b3 = binaryData[dataIndex + 2]; + + // log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3); + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) + : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) + : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) + : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + // log.debug( "val2 = " + val2 ); + // log.debug( "k4 = " + (k<<4) ); + // log.debug( "vak = " + (val2 | (k<<4)) ); + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 + | (k << 4)]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) + | val3]; + encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f]; + + encodedIndex += 4; + + // If we are chunking, let's put a chunk separator down. + if (isChunked) { + // this assumes that CHUNK_SIZE % 4 == 0 + if (encodedIndex == nextSeparatorIndex) { + System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, + encodedIndex, CHUNK_SEPARATOR.length); + chunksSoFar++; + nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + + (chunksSoFar * CHUNK_SEPARATOR.length); + encodedIndex += CHUNK_SEPARATOR.length; + } + } + } + + // form integral number of 6-bit groups + dataIndex = i * 3; + + if (fewerThan24bits == EIGHTBIT) { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + // log.debug("b1=" + b1); + // log.debug("b1<<2 = " + (b1>>2) ); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) + : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex + 2] = PAD; + encodedData[encodedIndex + 3] = PAD; + } else if (fewerThan24bits == SIXTEENBIT) { + + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) + : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) + : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 + | (k << 4)]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex + 3] = PAD; + } + + if (isChunked) { + // we also add a separator to the end of the final chunk. + if (chunksSoFar < nbrChunks) { + System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, + encodedDataLength - CHUNK_SEPARATOR.length, + CHUNK_SEPARATOR.length); + } + } + + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + protected static byte[] decodeBase64(byte[] base64Data) { + // RFC 2045 requires that we discard ALL non-Base64 characters + base64Data = discardNonBase64(base64Data); + + // handle the edge case, so we don't have to worry about it later + if (base64Data.length == 0) { + return new byte[0]; + } + + int numberQuadruple = base64Data.length / FOURBYTE; + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; + + // Throw away anything not in base64Data + + int encodedIndex = 0; + int dataIndex = 0; + { + // this sizes the output array properly - rlw + int lastData = base64Data.length; + // ignore the '=' padding + while (base64Data[lastData - 1] == PAD) { + if (--lastData == 0) { + return new byte[0]; + } + } + decodedData = new byte[lastData - numberQuadruple]; + } + + for (int i = 0; i < numberQuadruple; i++) { + dataIndex = i * 4; + marker0 = base64Data[dataIndex + 2]; + marker1 = base64Data[dataIndex + 3]; + + b1 = base64Alphabet[base64Data[dataIndex]]; + b2 = base64Alphabet[base64Data[dataIndex + 1]]; + + if (marker0 != PAD && marker1 != PAD) { + // No PAD e.g 3cQl + b3 = base64Alphabet[marker0]; + b4 = base64Alphabet[marker1]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); + } else if (marker0 == PAD) { + // Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + } else if (marker1 == PAD) { + // One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[marker0]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + } + encodedIndex += 3; + } + return decodedData; + } + + /** + * Discards any whitespace from a base-64 encoded block. + * + * @param data + * The base-64 encoded data to discard the whitespace from. + * @return The data, less whitespace (see RFC 2045). + */ + protected static byte[] discardWhitespace(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + switch (data[i]) { + case (byte) ' ': + case (byte) '\n': + case (byte) '\r': + case (byte) '\t': + break; + default: + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + /** + * Discards any characters outside of the base64 alphabet, per the + * requirements on page 25 of RFC 2045 - "Any characters outside of the + * base64 alphabet are to be ignored in base64 encoded data." + * + * @param data + * The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + protected static byte[] discardNonBase64(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + if (isBase64(data[i])) { + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + +} \ No newline at end of file diff --git a/src-wip/werkzeugkasten/common/util/ClassLoaderAwareEntityResolver.java b/src-wip/werkzeugkasten/common/util/ClassLoaderAwareEntityResolver.java new file mode 100755 index 0000000..49e9cbe --- /dev/null +++ b/src-wip/werkzeugkasten/common/util/ClassLoaderAwareEntityResolver.java @@ -0,0 +1,39 @@ +package werkzeugkasten.common.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class ClassLoaderAwareEntityResolver implements EntityResolver { + + protected Map paths = new HashMap(); + protected ClassLoader classLoader; + + public ClassLoaderAwareEntityResolver() { + this.classLoader = getClass().getClassLoader(); + } + + public ClassLoaderAwareEntityResolver(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + String path = this.paths.get(publicId); + if (StringUtil.isEmpty(path) == false) { + return new InputSource(new BufferedInputStream(this.classLoader + .getResourceAsStream(path))); + } + return null; + } + + public void add(String publicId, String path) { + this.paths.put(publicId, path); + } + +} diff --git a/src-wip/werkzeugkasten/common/util/CollectionUtil.java b/src-wip/werkzeugkasten/common/util/CollectionUtil.java new file mode 100755 index 0000000..f0ae95e --- /dev/null +++ b/src-wip/werkzeugkasten/common/util/CollectionUtil.java @@ -0,0 +1,29 @@ +package werkzeugkasten.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class CollectionUtil { + + public static List toList(Object maybeList) { + if (maybeList instanceof List) { + return (List) maybeList; + } else if (maybeList instanceof Collection) { + return new ArrayList((Collection) maybeList); + } else if (maybeList != null) { + Class clazz = maybeList.getClass(); + if (clazz.isArray()) { + Object[] ary = (Object[]) maybeList; + return Arrays.asList(ary); + } else { + List list = new ArrayList(1); + list.add(maybeList); + return list; + } + } + return Collections.emptyList(); + } +} diff --git a/src-wip/werkzeugkasten/common/util/ConverterUtil.java b/src-wip/werkzeugkasten/common/util/ConverterUtil.java new file mode 100755 index 0000000..736ff89 --- /dev/null +++ b/src-wip/werkzeugkasten/common/util/ConverterUtil.java @@ -0,0 +1,753 @@ +package werkzeugkasten.common.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Time; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * ConverterUtil is an utility class that converts object. + * + * @author shot + * @author taichi + */ +public class ConverterUtil { + + static final Logger LOG = Logger.getLogger(ConverterUtil.class.getName()); + + public static final Pattern YES_PATTERN = Pattern.compile("(yes|true|y|1)", + Pattern.CASE_INSENSITIVE); + + private static Map, Converter> map = new HashMap, Converter>( + 19); + + private static void init() { + map.put(BigDecimal.class, BIGDECIMAL_CONVERTER); + map.put(BigInteger.class, BIGINTEGER_CONVERTER); + map.put(Byte.class, BYTE_CONVERTER); + map.put(byte[].class, BINARY_CONVERTER); + map.put(Boolean.class, BOOLEAN_CONVERTER); + map.put(Calendar.class, CALENDAR_CONVERTER); + map.put(java.util.Date.class, DATE_CONVERTER); + map.put(Double.class, DOUBLE_CONVERTER); + map.put(Float.class, FLOAT_CONVERTER); + map.put(Integer.class, INTEGER_CONVERTER); + map.put(Long.class, LONG_CONVERTER); + map.put(Short.class, SHORT_CONVERTER); + map.put(java.sql.Date.class, SQLDATE_CONVERTER); + map.put(String.class, STRING_CONVERTER); + map.put(Time.class, TIME_CONVERTER); + map.put(Timestamp.class, TIMESTAMP_CONVERTER); + map.put(URL.class, URL_CONVERTER); + map.put(InputStream.class, INPUTSTREAM_CONVERTER); + map.put(Reader.class, READER_CONVERTER); + } + + public static T convert(Object target, Class convertClass) { + return convert(target, convertClass, null); + } + + @SuppressWarnings("unchecked") + public static T convert(Object target, Class convertClass, + String pattern) { + Converter converter = (Converter) map.get(convertClass); + if (converter == null) { + if (convertClass.isInstance(target)) { + return (T) target; + } + return null; + } + return converter.convert(target, pattern); + } + + public static interface Converter { + + /** + * convert to T from o. if conversion is failed, return null. + * + * @param o + * @return + * */ + T convert(Object o); + + /** + * convert to T from o. if conversion is failed, return null. + * + * @param o + * @param pattern + * some of type needly conversion format. + * @return converted object, or null. + */ + T convert(Object o, String pattern); + + } + + public static final Converter BIGDECIMAL_CONVERTER = new Converter() { + + @Override + public BigDecimal convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (BigDecimal.class.isInstance(o)) { + return (BigDecimal) o; + } else if (o instanceof java.util.Date) { + return new BigDecimal(((java.util.Date) o).getTime()); + } else if (Integer.class.isInstance(o)) { + int i = Integer.class.cast(o).intValue(); + return new BigDecimal(i); + } else if (Double.class.isInstance(o)) { + double d = Double.class.cast(o).doubleValue(); + return new BigDecimal(d); + } else if (Long.class.isInstance(o)) { + long l = Long.class.cast(o).longValue(); + return new BigDecimal(l); + } else if (Float.class.isInstance(o)) { + float f = Float.class.cast(o).floatValue(); + return new BigDecimal(f); + } else if (Byte.class.isInstance(o)) { + byte b = Byte.class.cast(o).byteValue(); + return new BigDecimal(b); + } else if (BigInteger.class.isInstance(o)) { + BigInteger bi = BigInteger.class.cast(o); + return new BigDecimal(bi); + } else if (String.class.isInstance(o)) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return new BigDecimal(s); + } + return null; + } else { + return null; + } + + } + + @Override + public BigDecimal convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter BIGINTEGER_CONVERTER = new Converter() { + + @Override + public BigInteger convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof BigInteger) { + return (BigInteger) o; + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return new BigInteger(s); + } + } + return null; + } + + @Override + public BigInteger convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter BYTE_CONVERTER = new Converter() { + + @Override + public Byte convert(Object o) { + return convert(o, null); + } + + @Override + public Byte convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (Byte) o; + } else if (o instanceof Number) { + return new Byte(((Number) o).byteValue()); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Byte.valueOf(s); + } + return null; + } + return null; + } + }; + + public static final Converter BINARY_CONVERTER = new Converter() { + + @Override + public byte[] convert(Object o) { + if (o == null) { + return null; + } else if (o instanceof byte[]) { + return (byte[]) o; + } else if (o instanceof String) { + return Base64Util.decode(String.class.cast(o)); + } + return null; + } + + @Override + public byte[] convert(Object o, String pattern) { + return convert(o); + } + + }; + + public static final Converter BOOLEAN_CONVERTER = new Converter() { + + @Override + public Boolean convert(Object o) { + if (o == null) { + return null; + } else if (Boolean.class.isInstance(o)) { + return Boolean.class.cast(o); + } else if (String.class.isInstance(o)) { + String s = String.class.cast(o); + return Boolean.valueOf(YES_PATTERN.matcher(s).matches()); + } else if (Number.class.isInstance(o)) { + Number n = Number.class.cast(o); + return Boolean.valueOf(n.intValue() != 0); + } + return null; + } + + @Override + public Boolean convert(Object o, String pattern) { + return convert(o); + } + + }; + + public static final Converter CALENDAR_CONVERTER = new Converter() { + + @Override + public Calendar convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Calendar) { + return (Calendar) o; + } + java.util.Date date = DATE_CONVERTER.convert(o, pattern); + if (date != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal; + } + return null; + } + + @Override + public Calendar convert(Object o) { + return convert(o, null); + } + + }; + + public abstract static class DateConverter implements + Converter { + + public java.util.Date toDate(String s, String pattern) { + return toDate(s, pattern, Locale.getDefault()); + } + + public java.util.Date toDate(String s, String pattern, Locale locale) { + SimpleDateFormat sdf = getDateFormat(s, pattern, locale); + try { + return sdf.parse(s); + } catch (ParseException ex) { + LOG.log(Level.CONFIG, ex.getMessage(), ex); + return null; + } + } + + public SimpleDateFormat getDateFormat(String s, String pattern, + Locale locale) { + if (pattern != null) { + return createSimpleDateFormat(pattern); + } + return getDateFormat(s, locale); + } + + public SimpleDateFormat getDateFormat(String s, Locale locale) { + String pattern = getPattern(locale); + String shortPattern = removeDelimiter(pattern); + String delimitor = findDelimiter(s); + if (delimitor == null) { + if (s.length() == shortPattern.length()) { + return createSimpleDateFormat(shortPattern); + } + if (s.length() == shortPattern.length() + 2) { + return createSimpleDateFormat(StringUtil.replace( + shortPattern, "yy", "yyyy")); + } + } else { + String[] array = s.split(delimitor); + for (int i = 0; i < array.length; ++i) { + if (array[i].length() == 4) { + pattern = StringUtil.replace(pattern, "yy", "yyyy"); + break; + } + } + return createSimpleDateFormat(pattern); + } + return createSimpleDateFormat(); + } + + public SimpleDateFormat getDateFormat(Locale locale) { + return createSimpleDateFormat(getPattern(locale)); + } + + public SimpleDateFormat getY4DateFormat(Locale locale) { + return createSimpleDateFormat(getY4Pattern(locale)); + } + + public String getY4Pattern(Locale locale) { + String pattern = getPattern(locale); + if (pattern.indexOf("yyyy") < 0) { + pattern = StringUtil.replace(pattern, "yy", "yyyy"); + } + return pattern; + } + + public String getPattern(Locale locale) { + SimpleDateFormat df = (SimpleDateFormat) DateFormat + .getDateInstance(DateFormat.SHORT, locale); + String pattern = df.toPattern(); + int index = pattern.indexOf(' '); + if (index > 0) { + pattern = pattern.substring(0, index); + } + if (pattern.indexOf("MM") < 0) { + pattern = StringUtil.replace(pattern, "M", "MM"); + } + if (pattern.indexOf("dd") < 0) { + pattern = StringUtil.replace(pattern, "d", "dd"); + } + return pattern; + } + + public String findDelimiter(String value) { + for (int i = 0; i < value.length(); ++i) { + char c = value.charAt(i); + if (Character.isDigit(c)) { + continue; + } + return Character.toString(c); + } + return null; + } + + public String removeDelimiter(String pattern) { + StringBuilder builder = new StringBuilder(pattern.length()); + for (int i = 0; i < pattern.length(); ++i) { + char c = pattern.charAt(i); + if (c == 'y' || c == 'M' || c == 'd') { + builder.append(c); + } + } + return builder.toString(); + } + + protected SimpleDateFormat createSimpleDateFormat(String pattern) { + return new SimpleDateFormat(pattern); + } + + protected SimpleDateFormat createSimpleDateFormat() { + return new SimpleDateFormat(); + } + + } + + public static final DateConverter DATE_CONVERTER = new DateConverter() { + + @Override + public java.util.Date convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof String) { + return toDate((String) o, pattern); + } else if (o instanceof java.util.Date) { + return (java.util.Date) o; + } else if (o instanceof Calendar) { + return ((Calendar) o).getTime(); + } else if (o instanceof Number) { + return new java.util.Date(((Number) o).longValue()); + } + return null; + } + + @Override + public java.util.Date convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter DOUBLE_CONVERTER = new Converter() { + + @Override + public Double convert(Object o) { + return convert(o, null); + } + + @Override + public Double convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Double) { + return (Double) o; + } else if (o instanceof Number) { + return ((Number) o).doubleValue(); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Double.valueOf(s); + } + return null; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue() ? 1.0 : 0.0; + } + return null; + } + + }; + + public static final Converter FLOAT_CONVERTER = new Converter() { + + @Override + public Float convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (Float) o; + } else if (o instanceof Number) { + return new Float(((Number) o).floatValue()); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Float.valueOf(s); + } + return null; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue() ? 1.0f : 0.0f; + } + return null; + } + + @Override + public Float convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter INTEGER_CONVERTER = new Converter() { + + @Override + public Integer convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof Number) { + return ((Number) o).intValue(); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Integer.valueOf(s); + } + return null; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue() ? 1 : 0; + } + return null; + } + + @Override + public Integer convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter LONG_CONVERTER = new Converter() { + + @Override + public Long convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Long) { + return (Long) o; + } else if (o instanceof Number) { + return ((Number) o).longValue(); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Long.valueOf(s); + } + return null; + } else if (o instanceof java.util.Date) { + return ((java.util.Date) o).getTime(); + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue() ? 1L : 0L; + } + return null; + } + + @Override + public Long convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter SHORT_CONVERTER = new Converter() { + + @Override + public Short convert(Object o, String pattern) { + if (o == null) { + return null; + } else if (o instanceof Short) { + return (Short) o; + } else if (o instanceof Number) { + return new Short(((Number) o).shortValue()); + } else if (o instanceof String) { + String s = DecimalFormatUtil.normalize(String.class.cast(o)); + if (StringUtil.isEmpty(s) == false) { + return Short.valueOf(s); + } + return null; + } else if (o instanceof Boolean) { + return ((Boolean) o).booleanValue() ? (short) 1 : (short) 0; + } + return null; + } + + @Override + public Short convert(Object o) { + return convert(o, null); + } + + }; + + public static final Converter SQLDATE_CONVERTER = new Converter() { + + @Override + public java.sql.Date convert(Object o, String pattern) { + if (o instanceof java.sql.Date) { + return (java.sql.Date) o; + } + java.util.Date date = DATE_CONVERTER.convert(o, pattern); + if (date != null) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.set(Calendar.HOUR_OF_DAY, 0); + c.set(Calendar.HOUR, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return new java.sql.Date(c.getTimeInMillis()); + } + return null; + } + + @Override + public java.sql.Date convert(Object o) { + return convert(o, null); + } + }; + + public static final Converter STRING_CONVERTER = new Converter() { + + @Override + public String convert(Object value, String pattern) { + if (value == null) { + return null; + } else if (value instanceof String) { + return (String) value; + } else if (value instanceof java.util.Date) { + return toString((java.util.Date) value, pattern); + } else if (value instanceof Number) { + return toString((Number) value, pattern); + } else if (value instanceof byte[]) { + return Base64Util.encode((byte[]) value); + } else if (value instanceof URL) { + return ((URL) value).toExternalForm(); + } else { + return value.toString(); + } + } + + protected String toString(Number value, String pattern) { + if (value != null) { + if (pattern != null) { + return new DecimalFormat(pattern).format(value); + } + return value.toString(); + } + return null; + } + + protected String toString(java.util.Date value, String pattern) { + if (pattern != null) { + return new SimpleDateFormat(pattern).format(value); + } + return value.toString(); + } + + @Override + public String convert(Object o) { + return convert(o, null); + } + }; + + public static final Converter