diff options
Diffstat (limited to 'src/org/xbill/DNS/Record.java')
-rw-r--r-- | src/org/xbill/DNS/Record.java | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Record.java b/src/org/xbill/DNS/Record.java new file mode 100644 index 0000000..8da7015 --- /dev/null +++ b/src/org/xbill/DNS/Record.java @@ -0,0 +1,736 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.text.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * A generic DNS resource record. The specific record types extend this class. + * A record contains a name, type, class, ttl, and rdata. + * + * @author Brian Wellington + */ + +public abstract class Record implements Cloneable, Comparable, Serializable { + +private static final long serialVersionUID = 2694906050116005466L; + +protected Name name; +protected int type, dclass; +protected long ttl; + +private static final DecimalFormat byteFormat = new DecimalFormat(); + +static { + byteFormat.setMinimumIntegerDigits(3); +} + +protected +Record() {} + +Record(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + this.name = name; + this.type = type; + this.dclass = dclass; + this.ttl = ttl; +} + +/** + * Creates an empty record of the correct type; must be overriden + */ +abstract Record +getObject(); + +private static final Record +getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { + Record proto, rec; + + if (hasData) { + proto = Type.getProto(type); + if (proto != null) + rec = proto.getObject(); + else + rec = new UNKRecord(); + } else + rec = new EmptyRecord(); + rec.name = name; + rec.type = type; + rec.dclass = dclass; + rec.ttl = ttl; + return rec; +} + +/** + * Converts the type-specific RR to wire format - must be overriden + */ +abstract void +rrFromWire(DNSInput in) throws IOException; + +private static Record +newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in) +throws IOException +{ + Record rec; + rec = getEmptyRecord(name, type, dclass, ttl, in != null); + if (in != null) { + if (in.remaining() < length) + throw new WireParseException("truncated record"); + in.setActive(length); + + rec.rrFromWire(in); + + if (in.remaining() > 0) + throw new WireParseException("invalid record length"); + in.clearActive(); + } + return rec; +} + +/** + * Creates a new record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param length The length of the record's data. + * @param data The rdata of the record, in uncompressed DNS wire format. Only + * the first length bytes are used. + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + DNSInput in; + if (data != null) + in = new DNSInput(data); + else + in = null; + try { + return newRecord(name, type, dclass, ttl, length, in); + } + catch (IOException e) { + return null; + } +} + +/** + * Creates a new record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param data The complete rdata of the record, in uncompressed DNS wire + * format. + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl, byte [] data) { + return newRecord(name, type, dclass, ttl, data.length, data); +} + +/** + * Creates a new empty record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @return An object of a subclass of Record + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + return getEmptyRecord(name, type, dclass, ttl, false); +} + +/** + * Creates a new empty record, with the given parameters. This method is + * designed to create records that will be added to the QUERY section + * of a message. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @return An object of a subclass of Record + */ +public static Record +newRecord(Name name, int type, int dclass) { + return newRecord(name, type, dclass, 0); +} + +static Record +fromWire(DNSInput in, int section, boolean isUpdate) throws IOException { + int type, dclass; + long ttl; + int length; + Name name; + Record rec; + + name = new Name(in); + type = in.readU16(); + dclass = in.readU16(); + + if (section == Section.QUESTION) + return newRecord(name, type, dclass); + + ttl = in.readU32(); + length = in.readU16(); + if (length == 0 && isUpdate && + (section == Section.PREREQ || section == Section.UPDATE)) + return newRecord(name, type, dclass, ttl); + rec = newRecord(name, type, dclass, ttl, length, in); + return rec; +} + +static Record +fromWire(DNSInput in, int section) throws IOException { + return fromWire(in, section, false); +} + +/** + * Builds a Record from DNS uncompressed wire format. + */ +public static Record +fromWire(byte [] b, int section) throws IOException { + return fromWire(new DNSInput(b), section, false); +} + +void +toWire(DNSOutput out, int section, Compression c) { + name.toWire(out, c); + out.writeU16(type); + out.writeU16(dclass); + if (section == Section.QUESTION) + return; + out.writeU32(ttl); + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, c, false); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); +} + +/** + * Converts a Record into DNS uncompressed wire format. + */ +public byte [] +toWire(int section) { + DNSOutput out = new DNSOutput(); + toWire(out, section, null); + return out.toByteArray(); +} + +private void +toWireCanonical(DNSOutput out, boolean noTTL) { + name.toWireCanonical(out); + out.writeU16(type); + out.writeU16(dclass); + if (noTTL) { + out.writeU32(0); + } else { + out.writeU32(ttl); + } + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, null, true); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); +} + +/* + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase), optionally ignoring the TTL. + */ +private byte [] +toWireCanonical(boolean noTTL) { + DNSOutput out = new DNSOutput(); + toWireCanonical(out, noTTL); + return out.toByteArray(); +} + +/** + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase). + */ +public byte [] +toWireCanonical() { + return toWireCanonical(false); +} + +/** + * Converts the rdata in a Record into canonical DNS uncompressed wire format + * (all names are converted to lowercase). + */ +public byte [] +rdataToWireCanonical() { + DNSOutput out = new DNSOutput(); + rrToWire(out, null, true); + return out.toByteArray(); +} + +/** + * Converts the type-specific RR to text format - must be overriden + */ +abstract String rrToString(); + +/** + * Converts the rdata portion of a Record into a String representation + */ +public String +rdataToString() { + return rrToString(); +} + +/** + * Converts a Record into a String representation + */ +public String +toString() { + StringBuffer sb = new StringBuffer(); + sb.append(name); + if (sb.length() < 8) + sb.append("\t"); + if (sb.length() < 16) + sb.append("\t"); + sb.append("\t"); + if (Options.check("BINDTTL")) + sb.append(TTL.format(ttl)); + else + sb.append(ttl); + sb.append("\t"); + if (dclass != DClass.IN || !Options.check("noPrintIN")) { + sb.append(DClass.string(dclass)); + sb.append("\t"); + } + sb.append(Type.string(type)); + String rdata = rrToString(); + if (!rdata.equals("")) { + sb.append("\t"); + sb.append(rdata); + } + return sb.toString(); +} + +/** + * Converts the text format of an RR to the internal format - must be overriden + */ +abstract void +rdataFromString(Tokenizer st, Name origin) throws IOException; + +/** + * Converts a String into a byte array. + */ +protected static byte [] +byteArrayFromString(String s) throws TextParseException { + byte [] array = s.getBytes(); + boolean escaped = false; + boolean hasEscapes = false; + + for (int i = 0; i < array.length; i++) { + if (array[i] == '\\') { + hasEscapes = true; + break; + } + } + if (!hasEscapes) { + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + return array; + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int digits = 0; + int intval = 0; + for (int i = 0; i < array.length; i++) { + byte b = array[i]; + if (escaped) { + if (b >= '0' && b <= '9' && digits < 3) { + digits++; + intval *= 10; + intval += (b - '0'); + if (intval > 255) + throw new TextParseException + ("bad escape"); + if (digits < 3) + continue; + b = (byte) intval; + } + else if (digits > 0 && digits < 3) + throw new TextParseException("bad escape"); + os.write(b); + escaped = false; + } + else if (array[i] == '\\') { + escaped = true; + digits = 0; + intval = 0; + } + else + os.write(array[i]); + } + if (digits > 0 && digits < 3) + throw new TextParseException("bad escape"); + array = os.toByteArray(); + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + + return os.toByteArray(); +} + +/** + * Converts a byte array into a String. + */ +protected static String +byteArrayToString(byte [] array, boolean quote) { + StringBuffer sb = new StringBuffer(); + if (quote) + sb.append('"'); + for (int i = 0; i < array.length; i++) { + int b = array[i] & 0xFF; + if (b < 0x20 || b >= 0x7f) { + sb.append('\\'); + sb.append(byteFormat.format(b)); + } else if (b == '"' || b == '\\') { + sb.append('\\'); + sb.append((char)b); + } else + sb.append((char)b); + } + if (quote) + sb.append('"'); + return sb.toString(); +} + +/** + * Converts a byte array into the unknown RR format. + */ +protected static String +unknownToString(byte [] data) { + StringBuffer sb = new StringBuffer(); + sb.append("\\# "); + sb.append(data.length); + sb.append(" "); + sb.append(base16.toString(data)); + return sb.toString(); +} + +/** + * Builds a new Record from its textual representation + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param st A tokenizer containing the textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * @return The new record + * @throws IOException The text format was invalid. + */ +public static Record +fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) +throws IOException +{ + Record rec; + + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + Tokenizer.Token t = st.get(); + if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { + int length = st.getUInt16(); + byte [] data = st.getHex(); + if (data == null) { + data = new byte[0]; + } + if (length != data.length) + throw st.exception("invalid unknown RR encoding: " + + "length mismatch"); + DNSInput in = new DNSInput(data); + return newRecord(name, type, dclass, ttl, length, in); + } + st.unget(); + rec = getEmptyRecord(name, type, dclass, ttl, true); + rec.rdataFromString(st, origin); + t = st.get(); + if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { + throw st.exception("unexpected tokens at end of record"); + } + return rec; +} + +/** + * Builds a new Record from its textual representation + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param s The textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * @return The new record + * @throws IOException The text format was invalid. + */ +public static Record +fromString(Name name, int type, int dclass, long ttl, String s, Name origin) +throws IOException +{ + return fromString(name, type, dclass, ttl, new Tokenizer(s), origin); +} + +/** + * Returns the record's name + * @see Name + */ +public Name +getName() { + return name; +} + +/** + * Returns the record's type + * @see Type + */ +public int +getType() { + return type; +} + +/** + * Returns the type of RRset that this record would belong to. For all types + * except RRSIG, this is equivalent to getType(). + * @return The type of record, if not RRSIG. If the type is RRSIG, + * the type covered is returned. + * @see Type + * @see RRset + * @see SIGRecord + */ +public int +getRRsetType() { + if (type == Type.RRSIG) { + RRSIGRecord sig = (RRSIGRecord) this; + return sig.getTypeCovered(); + } + return type; +} + +/** + * Returns the record's class + */ +public int +getDClass() { + return dclass; +} + +/** + * Returns the record's TTL + */ +public long +getTTL() { + return ttl; +} + +/** + * Converts the type-specific RR to wire format - must be overriden + */ +abstract void +rrToWire(DNSOutput out, Compression c, boolean canonical); + +/** + * Determines if two Records could be part of the same RRset. + * This compares the name, type, and class of the Records; the ttl and + * rdata are not compared. + */ +public boolean +sameRRset(Record rec) { + return (getRRsetType() == rec.getRRsetType() && + dclass == rec.dclass && + name.equals(rec.name)); +} + +/** + * Determines if two Records are identical. This compares the name, type, + * class, and rdata (with names canonicalized). The TTLs are not compared. + * @param arg The record to compare to + * @return true if the records are equal, false otherwise. + */ +public boolean +equals(Object arg) { + if (arg == null || !(arg instanceof Record)) + return false; + Record r = (Record) arg; + if (type != r.type || dclass != r.dclass || !name.equals(r.name)) + return false; + byte [] array1 = rdataToWireCanonical(); + byte [] array2 = r.rdataToWireCanonical(); + return Arrays.equals(array1, array2); +} + +/** + * Generates a hash code based on the Record's data. + */ +public int +hashCode() { + byte [] array = toWireCanonical(true); + int code = 0; + for (int i = 0; i < array.length; i++) + code += ((code << 3) + (array[i] & 0xFF)); + return code; +} + +Record +cloneRecord() { + try { + return (Record) clone(); + } + catch (CloneNotSupportedException e) { + throw new IllegalStateException(); + } +} + +/** + * Creates a new record identical to the current record, but with a different + * name. This is most useful for replacing the name of a wildcard record. + */ +public Record +withName(Name name) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Record rec = cloneRecord(); + rec.name = name; + return rec; +} + +/** + * Creates a new record identical to the current record, but with a different + * class and ttl. This is most useful for dynamic update. + */ +Record +withDClass(int dclass, long ttl) { + Record rec = cloneRecord(); + rec.dclass = dclass; + rec.ttl = ttl; + return rec; +} + +/* Sets the TTL to the specified value. This is intentionally not public. */ +void +setTTL(long ttl) { + this.ttl = ttl; +} + +/** + * Compares this Record to another Object. + * @param o The Object to be compared. + * @return The value 0 if the argument is a record equivalent to this record; + * a value less than 0 if the argument is less than this record in the + * canonical ordering, and a value greater than 0 if the argument is greater + * than this record in the canonical ordering. The canonical ordering + * is defined to compare by name, class, type, and rdata. + * @throws ClassCastException if the argument is not a Record. + */ +public int +compareTo(Object o) { + Record arg = (Record) o; + + if (this == arg) + return (0); + + int n = name.compareTo(arg.name); + if (n != 0) + return (n); + n = dclass - arg.dclass; + if (n != 0) + return (n); + n = type - arg.type; + if (n != 0) + return (n); + byte [] rdata1 = rdataToWireCanonical(); + byte [] rdata2 = arg.rdataToWireCanonical(); + for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { + n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); + if (n != 0) + return (n); + } + return (rdata1.length - rdata2.length); +} + +/** + * Returns the name for which additional data processing should be done + * for this record. This can be used both for building responses and + * parsing responses. + * @return The name to used for additional data processing, or null if this + * record type does not require additional data processing. + */ +public Name +getAdditionalName() { + return null; +} + +/* Checks that an int contains an unsigned 8 bit value */ +static int +checkU8(String field, int val) { + if (val < 0 || val > 0xFF) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 8 " + + "bit value"); + return val; +} + +/* Checks that an int contains an unsigned 16 bit value */ +static int +checkU16(String field, int val) { + if (val < 0 || val > 0xFFFF) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 16 " + + "bit value"); + return val; +} + +/* Checks that a long contains an unsigned 32 bit value */ +static long +checkU32(String field, long val) { + if (val < 0 || val > 0xFFFFFFFFL) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 32 " + + "bit value"); + return val; +} + +/* Checks that a name is absolute */ +static Name +checkName(String field, Name name) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + return name; +} + +static byte [] +checkByteArrayLength(String field, byte [] array, int maxLength) { + if (array.length > 0xFFFF) + throw new IllegalArgumentException("\"" + field + "\" array " + + "must have no more than " + + maxLength + " elements"); + byte [] out = new byte[array.length]; + System.arraycopy(array, 0, out, 0, array.length); + return out; +} + +} |