Persistence (#18)
* initial SQLite integration * iterable Journal * journal recovery validated * validation of journaling adds and deletes * journal recovery in named * updated journal documentation
This commit is contained in:
parent
32f0bff0a3
commit
85d71ca83e
11
CHANGELOG.md
11
CHANGELOG.md
@ -2,6 +2,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## unreleased
|
||||
### Added
|
||||
- Added recovery from journal to named startup
|
||||
- SQLite journal for dynamic update persistence
|
||||
|
||||
### Changed
|
||||
- Removed many of the unwraps in named binary
|
||||
|
||||
### Fixed
|
||||
- TXT record case sensitivity
|
||||
|
||||
## 0.6.0 2016-06-01
|
||||
### Added
|
||||
- Documentation on all modules, and many standard RFC types
|
||||
|
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -10,7 +10,9 @@ dependencies = [
|
||||
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -49,6 +51,11 @@ name = "bitflags"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.3.0"
|
||||
@ -132,11 +139,33 @@ dependencies = [
|
||||
"pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
version = "0.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"linked-hash-map 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "0.1.11"
|
||||
@ -276,6 +305,18 @@ name = "regex-syntax"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libsqlite3-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lru-cache 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.19"
|
||||
|
@ -76,4 +76,6 @@ mio = "^0.5.1"
|
||||
openssl = "^0.7.8"
|
||||
openssl-sys = "^0.7.8"
|
||||
rustc-serialize = "^0.3.18"
|
||||
rusqlite = "^0.7.3"
|
||||
time = "^0.1.35"
|
||||
toml = "^0.1.28"
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2016 Benjamin Fry
|
||||
// Copyright 2015-2016 Benjamin Fry <benjaminfry@me.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
|
@ -19,7 +19,8 @@ use std::cmp::Ordering;
|
||||
use chrono::offset::utc::UTC;
|
||||
use openssl::crypto::pkey::Role;
|
||||
|
||||
use ::authority::{UpdateResult, ZoneType, RRSet};
|
||||
use ::authority::{Journal, RRSet, UpdateResult, ZoneType};
|
||||
use ::error::{PersistenceError, PersistenceResult};
|
||||
use ::op::{Message, UpdateMessage, ResponseCode, Query};
|
||||
use ::rr::{DNSClass, Name, RData, Record, RecordType};
|
||||
use ::rr::rdata::{NSEC, SIG};
|
||||
@ -62,10 +63,14 @@ impl Ord for RrKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Authority is the storage method for all resource records
|
||||
/// Authority is responsible for storing the resource records for a particular zone.
|
||||
///
|
||||
/// Authorities default to DNSClass IN. The ZoneType specifies if this should be treated as the
|
||||
/// start of authority for the zone, is a slave, or a cached zone.
|
||||
pub struct Authority {
|
||||
origin: Name,
|
||||
class: DNSClass,
|
||||
journal: Option<Journal>,
|
||||
records: BTreeMap<RrKey, RRSet>,
|
||||
zone_type: ZoneType,
|
||||
allow_update: bool,
|
||||
@ -77,10 +82,6 @@ pub struct Authority {
|
||||
secure_keys: Vec<Signer>,
|
||||
}
|
||||
|
||||
/// Authority is responsible for storing the resource records for a particular zone.
|
||||
///
|
||||
/// Authorities default to DNSClass IN. The ZoneType specifies if this should be treated as the
|
||||
/// start of authority for the zone, is a slave, or a cached zone.
|
||||
impl Authority {
|
||||
/// Creates a new Authority.
|
||||
///
|
||||
@ -88,7 +89,7 @@ impl Authority {
|
||||
///
|
||||
/// * `origin` - The zone `Name` being created, this should match that of the `RecordType::SOA`
|
||||
/// record.
|
||||
/// * `records` - The `HashMap` of the initial set of records in the zone.
|
||||
/// * `records` - The map of the initial set of records in the zone.
|
||||
/// * `zone_type` - The type of zone, i.e. is this authoritative?
|
||||
/// * `allow_update` - If true, then this zone accepts dynamic updates.
|
||||
///
|
||||
@ -96,10 +97,15 @@ impl Authority {
|
||||
///
|
||||
/// The new `Authority`.
|
||||
pub fn new(origin: Name, records: BTreeMap<RrKey, RRSet>, zone_type: ZoneType, allow_update: bool) -> Authority {
|
||||
Authority{ origin: origin, class: DNSClass::IN, records: records, zone_type: zone_type,
|
||||
Authority{ origin: origin, class: DNSClass::IN, journal: None, records: records, zone_type: zone_type,
|
||||
allow_update: allow_update, secure_keys: Vec::new() }
|
||||
}
|
||||
|
||||
/// By adding a secure key, this will implicitly enable dnssec for the zone.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `signer` - Signer with associated private key
|
||||
pub fn add_secure_key(&mut self, signer: Signer) {
|
||||
// also add the key to the zone
|
||||
let zone_ttl = self.get_minimum_ttl();
|
||||
@ -111,6 +117,68 @@ impl Authority {
|
||||
self.secure_keys.push(signer);
|
||||
}
|
||||
|
||||
/// Recovers the zone from a Journal, returns an error on failure to recover the zone.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `journal` - the journal from which to load the persisted zone.
|
||||
pub fn recover_with_journal(&mut self, journal: &Journal) -> PersistenceResult<()> {
|
||||
assert!(self.records.is_empty(), "records should be empty during a recovery");
|
||||
|
||||
info!("recovering from journal");
|
||||
for record in journal.iter() {
|
||||
// AXFR is special, it is used to mark the dump of a full zone.
|
||||
// when recovering, if an AXFR is encountered, we should remove all the records in the
|
||||
// authority.
|
||||
if record.get_rr_type() == RecordType::AXFR {
|
||||
self.records.clear();
|
||||
} else {
|
||||
match self.update_records(&[record], false) {
|
||||
Err(error) => return Err(PersistenceError::RecoveryError(error_loc!(), format!("error recovering from journal: {:?}", error))),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// zone signing was off during load, now sign the zone.
|
||||
self.sign_zone();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Persist the state of the current zone to the journal, does nothing if there is no associated
|
||||
/// Journal.
|
||||
///
|
||||
/// Returns an error if there was an issue writing to the persistence layer.
|
||||
pub fn persist_to_journal(&self) -> PersistenceResult<()> {
|
||||
if let Some(journal) = self.journal.as_ref() {
|
||||
let serial = self.get_serial();
|
||||
|
||||
info!("persisting zone to journal at SOA.serial: {}", serial);
|
||||
|
||||
// TODO: THIS NEEDS TO BE IN A TRANSACTION!!!
|
||||
try!(journal.insert_record(serial, Record::new().rr_type(RecordType::AXFR)));
|
||||
|
||||
for rr_set in self.records.values() {
|
||||
// TODO: should we preserve rr_sets or not?
|
||||
for record in rr_set.iter() {
|
||||
try!(journal.insert_record(serial, record));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: COMMIT THE TRANSACTION!!!
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn journal(&mut self, journal: Journal) {
|
||||
self.journal = Some(journal);
|
||||
}
|
||||
|
||||
pub fn get_journal(&self) -> Option<&Journal> {
|
||||
self.journal.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_allow_update(&mut self, allow_update: bool) {
|
||||
self.allow_update = allow_update;
|
||||
@ -129,9 +197,13 @@ impl Authority {
|
||||
self.zone_type
|
||||
}
|
||||
|
||||
pub fn get_records(&self) -> &BTreeMap<RrKey, RRSet> {
|
||||
&self.records
|
||||
}
|
||||
|
||||
/// Returns the SOA of the authority.
|
||||
///
|
||||
/// *Note* This will only return the SOA, if this is fullfilling a request, a standard lookup
|
||||
/// *Note*: This will only return the SOA, if this is fullfilling a request, a standard lookup
|
||||
/// should be used, see `get_soa_secure()`, which will optionally return RRSIGs.
|
||||
pub fn get_soa(&self) -> Option<&Record> {
|
||||
// SOA should be origin|SOA
|
||||
@ -529,10 +601,24 @@ impl Authority {
|
||||
/// NONE rrset rr Delete an RR from an RRset
|
||||
/// zone rrset rr Add to an RRset
|
||||
/// ```
|
||||
fn update_records(&mut self, records: &[Record]) -> UpdateResult<bool> {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `records` - set of record instructions for update following above rules
|
||||
/// * `auto_sign` - if true, the zone will auto_sign (assuming there are signers present), this
|
||||
/// should be disabled during recovery.
|
||||
fn update_records(&mut self, records: &[Record], auto_sign: bool) -> UpdateResult<bool> {
|
||||
let mut updated = false;
|
||||
let serial: u32 = self.get_serial();
|
||||
|
||||
// the persistence act as a write-ahead log. The WAL will also be used for recovery of a zone
|
||||
// subsequent to a failure of the server.
|
||||
if let Some(ref journal) = self.journal {
|
||||
if let Err(error) = journal.insert_records(serial, records) {
|
||||
error!("could not persist update records: {}", error);
|
||||
return Err(ResponseCode::ServFail);
|
||||
}
|
||||
}
|
||||
|
||||
// 3.4.2.7 - Pseudocode For Update Section Processing
|
||||
//
|
||||
@ -655,7 +741,7 @@ impl Authority {
|
||||
}
|
||||
|
||||
// update the serial...
|
||||
if updated {
|
||||
if auto_sign && updated {
|
||||
self.secure_zone();
|
||||
}
|
||||
|
||||
@ -746,7 +832,7 @@ impl Authority {
|
||||
try!(self.verify_prerequisites(update.get_pre_requisites()));
|
||||
try!(self.pre_scan(update.get_updates()));
|
||||
|
||||
self.update_records(update.get_updates())
|
||||
self.update_records(update.get_updates(), true)
|
||||
}
|
||||
|
||||
/// Using the specified query, perform a lookup against this zone.
|
||||
@ -1232,12 +1318,12 @@ pub mod authority_tests {
|
||||
//
|
||||
// zone rrset rr Add to an RRset
|
||||
let add_record = &[Record::new().name(new_name.clone()).ttl(86400).rr_type(RecordType::A).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(93,184,216,24))).clone()];
|
||||
assert!(authority.update_records(add_record).expect("update failed"));
|
||||
assert!(authority.update_records(add_record, true).expect("update failed"));
|
||||
assert_eq!(authority.lookup(&new_name, RecordType::ANY, false), add_record.iter().collect::<Vec<&Record>>());
|
||||
assert_eq!(serial + 1, authority.get_serial());
|
||||
|
||||
let add_www_record = &[Record::new().name(www_name.clone()).ttl(86400).rr_type(RecordType::A).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(10,0,0,1))).clone()];
|
||||
assert!(authority.update_records(add_www_record).expect("update failed"));
|
||||
assert!(authority.update_records(add_www_record, true).expect("update failed"));
|
||||
assert_eq!(serial + 2, authority.get_serial());
|
||||
|
||||
{
|
||||
@ -1253,7 +1339,7 @@ pub mod authority_tests {
|
||||
//
|
||||
// NONE rrset rr Delete an RR from an RRset
|
||||
let del_record = &[Record::new().name(new_name.clone()).ttl(86400).rr_type(RecordType::A).dns_class(DNSClass::NONE).rdata(RData::A(Ipv4Addr::new(93,184,216,24))).clone()];
|
||||
assert!(authority.update_records(del_record).expect("update failed"));
|
||||
assert!(authority.update_records(del_record, true).expect("update failed"));
|
||||
assert_eq!(serial + 3, authority.get_serial());
|
||||
{
|
||||
println!("after delete of specific record: {:?}", authority.lookup(&new_name, RecordType::ANY, false));
|
||||
@ -1262,7 +1348,7 @@ pub mod authority_tests {
|
||||
|
||||
// remove one from www
|
||||
let del_record = &[Record::new().name(www_name.clone()).ttl(86400).rr_type(RecordType::A).dns_class(DNSClass::NONE).rdata(RData::A(Ipv4Addr::new(10,0,0,1))).clone()];
|
||||
assert!(authority.update_records(del_record).expect("update failed"));
|
||||
assert!(authority.update_records(del_record, true).expect("update failed"));
|
||||
assert_eq!(serial + 4, authority.get_serial());
|
||||
{
|
||||
let mut www_rrset = authority.lookup(&www_name, RecordType::ANY, false);
|
||||
@ -1274,7 +1360,7 @@ pub mod authority_tests {
|
||||
//
|
||||
// ANY rrset empty Delete an RRset
|
||||
let del_record = &[Record::new().name(www_name.clone()).ttl(86400).rr_type(RecordType::A).dns_class(DNSClass::ANY).rdata(RData::NULL(NULL::new())).clone()];
|
||||
assert!(authority.update_records(del_record).expect("update failed"));
|
||||
assert!(authority.update_records(del_record, true).expect("update failed"));
|
||||
assert_eq!(serial + 5, authority.get_serial());
|
||||
let mut removed_a_vec: Vec<_> = vec![
|
||||
Record::new().name(www_name.clone()).ttl(86400).rr_type(RecordType::TXT).dns_class(DNSClass::IN).rdata(RData::TXT(TXT::new(vec!["v=spf1 -all".to_string()]))).clone(),
|
||||
@ -1293,7 +1379,7 @@ pub mod authority_tests {
|
||||
// ANY ANY empty Delete all RRsets from a name
|
||||
println!("deleting all records");
|
||||
let del_record = &[Record::new().name(www_name.clone()).ttl(86400).rr_type(RecordType::ANY).dns_class(DNSClass::ANY).rdata(RData::NULL(NULL::new())).clone()];
|
||||
assert!(authority.update_records(del_record).expect("update failed"));
|
||||
assert!(authority.update_records(del_record, true).expect("update failed"));
|
||||
assert!(authority.lookup(&www_name, RecordType::ANY, false).is_empty());
|
||||
assert_eq!(serial + 6, authority.get_serial());
|
||||
}
|
||||
@ -1334,4 +1420,88 @@ pub mod authority_tests {
|
||||
assert!(record.get_name() < &name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_journal() {
|
||||
use std::net::Ipv4Addr;
|
||||
use rusqlite::Connection;
|
||||
use ::authority::Journal;
|
||||
|
||||
// test that this message can be inserted
|
||||
let conn = Connection::open_in_memory().expect("could not create in memory DB");
|
||||
let mut journal = Journal::new(conn).unwrap();
|
||||
journal.schema_up().unwrap();
|
||||
|
||||
let mut authority = create_example();
|
||||
authority.journal(journal);
|
||||
authority.persist_to_journal().unwrap();
|
||||
|
||||
let new_name = Name::new().label("new").label("example").label("com");
|
||||
let delete_name = Name::new().label("www").label("example").label("com");
|
||||
let new_record = Record::new().name(new_name.clone()).rdata(RData::A(Ipv4Addr::new(10,11,12,13))).clone();
|
||||
let delete_record = Record::new().name(delete_name.clone()).rdata(RData::A(Ipv4Addr::new(93,184,216,34))).dns_class(DNSClass::NONE).clone();
|
||||
authority.update_records(&[new_record.clone(), delete_record], true).unwrap();
|
||||
|
||||
// assert that the correct set of records is there.
|
||||
let new_rrset: Vec<&Record> = authority.lookup(&new_name, RecordType::A, false);
|
||||
assert!(new_rrset.iter().all(|r| *r == &new_record));
|
||||
|
||||
let delete_rrset: Vec<&Record> = authority.lookup(&delete_name, RecordType::A, false);
|
||||
assert!(delete_rrset.is_empty());
|
||||
|
||||
// that record should have been recorded... let's reload the journal and see if we get it.
|
||||
let mut recovered_authority = Authority::new(authority.get_origin().clone(),
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false);
|
||||
recovered_authority.recover_with_journal(authority.get_journal().expect("journal not Some")).expect("recovery");
|
||||
|
||||
// assert that the correct set of records is there.
|
||||
let new_rrset: Vec<&Record> = recovered_authority.lookup(&new_name, RecordType::A, false);
|
||||
assert!(new_rrset.iter().all(|r| *r == &new_record));
|
||||
|
||||
let delete_rrset: Vec<&Record> = authority.lookup(&delete_name, RecordType::A, false);
|
||||
assert!(delete_rrset.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recovery() {
|
||||
use rusqlite::Connection;
|
||||
use ::authority::Journal;
|
||||
|
||||
// test that this message can be inserted
|
||||
let conn = Connection::open_in_memory().expect("could not create in memory DB");
|
||||
let mut journal = Journal::new(conn).unwrap();
|
||||
journal.schema_up().unwrap();
|
||||
|
||||
let mut authority = create_example();
|
||||
authority.journal(journal);
|
||||
authority.persist_to_journal().unwrap();
|
||||
|
||||
let journal = authority.get_journal().unwrap();
|
||||
let mut recovered_authority = Authority::new(authority.get_origin().clone(),
|
||||
BTreeMap::new(),
|
||||
ZoneType::Master,
|
||||
false);
|
||||
|
||||
recovered_authority.recover_with_journal(journal).expect("recovery");
|
||||
|
||||
assert_eq!(recovered_authority.get_records().len(), authority.get_records().len());
|
||||
assert_eq!(recovered_authority.get_soa(), authority.get_soa());
|
||||
assert!(recovered_authority.get_records().iter().all(|(rr_key, rr_set)| {
|
||||
let other_rr_set = authority.get_records().get(rr_key).expect(&format!("key doesn't exist: {:?}", rr_key));
|
||||
rr_set.iter().zip(other_rr_set.iter()).all(|(record, other_record)| {
|
||||
record.get_ttl() == other_record.get_ttl() &&
|
||||
record.get_rdata() == other_record.get_rdata()
|
||||
})
|
||||
}));
|
||||
|
||||
assert!(authority.get_records().iter().all(|(rr_key, rr_set)| {
|
||||
let other_rr_set = recovered_authority.get_records().get(rr_key).expect(&format!("key doesn't exist: {:?}", rr_key));
|
||||
rr_set.iter().zip(other_rr_set.iter()).all(|(record, other_record)| {
|
||||
record.get_ttl() == other_record.get_ttl() &&
|
||||
record.get_rdata() == other_record.get_rdata()
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,14 @@ pub enum ZoneType { Master, Slave, Hint, Forward }
|
||||
|
||||
mod authority;
|
||||
mod catalog;
|
||||
mod persistence;
|
||||
mod rr_set;
|
||||
|
||||
pub use self::authority::Authority;
|
||||
pub use self::authority::RrKey;
|
||||
pub use self::catalog::Catalog;
|
||||
pub use self::rr_set::RRSet;
|
||||
pub use self::persistence::Journal;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use self::authority::authority_tests;
|
||||
|
348
src/authority/persistence.rs
Normal file
348
src/authority/persistence.rs
Normal file
@ -0,0 +1,348 @@
|
||||
// Copyright 2015-2016 Benjamin Fry <benjaminfry -@- me.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
|
||||
use time;
|
||||
use rusqlite;
|
||||
use rusqlite::Connection;
|
||||
use rusqlite::SqliteError;
|
||||
|
||||
use ::error::PersistenceError;
|
||||
use ::error::PersistenceResult;
|
||||
use ::rr::Record;
|
||||
use ::serialize::binary::{BinDecoder, BinEncoder, BinSerializable};
|
||||
|
||||
const CURRENT_VERSION: i64 = 1;
|
||||
|
||||
/// The Journal is the audit log of all changes to a zone after initial creation.
|
||||
pub struct Journal {
|
||||
conn: Connection,
|
||||
version: i64,
|
||||
}
|
||||
|
||||
impl Journal {
|
||||
pub fn new(conn: Connection) -> PersistenceResult<Journal> {
|
||||
let version = Self::select_schema_version(&conn);
|
||||
Ok(Journal { conn: conn, version: try!(version) })
|
||||
}
|
||||
|
||||
pub fn from_file(journal_file: &Path) -> PersistenceResult<Journal> {
|
||||
Self::new(try_rethrow!(PersistenceError::SqliteError, Connection::open(journal_file)))
|
||||
}
|
||||
|
||||
/// gets the current schema version of the journal
|
||||
pub fn get_schema_version(&self) -> i64 {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// this returns an iterator from the beginning of time, to be used to recreate an authority
|
||||
pub fn iter<'j>(&'j self) -> JournalIter<'j> {
|
||||
JournalIter::new(self)
|
||||
}
|
||||
|
||||
/// Inserts a record, this is an append only operation.
|
||||
///
|
||||
/// Records should never be posthumously modified. The message will be serialized into the.
|
||||
/// the first message serialized to the journal, should be a single AXFR of the entire zone,
|
||||
/// this will be used as a starting point to reconstruct the zone.
|
||||
///
|
||||
/// # Argument
|
||||
///
|
||||
/// * `record` - will be serialized into the journal
|
||||
pub fn insert_record(&self, soa_serial: u32, record: &Record) -> PersistenceResult<()> {
|
||||
assert!(self.version == CURRENT_VERSION, "schema version mismatch, schema_up() resolves this");
|
||||
|
||||
let mut serial_record: Vec<u8> = Vec::with_capacity(512);
|
||||
{
|
||||
let mut encoder = BinEncoder::new(&mut serial_record);
|
||||
try_rethrow!(PersistenceError::EncodeError, record.emit(&mut encoder));
|
||||
}
|
||||
|
||||
let timestamp = time::get_time();
|
||||
let client_id: i64 = 0; // TODO: we need better id information about the client, like pub_key
|
||||
let soa_serial: i64 = soa_serial as i64;
|
||||
|
||||
let count = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.execute("INSERT
|
||||
INTO records (client_id, soa_serial, timestamp, record)
|
||||
VALUES ($1, $2, $3, $4)",
|
||||
&[&client_id, &soa_serial, ×tamp, &serial_record]));
|
||||
//
|
||||
if count != 1 {
|
||||
return Err(PersistenceError::WrongInsertCount{loc: error_loc!(), got: count, expect: 1});
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts a set of records into the Journal, a convenience method for insert_record
|
||||
pub fn insert_records(&self, soa_serial: u32, records: &[Record]) -> PersistenceResult<()> {
|
||||
// TODO: NEED TRANSACTION HERE
|
||||
for record in records {
|
||||
try!(self.insert_record(soa_serial, record));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Selects a record from the given row_id.
|
||||
///
|
||||
/// This allows for the entire set of records to be iterated through, by starting at 0, and
|
||||
/// incrementing each subsequent row.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `row_id` - the row_id can either be exact, or start at 0 to get the earliest row in the
|
||||
/// list.
|
||||
pub fn select_record(&self, row_id: i64) -> PersistenceResult<Option<(i64, Record)>> {
|
||||
assert!(self.version == CURRENT_VERSION, "schema version mismatch, schema_up() resolves this");
|
||||
|
||||
let mut stmt = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.prepare("SELECT _rowid_, record
|
||||
FROM records
|
||||
WHERE _rowid_ >= $1
|
||||
LIMIT 1"));
|
||||
|
||||
let record_opt: Option<Result<(i64, Record), SqliteError>> = try_rethrow!(PersistenceError::SqliteError,
|
||||
stmt.query_and_then(&[&row_id],
|
||||
|row| -> Result<(i64, Record), SqliteError> {
|
||||
let row_id: i64 = try!(row.get_checked(0));
|
||||
let record_bytes: Vec<u8> = try!(row.get_checked(1));
|
||||
let mut decoder = BinDecoder::new(&record_bytes);
|
||||
|
||||
// todo add location to this...
|
||||
match Record::read(&mut decoder) {
|
||||
Ok(record) => Ok((row_id, record)),
|
||||
Err(decode_error) => Err(rusqlite::Error::FromSqlConversionFailure(Box::new(decode_error))),
|
||||
}
|
||||
})).next();
|
||||
|
||||
//
|
||||
match record_opt {
|
||||
Some(Ok((row_id, record))) => Ok(Some((row_id, record))),
|
||||
Some(Err(err)) => Err(PersistenceError::SqliteError(error_loc!(), err)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// selects the current schema version of the journal DB, returns -1 if there is no schema
|
||||
///
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `conn` - db connection to use
|
||||
fn select_schema_version(conn: &Connection) -> PersistenceResult<i64> {
|
||||
// first see if our schema is there
|
||||
let mut stmt = try_rethrow!(PersistenceError::SqliteError,
|
||||
conn.prepare("SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type='table'
|
||||
AND name='tdns_schema'"));
|
||||
|
||||
let tdns_schema_opt: Option<Result<String, _>> = try_rethrow!(PersistenceError::SqliteError,
|
||||
stmt.query_map(&[],
|
||||
|row| row.get(0)))
|
||||
.next();
|
||||
|
||||
let tdns_schema = match tdns_schema_opt {
|
||||
Some(Ok(string)) => string,
|
||||
Some(Err(err)) => return Err(PersistenceError::SqliteError(error_loc!(), err)),
|
||||
None => return Ok(-1),
|
||||
};
|
||||
|
||||
assert_eq!(&tdns_schema, "tdns_schema");
|
||||
|
||||
let version: i64 = try_rethrow!(PersistenceError::SqliteError,
|
||||
conn.query_row_safe("SELECT version
|
||||
FROM tdns_schema",
|
||||
&[],
|
||||
|row| row.get(0)));
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// update the schema version
|
||||
fn update_schema_version(&self, new_version: i64) -> PersistenceResult<()> {
|
||||
// validate the versions of all the schemas...
|
||||
assert!(new_version <= CURRENT_VERSION);
|
||||
|
||||
let count = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.execute("UPDATE tdns_schema SET version = $1", &[&new_version]));
|
||||
|
||||
//
|
||||
assert_eq!(count, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// initilizes the schema for the Journal
|
||||
pub fn schema_up(&mut self) -> PersistenceResult<i64> {
|
||||
while self.version < CURRENT_VERSION {
|
||||
match self.version + 1 {
|
||||
0 => self.version = try!(self.init_up()),
|
||||
1 => self.version = try!(self.records_up()),
|
||||
_ => panic!("incorrect version somewhere"),
|
||||
}
|
||||
|
||||
try!(self.update_schema_version(self.version));
|
||||
}
|
||||
|
||||
Ok(self.version)
|
||||
}
|
||||
|
||||
/// initial schema, include the tdns_schema table for tracking the Journal version
|
||||
fn init_up(&self) -> PersistenceResult<i64> {
|
||||
let count = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.execute("CREATE TABLE tdns_schema (
|
||||
version INTEGER NOT NULL
|
||||
)", &[]));
|
||||
//
|
||||
assert_eq!(count, 0);
|
||||
|
||||
let count = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.execute("INSERT INTO tdns_schema (version)
|
||||
VALUES (0)", &[]));
|
||||
//
|
||||
assert_eq!(count, 1);
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// adds the records table, this is the main and single table for the history of changes to an
|
||||
/// authority. Each record is expected to be in the format of an update record
|
||||
fn records_up(&self) -> PersistenceResult<i64> {
|
||||
// we'll be using rowid for our primary key, basically: `rowid INTEGER PRIMARY KEY ASC`
|
||||
let count = try_rethrow!(PersistenceError::SqliteError,
|
||||
self.conn.execute("CREATE TABLE records (
|
||||
client_id INTEGER NOT NULL,
|
||||
soa_serial INTEGER NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
record BLOB NOT NULL
|
||||
)", &[]));
|
||||
//
|
||||
assert_eq!(count, 1);
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JournalIter<'j> {
|
||||
current_row_id: i64,
|
||||
journal: &'j Journal,
|
||||
}
|
||||
|
||||
impl<'j> JournalIter<'j> {
|
||||
fn new(journal: &'j Journal) -> Self {
|
||||
JournalIter { current_row_id: 0, journal: journal }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'j> Iterator for JournalIter<'j> {
|
||||
type Item = Record;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next: PersistenceResult<Option<(i64, Record)>> = self.journal.select_record(self.current_row_id + 1);
|
||||
|
||||
match next {
|
||||
Ok(Some((row_id, record))) => {
|
||||
self.current_row_id = row_id;
|
||||
Some(record)
|
||||
},
|
||||
Ok(None) => {
|
||||
None
|
||||
},
|
||||
Err(err) => {
|
||||
error!("persistence error while iterating over journal: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_journal() {
|
||||
let conn = Connection::open_in_memory().expect("could not create in memory DB");
|
||||
assert_eq!(Journal::new(conn).expect("new Journal").get_schema_version(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init_journal() {
|
||||
let conn = Connection::open_in_memory().expect("could not create in memory DB");
|
||||
let mut journal = Journal::new(conn).unwrap();
|
||||
let version = journal.schema_up().unwrap();
|
||||
assert_eq!(version, CURRENT_VERSION);
|
||||
assert_eq!(Journal::select_schema_version(&journal.conn).unwrap(), CURRENT_VERSION);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn create_test_journal() -> (Record, Journal) {
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ::rr::{Name, RData, Record, RecordType};
|
||||
|
||||
let www = Name::with_labels(vec!["www".to_string(),"example".to_string(), "com".to_string()]);
|
||||
|
||||
let mut record = Record::new();
|
||||
record.name(www);
|
||||
record.rr_type(RecordType::A);
|
||||
record.rdata(RData::A(Ipv4Addr::from_str("127.0.0.1").unwrap()));
|
||||
|
||||
// test that this message can be inserted
|
||||
let conn = Connection::open_in_memory().expect("could not create in memory DB");
|
||||
let mut journal = Journal::new(conn).unwrap();
|
||||
journal.schema_up().unwrap();
|
||||
|
||||
// insert the message
|
||||
journal.insert_record(0, &record).unwrap();
|
||||
|
||||
// insert another...
|
||||
record.rdata(RData::A(Ipv4Addr::from_str("127.0.1.1").unwrap()));
|
||||
journal.insert_record(0, &record).unwrap();
|
||||
|
||||
(record, journal)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_and_select_record() {
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use rr::RData;
|
||||
|
||||
let (mut record, journal) = create_test_journal();
|
||||
|
||||
// select the record
|
||||
let (row_id, journal_record) = journal.select_record(0).expect("persistence error").expect("none");
|
||||
record.rdata(RData::A(Ipv4Addr::from_str("127.0.0.1").unwrap()));
|
||||
assert_eq!(journal_record, record);
|
||||
|
||||
// test another
|
||||
let (row_id, journal_record) = journal.select_record(row_id + 1).expect("persistence error").expect("none");
|
||||
record.rdata(RData::A(Ipv4Addr::from_str("127.0.1.1").unwrap()));
|
||||
assert_eq!(journal_record, record);
|
||||
|
||||
// check that we get nothing for id over row_id
|
||||
let option_none = journal.select_record(row_id + 1).expect("persistence error");
|
||||
assert!(option_none.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterator() {
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use rr::RData;
|
||||
|
||||
let (mut record, journal) = create_test_journal();
|
||||
|
||||
let mut iter = journal.iter();
|
||||
|
||||
assert_eq!(record.rdata(RData::A(Ipv4Addr::from_str("127.0.0.1").unwrap())), &iter.next().unwrap());
|
||||
assert_eq!(record.rdata(RData::A(Ipv4Addr::from_str("127.0.1.1").unwrap())), &iter.next().unwrap());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
@ -1,23 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2016 Benjamin Fry <benjaminfry@me.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Copyright 2015-2016 Benjamin Fry <benjaminfry@me.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use std::slice::Iter;
|
||||
|
||||
use ::rr::{Name, Record, RecordType, RData};
|
||||
|
||||
|
||||
/// Set of resource records associated to a name and type
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RRSet {
|
||||
name: Name,
|
||||
record_type: RecordType,
|
||||
@ -78,6 +70,11 @@ impl RRSet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the records in the set
|
||||
pub fn iter<'s>(&'s self) -> Iter<'s, Record> {
|
||||
self.records.iter()
|
||||
}
|
||||
|
||||
/// # Return value
|
||||
///
|
||||
/// True if there are no records in this set
|
||||
|
@ -90,6 +90,7 @@ pub struct ZoneConfig {
|
||||
zone_type: ZoneType,
|
||||
file: String,
|
||||
allow_update: Option<bool>,
|
||||
enable_dnssec: Option<bool>,
|
||||
}
|
||||
|
||||
impl ZoneConfig {
|
||||
@ -97,7 +98,8 @@ impl ZoneConfig {
|
||||
pub fn get_zone(&self) -> ParseResult<Name> { Name::parse(&self.zone, Some(&Name::new())) }
|
||||
pub fn get_zone_type(&self) -> ZoneType { self.zone_type }
|
||||
pub fn get_file(&self) -> PathBuf { PathBuf::from(&self.file) }
|
||||
pub fn get_allow_udpate(&self) -> bool { self.allow_update.unwrap_or(false) }
|
||||
pub fn is_update_allowed(&self) -> bool { self.allow_update.unwrap_or(false) }
|
||||
pub fn is_dnssec_enabled(&self) -> bool { self.enable_dnssec.unwrap_or(false) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,20 +1,20 @@
|
||||
#
|
||||
# This is an example configuration file for the TRust-DNS named server.
|
||||
#
|
||||
# The format is in TOML: https://github.com/toml-lang/toml which was chosen
|
||||
# as the configuration format for TRust-DNS. While Trust-DNS is inteaded to
|
||||
# be a drop-in replacement for BIND9, it will not support the named.conf files
|
||||
# directly. At some point, there will be a binary tool for converting the
|
||||
# BIND9 configuration files over to TRust-DNS TOML.
|
||||
#
|
||||
# Many of these options are available as both command line options and
|
||||
# configuration options in these files. In that case, the command line option
|
||||
# will take presidence.
|
||||
#
|
||||
# Comments with two hash marks, ##, document the config parameter
|
||||
# Comments with one hash mark, #, is an example line and should be the default
|
||||
#
|
||||
# The root options are similar to the options in 'options { .. }' in named.conf
|
||||
##
|
||||
## This is an example configuration file for the TRust-DNS named server.
|
||||
##
|
||||
## The format is in TOML: https://github.com/toml-lang/toml which was chosen
|
||||
## as the configuration format for TRust-DNS. While Trust-DNS is inteaded to
|
||||
## be a drop-in replacement for BIND9, it will not support the named.conf files
|
||||
## directly. At some point, there will be a binary tool for converting the
|
||||
## BIND9 configuration files over to TRust-DNS TOML.
|
||||
##
|
||||
## Many of these options are available as both command line options and
|
||||
## configuration options in these files. In that case, the command line option
|
||||
## will take presidence.
|
||||
##
|
||||
## Comments with two hash marks, ##, document the config parameter
|
||||
## Comments with one hash mark, #, is an example line and should be the default
|
||||
##
|
||||
## The root options are similar to the options in 'options { .. }' in named.conf
|
||||
|
||||
## listen_addrs: address on which to listen for incoming connections
|
||||
## this can be a list of ipv4 or ipv6 addresses
|
||||
@ -34,13 +34,8 @@
|
||||
## Default zones, these should be present on all nameservers, except in rare
|
||||
## configuration cases
|
||||
[[zones]]
|
||||
## zone: this is the ORIGIN of the zone, aka the base name, '.' is implied on the end
|
||||
zone = "localhost"
|
||||
|
||||
## zone_type: Master, Slave, Hint, Forward
|
||||
zone_type = "Master"
|
||||
|
||||
## file: this is relative to the directory above
|
||||
file = "default/localhost.zone"
|
||||
|
||||
[[zones]]
|
||||
@ -64,6 +59,20 @@ zone_type = "Master"
|
||||
file = "default/0.zone"
|
||||
|
||||
# [[zones]]
|
||||
## zone: this is the ORIGIN of the zone, aka the base name, '.' is implied on the end
|
||||
# zone = "example.com"
|
||||
|
||||
## zone_type: Master, Slave, Hint, Forward
|
||||
# zone_type = "Master"
|
||||
|
||||
## file: this is relative to the directory above
|
||||
# file = "example.com.zone"
|
||||
|
||||
## if false, updates will not be allowed, default false
|
||||
# allow_update = false
|
||||
|
||||
## if true, looks to see if a chained pem file exists at $file.pem
|
||||
## these keys will also be registered as authorities for update,
|
||||
## meaning that SIG(0) updates can be established by initially using these
|
||||
## keys.
|
||||
# enable_dnssec = false
|
||||
|
@ -38,11 +38,11 @@ fn test_read_config() {
|
||||
assert_eq!(config.get_log_level(), LogLevel::Info);
|
||||
assert_eq!(config.get_directory(), Path::new("/var/named"));
|
||||
assert_eq!(config.get_zones(), [
|
||||
ZoneConfig { zone: "localhost".into(), zone_type: ZoneType::Master, file: "default/localhost.zone".into(), allow_update: None },
|
||||
ZoneConfig { zone: "0.0.127.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/127.0.0.1.zone".into(), allow_update: None },
|
||||
ZoneConfig { zone: "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa".into(), zone_type: ZoneType::Master, file: "default/ipv6_1.zone".into(), allow_update: None },
|
||||
ZoneConfig { zone: "255.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/255.zone".into(), allow_update: None },
|
||||
ZoneConfig { zone: "0.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/0.zone".into(), allow_update: None }
|
||||
ZoneConfig { zone: "localhost".into(), zone_type: ZoneType::Master, file: "default/localhost.zone".into(), allow_update: None, enable_dnssec: None },
|
||||
ZoneConfig { zone: "0.0.127.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/127.0.0.1.zone".into(), allow_update: None, enable_dnssec: None },
|
||||
ZoneConfig { zone: "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa".into(), zone_type: ZoneType::Master, file: "default/ipv6_1.zone".into(), allow_update: None, enable_dnssec: None },
|
||||
ZoneConfig { zone: "255.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/255.zone".into(), allow_update: None, enable_dnssec: None },
|
||||
ZoneConfig { zone: "0.in-addr.arpa".into(), zone_type: ZoneType::Master, file: "default/0.zone".into(), allow_update: None, enable_dnssec: None }
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ mod encode_error;
|
||||
mod client_error;
|
||||
mod lexer_error;
|
||||
mod parse_error;
|
||||
mod persistence_error;
|
||||
mod config_error;
|
||||
|
||||
pub use self::base_error::ErrorLoc;
|
||||
@ -31,6 +32,7 @@ pub use self::encode_error::EncodeError;
|
||||
pub use self::client_error::ClientError;
|
||||
pub use self::lexer_error::LexerError;
|
||||
pub use self::parse_error::ParseError;
|
||||
pub use self::persistence_error::PersistenceError;
|
||||
pub use self::config_error::ConfigError;
|
||||
|
||||
pub type DecodeResult<T> = Result<T, DecodeError>;
|
||||
@ -38,4 +40,5 @@ pub type EncodeResult = Result<(), EncodeError>;
|
||||
pub type ClientResult<T> = Result<T, ClientError>;
|
||||
pub type LexerResult<T> = Result<T, LexerError>;
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
pub type PersistenceResult<T> = Result<T, PersistenceError>;
|
||||
pub type ConfigResult<T> = Result<T, ConfigError>;
|
||||
|
57
src/error/persistence_error.rs
Normal file
57
src/error/persistence_error.rs
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2015-2016 Benjamin Fry
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use rusqlite;
|
||||
|
||||
pub enum PersistenceError {
|
||||
DecodeError(super::ErrorLoc, super::DecodeError),
|
||||
EncodeError(super::ErrorLoc, super::EncodeError),
|
||||
SqliteError(super::ErrorLoc, rusqlite::Error),
|
||||
WrongInsertCount{ loc: super::ErrorLoc, got: i32, expect: i32 },
|
||||
RecoveryError(super::ErrorLoc, String),
|
||||
}
|
||||
|
||||
impl fmt::Debug for PersistenceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PersistenceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
PersistenceError::DecodeError(ref err_loc, ref err) => write!(f, "{}:{}", err_loc, err),
|
||||
PersistenceError::EncodeError(ref err_loc, ref err) => write!(f, "{}:{}", err_loc, err),
|
||||
PersistenceError::SqliteError(ref err_loc, ref err) => write!(f, "{}: {}", err_loc, err),
|
||||
PersistenceError::WrongInsertCount{ref loc, got, expect } => write!(f, "{}: got {}, expected: {}", loc, got, expect),
|
||||
PersistenceError::RecoveryError(ref err_loc, ref msg) => write!(f, "{}: error recovering: {}", err_loc, msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PersistenceError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
PersistenceError::DecodeError(_, ref err) => err.description(),
|
||||
PersistenceError::EncodeError(_, ref err) => err.description(),
|
||||
PersistenceError::SqliteError(_, ref err) => err.description(),
|
||||
PersistenceError::WrongInsertCount{ .. } => "an unexpected number of records were inserted",
|
||||
PersistenceError::RecoveryError( .. ) => "error recovering from journal",
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
PersistenceError::DecodeError(_, ref err) => Some(err),
|
||||
PersistenceError::EncodeError(_, ref err) => Some(err),
|
||||
PersistenceError::SqliteError(_, ref err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,9 @@ extern crate data_encoding;
|
||||
extern crate mio;
|
||||
extern crate openssl;
|
||||
extern crate openssl_sys;
|
||||
extern crate rusqlite;
|
||||
extern crate rustc_serialize;
|
||||
extern crate time;
|
||||
extern crate toml;
|
||||
|
||||
|
||||
|
97
src/named.rs
97
src/named.rs
@ -13,6 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//! The `named` binary for running a DNS server
|
||||
//!
|
||||
//! ```text
|
||||
//! Usage: named [options]
|
||||
//! named (-h | --help | --version)
|
||||
//!
|
||||
//! Options:
|
||||
//! -q, --quiet Disable INFO messages, WARN and ERROR will remain
|
||||
//! -d, --debug Turn on DEBUG messages (default is only INFO)
|
||||
//! -h, --help Show this message
|
||||
//! -v, --version Show the version of trust-dns
|
||||
//! -c FILE, --config=FILE Path to configuration file, default is /etc/named.toml
|
||||
//! -z DIR, --zonedir=DIR Path to the root directory for all zone files, see also config toml
|
||||
//! -p PORT, --port=PORT Override the listening port
|
||||
//! ```
|
||||
|
||||
extern crate trust_dns;
|
||||
extern crate rustc_serialize;
|
||||
extern crate docopt;
|
||||
@ -21,6 +38,7 @@ extern crate mio;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
@ -31,8 +49,8 @@ use docopt::Docopt;
|
||||
|
||||
use trust_dns::logger;
|
||||
use trust_dns::version;
|
||||
use trust_dns::authority::{Catalog, Authority};
|
||||
use trust_dns::config::Config;
|
||||
use trust_dns::authority::{Authority, Catalog, Journal};
|
||||
use trust_dns::config::{Config, ZoneConfig};
|
||||
use trust_dns::serialize::txt::Parser;
|
||||
use trust_dns::rr::Name;
|
||||
use trust_dns::server::Server;
|
||||
@ -64,9 +82,67 @@ struct Args {
|
||||
pub flag_port: Option<u16>,
|
||||
}
|
||||
|
||||
fn load_zone(zone_dir: &Path, zone: &ZoneConfig) -> Result<Authority, String> {
|
||||
let zone_name: Name = zone.get_zone().expect("bad zone name");
|
||||
let zone_path: PathBuf = zone_dir.to_owned().join(zone.get_file());
|
||||
let journal_path: PathBuf = zone_path.with_extension(".jrnl");
|
||||
|
||||
|
||||
if zone.is_update_allowed() && journal_path.exists() {
|
||||
info!("recovering zone from journal: {:?}", journal_path);
|
||||
let journal = match Journal::from_file(&journal_path) {
|
||||
Ok(j) => j,
|
||||
Err(e) => return Err(format!("error opening journal: {:?}: {}", journal_path, e)),
|
||||
};
|
||||
|
||||
let mut authority = Authority::new(zone_name.clone(), BTreeMap::new(), zone.get_zone_type(), zone.is_update_allowed());
|
||||
if let Err(e) = authority.recover_with_journal(&journal) {
|
||||
return Err(format!("error recovering from journal: {}", e))
|
||||
}
|
||||
|
||||
authority.journal(journal);
|
||||
info!("recovered zone: {}", zone_name);
|
||||
|
||||
Ok(authority)
|
||||
} else if zone_path.exists() {
|
||||
info!("loading zone file: {:?}", zone_path);
|
||||
|
||||
let zone_file = match File::open(&zone_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(format!("error opening zone file: {:?}: {}", zone_path, e)),
|
||||
};
|
||||
|
||||
let mut authority: Authority = match Parser::parse_file(zone_file, Some(zone_name.clone()), zone.get_zone_type(), zone.is_update_allowed()) {
|
||||
Ok(a) => a,
|
||||
Err(e) => return Err(format!("error reading zone: {:?}: {}", zone_path, e)),
|
||||
};
|
||||
|
||||
// if dynamic update is enabled, enable the journal
|
||||
if zone.is_update_allowed() {
|
||||
info!("enabling journal: {:?}", journal_path);
|
||||
let journal = match Journal::from_file(&journal_path) {
|
||||
Ok(j) => j,
|
||||
Err(e) => return Err(format!("error creating journal {:?}: {}", journal_path, e)),
|
||||
};
|
||||
|
||||
authority.journal(journal);
|
||||
|
||||
// preserve to the new journal, i.e. we just loaded the zone from disk, start the journal
|
||||
if let Err(e) = authority.persist_to_journal() {
|
||||
return Err(format!("error persisting to journal {:?}: {}", journal_path, e))
|
||||
}
|
||||
}
|
||||
|
||||
info!("loaded zone: {}", zone_name);
|
||||
Ok(authority)
|
||||
} else {
|
||||
Err(format!("no zone file defined at: {:?}", zone_path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Main method for running the named server.
|
||||
/// As of this writing, this will panic on any invalid input. At this top level binary is the only
|
||||
/// part Trust-DNS where panics are allowed.
|
||||
///
|
||||
/// `Note`: Tries to avoid panics, in favor of always starting.
|
||||
pub fn main() {
|
||||
// read any command line options
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
@ -93,15 +169,12 @@ pub fn main() {
|
||||
let mut catalog: Catalog = Catalog::new();
|
||||
// configure our server based on the config_path
|
||||
for zone in config.get_zones() {
|
||||
let zone_name: Name = zone.get_zone().unwrap();
|
||||
let zone_path: PathBuf = zone_dir.to_owned().join(zone.get_file());
|
||||
info!("loading zone file: {:?}", zone_path);
|
||||
let zone_name = zone.get_zone().expect(&format!("bad zone name in {:?}", config_path));
|
||||
|
||||
let zone_file = File::open(zone_path).unwrap();
|
||||
let authority: Authority = Parser::parse_file(zone_file, Some(zone_name.clone()), zone.get_zone_type(), zone.get_allow_udpate()).unwrap();
|
||||
info!("loaded zone: {}", zone_name);
|
||||
|
||||
catalog.upsert(zone_name, authority);
|
||||
match load_zone(zone_dir, zone) {
|
||||
Ok(authority) => catalog.upsert(zone_name, authority),
|
||||
Err(error) => error!("could not load zone {}: {}", zone_name, error),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO support all the IPs asked to listen on...
|
||||
|
@ -275,7 +275,7 @@ impl BinSerializable<Name> for Name {
|
||||
}
|
||||
},
|
||||
LabelParseState::Label => {
|
||||
labels.push(Rc::new(try!(decoder.read_character_data())));
|
||||
labels.push(Rc::new(try!(decoder.read_character_data(true))));
|
||||
|
||||
// reset to collect more data
|
||||
LabelParseState::LabelLengthOrPointer
|
||||
|
@ -20,7 +20,7 @@ use ::serialize::binary::*;
|
||||
use ::error::*;
|
||||
use ::rr::dnssec::Algorithm;
|
||||
|
||||
/// [RFC 4034, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4034#section-2)
|
||||
/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-2), DNSSEC Resource Records, March 2005
|
||||
///
|
||||
/// ```text
|
||||
/// 2. The DNSKEY Resource Record
|
||||
|
@ -53,7 +53,7 @@ use ::rr::dnssec::Algorithm;
|
||||
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
///
|
||||
/// ```
|
||||
/// [RFC 2931, DNS Request and Transaction Signatures, September 2000](https://tools.ietf.org/html/rfc2931)
|
||||
/// [RFC 2931](https://tools.ietf.org/html/rfc2931), DNS Request and Transaction Signatures, September 2000
|
||||
///
|
||||
/// NOTE: 2931 updates SIG0 to clarify certain particulars...
|
||||
///
|
||||
|
@ -61,7 +61,7 @@ pub fn read(decoder: &mut BinDecoder, rdata_length: u16) -> DecodeResult<TXT> {
|
||||
let mut strings = Vec::with_capacity(1);
|
||||
|
||||
while data_len - decoder.len() < rdata_length as usize {
|
||||
strings.push(try!(decoder.read_character_data()));
|
||||
strings.push(try!(decoder.read_character_data(false)));
|
||||
}
|
||||
Ok(TXT::new(strings))
|
||||
}
|
||||
@ -88,7 +88,7 @@ pub fn parse(tokens: &Vec<Token>) -> ParseResult<TXT> {
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let rdata = TXT::new(vec!["test me some".to_string(), "more please".to_string()]);
|
||||
let rdata = TXT::new(vec!["Test me some".to_string(), "more please".to_string()]);
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let mut encoder: BinEncoder = BinEncoder::new(&mut bytes);
|
||||
|
@ -31,7 +31,7 @@ use super::rdata::{ DNSKEY, DS, MX, NSEC, NSEC3, NSEC3PARAM, NULL, OPT, SIG, SOA
|
||||
|
||||
/// Record data enum variants
|
||||
///
|
||||
/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
|
||||
/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
|
||||
///
|
||||
/// ```text
|
||||
/// 3.3. Standard RRs
|
||||
|
@ -27,62 +27,40 @@ use super::record_type::RecordType;
|
||||
use super::dns_class::DNSClass;
|
||||
use super::domain;
|
||||
|
||||
/*
|
||||
* RFC 1035 Domain Implementation and Specification November 1987
|
||||
*
|
||||
* 4.1.3. Resource record format
|
||||
*
|
||||
* The answer, authority, and additional sections all share the same
|
||||
* format: a variable number of resource records, where the number of
|
||||
* records is specified in the corresponding count field in the header.
|
||||
* Each resource record has the following format:
|
||||
* 1 1 1 1 1 1
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* | |
|
||||
* / /
|
||||
* / NAME /
|
||||
* | |
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* | TYPE |
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* | CLASS |
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* | TTL |
|
||||
* | |
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
* | RDLENGTH |
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
* / RDATA /
|
||||
* / /
|
||||
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
*
|
||||
* where:
|
||||
*
|
||||
* NAME a domain name to which this resource record pertains.
|
||||
*
|
||||
* TYPE two octets containing one of the RR type codes. This
|
||||
* field specifies the meaning of the data in the RDATA
|
||||
* field.
|
||||
*
|
||||
* CLASS two octets which specify the class of the data in the
|
||||
* RDATA field.
|
||||
*
|
||||
* TTL a 32 bit unsigned integer that specifies the time
|
||||
* interval (in seconds) that the resource record may be
|
||||
* cached before it should be discarded. Zero values are
|
||||
* interpreted to mean that the RR can only be used for the
|
||||
* transaction in progress, and should not be cached.
|
||||
*
|
||||
* RDLENGTH an unsigned 16 bit integer that specifies the length in
|
||||
* octets of the RDATA field.
|
||||
*
|
||||
* RDATA a variable length string of octets that describes the
|
||||
* resource. The format of this information varies
|
||||
* according to the TYPE and CLASS of the resource record.
|
||||
* For example, the if the TYPE is A and the CLASS is IN,
|
||||
* the RDATA field is a 4 octet ARPA Internet address.
|
||||
*/
|
||||
|
||||
/// Resource records are storage value in DNS, into which all key/value pair data is stored.
|
||||
///
|
||||
/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
|
||||
///
|
||||
/// ```text
|
||||
/// 4.1.3. Resource record format
|
||||
///
|
||||
/// The answer, authority, and additional sections all share the same
|
||||
/// format: a variable number of resource records, where the number of
|
||||
/// records is specified in the corresponding count field in the header.
|
||||
/// Each resource record has the following format:
|
||||
/// 1 1 1 1 1 1
|
||||
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
/// | |
|
||||
/// / /
|
||||
/// / NAME /
|
||||
/// | |
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
/// | TYPE |
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
/// | CLASS |
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
/// | TTL |
|
||||
/// | |
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
/// | RDLENGTH |
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
/// / RDATA /
|
||||
/// / /
|
||||
/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
///
|
||||
/// ```
|
||||
#[derive(Eq, Ord, Debug, Clone)]
|
||||
pub struct Record {
|
||||
name_labels: domain::Name,
|
||||
@ -125,11 +103,41 @@ impl Record {
|
||||
}
|
||||
}
|
||||
|
||||
/// ```text
|
||||
/// NAME a domain name to which this resource record pertains.
|
||||
/// ```
|
||||
pub fn name(&mut self, name: domain::Name) -> &mut Self { self.name_labels = name; self }
|
||||
pub fn add_name(&mut self, label: String) -> &mut Self { self.name_labels.add_label(Rc::new(label)); self }
|
||||
|
||||
/// ```text
|
||||
/// TYPE two octets containing one of the RR type codes. This
|
||||
/// field specifies the meaning of the data in the RDATA
|
||||
/// field.
|
||||
/// ```
|
||||
pub fn rr_type(&mut self, rr_type: RecordType) -> &mut Self { self.rr_type = rr_type; self }
|
||||
|
||||
/// ```text
|
||||
/// CLASS two octets which specify the class of the data in the
|
||||
/// RDATA field.
|
||||
/// ```
|
||||
pub fn dns_class(&mut self, dns_class: DNSClass) -> &mut Self { self.dns_class = dns_class; self }
|
||||
|
||||
/// ```text
|
||||
/// TTL a 32 bit unsigned integer that specifies the time
|
||||
/// interval (in seconds) that the resource record may be
|
||||
/// cached before it should be discarded. Zero values are
|
||||
/// interpreted to mean that the RR can only be used for the
|
||||
/// transaction in progress, and should not be cached.
|
||||
/// ```
|
||||
pub fn ttl(&mut self, ttl: u32) -> &mut Self { self.ttl = ttl; self }
|
||||
|
||||
/// ```text
|
||||
/// RDATA a variable length string of octets that describes the
|
||||
/// resource. The format of this information varies
|
||||
/// according to the TYPE and CLASS of the resource record.
|
||||
/// For example, the if the TYPE is A and the CLASS is IN,
|
||||
/// the RDATA field is a 4 octet ARPA Internet address.
|
||||
/// ```
|
||||
pub fn rdata(&mut self, rdata: RData) -> &mut Self { self.rdata = rdata; self }
|
||||
|
||||
pub fn get_name(&self) -> &domain::Name { &self.name_labels }
|
||||
@ -222,26 +230,29 @@ impl BinSerializable<Record> for Record {
|
||||
}
|
||||
}
|
||||
|
||||
/// RFC 2136 DNS Update April 1997
|
||||
///
|
||||
/// 1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
|
||||
/// RDLENGTH and RDATA fields are equal. Note that the time-to-live
|
||||
/// (TTL) field is explicitly excluded from the comparison.
|
||||
///
|
||||
/// 1.1.2. The rules for comparison of character strings in names are
|
||||
/// specified in [RFC1035 2.3.3]. i.e. case insensitive
|
||||
impl PartialEq for Record {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// self == other && // the same pointer
|
||||
self.name_labels == other.name_labels &&
|
||||
self.rr_type == other.rr_type &&
|
||||
self.dns_class == other.dns_class &&
|
||||
self.rdata == other.rdata
|
||||
}
|
||||
/// Equality or records, as defined by
|
||||
/// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
|
||||
///
|
||||
/// ```text
|
||||
/// 1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
|
||||
/// RDLENGTH and RDATA fields are equal. Note that the time-to-live
|
||||
/// (TTL) field is explicitly excluded from the comparison.
|
||||
///
|
||||
/// 1.1.2. The rules for comparison of character strings in names are
|
||||
/// specified in [RFC1035 2.3.3]. i.e. case insensitive
|
||||
/// ```
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// self == other && // the same pointer
|
||||
self.name_labels == other.name_labels &&
|
||||
self.rr_type == other.rr_type &&
|
||||
self.dns_class == other.dns_class &&
|
||||
self.rdata == other.rdata
|
||||
}
|
||||
|
||||
fn ne(&self, other: &Self) -> bool {
|
||||
!self.eq(other)
|
||||
}
|
||||
fn ne(&self, other: &Self) -> bool {
|
||||
!self.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the value of the compare if the items are greater or lesser, but coninues on equal
|
||||
@ -256,32 +267,35 @@ macro_rules! compare_or_equal {
|
||||
}
|
||||
|
||||
impl PartialOrd<Record> for Record {
|
||||
// RFC 4034 DNSSEC Resource Records March 2005
|
||||
//
|
||||
// 6.2. Canonical RR Form
|
||||
//
|
||||
// For the purposes of DNS security, the canonical form of an RR is the
|
||||
// wire format of the RR where:
|
||||
//
|
||||
// 1. every domain name in the RR is fully expanded (no DNS name
|
||||
// compression) and fully qualified;
|
||||
//
|
||||
// 2. all uppercase US-ASCII letters in the owner name of the RR are
|
||||
// replaced by the corresponding lowercase US-ASCII letters;
|
||||
//
|
||||
// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
|
||||
// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
|
||||
// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
|
||||
// the DNS names contained within the RDATA are replaced by the
|
||||
// corresponding lowercase US-ASCII letters;
|
||||
//
|
||||
// 4. if the owner name of the RR is a wildcard name, the owner name is
|
||||
// in its original unexpanded form, including the "*" label (no
|
||||
// wildcard substitution); and
|
||||
//
|
||||
// 5. the RR's TTL is set to its original value as it appears in the
|
||||
// originating authoritative zone or the Original TTL field of the
|
||||
// covering RRSIG RR.
|
||||
/// Canonical ordering as defined by
|
||||
/// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
|
||||
///
|
||||
/// ```text
|
||||
/// 6.2. Canonical RR Form
|
||||
///
|
||||
/// For the purposes of DNS security, the canonical form of an RR is the
|
||||
/// wire format of the RR where:
|
||||
///
|
||||
/// 1. every domain name in the RR is fully expanded (no DNS name
|
||||
/// compression) and fully qualified;
|
||||
///
|
||||
/// 2. all uppercase US-ASCII letters in the owner name of the RR are
|
||||
/// replaced by the corresponding lowercase US-ASCII letters;
|
||||
///
|
||||
/// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
|
||||
/// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
|
||||
/// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
|
||||
/// the DNS names contained within the RDATA are replaced by the
|
||||
/// corresponding lowercase US-ASCII letters;
|
||||
///
|
||||
/// 4. if the owner name of the RR is a wildcard name, the owner name is
|
||||
/// in its original unexpanded form, including the "*" label (no
|
||||
/// wildcard substitution); and
|
||||
///
|
||||
/// 5. the RR's TTL is set to its original value as it appears in the
|
||||
/// originating authoritative zone or the Original TTL field of the
|
||||
/// covering RRSIG RR.
|
||||
/// ```
|
||||
fn partial_cmp(&self, other: &Record) -> Option<Ordering> {
|
||||
// TODO: given that the ordering of Resource Records is dependent on it's binary form and this
|
||||
// method will be used during insertion sort or similar, we should probably do this
|
||||
|
@ -28,7 +28,7 @@ fn get_character_data() -> Vec<(String, Vec<u8>)> {
|
||||
|
||||
#[test]
|
||||
fn read_character_data() {
|
||||
test_read_data_set(get_character_data(), |mut d| d.read_character_data());
|
||||
test_read_data_set(get_character_data(), |mut d| d.read_character_data(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -73,14 +73,19 @@ impl<'a> BinDecoder<'a> {
|
||||
/// length (including the length octet).
|
||||
///
|
||||
/// the vector should be reversed before calling.
|
||||
pub fn read_character_data(&mut self) -> DecodeResult<String> {
|
||||
pub fn read_character_data(&mut self, lowercase: bool) -> DecodeResult<String> {
|
||||
let length: u8 = try!(self.pop());
|
||||
|
||||
// TODO once Drain stabalizes on Vec, this should be replaced...
|
||||
let label_vec: Vec<u8> = try!(self.read_vec(length as usize));
|
||||
|
||||
// translate bytes to string, then lowercase...
|
||||
Ok(try!(String::from_utf8(label_vec)).to_lowercase())
|
||||
let data = try!(String::from_utf8(label_vec));
|
||||
if lowercase {
|
||||
Ok(data.to_lowercase())
|
||||
} else {
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_vec(&mut self, len: usize) -> DecodeResult<Vec<u8>> {
|
||||
|
@ -141,6 +141,7 @@ impl Parser {
|
||||
Self::new().parse(lexer, origin, zone_type, allow_update)
|
||||
}
|
||||
|
||||
// TODO: change this function to load into an Authority, using the update_records() method
|
||||
pub fn parse(&mut self, lexer: Lexer, origin: Option<Name>, zone_type: ZoneType, allow_update: bool) -> ParseResult<Authority> {
|
||||
let mut lexer = lexer;
|
||||
let mut records: BTreeMap<RrKey, RRSet> = BTreeMap::new();
|
||||
|
@ -1,3 +1,10 @@
|
||||
// Copyright 2015-2016 Benjamin Fry
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use std::char;
|
||||
|
@ -338,29 +338,18 @@ mod server_tests {
|
||||
use ::udp::UdpClientConnection;
|
||||
#[cfg(feature = "ftest")]
|
||||
use ::tcp::TcpClientConnection;
|
||||
#[cfg(feature = "ftest")]
|
||||
use mio::tcp::TcpListener;
|
||||
|
||||
#[test]
|
||||
fn test_server_www_udp() {
|
||||
// use log::LogLevel;
|
||||
// use ::logger;
|
||||
// logger::TrustDnsLogger::enable_logging(LogLevel::Debug);
|
||||
|
||||
let example = create_example();
|
||||
let origin = example.get_origin().clone();
|
||||
|
||||
let mut catalog: Catalog = Catalog::new();
|
||||
catalog.upsert(origin.clone(), example);
|
||||
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), 0));
|
||||
let udp_socket = UdpSocket::bound(&addr).unwrap();
|
||||
|
||||
let ipaddr = udp_socket.local_addr().unwrap();
|
||||
println!("udp_socket on port: {}", ipaddr);
|
||||
|
||||
let mut server = Server::new(catalog);
|
||||
server.register_socket(udp_socket);
|
||||
|
||||
/*let server_thread = */thread::Builder::new().name("test_server:udp:server".to_string()).spawn(move || server_thread(server)).unwrap();
|
||||
thread::Builder::new().name("test_server:udp:server".to_string()).spawn(move || server_thread_udp(udp_socket)).unwrap();
|
||||
|
||||
let client_conn = UdpClientConnection::new(ipaddr).unwrap();
|
||||
let client_thread = thread::Builder::new().name("test_server:udp:client".to_string()).spawn(move || client_thread_www(client_conn)).unwrap();
|
||||
@ -377,26 +366,13 @@ mod server_tests {
|
||||
fn test_server_www_tcp() {
|
||||
use mio::tcp::TcpListener;
|
||||
|
||||
// use log::LogLevel;
|
||||
// use ::logger;
|
||||
// logger::TrustDnsLogger::enable_logging(LogLevel::Debug);
|
||||
|
||||
let example = create_example();
|
||||
let origin = example.get_origin().clone();
|
||||
|
||||
let mut catalog: Catalog = Catalog::new();
|
||||
catalog.upsert(origin.clone(), example);
|
||||
|
||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), 0));
|
||||
let tcp_listener = TcpListener::bind(&addr).unwrap();
|
||||
|
||||
let ipaddr = tcp_listener.local_addr().unwrap();
|
||||
println!("tcp_listner on port: {}", ipaddr);
|
||||
|
||||
let mut server = Server::new(catalog);
|
||||
server.register_listener(tcp_listener);
|
||||
|
||||
/*let server_thread = */thread::Builder::new().name("test_server:tcp:server".to_string()).spawn(move || server_thread(server)).unwrap();
|
||||
thread::Builder::new().name("test_server:tcp:server".to_string()).spawn(move || server_thread_tcp(tcp_listener)).unwrap();
|
||||
|
||||
let client_conn = TcpClientConnection::new(ipaddr).unwrap();
|
||||
let client_thread = thread::Builder::new().name("test_server:tcp:client".to_string()).spawn(move || client_thread_www(client_conn)).unwrap();
|
||||
@ -439,7 +415,30 @@ mod server_tests {
|
||||
assert_eq!(ns.last().unwrap().get_rdata(), &RData::NS(Name::parse("b.iana-servers.net.", None).unwrap()) );
|
||||
}
|
||||
|
||||
fn server_thread(mut server: Server) {
|
||||
fn new_catalog() -> Catalog {
|
||||
let example = create_example();
|
||||
let origin = example.get_origin().clone();
|
||||
|
||||
let mut catalog: Catalog = Catalog::new();
|
||||
catalog.upsert(origin.clone(), example);
|
||||
catalog
|
||||
}
|
||||
|
||||
fn server_thread_udp(udp_socket: UdpSocket) {
|
||||
let catalog = new_catalog();
|
||||
|
||||
let mut server = Server::new(catalog);
|
||||
server.register_socket(udp_socket);
|
||||
|
||||
server.listen().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ftest")]
|
||||
fn server_thread_tcp(tcp_listener: TcpListener) {
|
||||
let catalog = new_catalog();
|
||||
let mut server = Server::new(catalog);
|
||||
server.register_listener(tcp_listener);
|
||||
|
||||
server.listen().unwrap();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user