--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/ResourceRecord.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsCommunicationException;
+import jdk.dns.client.ex.DnsInvalidNameException;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+
+/**
+ * The ResourceRecord class represents a DNS resource record.
+ * The string format is based on the master file representation in
+ * RFC 1035.
+ *
+ * @author Scott Seligman
+ */
+
+public class ResourceRecord {
+ /*
+ * Resource record type codes
+ */
+ public static final int TYPE_A = 1;
+ public static final int TYPE_CNAME = 5;
+ public static final int TYPE_AAAA = 28;
+ public static final int TYPE_PTR = 12;
+ static final int QTYPE_STAR = 255; // query type "*"
+
+ private static final int TYPE_NS = 2;
+ private static final int TYPE_SOA = 6;
+ private static final int TYPE_HINFO = 13;
+ private static final int TYPE_MX = 15;
+ private static final int TYPE_TXT = 16;
+ private static final int TYPE_SRV = 33;
+ private static final int TYPE_NAPTR = 35;
+ //static final int QTYPE_AXFR = 252; // zone transfer //Do not want to support that
+
+ /*
+ * Mapping from resource record type codes to type name strings.
+ */
+ private static final String[] rrTypeNames = {
+ null, "A", "NS", null, null,
+ "CNAME", "SOA", null, null, null,
+ null, null, "PTR", "HINFO", null,
+ "MX", "TXT", null, null, null,
+ null, null, null, null, null,
+ null, null, null, "AAAA", null,
+ null, null, null, "SRV", null,
+ "NAPTR"
+ };
+
+ /*
+ * Resource record class codes
+ */
+ public static final int CLASS_INTERNET = 1;
+ static final int QCLASS_STAR = 255; // query class "*"
+
+ /*
+ * Mapping from resource record type codes to class name strings.
+ */
+ private static final String[] rrClassNames = {
+ null, "IN"
+ };
+
+ /*
+ * Maximum number of compression references in labels.
+ * Used to detect compression loops.
+ */
+ private static final int MAXIMUM_COMPRESSION_REFERENCES = 16;
+
+ byte[] msg; // DNS message
+ int msgLen; // msg size (in octets)
+ boolean qSection; // true if this RR is part of question section
+ // and therefore has no ttl or rdata
+ int offset; // offset of RR w/in msg
+ int rrlen; // number of octets in encoded RR
+ DnsName name; // name field of RR, including root label
+ int rrtype; // type field of RR
+ String rrtypeName; // name of rrtype
+ int rrclass; // class field of RR
+ String rrclassName; // name of rrclass
+ int ttl = 0; // ttl field of RR
+ int rdlen = 0; // number of octets of rdata
+ Object rdata = null; // rdata -- most are String, unknown are byte[]
+
+
+ /*
+ * Constructs a new ResourceRecord. The encoded data of the DNS
+ * message is contained in msg; data for this RR begins at msg[offset].
+ * If qSection is true this RR is part of a question section. It's
+ * not a true resource record in that case, but is treated as if it
+ * were a shortened one (with no ttl or rdata). If decodeRdata is
+ * false, the rdata is not decoded (and getRdata() will return null)
+ * unless this is an SOA record.
+ *
+ * @throws CommunicationException if a decoded domain name isn't valid.
+ * @throws ArrayIndexOutOfBoundsException given certain other corrupt data.
+ */
+ ResourceRecord(byte[] msg, int msgLen, int offset,
+ boolean qSection, boolean decodeRdata)
+ throws DnsCommunicationException {
+
+ this.msg = msg;
+ this.msgLen = msgLen;
+ this.offset = offset;
+ this.qSection = qSection;
+ decode(decodeRdata);
+ }
+
+ public String toString() {
+ String text = name + " " + rrclassName + " " + rrtypeName;
+ if (!qSection) {
+ text += " " + ttl + " " +
+ ((rdata != null) ? rdata : "[n/a]");
+ }
+ return text;
+ }
+
+ /*
+ * Returns the name field of this RR, including the root label.
+ */
+ public DnsName getName() {
+ return name;
+ }
+
+ /*
+ * Returns the number of octets in the encoded RR.
+ */
+ public int size() {
+ return rrlen;
+ }
+
+ public int getType() {
+ return rrtype;
+ }
+
+ public int getRrclass() {
+ return rrclass;
+ }
+
+ public Object getRdata() {
+ return rdata;
+ }
+
+
+ public static String getTypeName(int rrtype) {
+ return valueToName(rrtype, rrTypeNames);
+ }
+
+ public static int getType(String typeName) {
+ return nameToValue(typeName, rrTypeNames);
+ }
+
+ public static int getRrclass(String className) {
+ return nameToValue(className, rrClassNames);
+ }
+
+ private static String valueToName(int val, String[] names) {
+ String name = null;
+ if ((val > 0) && (val < names.length)) {
+ name = names[val];
+ } else if (val == QTYPE_STAR) { // QTYPE_STAR == QCLASS_STAR
+ name = "*";
+ }
+ if (name == null) {
+ name = Integer.toString(val);
+ }
+ return name;
+ }
+
+ private static int nameToValue(String name, String[] names) {
+ if (name.isEmpty()) {
+ return -1; // invalid name
+ } else if (name.equals("*")) {
+ return QTYPE_STAR; // QTYPE_STAR == QCLASS_STAR
+ }
+ if (Character.isDigit(name.charAt(0))) {
+ try {
+ return Integer.parseInt(name);
+ } catch (NumberFormatException e) {
+ }
+ }
+ for (int i = 1; i < names.length; i++) {
+ if ((names[i] != null) &&
+ name.equalsIgnoreCase(names[i])) {
+ return i;
+ }
+ }
+ return -1; // unknown name
+ }
+
+ /*
+ * Decodes the binary format of the RR.
+ * May throw ArrayIndexOutOfBoundsException given corrupt data.
+ */
+ private void decode(boolean decodeRdata) throws DnsCommunicationException {
+ int pos = offset; // index of next unread octet
+
+ name = new DnsName(); // NAME
+ pos = decodeName(pos, name);
+
+ rrtype = getUShort(pos); // TYPE
+ rrtypeName = (rrtype < rrTypeNames.length)
+ ? rrTypeNames[rrtype]
+ : null;
+ if (rrtypeName == null) {
+ rrtypeName = Integer.toString(rrtype);
+ }
+ pos += 2;
+
+ rrclass = getUShort(pos); // CLASS
+ rrclassName = (rrclass < rrClassNames.length)
+ ? rrClassNames[rrclass]
+ : null;
+ if (rrclassName == null) {
+ rrclassName = Integer.toString(rrclass);
+ }
+ pos += 2;
+
+ if (!qSection) {
+ ttl = getInt(pos); // TTL
+ pos += 4;
+
+ rdlen = getUShort(pos); // RDLENGTH
+ pos += 2;
+
+ rdata = (decodeRdata || // RDATA
+ (rrtype == TYPE_SOA))
+ ? decodeRdata(pos)
+ : null;
+ if (rdata instanceof DnsName) {
+ rdata = rdata.toString();
+ }
+ pos += rdlen;
+ }
+
+ rrlen = pos - offset;
+
+ msg = null; // free up for GC
+ }
+
+ /*
+ * Returns the 1-byte unsigned value at msg[pos].
+ */
+ private int getUByte(int pos) {
+ return (msg[pos] & 0xFF);
+ }
+
+ /*
+ * Returns the 2-byte unsigned value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private int getUShort(int pos) {
+ return (((msg[pos] & 0xFF) << 8) |
+ (msg[pos + 1] & 0xFF));
+ }
+
+ /*
+ * Returns the 4-byte signed value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private int getInt(int pos) {
+ return ((getUShort(pos) << 16) | getUShort(pos + 2));
+ }
+
+ /*
+ * Returns the 4-byte unsigned value at msg[pos]. The high
+ * order byte comes first.
+ */
+ private long getUInt(int pos) {
+ return (getInt(pos) & 0xffffffffL);
+ }
+
+ /*
+ * Returns the name encoded at msg[pos], including the root label.
+ */
+ private DnsName decodeName(int pos) throws DnsCommunicationException {
+ DnsName n = new DnsName();
+ decodeName(pos, n);
+ return n;
+ }
+
+ /*
+ * Prepends to "n" the domain name encoded at msg[pos], including the root
+ * label. Returns the index into "msg" following the name.
+ */
+ private int decodeName(int pos, DnsName n) throws DnsCommunicationException {
+ int endPos = -1;
+ int level = 0;
+ try {
+ while (true) {
+ if (level > MAXIMUM_COMPRESSION_REFERENCES)
+ throw new IOException("Too many compression references");
+ int typeAndLen = msg[pos] & 0xFF;
+ if (typeAndLen == 0) { // end of name
+ ++pos;
+ n.add(0, "");
+ break;
+ } else if (typeAndLen <= 63) { // regular label
+ ++pos;
+ n.add(0, new String(msg, pos, typeAndLen,
+ StandardCharsets.ISO_8859_1));
+ pos += typeAndLen;
+ } else if ((typeAndLen & 0xC0) == 0xC0) { // name compression
+ ++level;
+ // cater for the case where the name pointed to is itself
+ // compressed: we don't want endPos to be reset by the second
+ // compression level.
+ int ppos = pos;
+ if (endPos == -1) endPos = pos + 2;
+ pos = getUShort(pos) & 0x3FFF;
+ if (debug) {
+ dprint("decode: name compression at " + ppos
+ + " -> " + pos + " endPos=" + endPos);
+ assert endPos > 0;
+ assert pos < ppos;
+ assert pos >= Header.HEADER_SIZE;
+ }
+ } else
+ throw new IOException("Invalid label type: " + typeAndLen);
+ }
+ } catch (IOException | DnsInvalidNameException e) {
+ DnsCommunicationException ce = new DnsCommunicationException(
+ "DNS error: malformed packet");
+ ce.initCause(e);
+ throw ce;
+ }
+ if (endPos == -1)
+ endPos = pos;
+ return endPos;
+ }
+
+ /*
+ * Returns the rdata encoded at msg[pos]. The format is dependent
+ * on the rrtype and rrclass values, which have already been set.
+ * The length of the encoded data is rdlen, which has already been
+ * set.
+ * The rdata of records with unknown type/class combinations is
+ * returned in a newly-allocated byte array.
+ */
+ private Object decodeRdata(int pos) throws DnsCommunicationException {
+ if (rrclass == CLASS_INTERNET) {
+ switch (rrtype) {
+ case TYPE_A:
+ return decodeA(pos);
+ case TYPE_AAAA:
+ return decodeAAAA(pos);
+ case TYPE_CNAME:
+ case TYPE_NS:
+ case TYPE_PTR:
+ return decodeName(pos);
+ case TYPE_MX:
+ return decodeMx(pos);
+ case TYPE_SOA:
+ return decodeSoa(pos);
+ case TYPE_SRV:
+ return decodeSrv(pos);
+ case TYPE_NAPTR:
+ return decodeNaptr(pos);
+ case TYPE_TXT:
+ return decodeTxt(pos);
+ case TYPE_HINFO:
+ return decodeHinfo(pos);
+ }
+ }
+ // Unknown RR type/class
+ if (debug) {
+ dprint("Unknown RR type for RR data: " + rrtype + " rdlen=" + rdlen
+ + ", pos=" + pos + ", msglen=" + msg.length + ", remaining="
+ + (msg.length - pos));
+ }
+ byte[] rd = new byte[rdlen];
+ System.arraycopy(msg, pos, rd, 0, rdlen);
+ return rd;
+ }
+
+ /*
+ * Returns the rdata of an MX record that is encoded at msg[pos].
+ */
+ private String decodeMx(int pos) throws DnsCommunicationException {
+ int preference = getUShort(pos);
+ pos += 2;
+ DnsName name = decodeName(pos);
+ return (preference + " " + name);
+ }
+
+ /*
+ * Returns the rdata of an SOA record that is encoded at msg[pos].
+ */
+ private String decodeSoa(int pos) throws DnsCommunicationException {
+ DnsName mname = new DnsName();
+ pos = decodeName(pos, mname);
+ DnsName rname = new DnsName();
+ pos = decodeName(pos, rname);
+
+ long serial = getUInt(pos);
+ pos += 4;
+ long refresh = getUInt(pos);
+ pos += 4;
+ long retry = getUInt(pos);
+ pos += 4;
+ long expire = getUInt(pos);
+ pos += 4;
+ long minimum = getUInt(pos); // now used as negative TTL
+ pos += 4;
+
+ return (mname + " " + rname + " " + serial + " " +
+ refresh + " " + retry + " " + expire + " " + minimum);
+ }
+
+ /*
+ * Returns the rdata of an SRV record that is encoded at msg[pos].
+ * See RFC 2782.
+ */
+ private String decodeSrv(int pos) throws DnsCommunicationException {
+ int priority = getUShort(pos);
+ pos += 2;
+ int weight = getUShort(pos);
+ pos += 2;
+ int port = getUShort(pos);
+ pos += 2;
+ DnsName target = decodeName(pos);
+ return (priority + " " + weight + " " + port + " " + target);
+ }
+
+ /*
+ * Returns the rdata of an NAPTR record that is encoded at msg[pos].
+ * See RFC 2915.
+ */
+ private String decodeNaptr(int pos) throws DnsCommunicationException {
+ int order = getUShort(pos);
+ pos += 2;
+ int preference = getUShort(pos);
+ pos += 2;
+ StringBuffer flags = new StringBuffer();
+ pos += decodeCharString(pos, flags);
+ StringBuffer services = new StringBuffer();
+ pos += decodeCharString(pos, services);
+ StringBuffer regexp = new StringBuffer(rdlen);
+ pos += decodeCharString(pos, regexp);
+ DnsName replacement = decodeName(pos);
+
+ return (order + " " + preference + " " + flags + " " +
+ services + " " + regexp + " " + replacement);
+ }
+
+ /*
+ * Returns the rdata of a TXT record that is encoded at msg[pos].
+ * The rdata consists of one or more <character-string>s.
+ */
+ private String decodeTxt(int pos) {
+ StringBuffer buf = new StringBuffer(rdlen);
+ int end = pos + rdlen;
+ while (pos < end) {
+ pos += decodeCharString(pos, buf);
+ if (pos < end) {
+ buf.append(' ');
+ }
+ }
+ return buf.toString();
+ }
+
+ /*
+ * Returns the rdata of an HINFO record that is encoded at msg[pos].
+ * The rdata consists of two <character-string>s.
+ */
+ private String decodeHinfo(int pos) {
+ StringBuffer buf = new StringBuffer(rdlen);
+ pos += decodeCharString(pos, buf);
+ buf.append(' ');
+ pos += decodeCharString(pos, buf);
+ return buf.toString();
+ }
+
+ /*
+ * Decodes the <character-string> at msg[pos] and adds it to buf.
+ * If the string contains one of the meta-characters ' ', '\\', or
+ * '"', then the result is quoted and any embedded '\\' or '"'
+ * chars are escaped with '\\'. Empty strings are also quoted.
+ * Returns the size of the encoded string, including the initial
+ * length octet.
+ */
+ private int decodeCharString(int pos, StringBuffer buf) {
+ int start = buf.length(); // starting index of this string
+ int len = getUByte(pos++); // encoded string length
+ boolean quoted = (len == 0); // quote string if empty
+ for (int i = 0; i < len; i++) {
+ int c = getUByte(pos++);
+ quoted |= (c == ' ');
+ if ((c == '\\') || (c == '"')) {
+ quoted = true;
+ buf.append('\\');
+ }
+ buf.append((char) c);
+ }
+ if (quoted) {
+ buf.insert(start, '"');
+ buf.append('"');
+ }
+ return (len + 1); // size includes initial octet
+ }
+
+ /*
+ * Returns the rdata of an A record, in dotted-decimal format,
+ * that is encoded at msg[pos].
+ */
+ private String decodeA(int pos) {
+ return ((msg[pos] & 0xff) + "." +
+ (msg[pos + 1] & 0xff) + "." +
+ (msg[pos + 2] & 0xff) + "." +
+ (msg[pos + 3] & 0xff));
+ }
+
+ /*
+ * Returns the rdata of an AAAA record, in colon-separated format,
+ * that is encoded at msg[pos]. For example: 4321:0:1:2:3:4:567:89ab.
+ * See RFCs 1886 and 2373.
+ */
+ private String decodeAAAA(int pos) {
+ int[] addr6 = new int[8]; // the unsigned 16-bit words of the address
+ for (int i = 0; i < 8; i++) {
+ addr6[i] = getUShort(pos);
+ pos += 2;
+ }
+
+ // Find longest sequence of two or more zeros, to compress them.
+ int curBase = -1;
+ int curLen = 0;
+ int bestBase = -1;
+ int bestLen = 0;
+ for (int i = 0; i < 8; i++) {
+ if (addr6[i] == 0) {
+ if (curBase == -1) { // new sequence
+ curBase = i;
+ curLen = 1;
+ } else { // extend sequence
+ ++curLen;
+ if ((curLen >= 2) && (curLen > bestLen)) {
+ bestBase = curBase;
+ bestLen = curLen;
+ }
+ }
+ } else { // not in sequence
+ curBase = -1;
+ }
+ }
+
+ // If addr begins with at least 6 zeros and is not :: or ::1,
+ // or with 5 zeros followed by 0xffff, use the text format for
+ // IPv4-compatible or IPv4-mapped addresses.
+ if (bestBase == 0) {
+ if ((bestLen == 6) ||
+ ((bestLen == 7) && (addr6[7] > 1))) {
+ return ("::" + decodeA(pos - 4));
+ } else if ((bestLen == 5) && (addr6[5] == 0xffff)) {
+ return ("::ffff:" + decodeA(pos - 4));
+ }
+ }
+
+ // If bestBase != -1, compress zeros in [bestBase, bestBase+bestLen)
+ boolean compress = (bestBase != -1);
+
+ StringBuilder sb = new StringBuilder(40);
+ if (bestBase == 0) {
+ sb.append(':');
+ }
+ for (int i = 0; i < 8; i++) {
+ if (!compress || (i < bestBase) || (i >= bestBase + bestLen)) {
+ sb.append(Integer.toHexString(addr6[i]));
+ if (i < 7) {
+ sb.append(':');
+ }
+ } else if (compress && (i == bestBase)) { // first compressed zero
+ sb.append(':');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ //-------------------------------------------------------------------------
+
+ private static final boolean debug = false;
+
+ private static void dprint(String mess) {
+ if (debug) {
+ System.err.println("DNS: " + mess);
+ }
+ }
+
+}