// DNS Resource Records (RFC 1035 section 4.1.3).
///|
pub struct RR {
name : String
rtype : UInt16
rclass : UInt16
ttl : UInt
rdlength : UInt16
rdata : RData
}
///|
pub fn RR::encode_checked(self : RR) -> Result[Array[Byte], String] {
match validate_rr_rdata(self.rtype, self.rdata) {
Ok(_) => ()
Err(err) => return Err("DNS RR RDATA: " + err)
}
if self.rtype == qtype_opt && self.name != "" {
return Err("OPT owner name must be the root domain")
}
let name_bytes = match encode_name_checked(self.name) {
Ok(bytes) => bytes
Err(err) => return Err("DNS RR owner: " + err)
}
let rdata_bytes = match self.rdata.encode_checked() {
Ok(bytes) => bytes
Err(err) => return Err("DNS RR RDATA: " + err)
}
if rdata_bytes.length() > 65535 {
return Err("DNS RR RDATA exceeds 65535 octets")
}
let out : Array[Byte] = Array::new(
capacity=name_bytes.length() + 10 + rdata_bytes.length(),
)
append_bytes(out, name_bytes)
append_u16(out, self.rtype.to_int())
append_u16(out, self.rclass.to_int())
append_u32(out, self.ttl)
append_u16(out, rdata_bytes.length())
append_bytes(out, rdata_bytes)
Ok(out)
}
///|
pub fn RR::encode(self : RR) -> Array[Byte] {
match self.encode_checked() {
Ok(bytes) => bytes
Err(error) => abort(error)
}
}
///|
pub fn decode_rr(
bytes : Array[Byte],
offset : Int,
msg_start : Int,
) -> Result[(RR, Int), String] {
let (name, after_name) = match decode_name(bytes, offset, msg_start) {
Ok(value) => value
Err(err) => return Err("DNS RR owner: " + err)
}
match wire_check_range(bytes, after_name, 10) {
Err(err) => return Err("truncated DNS RR header: " + err)
Ok(_) => ()
}
let rtype = ((bytes[after_name].to_int() << 8) |
bytes[after_name + 1].to_int()).to_uint16()
let rclass = ((bytes[after_name + 2].to_int() << 8) |
bytes[after_name + 3].to_int()).to_uint16()
let ttl = match wire_get_u32(bytes, after_name + 4) {
Ok((value, _)) => value
Err(err) => return Err(err)
}
let rdlength = ((bytes[after_name + 8].to_int() << 8) |
bytes[after_name + 9].to_int()).to_uint16()
let rdata_offset = after_name + 10
match wire_check_range(bytes, rdata_offset, rdlength.to_int()) {
Err(err) => return Err("truncated DNS RR RDATA: " + err)
Ok(_) => ()
}
let (rdata, next) = match
decode_rdata(rtype, bytes, rdata_offset, rdlength, msg_start) {
Ok(value) => value
Err(err) => return Err(err)
}
let end = rdata_offset + rdlength.to_int()
if next != end {
return Err("DNS RDATA parser did not consume exactly RDLENGTH octets")
}
if rtype == qtype_opt && name != "" {
return Err("OPT owner name must be the root domain")
}
Ok(({ name, rtype, rclass, ttl, rdlength, rdata }, end))
}
///|
pub fn decode_rrs(
bytes : Array[Byte],
offset : Int,
count : UInt16,
msg_start : Int,
) -> Result[(Array[RR], Int), String] {
if count.to_int() > max_section_records {
return Err("DNS resource-record count exceeds codec limit")
}
let records : Array[RR] = Array::new(capacity=count.to_int())
let pos = Ref(offset)
for _i in 0.. value
Err(err) => return Err(err)
}
records.push(record)
pos.val = next
}
Ok((records, pos.val))
}
///|
pub fn rr_new(
name : String,
rtype : UInt16,
rclass : UInt16,
ttl : UInt,
rdata : RData,
) -> RR {
let rdlength = match rdata.encode_checked() {
Ok(bytes) => bytes.length().to_uint16()
Err(_) => 0
}
{ name, rtype, rclass, ttl, rdlength, rdata }
}
///|
pub fn rr_a(name : String, ip : Int, ttl? : UInt = 300) -> RR {
rr_new(name, qtype_a, qclass_in, ttl, A(ip))
}
///|
pub fn rr_aaaa(
name : String,
w1 : Int,
w2 : Int,
w3 : Int,
w4 : Int,
ttl? : UInt = 300,
) -> RR {
rr_new(name, qtype_aaaa, qclass_in, ttl, AAAA(w1, w2, w3, w4))
}
///|
pub fn rr_cname(name : String, target : String, ttl? : UInt = 300) -> RR {
rr_new(name, qtype_cname, qclass_in, ttl, CNAME(target))
}
///|
pub fn rr_ns(name : String, ns : String, ttl? : UInt = 300) -> RR {
rr_new(name, qtype_ns, qclass_in, ttl, NS(ns))
}
///|
pub fn rr_mx(
name : String,
pref : Int,
exchange : String,
ttl? : UInt = 300,
) -> RR {
rr_new(name, qtype_mx, qclass_in, ttl, MX(pref, exchange))
}
///|
pub fn rr_txt(name : String, strings : Array[String], ttl? : UInt = 300) -> RR {
rr_new(name, qtype_txt, qclass_in, ttl, TXT(strings))
}
///|
pub fn rr_soa(
name : String,
mname : String,
rname : String,
serial : UInt,
refresh : UInt,
retry : UInt,
expire : UInt,
minimum : UInt,
ttl? : UInt = 86400,
) -> RR {
rr_new(
name,
qtype_soa,
qclass_in,
ttl,
SOA(mname, rname, serial, refresh, retry, expire, minimum),
)
}
///|
pub fn rr_srv(
name : String,
priority : UInt,
weight : UInt,
port : UInt,
target : String,
ttl? : UInt = 300,
) -> RR {
rr_new(name, qtype_srv, qclass_in, ttl, SRV(priority, weight, port, target))
}
///|
pub fn rr_ptr(name : String, ptr : String, ttl? : UInt = 300) -> RR {
rr_new(name, qtype_ptr, qclass_in, ttl, PTR(ptr))
}
///|
pub fn rr_is_type(rr : RR, rtype : UInt16) -> Bool {
rr.rtype == rtype
}
///|
/// Return whether an RR's unsigned TTL has elapsed after `current_time`
/// seconds. Equality is expired. Negative elapsed values are treated as being
/// before the record was inserted.
pub fn rr_is_expired(rr : RR, current_time : Int) -> Bool {
current_time >= 0 && rr.ttl.to_int64() <= current_time.to_int64()
}
///|
pub fn rr_short_description(rr : RR) -> String {
rr.name + " " + qtype_to_string(rr.rtype) + " TTL=" + rr.ttl.to_string()
}
///|
/// Compare two DNS TTLs using their RFC 1035 unsigned 32-bit ordering.
/// The result is -1, 0, or 1 and never depends on subtracting values that may
/// overflow a signed `Int`.
pub fn rr_compare_ttl(a : RR, b : RR) -> Int {
a.ttl.compare(b.ttl)
}
///|
pub fn rr_filter_by_min_ttl(rrs : Array[RR], min_ttl : UInt) -> Array[RR] {
let result : Array[RR] = Array::new(capacity=rrs.length())
for rr in rrs {
if rr.ttl >= min_ttl {
result.push(rr)
}
}
result
}
///|
pub fn rr_count_by_type(rrs : Array[RR], rtype : UInt16) -> Int {
let count = Ref(0)
for rr in rrs {
if rr.rtype == rtype {
count.val = count.val + 1
}
}
count.val
}
///|
pub fn ptr_name_from_ipv4(ip : Int) -> String {
let a = (ip >> 24) & 0xFF
let b = (ip >> 16) & 0xFF
let c = (ip >> 8) & 0xFF
let d = ip & 0xFF
d.to_string() +
"." +
c.to_string() +
"." +
b.to_string() +
"." +
a.to_string() +
".in-addr.arpa"
}