-
Notifications
You must be signed in to change notification settings - Fork 109
Model
A Zone contains resource records. Resource records with the same name, class, and type are referred to as Resource Record Sets.
Denominator employs immutable style. This implies that all parameters are passed at once to a constructor. It is widely recognized that factory methods and builders are preferred to constructors since they can hint the names of the parameters.
A Resource Record Set is what is signed when DNS providers implement DNSSEC. As DNSSEC is a feature denominator supports, when we list Resource Records, we are actually listing Resource Record Sets.
For example, the following is a resource record set which contains all ipv4 addresses associated with www
in the zone foo.com.
aka www.foo.com
name class type rdata
www IN A 192.168.254.3
www IN A 192.168.254.4
www IN A 192.168.254.5
The denominator ResourceRecordSet
class implements Set<R>
adding fields corresponding to its textual representation: name
, type
, and optional ttl
. Class IN
is assumed and left out. ResourceRecordSet
is an immutable set of either RData
or Strings elements, allowing users to choose a type-safe or type-free approach depending on their context.
ex. using the type-safe approach
ResourceRecordSet.<AData> builder()
.name("www")
.ttl(3600)
.add(a("192.168.254.3"))
.add(a("192.168.254.4"))
.add(a("192.168.254.5")).build();
ex. using the type-free approach
ResourceRecordSet.<String> builder()
.name("www")
.ttl(3600)
.type("A")
.add("192.168.254.3")
.add("192.168.254.4")
.add("192.168.254.5").build();
Resource records have several commonly used types. For example, the following record types are well-defined and supported across all DNS servers:
A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT
For example, the rdata fields for an A record is a simple string that represents the IP address:
1.2.3.4
SOA rdata is a more complex structure:
ns.example.com. hostmaster.example.com. (
2003080800 ; sn = serial number
172800 ; ref = refresh = 2d
900 ; ret = update retry = 15m
1209600 ; ex = expiry = 2w
3600 ; nx = nxdomain ttl = 1h
)
When Denominator calls toString()
on the RData, it expects it to be a well-formed based on its type.
RData
is largely a marker interface. However, it must enclose its textual type and a description of the toString method.
public interface RData {
/**
* the textual type (such as A or NS), or numeric type if unknown.
*/
String getType();
/**
* zone format of the rdata field.
*/
String toString();
}
public class AData implements RData {
...
public String getType() {
return "A";
}
}
Master format [section 5.1](from http://tools.ietf.org/html/rfc1035) shows that over the wire, types represented in text as AAAA
are passed by value (in this case the unsigned 16bit int 28
). It is possible to create records that do not have well-known types and use numeric values typically in 258-32767
. Denominator does not use the binary format, it uses textual record format. Accordingly, we follow RFC 3597, which suggests an unknown record's type is literally TEXT
with a suffix of the numeric code.
public class UknownData implements RData {
...
public String getType() {
return "TEXT" + getTypeValue();
}
}
The structure of the RData is type-specific, but eventually renders to a String. Exporters such as Edda do not need to deconstruct the RData into a java type, as they are only logging it. Intermediaries can also leave the rdata in its native String form, as all supported DNS providers simply accept and produce opaque rdata strings. That said, deconstructing RData is useful for some who are interested in the semantics of the record data. For example, a GUI might be interested so that it can populate fields without knowing the type up front. Since these two camps, type-safe and type-free, need to coexist, Denominator represents a ResourceRecordSet as primarily a Set of strings, optionally viewable as a Set of RData objects.
RData which have only one field are created via a static factory method named as lowercase type name. ex. a("1.2.3.4")
. This is far less clutter than the builder mechanism required for complex types which otherwise would look like AData.builder().address("1.2.3.4").build()
RData ranges from zero-length to highly structured data. When the rdata subtype contains more than 2 fields, it must contain an inner static interface Builder with a single build method. These builders do not need to inherit from any concrete types, nor be predefined in the base RData class, which would increase complexity of these types.
public class SOAData extends RData {
private final String ns;
...
public static class Builder {
private String ns;
...
/**
* @see #getNs()
*/
public Builder ns(String ns) {
this.ns = ns;
return this;
}
public SOAData build() {
return new SOAData(ns, ...
}
...
We wish to reduce clutter where possible, For example, while technically new SOAData.Builder().ns("ns.example.com.")...
is necessary, a shortcut soa().ns("ns.example.com.")...
is exposed to reduce the noise for users. soa()
in this case is a static method.
While all record data can be represented as an opaque string, well known types have formats specified in an RFC such as rfc1035. For the types that can only have specific formats, we facilitate populating them in a type-safe way through builders or factory methods. Builders expose parameter names and allow us to validate fields are correct. Denominator includes a number of common RData types to facilitate easy and safe construction of most records.
ex. AData
is simple, but is validatable.
rData = a("1.2.3.4");
ex. MXData
is structured with two mandatory parts.
rData = mx().preference(2).exchange("server2.mydomain.com.").build();
ex. SOAData
is complex.
rData = soa().ns("ns.mynameserver.com.")
.email("root.ns.mynameserver.com.")
.serial(2004123001)
....
.build();
Many users will employ type-safe classes such as SOAData
to construct record data. Others, for example those writing parsers, may wish to opt-out of the type safe classes. For example, it is a common case simply to pass records from one source to another. Converting to narrowed java types isn't necessary in this case, and attempts to do so would result is a large switch statement or complex factory lookup code. Users in these scenarios should not use RData types and instead just use Strings.
Ex. instead of the more verbose and high maintenance approach of type-specific lookups
rdata = findParser(row.column(3)).parse(row.column(4));
just use the string.
rdata = row.column(4);