Client Transport (#215)

This PR adds experimental support for client transport of DNS messages.

It also adds a new concept of unstable features which gate code that can introduce breaking changes also in non-breaking releases.
This commit is contained in:
Philip-NLnetLabs
2024-01-15 14:54:20 +01:00
committed by GitHub
parent 6aa6e94ffd
commit b8d2aa59bf
30 changed files with 7828 additions and 469 deletions
+20 -5
View File
@@ -17,8 +17,9 @@ name = "domain"
path = "src/lib.rs"
[dependencies]
octseq = { version = "0.3.2", default-features = false }
time = { version = "0.3.1", default-features = false }
octseq = { version = "0.3.2", default-features = false }
pin-project-lite = "0.2"
time = { version = "0.3.1", default-features = false }
rand = { version = "0.8", optional = true }
bytes = { version = "1.0", optional = true, default-features = false }
@@ -30,7 +31,11 @@ ring = { version = "0.17", optional = true }
serde = { version = "1.0.130", optional = true, features = ["derive"] }
siphasher = { version = "1", optional = true }
smallvec = { version = "1", optional = true }
tokio = { version = "1.0", optional = true, features = ["io-util", "macros", "net", "time"] }
tokio = { version = "1.33", optional = true, features = ["io-util", "macros", "net", "time", "sync", "rt-multi-thread" ] }
tokio-rustls = { version = "0.24", optional = true, features = [] }
# XXX Force proc-macro2 to at least 1.0.69 for minimal-version build
proc-macro2 = "1.0.69"
[target.'cfg(macos)'.dependencies]
# specifying this overrides minimum-version mio's 0.2.69 libc dependency, which allows the build to work
@@ -41,24 +46,31 @@ default = ["std", "rand"]
bytes = ["dep:bytes", "octseq/bytes"]
heapless = ["dep:heapless", "octseq/heapless"]
interop = ["bytes", "ring"]
resolv = ["bytes", "futures-util", "smallvec", "std", "tokio", "libc", "rand"]
resolv = ["net", "smallvec", "std", "rand", "unstable-client-transport"]
resolv-sync = ["resolv", "tokio/rt"]
serde = ["dep:serde", "octseq/serde"]
sign = ["std"]
smallvec = ["dep:smallvec", "octseq/smallvec"]
std = ["bytes?/std", "octseq/std", "time/std"]
net = ["bytes", "futures-util", "std", "tokio", "tokio-rustls"]
tsig = ["bytes", "ring", "smallvec"]
validate = ["std", "ring"]
zonefile = ["bytes", "std"]
# Unstable features
unstable-client-transport = []
# This feature should include all features that the CI should include for a
# test run. Which is everything except interop.
ci-test = ["resolv", "resolv-sync", "sign", "std", "serde", "tsig", "validate", "zonefile"]
ci-test = ["net", "resolv", "resolv-sync", "sign", "std", "serde", "tsig", "validate", "zonefile"]
[dev-dependencies]
rustls = { version = "0.21.9" }
serde_test = "1.0.130"
serde_yaml = "0.9"
tokio = { version = "1", features = ["rt-multi-thread", "io-util", "net"] }
tokio-test = "0.4"
webpki-roots = { version = "0.25" }
[package.metadata.docs.rs]
all-features = true
@@ -84,3 +96,6 @@ required-features = ["resolv-sync"]
name = "client"
required-features = ["std", "rand"]
[[example]]
name = "client-transports"
required-features = ["net"]
+233
View File
@@ -0,0 +1,233 @@
/// Using the `domain::net::client` module for sending a query.
use domain::base::Dname;
use domain::base::MessageBuilder;
use domain::base::Rtype::Aaaa;
use domain::net::client::dgram;
use domain::net::client::dgram_stream;
use domain::net::client::multi_stream;
use domain::net::client::protocol::{TcpConnect, TlsConnect, UdpConnect};
use domain::net::client::redundant;
use domain::net::client::request::{RequestMessage, SendRequest};
use domain::net::client::stream;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::timeout;
use tokio_rustls::rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore};
#[tokio::main]
async fn main() {
// Create DNS request message.
//
// Transports currently take a `RequestMessage` as their input to be able
// to add options along the way.
//
// In the future, it will also be possible to pass in a message or message
// builder directly as input but for now it needs to be converted into a
// `RequestMessage` manually.
let mut msg = MessageBuilder::new_vec();
msg.header_mut().set_rd(true);
let mut msg = msg.question();
msg.push((Dname::vec_from_str("example.com").unwrap(), Aaaa))
.unwrap();
let req = RequestMessage::new(msg);
// Destination for UDP and TCP
let server_addr = SocketAddr::new(IpAddr::from_str("::1").unwrap(), 53);
let mut stream_config = stream::Config::new();
stream_config.set_response_timeout(Duration::from_millis(100));
let multi_stream_config =
multi_stream::Config::from(stream_config.clone());
// Create a new UDP+TCP transport connection. Pass the destination address
// and port as parameter.
let mut dgram_config = dgram::Config::new();
dgram_config.set_max_parallel(1);
dgram_config.set_read_timeout(Duration::from_millis(1000));
dgram_config.set_max_retries(1);
dgram_config.set_udp_payload_size(Some(1400));
let dgram_stream_config = dgram_stream::Config::from_parts(
dgram_config.clone(),
multi_stream_config.clone(),
);
let udp_connect = UdpConnect::new(server_addr);
let tcp_connect = TcpConnect::new(server_addr);
let (udptcp_conn, transport) = dgram_stream::Connection::with_config(
udp_connect,
tcp_connect,
dgram_stream_config,
);
// Start the run function in a separate task. The run function will
// terminate when all references to the connection have been dropped.
// Make sure that the task does not accidentally get a reference to the
// connection.
tokio::spawn(async move {
transport.run().await;
println!("UDP+TCP run exited");
});
// Send a query message.
let mut request = udptcp_conn.send_request(req.clone());
// Get the reply
println!("Wating for UDP+TCP reply");
let reply = request.get_response().await;
println!("UDP+TCP reply: {:?}", reply);
// The query may have a reference to the connection. Drop the query
// when it is no longer needed.
drop(request);
// Create a new TCP connections object. Pass the destination address and
// port as parameter.
let tcp_connect = TcpConnect::new(server_addr);
// A muli_stream transport connection sets up new TCP connections when
// needed.
let (tcp_conn, transport) = multi_stream::Connection::with_config(
tcp_connect,
multi_stream_config.clone(),
);
// Get a future for the run function. The run function receives
// the connection stream as a parameter.
tokio::spawn(async move {
transport.run().await;
println!("multi TCP run exited");
});
// Send a query message.
let mut request = tcp_conn.send_request(req.clone());
// Get the reply. A multi_stream connection does not have any timeout.
// Wrap get_result in a timeout.
println!("Wating for multi TCP reply");
let reply =
timeout(Duration::from_millis(500), request.get_response()).await;
println!("multi TCP reply: {:?}", reply);
drop(request);
// Some TLS boiler plate for the root certificates.
let mut root_store = RootCertStore::empty();
root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(
|ta| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
},
));
// TLS config
let client_config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
// Currently the only support TLS connections are the ones that have a
// valid certificate. Use a well known public resolver.
let google_server_addr =
SocketAddr::new(IpAddr::from_str("8.8.8.8").unwrap(), 853);
// Create a new TLS connections object. We pass the TLS config, the name of
// the remote server and the destination address and port.
let tls_connect = TlsConnect::new(
client_config,
"dns.google".try_into().unwrap(),
google_server_addr,
);
// Again create a multi_stream transport connection.
let (tls_conn, transport) = multi_stream::Connection::with_config(
tls_connect,
multi_stream_config,
);
// Start the run function.
tokio::spawn(async move {
transport.run().await;
println!("TLS run exited");
});
let mut request = tls_conn.send_request(req.clone());
println!("Wating for TLS reply");
let reply =
timeout(Duration::from_millis(500), request.get_response()).await;
println!("TLS reply: {:?}", reply);
drop(request);
// Create a transport connection for redundant connections.
let (redun, transp) = redundant::Connection::new();
// Start the run function on a separate task.
let run_fut = transp.run();
tokio::spawn(async move {
run_fut.await;
println!("redundant run terminated");
});
// Add the previously created transports.
redun.add(Box::new(udptcp_conn)).await.unwrap();
redun.add(Box::new(tcp_conn)).await.unwrap();
redun.add(Box::new(tls_conn)).await.unwrap();
// Start a few queries.
for i in 1..10 {
let mut request = redun.send_request(req.clone());
let reply = request.get_response().await;
if i == 2 {
println!("redundant connection reply: {:?}", reply);
}
}
drop(redun);
// Create a new datagram transport connection. Pass the destination address
// and port as parameter. This transport does not retry over TCP if the
// reply is truncated. This transport does not have a separate run
// function.
let udp_connect = UdpConnect::new(server_addr);
let dgram_conn =
dgram::Connection::with_config(udp_connect, dgram_config);
// Send a message.
let mut request = dgram_conn.send_request(req.clone());
//
// Get the reply
let reply = request.get_response().await;
println!("Dgram reply: {:?}", reply);
// Create a single TCP transport connection. This is usefull for a
// single request or a small burst of requests.
let tcp_conn = match TcpStream::connect(server_addr).await {
Ok(conn) => conn,
Err(err) => {
println!(
"TCP Connection to {} failed: {}, exiting",
server_addr, err
);
return;
}
};
let (tcp, transport) = stream::Connection::new(tcp_conn);
tokio::spawn(async move {
transport.run().await;
println!("single TCP run terminated");
});
// Send a request message.
let mut request = tcp.send_request(req);
// Get the reply
let reply = request.get_response().await;
println!("TCP reply: {:?}", reply);
drop(tcp);
}
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
start.elapsed().unwrap().as_secs_f32()
);
let mut i = 0;
while let Some(_) = zone.next_entry().unwrap() {
while zone.next_entry().unwrap().is_some() {
i += 1;
if i % 100_000_000 == 0 {
eprintln!(
+18
View File
@@ -171,6 +171,18 @@ impl<Octs> Message<Octs> {
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
/// Creates a message from octets, returning the octets if it fails.
pub fn try_from_octets(octets: Octs) -> Result<Self, Octs>
where
Octs: AsRef<[u8]>,
{
if Message::check_slice(octets.as_ref()).is_err() {
Err(octets)
} else {
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
}
/// Creates a message from a bytes value without checking.
///
/// # Safety
@@ -1194,6 +1206,12 @@ impl From<ParseError> for CopyRecordsError {
}
}
impl From<PushError> for CopyRecordsError {
fn from(err: PushError) -> Self {
CopyRecordsError::Push(err)
}
}
//--- Display and Error
impl fmt::Display for CopyRecordsError {
+66 -26
View File
@@ -300,14 +300,16 @@ impl<Target: OctetsBuilder + AsMut<[u8]>> MessageBuilder<Target> {
/// # Conversions
///
impl<Target: Composer> MessageBuilder<Target> {
impl<Target> MessageBuilder<Target> {
/// Converts the message builder into a message builder
///
/// This is a no-op.
pub fn builder(self) -> MessageBuilder<Target> {
self
}
}
impl<Target: Composer> MessageBuilder<Target> {
/// Converts the message builder into a question builder.
pub fn question(self) -> QuestionBuilder<Target> {
QuestionBuilder::new(self)
@@ -340,15 +342,14 @@ impl<Target: Composer> MessageBuilder<Target> {
pub fn finish(self) -> Target {
self.target
}
}
impl<Target: FreezeBuilder> MessageBuilder<Target> {
/// Converts the builder into a message.
///
/// The method will return a message atop whatever octets sequence the
/// builders octets builder converts into.
pub fn into_message(self) -> Message<<Target as FreezeBuilder>::Octets>
where
Target: FreezeBuilder,
{
pub fn into_message(self) -> Message<Target::Octets> {
unsafe { Message::from_octets_unchecked(self.target.freeze()) }
}
}
@@ -448,6 +449,15 @@ where
}
}
impl<Target> From<MessageBuilder<Target>> for Message<Target::Octets>
where
Target: FreezeBuilder,
{
fn from(src: MessageBuilder<Target>) -> Self {
src.into_message()
}
}
//--- AsRef
//
// XXX Should we deref down to target?
@@ -554,14 +564,16 @@ impl<Target: Composer> QuestionBuilder<Target> {
}
}
impl<Target: Composer> QuestionBuilder<Target> {
impl<Target> QuestionBuilder<Target> {
/// Converts the question builder into a question builder.
///
/// In other words, doesnt do anything.
pub fn question(self) -> QuestionBuilder<Target> {
self
}
}
impl<Target: Composer> QuestionBuilder<Target> {
/// Converts the question builder into an answer builder.
pub fn answer(self) -> AnswerBuilder<Target> {
AnswerBuilder::new(self.builder)
@@ -587,15 +599,14 @@ impl<Target: Composer> QuestionBuilder<Target> {
pub fn finish(self) -> Target {
self.builder.finish()
}
}
impl<Target: FreezeBuilder> QuestionBuilder<Target> {
/// Converts the question builder into the final message.
///
/// The method will return a message atop whatever octets sequence the
/// builders octets builder converts into.
pub fn into_message(self) -> Message<Target::Octets>
where
Target: FreezeBuilder,
{
pub fn into_message(self) -> Message<Target::Octets> {
self.builder.into_message()
}
}
@@ -650,6 +661,15 @@ where
}
}
impl<Target> From<QuestionBuilder<Target>> for Message<Target::Octets>
where
Target: FreezeBuilder,
{
fn from(src: QuestionBuilder<Target>) -> Self {
src.into_message()
}
}
//--- Deref, DerefMut, AsRef, and AsMut
impl<Target> Deref for QuestionBuilder<Target> {
@@ -831,15 +851,14 @@ impl<Target: Composer> AnswerBuilder<Target> {
pub fn finish(self) -> Target {
self.builder.finish()
}
}
impl<Target: FreezeBuilder> AnswerBuilder<Target> {
/// Converts the answer builder into the final message.
///
/// The method will return a message atop whatever octets sequence the
/// builders octets builder converts into.
pub fn into_message(self) -> Message<Target::Octets>
where
Target: FreezeBuilder,
{
pub fn into_message(self) -> Message<Target::Octets> {
self.builder.into_message()
}
}
@@ -894,6 +913,15 @@ where
}
}
impl<Target> From<AnswerBuilder<Target>> for Message<Target::Octets>
where
Target: FreezeBuilder,
{
fn from(src: AnswerBuilder<Target>) -> Self {
src.into_message()
}
}
//--- Deref, DerefMut, AsRef, and AsMut
impl<Target> Deref for AnswerBuilder<Target> {
@@ -1055,9 +1083,7 @@ impl<Target: Composer> AuthorityBuilder<Target> {
self.rewind();
self.answer
}
}
impl<Target: Composer> AuthorityBuilder<Target> {
/// Converts the authority builder into an authority builder.
///
/// This is identical to the identity function.
@@ -1076,15 +1102,14 @@ impl<Target: Composer> AuthorityBuilder<Target> {
pub fn finish(self) -> Target {
self.answer.finish()
}
}
impl<Target: FreezeBuilder> AuthorityBuilder<Target> {
/// Converts the authority builder into the final message.
///
/// The method will return a message atop whatever octets sequence the
/// builders octets builder converts into.
pub fn into_message(self) -> Message<Target::Octets>
where
Target: FreezeBuilder,
{
pub fn into_message(self) -> Message<Target::Octets> {
self.answer.into_message()
}
}
@@ -1139,6 +1164,15 @@ where
}
}
impl<Target> From<AuthorityBuilder<Target>> for Message<Target::Octets>
where
Target: FreezeBuilder,
{
fn from(src: AuthorityBuilder<Target>) -> Self {
src.into_message()
}
}
//--- Deref, DerefMut, AsRef, and AsMut
impl<Target> Deref for AuthorityBuilder<Target> {
@@ -1336,9 +1370,7 @@ impl<Target: Composer> AdditionalBuilder<Target> {
self.rewind();
self.authority
}
}
impl<Target: Composer> AdditionalBuilder<Target> {
/// Converts the additional builder into an additional builder.
///
/// In other words, does absolutely nothing.
@@ -1350,15 +1382,14 @@ impl<Target: Composer> AdditionalBuilder<Target> {
pub fn finish(self) -> Target {
self.authority.finish()
}
}
impl<Target: FreezeBuilder> AdditionalBuilder<Target> {
/// Converts the additional builder into the final message.
///
/// The method will return a message atop whatever octets sequence the
/// builders octets builder converts into.
pub fn into_message(self) -> Message<Target::Octets>
where
Target: FreezeBuilder,
{
pub fn into_message(self) -> Message<Target::Octets> {
self.authority.into_message()
}
}
@@ -1413,6 +1444,15 @@ where
}
}
impl<Target> From<AdditionalBuilder<Target>> for Message<Target::Octets>
where
Target: FreezeBuilder,
{
fn from(src: AdditionalBuilder<Target>) -> Self {
src.into_message()
}
}
//--- Deref, DerefMut, AsRef, and AsMut
impl<Target> Deref for AdditionalBuilder<Target> {
+160 -7
View File
@@ -40,17 +40,17 @@ opt_types! {
//============ Module Content ================================================
use super::header::Header;
use super::iana::{OptRcode, OptionCode, Rtype};
use super::iana::{Class, OptRcode, OptionCode, Rtype};
use super::name::{Dname, ToDname};
use super::rdata::{ComposeRecordData, ParseRecordData, RecordData};
use super::record::Record;
use super::wire::{Composer, FormError, ParseError};
use super::record::{Record, Ttl};
use super::wire::{Compose, Composer, FormError, ParseError};
use crate::utils::base16;
use core::cmp::Ordering;
use core::convert::TryInto;
use core::marker::PhantomData;
use core::{fmt, hash, mem};
use octseq::builder::{OctetsBuilder, ShortBuf};
use octseq::builder::{EmptyBuilder, OctetsBuilder, ShortBuf};
use octseq::octets::{Octets, OctetsFrom};
use octseq::parse::Parser;
@@ -76,6 +76,15 @@ pub struct Opt<Octs: ?Sized> {
octets: Octs,
}
impl<Octs: EmptyBuilder> Opt<Octs> {
/// Creates empty OPT record data.
pub fn empty() -> Self {
Self {
octets: Octs::empty(),
}
}
}
impl<Octs: AsRef<[u8]>> Opt<Octs> {
/// Creates OPT record data from an octets sequence.
///
@@ -83,7 +92,18 @@ impl<Octs: AsRef<[u8]>> Opt<Octs> {
/// options. It does not check whether the options themselves are valid.
pub fn from_octets(octets: Octs) -> Result<Self, ParseError> {
Opt::check_slice(octets.as_ref())?;
Ok(Opt { octets })
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
/// Creates OPT record data from octets without checking.
///
/// # Safety
///
/// The caller needs to ensure that the slice contains correctly encoded
/// OPT record data. The data of the options themselves does not need to
/// be correct.
unsafe fn from_octets_unchecked(octets: Octs) -> Self {
Self { octets }
}
/// Parses OPT record data from the beginning of a parser.
@@ -128,6 +148,12 @@ impl Opt<[u8]> {
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Opt<Octs> {
pub fn for_slice_ref(&self) -> Opt<&[u8]> {
unsafe { Opt::from_octets_unchecked(self.octets.as_ref()) }
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Opt<Octs> {
/// Returns the length of the OPT record data.
pub fn len(&self) -> usize {
@@ -163,6 +189,44 @@ impl<Octs: AsRef<[u8]> + ?Sized> Opt<Octs> {
}
}
impl<Octs: Composer> Opt<Octs> {
/// Appends a new option to the OPT data.
pub fn push<Opt: ComposeOptData + ?Sized>(
&mut self,
option: &Opt,
) -> Result<(), BuildDataError> {
self.push_raw_option(option.code(), option.compose_len(), |target| {
option.compose_option(target)
})
}
/// Appends a raw option to the OPT data.
///
/// The method will append an option with the given option code. The data
/// of the option will be written via the closure `op`.
pub fn push_raw_option<F>(
&mut self,
code: OptionCode,
option_len: u16,
op: F,
) -> Result<(), BuildDataError>
where
F: FnOnce(&mut Octs) -> Result<(), Octs::AppendError>,
{
LongOptData::check_len(
self.octets
.as_ref()
.len()
.saturating_add(usize::from(option_len)),
)?;
code.compose(&mut self.octets)?;
option_len.compose(&mut self.octets)?;
op(&mut self.octets)?;
Ok(())
}
}
//--- OctetsFrom
impl<Octs, SrcOcts> OctetsFrom<Opt<SrcOcts>> for Opt<Octs>
@@ -283,7 +347,8 @@ impl<Octs: AsRef<[u8]> + ?Sized> fmt::Debug for Opt<Octs> {
///
/// The OPT record reappropriates the record header for encoding some
/// basic information. This type provides access to this information. It
/// consists of the record header accept for its `rdlen` field.
/// consists of the record header with the exception of the fiinal `rdlen`
/// field.
///
/// This is so that `OptBuilder` can safely deref to this type.
///
@@ -440,6 +505,23 @@ impl<Octs> OptRecord<Octs> {
}
}
/// Converts the OPT record into a regular record.
pub fn as_record(&self) -> Record<&'static Dname<[u8]>, Opt<&[u8]>>
where
Octs: AsRef<[u8]>,
{
Record::new(
Dname::root_slice(),
Class::Int(self.udp_payload_size),
Ttl::from_secs(
u32::from(self.ext_rcode) << 24
| u32::from(self.version) << 16
| u32::from(self.flags),
),
self.data.for_slice_ref(),
)
}
/// Returns the UDP payload size.
///
/// Through this field a sender of a message can signal the maximum size
@@ -451,6 +533,11 @@ impl<Octs> OptRecord<Octs> {
self.udp_payload_size
}
/// Sets the UDP payload size.
pub fn set_udp_payload_size(&mut self, value: u16) {
self.udp_payload_size = value
}
/// Returns the extended rcode.
///
/// Some of the bits of the rcode are stored in the regular message
@@ -484,6 +571,44 @@ impl<Octs> OptRecord<Octs> {
}
}
impl<Octs: Composer> OptRecord<Octs> {
/// Appends a new option to the OPT data.
pub fn push<Opt: ComposeOptData + ?Sized>(
&mut self,
option: &Opt,
) -> Result<(), BuildDataError> {
self.data.push(option)
}
/// Appends a raw option to the OPT data.
///
/// The method will append an option with the given option code. The data
/// of the option will be written via the closure `op`.
pub fn push_raw_option<F>(
&mut self,
code: OptionCode,
option_len: u16,
op: F,
) -> Result<(), BuildDataError>
where
F: FnOnce(&mut Octs) -> Result<(), Octs::AppendError>,
{
self.data.push_raw_option(code, option_len, op)
}
}
impl<Octs: EmptyBuilder> Default for OptRecord<Octs> {
fn default() -> Self {
Self {
udp_payload_size: 0,
ext_rcode: 0,
version: 0,
flags: 0,
data: Opt::empty(),
}
}
}
//--- From
impl<Octs, N: ToDname> From<Record<N, Opt<Octs>>> for OptRecord<Octs> {
@@ -521,6 +646,20 @@ impl<Octs> AsRef<Opt<Octs>> for OptRecord<Octs> {
}
}
//--- Debug
impl<Octs: AsRef<[u8]>> fmt::Debug for OptRecord<Octs> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("OptRecord")
.field("udp_payload_size", &self.udp_payload_size)
.field("ext_rcord", &self.ext_rcode)
.field("version", &self.version)
.field("flags", &self.flags)
.field("data", &self.data)
.finish()
}
}
//------------ OptionHeader --------------------------------------------------
/// The header of an OPT option.
@@ -859,13 +998,27 @@ impl std::error::Error for LongOptData {}
/// An error happened while constructing an SVCB value.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BuildDataError {
/// The value would exceed the allow length of a value.
/// The value would exceed the allowed length of a value.
LongOptData,
/// The underlying octets builder ran out of buffer space.
ShortBuf,
}
impl BuildDataError {
/// Converts the error into a `LongOptData` error for endless buffers.
///
/// # Panics
///
/// This method will panic if the error is of the `ShortBuf` variant.
pub fn unlimited_buf(self) -> LongOptData {
match self {
Self::LongOptData => LongOptData(()),
Self::ShortBuf => panic!("ShortBuf on unlimited buffer"),
}
}
}
impl From<LongOptData> for BuildDataError {
fn from(_: LongOptData) -> Self {
Self::LongOptData
+32 -3
View File
@@ -27,6 +27,9 @@
//!
//! Currently, there are the following modules:
//!
#![cfg_attr(feature = "net", doc = "* [net]:")]
#![cfg_attr(not(feature = "net"), doc = "* net:")]
//! Sending and receiving DNS message.
#![cfg_attr(feature = "resolv", doc = "* [resolv]:")]
#![cfg_attr(not(feature = "resolv"), doc = "* resolv:")]
//! An asynchronous DNS resolver based on the
@@ -48,12 +51,13 @@
//! Finally, the [dep] module contains re-exports of some important
//! dependencies to help avoid issues with multiple versions of a crate.
//!
//! # Reference of Feature Flags
//! # Reference of feature flags
//!
//! The following is the complete list of the feature flags available.
//! The following is the complete list of the feature flags with the
//! exception of unstable features which are described below.
//!
//! * `bytes`: Enables using the types `Bytes` and `BytesMut` from the
//! [bytes](https://github.com/tokio-rs/bytes) crate as octet sequences.
//! [bytes](https://github.com/tokio-rs/bytes) crate as octet sequences.
//! * `chrono`: Adds the [chrono](https://github.com/chronotope/chrono)
//! crate as a dependency. This adds support for generating serial numbers
//! from time stamps.
@@ -104,6 +108,30 @@
#![cfg_attr(feature = "zonefile", doc = " [zonefile]")]
#![cfg_attr(not(feature = "zonefile"), doc = " zonefile")]
//! module and currently also enables the `bytes` and `std` features.
//!
//! # Unstable features
//!
//! When adding new functionality to the crate, practical experience is
//! necessary to arrive at a good, user friendly design. Unstable features
//! allow adding and rapidly changing new code without having to release
//! versions allowing breaking changes all the time. If you use unstable
//! features, it is best to specify a concrete version as a dependency in
//! `Cargo.toml` using the `=` operator, e.g.:
//!
//! ```text
//! [dependencies]
//! domain = "=0.9.3"
//! ```
//!
//! Currently, the following unstable features exist:
//!
//! * `unstable-client-transport`: sending and receiving DNS messages from
//! a client perspective; primarily the `net::client` module.
//!
//! Note: Some functionality is currently informally marked as
//! “experimental” since it was introduced before adoption of the concept
//! of unstable features. These will follow proper Semver practice but may
//! significant changes in releases with breakting changes.
#![no_std]
#![allow(renamed_and_removed_lints)]
@@ -121,6 +149,7 @@ extern crate core;
pub mod base;
pub mod dep;
pub mod net;
pub mod rdata;
pub mod resolv;
pub mod sign;
+510
View File
@@ -0,0 +1,510 @@
//! A client over datagram protocols.
//!
//! This module implements a DNS client for use with datagram protocols, i.e.,
//! message-oriented, connection-less, unreliable network protocols. In
//! practice, this is pretty much exclusively UDP.
#![warn(missing_docs)]
// To do:
// - cookies
use crate::base::Message;
use crate::net::client::protocol::{
AsyncConnect, AsyncDgramRecv, AsyncDgramRecvEx, AsyncDgramSend,
AsyncDgramSendEx,
};
use crate::net::client::request::{
ComposeRequest, Error, GetResponse, SendRequest,
};
use bytes::Bytes;
use core::{cmp, fmt};
use octseq::OctetsInto;
use std::boxed::Box;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::{error, io};
use tokio::sync::Semaphore;
use tokio::time::{timeout_at, Duration, Instant};
//------------ Configuration Constants ----------------------------------------
/// Configuration limits for the maximum number of parallel requests.
const MAX_PARALLEL: DefMinMax<usize> = DefMinMax::new(100, 1, 1000);
/// Configuration limits for the read timeout.
const READ_TIMEOUT: DefMinMax<Duration> = DefMinMax::new(
Duration::from_secs(5),
Duration::from_millis(1),
Duration::from_secs(60),
);
/// Configuration limits for the maximum number of retries.
const MAX_RETRIES: DefMinMax<u8> = DefMinMax::new(5, 1, 100);
/// Default UDP payload size.
const DEF_UDP_PAYLOAD_SIZE: u16 = 1232;
/// The default receive buffer size.
const DEF_RECV_SIZE: usize = 2000;
//------------ Config ---------------------------------------------------------
/// Configuration of a datagram transport.
#[derive(Clone, Debug)]
pub struct Config {
/// Maximum number of parallel requests for a transport connection.
max_parallel: usize,
/// Read timeout.
read_timeout: Duration,
/// Maximum number of retries.
max_retries: u8,
/// EDNS UDP payload size.
///
/// If this is `None`, no OPT record will be included at all.
udp_payload_size: Option<u16>,
/// Receive buffer size.
recv_size: usize,
}
impl Config {
/// Creates a new config with default values.
pub fn new() -> Self {
Default::default()
}
/// Sets the maximum number of parallel requests.
///
/// Once this many number of requests are currently outstanding,
/// additional requests will wait.
///
/// If this value is too small or too large, it will be caped.
pub fn set_max_parallel(&mut self, value: usize) {
self.max_parallel = MAX_PARALLEL.limit(value)
}
/// Returns the maximum number of parallel requests.
pub fn max_parallel(&self) -> usize {
self.max_parallel
}
/// Sets the read timeout.
///
/// The read timeout is the maximum amount of time to wait for any
/// response after a request was sent.
///
/// If this value is too small or too large, it will be caped.
pub fn set_read_timeout(&mut self, value: Duration) {
self.read_timeout = READ_TIMEOUT.limit(value)
}
/// Returns the read timeout.
pub fn read_timeout(&self) -> Duration {
self.read_timeout
}
/// Sets the maximum number a request is retried before giving up.
///
/// If this value is too small or too large, it will be caped.
pub fn set_max_retries(&mut self, value: u8) {
self.max_retries = MAX_RETRIES.limit(value)
}
/// Returns the maximum number of request retries.
pub fn max_retries(&self) -> u8 {
self.max_retries
}
/// Sets the requested UDP payload size.
///
/// This value indicates to the server the maximum size of a UDP packet.
/// For UDP on public networks, this value should be left at the default
/// of 1232 to avoid issues rising from packet fragmentation. See
/// [draft-ietf-dnsop-avoid-fragmentation] for a discussion on these
/// issues and recommendations.
///
/// On private networks or protocols other than UDP, other values can be
/// used.
///
/// Setting the UDP payload size to `None` currently results in messages
/// that will not include an OPT record.
///
/// [draft-ietf-dnsop-avoid-fragmentation]: https://datatracker.ietf.org/doc/draft-ietf-dnsop-avoid-fragmentation/
pub fn set_udp_payload_size(&mut self, value: Option<u16>) {
self.udp_payload_size = value;
}
/// Returns the UDP payload size.
pub fn udp_payload_size(&self) -> Option<u16> {
self.udp_payload_size
}
/// Sets the receive buffer size.
///
/// This is the amount of memory that is allocated for receiving a
/// response.
pub fn set_recv_size(&mut self, size: usize) {
self.recv_size = size
}
/// Returns the receive buffer size.
pub fn recv_size(&self) -> usize {
self.recv_size
}
}
impl Default for Config {
fn default() -> Self {
Self {
max_parallel: MAX_PARALLEL.default(),
read_timeout: READ_TIMEOUT.default(),
max_retries: MAX_RETRIES.default(),
udp_payload_size: Some(DEF_UDP_PAYLOAD_SIZE),
recv_size: DEF_RECV_SIZE,
}
}
}
//------------ Connection -----------------------------------------------------
/// A datagram protocol connection.
///
/// Because it owns the connections resources, this type is not `Clone`.
/// However, it is entirely safe to share it by sticking it into e.g. an arc.
#[derive(Debug)]
pub struct Connection<S> {
state: Arc<ConnectionState<S>>,
}
#[derive(Debug)]
struct ConnectionState<S> {
/// User configuration variables.
config: Config,
/// Connections to datagram sockets.
connect: S,
/// Semaphore to limit access to UDP sockets.
semaphore: Semaphore,
}
impl<S> Connection<S> {
/// Create a new datagram transport with default configuration.
pub fn new(connect: S) -> Self {
Self::with_config(connect, Default::default())
}
/// Create a new datagram transport with a given configuration.
pub fn with_config(connect: S, config: Config) -> Self {
Self {
state: Arc::new(ConnectionState {
semaphore: Semaphore::new(config.max_parallel),
config,
connect,
}),
}
}
}
impl<S> Connection<S>
where
S: AsyncConnect,
S::Connection: AsyncDgramRecv + AsyncDgramSend + Unpin,
{
/// Performs a request.
///
/// Sends the provided and returns either a response or an error. If there
/// are currently too many active queries, the future will wait until the
/// number has dropped below the limit.
async fn handle_request_impl<Req: ComposeRequest>(
self,
mut request: Req,
) -> Result<Message<Bytes>, Error> {
// Acquire the semaphore or wait for it.
let _ = self
.state
.semaphore
.acquire()
.await
.expect("semaphore closed");
// A place to store the receive buffer for reuse.
let mut reuse_buf = None;
// Transmit loop.
for _ in 0..self.state.config.max_retries {
let mut sock = self
.state
.connect
.connect()
.await
.map_err(QueryError::connect)?;
// Set random ID in header
request.header_mut().set_random_id();
// Set UDP payload size if necessary.
if let Some(size) = self.state.config.udp_payload_size {
request.set_udp_payload_size(size)
}
// Create the message and send it out.
let request_msg = request.to_message();
let dgram = request_msg.as_slice();
let sent = sock.send(dgram).await.map_err(QueryError::send)?;
if sent != dgram.len() {
return Err(QueryError::short_send().into());
}
// Receive loop. It may at most take read_timeout time.
let deadline = Instant::now() + self.state.config.read_timeout;
while deadline > Instant::now() {
let mut buf = reuse_buf.take().unwrap_or_else(|| {
// XXX use uninit'ed mem here.
vec![0; self.state.config.recv_size]
});
let len =
match timeout_at(deadline, sock.recv(&mut buf)).await {
Ok(Ok(len)) => len,
Ok(Err(err)) => {
// Receiving failed.
return Err(QueryError::receive(err).into());
}
Err(_) => {
// Timeout.
break;
}
};
buf.truncate(len);
// We ignore garbage since there is a timer on this whole
// thing.
let answer = match Message::try_from_octets(buf) {
Ok(answer) => answer,
Err(buf) => {
// Just go back to receiving.
reuse_buf = Some(buf);
continue;
}
};
if !request.is_answer(answer.for_slice()) {
// Wrong answer, go back to receiving
reuse_buf = Some(answer.into_octets());
continue;
}
return Ok(answer.octets_into());
}
}
Err(QueryError::timeout().into())
}
}
//--- Clone
impl<S> Clone for Connection<S> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}
//--- SendRequest
impl<S, Req> SendRequest<Req> for Connection<S>
where
S: AsyncConnect + Clone + Send + Sync + 'static,
S::Connection:
AsyncDgramRecv + AsyncDgramSend + Send + Sync + Unpin + 'static,
Req: ComposeRequest + Clone + Send + Sync + 'static,
{
fn send_request(&self, request_msg: Req) -> Box<dyn GetResponse + Send> {
Box::new(Request {
fut: Box::pin(self.clone().handle_request_impl(request_msg)),
})
}
}
//------------ Request ------------------------------------------------------
/// The state of a DNS request.
pub struct Request {
/// Future that does the actual work of GetResponse.
fut: Pin<Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send>>,
}
impl Request {
/// Async function that waits for the future stored in Request to complete.
async fn get_response_impl(&mut self) -> Result<Message<Bytes>, Error> {
(&mut self.fut).await
}
}
impl fmt::Debug for Request {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
todo!()
}
}
impl GetResponse for Request {
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
> {
Box::pin(self.get_response_impl())
}
}
//------------ DefMinMax -----------------------------------------------------
/// The default, minimum, and maximum values for a config variable.
#[derive(Clone, Copy)]
struct DefMinMax<T> {
/// The default value,
def: T,
/// The minimum value,
min: T,
/// The maximum value,
max: T,
}
impl<T> DefMinMax<T> {
/// Creates a new value.
const fn new(def: T, min: T, max: T) -> Self {
Self { def, min, max }
}
/// Returns the default value.
fn default(self) -> T {
self.def
}
/// Trims the given value to fit into the minimum/maximum range.
fn limit(self, value: T) -> T
where
T: Ord,
{
cmp::max(self.min, cmp::min(self.max, value))
}
}
//============ Errors ========================================================
//------------ QueryError ----------------------------------------------------
/// A query failed.
#[derive(Debug)]
pub struct QueryError {
/// Which step failed?
kind: QueryErrorKind,
/// The underlying IO error.
io: std::io::Error,
}
impl QueryError {
fn new(kind: QueryErrorKind, io: io::Error) -> Self {
Self { kind, io }
}
fn connect(io: io::Error) -> Self {
Self::new(QueryErrorKind::Connect, io)
}
fn send(io: io::Error) -> Self {
Self::new(QueryErrorKind::Send, io)
}
fn short_send() -> Self {
Self::new(
QueryErrorKind::Send,
io::Error::new(io::ErrorKind::Other, "short request sent"),
)
}
fn timeout() -> Self {
Self::new(
QueryErrorKind::Timeout,
io::Error::new(io::ErrorKind::TimedOut, "timeout expired"),
)
}
fn receive(io: io::Error) -> Self {
Self::new(QueryErrorKind::Receive, io)
}
}
impl QueryError {
/// Returns information about when the query has failed.
pub fn kind(&self) -> QueryErrorKind {
self.kind
}
/// Converts the query error into the underlying IO error.
pub fn io_error(self) -> std::io::Error {
self.io
}
}
impl From<QueryError> for std::io::Error {
fn from(err: QueryError) -> std::io::Error {
err.io
}
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.kind.error_str(), self.io)
}
}
impl error::Error for QueryError {}
//------------ QueryErrorKind ------------------------------------------------
/// Which part of processing the query failed?
#[derive(Copy, Clone, Debug)]
pub enum QueryErrorKind {
/// Failed to connect to the remote.
Connect,
/// Failed to send the request.
Send,
/// The request has timed out.
Timeout,
/// Failed to read the response.
Receive,
}
impl QueryErrorKind {
/// Returns the string to be used when displaying a query error.
fn error_str(self) -> &'static str {
match self {
Self::Connect => "connecting failed",
Self::Send => "sending request failed",
Self::Timeout | Self::Receive => "reading response failed",
}
}
}
impl fmt::Display for QueryErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::Connect => "connecting failed",
Self::Send => "sending request failed",
Self::Timeout => "request timeout",
Self::Receive => "reading response failed",
})
}
}
+251
View File
@@ -0,0 +1,251 @@
//! A UDP transport that falls back to TCP if the reply is truncated
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
// To do:
// - handle shutdown
use crate::base::Message;
use crate::net::client::dgram;
use crate::net::client::multi_stream;
use crate::net::client::protocol::{
AsyncConnect, AsyncDgramRecv, AsyncDgramSend,
};
use crate::net::client::request::{
ComposeRequest, Error, GetResponse, SendRequest,
};
use bytes::Bytes;
use std::boxed::Box;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
//------------ Config ---------------------------------------------------------
/// Configuration for an octet_stream transport connection.
#[derive(Clone, Debug, Default)]
pub struct Config {
/// Configuration for the UDP transport.
dgram: dgram::Config,
/// Configuration for the multi_stream (TCP) transport.
multi_stream: multi_stream::Config,
}
impl Config {
/// Creates a new config with default values.
pub fn new() -> Self {
Default::default()
}
/// Creates a new config from the two portions.
pub fn from_parts(
dgram: dgram::Config,
multi_stream: multi_stream::Config,
) -> Self {
Self {
dgram,
multi_stream,
}
}
/// Returns the datagram config.
pub fn dgram(&self) -> &dgram::Config {
&self.dgram
}
/// Returns a mutable reference to the datagram config.
pub fn dgram_mut(&mut self) -> &mut dgram::Config {
&mut self.dgram
}
/// Sets the datagram config.
pub fn set_dgram(&mut self, dgram: dgram::Config) {
self.dgram = dgram
}
/// Returns the stream config.
pub fn stream(&self) -> &multi_stream::Config {
&self.multi_stream
}
/// Returns a mutable reference to the stream config.
pub fn stream_mut(&mut self) -> &mut multi_stream::Config {
&mut self.multi_stream
}
/// Sets the stream config.
pub fn set_stream(&mut self, stream: multi_stream::Config) {
self.multi_stream = stream
}
}
//------------ Connection -----------------------------------------------------
/// DNS transport connection that first issues a query over a UDP transport and
/// falls back to TCP if the reply is truncated.
#[derive(Clone)]
pub struct Connection<DgramS, Req> {
/// The UDP transport connection.
udp_conn: Arc<dgram::Connection<DgramS>>,
/// The TCP transport connection.
tcp_conn: multi_stream::Connection<Req>,
}
impl<DgramS, Req> Connection<DgramS, Req>
where
DgramS: AsyncConnect + Clone + Send + Sync + 'static,
DgramS::Connection:
AsyncDgramRecv + AsyncDgramSend + Send + Sync + Unpin + 'static,
{
/// Creates a new multi-stream transport with default configuration.
pub fn new<StreamS>(
dgram_remote: DgramS,
stream_remote: StreamS,
) -> (Self, multi_stream::Transport<StreamS, Req>) {
Self::with_config(dgram_remote, stream_remote, Default::default())
}
/// Creates a new multi-stream transport.
pub fn with_config<StreamS>(
dgram_remote: DgramS,
stream_remote: StreamS,
config: Config,
) -> (Self, multi_stream::Transport<StreamS, Req>) {
let udp_conn =
dgram::Connection::with_config(dgram_remote, config.dgram).into();
let (tcp_conn, transport) = multi_stream::Connection::with_config(
stream_remote,
config.multi_stream,
);
(Self { udp_conn, tcp_conn }, transport)
}
}
//--- SendRequest
impl<DgramS, Req> SendRequest<Req> for Connection<DgramS, Req>
where
DgramS: AsyncConnect + Clone + Debug + Send + Sync + 'static,
DgramS::Connection: AsyncDgramRecv + AsyncDgramSend + Send + Sync + Unpin,
Req: ComposeRequest + Clone + 'static,
{
fn send_request(&self, request_msg: Req) -> Box<dyn GetResponse + Send> {
Box::new(Request::new(
request_msg,
self.udp_conn.clone(),
self.tcp_conn.clone(),
))
}
}
//------------ Request --------------------------------------------------------
/// Object that contains the current state of a query.
#[derive(Debug)]
pub struct Request<S, Req> {
/// Reqeust message.
request_msg: Req,
/// UDP transport to be used.
udp_conn: Arc<dgram::Connection<S>>,
/// TCP transport to be used.
tcp_conn: multi_stream::Connection<Req>,
/// Current state of the request.
state: QueryState,
}
/// Status of the query.
#[derive(Debug)]
enum QueryState {
/// Start a request over the UDP transport.
StartUdpRequest,
/// Get the response from the UDP transport.
GetUdpResponse(Box<dyn GetResponse + Send>),
/// Start a request over the TCP transport.
StartTcpRequest,
/// Get the response from the TCP transport.
GetTcpResponse(Box<dyn GetResponse + Send>),
}
impl<S, Req> Request<S, Req>
where
S: AsyncConnect + Clone + Send + Sync + 'static,
Req: ComposeRequest + Clone + 'static,
{
/// Create a new Request object.
///
/// The initial state is to start with a UDP transport.
fn new(
request_msg: Req,
udp_conn: Arc<dgram::Connection<S>>,
tcp_conn: multi_stream::Connection<Req>,
) -> Request<S, Req> {
Self {
request_msg,
udp_conn,
tcp_conn,
state: QueryState::StartUdpRequest,
}
}
/// Get the response of a DNS request.
///
/// This function is cancel safe.
async fn get_response_impl(&mut self) -> Result<Message<Bytes>, Error>
where
S::Connection: AsyncDgramRecv + AsyncDgramSend + Send + Sync + Unpin,
{
loop {
match &mut self.state {
QueryState::StartUdpRequest => {
let msg = self.request_msg.clone();
let request = self.udp_conn.send_request(msg);
self.state = QueryState::GetUdpResponse(request);
continue;
}
QueryState::GetUdpResponse(ref mut request) => {
let response = request.get_response().await?;
if response.header().tc() {
self.state = QueryState::StartTcpRequest;
continue;
}
return Ok(response);
}
QueryState::StartTcpRequest => {
let msg = self.request_msg.clone();
let request = self.tcp_conn.send_request(msg);
self.state = QueryState::GetTcpResponse(request);
continue;
}
QueryState::GetTcpResponse(ref mut query) => {
let response = query.get_response().await?;
return Ok(response);
}
}
}
}
}
impl<S, Req> GetResponse for Request<S, Req>
where
S: AsyncConnect + Clone + Debug + Send + Sync + 'static,
S::Connection: AsyncDgramRecv + AsyncDgramSend + Send + Sync + Unpin,
Req: ComposeRequest + Clone + 'static,
{
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
> {
Box::pin(self.get_response_impl())
}
}
+145
View File
@@ -0,0 +1,145 @@
//! Sending requests and receiving responses.
//!
//! This module provides DNS transport protocols that allow sending a DNS
//! request and receiving the corresponding reply.
//!
//! Sending a request and receiving the reply consists of four steps:
//! 1) Creating a request message,
//! 2) Creating a DNS transport,
//! 3) Sending the request, and
//! 4) Receiving the reply.
//!
//! The first and second step are independent and can happen in any order.
//! The third step uses the resuts of the first and second step.
//! Finally, the fourth step uses the result of the third step.
//! # Creating a request message
//!
//! The DNS transport protocols expect a request message that implements the
//! [ComposeRequest][request::ComposeRequest] trait.
//! This trait allows transports to add ENDS(0) options, set flags, etc.
//! The [RequestMessage][request::RequestMessage] type implements this trait.
//! The [new][request::RequestMessage::new] method of RequestMessage create
//! a new RequestMessage object based an existing messsage (that implements
//! ```Into<Message<Octs>>```).
//!
//! For example:
//! ```rust
//! # use domain::base::{Dname, MessageBuilder, Rtype};
//! # use domain::net::client::request::RequestMessage;
//! let mut msg = MessageBuilder::new_vec();
//! msg.header_mut().set_rd(true);
//! let mut msg = msg.question();
//! msg.push(
//! (Dname::vec_from_str("example.com").unwrap(), Rtype::Aaaa)
//! ).unwrap();
//! let req = RequestMessage::new(msg);
//! ```
//! # Creating a DNS transport
//!
//! Creating a DNS transport typically involves creating a configuration
//! object, creating the underlying network connection, creating the
//! DNS transport and running a ```run``` method as a separate task. This
//! is illustrated in the following example:
//! ```rust
//! # use domain::net::client::multi_stream;
//! # use domain::net::client::protocol::TcpConnect;
//! # use domain::net::client::request::SendRequest;
//! # use std::time::Duration;
//! # async fn _test() {
//! # let server_addr = String::from("127.0.0.1:53");
//! let mut multi_stream_config = multi_stream::Config::default();
//! multi_stream_config.stream_mut().set_response_timeout(
//! Duration::from_millis(100),
//! );
//! let tcp_connect = TcpConnect::new(server_addr);
//! let (tcp_conn, transport) = multi_stream::Connection::with_config(
//! tcp_connect, multi_stream_config
//! );
//! tokio::spawn(transport.run());
//! # let req = domain::net::client::request::RequestMessage::new(
//! # domain::base::MessageBuilder::new_vec()
//! # );
//! # let mut request = tcp_conn.send_request(req);
//! # }
//! ```
//! The currently implemented DNS transports have the following layering. At
//! the lowest layer are [dgram] and [stream]. The dgram transport is used for
//! DNS over UDP, the stream transport is used for DNS over a single TCP or
//! TLS connection. The transport works as long as the connection continuous
//! to exist.
//! The [multi_stream] transport is layered on top of stream, and creates new
//! TCP or TLS connections when old ones terminates.
//! Next, [dgram_stream] combines the dgram transport with the multi_stream
//! transport. This is typically needed because a request over UDP can receive
//! a truncated response, which should be retried over TCP.
//! Finally, the [redundant] transport can select the best transport out of
//! a collection of underlying transports.
//! # Sending the request
//!
//! A DNS transport implements the [SendRequest][request::SendRequest] trait.
//! This trait provides a single method,
//! [send_request][request::SendRequest::send_request] and returns an object
//! that provides the response.
//!
//! For example:
//! ```no_run
//! # use domain::net::client::request::SendRequest;
//! # async fn _test() {
//! # let (tls_conn, _) = domain::net::client::stream::Connection::new(
//! # domain::net::client::protocol::TcpConnect::new(
//! # String::from("127.0.0.1:53")
//! # )
//! # );
//! # let req = domain::net::client::request::RequestMessage::new(
//! # domain::base::MessageBuilder::new_vec()
//! # );
//! let mut request = tls_conn.send_request(req);
//! # }
//! ```
//! where ```tls_conn``` is a transport connection for DNS over TLS.
//! # Receiving the request
//!
//! The [send_request][request::SendRequest::send_request] method returns an
//! object that implements the [GetResponse][request::GetResponse] trait.
//! This trait provides a single method,
//! [get_response][request::GetResponse::get_response], which returns the
//! DNS response message or an error. This method is intended to be
//! cancelation safe.
//!
//! For example:
//! ```no_run
//! # use crate::domain::net::client::request::SendRequest;
//! # async fn _test() {
//! # let (tls_conn, _) = domain::net::client::stream::Connection::new(
//! # domain::net::client::protocol::TcpConnect::new(
//! # String::from("127.0.0.1:53")
//! # )
//! # );
//! # let req = domain::net::client::request::RequestMessage::new(
//! # domain::base::MessageBuilder::new_vec()
//! # );
//! # let mut request = tls_conn.send_request(req);
//! let reply = request.get_response().await;
//! # }
//! ```
//! # Example with various transport connections
//! ```no_run
#![doc = include_str!("../../../examples/client-transports.rs")]
//! ```
#![cfg(feature = "unstable-client-transport")]
#![cfg_attr(docsrs, doc(cfg(feature = "unstable-client-transport")))]
#![warn(missing_docs)]
pub mod dgram;
pub mod dgram_stream;
pub mod multi_stream;
pub mod protocol;
pub mod redundant;
pub mod request;
pub mod stream;
+628
View File
@@ -0,0 +1,628 @@
//! A DNS over multiple octet streams transport
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
// To do:
// - too many connection errors
use crate::base::Message;
use crate::net::client::protocol::AsyncConnect;
use crate::net::client::request::{
ComposeRequest, Error, GetResponse, SendRequest,
};
use crate::net::client::stream;
use bytes::Bytes;
use futures_util::stream::FuturesUnordered;
use futures_util::StreamExt;
use rand::random;
use std::boxed::Box;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use tokio::io;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::{mpsc, oneshot};
use tokio::time::{sleep_until, Instant};
//------------ Constants -----------------------------------------------------
/// Capacity of the channel that transports `ChanReq`.
const DEF_CHAN_CAP: usize = 8;
/// Error messafe when the connection is closed.
const ERR_CONN_CLOSED: &str = "connection closed";
//------------ Config ---------------------------------------------------------
/// Configuration for an multi-stream transport.
#[derive(Clone, Debug, Default)]
pub struct Config {
/// Configuration of the underlying stream transport.
stream: stream::Config,
}
impl Config {
/// Returns the underlying stream config.
pub fn stream(&self) -> &stream::Config {
&self.stream
}
/// Returns a mutable reference to the underlying stream config.
pub fn stream_mut(&mut self) -> &mut stream::Config {
&mut self.stream
}
}
impl From<stream::Config> for Config {
fn from(stream: stream::Config) -> Self {
Self { stream }
}
}
//------------ Connection -----------------------------------------------------
/// A connection to a multi-stream transport.
#[derive(Debug)]
pub struct Connection<Req> {
/// The sender half of the connection request channel.
sender: mpsc::Sender<ChanReq<Req>>,
}
impl<Req> Connection<Req> {
/// Creates a new multi-stream transport with default configuration.
pub fn new<Remote>(remote: Remote) -> (Self, Transport<Remote, Req>) {
Self::with_config(remote, Default::default())
}
/// Creates a new multi-stream transport.
pub fn with_config<Remote>(
remote: Remote,
config: Config,
) -> (Self, Transport<Remote, Req>) {
let (sender, transport) = Transport::new(remote, config);
(Self { sender }, transport)
}
}
impl<Req: ComposeRequest + Clone + 'static> Connection<Req> {
/// Sends a request and receives a response.
pub async fn request(
&self,
request: Req,
) -> Result<Message<Bytes>, Error> {
Request::new(self.clone(), request).get_response().await
}
/// Starts a request.
///
/// This is the future that is returned by the `SendRequest` impl.
async fn _send_request(
&self,
request: &Req,
) -> Result<Box<dyn GetResponse + Send>, Error>
where
Req: 'static,
{
let gr = Request::new(self.clone(), request.clone());
Ok(Box::new(gr))
}
/// Request a new connection.
async fn new_conn(
&self,
opt_id: Option<u64>,
) -> Result<oneshot::Receiver<ChanResp<Req>>, Error> {
let (sender, receiver) = oneshot::channel();
let req = ChanReq {
cmd: ReqCmd::NewConn(opt_id, sender),
};
self.sender
.send(req)
.await
.map_err(|_| Error::ConnectionClosed)?;
Ok(receiver)
}
/// Request a shutdown.
pub async fn shutdown(&self) -> Result<(), &'static str> {
let req = ChanReq {
cmd: ReqCmd::Shutdown,
};
match self.sender.send(req).await {
Err(_) =>
// Send error. The receiver is gone, this means that the
// connection is closed.
{
Err(ERR_CONN_CLOSED)
}
Ok(_) => Ok(()),
}
}
}
//--- Clone
impl<Req> Clone for Connection<Req> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
}
}
}
//--- SendRequest
impl<Req> SendRequest<Req> for Connection<Req>
where
Req: ComposeRequest + Clone + 'static,
{
fn send_request(&self, request: Req) -> Box<dyn GetResponse + Send> {
Box::new(Request::new(self.clone(), request))
}
}
//------------ Request --------------------------------------------------------
/// The connection side of an active request.
#[derive(Debug)]
struct Request<Req> {
/// The request message.
///
/// It is kept so we can compare a response with it.
request_msg: Req,
/// Current state of the query.
state: QueryState<Req>,
/// The underlying transport.
conn: Connection<Req>,
/// The id of the most recent connection, if any.
conn_id: Option<u64>,
/// Number of retries with delay.
delayed_retry_count: u64,
}
/// The states of the query state machine.
#[derive(Debug)]
enum QueryState<Req> {
/// Request a new connection.
RequestConn,
/// Receive a new connection from the receiver.
ReceiveConn(oneshot::Receiver<ChanResp<Req>>),
/// Start a query using the given stream transport.
StartQuery(Arc<stream::Connection<Req>>),
/// Get the result of the query.
GetResult(stream::Request),
/// Wait until trying again.
///
/// The instant represents when the error occurred, the duration how
/// long to wait.
Delay(Instant, Duration),
/// A response has been received and the query is done.
Done,
}
/// The response to a connection request.
type ChanResp<Req> = Result<ChanRespOk<Req>, Arc<std::io::Error>>;
/// The successful response to a connection request.
#[derive(Debug)]
struct ChanRespOk<Req> {
/// The id of this connection.
id: u64,
/// The new stream transport to use for sending a request.
conn: Arc<stream::Connection<Req>>,
}
impl<Req> Request<Req> {
/// Creates a new query.
fn new(conn: Connection<Req>, request_msg: Req) -> Self {
Self {
conn,
request_msg,
state: QueryState::RequestConn,
conn_id: None,
delayed_retry_count: 0,
}
}
}
impl<Req: ComposeRequest + Clone + 'static> Request<Req> {
/// Get the result of a DNS request.
///
/// This function is cancellation safe. If its future is dropped before
/// it is resolved, you can call it again to get a new future.
pub async fn get_response(&mut self) -> Result<Message<Bytes>, Error> {
loop {
match self.state {
QueryState::RequestConn => {
let rx = match self.conn.new_conn(self.conn_id).await {
Ok(rx) => rx,
Err(err) => {
self.state = QueryState::Done;
return Err(err);
}
};
self.state = QueryState::ReceiveConn(rx);
}
QueryState::ReceiveConn(ref mut receiver) => {
let res = match receiver.await {
Ok(res) => res,
Err(_) => {
// Assume receive error
self.state = QueryState::Done;
return Err(Error::StreamReceiveError);
}
};
// Another Result. This time from executing the request
match res {
Err(_) => {
self.delayed_retry_count += 1;
let retry_time =
retry_time(self.delayed_retry_count);
self.state =
QueryState::Delay(Instant::now(), retry_time);
continue;
}
Ok(ok_res) => {
let id = ok_res.id;
let conn = ok_res.conn;
self.conn_id = Some(id);
self.state = QueryState::StartQuery(conn);
continue;
}
}
}
QueryState::StartQuery(ref mut conn) => {
self.state = QueryState::GetResult(
conn.get_request(self.request_msg.clone()),
);
continue;
}
QueryState::GetResult(ref mut query) => {
match query.get_response().await {
Ok(reply) => return Ok(reply),
// XXX This replicates the previous behavior. But
// maybe we should have a whole category of
// fatal errors where retrying doesnt make any
// sense?
Err(Error::WrongReplyForQuery) => {
return Err(Error::WrongReplyForQuery)
}
Err(_) => {
self.delayed_retry_count += 1;
let retry_time =
retry_time(self.delayed_retry_count);
self.state =
QueryState::Delay(Instant::now(), retry_time);
continue;
}
}
}
QueryState::Delay(instant, duration) => {
sleep_until(instant + duration).await;
self.state = QueryState::RequestConn;
}
QueryState::Done => {
panic!("Already done");
}
}
}
}
}
impl<Req: ComposeRequest + Clone + 'static> GetResponse for Request<Req> {
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
> {
Box::pin(Self::get_response(self))
}
}
//------------ Transport ------------------------------------------------
/// The actual implementation of [Connection].
#[derive(Debug)]
pub struct Transport<Remote, Req> {
/// User configuration values.
config: Config,
/// The remote destination.
stream: Remote,
/// Underlying stream connection.
conn_state: SingleConnState3<Req>,
/// Current connection id.
conn_id: u64,
/// Receiver part of the channel.
receiver: mpsc::Receiver<ChanReq<Req>>,
}
#[derive(Debug)]
/// A request to [Connection::run] either for a new stream or to
/// shutdown.
struct ChanReq<Req> {
/// A requests consists of a command.
cmd: ReqCmd<Req>,
}
#[derive(Debug)]
/// Commands that can be requested.
enum ReqCmd<Req> {
/// Request for a (new) connection.
///
/// The id of the previous connection (if any) is passed as well as a
/// channel to send the reply.
NewConn(Option<u64>, ReplySender<Req>),
/// Shutdown command.
Shutdown,
}
/// This is the type of sender in [ReqCmd].
type ReplySender<Req> = oneshot::Sender<ChanResp<Req>>;
/// State of the current underlying stream transport.
#[derive(Debug)]
enum SingleConnState3<Req> {
/// No current stream transport.
None,
/// Current stream transport.
Some(Arc<stream::Connection<Req>>),
/// State that deals with an error getting a new octet stream from
/// a connection stream.
Err(ErrorState),
}
/// State associated with a failed attempt to create a new stream
/// transport.
#[derive(Clone, Debug)]
struct ErrorState {
/// The error we got from the most recent attempt.
error: Arc<std::io::Error>,
/// How many times we tried so far.
retries: u64,
/// When we got an error.
timer: Instant,
/// Time to wait before trying to create a new connection.
timeout: Duration,
}
impl<Remote, Req> Transport<Remote, Req> {
/// Creates a new transport.
fn new(
stream: Remote,
config: Config,
) -> (mpsc::Sender<ChanReq<Req>>, Self) {
let (sender, receiver) = mpsc::channel(DEF_CHAN_CAP);
(
sender,
Self {
config,
stream,
conn_state: SingleConnState3::None,
conn_id: 0,
receiver,
},
)
}
}
impl<Remote, Req: ComposeRequest> Transport<Remote, Req>
where
Remote: AsyncConnect,
Remote::Connection: AsyncRead + AsyncWrite,
Req: ComposeRequest,
{
/// Run the transport machinery.
pub async fn run(mut self) {
let mut curr_cmd: Option<ReqCmd<Req>> = None;
let mut do_stream = false;
let mut runners = FuturesUnordered::new();
let mut stream_fut: Pin<
Box<
dyn Future<
Output = Result<Remote::Connection, std::io::Error>,
> + Send,
>,
> = Box::pin(stream_nop());
let mut opt_chan = None;
loop {
if let Some(req) = curr_cmd {
assert!(!do_stream);
curr_cmd = None;
match req {
ReqCmd::NewConn(opt_id, chan) => {
if let SingleConnState3::Err(error_state) =
&self.conn_state
{
if error_state.timer.elapsed()
< error_state.timeout
{
let resp =
ChanResp::Err(error_state.error.clone());
// Ignore errors. We don't care if the receiver
// is gone
_ = chan.send(resp);
continue;
}
// Try to set up a new connection
}
// Check if the command has an id greather than the
// current id.
if let Some(id) = opt_id {
if id >= self.conn_id {
// We need a new connection. Remove the
// current one. This is the best place to
// increment conn_id.
self.conn_id += 1;
self.conn_state = SingleConnState3::None;
}
}
// If we still have a connection then we can reply
// immediately.
if let SingleConnState3::Some(conn) = &self.conn_state
{
let resp = ChanResp::Ok(ChanRespOk {
id: self.conn_id,
conn: conn.clone(),
});
// Ignore errors. We don't care if the receiver
// is gone
_ = chan.send(resp);
} else {
opt_chan = Some(chan);
stream_fut = Box::pin(self.stream.connect());
do_stream = true;
}
}
ReqCmd::Shutdown => break,
}
}
if do_stream {
let runners_empty = runners.is_empty();
loop {
tokio::select! {
res_conn = stream_fut.as_mut() => {
do_stream = false;
stream_fut = Box::pin(stream_nop());
let stream = match res_conn {
Ok(stream) => stream,
Err(error) => {
let error = Arc::new(error);
match self.conn_state {
SingleConnState3::None =>
self.conn_state =
SingleConnState3::Err(ErrorState {
error: error.clone(),
retries: 0,
timer: Instant::now(),
timeout: retry_time(0),
}),
SingleConnState3::Some(_) =>
panic!("Illegal Some state"),
SingleConnState3::Err(error_state) => {
self.conn_state =
SingleConnState3::Err(ErrorState {
error:
error_state.error.clone(),
retries: error_state.retries+1,
timer: Instant::now(),
timeout: retry_time(
error_state.retries+1),
});
}
}
let resp = ChanResp::Err(error);
let loc_opt_chan = opt_chan.take();
// Ignore errors. We don't care if the receiver
// is gone
_ = loc_opt_chan.expect("weird, no channel?")
.send(resp);
break;
}
};
let (conn, tran) = stream::Connection::with_config(
stream, self.config.stream.clone()
);
let conn = Arc::new(conn);
runners.push(Box::pin(tran.run()));
let resp = ChanResp::Ok(ChanRespOk {
id: self.conn_id,
conn: conn.clone(),
});
self.conn_state = SingleConnState3::Some(conn);
let loc_opt_chan = opt_chan.take();
// Ignore errors. We don't care if the receiver
// is gone
_ = loc_opt_chan.expect("weird, no channel?")
.send(resp);
break;
}
_ = runners.next(), if !runners_empty => {
}
}
}
continue;
}
assert!(curr_cmd.is_none());
let recv_fut = self.receiver.recv();
let runners_empty = runners.is_empty();
tokio::select! {
msg = recv_fut => {
if msg.is_none() {
// All references to the connection object have been
// dropped. Shutdown.
break;
}
curr_cmd = Some(msg.expect("None is checked before").cmd);
}
_ = runners.next(), if !runners_empty => {
}
}
}
// Avoid new queries
drop(self.receiver);
// Wait for existing stream runners to terminate
while !runners.is_empty() {
runners.next().await;
}
}
}
//------------ Utility --------------------------------------------------------
/// Compute the retry timeout based on the number of retries so far.
///
/// The computation is a random value (in microseconds) between zero and
/// two to the power of the number of retries.
fn retry_time(retries: u64) -> Duration {
let to_secs = if retries > 6 { 60 } else { 1 << retries };
let to_usecs = to_secs * 1000000;
let rnd: f64 = random();
let to_usecs = to_usecs as f64 * rnd;
Duration::from_micros(to_usecs as u64)
}
/// Helper function to create an empty future that is compatible with the
/// future returned by a connection stream.
async fn stream_nop<IO>() -> Result<IO, std::io::Error> {
Err(io::Error::new(io::ErrorKind::Other, "nop"))
}
+311
View File
@@ -0,0 +1,311 @@
//! Underlying transport protocols.
use core::future::Future;
use core::pin::Pin;
use pin_project_lite::pin_project;
use std::boxed::Box;
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::ReadBuf;
use tokio::net::{TcpStream, ToSocketAddrs, UdpSocket};
use tokio_rustls::client::TlsStream;
use tokio_rustls::rustls::{ClientConfig, ServerName};
use tokio_rustls::TlsConnector;
/// How many times do we try a new random port if we get address in use.
const RETRY_RANDOM_PORT: usize = 10;
//------------ AsyncConnect --------------------------------------------------
/// Establish a connection asynchronously.
///
///
pub trait AsyncConnect {
/// The type of an established connection.
type Connection;
/// The future establishing the connection.
type Fut: Future<Output = Result<Self::Connection, io::Error>> + Send;
/// Returns a future that establishing a connection.
fn connect(&self) -> Self::Fut;
}
//------------ TcpConnect --------------------------------------------------
/// Create new TCP connections.
#[derive(Clone, Copy, Debug)]
pub struct TcpConnect<Addr> {
/// Remote address to connect to.
addr: Addr,
}
impl<Addr> TcpConnect<Addr> {
/// Create new TCP connections.
///
/// addr is the destination address to connect to.
pub fn new(addr: Addr) -> Self {
Self { addr }
}
}
impl<Addr> AsyncConnect for TcpConnect<Addr>
where
Addr: ToSocketAddrs + Clone + Send + 'static,
{
type Connection = TcpStream;
type Fut = Pin<
Box<
dyn Future<Output = Result<Self::Connection, std::io::Error>>
+ Send,
>,
>;
fn connect(&self) -> Self::Fut {
Box::pin(TcpStream::connect(self.addr.clone()))
}
}
//------------ TlsConnect -----------------------------------------------------
/// Create new TLS connections
#[derive(Clone, Debug)]
pub struct TlsConnect<Addr> {
/// Configuration for setting up a TLS connection.
client_config: Arc<ClientConfig>,
/// Server name for certificate verification.
server_name: ServerName,
/// Remote address to connect to.
addr: Addr,
}
impl<Addr> TlsConnect<Addr> {
/// Function to create a new TLS connection stream
pub fn new(
client_config: impl Into<Arc<ClientConfig>>,
server_name: ServerName,
addr: Addr,
) -> Self {
Self {
client_config: client_config.into(),
server_name,
addr,
}
}
}
impl<Addr> AsyncConnect for TlsConnect<Addr>
where
Addr: ToSocketAddrs + Clone + Send + 'static,
{
type Connection = TlsStream<TcpStream>;
type Fut = Pin<
Box<
dyn Future<Output = Result<Self::Connection, std::io::Error>>
+ Send,
>,
>;
fn connect(&self) -> Self::Fut {
let tls_connection = TlsConnector::from(self.client_config.clone());
let server_name = self.server_name.clone();
let addr = self.addr.clone();
Box::pin(async {
let box_connection = Box::new(tls_connection);
let tcp = TcpStream::connect(addr).await?;
box_connection.connect(server_name, tcp).await
})
}
}
//------------ UdpConnect --------------------------------------------------
/// Create new TCP connections.
#[derive(Clone, Copy, Debug)]
pub struct UdpConnect {
/// Remote address to connect to.
addr: SocketAddr,
}
impl UdpConnect {
/// Create new UDP connections.
///
/// addr is the destination address to connect to.
pub fn new(addr: SocketAddr) -> Self {
Self { addr }
}
/// Bind to a random local UDP port.
async fn bind_and_connect(self) -> Result<UdpSocket, io::Error> {
let mut i = 0;
let sock = loop {
let local: SocketAddr = if self.addr.is_ipv4() {
([0u8; 4], 0).into()
} else {
([0u16; 8], 0).into()
};
match UdpSocket::bind(&local).await {
Ok(sock) => break sock,
Err(err) => {
if i == RETRY_RANDOM_PORT {
return Err(err);
} else {
i += 1
}
}
}
};
sock.connect(self.addr).await?;
Ok(sock)
}
}
impl AsyncConnect for UdpConnect {
type Connection = UdpSocket;
type Fut = Pin<
Box<
dyn Future<Output = Result<Self::Connection, std::io::Error>>
+ Send,
>,
>;
fn connect(&self) -> Self::Fut {
Box::pin(self.bind_and_connect())
}
}
//------------ AsyncDgramRecv -------------------------------------------------
/// Receive a datagram packets asynchronously.
pub trait AsyncDgramRecv {
/// Polled receive.
fn poll_recv(
&self,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<(), io::Error>>;
}
impl AsyncDgramRecv for UdpSocket {
fn poll_recv(
&self,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<(), io::Error>> {
UdpSocket::poll_recv(self, cx, buf)
}
}
//------------ AsyncDgramRecvEx -----------------------------------------------
/// Convenvience trait to turn poll_recv into an asynchronous function.
pub trait AsyncDgramRecvEx: AsyncDgramRecv {
/// Asynchronous receive function.
fn recv<'a>(&'a mut self, buf: &'a mut [u8]) -> DgramRecv<'a, Self>
where
Self: Unpin,
{
DgramRecv {
receiver: self,
buf,
}
}
}
impl<R: AsyncDgramRecv> AsyncDgramRecvEx for R {}
//------------ DgramRecv -----------------------------------------------------
pin_project! {
/// Return value of recv. This captures the future for recv.
pub struct DgramRecv<'a, R: ?Sized> {
receiver: &'a R,
buf: &'a mut [u8],
}
}
impl<R: AsyncDgramRecv + Unpin> Future for DgramRecv<'_, R> {
type Output = io::Result<usize>;
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<usize>> {
let me = self.project();
let mut buf = ReadBuf::new(me.buf);
match Pin::new(me.receiver).poll_recv(cx, &mut buf) {
Poll::Pending => return Poll::Pending,
Poll::Ready(res) => {
if let Err(err) = res {
return Poll::Ready(Err(err));
}
}
}
Poll::Ready(Ok(buf.filled().len()))
}
}
//------------ AsyncDgramSend -------------------------------------------------
/// Send a datagram packet asynchronously.
///
///
pub trait AsyncDgramSend {
/// Polled send function.
fn poll_send(
&self,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>>;
}
impl AsyncDgramSend for UdpSocket {
fn poll_send(
&self,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
UdpSocket::poll_send(self, cx, buf)
}
}
//------------ AsyncDgramSendEx ----------------------------------------------
/// Convenience trait that turns poll_send into an asynchronous function.
pub trait AsyncDgramSendEx: AsyncDgramSend {
/// Asynchronous function to send a packet.
fn send<'a>(&'a self, buf: &'a [u8]) -> DgramSend<'a, Self>
where
Self: Unpin,
{
DgramSend { sender: self, buf }
}
}
impl<S: AsyncDgramSend> AsyncDgramSendEx for S {}
//------------ DgramSend -----------------------------------------------------
/// This is the return value of send. It captures the future for send.
pub struct DgramSend<'a, S: ?Sized> {
/// The datagram send object.
sender: &'a S,
/// The buffer that needs to be sent.
buf: &'a [u8],
}
impl<S: AsyncDgramSend + Unpin> Future for DgramSend<'_, S> {
type Output = io::Result<usize>;
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<usize>> {
Pin::new(self.sender).poll_send(cx, self.buf)
}
}
+762
View File
@@ -0,0 +1,762 @@
//! A transport that multiplexes requests over multiple redundant transports.
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
use bytes::Bytes;
use futures_util::stream::FuturesUnordered;
use futures_util::StreamExt;
use octseq::Octets;
use rand::random;
use std::boxed::Box;
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::vec::Vec;
use tokio::sync::{mpsc, oneshot};
use tokio::time::{sleep_until, Duration, Instant};
use crate::base::iana::OptRcode;
use crate::base::Message;
use crate::net::client::request::{Error, GetResponse, SendRequest};
/*
Basic algorithm:
- keep track of expected response time for every upstream
- start with the upstream with the lowest expected response time
- set a timer to the expect response time.
- if the timer expires before reply arrives, send the query to the next lowest
and set a timer
- when a reply arrives update the expected response time for the relevant
upstream and for the ones that failed.
Based on a random number generator:
- pick a different upstream rather then the best but set the timer to the
expected response time of the best.
*/
/// Capacity of the channel that transports [ChanReq].
const DEF_CHAN_CAP: usize = 8;
/// Time in milliseconds for the initial response time estimate.
const DEFAULT_RT_MS: u64 = 300;
/// The initial response time estimate for unused connections.
const DEFAULT_RT: Duration = Duration::from_millis(DEFAULT_RT_MS);
/// Maintain a moving average for the measured response time and the
/// square of that. The window is SMOOTH_N.
const SMOOTH_N: f64 = 8.;
/// Chance to probe a worse connection.
const PROBE_P: f64 = 0.05;
/// Avoid sending two requests at the same time.
///
/// When a worse connection is probed, give it a slight head start.
const PROBE_RT: Duration = Duration::from_millis(1);
//------------ Config ---------------------------------------------------------
/// User configuration variables.
#[derive(Clone, Copy, Debug, Default)]
pub struct Config {
/// Defer transport errors.
pub defer_transport_error: bool,
/// Defer replies that report Refused.
pub defer_refused: bool,
/// Defer replies that report ServFail.
pub defer_servfail: bool,
}
//------------ Connection -----------------------------------------------------
/// This type represents a transport connection.
#[derive(Debug)]
pub struct Connection<Req> {
/// User configuation.
config: Config,
/// To send a request to the runner.
sender: mpsc::Sender<ChanReq<Req>>,
}
impl<Req: Clone + Debug + Send + Sync + 'static> Connection<Req> {
/// Create a new connection.
pub fn new() -> (Self, Transport<Req>) {
Self::with_config(Default::default())
}
/// Create a new connection with a given config.
pub fn with_config(config: Config) -> (Self, Transport<Req>) {
let (sender, receiver) = mpsc::channel(DEF_CHAN_CAP);
(Self { config, sender }, Transport::new(receiver))
}
/// Add a transport connection.
pub async fn add(
&self,
conn: Box<dyn SendRequest<Req> + Send + Sync>,
) -> Result<(), Error> {
let (tx, rx) = oneshot::channel();
self.sender
.send(ChanReq::Add(AddReq { conn, tx }))
.await
.expect("send should not fail");
rx.await.expect("receive should not fail")
}
/// Implementation of the query method.
async fn request_impl(
self,
request_msg: Req,
) -> Result<Message<Bytes>, Error> {
let (tx, rx) = oneshot::channel();
self.sender
.send(ChanReq::GetRT(RTReq { tx }))
.await
.expect("send should not fail");
let conn_rt = rx.await.expect("receive should not fail")?;
Query::new(self.config, request_msg, conn_rt, self.sender.clone())
.get_response()
.await
}
}
impl<Req> Clone for Connection<Req> {
fn clone(&self) -> Self {
Self {
config: self.config,
sender: self.sender.clone(),
}
}
}
impl<Req: Clone + Debug + Send + Sync + 'static> SendRequest<Req>
for Connection<Req>
{
fn send_request(&self, request_msg: Req) -> Box<dyn GetResponse + Send> {
Box::new(Request {
fut: Box::pin(self.clone().request_impl(request_msg)),
})
}
}
//------------ Request -------------------------------------------------------
/// An active request.
pub struct Request {
/// The underlying future.
fut: Pin<Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send>>,
}
impl Request {
/// Async function that waits for the future stored in Query to complete.
async fn get_response_impl(&mut self) -> Result<Message<Bytes>, Error> {
(&mut self.fut).await
}
}
impl GetResponse for Request {
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
> {
Box::pin(self.get_response_impl())
}
}
impl Debug for Request {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
f.debug_struct("Request")
.field("fut", &format_args!("_"))
.finish()
}
}
//------------ Query --------------------------------------------------------
/// This type represents an active query request.
#[derive(Debug)]
pub struct Query<Req> {
/// User configuration.
config: Config,
/// The state of the query
state: QueryState,
/// The reuqest message
request_msg: Req,
/// List of connections identifiers and estimated response times.
conn_rt: Vec<ConnRT>,
/// Channel to send requests to the run function.
sender: mpsc::Sender<ChanReq<Req>>,
/// List of futures for outstanding requests.
fut_list:
FuturesUnordered<Pin<Box<dyn Future<Output = FutListOutput> + Send>>>,
/// Transport error that should be reported if nothing better shows
/// up.
deferred_transport_error: Option<Error>,
/// Reply that should be returned to the user if nothing better shows
/// up.
deferred_reply: Option<Message<Bytes>>,
/// The result from one of the connectons.
result: Option<Result<Message<Bytes>, Error>>,
/// Index of the connection that returned a result.
res_index: usize,
}
/// The various states a query can be in.
#[derive(Debug)]
enum QueryState {
/// The initial state
Init,
/// Start a request on a specific connection.
Probe(usize),
/// Report the response time for a specific index in the list.
Report(usize),
/// Wait for one of the requests to finish.
Wait,
}
/// The commands that can be sent to the run function.
enum ChanReq<Req> {
/// Add a connection
Add(AddReq<Req>),
/// Get the list of estimated response times for all connections
GetRT(RTReq),
/// Start a query
Query(RequestReq<Req>),
/// Report how long it took to get a response
Report(TimeReport),
/// Report that a connection failed to provide a timely response
Failure(TimeReport),
}
impl<Req> Debug for ChanReq<Req> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("ChanReq").finish()
}
}
/// Request to add a new connection
struct AddReq<Req> {
/// New connection to add
conn: Box<dyn SendRequest<Req> + Send + Sync>,
/// Channel to send the reply to
tx: oneshot::Sender<AddReply>,
}
/// Reply to an Add request
type AddReply = Result<(), Error>;
/// Request to give the estimated response times for all connections
struct RTReq /*<Octs>*/ {
/// Channel to send the reply to
tx: oneshot::Sender<RTReply>,
}
/// Reply to a RT request
type RTReply = Result<Vec<ConnRT>, Error>;
/// Request to start a request
struct RequestReq<Req> {
/// Identifier of connection
id: u64,
/// Request message
request_msg: Req,
/// Channel to send the reply to
tx: oneshot::Sender<RequestReply>,
}
impl<Req: Debug> Debug for RequestReq<Req> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("RequestReq")
.field("id", &self.id)
.field("request_msg", &self.request_msg)
.finish()
}
}
/// Reply to a request request.
type RequestReply = Result<Box<dyn GetResponse + Send>, Error>;
/// Report the amount of time until success or failure.
#[derive(Debug)]
struct TimeReport {
/// Identifier of the transport connection.
id: u64,
/// Time spend waiting for a reply.
elapsed: Duration,
}
/// Connection statistics to compute the estimated response time.
struct ConnStats {
/// Aproximation of the windowed average of response times.
mean: f64,
/// Aproximation of the windowed average of the square of response times.
mean_sq: f64,
}
/// Data required to schedule requests and report timing results.
#[derive(Clone, Debug)]
struct ConnRT {
/// Estimated response time.
est_rt: Duration,
/// Identifier of the connection.
id: u64,
/// Start of a request using this connection.
start: Option<Instant>,
}
/// Result of the futures in fut_list.
type FutListOutput = (usize, Result<Message<Bytes>, Error>);
impl<Req: Clone + Send + Sync + 'static> Query<Req> {
/// Create a new query object.
fn new(
config: Config,
request_msg: Req,
mut conn_rt: Vec<ConnRT>,
sender: mpsc::Sender<ChanReq<Req>>,
) -> Self {
let conn_rt_len = conn_rt.len();
conn_rt.sort_unstable_by(conn_rt_cmp);
// Do we want to probe a less performant upstream?
if conn_rt_len > 1 && random::<f64>() < PROBE_P {
let index: usize = 1 + random::<usize>() % (conn_rt_len - 1);
conn_rt[index].est_rt = PROBE_RT;
// Sort again
conn_rt.sort_unstable_by(conn_rt_cmp);
}
Self {
config,
request_msg,
conn_rt,
sender,
state: QueryState::Init,
fut_list: FuturesUnordered::new(),
deferred_transport_error: None,
deferred_reply: None,
result: None,
res_index: 0,
}
}
/// Implementation of get_response.
async fn get_response(&mut self) -> Result<Message<Bytes>, Error> {
loop {
match self.state {
QueryState::Init => {
if self.conn_rt.is_empty() {
return Err(Error::NoTransportAvailable);
}
self.state = QueryState::Probe(0);
continue;
}
QueryState::Probe(ind) => {
self.conn_rt[ind].start = Some(Instant::now());
let fut = start_request(
ind,
self.conn_rt[ind].id,
self.sender.clone(),
self.request_msg.clone(),
);
self.fut_list.push(Box::pin(fut));
let timeout = Instant::now() + self.conn_rt[ind].est_rt;
loop {
tokio::select! {
res = self.fut_list.next() => {
let res = res.expect("res should not be empty");
match res.1 {
Err(ref err) => {
if self.config.defer_transport_error {
if self.deferred_transport_error.is_none() {
self.deferred_transport_error = Some(err.clone());
}
if res.0 == ind {
// The current upstream finished,
// try the next one, if any.
self.state =
if ind+1 < self.conn_rt.len() {
QueryState::Probe(ind+1)
}
else
{
QueryState::Wait
};
// Break out of receive loop
break;
}
// Just continue receiving
continue;
}
// Return error to the user.
}
Ok(ref msg) => {
if skip(msg, &self.config) {
if self.deferred_reply.is_none() {
self.deferred_reply = Some(msg.clone());
}
if res.0 == ind {
// The current upstream finished,
// try the next one, if any.
self.state =
if ind+1 < self.conn_rt.len() {
QueryState::Probe(ind+1)
}
else
{
QueryState::Wait
};
// Break out of receive loop
break;
}
// Just continue receiving
continue;
}
// Now we have a reply that can be
// returned to the user.
}
}
self.result = Some(res.1);
self.res_index= res.0;
self.state = QueryState::Report(0);
// Break out of receive loop
break;
}
_ = sleep_until(timeout) => {
// Move to the next Probe state if there
// are more upstreams to try, otherwise
// move to the Wait state.
self.state =
if ind+1 < self.conn_rt.len() {
QueryState::Probe(ind+1)
}
else {
QueryState::Wait
};
// Break out of receive loop
break;
}
}
}
// Continue with state machine loop
continue;
}
QueryState::Report(ind) => {
if ind >= self.conn_rt.len()
|| self.conn_rt[ind].start.is_none()
{
// Nothing more to report. Return result.
let res = self
.result
.take()
.expect("result should not be empty");
return res;
}
let start = self.conn_rt[ind]
.start
.expect("start time should not be empty");
let elapsed = start.elapsed();
let time_report = TimeReport {
id: self.conn_rt[ind].id,
elapsed,
};
let report = if ind == self.res_index {
// Succesfull entry
ChanReq::Report(time_report)
} else {
// Failed entry
ChanReq::Failure(time_report)
};
// Send could fail but we don't care.
let _ = self.sender.send(report).await;
self.state = QueryState::Report(ind + 1);
continue;
}
QueryState::Wait => {
loop {
if self.fut_list.is_empty() {
// We have nothing left. There should be a reply or
// an error. Prefer a reply over an error.
if self.deferred_reply.is_some() {
let msg = self
.deferred_reply
.take()
.expect("just checked for Some");
return Ok(msg);
}
if self.deferred_transport_error.is_some() {
let err = self
.deferred_transport_error
.take()
.expect("just checked for Some");
return Err(err);
}
panic!("either deferred_reply or deferred_error should be present");
}
let res = self.fut_list.next().await;
let res = res.expect("res should not be empty");
match res.1 {
Err(ref err) => {
if self.config.defer_transport_error {
if self.deferred_transport_error.is_none()
{
self.deferred_transport_error =
Some(err.clone());
}
// Just continue with the next future, or
// finish if fut_list is empty.
continue;
}
// Return error to the user.
}
Ok(ref msg) => {
if skip(msg, &self.config) {
if self.deferred_reply.is_none() {
self.deferred_reply =
Some(msg.clone());
}
// Just continue with the next future, or
// finish if fut_list is empty.
continue;
}
// Return reply to user.
}
}
self.result = Some(res.1);
self.res_index = res.0;
self.state = QueryState::Report(0);
// Break out of loop to continue with the state machine
break;
}
continue;
}
}
}
}
}
//------------ Transport -----------------------------------------------------
/// Type that actually implements the connection.
#[derive(Debug)]
pub struct Transport<Req> {
/// Receive side of the channel used by the runner.
receiver: mpsc::Receiver<ChanReq<Req>>,
}
impl<'a, Req: Clone + Send + Sync + 'static> Transport<Req> {
/// Implementation of the new method.
fn new(receiver: mpsc::Receiver<ChanReq<Req>>) -> Self {
Self { receiver }
}
/// Run method.
pub async fn run(mut self) {
let mut next_id: u64 = 10;
let mut conn_stats: Vec<ConnStats> = Vec::new();
let mut conn_rt: Vec<ConnRT> = Vec::new();
let mut conns: Vec<Box<dyn SendRequest<Req> + Send + Sync>> =
Vec::new();
loop {
let req = match self.receiver.recv().await {
Some(req) => req,
None => break, // All references to connection objects are
// dropped. Shutdown.
};
match req {
ChanReq::Add(add_req) => {
let id = next_id;
next_id += 1;
conn_stats.push(ConnStats {
mean: (DEFAULT_RT_MS as f64) / 1000.,
mean_sq: 0.,
});
conn_rt.push(ConnRT {
id,
est_rt: DEFAULT_RT,
start: None,
});
conns.push(add_req.conn);
// Don't care if send fails
let _ = add_req.tx.send(Ok(()));
}
ChanReq::GetRT(rt_req) => {
// Don't care if send fails
let _ = rt_req.tx.send(Ok(conn_rt.clone()));
}
ChanReq::Query(request_req) => {
let opt_ind =
conn_rt.iter().position(|e| e.id == request_req.id);
match opt_ind {
Some(ind) => {
let query = conns[ind]
.send_request(request_req.request_msg);
// Don't care if send fails
let _ = request_req.tx.send(Ok(query));
}
None => {
// Don't care if send fails
let _ = request_req
.tx
.send(Err(Error::RedundantTransportNotFound));
}
}
}
ChanReq::Report(time_report) => {
let opt_ind =
conn_rt.iter().position(|e| e.id == time_report.id);
if let Some(ind) = opt_ind {
let elapsed = time_report.elapsed.as_secs_f64();
conn_stats[ind].mean +=
(elapsed - conn_stats[ind].mean) / SMOOTH_N;
let elapsed_sq = elapsed * elapsed;
conn_stats[ind].mean_sq +=
(elapsed_sq - conn_stats[ind].mean_sq) / SMOOTH_N;
let mean = conn_stats[ind].mean;
let var = conn_stats[ind].mean_sq - mean * mean;
let std_dev =
if var < 0. { 0. } else { f64::sqrt(var) };
let est_rt = mean + 3. * std_dev;
conn_rt[ind].est_rt = Duration::from_secs_f64(est_rt);
}
}
ChanReq::Failure(time_report) => {
let opt_ind =
conn_rt.iter().position(|e| e.id == time_report.id);
if let Some(ind) = opt_ind {
let elapsed = time_report.elapsed.as_secs_f64();
if elapsed < conn_stats[ind].mean {
// Do not update the mean if a
// failure took less time than the
// current mean.
continue;
}
conn_stats[ind].mean +=
(elapsed - conn_stats[ind].mean) / SMOOTH_N;
let elapsed_sq = elapsed * elapsed;
conn_stats[ind].mean_sq +=
(elapsed_sq - conn_stats[ind].mean_sq) / SMOOTH_N;
let mean = conn_stats[ind].mean;
let var = conn_stats[ind].mean_sq - mean * mean;
let std_dev =
if var < 0. { 0. } else { f64::sqrt(var) };
let est_rt = mean + 3. * std_dev;
conn_rt[ind].est_rt = Duration::from_secs_f64(est_rt);
}
}
}
}
}
}
//------------ Utility --------------------------------------------------------
/// Async function to send a request and wait for the reply.
///
/// This gives a single future that we can put in a list.
async fn start_request<Req>(
index: usize,
id: u64,
sender: mpsc::Sender<ChanReq<Req>>,
request_msg: Req,
) -> (usize, Result<Message<Bytes>, Error>) {
let (tx, rx) = oneshot::channel();
sender
.send(ChanReq::Query(RequestReq {
id,
request_msg,
tx,
}))
.await
.expect("send is expected to work");
let mut request = match rx.await.expect("receive is expected to work") {
Err(err) => return (index, Err(err)),
Ok(request) => request,
};
let reply = request.get_response().await;
(index, reply)
}
/// Compare ConnRT elements based on estimated response time.
fn conn_rt_cmp(e1: &ConnRT, e2: &ConnRT) -> Ordering {
e1.est_rt.cmp(&e2.est_rt)
}
/// Return if this reply should be skipped or not.
fn skip<Octs: Octets>(msg: &Message<Octs>, config: &Config) -> bool {
// Check if we actually need to check.
if !config.defer_refused && !config.defer_servfail {
return false;
}
let opt_rcode = get_opt_rcode(msg);
// OptRcode needs PartialEq
if let OptRcode::Refused = opt_rcode {
if config.defer_refused {
return true;
}
}
if let OptRcode::ServFail = opt_rcode {
if config.defer_servfail {
return true;
}
}
false
}
/// Get the extended rcode of a message.
fn get_opt_rcode<Octs: Octets>(msg: &Message<Octs>) -> OptRcode {
let opt = msg.opt();
match opt {
Some(opt) => opt.rcode(msg.header()),
None => {
// Convert Rcode to OptRcode, this should be part of
// OptRcode
OptRcode::from_int(msg.header().rcode().to_int() as u16)
}
}
}
+410
View File
@@ -0,0 +1,410 @@
//! Constructing and sending requests.
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
use crate::base::iana::Rcode;
use crate::base::message::CopyRecordsError;
use crate::base::message_builder::{
AdditionalBuilder, MessageBuilder, PushError, StaticCompressor,
};
use crate::base::opt::{ComposeOptData, LongOptData, OptRecord};
use crate::base::wire::Composer;
use crate::base::{Header, Message, ParsedDname, Rtype};
use crate::rdata::AllRecordData;
use bytes::Bytes;
use octseq::Octets;
use std::boxed::Box;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::vec::Vec;
use std::{error, fmt};
//------------ ComposeRequest ------------------------------------------------
/// A trait that allows composing a request as a series.
pub trait ComposeRequest: Debug + Send + Sync {
/// Appends the final message to a provided composer.
fn append_message<Target: Composer>(
&self,
target: &mut Target,
) -> Result<(), CopyRecordsError>;
/// Create a message that captures the recorded changes.
fn to_message(&self) -> Message<Vec<u8>>;
/// Create a message that captures the recorded changes and convert to
/// a Vec.
fn to_vec(&self) -> Vec<u8>;
/// Return a reference to a mutable Header to record changes to the header.
fn header_mut(&mut self) -> &mut Header;
/// Set the UDP payload size.
fn set_udp_payload_size(&mut self, value: u16);
/// Add an EDNS option.
fn add_opt(
&mut self,
opt: &impl ComposeOptData,
) -> Result<(), LongOptData>;
/// Returns whether a message is an answer to the request.
fn is_answer(&self, answer: &Message<[u8]>) -> bool;
}
//------------ SendRequest ---------------------------------------------------
/// Trait for starting a DNS request based on a request composer.
///
/// In the future, the return type of request should become an associated type.
/// However, the use of 'dyn Request' in redundant currently prevents that.
pub trait SendRequest<CR> {
/// Request function that takes a ComposeRequest type.
fn send_request(&self, request_msg: CR) -> Box<dyn GetResponse + Send>;
}
//------------ GetResponse ---------------------------------------------------
/// Trait for getting the result of a DNS query.
///
/// In the future, the return type of get_response should become an associated
/// type. However, too many uses of 'dyn GetResponse' currently prevent that.
pub trait GetResponse: Debug {
/// Get the result of a DNS request.
///
/// This function is intended to be cancel safe.
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
>;
}
//------------ RequestMessage ------------------------------------------------
/// Object that implements the ComposeRequest trait for a Message object.
#[derive(Clone, Debug)]
pub struct RequestMessage<Octs: AsRef<[u8]>> {
/// Base message.
msg: Message<Octs>,
/// New header.
header: Header,
/// The OPT record to add if required.
opt: Option<OptRecord<Vec<u8>>>,
}
impl<Octs: AsRef<[u8]> + Debug + Octets> RequestMessage<Octs> {
/// Create a new BMB object.
pub fn new(msg: impl Into<Message<Octs>>) -> Self {
let msg = msg.into();
let header = msg.header();
Self {
msg,
header,
opt: None,
}
}
/// Returns a mutable reference to the OPT record.
///
/// Adds one if necessary.
fn opt_mut(&mut self) -> &mut OptRecord<Vec<u8>> {
self.opt.get_or_insert_with(Default::default)
}
/// Appends the message to a composer.
fn append_message_impl<Target: Composer>(
&self,
mut target: MessageBuilder<Target>,
) -> Result<AdditionalBuilder<Target>, CopyRecordsError> {
let source = &self.msg;
*target.header_mut() = self.header;
let source = source.question();
let mut target = target.question();
for rr in source {
target.push(rr?)?;
}
let mut source = source.answer()?;
let mut target = target.answer();
for rr in &mut source {
let rr = rr?
.into_record::<AllRecordData<_, ParsedDname<_>>>()?
.expect("record expected");
target.push(rr)?;
}
let mut source =
source.next_section()?.expect("section should be present");
let mut target = target.authority();
for rr in &mut source {
let rr = rr?
.into_record::<AllRecordData<_, ParsedDname<_>>>()?
.expect("record expected");
target.push(rr)?;
}
let source =
source.next_section()?.expect("section should be present");
let mut target = target.additional();
for rr in source {
let rr = rr?;
if rr.rtype() != Rtype::Opt {
let rr = rr
.into_record::<AllRecordData<_, ParsedDname<_>>>()?
.expect("record expected");
target.push(rr)?;
}
}
if let Some(opt) = self.opt.as_ref() {
target.push(opt.as_record())?;
}
Ok(target)
}
/// Create new message based on the changes to the base message.
fn to_message_impl(&self) -> Result<Message<Vec<u8>>, Error> {
let target =
MessageBuilder::from_target(StaticCompressor::new(Vec::new()))
.expect("Vec is expected to have enough space");
let target = self.append_message_impl(target)?;
// It would be nice to use .builder() here. But that one deletes all
// section. We have to resort to .as_builder() which gives a
// reference and then .clone()
let result = target.as_builder().clone();
let msg = Message::from_octets(result.finish().into_target()).expect(
"Message should be able to parse output from MessageBuilder",
);
Ok(msg)
}
}
impl<Octs: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync + 'static>
ComposeRequest for RequestMessage<Octs>
{
fn append_message<Target: Composer>(
&self,
target: &mut Target,
) -> Result<(), CopyRecordsError> {
let target = MessageBuilder::from_target(target)
.map_err(|_| CopyRecordsError::Push(PushError::ShortBuf))?;
self.append_message_impl(target)?;
Ok(())
}
fn to_vec(&self) -> Vec<u8> {
let msg = self.to_message();
msg.as_octets().clone()
}
fn to_message(&self) -> Message<Vec<u8>> {
self.to_message_impl().unwrap()
}
fn header_mut(&mut self) -> &mut Header {
&mut self.header
}
fn set_udp_payload_size(&mut self, value: u16) {
self.opt_mut().set_udp_payload_size(value);
}
fn add_opt(
&mut self,
opt: &impl ComposeOptData,
) -> Result<(), LongOptData> {
self.opt_mut().push(opt).map_err(|e| e.unlimited_buf())
}
fn is_answer(&self, answer: &Message<[u8]>) -> bool {
let answer_header = answer.header();
let answer_hcounts = answer.header_counts();
// First check qr is set and IDs match.
if !answer_header.qr() || answer_header.id() != self.header.id() {
return false;
}
// If the result is an error, then the question section can be empty.
// In that case we require all other sections to be empty as well.
if answer_header.rcode() != Rcode::NoError
&& answer_hcounts.qdcount() == 0
&& answer_hcounts.ancount() == 0
&& answer_hcounts.nscount() == 0
&& answer_hcounts.arcount() == 0
{
// We can accept this as a valid reply.
return true;
}
// Now the question section in the reply has to be the same as in the
// query.
if answer_hcounts.qdcount() != self.msg.header_counts().qdcount() {
false
} else {
answer.question() == self.msg.for_slice().question()
}
}
}
//------------ Error ---------------------------------------------------------
/// Error type for client transports.
#[derive(Clone, Debug)]
pub enum Error {
/// Connection was already closed.
ConnectionClosed,
/// The OPT record has become too long.
OptTooLong,
/// PushError from MessageBuilder.
MessageBuilderPushError,
/// ParseError from Message.
MessageParseError,
/// Underlying transport not found in redundant connection
RedundantTransportNotFound,
/// Octet sequence too short to be a valid DNS message.
ShortMessage,
/// Message too long for stream transport.
StreamLongMessage,
/// Stream transport closed because it was idle (for too long).
StreamIdleTimeout,
/// Error receiving a reply.
//
StreamReceiveError,
/// Reading from stream gave an error.
StreamReadError(Arc<std::io::Error>),
/// Reading from stream took too long.
StreamReadTimeout,
/// Too many outstand queries on a single stream transport.
StreamTooManyOutstandingQueries,
/// Writing to a stream gave an error.
StreamWriteError(Arc<std::io::Error>),
/// Reading for a stream ended unexpectedly.
StreamUnexpectedEndOfData,
/// Reply does not match the query.
WrongReplyForQuery,
/// No transport available to transmit request.
NoTransportAvailable,
/// An error happened in the datagram transport.
Dgram(Arc<super::dgram::QueryError>),
}
impl From<LongOptData> for Error {
fn from(_: LongOptData) -> Self {
Self::OptTooLong
}
}
impl From<super::dgram::QueryError> for Error {
fn from(err: super::dgram::QueryError) -> Self {
Self::Dgram(err.into())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::ConnectionClosed => write!(f, "connection closed"),
Error::OptTooLong => write!(f, "OPT record is too long"),
Error::MessageBuilderPushError => {
write!(f, "PushError from MessageBuilder")
}
Error::MessageParseError => write!(f, "ParseError from Message"),
Error::RedundantTransportNotFound => write!(
f,
"Underlying transport not found in redundant connection"
),
Error::ShortMessage => {
write!(f, "octet sequence to short to be a valid message")
}
Error::StreamLongMessage => {
write!(f, "message too long for stream transport")
}
Error::StreamIdleTimeout => {
write!(f, "stream was idle for too long")
}
Error::StreamReceiveError => write!(f, "error receiving a reply"),
Error::StreamReadError(_) => {
write!(f, "error reading from stream")
}
Error::StreamReadTimeout => {
write!(f, "timeout reading from stream")
}
Error::StreamTooManyOutstandingQueries => {
write!(f, "too many outstanding queries on stream")
}
Error::StreamWriteError(_) => {
write!(f, "error writing to stream")
}
Error::StreamUnexpectedEndOfData => {
write!(f, "unexpected end of data")
}
Error::WrongReplyForQuery => {
write!(f, "reply does not match query")
}
Error::NoTransportAvailable => {
write!(f, "no transport available")
}
Error::Dgram(err) => fmt::Display::fmt(err, f),
}
}
}
impl From<CopyRecordsError> for Error {
fn from(err: CopyRecordsError) -> Self {
match err {
CopyRecordsError::Parse(_) => Self::MessageParseError,
CopyRecordsError::Push(_) => Self::MessageBuilderPushError,
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::ConnectionClosed => None,
Error::OptTooLong => None,
Error::MessageBuilderPushError => None,
Error::MessageParseError => None,
Error::RedundantTransportNotFound => None,
Error::ShortMessage => None,
Error::StreamLongMessage => None,
Error::StreamIdleTimeout => None,
Error::StreamReceiveError => None,
Error::StreamReadError(e) => Some(e),
Error::StreamReadTimeout => None,
Error::StreamTooManyOutstandingQueries => None,
Error::StreamWriteError(e) => Some(e),
Error::StreamUnexpectedEndOfData => None,
Error::WrongReplyForQuery => None,
Error::NoTransportAvailable => None,
Error::Dgram(err) => Some(err),
}
}
}
+897
View File
@@ -0,0 +1,897 @@
//! A client transport using a stream socket.
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
// RFC 7766 describes DNS over TCP
// RFC 7828 describes the edns-tcp-keepalive option
// TODO:
// - errors
// - connect errors? Retry after connection refused?
// - server errors
// - ID out of range
// - ID not in use
// - reply for wrong query
// - timeouts
// - request timeout
// - create new connection after end/failure of previous one
use crate::base::message::Message;
use crate::base::message_builder::StreamTarget;
use crate::base::opt::{AllOptData, OptRecord, TcpKeepalive};
use crate::net::client::request::{
ComposeRequest, Error, GetResponse, SendRequest,
};
use bytes;
use bytes::{Bytes, BytesMut};
use core::cmp;
use core::convert::From;
use octseq::Octets;
use std::boxed::Box;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::vec::Vec;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::sync::{mpsc, oneshot};
use tokio::time::sleep;
//------------ Configuration Constants ----------------------------------------
/// Default response timeout.
///
/// Note: nsd has 120 seconds, unbound has 3 seconds.
const DEF_RESPONSE_TIMEOUT: Duration = Duration::from_secs(19);
/// Minimum configuration value for the response timeout.
const MIN_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1);
/// Maximum configuration value for the response timeout.
const MAX_RESPONSE_TIMEOUT: Duration = Duration::from_secs(600);
/// Capacity of the channel that transports `ChanReq`s.
const DEF_CHAN_CAP: usize = 8;
/// Capacity of a private channel dispatching responses.
const READ_REPLY_CHAN_CAP: usize = 8;
//------------ Config ---------------------------------------------------------
/// Configuration for a stream transport connection.
#[derive(Clone, Debug)]
pub struct Config {
/// Response timeout.
response_timeout: Duration,
}
impl Config {
/// Creates a new, default config.
pub fn new() -> Self {
Default::default()
}
/// Returns the response timeout.
///
/// This is the amount of time to wait on a non-idle connection for a
/// response to an outstanding request.
pub fn response_timeout(&self) -> Duration {
self.response_timeout
}
/// Sets the response timeout.
///
/// Excessive values are quietly trimmed.
//
// XXX Maybe thats wrong and we should rather return an error?
pub fn set_response_timeout(&mut self, timeout: Duration) {
self.response_timeout = cmp::max(
cmp::min(timeout, MAX_RESPONSE_TIMEOUT),
MIN_RESPONSE_TIMEOUT,
)
}
}
impl Default for Config {
fn default() -> Self {
Self {
response_timeout: DEF_RESPONSE_TIMEOUT,
}
}
}
//------------ Connection -----------------------------------------------------
/// A connection to a single stream transport.
#[derive(Debug)]
pub struct Connection<Req> {
/// The sender half of the request channel.
sender: mpsc::Sender<ChanReq<Req>>,
}
impl<Req> Connection<Req> {
/// Creates a new stream transport with default configuration.
///
/// Returns a connection and a future that drives the transport using
/// the provided stream. This future needs to be run while any queries
/// are active. This is most easly achieved by spawning it into a runtime.
/// It terminates when the last connection is dropped.
pub fn new<Stream>(stream: Stream) -> (Self, Transport<Stream, Req>) {
Self::with_config(stream, Default::default())
}
/// Creates a new stream transport with the given configuration.
///
/// Returns a connection and a future that drives the transport using
/// the provided stream. This future needs to be run while any queries
/// are active. This is most easly achieved by spawning it into a runtime.
/// It terminates when the last connection is dropped.
pub fn with_config<Stream>(
stream: Stream,
config: Config,
) -> (Self, Transport<Stream, Req>) {
let (sender, transport) = Transport::new(stream, config);
(Self { sender }, transport)
}
}
impl<Req: ComposeRequest + 'static> Connection<Req> {
/// Start a DNS request.
///
/// This function takes a precomposed message as a parameter and
/// returns a [ReqRepl] object wrapped in a [Result].
async fn handle_request_impl(
self,
msg: Req,
) -> Result<Message<Bytes>, Error> {
let (sender, receiver) = oneshot::channel();
let req = ChanReq { sender, msg };
self.sender.send(req).await.map_err(|_| {
// Send error. The receiver is gone, this means that the
// connection is closed.
Error::ConnectionClosed
})?;
receiver.await.map_err(|_| Error::StreamReceiveError)?
}
/// Returns a request handler for this connection.
pub fn get_request(&self, request_msg: Req) -> Request {
Request {
fut: Box::pin(self.clone().handle_request_impl(request_msg)),
}
}
}
impl<Req> Clone for Connection<Req> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
}
}
}
impl<Req: ComposeRequest + Clone + 'static> SendRequest<Req>
for Connection<Req>
{
fn send_request(&self, request_msg: Req) -> Box<dyn GetResponse + Send> {
Box::new(self.get_request(request_msg))
}
}
//------------ Request -------------------------------------------------------
/// An active request.
pub struct Request {
/// The underlying future.
fut: Pin<Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send>>,
}
impl Request {
/// Async function that waits for the future stored in Request to complete.
async fn get_response_impl(&mut self) -> Result<Message<Bytes>, Error> {
(&mut self.fut).await
}
}
impl GetResponse for Request {
fn get_response(
&mut self,
) -> Pin<
Box<dyn Future<Output = Result<Message<Bytes>, Error>> + Send + '_>,
> {
Box::pin(self.get_response_impl())
}
}
impl Debug for Request {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Request")
.field("fut", &format_args!("_"))
.finish()
}
}
//------------ Transport -----------------------------------------------------
/// The underlying machinery of a stream transport.
#[derive(Debug)]
pub struct Transport<Stream, Req> {
/// The stream socket towards the remove end.
stream: Stream,
/// Transport configuration.
config: Config,
/// The receiver half of request channel.
receiver: mpsc::Receiver<ChanReq<Req>>,
}
/// A message from a `Request` to start a new request.
#[derive(Debug)]
struct ChanReq<Req> {
/// DNS request message
msg: Req,
/// Sender to send result back to [Request]
sender: ReplySender,
}
/// This is the type of sender in [ChanReq].
type ReplySender = oneshot::Sender<ChanResp>;
/// A message back to `Request` returning a response.
type ChanResp = Result<Message<Bytes>, Error>;
/// Internal datastructure of [Transport::run] to keep track of
/// the status of the connection.
// The types Status and ConnState are only used in Transport
struct Status {
/// State of the connection.
state: ConnState,
/// Do we need to include edns-tcp-keepalive in an outogoing request.
///
/// Typically this is true at the start of the connection and gets
/// cleared when we successfully managed to include the option in a
/// request.
send_keepalive: bool,
/// Time we are allow to keep the connection open when idle.
///
/// Initially we assume that the idle timeout is zero. A received
/// edns-tcp-keepalive option may change that.
idle_timeout: Option<Duration>,
}
/// Status of the connection. Used in [Status].
enum ConnState {
/// The connection is in this state from the start and when at least
/// one active DNS request is present.
///
/// The instant contains the time of the first request or the
/// most recent response that was received.
Active(Option<Instant>),
/// This state represent a connection that went idle and has an
/// idle timeout.
///
/// The instant contains the time the connection went idle.
Idle(Instant),
/// This state represent an idle connection where either there was no
/// idle timeout or the idle timer expired.
IdleTimeout,
/// A read error occurred.
ReadError(Error),
/// It took too long to receive a response.
ReadTimeout,
/// A write error occurred.
WriteError(Error),
}
impl<Stream, Req> Transport<Stream, Req> {
/// Creates a new transport.
fn new(
stream: Stream,
config: Config,
) -> (mpsc::Sender<ChanReq<Req>>, Self) {
let (sender, receiver) = mpsc::channel(DEF_CHAN_CAP);
(
sender,
Self {
config,
stream,
receiver,
},
)
}
}
impl<Stream, Req> Transport<Stream, Req>
where
Stream: AsyncRead + AsyncWrite,
Req: ComposeRequest,
{
/// Run the transport machinery.
pub async fn run(mut self) {
let (reply_sender, mut reply_receiver) =
mpsc::channel::<Message<Bytes>>(READ_REPLY_CHAN_CAP);
let (read_stream, mut write_stream) = tokio::io::split(self.stream);
let reader_fut = Self::reader(read_stream, reply_sender);
tokio::pin!(reader_fut);
let mut status = Status {
state: ConnState::Active(None),
idle_timeout: None,
send_keepalive: true,
};
let mut query_vec = Queries::new();
let mut reqmsg: Option<Vec<u8>> = None;
let mut reqmsg_offset = 0;
loop {
let opt_timeout = match status.state {
ConnState::Active(opt_instant) => {
if let Some(instant) = opt_instant {
let elapsed = instant.elapsed();
if elapsed > self.config.response_timeout {
Self::error(
Error::StreamReadTimeout,
&mut query_vec,
);
status.state = ConnState::ReadTimeout;
break;
}
Some(self.config.response_timeout - elapsed)
} else {
None
}
}
ConnState::Idle(instant) => {
if let Some(timeout) = &status.idle_timeout {
let elapsed = instant.elapsed();
if elapsed >= *timeout {
// Move to IdleTimeout and end
// the loop
status.state = ConnState::IdleTimeout;
break;
}
Some(*timeout - elapsed)
} else {
panic!("Idle state but no timeout");
}
}
ConnState::IdleTimeout
| ConnState::ReadError(_)
| ConnState::WriteError(_) => None, // No timers here
ConnState::ReadTimeout => {
panic!("should not be in loop with ReadTimeout");
}
};
// For simplicity, make sure we always have a timeout
let timeout = match opt_timeout {
Some(timeout) => timeout,
None =>
// Just use the response timeout
{
self.config.response_timeout
}
};
let sleep_fut = sleep(timeout);
let recv_fut = self.receiver.recv();
let (do_write, msg) = match &reqmsg {
None => {
let msg: &[u8] = &[];
(false, msg)
}
Some(msg) => {
let msg: &[u8] = msg;
(true, msg)
}
};
tokio::select! {
biased;
res = &mut reader_fut => {
match res {
Ok(_) =>
// The reader should not
// terminate without
// error.
panic!("reader terminated"),
Err(error) => {
Self::error(error.clone(), &mut query_vec);
status.state = ConnState::ReadError(error);
// Reader failed. Break
// out of loop and
// shut down
break
}
}
}
opt_answer = reply_receiver.recv() => {
let answer = opt_answer.expect("reader died?");
// Check for a edns-tcp-keepalive option
let opt_record = answer.opt();
if let Some(ref opts) = opt_record {
Self::handle_opts(opts,
&mut status);
};
drop(opt_record);
Self::demux_reply(answer, &mut status, &mut query_vec);
}
res = write_stream.write(&msg[reqmsg_offset..]),
if do_write => {
match res {
Err(error) => {
let error =
Error::StreamWriteError(Arc::new(error));
Self::error(error.clone(), &mut query_vec);
status.state =
ConnState::WriteError(error);
break;
}
Ok(len) => {
reqmsg_offset += len;
if reqmsg_offset >= msg.len() {
reqmsg = None;
reqmsg_offset = 0;
}
}
}
}
res = recv_fut, if !do_write => {
match res {
Some(req) => {
Self::insert_req(
req, &mut status, &mut reqmsg, &mut query_vec
)
}
None => {
// All references to the connection object have
// been dropped. Shutdown.
break;
}
}
}
_ = sleep_fut => {
// Timeout expired, just
// continue with the loop
}
}
// Check if the connection is idle
match status.state {
ConnState::Active(_) | ConnState::Idle(_) => {
// Keep going
}
ConnState::IdleTimeout => break,
ConnState::ReadError(_)
| ConnState::ReadTimeout
| ConnState::WriteError(_) => {
panic!("Should not be here");
}
}
}
// Send FIN
_ = write_stream.shutdown().await;
}
/// This function reads a DNS message from the connection and sends
/// it to [Transport::run].
///
/// Reading has to be done in two steps: first read a two octet value
/// the specifies the length of the message, and then read in a loop the
/// body of the message.
///
/// This function is not async cancellation safe.
async fn reader(
mut sock: tokio::io::ReadHalf<Stream>,
sender: mpsc::Sender<Message<Bytes>>,
) -> Result<(), Error> {
loop {
let read_res = sock.read_u16().await;
let len = match read_res {
Ok(len) => len,
Err(error) => {
return Err(Error::StreamReadError(Arc::new(error)));
}
} as usize;
let mut buf = BytesMut::with_capacity(len);
loop {
let curlen = buf.len();
if curlen >= len {
if curlen > len {
panic!(
"reader: got too much data {curlen}, expetect {len}");
}
// We got what we need
break;
}
let read_res = sock.read_buf(&mut buf).await;
match read_res {
Ok(readlen) => {
if readlen == 0 {
return Err(Error::StreamUnexpectedEndOfData);
}
}
Err(error) => {
return Err(Error::StreamReadError(Arc::new(error)));
}
};
// Check if we are done at the head of the loop
}
let reply_message = Message::<Bytes>::from_octets(buf.into());
match reply_message {
Ok(answer) => {
sender
.send(answer)
.await
.expect("can't send reply to run");
}
Err(_) => {
// The only possible error is short message
return Err(Error::ShortMessage);
}
}
}
}
/// Reports an error to all outstanding queries.
fn error(error: Error, query_vec: &mut Queries<ChanReq<Req>>) {
// Update all requests that are in progress. Don't wait for
// any reply that may be on its way.
for item in query_vec.drain() {
_ = item.sender.send(Err(error.clone()));
}
}
/// Handles received EDNS options.
///
/// In particular, it processes the edns-tcp-keepalive option.
fn handle_opts<Octs: Octets + AsRef<[u8]>>(
opts: &OptRecord<Octs>,
status: &mut Status,
) {
// XXX This handles _all_ keepalive options. I think just using the
// first option as returned by Opt::tcp_keepalive should be good
// enough? -- M.
for option in opts.opt().iter().flatten() {
if let AllOptData::TcpKeepalive(tcpkeepalive) = option {
Self::handle_keepalive(tcpkeepalive, status);
}
}
}
/// Demultiplexes a response and sends it to the right query.
///
/// In addition, the status is updated to IdleTimeout or Idle if there
/// are no remaining pending requests.
fn demux_reply(
answer: Message<Bytes>,
status: &mut Status,
query_vec: &mut Queries<ChanReq<Req>>,
) {
// We got an answer, reset the timer
status.state = ConnState::Active(Some(Instant::now()));
// Get the correct query and send it the reply.
let req = match query_vec.try_remove(answer.header().id()) {
Some(req) => req,
None => {
// No query with this ID. We should
// mark the connection as broken
return;
}
};
let answer = if req.msg.is_answer(answer.for_slice()) {
Ok(answer)
} else {
Err(Error::WrongReplyForQuery)
};
_ = req.sender.send(answer);
if query_vec.is_empty() {
// Clear the activity timer. There is no need to do
// this because state will be set to either IdleTimeout
// or Idle just below. However, it is nicer to keep
// this independent.
status.state = ConnState::Active(None);
status.state = if status.idle_timeout.is_none() {
// Assume that we can just move to IdleTimeout
// state
ConnState::IdleTimeout
} else {
ConnState::Idle(Instant::now())
}
}
}
/// Insert a request in query_vec and return the request to be sent
/// in *reqmsg.
///
/// First the status is checked, an error is returned if not Active or
/// idle. Addend a edns-tcp-keepalive option if needed.
// Note: maybe reqmsg should be a return value.
fn insert_req(
req: ChanReq<Req>,
status: &mut Status,
reqmsg: &mut Option<Vec<u8>>,
query_vec: &mut Queries<ChanReq<Req>>,
) {
match &status.state {
ConnState::Active(timer) => {
// Set timer if we don't have one already
if timer.is_none() {
status.state = ConnState::Active(Some(Instant::now()));
}
}
ConnState::Idle(_) => {
// Go back to active
status.state = ConnState::Active(Some(Instant::now()));
}
ConnState::IdleTimeout => {
// The connection has been closed. Report error
_ = req.sender.send(Err(Error::StreamIdleTimeout));
return;
}
ConnState::ReadError(error) => {
_ = req.sender.send(Err(error.clone()));
return;
}
ConnState::ReadTimeout => {
_ = req.sender.send(Err(Error::StreamReadTimeout));
return;
}
ConnState::WriteError(error) => {
_ = req.sender.send(Err(error.clone()));
return;
}
}
// Note that insert may fail if there are too many
// outstanding queires. First call insert before checking
// send_keepalive.
let (index, req) = match query_vec.insert(req) {
Ok(res) => res,
Err(req) => {
// Send an appropriate error and return.
_ = req
.sender
.send(Err(Error::StreamTooManyOutstandingQueries));
return;
}
};
// We set the ID to the array index. Defense in depth
// suggests that a random ID is better because it works
// even if TCP sequence numbers could be predicted. However,
// Section 9.3 of RFC 5452 recommends retrying over TCP
// if many spoofed answers arrive over UDP: "TCP, by the
// nature of its use of sequence numbers, is far more
// resilient against forgery by third parties."
let hdr = req.msg.header_mut();
hdr.set_id(index);
if status.send_keepalive
&& req.msg.add_opt(&TcpKeepalive::new(None)).is_ok()
{
status.send_keepalive = false;
}
match Self::convert_query(&req.msg) {
Ok(msg) => {
*reqmsg = Some(msg);
}
Err(err) => {
// Take the sender out again and return the error.
if let Some(req) = query_vec.try_remove(index) {
_ = req.sender.send(Err(err));
}
}
}
}
/// Handle a received edns-tcp-keepalive option.
fn handle_keepalive(opt_value: TcpKeepalive, status: &mut Status) {
if let Some(value) = opt_value.timeout() {
let value_dur = Duration::from(value);
status.idle_timeout = Some(value_dur);
}
}
/// Convert the query message to a vector.
fn convert_query(msg: &Req) -> Result<Vec<u8>, Error> {
let mut target = StreamTarget::new_vec();
msg.append_message(&mut target)
.map_err(|_| Error::StreamLongMessage)?;
Ok(target.into_target())
}
}
//------------ Queries -------------------------------------------------------
/// Mapping outstanding queries to their ID.
///
/// This is generic over anything rather than our concrete request type for
/// easier testing.
#[derive(Clone, Debug)]
struct Queries<T> {
/// The number of elements in `vec` that are not None.
count: usize,
/// Index in `vec? where to look for a space for a new query.
curr: usize,
/// Vector of senders to forward a DNS reply message (or error) to.
vec: Vec<Option<T>>,
}
impl<T> Queries<T> {
/// Creates a new empty value.
fn new() -> Self {
Self {
count: 0,
curr: 0,
vec: Vec::new(),
}
}
/// Returns whether there are no more outstanding queries.
fn is_empty(&self) -> bool {
self.count == 0
}
/// Inserts the given query.
///
/// Upon success, returns the index and a mutable reference to the stored
/// query.
///
/// Upon error, which means the set is full, returns the query.
fn insert(&mut self, req: T) -> Result<(u16, &mut T), T> {
// Fail if there are to many entries already in this vector
// We cannot have more than u16::MAX entries because the
// index needs to fit in an u16. For efficiency we want to
// keep the vector half empty. So we return a failure if
// 2*count > u16::MAX
if 2 * self.count > u16::MAX.into() {
return Err(req);
}
// If more than half the vec is empty, we try and find the index of
// an empty slot.
let idx = if self.vec.len() >= 2 * self.count {
let mut found = None;
for idx in self.curr..self.vec.len() {
if self.vec[idx].is_none() {
found = Some(idx);
break;
}
}
found
} else {
None
};
// If we have an index, we can insert there, otherwise we need to
// append.
let idx = match idx {
Some(idx) => {
self.vec[idx] = Some(req);
idx
}
None => {
let idx = self.vec.len();
self.vec.push(Some(req));
idx
}
};
self.count += 1;
if idx == self.curr {
self.curr += 1;
}
let req = self.vec[idx].as_mut().expect("no inserted item?");
let idx = u16::try_from(idx).expect("query vec too large");
Ok((idx, req))
}
/// Tries to remove and return the query at the given index.
///
/// Returns `None` if there was no query there.
fn try_remove(&mut self, index: u16) -> Option<T> {
let res = self.vec.get_mut(usize::from(index))?.take()?;
self.count = self.count.saturating_sub(1);
self.curr = cmp::min(self.curr, index.into());
Some(res)
}
/// Removes all queries and returns an iterator over them.
fn drain(&mut self) -> impl Iterator<Item = T> + '_ {
let res = self.vec.drain(..).flatten(); // Skips all the `None`s.
self.count = 0;
self.curr = 0;
res
}
}
//============ Tests =========================================================
#[cfg(test)]
mod test {
use super::*;
#[test]
#[allow(clippy::needless_range_loop)]
fn queries_insert_remove() {
// Insert items, remove a few, insert a few more. Check that
// everything looks right.
let mut idxs = [None; 20];
let mut queries = Queries::new();
for i in 0..12 {
let (idx, item) = queries.insert(i).unwrap();
idxs[i] = Some(idx);
assert_eq!(i, *item);
}
assert_eq!(queries.count, 12);
assert_eq!(queries.vec.iter().flatten().count(), 12);
for i in [1, 2, 3, 4, 7, 9] {
let item = queries.try_remove(idxs[i].unwrap()).unwrap();
assert_eq!(i, item);
idxs[i] = None;
}
assert_eq!(queries.count, 6);
assert_eq!(queries.vec.iter().flatten().count(), 6);
for i in 12..20 {
let (idx, item) = queries.insert(i).unwrap();
idxs[i] = Some(idx);
assert_eq!(i, *item);
}
assert_eq!(queries.count, 14);
assert_eq!(queries.vec.iter().flatten().count(), 14);
for i in 0..20 {
if let Some(idx) = idxs[i] {
let item = queries.try_remove(idx).unwrap();
assert_eq!(i, item);
}
}
assert_eq!(queries.count, 0);
assert_eq!(queries.vec.iter().flatten().count(), 0);
}
#[test]
fn queries_overrun() {
// This is just a quick check that inserting to much stuff doesnt
// break.
let mut queries = Queries::new();
for i in 0..usize::from(u16::MAX) * 2 {
let _ = queries.insert(i);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
//! Sending and receiving DNS messages.
//!
//! This module provides types, traits, and function for sending and receiving
//! DNS messages.
//!
//! Currently, the module only provides the unstable
#![cfg_attr(feature = "unstable-client-transport", doc = " [`client`]")]
#![cfg_attr(not(feature = "unstable-client-transport"), doc = " `client`")]
//! sub-module intended for sending requests and receiving responses to them.
#![cfg_attr(
not(feature = "unstable-client-transport"),
doc = " The `unstable-client-transport` feature is necessary to enable this module."
)]
//!
#![cfg(feature = "net")]
#![cfg_attr(docsrs, doc(cfg(feature = "net")))]
pub mod client;
-9
View File
@@ -66,11 +66,6 @@ pub struct ResolvOptions {
/// it is supposed to mean.
pub primary: bool,
/// Ignore trunactions errors, dont retry with TCP.
///
/// This option is implemented by the query.
pub ign_tc: bool,
/// Set the recursion desired bit in queries.
///
/// Enabled by default.
@@ -186,7 +181,6 @@ impl Default for ResolvOptions {
aa_only: false,
use_vc: false,
primary: false,
ign_tc: false,
stay_open: false,
use_inet6: false,
rotate: false,
@@ -556,9 +550,6 @@ impl fmt::Display for ResolvConf {
if self.options.primary {
options.push("primary".into())
}
if self.options.ign_tc {
options.push("ign-tc".into())
}
if !self.options.recurse {
options.push("no-recurse".into())
}
+158 -418
View File
@@ -14,30 +14,36 @@ use self::conf::{
};
use crate::base::iana::Rcode;
use crate::base::message::Message;
use crate::base::message_builder::{
AdditionalBuilder, MessageBuilder, StreamTarget,
};
use crate::base::message_builder::{AdditionalBuilder, MessageBuilder};
use crate::base::name::{ToDname, ToRelativeDname};
use crate::base::question::Question;
use crate::net::client::dgram_stream;
use crate::net::client::multi_stream;
use crate::net::client::protocol::{TcpConnect, UdpConnect};
use crate::net::client::redundant;
use crate::net::client::request::{
ComposeRequest, Error, RequestMessage, SendRequest,
};
use crate::resolv::lookup::addr::{lookup_addr, FoundAddrs};
use crate::resolv::lookup::host::{lookup_host, search_host, FoundHosts};
use crate::resolv::lookup::srv::{lookup_srv, FoundSrvs, SrvError};
use crate::resolv::resolver::{Resolver, SearchNames};
use bytes::Bytes;
use futures_util::stream::{FuturesUnordered, StreamExt};
use octseq::array::Array;
use std::boxed::Box;
use std::fmt::Debug;
use std::future::Future;
use std::net::{IpAddr, SocketAddr};
use std::net::IpAddr;
use std::pin::Pin;
use std::slice::SliceIndex;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::string::ToString;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::vec::Vec;
use std::{io, ops};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpStream, UdpSocket};
#[cfg(feature = "resolv-sync")]
use tokio::runtime;
use tokio::sync::Mutex;
use tokio::time::timeout;
//------------ Sub-modules ---------------------------------------------------
@@ -46,9 +52,6 @@ pub mod conf;
//------------ Module Configuration ------------------------------------------
/// How many times do we try a new random port if we get address in use.
const RETRY_RANDOM_PORT: usize = 10;
//------------ StubResolver --------------------------------------------------
/// A DNS stub resolver.
@@ -70,16 +73,14 @@ const RETRY_RANDOM_PORT: usize = 10;
/// [`query()`]: #method.query
/// [`run()`]: #method.run
/// [`run_with_conf()`]: #method.run_with_conf
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct StubResolver {
/// Preferred servers.
preferred: ServerList,
/// Streaming servers.
stream: ServerList,
transport: Mutex<Option<redundant::Connection<RequestMessage<Vec<u8>>>>>,
/// Resolver options.
options: ResolvOptions,
servers: Vec<ServerConf>,
}
impl StubResolver {
@@ -91,11 +92,10 @@ impl StubResolver {
/// Creates a new resolver using the given configuraiton.
pub fn from_conf(conf: ResolvConf) -> Self {
StubResolver {
preferred: ServerList::from_conf(&conf, |s| {
s.transport.is_preferred()
}),
stream: ServerList::from_conf(&conf, |s| s.transport.is_stream()),
transport: None.into(),
options: conf.options,
servers: conf.servers,
}
}
@@ -118,6 +118,108 @@ impl StubResolver {
) -> Result<Answer, io::Error> {
Query::new(self)?.run(message).await
}
async fn setup_transport<
CR: Clone + Debug + ComposeRequest + Send + Sync + 'static,
>(
&self,
) -> Result<redundant::Connection<CR>, Error> {
// Create a redundant transport and fill it with the right transports
let (redun, transp) = redundant::Connection::new();
// Start the run function on a separate task.
let redun_run_fut = transp.run();
// It would be nice to have just one task. However redun.run() has to
// execute before we can call redun.add(). However, we need to know
// the type of the elements we add to FuturesUnordered. For the moment
// we have two tasks.
tokio::spawn(async move {
redun_run_fut.await;
});
let fut_list_tcp = FuturesUnordered::new();
let fut_list_udp_tcp = FuturesUnordered::new();
// Start the tasks with empty base transports. We need redun to be
// running before we can add transports.
// We have 3 modes of operation: use_vc: only use TCP, ign_tc: only
// UDP no fallback to TCP, and normal with is UDP falling back to TCP.
if self.options.use_vc {
for s in &self.servers {
if let Transport::Tcp = s.transport {
let (conn, tran) = multi_stream::Connection::new(
TcpConnect::new(s.addr),
);
// Start the run function on a separate task.
let run_fut = tran.run();
fut_list_tcp.push(async move {
run_fut.await;
});
redun.add(Box::new(conn)).await?;
}
}
} else {
for s in &self.servers {
if let Transport::Udp = s.transport {
let udp_connect = UdpConnect::new(s.addr);
let tcp_connect = TcpConnect::new(s.addr);
let (conn, tran) = dgram_stream::Connection::new(
udp_connect,
tcp_connect,
);
// Start the run function on a separate task.
fut_list_udp_tcp.push(async move {
tran.run().await;
});
redun.add(Box::new(conn)).await?;
}
}
}
tokio::spawn(async move {
run(fut_list_tcp, fut_list_udp_tcp).await;
});
Ok(redun)
}
async fn get_transport(
&self,
) -> Result<redundant::Connection<RequestMessage<Vec<u8>>>, Error> {
let mut opt_transport = self.transport.lock().await;
match &*opt_transport {
Some(transport) => Ok(transport.clone()),
None => {
let transport = self.setup_transport().await?;
*opt_transport = Some(transport.clone());
Ok(transport)
}
}
}
}
async fn run<TcpFut: Future, UdpTcpFut: Future>(
mut fut_list_tcp: FuturesUnordered<TcpFut>,
mut fut_list_udp_tcp: FuturesUnordered<UdpTcpFut>,
) {
loop {
let tcp_empty = fut_list_tcp.is_empty();
let udp_tcp_empty = fut_list_udp_tcp.is_empty();
if tcp_empty && udp_tcp_empty {
break;
}
tokio::select! {
_ = fut_list_tcp.next(), if !tcp_empty => {
// Nothing to do
}
_ = fut_list_udp_tcp.next(), if !udp_tcp_empty => {
// Nothing to do
}
}
}
}
impl StubResolver {
@@ -168,10 +270,10 @@ impl StubResolver {
/// The only argument is a closure taking a reference to a `StubResolver`
/// and returning a future. Whatever that future resolves to will be
/// returned.
pub fn run<R, F>(op: F) -> R::Output
pub fn run<R, T, E, F>(op: F) -> R::Output
where
R: Future + Send + 'static,
R::Output: Send + 'static,
R: Future<Output = Result<T, E>> + Send + 'static,
E: From<io::Error>,
F: FnOnce(StubResolver) -> R + Send + 'static,
{
Self::run_with_conf(ResolvConf::default(), op)
@@ -183,17 +285,16 @@ impl StubResolver {
/// tailor-making your own resolver.
///
/// [`run()`]: #method.run
pub fn run_with_conf<R, F>(conf: ResolvConf, op: F) -> R::Output
pub fn run_with_conf<R, T, E, F>(conf: ResolvConf, op: F) -> R::Output
where
R: Future + Send + 'static,
R::Output: Send + 'static,
R: Future<Output = Result<T, E>> + Send + 'static,
E: From<io::Error>,
F: FnOnce(StubResolver) -> R + Send + 'static,
{
let resolver = Self::from_conf(conf);
let runtime = runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
.build()?;
runtime.block_on(op(resolver))
}
}
@@ -238,14 +339,7 @@ pub struct Query<'a> {
/// The resolver whose configuration we are using.
resolver: &'a StubResolver,
/// Are we still in the preferred server list or have gone streaming?
preferred: bool,
/// The number of attempts, starting with zero.
attempt: usize,
/// The index in the server list we currently trying.
counter: ServerListCounter,
edns: Arc<AtomicBool>,
/// The preferred error to return.
///
@@ -259,23 +353,9 @@ pub struct Query<'a> {
impl<'a> Query<'a> {
pub fn new(resolver: &'a StubResolver) -> Result<Self, io::Error> {
let (preferred, counter) =
if resolver.options().use_vc || resolver.preferred.is_empty() {
if resolver.stream.is_empty() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"no servers available",
));
}
(false, resolver.stream.counter(resolver.options().rotate))
} else {
(true, resolver.preferred.counter(resolver.options().rotate))
};
Ok(Query {
resolver,
preferred,
attempt: 0,
counter,
edns: Arc::new(AtomicBool::new(true)),
error: Err(io::Error::new(
io::ErrorKind::TimedOut,
"all timed out",
@@ -291,26 +371,14 @@ impl<'a> Query<'a> {
match self.run_query(&mut message).await {
Ok(answer) => {
if answer.header().rcode() == Rcode::FormErr
&& self.current_server().does_edns()
&& self.does_edns()
{
// FORMERR with EDNS: turn off EDNS and try again.
self.current_server().disable_edns();
self.disable_edns();
continue;
} else if answer.header().rcode() == Rcode::ServFail {
// SERVFAIL: go to next server.
self.update_error_servfail(answer);
} else if answer.header().tc()
&& self.preferred
&& !self.resolver.options().ign_tc
{
// Truncated. If we can, switch to stream transports
// and try again. Otherwise return the truncated
// answer.
if self.switch_to_stream() {
continue;
} else {
return Ok(answer);
}
} else {
// I guess we have an answer ...
return Ok(answer);
@@ -318,20 +386,16 @@ impl<'a> Query<'a> {
}
Err(err) => self.update_error(err),
}
if !self.next_server() {
return self.error;
}
return self.error;
}
}
fn create_message(question: Question<impl ToDname>) -> QueryMessage {
let mut message = MessageBuilder::from_target(
StreamTarget::new(Default::default()).unwrap(),
)
.unwrap();
let mut message = MessageBuilder::from_target(Default::default())
.expect("MessageBuilder should not fail");
message.header_mut().set_rd(true);
let mut message = message.question();
message.push(question).unwrap();
message.push(question).expect("push should not fail");
message.additional()
}
@@ -339,18 +403,22 @@ impl<'a> Query<'a> {
&mut self,
message: &mut QueryMessage,
) -> Result<Answer, io::Error> {
let server = self.current_server();
server.prepare_message(message);
server.query(message).await
}
let msg = Message::from_octets(message.as_target().to_vec())
.expect("Message::from_octets should not fail");
fn current_server(&self) -> &ServerInfo {
let list = if self.preferred {
&self.resolver.preferred
} else {
&self.resolver.stream
};
self.counter.info(list)
let request_msg = RequestMessage::new(msg);
let transport = self.resolver.get_transport().await.map_err(|e| {
io::Error::new(io::ErrorKind::Other, e.to_string())
})?;
let mut gr_fut = transport.send_request(request_msg);
let reply =
timeout(self.resolver.options.timeout, gr_fut.get_response())
.await?
.map_err(|e| {
io::Error::new(io::ErrorKind::Other, e.to_string())
})?;
Ok(Answer { message: reply })
}
fn update_error(&mut self, err: io::Error) {
@@ -366,41 +434,19 @@ impl<'a> Query<'a> {
self.error = Ok(answer)
}
fn switch_to_stream(&mut self) -> bool {
if !self.preferred {
// We already did this.
return false;
}
self.preferred = false;
self.attempt = 0;
self.counter =
self.resolver.stream.counter(self.resolver.options().rotate);
true
pub fn does_edns(&self) -> bool {
self.edns.load(Ordering::Relaxed)
}
fn next_server(&mut self) -> bool {
if self.counter.next() {
return true;
}
self.attempt += 1;
if self.attempt >= self.resolver.options().attempts {
return false;
}
self.counter = if self.preferred {
self.resolver
.preferred
.counter(self.resolver.options().rotate)
} else {
self.resolver.stream.counter(self.resolver.options().rotate)
};
true
pub fn disable_edns(&self) {
self.edns.store(false, Ordering::Relaxed);
}
}
//------------ QueryMessage --------------------------------------------------
// XXX This needs to be re-evaluated if we start adding OPTtions to the query.
pub(super) type QueryMessage = AdditionalBuilder<StreamTarget<Array<512>>>;
pub(super) type QueryMessage = AdditionalBuilder<Array<512>>;
//------------ Answer --------------------------------------------------------
@@ -451,312 +497,6 @@ impl AsRef<Message<Bytes>> for Answer {
}
}
//------------ ServerInfo ----------------------------------------------------
#[derive(Clone, Debug)]
struct ServerInfo {
/// The basic server configuration.
conf: ServerConf,
/// Whether this server supports EDNS.
///
/// We start out with assuming it does and unset it if we get a FORMERR.
edns: Arc<AtomicBool>,
}
impl ServerInfo {
pub fn does_edns(&self) -> bool {
self.edns.load(Ordering::Relaxed)
}
pub fn disable_edns(&self) {
self.edns.store(false, Ordering::Relaxed);
}
pub fn prepare_message(&self, query: &mut QueryMessage) {
query.rewind();
if self.does_edns() {
query
.opt(|opt| {
opt.set_udp_payload_size(self.conf.udp_payload_size);
Ok(())
})
.unwrap();
}
}
pub async fn query(
&self,
query: &QueryMessage,
) -> Result<Answer, io::Error> {
let res = match self.conf.transport {
Transport::Udp => {
timeout(
self.conf.request_timeout,
Self::udp_query(
query,
self.conf.addr,
self.conf.recv_size,
),
)
.await
}
Transport::Tcp => {
timeout(
self.conf.request_timeout,
Self::tcp_query(query, self.conf.addr),
)
.await
}
};
match res {
Ok(Ok(answer)) => Ok(answer),
Ok(Err(err)) => Err(err),
Err(_) => Err(io::Error::new(
io::ErrorKind::TimedOut,
"request timed out",
)),
}
}
pub async fn tcp_query(
query: &QueryMessage,
addr: SocketAddr,
) -> Result<Answer, io::Error> {
let mut sock = TcpStream::connect(&addr).await?;
sock.write_all(query.as_target().as_stream_slice()).await?;
// This loop can be infinite because we have a timeout on this whole
// thing, anyway.
loop {
let mut buf = Vec::new();
let len = sock.read_u16().await? as u64;
AsyncReadExt::take(&mut sock, len)
.read_to_end(&mut buf)
.await?;
if let Ok(answer) = Message::from_octets(buf.into()) {
if answer.is_answer(&query.as_message()) {
return Ok(answer.into());
}
// else try with the next message.
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
"short buf",
));
}
}
}
pub async fn udp_query(
query: &QueryMessage,
addr: SocketAddr,
recv_size: usize,
) -> Result<Answer, io::Error> {
let sock = Self::udp_bind(addr.is_ipv4()).await?;
sock.connect(addr).await?;
let sent = sock.send(query.as_target().as_dgram_slice()).await?;
if sent != query.as_target().as_dgram_slice().len() {
return Err(io::Error::new(
io::ErrorKind::Other,
"short UDP send",
));
}
loop {
let mut buf = vec![0; recv_size]; // XXX use uninit'ed mem here.
let len = sock.recv(&mut buf).await?;
buf.truncate(len);
// We ignore garbage since there is a timer on this whole thing.
let answer = match Message::from_octets(buf.into()) {
Ok(answer) => answer,
Err(_) => continue,
};
if !answer.is_answer(&query.as_message()) {
continue;
}
return Ok(answer.into());
}
}
async fn udp_bind(v4: bool) -> Result<UdpSocket, io::Error> {
let mut i = 0;
loop {
let local: SocketAddr = if v4 {
([0u8; 4], 0).into()
} else {
([0u16; 8], 0).into()
};
match UdpSocket::bind(&local).await {
Ok(sock) => return Ok(sock),
Err(err) => {
if i == RETRY_RANDOM_PORT {
return Err(err);
} else {
i += 1
}
}
}
}
}
}
impl From<ServerConf> for ServerInfo {
fn from(conf: ServerConf) -> Self {
ServerInfo {
conf,
edns: Arc::new(AtomicBool::new(true)),
}
}
}
impl<'a> From<&'a ServerConf> for ServerInfo {
fn from(conf: &'a ServerConf) -> Self {
conf.clone().into()
}
}
//------------ ServerList ----------------------------------------------------
#[derive(Clone, Debug)]
struct ServerList {
/// The actual list of servers.
servers: Vec<ServerInfo>,
/// Where to start accessing the list.
///
/// In rotate mode, this value will always keep growing and will have to
/// be used modulo `servers`s length.
///
/// When it eventually wraps around the end of usizes range, there will
/// be a jump in rotation. Since that will happen only oh-so-often, we
/// accept that in favour of simpler code.
start: Arc<AtomicUsize>,
}
impl ServerList {
pub fn from_conf<F>(conf: &ResolvConf, filter: F) -> Self
where
F: Fn(&ServerConf) -> bool,
{
ServerList {
servers: {
conf.servers
.iter()
.filter(|f| filter(f))
.map(Into::into)
.collect()
},
start: Arc::new(AtomicUsize::new(0)),
}
}
pub fn is_empty(&self) -> bool {
self.servers.is_empty()
}
pub fn counter(&self, rotate: bool) -> ServerListCounter {
let res = ServerListCounter::new(self);
if rotate {
self.rotate()
}
res
}
pub fn iter(&self) -> ServerListIter {
ServerListIter::new(self)
}
pub fn rotate(&self) {
self.start.fetch_add(1, Ordering::SeqCst);
}
}
impl<'a> IntoIterator for &'a ServerList {
type Item = &'a ServerInfo;
type IntoIter = ServerListIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<I: SliceIndex<[ServerInfo]>> ops::Index<I> for ServerList {
type Output = <I as SliceIndex<[ServerInfo]>>::Output;
fn index(&self, index: I) -> &<I as SliceIndex<[ServerInfo]>>::Output {
self.servers.index(index)
}
}
//------------ ServerListCounter ---------------------------------------------
#[derive(Clone, Debug)]
struct ServerListCounter {
cur: usize,
end: usize,
}
impl ServerListCounter {
fn new(list: &ServerList) -> Self {
if list.servers.is_empty() {
return ServerListCounter { cur: 0, end: 0 };
}
// We modulo the start value here to prevent hick-ups towards the
// end of usizes range.
let start = list.start.load(Ordering::Relaxed) % list.servers.len();
ServerListCounter {
cur: start,
end: start + list.servers.len(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> bool {
let next = self.cur + 1;
if next < self.end {
self.cur = next;
true
} else {
false
}
}
pub fn info<'a>(&self, list: &'a ServerList) -> &'a ServerInfo {
&list[self.cur % list.servers.len()]
}
}
//------------ ServerListIter ------------------------------------------------
#[derive(Clone, Debug)]
struct ServerListIter<'a> {
servers: &'a ServerList,
counter: ServerListCounter,
}
impl<'a> ServerListIter<'a> {
fn new(list: &'a ServerList) -> Self {
ServerListIter {
servers: list,
counter: ServerListCounter::new(list),
}
}
}
impl<'a> Iterator for ServerListIter<'a> {
type Item = &'a ServerInfo;
fn next(&mut self) -> Option<Self::Item> {
if self.counter.next() {
Some(self.counter.info(self.servers))
} else {
None
}
}
}
//------------ SearchIter ----------------------------------------------------
#[derive(Clone, Debug)]
+161
View File
@@ -0,0 +1,161 @@
do-ip6: no
; config options
; target-fetch-policy: "3 2 1 0 0"
; name: "."
stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
CONFIG_END
SCENARIO_BEGIN Test iterator with NS falsely declaring referral answer as authoritative.
; K.ROOT-SERVERS.NET.
RANGE_BEGIN 0 100
ADDRESS 193.0.14.129
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
. IN NS
SECTION ANSWER
. IN NS K.ROOT-SERVERS.NET.
SECTION ADDITIONAL
K.ROOT-SERVERS.NET. IN A 193.0.14.129
ENTRY_END
; net.
ENTRY_BEGIN
MATCH opcode qname
ADJUST copy_id copy_query
REPLY QR NOERROR
SECTION QUESTION
net. IN NS
SECTION AUTHORITY
. IN SOA . . 0 0 0 0 0
ENTRY_END
; root-servers.net.
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
root-servers.net. IN NS
SECTION ANSWER
root-servers.net. IN NS k.root-servers.net.
SECTION ADDITIONAL
k.root-servers.net. IN A 193.0.14.129
ENTRY_END
ENTRY_BEGIN
MATCH opcode qname
ADJUST copy_id copy_query
REPLY QR NOERROR
SECTION QUESTION
root-servers.net. IN A
SECTION AUTHORITY
root-servers.net. IN SOA . . 0 0 0 0 0
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
k.root-servers.net. IN A
SECTION ANSWER
k.root-servers.net. IN A 193.0.14.129
SECTION ADDITIONAL
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
k.root-servers.net. IN AAAA
SECTION AUTHORITY
root-servers.net. IN SOA . . 0 0 0 0 0
ENTRY_END
; gtld-servers.net.
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
gtld-servers.net. IN NS
SECTION ANSWER
gtld-servers.net. IN NS a.gtld-servers.net.
SECTION ADDITIONAL
a.gtld-servers.net. IN A 192.5.6.30
ENTRY_END
ENTRY_BEGIN
MATCH opcode qname
ADJUST copy_id copy_query
REPLY QR NOERROR
SECTION QUESTION
gtld-servers.net. IN A
SECTION AUTHORITY
gtld-servers.net. IN SOA . . 0 0 0 0 0
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
a.gtld-servers.net. IN A
SECTION ANSWER
a.gtld-servers.net. IN A 192.5.6.30
SECTION ADDITIONAL
ENTRY_END
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR NOERROR
SECTION QUESTION
a.gtld-servers.net. IN AAAA
SECTION AUTHORITY
gtld-servers.net. IN SOA . . 0 0 0 0 0
ENTRY_END
RANGE_END
; a.gtld-servers.net.
RANGE_BEGIN 0 100
ADDRESS 192.5.6.30
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id copy_query
REPLY QR RD NOERROR
SECTION QUESTION
example.com. IN A
SECTION ANSWER
example.com. IN A 93.184.216.34
ENTRY_END
RANGE_END
STEP 1 QUERY
ENTRY_BEGIN
REPLY RD
SECTION QUESTION
example.com. IN A
ENTRY_END
; recursion happens here.
STEP 10 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RD RA NOERROR
SECTION QUESTION
example.com. IN A
SECTION ANSWER
example.com. IN A 93.184.216.34
ENTRY_END
SCENARIO_END
+148
View File
@@ -0,0 +1,148 @@
#![cfg(feature = "net")]
mod net;
use crate::net::deckard::client::do_client;
use crate::net::deckard::client::CurrStepValue;
use crate::net::deckard::connect::Connect;
use crate::net::deckard::connection::Connection;
use crate::net::deckard::dgram::Dgram;
use crate::net::deckard::parse_deckard::parse_file;
use domain::net::client::dgram;
use domain::net::client::dgram_stream;
use domain::net::client::multi_stream;
use domain::net::client::redundant;
use domain::net::client::stream;
use std::fs::File;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use tokio::net::TcpStream;
const TEST_FILE: &str = "test-data/basic.rpl";
#[test]
fn dgram() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let step_value = Arc::new(CurrStepValue::new());
let conn = Dgram::new(deckard.clone(), step_value.clone());
let octstr = dgram::Connection::new(conn);
do_client(&deckard, octstr, &step_value).await;
});
}
#[test]
fn single() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let step_value = Arc::new(CurrStepValue::new());
let conn = Connection::new(deckard.clone(), step_value.clone());
let (octstr, transport) = stream::Connection::new(conn);
tokio::spawn(async move {
transport.run().await;
});
do_client(&deckard, octstr, &step_value).await;
});
}
#[test]
fn multi() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let step_value = Arc::new(CurrStepValue::new());
let multi_conn = Connect::new(deckard.clone(), step_value.clone());
let (ms, ms_tran) = multi_stream::Connection::new(multi_conn);
tokio::spawn(async move {
ms_tran.run().await;
println!("multi conn run terminated");
});
do_client(&deckard, ms.clone(), &step_value).await;
});
}
#[test]
fn dgram_stream() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let step_value = Arc::new(CurrStepValue::new());
let conn = Dgram::new(deckard.clone(), step_value.clone());
let multi_conn = Connect::new(deckard.clone(), step_value.clone());
let (ds, tran) = dgram_stream::Connection::new(conn, multi_conn);
tokio::spawn(async move {
tran.run().await;
println!("dgram_stream conn run terminated");
});
do_client(&deckard, ds, &step_value).await;
});
}
#[test]
fn redundant() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let step_value = Arc::new(CurrStepValue::new());
let multi_conn = Connect::new(deckard.clone(), step_value.clone());
let (ms, ms_tran) = multi_stream::Connection::new(multi_conn);
tokio::spawn(async move {
ms_tran.run().await;
println!("multi conn run terminated");
});
// Redundant add previous connection.
let (redun, transp) = redundant::Connection::new();
let run_fut = transp.run();
tokio::spawn(async move {
run_fut.await;
println!("redundant conn run terminated");
});
redun.add(Box::new(ms.clone())).await.unwrap();
do_client(&deckard, redun, &step_value).await;
});
}
#[test]
#[ignore]
// Connect directly to the internet. Disabled by default.
fn tcp() {
tokio_test::block_on(async {
let file = File::open(TEST_FILE).unwrap();
let deckard = parse_file(file);
let server_addr =
SocketAddr::new(IpAddr::from_str("9.9.9.9").unwrap(), 53);
let tcp_conn = match TcpStream::connect(server_addr).await {
Ok(conn) => conn,
Err(err) => {
println!(
"TCP Connection to {server_addr} failed: {err}, exiting"
);
return;
}
};
let (tcp, transport) = stream::Connection::new(tcp_conn);
tokio::spawn(async move {
transport.run().await;
println!("single TCP run terminated");
});
do_client(&deckard, tcp, &CurrStepValue::new()).await;
});
}
+90
View File
@@ -0,0 +1,90 @@
use crate::net::deckard::matches::match_msg;
use crate::net::deckard::parse_deckard::{Deckard, Entry, Reply, StepType};
use crate::net::deckard::parse_query;
use bytes::Bytes;
use domain::base::{Message, MessageBuilder};
use domain::net::client::request::{RequestMessage, SendRequest};
use std::sync::Mutex;
pub async fn do_client<R: SendRequest<RequestMessage<Vec<u8>>>>(
deckard: &Deckard,
request: R,
step_value: &CurrStepValue,
) {
let mut resp: Option<Message<Bytes>> = None;
// Assume steps are in order. Maybe we need to define that.
for step in &deckard.scenario.steps {
step_value.set(step.step_value);
match step.step_type {
StepType::Query => {
let reqmsg = entry2reqmsg(step.entry.as_ref().unwrap());
let mut req = request.send_request(reqmsg);
resp = Some(req.get_response().await.unwrap());
}
StepType::CheckAnswer => {
let answer = resp.take().unwrap();
if !match_msg(step.entry.as_ref().unwrap(), &answer, true) {
panic!("reply failed");
}
}
StepType::TimePasses
| StepType::Traffic
| StepType::CheckTempfile
| StepType::Assign => todo!(),
}
}
println!("Done");
}
fn entry2reqmsg(entry: &Entry) -> RequestMessage<Vec<u8>> {
let sections = entry.sections.as_ref().unwrap();
let mut msg = MessageBuilder::new_vec().question();
for q in &sections.question {
let question = match q {
parse_query::Entry::QueryRecord(question) => question,
_ => todo!(),
};
msg.push(question).unwrap();
}
let msg = msg.answer();
for _a in &sections.answer {
todo!();
}
let msg = msg.authority();
for _a in &sections.authority {
todo!();
}
let mut msg = msg.additional();
for _a in &sections.additional {
todo!();
}
let reply: Reply = match &entry.reply {
Some(reply) => reply.clone(),
None => Default::default(),
};
if reply.rd {
msg.header_mut().set_rd(true);
}
let msg = msg.into_message();
RequestMessage::new(msg)
}
#[derive(Debug)]
pub struct CurrStepValue {
v: Mutex<u64>,
}
impl CurrStepValue {
pub fn new() -> Self {
Self { v: 0.into() }
}
fn set(&self, v: u64) {
let mut self_v = self.v.lock().unwrap();
*self_v = v;
}
pub fn get(&self) -> u64 {
*(self.v.lock().unwrap())
}
}
+34
View File
@@ -0,0 +1,34 @@
use crate::net::deckard::client::CurrStepValue;
use crate::net::deckard::connection::Connection;
use crate::net::deckard::parse_deckard::Deckard;
use domain::net::client::protocol::AsyncConnect;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub struct Connect {
deckard: Deckard,
step_value: Arc<CurrStepValue>,
}
impl Connect {
pub fn new(deckard: Deckard, step_value: Arc<CurrStepValue>) -> Connect {
Self {
deckard,
step_value,
}
}
}
impl AsyncConnect for Connect {
type Connection = Connection;
type Fut = Pin<
Box<dyn Future<Output = Result<Connection, std::io::Error>> + Send>,
>;
fn connect(&self) -> Self::Fut {
let deckard = self.deckard.clone();
let step_value = self.step_value.clone();
Box::pin(async move { Ok(Connection::new(deckard, step_value)) })
}
}
+110
View File
@@ -0,0 +1,110 @@
use crate::net::deckard::client::CurrStepValue;
use crate::net::deckard::parse_deckard::Deckard;
use crate::net::deckard::server::do_server;
use domain::base::Message;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::ReadBuf;
#[derive(Debug)]
pub struct Connection {
deckard: Deckard,
step_value: Arc<CurrStepValue>,
waker: Option<Waker>,
reply: Option<Message<Vec<u8>>>,
send_body: bool,
tmpbuf: Vec<u8>,
}
impl Connection {
pub fn new(
deckard: Deckard,
step_value: Arc<CurrStepValue>,
) -> Connection {
Self {
deckard,
step_value,
waker: None,
reply: None,
send_body: false,
tmpbuf: Vec::new(),
}
}
}
impl AsyncRead for Connection {
fn poll_read(
mut self: Pin<&mut Self>,
context: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<(), std::io::Error>> {
if self.reply.is_some() {
let slice = self.reply.as_ref().unwrap().as_slice();
let len = slice.len();
if self.send_body {
buf.put_slice(slice);
self.reply = None;
return Poll::Ready(Ok(()));
} else {
buf.put_slice(&(len as u16).to_be_bytes());
self.send_body = true;
return Poll::Ready(Ok(()));
}
}
self.reply = None;
self.send_body = false;
self.waker = Some(context.waker().clone());
Poll::Pending
}
}
impl AsyncWrite for Connection {
fn poll_write(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
self.tmpbuf.push(buf[0]);
let buflen = self.tmpbuf.len();
if buflen < 2 {
return Poll::Ready(Ok(1));
}
let mut len_str: [u8; 2] = [0; 2];
len_str.copy_from_slice(&self.tmpbuf[0..2]);
let len = u16::from_be_bytes(len_str) as usize;
if buflen != 2 + len {
return Poll::Ready(Ok(1));
}
let msg = Message::from_octets(self.tmpbuf[2..].to_vec()).unwrap();
self.tmpbuf = Vec::new();
let opt_reply = do_server(&msg, &self.deckard, &self.step_value);
if opt_reply.is_some() {
// Do we need to support more than one reply?
self.reply = opt_reply;
let opt_waker = self.waker.take();
if let Some(waker) = opt_waker {
waker.wake();
}
}
Poll::Ready(Ok(1))
}
fn poll_flush(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
todo!()
}
fn poll_shutdown(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
// Do we need to do anything here?
Poll::Ready(Ok(()))
}
}
+108
View File
@@ -0,0 +1,108 @@
//! Provide server-side of datagram protocols
use crate::net::deckard::client::CurrStepValue;
use crate::net::deckard::parse_deckard::Deckard;
use crate::net::deckard::server::do_server;
use domain::base::Message;
use domain::net::client::protocol::{
AsyncConnect, AsyncDgramRecv, AsyncDgramSend,
};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::Mutex as SyncMutex;
use std::task::{Context, Poll, Waker};
use tokio::io::ReadBuf;
#[derive(Clone, Debug)]
pub struct Dgram {
deckard: Deckard,
step_value: Arc<CurrStepValue>,
}
impl Dgram {
pub fn new(deckard: Deckard, step_value: Arc<CurrStepValue>) -> Self {
Self {
deckard,
step_value,
}
}
}
impl AsyncConnect for Dgram {
type Connection = DgramConnection;
type Fut = Pin<
Box<
dyn Future<Output = Result<Self::Connection, std::io::Error>>
+ Send,
>,
>;
fn connect(&self) -> Self::Fut {
let deckard = self.deckard.clone();
let step_value = self.step_value.clone();
Box::pin(async move { Ok(DgramConnection::new(deckard, step_value)) })
}
}
pub struct DgramConnection {
deckard: Deckard,
step_value: Arc<CurrStepValue>,
reply: SyncMutex<Option<Message<Vec<u8>>>>,
waker: SyncMutex<Option<Waker>>,
}
impl DgramConnection {
fn new(deckard: Deckard, step_value: Arc<CurrStepValue>) -> Self {
Self {
deckard,
step_value,
reply: SyncMutex::new(None),
waker: SyncMutex::new(None),
}
}
}
impl AsyncDgramRecv for DgramConnection {
fn poll_recv(
&self,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<(), std::io::Error>> {
let mut reply = self.reply.lock().unwrap();
if (*reply).is_some() {
let slice = (*reply).as_ref().unwrap().as_slice();
buf.put_slice(slice);
*reply = None;
return Poll::Ready(Ok(()));
}
*reply = None;
let mut waker = self.waker.lock().unwrap();
*waker = Some(cx.waker().clone());
Poll::Pending
}
}
impl AsyncDgramSend for DgramConnection {
fn poll_send(
&self,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
let msg = Message::from_octets(buf).unwrap();
let opt_reply = do_server(&msg, &self.deckard, &self.step_value);
let len = buf.len();
if opt_reply.is_some() {
// Do we need to support more than one reply?
let mut reply = self.reply.lock().unwrap();
*reply = opt_reply;
drop(reply);
let mut waker = self.waker.lock().unwrap();
let opt_waker = (*waker).take();
drop(waker);
if let Some(waker) = opt_waker {
waker.wake();
}
}
Poll::Ready(Ok(len))
}
}
+305
View File
@@ -0,0 +1,305 @@
use crate::net::deckard::parse_deckard::{Entry, Matches, Reply};
use crate::net::deckard::parse_query;
use domain::base::iana::Opcode;
use domain::base::iana::OptRcode;
use domain::base::iana::Rtype;
use domain::base::Message;
use domain::base::ParsedDname;
use domain::base::QuestionSection;
use domain::base::RecordSection;
use domain::dep::octseq::Octets;
use domain::rdata::ZoneRecordData;
use domain::zonefile::inplace::Entry as ZonefileEntry;
//use std::fmt::Debug;
pub fn match_msg<'a, Octs: AsRef<[u8]> + Clone + Octets + 'a>(
entry: &Entry,
msg: &'a Message<Octs>,
verbose: bool,
) -> bool
where
<Octs as Octets>::Range<'a>: Clone,
{
let sections = entry.sections.as_ref().unwrap();
let mut matches: Matches = match &entry.matches {
Some(matches) => matches.clone(),
None => Default::default(),
};
let reply: Reply = match &entry.reply {
Some(reply) => reply.clone(),
None => Default::default(),
};
if matches.all {
matches.opcode = true;
matches.qtype = true;
matches.qname = true;
matches.flags = true;
matches.rcode = true;
matches.answer = true;
matches.authority = true;
matches.additional = true;
}
if matches.question {
matches.qtype = true;
matches.qname = true;
}
if matches.additional {
let mut arcount = msg.header_counts().arcount();
if msg.opt().is_some() {
arcount -= 1;
}
if !match_section(
sections.additional.clone(),
msg.additional().unwrap(),
arcount,
verbose,
) {
if verbose {
println!("match_msg: additional section does not match");
}
return false;
}
}
if matches.answer
&& !match_section(
sections.answer.clone(),
msg.answer().unwrap(),
msg.header_counts().ancount(),
verbose,
)
{
if verbose {
todo!();
}
return false;
}
if matches.authority
&& !match_section(
sections.authority.clone(),
msg.authority().unwrap(),
msg.header_counts().nscount(),
verbose,
)
{
if verbose {
todo!();
}
return false;
}
if matches.fl_do {
todo!();
}
if matches.flags {
let header = msg.header();
if reply.qr != header.qr() {
if verbose {
todo!();
}
return false;
}
if reply.aa != header.aa() {
if verbose {
println!(
"match_msg: AA does not match, got {}, expected {}",
header.aa(),
reply.aa
);
}
return false;
}
if reply.tc != header.tc() {
if verbose {
todo!();
}
return false;
}
if reply.rd != header.rd() {
if verbose {
println!(
"match_msg: RD does not match, got {}, expected {}",
header.aa(),
reply.aa
);
}
return false;
}
if reply.ad != header.ad() {
if verbose {
todo!();
}
return false;
}
if reply.cd != header.cd() {
if verbose {
todo!();
}
return false;
}
}
if matches.opcode {
// Not clear what that means. JUst check if it is Query
if msg.header().opcode() != Opcode::Query {
if verbose {
todo!();
}
return false;
}
}
if (matches.qname || matches.qtype)
&& !match_question(
sections.question.clone(),
msg.question(),
matches.qname,
matches.qtype,
)
{
if verbose {
println!("match_msg: question section does not match");
}
return false;
}
if matches.rcode {
let msg_rcode =
get_opt_rcode(&Message::from_octets(msg.as_slice()).unwrap());
if reply.noerror {
if let OptRcode::NoError = msg_rcode {
// Okay
} else {
if verbose {
todo!();
}
return false;
}
} else {
println!("reply {reply:?}");
panic!("no rcode to match?");
}
}
if matches.subdomain {
todo!()
}
if matches.tcp {
todo!()
}
if matches.ttl {
todo!()
}
if matches.udp {
todo!()
}
// All checks passed!
true
}
fn match_section<
'a,
Octs: Clone + Octets<Range<'a> = Octs2> + 'a,
Octs2: AsRef<[u8]> + Clone,
>(
mut match_section: Vec<ZonefileEntry>,
msg_section: RecordSection<'a, Octs>,
msg_count: u16,
verbose: bool,
) -> bool {
if match_section.len() != msg_count.into() {
if verbose {
todo!();
}
return false;
}
'outer: for msg_rr in msg_section {
let msg_rr = msg_rr.unwrap();
if msg_rr.rtype() == Rtype::Opt {
continue;
}
for (index, mat_rr) in match_section.iter().enumerate() {
// Remove outer Record
let mat_rr = if let ZonefileEntry::Record(record) = mat_rr {
record
} else {
panic!("include not expected");
};
if msg_rr.owner() != mat_rr.owner() {
continue;
}
if msg_rr.class() != mat_rr.class() {
continue;
}
if msg_rr.rtype() != mat_rr.rtype() {
continue;
}
let msg_rdata = msg_rr
.clone()
.into_record::<ZoneRecordData<Octs2, ParsedDname<Octs2>>>()
.unwrap()
.unwrap();
if msg_rdata.data() != mat_rr.data() {
continue;
}
// Found one. Delete this entry
match_section.swap_remove(index);
continue 'outer;
}
// Nothing matches
if verbose {
println!(
"no match for record {} {} {}",
msg_rr.owner(),
msg_rr.class(),
msg_rr.rtype()
);
}
return false;
}
// All entries in the reply were matched.
true
}
fn match_question<Octs: Octets>(
match_section: Vec<parse_query::Entry>,
msg_section: QuestionSection<'_, Octs>,
match_qname: bool,
match_qtype: bool,
) -> bool {
if match_section.is_empty() {
// Nothing to match.
return true;
}
for msg_rr in msg_section {
let msg_rr = msg_rr.unwrap();
let mat_rr = if let parse_query::Entry::QueryRecord(record) =
&match_section[0]
{
record
} else {
panic!("include not expected");
};
if match_qname && msg_rr.qname() != mat_rr.qname() {
return false;
}
if match_qtype && msg_rr.qtype() != mat_rr.qtype() {
return false;
}
}
// All entries in the reply were matched.
true
}
fn get_opt_rcode<Octs: Octets>(msg: &Message<Octs>) -> OptRcode {
let opt = msg.opt();
match opt {
Some(opt) => opt.rcode(msg.header()),
None => {
// Convert Rcode to OptRcode, this should be part of
// OptRcode
OptRcode::from_int(msg.header().rcode().to_int() as u16)
}
}
}
+8
View File
@@ -0,0 +1,8 @@
pub mod client;
pub mod connect;
pub mod connection;
pub mod dgram;
mod matches;
pub mod parse_deckard;
mod parse_query;
mod server;
+609
View File
@@ -0,0 +1,609 @@
use std::default::Default;
use std::fmt::Debug;
use std::io::{self, BufRead, Read};
use std::net::IpAddr;
use crate::net::deckard::parse_query;
use crate::net::deckard::parse_query::Zonefile as QueryZonefile;
use domain::zonefile::inplace::Entry as ZonefileEntry;
use domain::zonefile::inplace::Zonefile;
const CONFIG_END: &str = "CONFIG_END";
const SCENARIO_BEGIN: &str = "SCENARIO_BEGIN";
const SCENARIO_END: &str = "SCENARIO_END";
const RANGE_BEGIN: &str = "RANGE_BEGIN";
const RANGE_END: &str = "RANGE_END";
const ADDRESS: &str = "ADDRESS";
const ENTRY_BEGIN: &str = "ENTRY_BEGIN";
const ENTRY_END: &str = "ENTRY_END";
const MATCH: &str = "MATCH";
const ADJUST: &str = "ADJUST";
const REPLY: &str = "REPLY";
const SECTION: &str = "SECTION";
const QUESTION: &str = "QUESTION";
const ANSWER: &str = "ANSWER";
const AUTHORITY: &str = "AUTHORITY";
const ADDITIONAL: &str = "ADDITIONAL";
const STEP: &str = "STEP";
const STEP_TYPE_QUERY: &str = "QUERY";
const STEP_TYPE_CHECK_ANSWER: &str = "CHECK_ANSWER";
const STEP_TYPE_TIME_PASSES: &str = "TIME_PASSES";
const STEP_TYPE_TRAFFIC: &str = "TRAFFIC";
const STEP_TYPE_CHECK_TEMPFILE: &str = "CHECK_TEMPFILE";
const STEP_TYPE_ASSIGN: &str = "ASSIGN";
enum Section {
Question,
Answer,
Authority,
Additional,
}
#[derive(Clone, Debug)]
pub enum StepType {
Query,
CheckAnswer,
TimePasses,
Traffic,
CheckTempfile,
Assign,
}
#[derive(Clone, Debug, Default)]
pub struct Config {
lines: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct Deckard {
pub config: Config,
pub scenario: Scenario,
}
pub fn parse_file<F: Debug + Read>(file: F) -> Deckard {
let mut lines = io::BufReader::new(file).lines();
Deckard {
config: parse_config(&mut lines),
scenario: parse_scenario(&mut lines),
}
}
fn parse_config<Lines: Iterator<Item = Result<String, std::io::Error>>>(
l: &mut Lines,
) -> Config {
let mut config: Config = Default::default();
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
if clean_line == CONFIG_END {
break;
}
config.lines.push(clean_line.to_string());
}
config
}
#[derive(Clone, Debug, Default)]
pub struct Scenario {
pub ranges: Vec<Range>,
pub steps: Vec<Step>,
}
pub fn parse_scenario<
Lines: Iterator<Item = Result<String, std::io::Error>>,
>(
l: &mut Lines,
) -> Scenario {
let mut scenario: Scenario = Default::default();
// Find SCENARIO_BEGIN
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == SCENARIO_BEGIN {
break;
}
println!("parse_scenario: garbage line {clean_line:?}");
panic!("bad line");
}
// Find RANGE_BEGIN, STEP, or SCENARIO_END
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == RANGE_BEGIN {
scenario.ranges.push(parse_range(tokens, l));
continue;
}
if token == STEP {
scenario.steps.push(parse_step(tokens, l));
continue;
}
if token == SCENARIO_END {
break;
}
todo!();
}
scenario
}
#[derive(Clone, Debug, Default)]
pub struct Range {
pub start_value: u64,
pub end_value: u64,
addr: Option<IpAddr>,
pub entry: Vec<Entry>,
}
fn parse_range<Lines: Iterator<Item = Result<String, std::io::Error>>>(
mut tokens: LineTokens<'_>,
l: &mut Lines,
) -> Range {
let mut range: Range = Range {
start_value: tokens.next().unwrap().parse::<u64>().unwrap(),
end_value: tokens.next().unwrap().parse::<u64>().unwrap(),
..Default::default()
};
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == ADDRESS {
let addr_str = tokens.next().unwrap();
range.addr = Some(addr_str.parse().unwrap());
continue;
}
if token == ENTRY_BEGIN {
range.entry.push(parse_entry(l));
continue;
}
if token == RANGE_END {
break;
}
todo!();
}
//println!("parse_range: {:?}", range);
range
}
#[derive(Clone, Debug)]
pub struct Step {
pub step_value: u64,
pub step_type: StepType,
pub entry: Option<Entry>,
}
fn parse_step<Lines: Iterator<Item = Result<String, std::io::Error>>>(
mut tokens: LineTokens<'_>,
l: &mut Lines,
) -> Step {
let step_value = tokens.next().unwrap().parse::<u64>().unwrap();
let step_type_str = tokens.next().unwrap();
let step_type = if step_type_str == STEP_TYPE_QUERY {
StepType::Query
} else if step_type_str == STEP_TYPE_CHECK_ANSWER {
StepType::CheckAnswer
} else if step_type_str == STEP_TYPE_TIME_PASSES {
StepType::TimePasses
} else if step_type_str == STEP_TYPE_TRAFFIC {
StepType::Traffic
} else if step_type_str == STEP_TYPE_CHECK_TEMPFILE {
StepType::CheckTempfile
} else if step_type_str == STEP_TYPE_ASSIGN {
StepType::Assign
} else {
todo!();
};
let mut step = Step {
step_value,
step_type,
entry: None,
};
match step.step_type {
StepType::Query => (), // Continue with entry
StepType::CheckAnswer => (), // Continue with entry
StepType::TimePasses => {
println!("parse_step: should handle TIME_PASSES");
return step;
}
StepType::Traffic => {
println!("parse_step: should handle TRAFFIC");
return step;
}
StepType::CheckTempfile => {
println!("parse_step: should handle CHECK_TEMPFILE");
return step;
}
StepType::Assign => {
println!("parse_step: should handle ASSIGN");
return step;
}
}
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == ENTRY_BEGIN {
step.entry = Some(parse_entry(l));
//println!("parse_step: {:?}", step);
return step;
}
todo!();
}
}
#[derive(Clone, Debug, Default)]
pub struct Entry {
pub matches: Option<Matches>,
pub adjust: Option<Adjust>,
pub reply: Option<Reply>,
pub sections: Option<Sections>,
}
fn parse_entry<Lines: Iterator<Item = Result<String, std::io::Error>>>(
l: &mut Lines,
) -> Entry {
let mut entry = Entry {
matches: None,
adjust: None,
reply: None,
sections: None,
};
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == MATCH {
entry.matches = Some(parse_match(tokens));
continue;
}
if token == ADJUST {
entry.adjust = Some(parse_adjust(tokens));
continue;
}
if token == REPLY {
entry.reply = Some(parse_reply(tokens));
continue;
}
if token == SECTION {
let (sections, line) = parse_section(tokens, l);
//println!("parse_entry: sections {:?}", sections);
entry.sections = Some(sections);
let clean_line = get_clean_line(line.as_ref());
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == ENTRY_END {
break;
}
todo!();
}
if token == ENTRY_END {
break;
}
todo!();
}
entry
}
#[derive(Clone, Debug)]
pub struct Sections {
pub question: Vec<parse_query::Entry>,
pub answer: Vec<ZonefileEntry>,
pub authority: Vec<ZonefileEntry>,
pub additional: Vec<ZonefileEntry>,
}
fn parse_section<Lines: Iterator<Item = Result<String, std::io::Error>>>(
mut tokens: LineTokens<'_>,
l: &mut Lines,
) -> (Sections, String) {
let mut sections = Sections {
question: Vec::new(),
answer: Vec::new(),
authority: Vec::new(),
additional: Vec::new(),
};
let next = tokens.next().unwrap();
let mut section = if next == QUESTION {
Section::Question
} else {
panic!("Bad section {next}");
};
// Should extract which section
loop {
let line = l.next().unwrap().unwrap();
let clean_line = get_clean_line(line.as_ref());
if clean_line.is_none() {
continue;
}
let clean_line = clean_line.unwrap();
let mut tokens = LineTokens::new(clean_line);
let token = tokens.next().unwrap();
if token == SECTION {
let next = tokens.next().unwrap();
section = if next == QUESTION {
Section::Question
} else if next == ANSWER {
Section::Answer
} else if next == AUTHORITY {
Section::Authority
} else if next == ADDITIONAL {
Section::Additional
} else {
panic!("Bad section {next}");
};
continue;
}
if token == ENTRY_END {
return (sections, line);
}
match section {
Section::Question => {
let mut zonefile = QueryZonefile::new();
zonefile.extend_from_slice(clean_line.as_ref());
zonefile.extend_from_slice(b"\n");
let e = zonefile.next_entry().unwrap();
sections.question.push(e.unwrap());
}
Section::Answer | Section::Authority | Section::Additional => {
let mut zonefile = Zonefile::new();
zonefile.extend_from_slice(b"$ORIGIN .\n");
zonefile.extend_from_slice(b"ignore 3600 in ns ignore\n");
zonefile.extend_from_slice(clean_line.as_ref());
zonefile.extend_from_slice(b"\n");
let _e = zonefile.next_entry().unwrap();
let e = zonefile.next_entry().unwrap();
let e = e.unwrap();
match section {
Section::Question => panic!("should not be here"),
Section::Answer => sections.answer.push(e),
Section::Authority => sections.authority.push(e),
Section::Additional => sections.additional.push(e),
}
}
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Matches {
pub additional: bool,
pub all: bool,
pub answer: bool,
pub authority: bool,
pub fl_do: bool,
pub flags: bool,
pub opcode: bool,
pub qname: bool,
pub qtype: bool,
pub question: bool,
pub rcode: bool,
pub subdomain: bool,
pub tcp: bool,
pub ttl: bool,
pub udp: bool,
}
fn parse_match(mut tokens: LineTokens<'_>) -> Matches {
let mut matches: Matches = Default::default();
loop {
let token = match tokens.next() {
None => return matches,
Some(token) => token,
};
if token == "all" {
matches.all = true;
} else if token == "DO" {
matches.fl_do = true;
} else if token == "opcode" {
matches.opcode = true;
} else if token == "qname" {
matches.qname = true;
} else if token == "question" {
matches.question = true;
} else if token == "qtype" {
matches.qtype = true;
} else if token == "subdomain" {
matches.subdomain = true;
} else if token == "TCP" {
matches.tcp = true;
} else if token == "ttl" {
matches.ttl = true;
} else if token == "UDP" {
matches.tcp = true;
} else {
println!("should handle match {token:?}");
todo!();
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Adjust {
pub copy_id: bool,
pub copy_query: bool,
}
fn parse_adjust(mut tokens: LineTokens<'_>) -> Adjust {
let mut adjust: Adjust = Default::default();
loop {
let token = match tokens.next() {
None => return adjust,
Some(token) => token,
};
if token == "copy_id" {
adjust.copy_id = true;
} else if token == "copy_query" {
adjust.copy_query = true;
} else {
println!("should handle adjust {token:?}");
todo!();
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Reply {
pub aa: bool,
pub ad: bool,
pub cd: bool,
pub fl_do: bool,
pub formerr: bool,
pub noerror: bool,
pub nxdomain: bool,
pub qr: bool,
pub ra: bool,
pub rd: bool,
pub refused: bool,
pub servfail: bool,
pub tc: bool,
pub yxdomain: bool,
}
fn parse_reply(mut tokens: LineTokens<'_>) -> Reply {
let mut reply: Reply = Default::default();
loop {
let token = match tokens.next() {
None => return reply,
Some(token) => token,
};
if token == "AA" {
reply.aa = true;
} else if token == "AD" {
reply.ad = true;
} else if token == "CD" {
reply.cd = true;
} else if token == "DO" {
reply.fl_do = true;
} else if token == "FORMERR" {
reply.formerr = true;
} else if token == "NOERROR" {
reply.noerror = true;
} else if token == "NXDOMAIN" {
reply.nxdomain = true;
} else if token == "QR" {
reply.qr = true;
} else if token == "RA" {
reply.ra = true;
} else if token == "RD" {
reply.rd = true;
} else if token == "REFUSED" {
reply.refused = true;
} else if token == "SERVFAIL" {
reply.servfail = true;
} else if token == "TC" {
reply.tc = true;
} else if token == "YXDOMAIN" {
reply.yxdomain = true;
} else {
println!("should handle reply {token:?}");
todo!();
}
}
}
fn get_clean_line(line: &str) -> Option<&str> {
//println!("get clean line for {:?}", line);
let opt_comment = line.find(';');
let line = if let Some(index) = opt_comment {
&line[0..index]
} else {
line
};
let trimmed = line.trim();
//println!("line after trim() {:?}", trimmed);
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
}
struct LineTokens<'a> {
str: &'a str,
curr_index: usize,
}
impl<'a> LineTokens<'a> {
fn new(str: &'a str) -> Self {
Self { str, curr_index: 0 }
}
}
impl<'a> Iterator for LineTokens<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let cur_str = &self.str[self.curr_index..];
if cur_str.is_empty() {
return None;
}
// Assume cur_str starts with a token
for (index, char) in cur_str.char_indices() {
if !char.is_whitespace() {
continue;
}
let start_index = self.curr_index;
let end_index = start_index + index;
let space_str = &self.str[end_index..];
for (index, char) in space_str.char_indices() {
if char.is_whitespace() {
continue;
}
self.curr_index = end_index + index;
return Some(&self.str[start_index..end_index]);
}
todo!();
}
self.curr_index = self.str.len();
Some(cur_str)
}
}
File diff suppressed because it is too large Load Diff
+133
View File
@@ -0,0 +1,133 @@
use crate::net::deckard::client::CurrStepValue;
use crate::net::deckard::matches::match_msg;
use crate::net::deckard::parse_deckard;
use crate::net::deckard::parse_deckard::{Adjust, Deckard, Reply};
use crate::net::deckard::parse_query;
use domain::base::iana::rcode::Rcode;
use domain::base::{Message, MessageBuilder};
use domain::dep::octseq::Octets;
use domain::zonefile::inplace::Entry as ZonefileEntry;
pub fn do_server<'a, Oct: Clone + Octets + 'a>(
msg: &'a Message<Oct>,
deckard: &Deckard,
step_value: &CurrStepValue,
) -> Option<Message<Vec<u8>>>
where
<Oct as Octets>::Range<'a>: Clone,
{
let ranges = &deckard.scenario.ranges;
let step = step_value.get();
for range in ranges {
if step < range.start_value || step > range.end_value {
continue;
}
for entry in &range.entry {
if !match_msg(entry, msg, false) {
continue;
}
let reply = do_adjust(entry, msg);
return Some(reply);
}
}
todo!();
}
fn do_adjust<Octs: Octets>(
entry: &parse_deckard::Entry,
reqmsg: &Message<Octs>,
) -> Message<Vec<u8>> {
let sections = entry.sections.as_ref().unwrap();
let adjust: Adjust = match &entry.adjust {
Some(adjust) => adjust.clone(),
None => Default::default(),
};
let mut msg = MessageBuilder::new_vec().question();
if adjust.copy_query {
for q in reqmsg.question() {
msg.push(q.unwrap()).unwrap();
}
} else {
for q in &sections.question {
let question = match q {
parse_query::Entry::QueryRecord(question) => question,
_ => todo!(),
};
msg.push(question).unwrap();
}
}
let mut msg = msg.answer();
for a in &sections.answer {
let rec = if let ZonefileEntry::Record(record) = a {
record
} else {
panic!("include not expected")
};
msg.push(rec).unwrap();
}
let mut msg = msg.authority();
for a in &sections.authority {
let rec = if let ZonefileEntry::Record(record) = a {
record
} else {
panic!("include not expected")
};
msg.push(rec).unwrap();
}
let mut msg = msg.additional();
for _a in &sections.additional {
todo!();
}
let reply: Reply = match &entry.reply {
Some(reply) => reply.clone(),
None => Default::default(),
};
if reply.aa {
msg.header_mut().set_aa(true);
}
if reply.ad {
todo!()
}
if reply.cd {
todo!()
}
if reply.fl_do {
todo!()
}
if reply.formerr {
todo!()
}
if reply.noerror {
msg.header_mut().set_rcode(Rcode::NoError);
}
if reply.nxdomain {
todo!()
}
if reply.qr {
msg.header_mut().set_qr(true);
}
if reply.ra {
todo!()
}
if reply.rd {
msg.header_mut().set_rd(true);
}
if reply.refused {
todo!()
}
if reply.servfail {
todo!()
}
if reply.tc {
todo!()
}
if reply.yxdomain {
todo!()
}
if adjust.copy_id {
msg.header_mut().set_id(reqmsg.header().id());
} else {
todo!();
}
msg.into_message()
}
+1
View File
@@ -0,0 +1 @@
pub mod deckard;