org.xbill.DNS.Record Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ddns-dnsjava Show documentation
Show all versions of ddns-dnsjava Show documentation
DiscoveryDNS fork of DNSJava project
// Copyright (c) 1999-2004 Brian Wellington ([email protected])
package org.xbill.DNS;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.Arrays;
import org.xbill.DNS.utils.base16;
/**
* 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;
/**
* Flag to indicate if the record is authoritative. Needed for
* DiscoveryDNS-PowerDNS integration.
*/
private boolean authoratative;
public boolean isAuthoratative() {
return authoratative;
}
public void setAuthoratative(final boolean authoratative) {
this.authoratative = authoratative;
}
private String orderName;
public String getOrderName() {
return orderName;
}
public void setOrderName(final String orderName) {
this.orderName = orderName;
}
/**
* DiscoveryDNS record types
*/
public enum DDNSRecordType {
/**
* User-generated record type
*/
user,
/**
* System-generated record type
*/
ddns,
/**
* DNSSEC system-generated record type
*/
dnssec,
/**
* Empty Non Terminal record types
*/
ent
}
/**
* DiscoveryDNS record type
*/
private DDNSRecordType ddnsRecordType;
public DDNSRecordType getDDNSRecordType() {
return ddnsRecordType;
}
public void setDDNSRecordType(final DDNSRecordType ddnsRecordType) {
this.ddnsRecordType = ddnsRecordType;
}
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.
*
* @return the built record
*/
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.
*
* @return the wire bytes
*/
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.
*
* @param noTTL
* include the ttl or not
* @return the wire canonical bytes
*/
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).
*
* @return the wire canonical bytes
*/
public byte[] toWireCanonical() {
return toWireCanonical(false);
}
/**
* Converts the rdata in a Record into canonical DNS uncompressed wire
* format (all names are converted to lowercase).
*
* @return the wire canonical bytes
*/
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
*
* @return the rr string
*/
abstract String rrToString();
/**
* Converts the rdata portion of a Record into a String representation
*
* @return the rdata string
*/
public String rdataToString() {
return rrToString();
}
/**
* Converts a Record into a String representation
*
* @return the record string
*/
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
*
* @param st
* the tokenizer
* @param orign
* the origin
*/
abstract void rdataFromString(Tokenizer st, Name origin) throws IOException;
/**
* Converts a String into a byte array.
*
* @param s
* the string
* @return the 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.
*
* @param array
* the array
* @param quote
* include quotes
* @return the 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.
*
* @param data
* the data
* @return the string
*/
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
*
* @return the name
* @see Name
*/
public Name getName() {
return name;
}
/**
* Returns the record's type
*
* @return the 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;
}
/**
* @return the record's class
*/
public int getDClass() {
return dclass;
}
/**
* @return the record's TTL
*/
public long getTTL() {
return ttl;
}
/**
* Converts the type-specific RR to wire format - must be overriden
*
* @param out
* the output
* @param c
* the compression
* @prama canonical should it be canonical?
*/
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.
*
* @param rec
* the record to compare to
* @return are they the same
*
*/
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.
*
* @return the hash number
*/
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.
*
* @param name
* the name for the new record
* @return the new 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.
*
* @param dclass
* the class
* @param ttl
* the ttl
* @return the new record
*/
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.
*
* @param ttl
* the ttl
* */
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
*
* @param field
* the field name
* @param val
* the value
* @return the result
* */
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
*
* @param field
* the field
* @param val
* the field value
* @return the result
* */
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
*
* @param field
* the field
* @param val
* the field value
* @return the result
* */
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
*
* @param field
* the field
* @param name
* the name
* @return the result
* */
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;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy