DNSSEC resolver, record encoding/decoding
Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
parent
3e4ef61edb
commit
3797ca869d
|
@ -25,13 +25,20 @@ crate_type = [ "lib", "cdylib", "staticlib" ]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# please keep these in alphabetical order
|
# please keep these in alphabetical order
|
||||||
|
|
||||||
|
# base85 repo has no tags, fix on a commit. v1.1.1 points to older, wrong version
|
||||||
|
base85 = { git = "https://gitlab.com/darkwyrm/base85", rev = "d98efbfd171dd9ba48e30a5c88f94db92fc7b3c6" }
|
||||||
futures = { version = "^0.3" }
|
futures = { version = "^0.3" }
|
||||||
|
num-traits = { version = "^0.2" }
|
||||||
|
num-derive = { version = "^0.3" }
|
||||||
|
strum = { version = "^0.24" }
|
||||||
|
strum_macros = { version = "^0.24" }
|
||||||
thiserror = { version = "^1.0" }
|
thiserror = { version = "^1.0" }
|
||||||
tokio = { version = "^1", features = ["full"] }
|
tokio = { version = "^1", features = ["full"] }
|
||||||
# PERF: todo linux-only, behind "iouring" feature
|
# PERF: todo linux-only, behind "iouring" feature
|
||||||
#tokio-uring = { version = "^0.4" }
|
#tokio-uring = { version = "^0.4" }
|
||||||
tracing = { version = "^0.1" }
|
tracing = { version = "^0.1" }
|
||||||
trust-dns-client = { version = "^0.22" }
|
trust-dns-resolver = { version = "^0.22", features = [ "dnssec-ring" ] }
|
||||||
|
trust-dns-client = { version = "^0.22", features = [ "dnssec" ] }
|
||||||
trust-dns-proto = { version = "^0.22" }
|
trust-dns-proto = { version = "^0.22" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
//! Common dnssec utils
|
||||||
|
|
||||||
|
use ::std::{net::SocketAddr, vec::Vec};
|
||||||
|
use ::tracing::error;
|
||||||
|
use ::trust_dns_resolver::TokioAsyncResolver;
|
||||||
|
|
||||||
|
pub mod record;
|
||||||
|
pub use record::Record;
|
||||||
|
|
||||||
|
/// Common errors for Dnssec setup and usage
|
||||||
|
#[derive(::thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Error reading or parsing /etc/resolv.conf
|
||||||
|
#[error("resolv.conf: {0:?}")]
|
||||||
|
ResolvConf(String),
|
||||||
|
/// Error in the resolver setup
|
||||||
|
#[error("resolver setup: {0:?}")]
|
||||||
|
Setup(String),
|
||||||
|
/// Errors in establishing connection or connection drops
|
||||||
|
#[error("nameserver connection: {0:?}")]
|
||||||
|
NameserverConnection(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
))]
|
||||||
|
|
||||||
|
/// Easy Dnssec handling for Fenrir
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Dnssec {
|
||||||
|
/// real resolver
|
||||||
|
resolver: TokioAsyncResolver,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dnssec {
|
||||||
|
/// Spawn connections to DNS via TCP
|
||||||
|
pub async fn new(resolvers: &Vec<SocketAddr>) -> Result<Self, Error> {
|
||||||
|
// use a TCP connection to the DNS.
|
||||||
|
// the records we need are big, will not fit in a UDP packet
|
||||||
|
let resolv_conf_resolvers: Vec<SocketAddr>;
|
||||||
|
let resolvers = if resolvers.len() > 0 {
|
||||||
|
resolvers
|
||||||
|
} else {
|
||||||
|
// load from system's /etc/resolv.conf
|
||||||
|
let resolv_conf =
|
||||||
|
match ::trust_dns_resolver::system_conf::read_system_conf() {
|
||||||
|
Ok((resolv_conf, _)) => resolv_conf,
|
||||||
|
Err(e) => return Err(Error::ResolvConf(e.to_string())),
|
||||||
|
};
|
||||||
|
resolv_conf_resolvers = resolv_conf
|
||||||
|
.name_servers()
|
||||||
|
.iter()
|
||||||
|
.map(|r| r.socket_addr)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
&resolv_conf_resolvers
|
||||||
|
};
|
||||||
|
use ::trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
|
||||||
|
let mut opts = ResolverOpts::default();
|
||||||
|
opts.edns0 = true;
|
||||||
|
opts.validate = true;
|
||||||
|
opts.use_hosts_file = false;
|
||||||
|
opts.try_tcp_on_error = true;
|
||||||
|
let opts = opts; // no more mut
|
||||||
|
|
||||||
|
// default() uses google's dns: don't use
|
||||||
|
let mut config = ResolverConfig::new();
|
||||||
|
|
||||||
|
for resolver in resolvers.iter() {
|
||||||
|
use ::trust_dns_resolver::config::{NameServerConfig, Protocol};
|
||||||
|
config.add_name_server(NameServerConfig::new(
|
||||||
|
resolver.clone(),
|
||||||
|
Protocol::Tcp,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolver = match TokioAsyncResolver::tokio(config, opts) {
|
||||||
|
Ok(resolver) => resolver,
|
||||||
|
Err(e) => return Err(Error::Setup(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { resolver })
|
||||||
|
}
|
||||||
|
/// Get the fenrir data for a domain
|
||||||
|
pub async fn resolv(&self, domain: &str) -> ::std::io::Result<Record> {
|
||||||
|
use ::trust_dns_client::rr::Name;
|
||||||
|
|
||||||
|
let fqdn_str = "_fenrir.".to_owned() + domain;
|
||||||
|
::tracing::debug!("Resolving: {}", fqdn_str);
|
||||||
|
let fqdn = Name::from_utf8(&fqdn_str)?;
|
||||||
|
let answers = self.resolver.txt_lookup(fqdn).await?;
|
||||||
|
const TXT_RECORD_START: &str = "v=Fenrir1 ";
|
||||||
|
let mut found_but_wrong: bool = false;
|
||||||
|
for txt_raw in answers.into_iter() {
|
||||||
|
let txt = ::std::format!("{}", txt_raw);
|
||||||
|
if !txt.starts_with(TXT_RECORD_START) {
|
||||||
|
::tracing::trace!("Found NON-fenrir record: {}", txt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
::tracing::trace!("Found fenrir record: {}", txt);
|
||||||
|
let base85 = txt[TXT_RECORD_START.len()..].trim();
|
||||||
|
let bytes = match ::base85::decode(base85) {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(e) => {
|
||||||
|
::tracing::error!("Invalid DNSSEC base85: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let record = match Record::decode(&bytes) {
|
||||||
|
Ok(record) => record,
|
||||||
|
Err(e) => {
|
||||||
|
found_but_wrong = true;
|
||||||
|
::tracing::error!("{}: {}", fqdn_str, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Ok(record);
|
||||||
|
}
|
||||||
|
return Err(if found_but_wrong {
|
||||||
|
::std::io::Error::new(
|
||||||
|
::std::io::ErrorKind::InvalidData,
|
||||||
|
"record found but not parsable",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
::std::io::Error::new(
|
||||||
|
::std::io::ErrorKind::NotFound,
|
||||||
|
"record not found",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,417 @@
|
||||||
|
//!
|
||||||
|
//! Structs and information to create/parse the _fenrir DNSSEC record
|
||||||
|
//!
|
||||||
|
//! Encoding and decoding in base85, RFC1924
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! Basic encoding idea:
|
||||||
|
//! * 1 byte: half-bytes
|
||||||
|
//! * half: num of addresses
|
||||||
|
//! * half: num of pubkeys
|
||||||
|
//! [ # list of addresses
|
||||||
|
//! * 1 byte: bitfield
|
||||||
|
//! * 0..1 ipv4/ipv6
|
||||||
|
//! * 2..4 priority (for failover)
|
||||||
|
//! * 5..7 weight between priority
|
||||||
|
//! * 1 byte: public key id
|
||||||
|
//! * 2 bytes: UDP port
|
||||||
|
//! * X bytes: IP
|
||||||
|
//! ]
|
||||||
|
//! [ # list of pubkeys
|
||||||
|
//! * 1 byte: pubkey type
|
||||||
|
//! * 1 byte: pubkey id
|
||||||
|
//! * Y bytes: pubkey
|
||||||
|
//! ]
|
||||||
|
|
||||||
|
use ::core::num::NonZeroU16;
|
||||||
|
use ::num_traits::FromPrimitive;
|
||||||
|
use ::std::{net::IpAddr, vec::Vec};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public key data
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Public Key ID
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct PublicKeyId(u8);
|
||||||
|
|
||||||
|
/// Public Key Type
|
||||||
|
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
|
||||||
|
// public enum: use non_exhaustive to force users to add a default case
|
||||||
|
// so in the future we can expand this easily
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum PublicKeyType {
|
||||||
|
/// ed25519 asymmetric key
|
||||||
|
Ed25519 = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicKeyType {
|
||||||
|
/// Get the size of a public key of this kind
|
||||||
|
pub fn key_len(&self) -> usize {
|
||||||
|
match &self {
|
||||||
|
PublicKeyType::Ed25519 => 42,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public Key, with its type and id
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PublicKey {
|
||||||
|
/// public key raw data
|
||||||
|
raw: Vec<u8>,
|
||||||
|
/// type of public key
|
||||||
|
kind: PublicKeyType,
|
||||||
|
/// id of public key
|
||||||
|
id: PublicKeyId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicKey {
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
let size = 2; // Public Key Type + ID
|
||||||
|
size + self.raw.len()
|
||||||
|
}
|
||||||
|
fn encode_into(&self, raw: &mut Vec<u8>) {
|
||||||
|
raw.push(self.kind as u8);
|
||||||
|
raw.push(self.id.0);
|
||||||
|
raw.extend_from_slice(&self.raw);
|
||||||
|
}
|
||||||
|
fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> {
|
||||||
|
if raw.len() < 4 {
|
||||||
|
return Err(Error::NotEnoughData(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let kind = PublicKeyType::from_u8(raw[0]).unwrap();
|
||||||
|
let id = PublicKeyId(raw[1]);
|
||||||
|
if raw.len() < 2 + kind.key_len() {
|
||||||
|
return Err(Error::NotEnoughData(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut raw_key = Vec::with_capacity(kind.key_len());
|
||||||
|
raw_key.extend_from_slice(&raw[2..(2 + kind.key_len())]);
|
||||||
|
|
||||||
|
let total_length = 2 + kind.key_len();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Self {
|
||||||
|
raw: raw_key,
|
||||||
|
kind,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
total_length,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Address data
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Priority of each group of addresses
|
||||||
|
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
|
||||||
|
// public enum: use non_exhaustive to force users to add a default case
|
||||||
|
// so in the future we can expand this easily
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AddressPriority {
|
||||||
|
/// Initially contact addresses in this priority
|
||||||
|
P0 = 0,
|
||||||
|
/// First failover
|
||||||
|
P1,
|
||||||
|
/// Second failover
|
||||||
|
P2,
|
||||||
|
/// Third failover
|
||||||
|
P3,
|
||||||
|
/// Fourth failover
|
||||||
|
P4,
|
||||||
|
/// Fifth failover
|
||||||
|
P5,
|
||||||
|
/// Sisth failover
|
||||||
|
P6,
|
||||||
|
/// Seventh failover
|
||||||
|
P7,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inside of each group, weight of the address
|
||||||
|
/// This helps in distributing the traffic to multiple authentication servers:
|
||||||
|
/// * client sums all weights in a group
|
||||||
|
/// * generate a random number [0..sum_of_weights]
|
||||||
|
/// * the number indicates which server will take the connection
|
||||||
|
/// So to equally distribute all connections you just have to use the same
|
||||||
|
/// weight in the same group
|
||||||
|
#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)]
|
||||||
|
// public enum: use non_exhaustive to force users to add a default case
|
||||||
|
// so in the future we can expand this easily
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AddressWeight {
|
||||||
|
/// Minimum weigth: 1
|
||||||
|
W1 = 0,
|
||||||
|
/// little weigth: 2
|
||||||
|
W2,
|
||||||
|
/// little weigth: 3
|
||||||
|
W3,
|
||||||
|
/// medium weigth: 4
|
||||||
|
W4,
|
||||||
|
/// medium weigth: 5
|
||||||
|
W5,
|
||||||
|
/// heavy weigth: 6
|
||||||
|
W6,
|
||||||
|
/// heavy weigth: 7
|
||||||
|
W7,
|
||||||
|
/// Maximum weigth: 8
|
||||||
|
W8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authentication server address information:
|
||||||
|
/// * ip
|
||||||
|
/// * udp port
|
||||||
|
/// * priority
|
||||||
|
/// * weight within priority
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Address {
|
||||||
|
/// Ip address of server, v4 or v6
|
||||||
|
pub ip: IpAddr,
|
||||||
|
/// udp port. None means that this address is reachable only
|
||||||
|
/// with Fenrir over IP
|
||||||
|
pub port: Option<NonZeroU16>,
|
||||||
|
/// Priority group of this address
|
||||||
|
pub priority: AddressPriority,
|
||||||
|
/// Weight of this address in the priority group
|
||||||
|
pub weight: AddressWeight,
|
||||||
|
/// Public key ID used by this address
|
||||||
|
pub public_key_id: PublicKeyId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Address {
|
||||||
|
fn raw_len(&self) -> usize {
|
||||||
|
let size = 4; // UDP port + Priority + Weight
|
||||||
|
match self.ip {
|
||||||
|
IpAddr::V4(_) => size + 4,
|
||||||
|
IpAddr::V6(_) => size + 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn encode_into(&self, raw: &mut Vec<u8>) {
|
||||||
|
let mut bitfield: u8 = match self.ip {
|
||||||
|
IpAddr::V4(_) => 0,
|
||||||
|
IpAddr::V6(_) => 1,
|
||||||
|
};
|
||||||
|
bitfield <<= 3;
|
||||||
|
bitfield |= self.priority as u8;
|
||||||
|
bitfield <<= 3;
|
||||||
|
bitfield |= self.weight as u8;
|
||||||
|
|
||||||
|
raw.push(bitfield);
|
||||||
|
|
||||||
|
raw.push(self.public_key_id.0);
|
||||||
|
raw.extend_from_slice(
|
||||||
|
&(match self.port {
|
||||||
|
Some(port) => port.get().to_le_bytes(),
|
||||||
|
None => [0, 0], // oh noez, which zero goes first?
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.ip {
|
||||||
|
IpAddr::V4(ip) => {
|
||||||
|
let raw_ip = ip.octets();
|
||||||
|
raw.extend_from_slice(&raw_ip);
|
||||||
|
}
|
||||||
|
IpAddr::V6(ip) => {
|
||||||
|
let raw_ip = ip.octets();
|
||||||
|
raw.extend_from_slice(&raw_ip);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn decode_raw(raw: &[u8]) -> Result<(Self, usize), Error> {
|
||||||
|
if raw.len() < 8 {
|
||||||
|
return Err(Error::NotEnoughData(0));
|
||||||
|
}
|
||||||
|
let ip_type = raw[0] >> 6;
|
||||||
|
let is_ipv6: bool;
|
||||||
|
let total_length: usize;
|
||||||
|
match ip_type {
|
||||||
|
0 => {
|
||||||
|
is_ipv6 = false;
|
||||||
|
total_length = 8;
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
total_length = 20;
|
||||||
|
if raw.len() < total_length {
|
||||||
|
return Err(Error::NotEnoughData(1));
|
||||||
|
}
|
||||||
|
is_ipv6 = true
|
||||||
|
}
|
||||||
|
_ => return Err(Error::UnsupportedData(0)),
|
||||||
|
}
|
||||||
|
let raw_priority = (raw[0] << 2) >> 5;
|
||||||
|
let raw_weight = (raw[0] << 5) >> 5;
|
||||||
|
let priority = AddressPriority::from_u8(raw_priority).unwrap();
|
||||||
|
let weight = AddressWeight::from_u8(raw_weight).unwrap();
|
||||||
|
|
||||||
|
let public_key_id = PublicKeyId(raw[1]);
|
||||||
|
|
||||||
|
let raw_port = u16::from_le_bytes(raw[2..3].try_into().unwrap());
|
||||||
|
let port = if raw_port == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(NonZeroU16::new(raw_port).unwrap())
|
||||||
|
};
|
||||||
|
let ip = if is_ipv6 {
|
||||||
|
let raw_ip: [u8; 16] = raw[4..20].try_into().unwrap();
|
||||||
|
IpAddr::from(raw_ip)
|
||||||
|
} else {
|
||||||
|
let raw_ip: [u8; 4] = raw[4..8].try_into().unwrap();
|
||||||
|
IpAddr::from(raw_ip)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Self {
|
||||||
|
ip,
|
||||||
|
port,
|
||||||
|
priority,
|
||||||
|
weight,
|
||||||
|
public_key_id,
|
||||||
|
},
|
||||||
|
total_length,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Actual record puuting it all toghether
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// All informations found in the DNSSEC record
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Record {
|
||||||
|
/// Public keys used by any authentication server
|
||||||
|
public_keys: Vec<PublicKey>,
|
||||||
|
/// List of all authentication servers' addresses.
|
||||||
|
/// Multiple ones can point to the same authentication server
|
||||||
|
addresses: Vec<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Record {
|
||||||
|
/// Simply encode all the record in base85
|
||||||
|
pub fn encode(&self) -> Result<String, Error> {
|
||||||
|
// check possible failure scenarios
|
||||||
|
if self.public_keys.len() == 0 {
|
||||||
|
return Err(Error::NoPublicKeyFound);
|
||||||
|
}
|
||||||
|
if self.public_keys.len() > 16 {
|
||||||
|
return Err(Error::Max16PublicKeys);
|
||||||
|
}
|
||||||
|
if self.addresses.len() == 0 {
|
||||||
|
return Err(Error::NoAddressFound);
|
||||||
|
}
|
||||||
|
if self.addresses.len() > 16 {
|
||||||
|
return Err(Error::Max16Addresses);
|
||||||
|
}
|
||||||
|
// everything else is all good
|
||||||
|
|
||||||
|
let total_size: usize = 1
|
||||||
|
+ self.addresses.iter().map(|a| a.raw_len()).sum::<usize>()
|
||||||
|
+ self.public_keys.iter().map(|a| a.raw_len()).sum::<usize>();
|
||||||
|
|
||||||
|
let mut raw = Vec::with_capacity(total_size);
|
||||||
|
|
||||||
|
// amount of data. addresses, then pubkeys. 4 bits each
|
||||||
|
let len_combined: u8 = self.addresses.len() as u8;
|
||||||
|
let len_combined = len_combined << 4;
|
||||||
|
let len_combined = len_combined | self.public_keys.len() as u8;
|
||||||
|
|
||||||
|
raw.push(len_combined);
|
||||||
|
|
||||||
|
for address in self.addresses.iter() {
|
||||||
|
address.encode_into(&mut raw);
|
||||||
|
}
|
||||||
|
for public_key in self.public_keys.iter() {
|
||||||
|
public_key.encode_into(&mut raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(::base85::encode(&raw))
|
||||||
|
}
|
||||||
|
/// Decode from base85 to the actual object
|
||||||
|
pub fn decode(raw: &[u8]) -> Result<Self, Error> {
|
||||||
|
// bare minimum for 1 address and key
|
||||||
|
const MIN_RAW_LENGTH: usize = 1 + 8 + 8;
|
||||||
|
if raw.len() <= MIN_RAW_LENGTH {
|
||||||
|
return Err(Error::NotEnoughData(0));
|
||||||
|
}
|
||||||
|
let mut num_addresses = (raw[0] >> 4) as usize;
|
||||||
|
let mut num_public_keys = (raw[0] & 0x0F) as usize;
|
||||||
|
let mut bytes_parsed = 1;
|
||||||
|
|
||||||
|
let mut result = Self {
|
||||||
|
addresses: Vec::with_capacity(num_addresses),
|
||||||
|
public_keys: Vec::with_capacity(num_public_keys),
|
||||||
|
};
|
||||||
|
|
||||||
|
while num_addresses > 0 {
|
||||||
|
let (address, bytes) =
|
||||||
|
match Address::decode_raw(&raw[bytes_parsed..]) {
|
||||||
|
Ok(address) => address,
|
||||||
|
Err(Error::UnsupportedData(b)) => {
|
||||||
|
return Err(Error::UnsupportedData(bytes_parsed + b))
|
||||||
|
}
|
||||||
|
Err(Error::NotEnoughData(b)) => {
|
||||||
|
return Err(Error::NotEnoughData(bytes_parsed + b))
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
bytes_parsed = bytes_parsed + bytes;
|
||||||
|
result.addresses.push(address);
|
||||||
|
num_addresses = num_addresses - 1;
|
||||||
|
}
|
||||||
|
while num_public_keys > 0 {
|
||||||
|
let (public_key, bytes) =
|
||||||
|
match PublicKey::decode_raw(&raw[bytes_parsed..]) {
|
||||||
|
Ok(public_key) => public_key,
|
||||||
|
Err(Error::UnsupportedData(b)) => {
|
||||||
|
return Err(Error::UnsupportedData(bytes_parsed + b))
|
||||||
|
}
|
||||||
|
Err(Error::NotEnoughData(b)) => {
|
||||||
|
return Err(Error::NotEnoughData(bytes_parsed + b))
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
bytes_parsed = bytes_parsed + bytes;
|
||||||
|
result.public_keys.push(public_key);
|
||||||
|
num_public_keys = num_public_keys - 1;
|
||||||
|
}
|
||||||
|
if bytes_parsed != raw.len() {
|
||||||
|
Err(Error::UnknownData(bytes_parsed))
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible errors in encoding or decoding the DNSSEC record
|
||||||
|
#[derive(::thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// General IO error
|
||||||
|
#[error("IO error: {0:?}")]
|
||||||
|
IO(#[from] ::std::io::Error),
|
||||||
|
/// We need at least one address
|
||||||
|
#[error("no addresses found")]
|
||||||
|
NoAddressFound,
|
||||||
|
/// Too many addresses (max 16)
|
||||||
|
#[error("can't encode more than 16 addresses")]
|
||||||
|
Max16Addresses,
|
||||||
|
/// We need at least one public key
|
||||||
|
#[error("no public keys found")]
|
||||||
|
NoPublicKeyFound,
|
||||||
|
/// Maximum 16 public keys supported
|
||||||
|
#[error("can't encode more than 16 public keys")]
|
||||||
|
Max16PublicKeys,
|
||||||
|
/// Not enough data to decode something meaningful
|
||||||
|
#[error("not enough data. Parsed {0} bytes")]
|
||||||
|
NotEnoughData(usize),
|
||||||
|
/// Unsupported Data: can't parse
|
||||||
|
#[error("Unsupported data. Parsed {0} bytes")]
|
||||||
|
UnsupportedData(usize),
|
||||||
|
/// Unknown data at the end
|
||||||
|
#[error("Unknown data after {0} bytes")]
|
||||||
|
UnknownData(usize),
|
||||||
|
}
|
153
src/lib.rs
153
src/lib.rs
|
@ -13,29 +13,36 @@
|
||||||
//!
|
//!
|
||||||
//! libFenrir is the official rust library implementing the Fenrir protocol
|
//! libFenrir is the official rust library implementing the Fenrir protocol
|
||||||
|
|
||||||
use ::std::{io::Result, net::SocketAddr, result, sync::Arc};
|
use ::std::{net::SocketAddr, sync::Arc};
|
||||||
use ::tokio::{net::UdpSocket, runtime, task::JoinHandle};
|
use ::tokio::{net::UdpSocket, task::JoinHandle};
|
||||||
use trust_dns_client::client::{
|
|
||||||
AsyncClient as DnssecClient, ClientHandle as DnssecHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
pub mod dnssec;
|
||||||
|
|
||||||
|
/// Main fenrir library errors
|
||||||
|
#[derive(::thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// General I/O error
|
||||||
|
#[error("IO: {0:?}")]
|
||||||
|
IO(#[from] ::std::io::Error),
|
||||||
|
/// Dnssec errors
|
||||||
|
#[error("Dnssec: {0:?}")]
|
||||||
|
Dnssec(#[from] dnssec::Error),
|
||||||
|
/// The library was not initialized (run .start())
|
||||||
|
#[error("not initialized")]
|
||||||
|
NotInitialized,
|
||||||
|
}
|
||||||
|
|
||||||
/// Instance of a fenrir endpoint
|
/// Instance of a fenrir endpoint
|
||||||
#[allow(missing_copy_implementations, missing_debug_implementations)]
|
#[allow(missing_copy_implementations, missing_debug_implementations)]
|
||||||
pub struct Fenrir {
|
pub struct Fenrir {
|
||||||
/// library Configuration
|
/// library Configuration
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
/// internal runtime
|
|
||||||
rt: ::tokio::runtime::Runtime,
|
|
||||||
/// listening udp sockets
|
/// listening udp sockets
|
||||||
sockets: Vec<(Arc<UdpSocket>, JoinHandle<Result<()>>)>,
|
sockets: Vec<(Arc<UdpSocket>, JoinHandle<::std::io::Result<()>>)>,
|
||||||
/// DNSSEC resolvers. multiple for failover
|
/// DNSSEC resolver, with failovers
|
||||||
resolvers: Vec<(
|
dnssec: Option<dnssec::Dnssec>,
|
||||||
DnssecClient,
|
|
||||||
JoinHandle<result::Result<(), ::trust_dns_proto::error::ProtoError>>,
|
|
||||||
)>,
|
|
||||||
/// Broadcast channel to tell workers to stop working
|
/// Broadcast channel to tell workers to stop working
|
||||||
stop_working: ::tokio::sync::broadcast::Sender<bool>,
|
stop_working: ::tokio::sync::broadcast::Sender<bool>,
|
||||||
}
|
}
|
||||||
|
@ -50,86 +57,56 @@ impl Drop for Fenrir {
|
||||||
|
|
||||||
impl Fenrir {
|
impl Fenrir {
|
||||||
/// Create a new Fenrir endpoint
|
/// Create a new Fenrir endpoint
|
||||||
pub fn new(config: &Config) -> Result<Self> {
|
pub fn new(config: &Config) -> Result<Self, Error> {
|
||||||
let mut builder = runtime::Builder::new_multi_thread();
|
|
||||||
if let Some(threads) = config.threads {
|
|
||||||
builder.worker_threads(threads.get());
|
|
||||||
}
|
|
||||||
let rt = match builder.thread_name("libFenrir").build() {
|
|
||||||
Ok(rt) => rt,
|
|
||||||
Err(e) => {
|
|
||||||
::tracing::error!("Can't initialize: {}", e);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// start listening
|
|
||||||
let listen_num = config.listen.len();
|
let listen_num = config.listen.len();
|
||||||
let resolvers_num = config.resolvers.len();
|
|
||||||
let (sender, _) = ::tokio::sync::broadcast::channel(1);
|
let (sender, _) = ::tokio::sync::broadcast::channel(1);
|
||||||
let endpoint = Fenrir {
|
let endpoint = Fenrir {
|
||||||
cfg: config.clone(),
|
cfg: config.clone(),
|
||||||
rt: rt,
|
|
||||||
sockets: Vec::with_capacity(listen_num),
|
sockets: Vec::with_capacity(listen_num),
|
||||||
resolvers: Vec::with_capacity(resolvers_num),
|
dnssec: None,
|
||||||
stop_working: sender,
|
stop_working: sender,
|
||||||
};
|
};
|
||||||
Ok(endpoint)
|
Ok(endpoint)
|
||||||
}
|
}
|
||||||
/// Start all workers, listeners
|
/// Start all workers, listeners
|
||||||
pub async fn start(&mut self) -> Result<()> {
|
pub async fn start(&mut self) -> Result<(), Error> {
|
||||||
if let Err(e) = self.add_sockets().await {
|
if let Err(e) = self.add_sockets().await {
|
||||||
self.stop().await;
|
self.stop().await;
|
||||||
return Err(e);
|
return Err(e.into());
|
||||||
}
|
|
||||||
if let Err(e) = self.setup_dnssec().await {
|
|
||||||
self.stop().await;
|
|
||||||
return Err(e);
|
|
||||||
}
|
}
|
||||||
|
self.dnssec = Some(dnssec::Dnssec::new(&self.cfg.resolvers).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Stop all workers, listeners
|
/// Stop all workers, listeners
|
||||||
/// asyncronous version for Drop
|
/// asyncronous version for Drop
|
||||||
fn stop_sync(&mut self) {
|
fn stop_sync(&mut self) {
|
||||||
let mut toempty_resolv = Vec::new();
|
|
||||||
let mut toempty_socket = Vec::new();
|
let mut toempty_socket = Vec::new();
|
||||||
::std::mem::swap(&mut self.resolvers, &mut toempty_resolv);
|
|
||||||
::std::mem::swap(&mut self.sockets, &mut toempty_socket);
|
::std::mem::swap(&mut self.sockets, &mut toempty_socket);
|
||||||
self.rt
|
let task = ::tokio::task::spawn(Self::real_stop(toempty_socket));
|
||||||
.block_on(Self::real_stop(toempty_socket, toempty_resolv));
|
let _ = futures::executor::block_on(task);
|
||||||
|
self.dnssec = None;
|
||||||
}
|
}
|
||||||
/// Stop all workers, listeners
|
/// Stop all workers, listeners
|
||||||
pub async fn stop(&mut self) {
|
pub async fn stop(&mut self) {
|
||||||
// TODO
|
|
||||||
let _ = self.stop_working.send(true);
|
let _ = self.stop_working.send(true);
|
||||||
let mut toempty_resolv = Vec::new();
|
|
||||||
let mut toempty_socket = Vec::new();
|
let mut toempty_socket = Vec::new();
|
||||||
::std::mem::swap(&mut self.resolvers, &mut toempty_resolv);
|
|
||||||
::std::mem::swap(&mut self.sockets, &mut toempty_socket);
|
::std::mem::swap(&mut self.sockets, &mut toempty_socket);
|
||||||
Self::real_stop(toempty_socket, toempty_resolv).await;
|
Self::real_stop(toempty_socket).await;
|
||||||
|
self.dnssec = None;
|
||||||
}
|
}
|
||||||
/// actually do the work of stopping resolvers and listeners
|
/// actually do the work of stopping resolvers and listeners
|
||||||
async fn real_stop(
|
async fn real_stop(
|
||||||
sockets: Vec<(Arc<UdpSocket>, JoinHandle<Result<()>>)>,
|
sockets: Vec<(Arc<UdpSocket>, JoinHandle<::std::io::Result<()>>)>,
|
||||||
resolvers: Vec<(
|
|
||||||
DnssecClient,
|
|
||||||
JoinHandle<
|
|
||||||
result::Result<(), ::trust_dns_proto::error::ProtoError>,
|
|
||||||
>,
|
|
||||||
)>,
|
|
||||||
) {
|
) {
|
||||||
for r in resolvers.into_iter() {
|
|
||||||
let _ = r.1.await;
|
|
||||||
}
|
|
||||||
for s in sockets.into_iter() {
|
for s in sockets.into_iter() {
|
||||||
let _ = s.1.await;
|
let _ = s.1.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Add all UDP sockets found in config
|
/// Add all UDP sockets found in config
|
||||||
/// and start listening for packets
|
/// and start listening for packets
|
||||||
async fn add_sockets(&mut self) -> Result<()> {
|
async fn add_sockets(&mut self) -> ::std::io::Result<()> {
|
||||||
let sockets = self.cfg.listen.iter().map(|s_addr| async {
|
let sockets = self.cfg.listen.iter().map(|s_addr| async {
|
||||||
//let socket = self.rt.block_on(bind_udp(s_addr.clone()))?;
|
let socket = ::tokio::spawn(bind_udp(s_addr.clone())).await??;
|
||||||
let socket = self.rt.spawn(bind_udp(s_addr.clone())).await??;
|
|
||||||
Ok(Arc::new(socket))
|
Ok(Arc::new(socket))
|
||||||
});
|
});
|
||||||
let sockets = ::futures::future::join_all(sockets).await;
|
let sockets = ::futures::future::join_all(sockets).await;
|
||||||
|
@ -138,7 +115,7 @@ impl Fenrir {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
let stop_working = self.stop_working.subscribe();
|
let stop_working = self.stop_working.subscribe();
|
||||||
let join =
|
let join =
|
||||||
self.rt.spawn(listen_udp(stop_working, s.clone()));
|
::tokio::spawn(listen_udp(stop_working, s.clone()));
|
||||||
self.sockets.push((s, join));
|
self.sockets.push((s, join));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -148,65 +125,17 @@ impl Fenrir {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Add and initialize the DNSSEC resolvers to the endpoint
|
/// Get the raw TXT record of a Fenrir domain
|
||||||
async fn setup_dnssec(&mut self) -> Result<()> {
|
pub async fn resolv(&self, domain: &str) -> Result<dnssec::Record, Error> {
|
||||||
// use a TCP connection to the DNS.
|
match &self.dnssec {
|
||||||
// the records we need are big, will not fit in a UDP packet
|
Some(dnssec) => Ok(dnssec.resolv(domain).await?),
|
||||||
use tokio::net::TcpStream;
|
None => Err(Error::NotInitialized),
|
||||||
use trust_dns_client::{
|
|
||||||
client::AsyncClient, proto::iocompat::AsyncIoTokioAsStd,
|
|
||||||
tcp::TcpClientStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
for resolver in self.cfg.resolvers.iter() {
|
|
||||||
let (stream, sender) = TcpClientStream::<
|
|
||||||
AsyncIoTokioAsStd<TcpStream>,
|
|
||||||
>::new(resolver.clone());
|
|
||||||
let client_tostart = AsyncClient::new(stream, sender, None);
|
|
||||||
// await the connection to be established
|
|
||||||
let (client, bg) = client_tostart.await?;
|
|
||||||
|
|
||||||
let handle = self.rt.spawn(bg);
|
|
||||||
self.resolvers.push((client, handle));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// get the fenrir data for a domain
|
|
||||||
pub async fn resolv(&mut self, domain: &str) -> Result<String> {
|
|
||||||
use std::str::FromStr;
|
|
||||||
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
|
||||||
|
|
||||||
let fullname = "_fenrir".to_owned() + domain;
|
|
||||||
for client in self.resolvers.iter_mut() {
|
|
||||||
let query = client.0.query(
|
|
||||||
Name::from_str(&fullname).unwrap(),
|
|
||||||
DNSClass::IN,
|
|
||||||
RecordType::TXT,
|
|
||||||
);
|
|
||||||
|
|
||||||
// wait for its response
|
|
||||||
let response = query.await?;
|
|
||||||
|
|
||||||
// validate it's what we expected
|
|
||||||
match response.answers()[0].data() {
|
|
||||||
Some(RData::TXT(text)) => return Ok(text.to_string()),
|
|
||||||
_ => {
|
|
||||||
return Err(::std::io::Error::new(
|
|
||||||
::std::io::ErrorKind::NotFound,
|
|
||||||
"No TXT record found",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(::std::io::Error::new(
|
|
||||||
::std::io::ErrorKind::NotConnected,
|
|
||||||
"No DNS server usable",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an async udp listener
|
/// Add an async udp listener
|
||||||
async fn bind_udp(sock: SocketAddr) -> Result<UdpSocket> {
|
async fn bind_udp(sock: SocketAddr) -> ::std::io::Result<UdpSocket> {
|
||||||
let socket = UdpSocket::bind(sock).await?;
|
let socket = UdpSocket::bind(sock).await?;
|
||||||
// PERF: SO_REUSEADDR/REUSEPORT
|
// PERF: SO_REUSEADDR/REUSEPORT
|
||||||
Ok(socket)
|
Ok(socket)
|
||||||
|
@ -216,7 +145,7 @@ async fn bind_udp(sock: SocketAddr) -> Result<UdpSocket> {
|
||||||
async fn listen_udp(
|
async fn listen_udp(
|
||||||
mut stop_working: ::tokio::sync::broadcast::Receiver<bool>,
|
mut stop_working: ::tokio::sync::broadcast::Receiver<bool>,
|
||||||
socket: Arc<UdpSocket>,
|
socket: Arc<UdpSocket>,
|
||||||
) -> Result<()> {
|
) -> ::std::io::Result<()> {
|
||||||
// jumbo frames are 9K max
|
// jumbo frames are 9K max
|
||||||
let mut buffer: [u8; 9000] = [0; 9000];
|
let mut buffer: [u8; 9000] = [0; 9000];
|
||||||
loop {
|
loop {
|
||||||
|
|
Loading…
Reference in New Issue