From 749ededab7046aa684ad0ce08467b513bee1bfc3 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 12 Jul 2024 16:56:12 +0200 Subject: [PATCH 01/38] Prototype version of a query router. --- Cargo.toml | 1 + examples/query-routing.rs | 815 +++++++++++++++++++++++++++++++++ src/net/server/adapter.rs | 108 +++++ src/net/server/message.rs | 478 +++++++++++++++++++ src/net/server/mod.rs | 5 +- src/net/server/query_router.rs | 78 ++++ src/net/server/sr_service.rs | 178 +++++++ 7 files changed, 1662 insertions(+), 1 deletion(-) create mode 100644 examples/query-routing.rs create mode 100644 src/net/server/adapter.rs create mode 100644 src/net/server/query_router.rs create mode 100644 src/net/server/sr_service.rs diff --git a/Cargo.toml b/Cargo.toml index 554aae9ff..a11e318a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ unstable-zonetree = ["futures", "parking_lot", "serde", "tokio", "tracing"] [dev-dependencies] lazy_static = { version = "1.4.0" } +mock_instant = { version = "0.4.0" } rstest = "0.19.0" rustls-pemfile = { version = "2.1.2" } serde_test = "1.0.130" diff --git a/examples/query-routing.rs b/examples/query-routing.rs new file mode 100644 index 000000000..f968f0a9c --- /dev/null +++ b/examples/query-routing.rs @@ -0,0 +1,815 @@ +use core::future::ready; + +use core::fmt; +use core::fmt::Debug; +use core::future::{Future, Ready}; +use core::ops::ControlFlow; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::task::{Context, Poll}; +use core::time::Duration; +use std::fs::File; +use std::io; +use std::io::BufReader; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::RwLock; + +use octseq::{FreezeBuilder, Octets}; +use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; +use tokio::time::Instant; +use tokio_rustls::rustls; +use tokio_rustls::TlsAcceptor; +use tokio_tfo::{TfoListener, TfoStream}; +//use tracing_subscriber::EnvFilter; + +use domain::base::iana::{Class, Rcode}; +use domain::base::message_builder::{AdditionalBuilder, PushError}; +use domain::base::name::ToLabelIter; +use domain::base::wire::Composer; +use domain::base::{MessageBuilder, Name, StreamTarget}; +use domain::net::client::dgram as client_dgram; +use domain::net::client::protocol::UdpConnect; +use domain::net::server::adapter::ClientTransportToSrService; +use domain::net::server::adapter::SrServiceToService; +use domain::net::server::buf::VecBufSource; +use domain::net::server::dgram; +use domain::net::server::dgram::DgramServer; +use domain::net::server::message::Request; +use domain::net::server::middleware::builder::MiddlewareBuilder; +use domain::net::server::middleware::processor::MiddlewareProcessor; +#[cfg(feature = "siphasher")] +use domain::net::server::middleware::processors::cookies::CookiesMiddlewareProcessor; +use domain::net::server::middleware::processors::mandatory::MandatoryMiddlewareProcessor; +use domain::net::server::query_router::QueryRouter; +use domain::net::server::service::{ + CallResult, Service, ServiceError, ServiceFeedback, Transaction, +}; +use domain::net::server::sock::AsyncAccept; +use domain::net::server::sr_service::ReplyMessage; +use domain::net::server::stream; +use domain::net::server::stream::StreamServer; +use domain::net::server::util::{mk_builder_for_target, service_fn}; +use domain::net::server::ConnectionConfig; +use domain::rdata::A; + +//----------- mk_answer() ---------------------------------------------------- + +// Helper fn to create a dummy response to send back to the client +fn mk_answer( + msg: &Request>, + builder: MessageBuilder>, +) -> Result>, PushError> +where + Target: Octets + Composer + FreezeBuilder, + ::AppendError: fmt::Debug, +{ + let mut answer = + builder.start_answer(msg.message(), Rcode::NOERROR).unwrap(); + answer.push(( + Name::root_ref(), + Class::IN, + 86400, + A::from_octets(192, 0, 2, 1), + ))?; + Ok(answer.additional()) +} + +//----------- Example Service trait implementations -------------------------- + +//--- MyService + +struct MyService; + +/// This example shows how to implement the [`Service`] trait directly. +/// +/// See [`query`] and [`name_to_ip`] for ways of implementing the [`Service`] +/// trait for a function instead of a struct. +impl Service> for MyService { + type Target = Vec; + type Future = Ready, ServiceError>>; + + fn call( + &self, + request: Request>, + ) -> Result, ServiceError> { + let builder = mk_builder_for_target(); + let additional = mk_answer(&request, builder)?; + let item = ready(Ok(CallResult::new(additional))); + let txn = Transaction::single(item); + Ok(txn) + } +} + +//--- name_to_ip() + +/// This function shows how to implement [`Service`] logic by matching the +/// function signature required by the [`Service`] trait. +/// +/// The function signature is slightly more complex than when using +/// [`service_fn`] (see the [`query`] example below). +#[allow(clippy::type_complexity)] +fn name_to_ip( + request: Request>, +) -> Result< + Transaction< + Target, + impl Future, ServiceError>> + Send, + >, + ServiceError, +> +where + Target: + Composer + Octets + FreezeBuilder + Default + Send, + ::AppendError: Debug, +{ + let mut out_answer = None; + if let Ok(question) = request.message().sole_question() { + let qname = question.qname(); + let num_labels = qname.label_count(); + if num_labels >= 5 { + let mut iter = qname.iter_labels(); + let a = iter.nth(num_labels - 5).unwrap(); + let b = iter.next().unwrap(); + let c = iter.next().unwrap(); + let d = iter.next().unwrap(); + let a_rec: Result = format!("{a}.{b}.{c}.{d}").parse(); + if let Ok(a_rec) = a_rec { + let builder = mk_builder_for_target(); + let mut answer = builder + .start_answer(request.message(), Rcode::NOERROR) + .unwrap(); + answer + .push((Name::root_ref(), Class::IN, 86400, a_rec)) + .unwrap(); + out_answer = Some(answer); + } + } + } + + if out_answer.is_none() { + let builder = mk_builder_for_target(); + eprintln!("Refusing request, only requests for A records in IPv4 dotted quad format are accepted by this service."); + out_answer = Some( + builder + .start_answer(request.message(), Rcode::REFUSED) + .unwrap(), + ); + } + + let additional = out_answer.unwrap().additional(); + let item = Ok(CallResult::new(additional)); + Ok(Transaction::single(ready(item))) +} + +//--- query() + +/// This function shows how to implement [`Service`] logic by matching the +/// function signature required by [`service_fn`]. +/// +/// The function signature is slightly simpler to write than when not using +/// [`service_fn`] and supports passing in meta data without any extra +/// boilerplate. +#[allow(clippy::type_complexity)] +fn query( + request: Request>, + count: Arc, +) -> Result< + Transaction< + Vec, + impl Future>, ServiceError>> + Send, + >, + ServiceError, +> { + let cnt = count + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| { + Some(if x > 0 { x - 1 } else { 0 }) + }) + .unwrap(); + + // This fn blocks the server until it returns. By returning a future that + // handles the request we allow the server to execute the future in the + // background without blocking the server. + let fut = async move { + eprintln!("Sleeping for 100ms"); + tokio::time::sleep(Duration::from_millis(100)).await; + + // Note: A real service would have application logic here to process + // the request and generate an response. + + let idle_timeout = Duration::from_millis((50 * cnt).into()); + let cmd = ServiceFeedback::Reconfigure { + idle_timeout: Some(idle_timeout), + }; + eprintln!("Setting idle timeout to {idle_timeout:?}"); + + let builder = mk_builder_for_target(); + let answer = mk_answer(&request, builder)?; + let res = CallResult::new(answer).with_feedback(cmd); + Ok(res) + }; + Ok(Transaction::single(fut)) +} + +//----------- Example socket trait implementations --------------------------- + +//--- DoubleListener + +struct DoubleListener { + a: TcpListener, + b: TcpListener, + alt: AtomicBool, +} + +impl DoubleListener { + fn new(a: TcpListener, b: TcpListener) -> Self { + let alt = AtomicBool::new(false); + Self { a, b, alt } + } +} + +/// Combine two streams into one by interleaving the output of both as it is +/// produced. +impl AsyncAccept for DoubleListener { + type Error = io::Error; + type StreamType = TcpStream; + type Future = Ready>; + + fn poll_accept( + &self, + cx: &mut Context, + ) -> Poll> { + let (x, y) = match self.alt.fetch_xor(true, Ordering::SeqCst) { + false => (&self.a, &self.b), + true => (&self.b, &self.a), + }; + + match TcpListener::poll_accept(x, cx) + .map(|res| res.map(|(stream, addr)| (ready(Ok(stream)), addr))) + { + Poll::Ready(res) => Poll::Ready(res), + Poll::Pending => TcpListener::poll_accept(y, cx).map(|res| { + res.map(|(stream, addr)| (ready(Ok(stream)), addr)) + }), + } + } +} + +//--- LocalTfoListener + +struct LocalTfoListener(TfoListener); + +impl std::ops::DerefMut for LocalTfoListener { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::ops::Deref for LocalTfoListener { + type Target = TfoListener; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsyncAccept for LocalTfoListener { + type Error = io::Error; + type StreamType = TfoStream; + type Future = Ready>; + + fn poll_accept( + &self, + cx: &mut Context, + ) -> Poll> { + TfoListener::poll_accept(self, cx) + .map(|res| res.map(|(stream, addr)| (ready(Ok(stream)), addr))) + } +} + +//--- BufferedTcpListener + +struct BufferedTcpListener(TcpListener); + +impl std::ops::DerefMut for BufferedTcpListener { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::ops::Deref for BufferedTcpListener { + type Target = TcpListener; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsyncAccept for BufferedTcpListener { + type Error = io::Error; + type StreamType = tokio::io::BufReader; + type Future = Ready>; + + fn poll_accept( + &self, + cx: &mut Context, + ) -> Poll> { + match TcpListener::poll_accept(self, cx) { + Poll::Ready(Ok((stream, addr))) => { + let stream = tokio::io::BufReader::new(stream); + Poll::Ready(Ok((ready(Ok(stream)), addr))) + } + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending => Poll::Pending, + } + } +} + +//--- RustlsTcpListener + +pub struct RustlsTcpListener { + listener: TcpListener, + acceptor: tokio_rustls::TlsAcceptor, +} + +impl RustlsTcpListener { + pub fn new( + listener: TcpListener, + acceptor: tokio_rustls::TlsAcceptor, + ) -> Self { + Self { listener, acceptor } + } +} + +impl AsyncAccept for RustlsTcpListener { + type Error = io::Error; + type StreamType = tokio_rustls::server::TlsStream; + type Future = tokio_rustls::Accept; + + #[allow(clippy::type_complexity)] + fn poll_accept( + &self, + cx: &mut Context, + ) -> Poll> { + TcpListener::poll_accept(&self.listener, cx).map(|res| { + res.map(|(stream, addr)| (self.acceptor.accept(stream), addr)) + }) + } +} + +//----------- CustomMiddleware ----------------------------------------------- + +#[derive(Default)] +struct Stats { + slowest_req: Duration, + fastest_req: Duration, + num_req_bytes: u32, + num_resp_bytes: u32, + num_reqs: u32, + num_ipv4: u32, + num_ipv6: u32, + num_udp: u32, +} + +#[derive(Default)] +pub struct StatsMiddlewareProcessor { + stats: RwLock, +} + +impl StatsMiddlewareProcessor { + /// Creates an instance of this processor. + #[must_use] + pub fn new() -> Self { + Default::default() + } +} + +impl std::fmt::Display for StatsMiddlewareProcessor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let stats = self.stats.read().unwrap(); + write!(f, "# Reqs={} [UDP={}, IPv4={}, IPv6={}] Bytes [rx={}, tx={}] Speed [fastest={}μs, slowest={}μs]", + stats.num_reqs, + stats.num_udp, + stats.num_ipv4, + stats.num_ipv6, + stats.num_req_bytes, + stats.num_resp_bytes, + stats.fastest_req.as_micros(), + stats.slowest_req.as_micros())?; + Ok(()) + } +} + +impl MiddlewareProcessor + for StatsMiddlewareProcessor +where + RequestOctets: AsRef<[u8]> + Octets, + Target: Composer + Default, +{ + fn preprocess( + &self, + _request: &Request, + ) -> ControlFlow>> { + ControlFlow::Continue(()) + } + + fn postprocess( + &self, + request: &Request, + _response: &mut AdditionalBuilder>, + ) { + let duration = Instant::now().duration_since(request.received_at()); + let mut stats = self.stats.write().unwrap(); + + stats.num_reqs += 1; + stats.num_req_bytes += request.message().as_slice().len() as u32; + stats.num_resp_bytes += _response.as_slice().len() as u32; + + if request.transport_ctx().is_udp() { + stats.num_udp += 1; + } + + if request.client_addr().is_ipv4() { + stats.num_ipv4 += 1; + } else { + stats.num_ipv6 += 1; + } + + if duration < stats.fastest_req { + stats.fastest_req = duration; + } + if duration > stats.slowest_req { + stats.slowest_req = duration; + } + } +} + +//----------- main() --------------------------------------------------------- + +#[tokio::main(flavor = "multi_thread")] +async fn main() { + eprintln!("Test with commands such as:"); + eprintln!(" dig +short -4 @127.0.0.1 -p 8053 A 1.2.3.4"); + eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8053 A google.com"); + eprintln!(" dig +short -4 @127.0.0.1 -p 8054 A google.com"); + eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8080 A google.com"); + eprintln!(" dig +short -6 @::1 +tcp -p 8080 A google.com"); + eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8081 A google.com"); + eprintln!(" dig +short -4 @127.0.0.1 +tls -p 8443 A google.com"); + eprintln!( + " dnsi query --format table --server 127.0.0.1 -p 8053 1.2.3.4 A" + ); + eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8053 --tcp google.com A"); // Fails, dig version works but slow. + eprintln!( + " dnsi query --format table --server 127.0.0.1 -p 8054 google.com A" + ); + eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8080 --tcp google.com A"); + eprintln!( + " dnsi query --format table --server ::1 -p 8080 --tcp google.com A" + ); + eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8081 --tcp google.com A"); + eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8443 --tls google.com A"); + + /* + // ----------------------------------------------------------------------- + // Setup logging. You can override the log level by setting environment + // variable RUST_LOG, e.g. RUST_LOG=trace. + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_thread_ids(true) + .without_time() + .try_init() + .ok(); + */ + + // Start building the query router plus upstreams. + let mut qr: QueryRouter, Vec, ReplyMessage> = + QueryRouter::new(); + + // Queries to the root go to 1.1.1.1 + let server_addr = + SocketAddr::new(IpAddr::from_str("1.1.1.1").unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let dgram_conn = client_dgram::Connection::new(udp_connect); + let conn_service = ClientTransportToSrService::new(dgram_conn); + qr.add(Name::>::from_str(".").unwrap(), conn_service); + + // Queries to .com go to 8.8.8.8 + let server_addr = + SocketAddr::new(IpAddr::from_str("8.8.8.8").unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let dgram_conn = client_dgram::Connection::new(udp_connect); + let conn_service = ClientTransportToSrService::new(dgram_conn); + qr.add(Name::>::from_str("com").unwrap(), conn_service); + + // Queries to .nl go to 9.9.9.9 + let server_addr = + SocketAddr::new(IpAddr::from_str("9.9.9.9").unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let dgram_conn = client_dgram::Connection::new(udp_connect); + let conn_service = ClientTransportToSrService::new(dgram_conn); + qr.add(Name::>::from_str("nl").unwrap(), conn_service); + + let srv = SrServiceToService::new(qr); + + // ----------------------------------------------------------------------- + // Wrap `MyService` in an `Arc` so that it can be used by multiple servers + // at once. + let svc = Arc::new(srv); + + // ----------------------------------------------------------------------- + // Prepare a modern middleware chain for use by servers defined below. + // Inject a custom statistics middleware processor (defined above) at the + // start of the chain so that it can time the request processing time from + // as early till as late as possible (excluding time spent in the servers + // that receive the requests and send the responses). + let mut middleware = MiddlewareBuilder::default(); + let stats = Arc::new(StatsMiddlewareProcessor::new()); + middleware.push_front(stats.clone()); + let middleware = middleware.build(); + + // ----------------------------------------------------------------------- + // Run a DNS server on UDP port 8053 on 127.0.0.1. Test it like so: + // dig +short -4 @127.0.0.1 -p 8053 A google.com + let udpsocket = UdpSocket::bind("127.0.0.1:8053").await.unwrap(); + let buf = Arc::new(VecBufSource); + let mut config = dgram::Config::default(); + config.set_middleware_chain(middleware.clone()); + let srv = + DgramServer::with_config(udpsocket, buf.clone(), name_to_ip, config); + + let udp_join_handle = tokio::spawn(async move { srv.run().await }); + + // ----------------------------------------------------------------------- + // Run a DNS server on TCP port 8053 on 127.0.0.1. Test it like so: + // dig +short +keepopen +tcp -4 @127.0.0.1 -p 8053 A google.com + let v4socket = TcpSocket::new_v4().unwrap(); + v4socket.set_reuseaddr(true).unwrap(); + v4socket.bind("127.0.0.1:8053".parse().unwrap()).unwrap(); + let v4listener = v4socket.listen(1024).unwrap(); + let buf = Arc::new(VecBufSource); + let mut conn_config = ConnectionConfig::default(); + conn_config.set_middleware_chain(middleware.clone()); + let mut config = stream::Config::default(); + config.set_connection_config(conn_config); + let srv = StreamServer::with_config( + v4listener, + buf.clone(), + svc.clone(), + config, + ); + let srv = srv.with_pre_connect_hook(|stream| { + // Demonstrate one way without having access to the code that creates + // the socket initially to enable TCP keep alive, + eprintln!("TCP connection detected: enabling socket TCP keepalive."); + + let keep_alive = socket2::TcpKeepalive::new() + .with_time(Duration::from_secs(20)) + .with_interval(Duration::from_secs(20)); + let socket = socket2::SockRef::from(&stream); + socket.set_tcp_keepalive(&keep_alive).unwrap(); + + // Sleep to give us time to run a command like + // `ss -nte` to see the keep-alive is set. It + // shows up in the ss output like this: + // timer:(keepalive,18sec,0) + eprintln!("Waiting for 5 seconds so you can run a command like:"); + eprintln!(" ss -nte | grep 8053 | grep keepalive"); + eprintln!("and see `timer:(keepalive,20sec,0) or similar."); + std::thread::sleep(Duration::from_secs(5)); + }); + + let tcp_join_handle = tokio::spawn(async move { srv.run().await }); + + #[cfg(target_os = "linux")] + let udp_mtu_join_handle = { + // This UDP example sets IP_MTU_DISCOVER via setsockopt(), using the + // libc crate (as the nix crate doesn't support IP_MTU_DISCOVER at the + // time of writing). This example is inspired by: + // + // - https://www.ietf.org/archive/id/draft-ietf-dnsop-avoid-fragmentation-17.html#name-recommendations-for-udp-res + // - https://mailarchive.ietf.org/arch/msg/dnsop/Zy3wbhHephubsy2uJesGeDst4F4/ + // - https://man7.org/linux/man-pages/man7/ip.7.html + // + // Some other good reading on sending faster via UDP with Rust: + // - https://devork.be/blog/2023/11/modern-linux-sockets/ + // + // We could also try the following settings that the Unbound man page + // mentions: + // - SO_RCVBUF - Unbound advises setting so-rcvbuf to 4m on busy + // servers to prevent short request spikes causing + // packet drops, + // - SO_SNDBUF - Unbound advises setting so-sndbuf to 4m on busy + // servers to avoid resource temporarily + // unavailable errors, + // - SO_REUSEPORT - Unbound advises to turn it off at extreme load + // to distribute queries evenly, + // - IP_TRANSPARENT - Allows to bind to non-existent IP addresses + // that are going to exist later on. Unbound uses + // IP_BINDANY on FreeBSD and SO_BINDANY on + // OpenBSD. + // - IP_FREEBIND - Linux only, similar to IP_TRANSPARENT. Allows + // to bind to IP addresses that are nonlocal or do + // not exist, like when the network interface is + // down. + // - TCP_MAXSEG - Value lower than common MSS on Ethernet (1220 + // for example) will address path MTU problem. + // - A means to control the value of the Differentiated Services + // Codepoint (DSCP) in the differentiated services field (DS) of + // the outgoing IP packet headers. + fn setsockopt(socket: libc::c_int, flag: libc::c_int) -> libc::c_int { + unsafe { + libc::setsockopt( + socket, + libc::IPPROTO_UDP, + libc::IP_MTU_DISCOVER, + &flag as *const libc::c_int as *const libc::c_void, + std::mem::size_of_val(&flag) as libc::socklen_t, + ) + } + } + + let udpsocket = UdpSocket::bind("127.0.0.1:8054").await.unwrap(); + let fd = ::as_raw_fd(&udpsocket); + if setsockopt(fd, libc::IP_PMTUDISC_OMIT) == -1 { + eprintln!( + "setsockopt error when setting IP_MTU_DISCOVER to IP_PMTUDISC_OMIT, will retry with IP_PMTUDISC_DONT: {}", + std::io::Error::last_os_error() + ); + + if setsockopt(fd, libc::IP_PMTUDISC_DONT) == -1 { + eprintln!( + "setsockopt error when setting IP_MTU_DISCOVER to IP_PMTUDISC_DONT: {}", + std::io::Error::last_os_error() + ); + } + } + + let mut config = dgram::Config::default(); + config.set_middleware_chain(middleware.clone()); + let srv = DgramServer::with_config( + udpsocket, + buf.clone(), + svc.clone(), + config, + ); + + tokio::spawn(async move { srv.run().await }) + }; + + // ----------------------------------------------------------------------- + // Demonstrate manually binding to two separate IPv4 and IPv6 sockets and + // then listening on both at once using a single server instance. (e.g. + // for on platforms that don't support binding to IPv4 and IPv6 at once + // using a single socket). + let v4socket = TcpSocket::new_v4().unwrap(); + v4socket.set_reuseaddr(true).unwrap(); + v4socket.bind("127.0.0.1:8080".parse().unwrap()).unwrap(); + let v4listener = v4socket.listen(1024).unwrap(); + + let v6socket = TcpSocket::new_v6().unwrap(); + v6socket.set_reuseaddr(true).unwrap(); + v6socket.bind("[::1]:8080".parse().unwrap()).unwrap(); + let v6listener = v6socket.listen(1024).unwrap(); + + let listener = DoubleListener::new(v4listener, v6listener); + let mut conn_config = ConnectionConfig::new(); + conn_config.set_middleware_chain(middleware.clone()); + let mut config = stream::Config::new(); + config.set_connection_config(conn_config); + let srv = + StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + let double_tcp_join_handle = tokio::spawn(async move { srv.run().await }); + + // ----------------------------------------------------------------------- + // Demonstrate listening with TCP Fast Open enabled (via the tokio-tfo crate). + // On Linux strace can be used to show that the socket options are indeed + // set as expected, e.g.: + // + // > strace -e trace=setsockopt cargo run --example serve \ + // --features serve,tokio-tfo --release + // Finished release [optimized] target(s) in 0.12s + // Running `target/release/examples/serve` + // setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + // setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + // setsockopt(8, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + // setsockopt(8, SOL_TCP, TCP_FASTOPEN, [1024], 4) = 0 + + let listener = TfoListener::bind("127.0.0.1:8081".parse().unwrap()) + .await + .unwrap(); + let listener = LocalTfoListener(listener); + let mut conn_config = ConnectionConfig::new(); + conn_config.set_middleware_chain(middleware.clone()); + let mut config = stream::Config::new(); + config.set_connection_config(conn_config); + let srv = + StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + let tfo_join_handle = tokio::spawn(async move { srv.run().await }); + + // ----------------------------------------------------------------------- + // Demonstrate using a simple function instead of a struct as the service + // Note that this service reduces its connection timeout on each subsequent + // query handled on the same connection, so try someting like this and you + // should see later queries getting communication errors: + // + // > dig +short +keepopen +tcp -4 @127.0.0.1 -p 8082 A google.com A \ + // google.com A google.com A google.com A google.com A google.com \ + // A google.com + // .. + // 192.0.2.1 + // 192.0.2.1 + // .. + // ;; communications error to 127.0.0.1#8082: end of file + // + // This example also demonstrates wrapping the TcpStream inside a + // BufReader to minimize overhead from system I/O calls. + + let listener = TcpListener::bind("127.0.0.1:8082").await.unwrap(); + let listener = BufferedTcpListener(listener); + let count = Arc::new(AtomicU8::new(5)); + + // Make our service from the `query` function with the help of the + // `service_fn` function. + let fn_svc = service_fn(query, count); + + // Show that we don't have to use the same middleware with every server by + // creating a separate middleware chain for use just by this server, and + // also show that by creating the individual middleware processors + // ourselves we can override their default configuration. + let mut fn_svc_middleware = MiddlewareBuilder::new(); + fn_svc_middleware.push(MandatoryMiddlewareProcessor::new().into()); + + #[cfg(feature = "siphasher")] + { + let server_secret = "server12secret34".as_bytes().try_into().unwrap(); + fn_svc_middleware + .push(CookiesMiddlewareProcessor::new(server_secret).into()); + } + + let fn_svc_middleware = fn_svc_middleware.build(); + + let mut conn_config = ConnectionConfig::new(); + conn_config.set_middleware_chain(fn_svc_middleware); + let mut config = stream::Config::new(); + config.set_connection_config(conn_config); + let srv = + StreamServer::with_config(listener, buf.clone(), fn_svc, config); + let fn_join_handle = tokio::spawn(async move { srv.run().await }); + + // ----------------------------------------------------------------------- + // Demonstrate using a TLS secured TCP DNS server. + + // Credit: The sample.(pem|rsa) files used here were taken from + // https://github.com/rustls/hyper-rustls/blob/main/examples/ + let certs = rustls_pemfile::certs(&mut BufReader::new( + File::open("examples/sample.pem").unwrap(), + )) + .collect::, _>>() + .unwrap(); + let key = rustls_pemfile::private_key(&mut BufReader::new( + File::open("examples/sample.rsa").unwrap(), + )) + .unwrap() + .unwrap(); + + let config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, key) + .unwrap(); + let acceptor = TlsAcceptor::from(Arc::new(config)); + let listener = TcpListener::bind("127.0.0.1:8443").await.unwrap(); + let listener = RustlsTcpListener::new(listener, acceptor); + + let mut conn_config = ConnectionConfig::new(); + conn_config.set_middleware_chain(middleware.clone()); + let mut config = stream::Config::new(); + config.set_connection_config(conn_config); + let srv = + StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + + let tls_join_handle = tokio::spawn(async move { srv.run().await }); + + // ----------------------------------------------------------------------- + // Print statistics periodically + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(15)); + loop { + interval.tick().await; + println!("Statistics report: {stats}"); + } + }); + + // ----------------------------------------------------------------------- + // Keep the services running in the background + + udp_join_handle.await.unwrap(); + tcp_join_handle.await.unwrap(); + #[cfg(target_os = "linux")] + udp_mtu_join_handle.await.unwrap(); + double_tcp_join_handle.await.unwrap(); + tfo_join_handle.await.unwrap(); + fn_join_handle.await.unwrap(); + tls_join_handle.await.unwrap(); +} diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs new file mode 100644 index 000000000..2d0926d03 --- /dev/null +++ b/src/net/server/adapter.rs @@ -0,0 +1,108 @@ +// adapters + +use super::message::{Request, RequestNG}; +use super::service::{CallResult, Service, ServiceError, Transaction}; +use super::sr_service::{ComposeReply, SrService}; +use std::boxed::Box; +use std::fmt::Debug; +use std::future::Future; +use std::marker::PhantomData; +//use std::future::Ready; +use crate::dep::octseq::Octets; +use crate::net::client::request::RequestMessage; +use crate::net::client::request::SendRequest; +use std::pin::Pin; +use std::vec::Vec; + +pub struct SrServiceToService { + service: SVC, + phantom: PhantomData, +} + +impl SrServiceToService { + pub fn new(service: SVC) -> Self { + Self { + service, + phantom: PhantomData, + } + } +} + +impl Service for SrServiceToService +where + SVC: SrService, CR>, + CR: ComposeReply + 'static, +{ + type Target = Vec; + //type Future = Ready, ServiceError>>; + type Future = Pin< + Box< + dyn Future< + Output = Result, ServiceError>, + > + Send + + Sync, + >, + >; + + fn call( + &self, + request: Request>, + ) -> Result, ServiceError> { + let req = RequestNG::from_request(request); + let fut = self.service.call(req); + let fut = async move { + let reply = fut.await.unwrap(); + let abs = reply.additional_builder_stream_target(); + Ok(CallResult::new(abs)) + }; + Ok(Transaction::single(Box::pin(fut))) + } +} + +pub struct ClientTransportToSrService +where + RequestOcts: AsRef<[u8]>, + SR: SendRequest>, +{ + conn: SR, + _phantom: PhantomData, +} + +impl ClientTransportToSrService +where + RequestOcts: AsRef<[u8]>, + SR: SendRequest>, +{ + pub fn new(conn: SR) -> Self { + Self { + conn, + _phantom: PhantomData, + } + } +} + +impl SrService + for ClientTransportToSrService +where + RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, + SR: SendRequest> + Sync, + CR: ComposeReply, +{ + type Target = Vec; + + fn call( + &self, + request: RequestNG, + ) -> Pin> + Send + Sync>> + where + RequestOcts: AsRef<[u8]>, + { + let req = request.to_request_message(); + let mut gr = self.conn.send_request(req); + let fut = async move { + let msg = gr.get_response().await.unwrap(); + Ok(CR::from_message(&msg)) + }; + Box::pin(fut) + } +} diff --git a/src/net/server/message.rs b/src/net/server/message.rs index 84889a45b..cfc33c3a9 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -1,11 +1,30 @@ //! Support for working with DNS messages in servers. +use bytes::Bytes; +use core::ops::ControlFlow; use core::time::Duration; +use std::fmt::Debug; +use std::net::SocketAddr; use std::sync::{Arc, Mutex}; +use std::vec::Vec; use tokio::time::Instant; +use crate::base::opt::AllOptData; use crate::base::Message; +use crate::base::Name; +//use crate::base::opt::OptRecord; +//use crate::base::opt::subnet::ClientSubnet; +//use crate::dep::octseq::OctetsFrom; +use crate::net::client::request::ComposeRequest; +use crate::net::client::request::RequestMessage; +use crate::net::server::buf::BufSource; +use crate::net::server::metrics::ServerMetrics; +use crate::net::server::middleware::chain::MiddlewareChain; + +use super::service::{CallResult, Service, ServiceError, Transaction}; +use super::util::start_reply; +use crate::base::wire::Composer; //------------ UdpTransportContext ------------------------------------------- @@ -284,3 +303,462 @@ where } } } + +//------------ RequestNG ------------------------------------------------------ + +/// A DNS message with additional properties describing its context. +/// +/// DNS messages don't exist in isolation, they are received from somewhere or +/// created by something. This type wraps a message with additional context +/// about its origins so that decisions can be taken based not just on the +/// message itself but also on the circumstances surrounding its creation and +/// delivery. +#[derive(Debug)] +pub struct RequestNG> { + /// The network address of the connected client. + client_addr: std::net::SocketAddr, + + /// The instant when the request was received. + received_at: Instant, + + /// The message that was received. + message: Arc>, + + /// Properties of the request specific to the server and transport + /// protocol via which it was received. + transport_specific: TransportSpecificContext, + + /// Options that should be used upstream in providing the service. + opt: Vec>>, +} + +impl> RequestNG { + /// Creates a new request wrapper around a message along with its context. + pub fn new( + client_addr: std::net::SocketAddr, + received_at: Instant, + message: Message, + transport_specific: TransportSpecificContext, + ) -> Self { + Self { + client_addr, + received_at, + message: Arc::new(message), + transport_specific, + opt: Vec::new(), + } + } + + pub fn from_request(request: Request) -> Self + where + Octs: Octets, + { + let mut req = Self { + client_addr: request.client_addr, + received_at: request.received_at, + message: request.message, + transport_specific: request.transport_specific, + opt: Vec::new(), + }; + + // Copy the ECS option from the message. This is just an example, + // there should be a separate plugin that deals with ECS. + + // We want the ECS options in Bytes. No clue how to do this. Just + // convert the message to Bytes and use that. + let bytes = Bytes::copy_from_slice(req.message.as_slice()); + let bytes_msg = Message::from_octets(bytes).unwrap(); + if let Some(optrec) = bytes_msg.opt() { + for opt in optrec.opt().iter::>() { + let opt = opt.unwrap(); + if let AllOptData::ClientSubnet(_ecs) = opt { + req.opt.push(opt); + } + } + } + + req + } + + pub fn to_request_message(&self) -> RequestMessage + where + Octs: Clone + Debug + Octets + Send + Sync, + { + // We need to make a copy of message. Somehow we can't use the + // message in the Arc directly. + let msg = + Message::from_octets(self.message.as_octets().clone()).unwrap(); + let mut reqmsg = RequestMessage::new(msg); + + // Copy DO bit + if dnssec_ok(&self.message) { + reqmsg.set_dnssec_ok(true); + } + + // Copy options + for opt in &self.opt { + reqmsg.add_opt(opt).unwrap(); + } + reqmsg + } + + /// When was this message received? + pub fn received_at(&self) -> Instant { + self.received_at + } + + /// Get a reference to the transport specific context + pub fn transport_ctx(&self) -> &TransportSpecificContext { + &self.transport_specific + } + + /// From which IP address and port number was this message received? + pub fn client_addr(&self) -> std::net::SocketAddr { + self.client_addr + } + + /// Read access to the inner message + pub fn message(&self) -> &Arc> { + &self.message + } +} + +//--- Clone + +impl> Clone for RequestNG { + fn clone(&self) -> Self { + Self { + client_addr: self.client_addr, + received_at: self.received_at, + message: Arc::clone(&self.message), + transport_specific: self.transport_specific.clone(), + opt: self.opt.clone(), + } + } +} + +//----------- CommonMessageFlow ---------------------------------------------- + +/// Perform processing common to all messages being handled by a DNS server. +/// +/// All messages received by a DNS server need to pass through the following +/// processing stages: +/// +/// - Pre-processing. +/// - Service processing. +/// - Post-processing. +/// +/// The strategy is common but some server specific aspects are delegated to +/// the server that implements this trait: +/// +/// - Adding context to a request. +/// - Finalizing the handling of a response. +/// +/// Servers implement this trait to benefit from the common processing +/// required while still handling aspects specific to the server themselves. +/// +/// Processing starts at [`process_request`]. +/// +///
+/// +/// This trait exists as a convenient mechanism for sharing common code +/// between server implementations. The default function implementations +/// provided by this trait are not intended to be overridden by consumers of +/// this library. +/// +///
+/// +/// [`process_request`]: Self::process_request() +pub trait CommonMessageFlow +where + Buf: BufSource, + Buf::Output: Octets + Send + Sync, + Svc: Service + Send + Sync, +{ + /// Server-specific data that it chooses to pass along with the request in + /// order that it may receive it when `process_call_result()` is + /// invoked on the implementing server. + type Meta: Clone + Send + Sync + 'static; + + /// Process a DNS request message. + /// + /// This function consumes the given message buffer and processes the + /// contained message, if any, to completion, possibly resulting in a + /// response being passed to [`Self::process_call_result`]. + /// + /// The request message is a given as a seqeuence of bytes in `buf` + /// originating from client address `addr`. + /// + /// The [`MiddlewareChain`] and [`Service`] to be used to process the + /// message are supplied in the `middleware_chain` and `svc` arguments + /// respectively. + /// + /// Any server specific state to be used and/or updated as part of the + /// processing should be supplied via the `state` argument whose type is + /// defined by the implementing type. + /// + /// On error the result will be a [`ServiceError`]. + #[allow(clippy::too_many_arguments)] + fn process_request( + &self, + buf: Buf::Output, + received_at: Instant, + addr: SocketAddr, + middleware_chain: MiddlewareChain, + svc: &Svc, + metrics: Arc, + meta: Self::Meta, + ) -> Result<(), ServiceError> + where + Svc: 'static, + Svc::Target: Send + Composer + Default, + Svc::Future: Send, + Buf::Output: 'static, + { + boomerang( + self, + buf, + received_at, + addr, + middleware_chain, + metrics, + svc, + meta, + ) + } + + /// Add context to a request. + /// + /// The server supplies this function to annotate the received message + /// with additional information about its origins. + fn add_context_to_request( + &self, + request: Message, + received_at: Instant, + addr: SocketAddr, + ) -> Request; + + /// Finalize a response. + /// + /// The server supplies this function to handle the response as + /// appropriate for the server, e.g. to write the response back to the + /// originating client. + /// + /// The response is the form of a [`CallResult`]. + fn process_call_result( + request: &Request, + call_result: CallResult, + state: Self::Meta, + metrics: Arc, + ); +} + +/// Propogate a message through the [`MiddlewareChain`] to the [`Service`] and +/// flow the response in reverse back down the same path, a bit like throwing +/// a boomerang. +#[allow(clippy::too_many_arguments)] +fn boomerang( + server: &Server, + buf: ::Output, + received_at: Instant, + addr: SocketAddr, + middleware_chain: MiddlewareChain< + ::Output, + ::Output>>::Target, + >, + metrics: Arc, + svc: &Svc, + meta: Server::Meta, +) -> Result<(), ServiceError> +where + Buf: BufSource, + Buf::Output: Octets + Send + Sync + 'static, + Svc: Service + Send + Sync + 'static, + Svc::Future: Send, + Svc::Target: Send + Composer + Default, + Server: CommonMessageFlow + ?Sized, +{ + let message = Message::from_octets(buf).map_err(|err| { + warn!("Failed while parsing request message: {err}"); + ServiceError::InternalError + })?; + + let request = server.add_context_to_request(message, received_at, addr); + + let preprocessing_result = do_middleware_preprocessing::( + &request, + &middleware_chain, + &metrics, + )?; + + let (txn, aborted_preprocessor_idx) = + do_service_call::(preprocessing_result, &request, svc); + + do_middleware_postprocessing::( + request, + meta, + middleware_chain, + txn, + aborted_preprocessor_idx, + metrics, + ); + + Ok(()) +} + +/// Pass a pre-processed request to the [`Service`] to handle. +/// +/// If [`Service::call`] returns an error this function will produce a DNS +/// ServFail error response. If the returned error is +/// [`ServiceError::InternalError`] it will also be logged. +#[allow(clippy::type_complexity)] +fn do_service_call( + preprocessing_result: ControlFlow<( + Transaction, + usize, + )>, + request: &Request<::Output>, + svc: &Svc, +) -> (Transaction, Option) +where + Buf: BufSource, + Buf::Output: Octets, + Svc: Service, + Svc::Target: Composer + Default, +{ + match preprocessing_result { + ControlFlow::Continue(()) => { + let res = if enabled!(Level::INFO) { + let span = info_span!("svc-call", + msg_id = request.message().header().id(), + client = %request.client_addr(), + ); + let _guard = span.enter(); + svc.call(request.clone()) + } else { + svc.call(request.clone()) + }; + + // Handle any error returned by the service. + let txn = res.unwrap_or_else(|err| { + if matches!(err, ServiceError::InternalError) { + error!("Service error while processing request: {err}"); + } + + let mut response = start_reply(request); + response.header_mut().set_rcode(err.rcode()); + let call_result = CallResult::new(response.additional()); + Transaction::immediate(Ok(call_result)) + }); + + // Pass the transaction out for post-processing. + (txn, None) + } + + ControlFlow::Break((txn, aborted_preprocessor_idx)) => { + (txn, Some(aborted_preprocessor_idx)) + } + } +} + +/// Pre-process a request. +/// +/// Pre-processing involves parsing a [`Message`] from the byte buffer and +/// pre-processing it via any supplied [`MiddlewareChain`]. +/// +/// On success the result is an immutable request message and a +/// [`ControlFlow`] decision about whether to continue with further processing +/// or to break early with a possible response. If processing failed the +/// result will be a [`ServiceError`]. +/// +/// On break the result will be one ([`Transaction::single`]) or more +/// ([`Transaction::stream`]) to post-process. +#[allow(clippy::type_complexity)] +fn do_middleware_preprocessing( + request: &Request, + middleware_chain: &MiddlewareChain, + metrics: &Arc, +) -> Result< + ControlFlow<(Transaction, usize)>, + ServiceError, +> +where + Buf: BufSource, + Buf::Output: Octets + Send + Sync + 'static, + Svc: Service + Send + Sync, + Svc::Future: Send, + Svc::Target: Send + Composer + Default + 'static, +{ + let span = info_span!("pre-process", + msg_id = request.message().header().id(), + client = %request.client_addr(), + ); + let _guard = span.enter(); + + metrics.inc_num_inflight_requests(); + + let pp_res = middleware_chain.preprocess(request); + + Ok(pp_res) +} + +/// Post-process a response in the context of its originating request. +/// +/// Each response is post-processed in its own Tokio task. Note that there is +/// no guarantee about the order in which responses will be post-processed. If +/// the order of a seqence of responses is important it should be provided as +/// a [`Transaction::stream`] rather than [`Transaction::single`]. +/// +/// Responses are first post-processed by the [`MiddlewareChain`] provided, if +/// any, then passed to [`Self::process_call_result`] for final processing. +fn do_middleware_postprocessing( + request: Request, + meta: Server::Meta, + middleware_chain: MiddlewareChain, + mut response_txn: Transaction, + last_processor_id: Option, + metrics: Arc, +) where + Buf: BufSource, + Buf::Output: Octets + Send + Sync + 'static, + Svc: Service + Send + Sync + 'static, + Svc::Future: Send, + Svc::Target: Send + Composer + Default, + Server: CommonMessageFlow + ?Sized, +{ + tokio::spawn(async move { + let span = info_span!("post-process", + msg_id = request.message().header().id(), + client = %request.client_addr(), + ); + let _guard = span.enter(); + + while let Some(Ok(mut call_result)) = response_txn.next().await { + if let Some(response) = call_result.get_response_mut() { + middleware_chain.postprocess( + &request, + response, + last_processor_id, + ); + } + + Server::process_call_result( + &request, + call_result, + meta.clone(), + metrics.clone(), + ); + } + + metrics.dec_num_inflight_requests(); + }); +} + +/// Return whether the DO flag is set. This should move to Message. +fn dnssec_ok(msg: &Message) -> bool { + if let Some(opt) = msg.opt() { + opt.dnssec_ok() + } else { + false + } +} diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index ab7a3b7dc..c1fcbd6f4 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -3,7 +3,7 @@ doc = " The `unstable-server-transport` feature is necessary to enable this module." )] // #![warn(missing_docs)] -// #![warn(clippy::missing_docs_in_private_items)] +#![warn(clippy::missing_docs_in_private_items)] //! Receiving requests and sending responses. //! //! This module provides skeleton asynchronous server implementations based on @@ -180,14 +180,17 @@ mod connection; pub use connection::Config as ConnectionConfig; +pub mod adapter; pub mod buf; pub mod dgram; pub mod error; pub mod message; pub mod metrics; pub mod middleware; +pub mod query_router; pub mod service; pub mod sock; +pub mod sr_service; pub mod stream; pub mod util; diff --git a/src/net/server/query_router.rs b/src/net/server/query_router.rs new file mode 100644 index 000000000..0c4d5b950 --- /dev/null +++ b/src/net/server/query_router.rs @@ -0,0 +1,78 @@ +// Query Router + +use super::message::RequestNG; +use super::sr_service::SrService; +use crate::base::Name; +use crate::base::ToName; +use crate::dep::octseq::EmptyBuilder; +use crate::dep::octseq::FromBuilder; +use crate::dep::octseq::Octets; +use std::boxed::Box; +use std::future::Future; +use std::pin::Pin; +use std::vec::Vec; + +pub struct QueryRouter { + list: Vec>, +} + +struct Element { + name: Name, + service: + Box> + Send + Sync>, +} + +impl QueryRouter { + pub fn new() -> Self { + Self { list: Vec::new() } + } + + pub fn add(&mut self, name: TN, service: SVC) + where + Octs: FromBuilder, + ::Builder: EmptyBuilder, + TN: ToName, + SVC: SrService> + + Send + + Sync + + 'static, + { + let el = Element { + name: name.try_to_name().ok().unwrap(), + service: Box::new(service), + }; + self.list.push(el); + } +} + +impl SrService + for QueryRouter +where + Octs: AsRef<[u8]>, +{ + type Target = (); + + fn call( + &self, + request: RequestNG, + ) -> Pin> + Send + Sync>> + where + RequestOcts: AsRef<[u8]> + Octets, + { + let question = request + .message() + .question() + .into_iter() + .next() + .unwrap() + .unwrap(); + let name = question.qname(); + self.list + .iter() + .filter(|l| name.ends_with(&l.name)) + .max_by_key(|l| l.name.label_count()) + .unwrap() + .service + .call(request.clone()) + } +} diff --git a/src/net/server/sr_service.rs b/src/net/server/sr_service.rs new file mode 100644 index 000000000..b280c7a54 --- /dev/null +++ b/src/net/server/sr_service.rs @@ -0,0 +1,178 @@ +// Single reply service + +use super::message::RequestNG; +use crate::base::message_builder::AdditionalBuilder; +use crate::base::opt::AllOptData; +use crate::base::opt::ComposeOptData; +use crate::base::opt::LongOptData; +use crate::base::opt::OptRecord; +use crate::base::Message; +use crate::base::MessageBuilder; +use crate::base::ParsedName; +use crate::base::Rtype; +use crate::base::StreamTarget; +use crate::dep::octseq::Octets; +use crate::rdata::AllRecordData; +use std::boxed::Box; +use std::future::Future; +use std::pin::Pin; +use std::vec::Vec; + +pub trait SrService { + type Target; + + fn call( + &self, + request: RequestNG, + ) -> Pin> + Send + Sync>> + where + RequestOcts: AsRef<[u8]> + Octets; +} + +pub trait ComposeReply { + fn from_message(msg: &Message) -> Self + where + Octs: AsRef<[u8]>; + fn additional_builder_stream_target( + &self, + ) -> AdditionalBuilder>>; +} + +pub struct ReplyMessage { + msg: Message>, + + /// The OPT record to add if required. + opt: Option>>, +} + +impl ReplyMessage { + fn add_opt( + &mut self, + opt: &impl ComposeOptData, + ) -> Result<(), LongOptData> { + self.opt_mut().push(opt).map_err(|e| e.unlimited_buf()) + } + + /// Returns a mutable reference to the OPT record. + /// + /// Adds one if necessary. + fn opt_mut(&mut self) -> &mut OptRecord> { + self.opt.get_or_insert_with(Default::default) + } +} + +impl ComposeReply for ReplyMessage { + fn from_message(msg: &Message) -> Self + where + Octs: AsRef<[u8]>, + { + let vec = msg.as_slice().to_vec(); + let msg = Message::from_octets(vec).unwrap(); + let mut repl = Self { msg, opt: None }; + + // As an example, copy any ECS option from the message. + // though this should be done in a separate ECS plugin. + let msg = repl.msg.clone(); + if let Some(optrec) = msg.opt() { + // Copy opt header. + let opt = repl.opt_mut(); + opt.set_udp_payload_size(optrec.udp_payload_size()); + //opt.set_version(optrec.version()); + opt.set_dnssec_ok(optrec.dnssec_ok()); + + for opt in optrec.opt().iter::>() { + let opt = opt.unwrap(); + if let AllOptData::ClientSubnet(_ecs) = opt { + repl.add_opt(&opt).unwrap(); + } + if let AllOptData::ExtendedError(ref _ede) = opt { + repl.add_opt(&opt).unwrap(); + } + } + } + repl + } + + fn additional_builder_stream_target( + &self, + ) -> AdditionalBuilder>> { + let source = &self.msg; + + let mut target = MessageBuilder::from_target( + StreamTarget::>::new(Default::default()).unwrap(), + ) + .unwrap(); + + let header = source.header(); + *target.header_mut() = header; + + let source = source.question(); + let mut target = target.additional().builder().question(); + for rr in source { + let rr = rr.unwrap(); + target.push(rr).unwrap(); + } + let mut source = source.answer().unwrap(); + let mut target = target.answer(); + for rr in &mut source { + let rr = rr.unwrap(); + let rr = rr + .into_record::>>() + .unwrap() + .unwrap(); + target.push(rr).unwrap(); + } + + let mut source = source.next_section().unwrap().unwrap(); + let mut target = target.authority(); + for rr in &mut source { + let rr = rr.unwrap(); + let rr = rr + .into_record::>>() + .unwrap() + .unwrap(); + target.push(rr).unwrap(); + } + + let source = source.next_section().unwrap().unwrap(); + let mut target = target.additional(); + for rr in source { + let rr = rr.unwrap(); + if rr.rtype() == Rtype::OPT { + /* + let rr = rr.into_record::>().unwrap().unwrap(); + let opt_record = OptRecord::from_record(rr); + target + .opt(|newopt| { + newopt + .set_udp_payload_size(opt_record.udp_payload_size()); + newopt.set_version(opt_record.version()); + newopt.set_dnssec_ok(opt_record.dnssec_ok()); + + // Copy the transitive options that we support. + for option in opt_record.opt().iter::>() + { + let option = option.unwrap(); + if let AllOptData::ExtendedError(_) = option { + newopt.push(&option).unwrap(); + } + } + Ok(()) + }) + .unwrap(); + */ + } else { + let rr = rr + .into_record::>>() + .unwrap() + .unwrap(); + target.push(rr).unwrap(); + } + } + if let Some(opt) = self.opt.as_ref() { + target.push(opt.as_record()).unwrap(); + } + + target + } +} From a62225c71c1e218f26d29de1424bec8faa86fd32 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 12 Jul 2024 16:58:50 +0200 Subject: [PATCH 02/38] Clippy --- src/net/server/mod.rs | 2 +- src/net/server/query_router.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index c1fcbd6f4..a05d90956 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -3,7 +3,7 @@ doc = " The `unstable-server-transport` feature is necessary to enable this module." )] // #![warn(missing_docs)] -#![warn(clippy::missing_docs_in_private_items)] +// #![warn(clippy::missing_docs_in_private_items)] //! Receiving requests and sending responses. //! //! This module provides skeleton asynchronous server implementations based on diff --git a/src/net/server/query_router.rs b/src/net/server/query_router.rs index 0c4d5b950..355ed10c3 100644 --- a/src/net/server/query_router.rs +++ b/src/net/server/query_router.rs @@ -45,6 +45,12 @@ impl QueryRouter { } } +impl Default for QueryRouter { + fn default() -> Self { + Self::new() + } +} + impl SrService for QueryRouter where From 9772ae0c8a0ab5e0b510df754a55dc5d05e26f2f Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 12 Jul 2024 17:07:40 +0200 Subject: [PATCH 03/38] Features for query-routing. --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a11e318a6..68ad202e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,10 @@ required-features = ["zonefile", "unstable-zonetree"] name = "serve-zone" required-features = ["zonefile", "net", "unstable-server-transport", "unstable-zonetree"] +[[example]] +name = "query-routing" +required-features = ["net", "unstable-client-transport", "unstable-server-transport"] + # This example is commented out because it is difficult, if not impossible, # when including the sqlx dependency, to make the dependency tree compatible # with both `cargo +nightly update -Z minimal versions` and the crate minimum From af1ee534eed4ed85e00f51086e4a8cdf0e423ac3 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 12 Jul 2024 17:15:32 +0200 Subject: [PATCH 04/38] Remove MyService --- examples/query-routing.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index f968f0a9c..8c9b8ac78 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -44,7 +44,7 @@ use domain::net::server::middleware::processors::cookies::CookiesMiddlewareProce use domain::net::server::middleware::processors::mandatory::MandatoryMiddlewareProcessor; use domain::net::server::query_router::QueryRouter; use domain::net::server::service::{ - CallResult, Service, ServiceError, ServiceFeedback, Transaction, + CallResult, ServiceError, ServiceFeedback, Transaction, }; use domain::net::server::sock::AsyncAccept; use domain::net::server::sr_service::ReplyMessage; @@ -78,30 +78,6 @@ where //----------- Example Service trait implementations -------------------------- -//--- MyService - -struct MyService; - -/// This example shows how to implement the [`Service`] trait directly. -/// -/// See [`query`] and [`name_to_ip`] for ways of implementing the [`Service`] -/// trait for a function instead of a struct. -impl Service> for MyService { - type Target = Vec; - type Future = Ready, ServiceError>>; - - fn call( - &self, - request: Request>, - ) -> Result, ServiceError> { - let builder = mk_builder_for_target(); - let additional = mk_answer(&request, builder)?; - let item = ready(Ok(CallResult::new(additional))); - let txn = Transaction::single(item); - Ok(txn) - } -} - //--- name_to_ip() /// This function shows how to implement [`Service`] logic by matching the From 0e6d67d284cd02a4d81d6003ae4a3b4396811a95 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 12 Jul 2024 17:28:16 +0200 Subject: [PATCH 05/38] Duplicate mock_instant --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 68ad202e9..f2a9a3edb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,6 @@ unstable-zonetree = ["futures", "parking_lot", "serde", "tokio", "tracing"] [dev-dependencies] lazy_static = { version = "1.4.0" } -mock_instant = { version = "0.4.0" } rstest = "0.19.0" rustls-pemfile = { version = "2.1.2" } serde_test = "1.0.130" From 27689d29586b1504c5c8bbf964e9959d8158f2ed Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 22 Jul 2024 12:34:02 +0200 Subject: [PATCH 06/38] QnameRouter instead of QueryRouter. --- examples/query-routing.rs | 6 +++--- src/net/server/mod.rs | 2 +- src/net/server/{query_router.rs => qname_router.rs} | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/net/server/{query_router.rs => qname_router.rs} (88%) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 8c9b8ac78..f723d678c 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -42,7 +42,7 @@ use domain::net::server::middleware::processor::MiddlewareProcessor; #[cfg(feature = "siphasher")] use domain::net::server::middleware::processors::cookies::CookiesMiddlewareProcessor; use domain::net::server::middleware::processors::mandatory::MandatoryMiddlewareProcessor; -use domain::net::server::query_router::QueryRouter; +use domain::net::server::qname_router::QnameRouter; use domain::net::server::service::{ CallResult, ServiceError, ServiceFeedback, Transaction, }; @@ -460,8 +460,8 @@ async fn main() { */ // Start building the query router plus upstreams. - let mut qr: QueryRouter, Vec, ReplyMessage> = - QueryRouter::new(); + let mut qr: QnameRouter, Vec, ReplyMessage> = + QnameRouter::new(); // Queries to the root go to 1.1.1.1 let server_addr = diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index a05d90956..5004299c7 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -187,7 +187,7 @@ pub mod error; pub mod message; pub mod metrics; pub mod middleware; -pub mod query_router; +pub mod qname_router; pub mod service; pub mod sock; pub mod sr_service; diff --git a/src/net/server/query_router.rs b/src/net/server/qname_router.rs similarity index 88% rename from src/net/server/query_router.rs rename to src/net/server/qname_router.rs index 355ed10c3..1ee0f4d83 100644 --- a/src/net/server/query_router.rs +++ b/src/net/server/qname_router.rs @@ -1,4 +1,4 @@ -// Query Router +// Qname Router use super::message::RequestNG; use super::sr_service::SrService; @@ -12,7 +12,7 @@ use std::future::Future; use std::pin::Pin; use std::vec::Vec; -pub struct QueryRouter { +pub struct QnameRouter { list: Vec>, } @@ -22,7 +22,7 @@ struct Element { Box> + Send + Sync>, } -impl QueryRouter { +impl QnameRouter { pub fn new() -> Self { Self { list: Vec::new() } } @@ -45,14 +45,14 @@ impl QueryRouter { } } -impl Default for QueryRouter { +impl Default for QnameRouter { fn default() -> Self { Self::new() } } impl SrService - for QueryRouter + for QnameRouter where Octs: AsRef<[u8]>, { From 7243cde58460493d7192b50c650a14fa87cd89b3 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 22 Jul 2024 15:40:48 +0200 Subject: [PATCH 07/38] SrService becomes SingleService --- examples/query-routing.rs | 2 +- src/net/server/adapter.rs | 6 +++--- src/net/server/mod.rs | 2 +- src/net/server/qname_router.rs | 8 ++++---- src/net/server/{sr_service.rs => single_service.rs} | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename src/net/server/{sr_service.rs => single_service.rs} (99%) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index f723d678c..1cfbdf2ac 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -47,7 +47,7 @@ use domain::net::server::service::{ CallResult, ServiceError, ServiceFeedback, Transaction, }; use domain::net::server::sock::AsyncAccept; -use domain::net::server::sr_service::ReplyMessage; +use domain::net::server::single_service::ReplyMessage; use domain::net::server::stream; use domain::net::server::stream::StreamServer; use domain::net::server::util::{mk_builder_for_target, service_fn}; diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 2d0926d03..c66d1b4f9 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -2,7 +2,7 @@ use super::message::{Request, RequestNG}; use super::service::{CallResult, Service, ServiceError, Transaction}; -use super::sr_service::{ComposeReply, SrService}; +use super::single_service::{ComposeReply, SingleService}; use std::boxed::Box; use std::fmt::Debug; use std::future::Future; @@ -30,7 +30,7 @@ impl SrServiceToService { impl Service for SrServiceToService where - SVC: SrService, CR>, + SVC: SingleService, CR>, CR: ComposeReply + 'static, { type Target = Vec; @@ -81,7 +81,7 @@ where } } -impl SrService +impl SingleService for ClientTransportToSrService where RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index 5004299c7..eb05ab8a0 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -190,7 +190,7 @@ pub mod middleware; pub mod qname_router; pub mod service; pub mod sock; -pub mod sr_service; +pub mod single_service; pub mod stream; pub mod util; diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 1ee0f4d83..1b7aa24c5 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -1,7 +1,7 @@ // Qname Router use super::message::RequestNG; -use super::sr_service::SrService; +use super::single_service::SingleService; use crate::base::Name; use crate::base::ToName; use crate::dep::octseq::EmptyBuilder; @@ -19,7 +19,7 @@ pub struct QnameRouter { struct Element { name: Name, service: - Box> + Send + Sync>, + Box> + Send + Sync>, } impl QnameRouter { @@ -32,7 +32,7 @@ impl QnameRouter { Octs: FromBuilder, ::Builder: EmptyBuilder, TN: ToName, - SVC: SrService> + SVC: SingleService> + Send + Sync + 'static, @@ -51,7 +51,7 @@ impl Default for QnameRouter { } } -impl SrService +impl SingleService for QnameRouter where Octs: AsRef<[u8]>, diff --git a/src/net/server/sr_service.rs b/src/net/server/single_service.rs similarity index 99% rename from src/net/server/sr_service.rs rename to src/net/server/single_service.rs index b280c7a54..dfd8ac2c3 100644 --- a/src/net/server/sr_service.rs +++ b/src/net/server/single_service.rs @@ -18,7 +18,7 @@ use std::future::Future; use std::pin::Pin; use std::vec::Vec; -pub trait SrService { +pub trait SingleService { type Target; fn call( From 65095d39de827580e6c4d13a63086793f771e8c7 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 23 Jul 2024 15:35:59 +0200 Subject: [PATCH 08/38] Cargo fmt --- examples/query-routing.rs | 2 +- src/net/server/mod.rs | 2 +- src/net/server/qname_router.rs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 1cfbdf2ac..e8d02cef2 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -46,8 +46,8 @@ use domain::net::server::qname_router::QnameRouter; use domain::net::server::service::{ CallResult, ServiceError, ServiceFeedback, Transaction, }; -use domain::net::server::sock::AsyncAccept; use domain::net::server::single_service::ReplyMessage; +use domain::net::server::sock::AsyncAccept; use domain::net::server::stream; use domain::net::server::stream::StreamServer; use domain::net::server::util::{mk_builder_for_target, service_fn}; diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index eb05ab8a0..24c3f5387 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -189,8 +189,8 @@ pub mod metrics; pub mod middleware; pub mod qname_router; pub mod service; -pub mod sock; pub mod single_service; +pub mod sock; pub mod stream; pub mod util; diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 1b7aa24c5..b45852d69 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -18,8 +18,9 @@ pub struct QnameRouter { struct Element { name: Name, - service: - Box> + Send + Sync>, + service: Box< + dyn SingleService> + Send + Sync, + >, } impl QnameRouter { From 67fe2f574499ffa9f75bb5b0dfde1918b5ea0343 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 26 Jul 2024 15:50:59 +0200 Subject: [PATCH 09/38] Changes for service-layering --- Cargo.toml | 2 +- examples/query-routing.rs | 657 +++++++++++++++++++++++--------------- src/net/server/adapter.rs | 26 +- src/net/server/message.rs | 336 +------------------ 4 files changed, 419 insertions(+), 602 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2a9a3edb..d54c08ab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,7 @@ required-features = ["zonefile", "net", "unstable-server-transport", "unstable-z [[example]] name = "query-routing" -required-features = ["net", "unstable-client-transport", "unstable-server-transport"] +required-features = ["net", "unstable-client-transport", "unstable-server-transport","futures", "tracing-subscriber"] # This example is commented out because it is difficult, if not impossible, # when including the sqlx dependency, to make the dependency tree compatible diff --git a/examples/query-routing.rs b/examples/query-routing.rs index e8d02cef2..affdb7977 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -1,58 +1,57 @@ -use core::future::ready; - use core::fmt; -use core::fmt::Debug; -use core::future::{Future, Ready}; -use core::ops::ControlFlow; +use core::future::{ready, Future, Ready}; use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use core::task::{Context, Poll}; use core::time::Duration; + use std::fs::File; use std::io; use std::io::BufReader; -use std::net::IpAddr; use std::net::SocketAddr; -use std::str::FromStr; +use std::pin::Pin; use std::sync::Arc; use std::sync::RwLock; +use futures::channel::mpsc::unbounded; +use futures::stream::{once, Empty, Once, Stream}; use octseq::{FreezeBuilder, Octets}; use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; use tokio::time::Instant; use tokio_rustls::rustls; use tokio_rustls::TlsAcceptor; use tokio_tfo::{TfoListener, TfoStream}; -//use tracing_subscriber::EnvFilter; +use tracing_subscriber::EnvFilter; use domain::base::iana::{Class, Rcode}; use domain::base::message_builder::{AdditionalBuilder, PushError}; use domain::base::name::ToLabelIter; use domain::base::wire::Composer; -use domain::base::{MessageBuilder, Name, StreamTarget}; +use domain::base::{MessageBuilder, Name, Rtype, Serial, StreamTarget, Ttl}; use domain::net::client::dgram as client_dgram; use domain::net::client::protocol::UdpConnect; use domain::net::server::adapter::ClientTransportToSrService; -use domain::net::server::adapter::SrServiceToService; +use domain::net::server::adapter::SingleServiceToService; use domain::net::server::buf::VecBufSource; -use domain::net::server::dgram; use domain::net::server::dgram::DgramServer; use domain::net::server::message::Request; -use domain::net::server::middleware::builder::MiddlewareBuilder; -use domain::net::server::middleware::processor::MiddlewareProcessor; -#[cfg(feature = "siphasher")] -use domain::net::server::middleware::processors::cookies::CookiesMiddlewareProcessor; -use domain::net::server::middleware::processors::mandatory::MandatoryMiddlewareProcessor; +use domain::net::server::middleware::cookies::CookiesMiddlewareSvc; +use domain::net::server::middleware::edns::EdnsMiddlewareSvc; +use domain::net::server::middleware::mandatory::MandatoryMiddlewareSvc; +use domain::net::server::middleware::stream::{ + MiddlewareStream, PostprocessingStream, +}; use domain::net::server::qname_router::QnameRouter; use domain::net::server::service::{ - CallResult, ServiceError, ServiceFeedback, Transaction, + CallResult, Service, ServiceFeedback, ServiceResult, }; use domain::net::server::single_service::ReplyMessage; use domain::net::server::sock::AsyncAccept; -use domain::net::server::stream; use domain::net::server::stream::StreamServer; -use domain::net::server::util::{mk_builder_for_target, service_fn}; -use domain::net::server::ConnectionConfig; -use domain::rdata::A; +use domain::net::server::util::{mk_builder_for_target, /*service_fn*/}; +use domain::rdata::{Soa, A}; +use std::vec::Vec; +use std::net::IpAddr; +use std::str::FromStr; //----------- mk_answer() ---------------------------------------------------- @@ -76,8 +75,139 @@ where Ok(answer.additional()) } +fn mk_soa_answer( + msg: &Request>, + builder: MessageBuilder>, +) -> Result>, PushError> +where + Target: Octets + Composer + FreezeBuilder, + ::AppendError: fmt::Debug, +{ + let mname: Name> = "a.root-servers.net".parse().unwrap(); + let rname = "nstld.verisign-grs.com".parse().unwrap(); + let mut answer = + builder.start_answer(msg.message(), Rcode::NOERROR).unwrap(); + answer.push(( + Name::root_slice(), + 86390, + Soa::new( + mname, + rname, + Serial(2020081701), + Ttl::from_secs(1800), + Ttl::from_secs(900), + Ttl::from_secs(604800), + Ttl::from_secs(86400), + ), + ))?; + Ok(answer.additional()) +} + //----------- Example Service trait implementations -------------------------- +//--- MySingleResultService + +struct MySingleResultService; + +/// This example shows how to implement the [`Service`] trait directly. +/// +/// By implementing the trait directly you can do async calls with .await by +/// returning an async block, and can control the type of stream used and how +/// and when it gets populated. Neither are possible if implementing a service +/// via a simple compatible function signature or via service_fn, examples of +/// which can be seen below. +/// +/// For readability this example uses nonsensical future and stream types, +/// nonsensical because the future doesn't do any waiting and the stream +/// doesn't do any streaming. See the example below for a more complex case. +/// +/// See [`query`] and [`name_to_ip`] for ways of implementing the [`Service`] +/// trait for a function instead of a struct. +impl Service> for MySingleResultService { + type Target = Vec; + type Stream = Once>>; + type Future = Ready; + + fn call(&self, request: Request>) -> Self::Future { + let builder = mk_builder_for_target(); + let additional = mk_answer(&request, builder).unwrap(); + let item = Ok(CallResult::new(additional)); + ready(once(ready(item))) + } +} + +//--- MyAsyncStreamingService + +struct MyAsyncStreamingService; + +/// This example also shows how to implement the [`Service`] trait directly. +/// +/// It implements a very simplistic dummy AXFR responder which can be tested +/// using `dig AXFR `. +/// +/// Unlike the simpler example above which returns a fixed type of future and +/// stream which are neither waiting nor streaming, this example goes to the +/// other extreme of returning future and stream types which are determined at +/// runtime (and thus involve Box'ing). +/// +/// There is a middle ground not shown here whereby you return concrete Future +/// and/or Stream implementations that actually wait and/or stream, e.g. +/// making the Stream type be UnboundedReceiver instead of Pin>. +impl Service> for MyAsyncStreamingService { + type Target = Vec; + type Stream = + Pin> + Send>>; + type Future = Pin + Send>>; + + fn call(&self, request: Request>) -> Self::Future { + Box::pin(async move { + if !matches!( + request + .message() + .sole_question() + .map(|q| q.qtype() == Rtype::AXFR), + Ok(true) + ) { + let builder = mk_builder_for_target(); + let additional = builder + .start_answer(request.message(), Rcode::NOTIMP) + .unwrap() + .additional(); + let item = Ok(CallResult::new(additional)); + let immediate_result = once(ready(item)); + return Box::pin(immediate_result) as Self::Stream; + } + + let (sender, receiver) = unbounded(); + let cloned_sender = sender.clone(); + + tokio::spawn(async move { + // Dummy AXFR response: SOA, record, SOA + tokio::time::sleep(Duration::from_millis(100)).await; + let builder = mk_builder_for_target(); + let additional = mk_soa_answer(&request, builder).unwrap(); + let item = Ok(CallResult::new(additional)); + cloned_sender.unbounded_send(item).unwrap(); + + tokio::time::sleep(Duration::from_millis(100)).await; + let builder = mk_builder_for_target(); + let additional = mk_answer(&request, builder).unwrap(); + let item = Ok(CallResult::new(additional)); + cloned_sender.unbounded_send(item).unwrap(); + + tokio::time::sleep(Duration::from_millis(100)).await; + let builder = mk_builder_for_target(); + let additional = mk_soa_answer(&request, builder).unwrap(); + let item = Ok(CallResult::new(additional)); + cloned_sender.unbounded_send(item).unwrap(); + }); + + Box::pin(receiver) as Self::Stream + }) + } +} + //--- name_to_ip() /// This function shows how to implement [`Service`] logic by matching the @@ -86,20 +216,7 @@ where /// The function signature is slightly more complex than when using /// [`service_fn`] (see the [`query`] example below). #[allow(clippy::type_complexity)] -fn name_to_ip( - request: Request>, -) -> Result< - Transaction< - Target, - impl Future, ServiceError>> + Send, - >, - ServiceError, -> -where - Target: - Composer + Octets + FreezeBuilder + Default + Send, - ::AppendError: Debug, -{ +fn name_to_ip(request: Request>) -> ServiceResult> { let mut out_answer = None; if let Ok(question) = request.message().sole_question() { let qname = question.qname(); @@ -135,8 +252,7 @@ where } let additional = out_answer.unwrap().additional(); - let item = Ok(CallResult::new(additional)); - Ok(Transaction::single(ready(item))) + Ok(CallResult::new(additional)) } //--- query() @@ -147,45 +263,28 @@ where /// The function signature is slightly simpler to write than when not using /// [`service_fn`] and supports passing in meta data without any extra /// boilerplate. -#[allow(clippy::type_complexity)] fn query( request: Request>, count: Arc, -) -> Result< - Transaction< - Vec, - impl Future>, ServiceError>> + Send, - >, - ServiceError, -> { +) -> ServiceResult> { let cnt = count .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| { Some(if x > 0 { x - 1 } else { 0 }) }) .unwrap(); - // This fn blocks the server until it returns. By returning a future that - // handles the request we allow the server to execute the future in the - // background without blocking the server. - let fut = async move { - eprintln!("Sleeping for 100ms"); - tokio::time::sleep(Duration::from_millis(100)).await; + // Note: A real service would have application logic here to process + // the request and generate an response. - // Note: A real service would have application logic here to process - // the request and generate an response. - - let idle_timeout = Duration::from_millis((50 * cnt).into()); - let cmd = ServiceFeedback::Reconfigure { - idle_timeout: Some(idle_timeout), - }; - eprintln!("Setting idle timeout to {idle_timeout:?}"); - - let builder = mk_builder_for_target(); - let answer = mk_answer(&request, builder)?; - let res = CallResult::new(answer).with_feedback(cmd); - Ok(res) + let idle_timeout = Duration::from_millis((50 * cnt).into()); + let cmd = ServiceFeedback::Reconfigure { + idle_timeout: Some(idle_timeout), }; - Ok(Transaction::single(fut)) + eprintln!("Setting idle timeout to {idle_timeout:?}"); + + let builder = mk_builder_for_target(); + let answer = mk_answer(&request, builder)?; + Ok(CallResult::new(answer).with_feedback(cmd)) } //----------- Example socket trait implementations --------------------------- @@ -337,9 +436,9 @@ impl AsyncAccept for RustlsTcpListener { //----------- CustomMiddleware ----------------------------------------------- #[derive(Default)] -struct Stats { - slowest_req: Duration, - fastest_req: Duration, +pub struct Stats { + slowest_req: Option, + fastest_req: Option, num_req_bytes: u32, num_resp_bytes: u32, num_reqs: u32, @@ -348,59 +447,41 @@ struct Stats { num_udp: u32, } -#[derive(Default)] -pub struct StatsMiddlewareProcessor { - stats: RwLock, -} - -impl StatsMiddlewareProcessor { - /// Creates an instance of this processor. - #[must_use] - pub fn new() -> Self { - Default::default() +impl std::fmt::Display for Stats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "# Reqs={} [UDP={}, IPv4={}, IPv6={}] Bytes [rx={}, tx={}] Speed [fastest={}, slowest={}]", + self.num_reqs, + self.num_udp, + self.num_ipv4, + self.num_ipv6, + self.num_req_bytes, + self.num_resp_bytes, + self.fastest_req.map(|v| format!("{}μs", v.as_micros())).unwrap_or_else(|| "-".to_string()), + self.slowest_req.map(|v| format!("{}ms", v.as_millis())).unwrap_or_else(|| "-".to_string()), + ) } } -impl std::fmt::Display for StatsMiddlewareProcessor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let stats = self.stats.read().unwrap(); - write!(f, "# Reqs={} [UDP={}, IPv4={}, IPv6={}] Bytes [rx={}, tx={}] Speed [fastest={}μs, slowest={}μs]", - stats.num_reqs, - stats.num_udp, - stats.num_ipv4, - stats.num_ipv6, - stats.num_req_bytes, - stats.num_resp_bytes, - stats.fastest_req.as_micros(), - stats.slowest_req.as_micros())?; - Ok(()) - } +pub struct StatsMiddlewareSvc { + svc: Svc, + stats: Arc>, } -impl MiddlewareProcessor - for StatsMiddlewareProcessor -where - RequestOctets: AsRef<[u8]> + Octets, - Target: Composer + Default, -{ - fn preprocess( - &self, - _request: &Request, - ) -> ControlFlow>> { - ControlFlow::Continue(()) +impl StatsMiddlewareSvc { + /// Creates an instance of this processor. + #[must_use] + pub fn new(svc: Svc, stats: Arc>) -> Self { + Self { svc, stats } } - fn postprocess( - &self, - request: &Request, - _response: &mut AdditionalBuilder>, - ) { - let duration = Instant::now().duration_since(request.received_at()); + fn preprocess(&self, request: &Request) + where + RequestOctets: Octets + Send + Sync + Unpin, + { let mut stats = self.stats.write().unwrap(); stats.num_reqs += 1; stats.num_req_bytes += request.message().as_slice().len() as u32; - stats.num_resp_bytes += _response.as_slice().len() as u32; if request.transport_ctx().is_udp() { stats.num_udp += 1; @@ -411,14 +492,101 @@ where } else { stats.num_ipv6 += 1; } + } - if duration < stats.fastest_req { - stats.fastest_req = duration; + fn postprocess( + request: &Request, + response: &AdditionalBuilder>, + stats: Arc>, + ) where + RequestOctets: Octets + Send + Sync + Unpin, + Svc: Service, + Svc::Target: AsRef<[u8]>, + { + let duration = Instant::now().duration_since(request.received_at()); + let mut stats = stats.write().unwrap(); + + stats.num_resp_bytes += response.as_slice().len() as u32; + + if duration < stats.fastest_req.unwrap_or(Duration::MAX) { + stats.fastest_req = Some(duration); } - if duration > stats.slowest_req { - stats.slowest_req = duration; + if duration > stats.slowest_req.unwrap_or(Duration::ZERO) { + stats.slowest_req = Some(duration); } } + + fn map_stream_item( + request: Request, + stream_item: ServiceResult, + stats: Arc>, + ) -> ServiceResult + where + RequestOctets: Octets + Send + Sync + Unpin, + Svc: Service, + Svc::Target: AsRef<[u8]>, + { + if let Ok(cr) = &stream_item { + if let Some(response) = cr.response() { + Self::postprocess(&request, response, stats); + } + } + stream_item + } +} + +impl Service for StatsMiddlewareSvc +where + RequestOctets: Octets + Send + Sync + 'static + Unpin, + Svc: Service, + Svc::Target: AsRef<[u8]>, + Svc::Future: Unpin, +{ + type Target = Svc::Target; + type Stream = MiddlewareStream< + Svc::Future, + Svc::Stream, + PostprocessingStream< + RequestOctets, + Svc::Future, + Svc::Stream, + Arc>, + >, + Empty>, + ServiceResult, + >; + type Future = Ready; + + fn call(&self, request: Request) -> Self::Future { + self.preprocess(&request); + let svc_call_fut = self.svc.call(request.clone()); + let map = PostprocessingStream::new( + svc_call_fut, + request, + self.stats.clone(), + Self::map_stream_item, + ); + ready(MiddlewareStream::Map(map)) + } +} + +//------------ build_middleware_chain() -------------------------------------- + +#[allow(clippy::type_complexity)] +fn build_middleware_chain( + svc: Svc, + stats: Arc>, +) -> StatsMiddlewareSvc< + MandatoryMiddlewareSvc< + Vec, + EdnsMiddlewareSvc, CookiesMiddlewareSvc, Svc>>, + >, +> { + #[cfg(feature = "siphasher")] + let svc = CookiesMiddlewareSvc::, _>::with_random_secret(svc); + let svc = EdnsMiddlewareSvc::, _>::new(svc); + let svc = MandatoryMiddlewareSvc::, _>::new(svc); + StatsMiddlewareSvc::new(svc, stats.clone()) } //----------- main() --------------------------------------------------------- @@ -429,25 +597,11 @@ async fn main() { eprintln!(" dig +short -4 @127.0.0.1 -p 8053 A 1.2.3.4"); eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8053 A google.com"); eprintln!(" dig +short -4 @127.0.0.1 -p 8054 A google.com"); - eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8080 A google.com"); - eprintln!(" dig +short -6 @::1 +tcp -p 8080 A google.com"); + eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8080 AXFR google.com"); + eprintln!(" dig +short -6 @::1 +tcp -p 8080 AXFR google.com"); eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8081 A google.com"); eprintln!(" dig +short -4 @127.0.0.1 +tls -p 8443 A google.com"); - eprintln!( - " dnsi query --format table --server 127.0.0.1 -p 8053 1.2.3.4 A" - ); - eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8053 --tcp google.com A"); // Fails, dig version works but slow. - eprintln!( - " dnsi query --format table --server 127.0.0.1 -p 8054 google.com A" - ); - eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8080 --tcp google.com A"); - eprintln!( - " dnsi query --format table --server ::1 -p 8080 --tcp google.com A" - ); - eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8081 --tcp google.com A"); - eprintln!(" dnsi query --format table --server 127.0.0.1 -p 8443 --tls google.com A"); - - /* + // ----------------------------------------------------------------------- // Setup logging. You can override the log level by setting environment // variable RUST_LOG, e.g. RUST_LOG=trace. @@ -457,8 +611,55 @@ async fn main() { .without_time() .try_init() .ok(); - */ + // ----------------------------------------------------------------------- + // Inject a custom statistics middleware service (defined above) at the + // start of each middleware chain constructed below so that it can time + // the request processing time from as early till as late as possible + // (excluding time spent in the servers that receive the requests and send + // the responses). Each chain needs its own copy of the stats middleware + // but they can share a single set of statistic counters. + let stats = Arc::new(RwLock::new(Stats::default())); + + // ----------------------------------------------------------------------- + // Create services with accompanying middleware chains to answer incoming + // requests. + +/* + // 1. MySingleResultService: a struct that implements the `Service` trait + // directly. + let my_svc = Arc::new(build_middleware_chain( + MySingleResultService, + stats.clone(), + )); + + // 2. MyAsyncStreamingService: another struct that implements the + // `Service` trait directly. + let my_async_svc = Arc::new(build_middleware_chain( + MyAsyncStreamingService, + stats.clone(), + )); + + // 2. name_to_ip: a service impl defined as a function compatible with the + // `Service` trait. + let name_into_ip_svc = + Arc::new(build_middleware_chain(name_to_ip, stats.clone())); + + // 3. query: a service impl defined as a function converted to a `Service` + // impl via the `service_fn()` helper function. + // Show that we don't have to use the same middleware with every server by + // creating a separate middleware chain for use just by this server. + let count = Arc::new(AtomicU8::new(5)); + let svc = service_fn(query, count); + let svc = MandatoryMiddlewareSvc::, _>::new(svc); + #[cfg(feature = "siphasher")] + let svc = { + let server_secret = "server12secret34".as_bytes().try_into().unwrap(); + CookiesMiddlewareSvc::, _>::new(svc, server_secret) + }; + let svc = StatsMiddlewareSvc::new(svc, stats.clone()); + let query_svc = Arc::new(svc); +*/ // Start building the query router plus upstreams. let mut qr: QnameRouter, Vec, ReplyMessage> = QnameRouter::new(); @@ -487,36 +688,26 @@ async fn main() { let conn_service = ClientTransportToSrService::new(dgram_conn); qr.add(Name::>::from_str("nl").unwrap(), conn_service); - let srv = SrServiceToService::new(qr); + let srv = SingleServiceToService::new(qr); + let my_svc = Arc::new(build_middleware_chain( + srv, + stats.clone(), + )); // ----------------------------------------------------------------------- - // Wrap `MyService` in an `Arc` so that it can be used by multiple servers - // at once. - let svc = Arc::new(srv); - - // ----------------------------------------------------------------------- - // Prepare a modern middleware chain for use by servers defined below. - // Inject a custom statistics middleware processor (defined above) at the - // start of the chain so that it can time the request processing time from - // as early till as late as possible (excluding time spent in the servers - // that receive the requests and send the responses). - let mut middleware = MiddlewareBuilder::default(); - let stats = Arc::new(StatsMiddlewareProcessor::new()); - middleware.push_front(stats.clone()); - let middleware = middleware.build(); - - // ----------------------------------------------------------------------- - // Run a DNS server on UDP port 8053 on 127.0.0.1. Test it like so: + // Run a DNS server on UDP port 8053 on 127.0.0.1 using the name_to_ip + // service defined above and accompanying middleware. Test it like so: // dig +short -4 @127.0.0.1 -p 8053 A google.com + let udpsocket = UdpSocket::bind("127.0.0.1:8053").await.unwrap(); let buf = Arc::new(VecBufSource); - let mut config = dgram::Config::default(); - config.set_middleware_chain(middleware.clone()); - let srv = - DgramServer::with_config(udpsocket, buf.clone(), name_to_ip, config); - + let srv = DgramServer::new(udpsocket, buf.clone(), my_svc.clone()); let udp_join_handle = tokio::spawn(async move { srv.run().await }); + // ----------------------------------------------------------------------- + // Create an instance of our MyService `Service` impl with accompanying + // middleware. + // ----------------------------------------------------------------------- // Run a DNS server on TCP port 8053 on 127.0.0.1. Test it like so: // dig +short +keepopen +tcp -4 @127.0.0.1 -p 8053 A google.com @@ -525,16 +716,7 @@ async fn main() { v4socket.bind("127.0.0.1:8053".parse().unwrap()).unwrap(); let v4listener = v4socket.listen(1024).unwrap(); let buf = Arc::new(VecBufSource); - let mut conn_config = ConnectionConfig::default(); - conn_config.set_middleware_chain(middleware.clone()); - let mut config = stream::Config::default(); - config.set_connection_config(conn_config); - let srv = StreamServer::with_config( - v4listener, - buf.clone(), - svc.clone(), - config, - ); + let srv = StreamServer::new(v4listener, buf.clone(), my_svc.clone()); let srv = srv.with_pre_connect_hook(|stream| { // Demonstrate one way without having access to the code that creates // the socket initially to enable TCP keep alive, @@ -558,42 +740,41 @@ async fn main() { let tcp_join_handle = tokio::spawn(async move { srv.run().await }); + // ----------------------------------------------------------------------- + // This UDP example sets IP_MTU_DISCOVER via setsockopt(), using the libc + // crate (as the nix crate doesn't support IP_MTU_DISCOVER at the time of + // writing). This example is inspired by: + // + // - https://www.ietf.org/archive/id/draft-ietf-dnsop-avoid-fragmentation-17.html#name-recommendations-for-udp-res + // - https://mailarchive.ietf.org/arch/msg/dnsop/Zy3wbhHephubsy2uJesGeDst4F4/ + // - https://man7.org/linux/man-pages/man7/ip.7.html + // + // Some other good reading on sending faster via UDP with Rust: + // - https://devork.be/blog/2023/11/modern-linux-sockets/ + // + // We could also try the following settings that the Unbound man page + // mentions: + // - SO_RCVBUF - Unbound advises setting so-rcvbuf to 4m on busy + // servers to prevent short request spikes causing + // packet drops, + // - SO_SNDBUF - Unbound advises setting so-sndbuf to 4m on busy + // servers to avoid resource temporarily unavailable + // errors, + // - SO_REUSEPORT - Unbound advises to turn it off at extreme load to + // distribute queries evenly, + // - IP_TRANSPARENT - Allows to bind to non-existent IP addresses that + // are going to exist later on. Unbound uses + // IP_BINDANY on FreeBSD and SO_BINDANY on OpenBSD. + // - IP_FREEBIND - Linux only, similar to IP_TRANSPARENT. Allows to + // bind to IP addresses that are nonlocal or do not + // exist, like when the network interface is down. + // - TCP_MAXSEG - Value lower than common MSS on Ethernet (1220 for + // example) will address path MTU problem. + // - A means to control the value of the Differentiated Services + // Codepoint (DSCP) in the differentiated services field (DS) of the + // outgoing IP packet headers. #[cfg(target_os = "linux")] let udp_mtu_join_handle = { - // This UDP example sets IP_MTU_DISCOVER via setsockopt(), using the - // libc crate (as the nix crate doesn't support IP_MTU_DISCOVER at the - // time of writing). This example is inspired by: - // - // - https://www.ietf.org/archive/id/draft-ietf-dnsop-avoid-fragmentation-17.html#name-recommendations-for-udp-res - // - https://mailarchive.ietf.org/arch/msg/dnsop/Zy3wbhHephubsy2uJesGeDst4F4/ - // - https://man7.org/linux/man-pages/man7/ip.7.html - // - // Some other good reading on sending faster via UDP with Rust: - // - https://devork.be/blog/2023/11/modern-linux-sockets/ - // - // We could also try the following settings that the Unbound man page - // mentions: - // - SO_RCVBUF - Unbound advises setting so-rcvbuf to 4m on busy - // servers to prevent short request spikes causing - // packet drops, - // - SO_SNDBUF - Unbound advises setting so-sndbuf to 4m on busy - // servers to avoid resource temporarily - // unavailable errors, - // - SO_REUSEPORT - Unbound advises to turn it off at extreme load - // to distribute queries evenly, - // - IP_TRANSPARENT - Allows to bind to non-existent IP addresses - // that are going to exist later on. Unbound uses - // IP_BINDANY on FreeBSD and SO_BINDANY on - // OpenBSD. - // - IP_FREEBIND - Linux only, similar to IP_TRANSPARENT. Allows - // to bind to IP addresses that are nonlocal or do - // not exist, like when the network interface is - // down. - // - TCP_MAXSEG - Value lower than common MSS on Ethernet (1220 - // for example) will address path MTU problem. - // - A means to control the value of the Differentiated Services - // Codepoint (DSCP) in the differentiated services field (DS) of - // the outgoing IP packet headers. fn setsockopt(socket: libc::c_int, flag: libc::c_int) -> libc::c_int { unsafe { libc::setsockopt( @@ -622,14 +803,7 @@ async fn main() { } } - let mut config = dgram::Config::default(); - config.set_middleware_chain(middleware.clone()); - let srv = DgramServer::with_config( - udpsocket, - buf.clone(), - svc.clone(), - config, - ); + let srv = DgramServer::new(udpsocket, buf.clone(), my_svc.clone()); tokio::spawn(async move { srv.run().await }) }; @@ -650,38 +824,28 @@ async fn main() { let v6listener = v6socket.listen(1024).unwrap(); let listener = DoubleListener::new(v4listener, v6listener); - let mut conn_config = ConnectionConfig::new(); - conn_config.set_middleware_chain(middleware.clone()); - let mut config = stream::Config::new(); - config.set_connection_config(conn_config); - let srv = - StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); let double_tcp_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- - // Demonstrate listening with TCP Fast Open enabled (via the tokio-tfo crate). - // On Linux strace can be used to show that the socket options are indeed - // set as expected, e.g.: + // Demonstrate listening with TCP Fast Open enabled (via the tokio-tfo + // crate). On Linux strace can be used to show that the socket options are + // indeed set as expected, e.g.: // // > strace -e trace=setsockopt cargo run --example serve \ // --features serve,tokio-tfo --release // Finished release [optimized] target(s) in 0.12s // Running `target/release/examples/serve` - // setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - // setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - // setsockopt(8, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 - // setsockopt(8, SOL_TCP, TCP_FASTOPEN, [1024], 4) = 0 + // setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 setsockopt(7, + // SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 setsockopt(8, SOL_SOCKET, + // SO_REUSEADDR, [1], 4) = 0 setsockopt(8, SOL_TCP, TCP_FASTOPEN, + // [1024], 4) = 0 let listener = TfoListener::bind("127.0.0.1:8081".parse().unwrap()) .await .unwrap(); let listener = LocalTfoListener(listener); - let mut conn_config = ConnectionConfig::new(); - conn_config.set_middleware_chain(middleware.clone()); - let mut config = stream::Config::new(); - config.set_connection_config(conn_config); - let srv = - StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); let tfo_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- @@ -704,34 +868,7 @@ async fn main() { let listener = TcpListener::bind("127.0.0.1:8082").await.unwrap(); let listener = BufferedTcpListener(listener); - let count = Arc::new(AtomicU8::new(5)); - - // Make our service from the `query` function with the help of the - // `service_fn` function. - let fn_svc = service_fn(query, count); - - // Show that we don't have to use the same middleware with every server by - // creating a separate middleware chain for use just by this server, and - // also show that by creating the individual middleware processors - // ourselves we can override their default configuration. - let mut fn_svc_middleware = MiddlewareBuilder::new(); - fn_svc_middleware.push(MandatoryMiddlewareProcessor::new().into()); - - #[cfg(feature = "siphasher")] - { - let server_secret = "server12secret34".as_bytes().try_into().unwrap(); - fn_svc_middleware - .push(CookiesMiddlewareProcessor::new(server_secret).into()); - } - - let fn_svc_middleware = fn_svc_middleware.build(); - - let mut conn_config = ConnectionConfig::new(); - conn_config.set_middleware_chain(fn_svc_middleware); - let mut config = stream::Config::new(); - config.set_connection_config(conn_config); - let srv = - StreamServer::with_config(listener, buf.clone(), fn_svc, config); + let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); let fn_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- @@ -757,23 +894,17 @@ async fn main() { let acceptor = TlsAcceptor::from(Arc::new(config)); let listener = TcpListener::bind("127.0.0.1:8443").await.unwrap(); let listener = RustlsTcpListener::new(listener, acceptor); - - let mut conn_config = ConnectionConfig::new(); - conn_config.set_middleware_chain(middleware.clone()); - let mut config = stream::Config::new(); - config.set_connection_config(conn_config); - let srv = - StreamServer::with_config(listener, buf.clone(), svc.clone(), config); + let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); let tls_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- // Print statistics periodically tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(15)); + let mut interval = tokio::time::interval(Duration::from_secs(5)); loop { interval.tick().await; - println!("Statistics report: {stats}"); + println!("Statistics report: {}", stats.read().unwrap()); } }); diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index c66d1b4f9..acbc86581 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -1,7 +1,7 @@ // adapters use super::message::{Request, RequestNG}; -use super::service::{CallResult, Service, ServiceError, Transaction}; +use super::service::{CallResult, Service, ServiceResult, }; use super::single_service::{ComposeReply, SingleService}; use std::boxed::Box; use std::fmt::Debug; @@ -11,15 +11,20 @@ use std::marker::PhantomData; use crate::dep::octseq::Octets; use crate::net::client::request::RequestMessage; use crate::net::client::request::SendRequest; +//use futures::Stream; +use futures::stream::Once; +use futures::stream::once; +use std::future::Ready; +use std::future::ready; use std::pin::Pin; use std::vec::Vec; -pub struct SrServiceToService { +pub struct SingleServiceToService { service: SVC, phantom: PhantomData, } -impl SrServiceToService { +impl SingleServiceToService { pub fn new(service: SVC) -> Self { Self { service, @@ -28,34 +33,31 @@ impl SrServiceToService { } } -impl Service for SrServiceToService +impl Service for SingleServiceToService where SVC: SingleService, CR>, CR: ComposeReply + 'static, { type Target = Vec; - //type Future = Ready, ServiceError>>; + type Stream = Once>>; type Future = Pin< Box< - dyn Future< - Output = Result, ServiceError>, - > + Send - + Sync, + dyn Future + Send, >, >; fn call( &self, request: Request>, - ) -> Result, ServiceError> { + ) -> Self::Future { let req = RequestNG::from_request(request); let fut = self.service.call(req); let fut = async move { let reply = fut.await.unwrap(); let abs = reply.additional_builder_stream_target(); - Ok(CallResult::new(abs)) + once(ready(Ok(CallResult::new(abs)))) }; - Ok(Transaction::single(Box::pin(fut))) + Box::pin(fut) } } diff --git a/src/net/server/message.rs b/src/net/server/message.rs index cfc33c3a9..f9be4afbc 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -1,10 +1,10 @@ //! Support for working with DNS messages in servers. use bytes::Bytes; -use core::ops::ControlFlow; +//use core::ops::ControlFlow; use core::time::Duration; use std::fmt::Debug; -use std::net::SocketAddr; +//use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::vec::Vec; @@ -13,18 +13,19 @@ use tokio::time::Instant; use crate::base::opt::AllOptData; use crate::base::Message; use crate::base::Name; +use crate::dep::octseq::Octets; //use crate::base::opt::OptRecord; //use crate::base::opt::subnet::ClientSubnet; //use crate::dep::octseq::OctetsFrom; use crate::net::client::request::ComposeRequest; use crate::net::client::request::RequestMessage; -use crate::net::server::buf::BufSource; -use crate::net::server::metrics::ServerMetrics; -use crate::net::server::middleware::chain::MiddlewareChain; +//use crate::net::server::buf::BufSource; +//use crate::net::server::metrics::ServerMetrics; +//use crate::net::server::middleware::chain::MiddlewareChain; -use super::service::{CallResult, Service, ServiceError, Transaction}; -use super::util::start_reply; -use crate::base::wire::Composer; +//use super::service::{Service, }; +//use super::util::start_reply; +//use crate::base::wire::Composer; //------------ UdpTransportContext ------------------------------------------- @@ -351,7 +352,7 @@ impl> RequestNG { pub fn from_request(request: Request) -> Self where - Octs: Octets, + Octs: Octets + Send + Sync + Unpin, { let mut req = Self { client_addr: request.client_addr, @@ -437,323 +438,6 @@ impl> Clone for RequestNG { } } -//----------- CommonMessageFlow ---------------------------------------------- - -/// Perform processing common to all messages being handled by a DNS server. -/// -/// All messages received by a DNS server need to pass through the following -/// processing stages: -/// -/// - Pre-processing. -/// - Service processing. -/// - Post-processing. -/// -/// The strategy is common but some server specific aspects are delegated to -/// the server that implements this trait: -/// -/// - Adding context to a request. -/// - Finalizing the handling of a response. -/// -/// Servers implement this trait to benefit from the common processing -/// required while still handling aspects specific to the server themselves. -/// -/// Processing starts at [`process_request`]. -/// -///
-/// -/// This trait exists as a convenient mechanism for sharing common code -/// between server implementations. The default function implementations -/// provided by this trait are not intended to be overridden by consumers of -/// this library. -/// -///
-/// -/// [`process_request`]: Self::process_request() -pub trait CommonMessageFlow -where - Buf: BufSource, - Buf::Output: Octets + Send + Sync, - Svc: Service + Send + Sync, -{ - /// Server-specific data that it chooses to pass along with the request in - /// order that it may receive it when `process_call_result()` is - /// invoked on the implementing server. - type Meta: Clone + Send + Sync + 'static; - - /// Process a DNS request message. - /// - /// This function consumes the given message buffer and processes the - /// contained message, if any, to completion, possibly resulting in a - /// response being passed to [`Self::process_call_result`]. - /// - /// The request message is a given as a seqeuence of bytes in `buf` - /// originating from client address `addr`. - /// - /// The [`MiddlewareChain`] and [`Service`] to be used to process the - /// message are supplied in the `middleware_chain` and `svc` arguments - /// respectively. - /// - /// Any server specific state to be used and/or updated as part of the - /// processing should be supplied via the `state` argument whose type is - /// defined by the implementing type. - /// - /// On error the result will be a [`ServiceError`]. - #[allow(clippy::too_many_arguments)] - fn process_request( - &self, - buf: Buf::Output, - received_at: Instant, - addr: SocketAddr, - middleware_chain: MiddlewareChain, - svc: &Svc, - metrics: Arc, - meta: Self::Meta, - ) -> Result<(), ServiceError> - where - Svc: 'static, - Svc::Target: Send + Composer + Default, - Svc::Future: Send, - Buf::Output: 'static, - { - boomerang( - self, - buf, - received_at, - addr, - middleware_chain, - metrics, - svc, - meta, - ) - } - - /// Add context to a request. - /// - /// The server supplies this function to annotate the received message - /// with additional information about its origins. - fn add_context_to_request( - &self, - request: Message, - received_at: Instant, - addr: SocketAddr, - ) -> Request; - - /// Finalize a response. - /// - /// The server supplies this function to handle the response as - /// appropriate for the server, e.g. to write the response back to the - /// originating client. - /// - /// The response is the form of a [`CallResult`]. - fn process_call_result( - request: &Request, - call_result: CallResult, - state: Self::Meta, - metrics: Arc, - ); -} - -/// Propogate a message through the [`MiddlewareChain`] to the [`Service`] and -/// flow the response in reverse back down the same path, a bit like throwing -/// a boomerang. -#[allow(clippy::too_many_arguments)] -fn boomerang( - server: &Server, - buf: ::Output, - received_at: Instant, - addr: SocketAddr, - middleware_chain: MiddlewareChain< - ::Output, - ::Output>>::Target, - >, - metrics: Arc, - svc: &Svc, - meta: Server::Meta, -) -> Result<(), ServiceError> -where - Buf: BufSource, - Buf::Output: Octets + Send + Sync + 'static, - Svc: Service + Send + Sync + 'static, - Svc::Future: Send, - Svc::Target: Send + Composer + Default, - Server: CommonMessageFlow + ?Sized, -{ - let message = Message::from_octets(buf).map_err(|err| { - warn!("Failed while parsing request message: {err}"); - ServiceError::InternalError - })?; - - let request = server.add_context_to_request(message, received_at, addr); - - let preprocessing_result = do_middleware_preprocessing::( - &request, - &middleware_chain, - &metrics, - )?; - - let (txn, aborted_preprocessor_idx) = - do_service_call::(preprocessing_result, &request, svc); - - do_middleware_postprocessing::( - request, - meta, - middleware_chain, - txn, - aborted_preprocessor_idx, - metrics, - ); - - Ok(()) -} - -/// Pass a pre-processed request to the [`Service`] to handle. -/// -/// If [`Service::call`] returns an error this function will produce a DNS -/// ServFail error response. If the returned error is -/// [`ServiceError::InternalError`] it will also be logged. -#[allow(clippy::type_complexity)] -fn do_service_call( - preprocessing_result: ControlFlow<( - Transaction, - usize, - )>, - request: &Request<::Output>, - svc: &Svc, -) -> (Transaction, Option) -where - Buf: BufSource, - Buf::Output: Octets, - Svc: Service, - Svc::Target: Composer + Default, -{ - match preprocessing_result { - ControlFlow::Continue(()) => { - let res = if enabled!(Level::INFO) { - let span = info_span!("svc-call", - msg_id = request.message().header().id(), - client = %request.client_addr(), - ); - let _guard = span.enter(); - svc.call(request.clone()) - } else { - svc.call(request.clone()) - }; - - // Handle any error returned by the service. - let txn = res.unwrap_or_else(|err| { - if matches!(err, ServiceError::InternalError) { - error!("Service error while processing request: {err}"); - } - - let mut response = start_reply(request); - response.header_mut().set_rcode(err.rcode()); - let call_result = CallResult::new(response.additional()); - Transaction::immediate(Ok(call_result)) - }); - - // Pass the transaction out for post-processing. - (txn, None) - } - - ControlFlow::Break((txn, aborted_preprocessor_idx)) => { - (txn, Some(aborted_preprocessor_idx)) - } - } -} - -/// Pre-process a request. -/// -/// Pre-processing involves parsing a [`Message`] from the byte buffer and -/// pre-processing it via any supplied [`MiddlewareChain`]. -/// -/// On success the result is an immutable request message and a -/// [`ControlFlow`] decision about whether to continue with further processing -/// or to break early with a possible response. If processing failed the -/// result will be a [`ServiceError`]. -/// -/// On break the result will be one ([`Transaction::single`]) or more -/// ([`Transaction::stream`]) to post-process. -#[allow(clippy::type_complexity)] -fn do_middleware_preprocessing( - request: &Request, - middleware_chain: &MiddlewareChain, - metrics: &Arc, -) -> Result< - ControlFlow<(Transaction, usize)>, - ServiceError, -> -where - Buf: BufSource, - Buf::Output: Octets + Send + Sync + 'static, - Svc: Service + Send + Sync, - Svc::Future: Send, - Svc::Target: Send + Composer + Default + 'static, -{ - let span = info_span!("pre-process", - msg_id = request.message().header().id(), - client = %request.client_addr(), - ); - let _guard = span.enter(); - - metrics.inc_num_inflight_requests(); - - let pp_res = middleware_chain.preprocess(request); - - Ok(pp_res) -} - -/// Post-process a response in the context of its originating request. -/// -/// Each response is post-processed in its own Tokio task. Note that there is -/// no guarantee about the order in which responses will be post-processed. If -/// the order of a seqence of responses is important it should be provided as -/// a [`Transaction::stream`] rather than [`Transaction::single`]. -/// -/// Responses are first post-processed by the [`MiddlewareChain`] provided, if -/// any, then passed to [`Self::process_call_result`] for final processing. -fn do_middleware_postprocessing( - request: Request, - meta: Server::Meta, - middleware_chain: MiddlewareChain, - mut response_txn: Transaction, - last_processor_id: Option, - metrics: Arc, -) where - Buf: BufSource, - Buf::Output: Octets + Send + Sync + 'static, - Svc: Service + Send + Sync + 'static, - Svc::Future: Send, - Svc::Target: Send + Composer + Default, - Server: CommonMessageFlow + ?Sized, -{ - tokio::spawn(async move { - let span = info_span!("post-process", - msg_id = request.message().header().id(), - client = %request.client_addr(), - ); - let _guard = span.enter(); - - while let Some(Ok(mut call_result)) = response_txn.next().await { - if let Some(response) = call_result.get_response_mut() { - middleware_chain.postprocess( - &request, - response, - last_processor_id, - ); - } - - Server::process_call_result( - &request, - call_result, - meta.clone(), - metrics.clone(), - ); - } - - metrics.dec_num_inflight_requests(); - }); -} - /// Return whether the DO flag is set. This should move to Message. fn dnssec_ok(msg: &Message) -> bool { if let Some(opt) = msg.opt() { From c3fae7ad60fbf9981204325c978be8d15aa52422 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 29 Aug 2024 14:27:09 +0200 Subject: [PATCH 10/38] Adapt to changes in net::client. --- src/net/server/adapter.rs | 11 +++++++---- src/net/server/message.rs | 8 ++++---- src/net/server/qname_router.rs | 3 ++- src/net/server/single_service.rs | 3 ++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index acbc86581..248c44cc0 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -9,7 +9,7 @@ use std::future::Future; use std::marker::PhantomData; //use std::future::Ready; use crate::dep::octseq::Octets; -use crate::net::client::request::RequestMessage; +use crate::net::client::request::{Error, RequestMessage}; use crate::net::client::request::SendRequest; //use futures::Stream; use futures::stream::Once; @@ -88,18 +88,21 @@ impl SingleService where RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, SR: SendRequest> + Sync, - CR: ComposeReply, + CR: ComposeReply + Send + Sync + 'static, { type Target = Vec; fn call( &self, request: RequestNG, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]>, { - let req = request.to_request_message(); + let req = match request.to_request_message() { + Ok(req) => req, + Err(e) => return Box::pin(ready(Err(e))), + }; let mut gr = self.conn.send_request(req); let fut = async move { let msg = gr.get_response().await.unwrap(); diff --git a/src/net/server/message.rs b/src/net/server/message.rs index f9be4afbc..e7f06b04e 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -18,7 +18,7 @@ use crate::dep::octseq::Octets; //use crate::base::opt::subnet::ClientSubnet; //use crate::dep::octseq::OctetsFrom; use crate::net::client::request::ComposeRequest; -use crate::net::client::request::RequestMessage; +use crate::net::client::request::{Error, RequestMessage}; //use crate::net::server::buf::BufSource; //use crate::net::server::metrics::ServerMetrics; //use crate::net::server::middleware::chain::MiddlewareChain; @@ -381,7 +381,7 @@ impl> RequestNG { req } - pub fn to_request_message(&self) -> RequestMessage + pub fn to_request_message(&self) -> Result, Error> where Octs: Clone + Debug + Octets + Send + Sync, { @@ -389,7 +389,7 @@ impl> RequestNG { // message in the Arc directly. let msg = Message::from_octets(self.message.as_octets().clone()).unwrap(); - let mut reqmsg = RequestMessage::new(msg); + let mut reqmsg = RequestMessage::new(msg)?; // Copy DO bit if dnssec_ok(&self.message) { @@ -400,7 +400,7 @@ impl> RequestNG { for opt in &self.opt { reqmsg.add_opt(opt).unwrap(); } - reqmsg + Ok(reqmsg) } /// When was this message received? diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index b45852d69..17de89358 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -2,6 +2,7 @@ use super::message::RequestNG; use super::single_service::SingleService; +use crate::net::client::request::Error; use crate::base::Name; use crate::base::ToName; use crate::dep::octseq::EmptyBuilder; @@ -62,7 +63,7 @@ where fn call( &self, request: RequestNG, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets, { diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index dfd8ac2c3..69197ac9f 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -11,6 +11,7 @@ use crate::base::MessageBuilder; use crate::base::ParsedName; use crate::base::Rtype; use crate::base::StreamTarget; +use crate::net::client::request::Error; use crate::dep::octseq::Octets; use crate::rdata::AllRecordData; use std::boxed::Box; @@ -24,7 +25,7 @@ pub trait SingleService { fn call( &self, request: RequestNG, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets; } From 1e791a8255b975209851ce1ddbdeae92d0d6b05b Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 2 Sep 2024 14:12:01 +0200 Subject: [PATCH 11/38] Adapts to changes in Service. --- examples/query-routing.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index affdb7977..256740340 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -549,7 +549,7 @@ where PostprocessingStream< RequestOctets, Svc::Future, - Svc::Stream, + Svc::Stream, (), Arc>, >, Empty>, @@ -579,13 +579,14 @@ fn build_middleware_chain( ) -> StatsMiddlewareSvc< MandatoryMiddlewareSvc< Vec, - EdnsMiddlewareSvc, CookiesMiddlewareSvc, Svc>>, + EdnsMiddlewareSvc, CookiesMiddlewareSvc, Svc, ()>, ()>, + () >, > { #[cfg(feature = "siphasher")] - let svc = CookiesMiddlewareSvc::, _>::with_random_secret(svc); - let svc = EdnsMiddlewareSvc::, _>::new(svc); - let svc = MandatoryMiddlewareSvc::, _>::new(svc); + let svc = CookiesMiddlewareSvc::, _, _>::with_random_secret(svc); + let svc = EdnsMiddlewareSvc::, _, _>::new(svc); + let svc = MandatoryMiddlewareSvc::, _, _>::new(svc); StatsMiddlewareSvc::new(svc, stats.clone()) } From 0460739ff3b9ae1a897dbc4c473796009927a4fc Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 2 Sep 2024 14:12:45 +0200 Subject: [PATCH 12/38] Changes for proxy. --- src/net/server/adapter.rs | 65 ++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 248c44cc0..bafa9079e 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -19,23 +19,26 @@ use std::future::ready; use std::pin::Pin; use std::vec::Vec; -pub struct SingleServiceToService { +pub struct SingleServiceToService { service: SVC, - phantom: PhantomData, + ro_phantom: PhantomData, + cr_phantom: PhantomData, } -impl SingleServiceToService { +impl SingleServiceToService { pub fn new(service: SVC) -> Self { Self { service, - phantom: PhantomData, + ro_phantom: PhantomData, + cr_phantom: PhantomData, } } } -impl Service for SingleServiceToService +impl Service for SingleServiceToService where - SVC: SingleService, CR>, + RequestOcts: Octets + Send + Sync + Unpin, + SVC: SingleService, CR: ComposeReply + 'static, { type Target = Vec; @@ -48,7 +51,7 @@ where fn call( &self, - request: Request>, + request: Request, ) -> Self::Future { let req = RequestNG::from_request(request); let fut = self.service.call(req); @@ -111,3 +114,51 @@ where Box::pin(fut) } } + +pub struct BoxClientTransportToSrService +where + RequestOcts: AsRef<[u8]>, +{ + conn: Box> + Send + Sync>, + _phantom: PhantomData, +} + +impl BoxClientTransportToSrService +where + RequestOcts: AsRef<[u8]>, +{ + pub fn new(conn: Box> + Send + Sync>) -> Self { + Self { + conn, + _phantom: PhantomData, + } + } +} + +impl SingleService + for BoxClientTransportToSrService +where + RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, + CR: ComposeReply + Send + Sync + 'static, +{ + type Target = Vec; + + fn call( + &self, + request: RequestNG, + ) -> Pin> + Send + Sync>> + where + RequestOcts: AsRef<[u8]>, + { + let req = match request.to_request_message() { + Ok(req) => req, + Err(e) => return Box::pin(ready(Err(e))), + }; + let mut gr = self.conn.send_request(req); + let fut = async move { + let msg = gr.get_response().await.unwrap(); + Ok(CR::from_message(&msg)) + }; + Box::pin(fut) + } +} From 9cab8db22af91703bd24453b2dd0ca422b699a83 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 2 Sep 2024 14:22:11 +0200 Subject: [PATCH 13/38] Fmt --- examples/query-routing.rs | 130 +++++++++++++++---------------- src/net/server/adapter.rs | 46 +++++------ src/net/server/qname_router.rs | 7 +- src/net/server/single_service.rs | 13 +--- 4 files changed, 87 insertions(+), 109 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 256740340..0191e8f26 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -3,25 +3,6 @@ use core::future::{ready, Future, Ready}; use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use core::task::{Context, Poll}; use core::time::Duration; - -use std::fs::File; -use std::io; -use std::io::BufReader; -use std::net::SocketAddr; -use std::pin::Pin; -use std::sync::Arc; -use std::sync::RwLock; - -use futures::channel::mpsc::unbounded; -use futures::stream::{once, Empty, Once, Stream}; -use octseq::{FreezeBuilder, Octets}; -use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; -use tokio::time::Instant; -use tokio_rustls::rustls; -use tokio_rustls::TlsAcceptor; -use tokio_tfo::{TfoListener, TfoStream}; -use tracing_subscriber::EnvFilter; - use domain::base::iana::{Class, Rcode}; use domain::base::message_builder::{AdditionalBuilder, PushError}; use domain::base::name::ToLabelIter; @@ -29,8 +10,9 @@ use domain::base::wire::Composer; use domain::base::{MessageBuilder, Name, Rtype, Serial, StreamTarget, Ttl}; use domain::net::client::dgram as client_dgram; use domain::net::client::protocol::UdpConnect; -use domain::net::server::adapter::ClientTransportToSrService; -use domain::net::server::adapter::SingleServiceToService; +use domain::net::server::adapter::{ + ClientTransportToSrService, SingleServiceToService, +}; use domain::net::server::buf::VecBufSource; use domain::net::server::dgram::DgramServer; use domain::net::server::message::Request; @@ -47,11 +29,25 @@ use domain::net::server::service::{ use domain::net::server::single_service::ReplyMessage; use domain::net::server::sock::AsyncAccept; use domain::net::server::stream::StreamServer; -use domain::net::server::util::{mk_builder_for_target, /*service_fn*/}; +use domain::net::server::util::mk_builder_for_target; use domain::rdata::{Soa, A}; -use std::vec::Vec; -use std::net::IpAddr; +use futures::channel::mpsc::unbounded; +use futures::stream::{once, Empty, Once, Stream}; +use octseq::{FreezeBuilder, Octets}; +use std::fs::File; +use std::io; +use std::io::BufReader; +use std::net::{IpAddr, SocketAddr}; +use std::pin::Pin; use std::str::FromStr; +use std::sync::Arc; +use std::sync::RwLock; +use std::vec::Vec; +use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; +use tokio::time::Instant; +use tokio_rustls::{rustls, TlsAcceptor}; +use tokio_tfo::{TfoListener, TfoStream}; +use tracing_subscriber::EnvFilter; //----------- mk_answer() ---------------------------------------------------- @@ -549,7 +545,8 @@ where PostprocessingStream< RequestOctets, Svc::Future, - Svc::Stream, (), + Svc::Stream, + (), Arc>, >, Empty>, @@ -579,8 +576,12 @@ fn build_middleware_chain( ) -> StatsMiddlewareSvc< MandatoryMiddlewareSvc< Vec, - EdnsMiddlewareSvc, CookiesMiddlewareSvc, Svc, ()>, ()>, - () + EdnsMiddlewareSvc< + Vec, + CookiesMiddlewareSvc, Svc, ()>, + (), + >, + (), >, > { #[cfg(feature = "siphasher")] @@ -626,41 +627,41 @@ async fn main() { // Create services with accompanying middleware chains to answer incoming // requests. -/* - // 1. MySingleResultService: a struct that implements the `Service` trait - // directly. - let my_svc = Arc::new(build_middleware_chain( - MySingleResultService, - stats.clone(), - )); - - // 2. MyAsyncStreamingService: another struct that implements the - // `Service` trait directly. - let my_async_svc = Arc::new(build_middleware_chain( - MyAsyncStreamingService, - stats.clone(), - )); - - // 2. name_to_ip: a service impl defined as a function compatible with the - // `Service` trait. - let name_into_ip_svc = - Arc::new(build_middleware_chain(name_to_ip, stats.clone())); - - // 3. query: a service impl defined as a function converted to a `Service` - // impl via the `service_fn()` helper function. - // Show that we don't have to use the same middleware with every server by - // creating a separate middleware chain for use just by this server. - let count = Arc::new(AtomicU8::new(5)); - let svc = service_fn(query, count); - let svc = MandatoryMiddlewareSvc::, _>::new(svc); - #[cfg(feature = "siphasher")] - let svc = { - let server_secret = "server12secret34".as_bytes().try_into().unwrap(); - CookiesMiddlewareSvc::, _>::new(svc, server_secret) - }; - let svc = StatsMiddlewareSvc::new(svc, stats.clone()); - let query_svc = Arc::new(svc); -*/ + /* + // 1. MySingleResultService: a struct that implements the `Service` trait + // directly. + let my_svc = Arc::new(build_middleware_chain( + MySingleResultService, + stats.clone(), + )); + + // 2. MyAsyncStreamingService: another struct that implements the + // `Service` trait directly. + let my_async_svc = Arc::new(build_middleware_chain( + MyAsyncStreamingService, + stats.clone(), + )); + + // 2. name_to_ip: a service impl defined as a function compatible with the + // `Service` trait. + let name_into_ip_svc = + Arc::new(build_middleware_chain(name_to_ip, stats.clone())); + + // 3. query: a service impl defined as a function converted to a `Service` + // impl via the `service_fn()` helper function. + // Show that we don't have to use the same middleware with every server by + // creating a separate middleware chain for use just by this server. + let count = Arc::new(AtomicU8::new(5)); + let svc = service_fn(query, count); + let svc = MandatoryMiddlewareSvc::, _>::new(svc); + #[cfg(feature = "siphasher")] + let svc = { + let server_secret = "server12secret34".as_bytes().try_into().unwrap(); + CookiesMiddlewareSvc::, _>::new(svc, server_secret) + }; + let svc = StatsMiddlewareSvc::new(svc, stats.clone()); + let query_svc = Arc::new(svc); + */ // Start building the query router plus upstreams. let mut qr: QnameRouter, Vec, ReplyMessage> = QnameRouter::new(); @@ -690,10 +691,7 @@ async fn main() { qr.add(Name::>::from_str("nl").unwrap(), conn_service); let srv = SingleServiceToService::new(qr); - let my_svc = Arc::new(build_middleware_chain( - srv, - stats.clone(), - )); + let my_svc = Arc::new(build_middleware_chain(srv, stats.clone())); // ----------------------------------------------------------------------- // Run a DNS server on UDP port 8053 on 127.0.0.1 using the name_to_ip diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index bafa9079e..f75c0ae9e 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -1,21 +1,15 @@ // adapters use super::message::{Request, RequestNG}; -use super::service::{CallResult, Service, ServiceResult, }; +use super::service::{CallResult, Service, ServiceResult}; use super::single_service::{ComposeReply, SingleService}; +use crate::dep::octseq::Octets; +use crate::net::client::request::{Error, RequestMessage, SendRequest}; +use futures::stream::{once, Once}; use std::boxed::Box; use std::fmt::Debug; -use std::future::Future; +use std::future::{ready, Future, Ready}; use std::marker::PhantomData; -//use std::future::Ready; -use crate::dep::octseq::Octets; -use crate::net::client::request::{Error, RequestMessage}; -use crate::net::client::request::SendRequest; -//use futures::Stream; -use futures::stream::Once; -use futures::stream::once; -use std::future::Ready; -use std::future::ready; use std::pin::Pin; use std::vec::Vec; @@ -35,7 +29,8 @@ impl SingleServiceToService { } } -impl Service for SingleServiceToService +impl Service + for SingleServiceToService where RequestOcts: Octets + Send + Sync + Unpin, SVC: SingleService, @@ -43,16 +38,9 @@ where { type Target = Vec; type Stream = Once>>; - type Future = Pin< - Box< - dyn Future + Send, - >, - >; + type Future = Pin + Send>>; - fn call( - &self, - request: Request, - ) -> Self::Future { + fn call(&self, request: Request) -> Self::Future { let req = RequestNG::from_request(request); let fut = self.service.call(req); let fut = async move { @@ -103,9 +91,9 @@ where RequestOcts: AsRef<[u8]>, { let req = match request.to_request_message() { - Ok(req) => req, - Err(e) => return Box::pin(ready(Err(e))), - }; + Ok(req) => req, + Err(e) => return Box::pin(ready(Err(e))), + }; let mut gr = self.conn.send_request(req); let fut = async move { let msg = gr.get_response().await.unwrap(); @@ -127,7 +115,9 @@ impl BoxClientTransportToSrService where RequestOcts: AsRef<[u8]>, { - pub fn new(conn: Box> + Send + Sync>) -> Self { + pub fn new( + conn: Box> + Send + Sync>, + ) -> Self { Self { conn, _phantom: PhantomData, @@ -151,9 +141,9 @@ where RequestOcts: AsRef<[u8]>, { let req = match request.to_request_message() { - Ok(req) => req, - Err(e) => return Box::pin(ready(Err(e))), - }; + Ok(req) => req, + Err(e) => return Box::pin(ready(Err(e))), + }; let mut gr = self.conn.send_request(req); let fut = async move { let msg = gr.get_response().await.unwrap(); diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 17de89358..233098fba 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -2,12 +2,9 @@ use super::message::RequestNG; use super::single_service::SingleService; +use crate::base::{Name, ToName}; +use crate::dep::octseq::{EmptyBuilder, FromBuilder, Octets}; use crate::net::client::request::Error; -use crate::base::Name; -use crate::base::ToName; -use crate::dep::octseq::EmptyBuilder; -use crate::dep::octseq::FromBuilder; -use crate::dep::octseq::Octets; use std::boxed::Box; use std::future::Future; use std::pin::Pin; diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 69197ac9f..d3d108cd7 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -2,17 +2,10 @@ use super::message::RequestNG; use crate::base::message_builder::AdditionalBuilder; -use crate::base::opt::AllOptData; -use crate::base::opt::ComposeOptData; -use crate::base::opt::LongOptData; -use crate::base::opt::OptRecord; -use crate::base::Message; -use crate::base::MessageBuilder; -use crate::base::ParsedName; -use crate::base::Rtype; -use crate::base::StreamTarget; -use crate::net::client::request::Error; +use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord}; +use crate::base::{Message, MessageBuilder, ParsedName, Rtype, StreamTarget}; use crate::dep::octseq::Octets; +use crate::net::client::request::Error; use crate::rdata::AllRecordData; use std::boxed::Box; use std::future::Future; From 2b7e9495b379929b090cc5ad54c3f8bdd3b8a577 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 3 Sep 2024 15:29:50 +0200 Subject: [PATCH 14/38] Docs --- src/net/server/adapter.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index f75c0ae9e..7ed8e6d69 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -1,4 +1,15 @@ -// adapters +//! Service adapters. +//! +//! This module defines three adapters for [SingleService]. The first, +//! [ClientTransportToSingleService] implements [SingleService] for a +//! client transport ([SendRequest]). +//! The second one, [BoxClientTransportToSingleService], +//! implements [Service] for a boxed trait object of [SendRequest]. +//! The third one, [SingleServiceToService] implements [Service] for +//! [SingleService]. + +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] use super::message::{Request, RequestNG}; use super::service::{CallResult, Service, ServiceResult}; @@ -13,6 +24,7 @@ use std::marker::PhantomData; use std::pin::Pin; use std::vec::Vec; +/// Provide a [Service] trait for an object that implements [SingleService]. pub struct SingleServiceToService { service: SVC, ro_phantom: PhantomData, @@ -20,6 +32,7 @@ pub struct SingleServiceToService { } impl SingleServiceToService { + /// Create a new [SingleServiceToService] object. pub fn new(service: SVC) -> Self { Self { service, @@ -52,7 +65,9 @@ where } } -pub struct ClientTransportToSrService +/// Provide a [SingleService] trait for an object that implements the +/// [SendRequest] trait. +pub struct ClientTransportToSingleService where RequestOcts: AsRef<[u8]>, SR: SendRequest>, @@ -61,11 +76,12 @@ where _phantom: PhantomData, } -impl ClientTransportToSrService +impl ClientTransportToSingleService where RequestOcts: AsRef<[u8]>, SR: SendRequest>, { + /// Create a new [ClientTransportToSingleService] object. pub fn new(conn: SR) -> Self { Self { conn, @@ -75,7 +91,7 @@ where } impl SingleService - for ClientTransportToSrService + for ClientTransportToSingleService where RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, SR: SendRequest> + Sync, @@ -103,7 +119,8 @@ where } } -pub struct BoxClientTransportToSrService +/// Implement the [SingleService] trait for a boxed [SendRequest] trait object. +pub struct BoxClientTransportToSingleService where RequestOcts: AsRef<[u8]>, { @@ -111,10 +128,11 @@ where _phantom: PhantomData, } -impl BoxClientTransportToSrService +impl BoxClientTransportToSingleService where RequestOcts: AsRef<[u8]>, { + /// Create a new [BoxClientTransportToSingleService] object. pub fn new( conn: Box> + Send + Sync>, ) -> Self { @@ -126,7 +144,7 @@ where } impl SingleService - for BoxClientTransportToSrService + for BoxClientTransportToSingleService where RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, CR: ComposeReply + Send + Sync + 'static, From 1e778875cc039d89369daa1b4aef874b89ae5fe2 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 5 Sep 2024 09:38:03 +0200 Subject: [PATCH 15/38] Fix example. --- examples/query-routing.rs | 92 +++------------------------------------ 1 file changed, 6 insertions(+), 86 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 0191e8f26..cf1f1ac36 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -1,17 +1,16 @@ use core::fmt; use core::future::{ready, Future, Ready}; -use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::sync::atomic::{AtomicBool, Ordering}; use core::task::{Context, Poll}; use core::time::Duration; use domain::base::iana::{Class, Rcode}; use domain::base::message_builder::{AdditionalBuilder, PushError}; -use domain::base::name::ToLabelIter; use domain::base::wire::Composer; use domain::base::{MessageBuilder, Name, Rtype, Serial, StreamTarget, Ttl}; use domain::net::client::dgram as client_dgram; use domain::net::client::protocol::UdpConnect; use domain::net::server::adapter::{ - ClientTransportToSrService, SingleServiceToService, + ClientTransportToSingleService, SingleServiceToService, }; use domain::net::server::buf::VecBufSource; use domain::net::server::dgram::DgramServer; @@ -24,7 +23,7 @@ use domain::net::server::middleware::stream::{ }; use domain::net::server::qname_router::QnameRouter; use domain::net::server::service::{ - CallResult, Service, ServiceFeedback, ServiceResult, + CallResult, Service, ServiceResult, }; use domain::net::server::single_service::ReplyMessage; use domain::net::server::sock::AsyncAccept; @@ -204,85 +203,6 @@ impl Service> for MyAsyncStreamingService { } } -//--- name_to_ip() - -/// This function shows how to implement [`Service`] logic by matching the -/// function signature required by the [`Service`] trait. -/// -/// The function signature is slightly more complex than when using -/// [`service_fn`] (see the [`query`] example below). -#[allow(clippy::type_complexity)] -fn name_to_ip(request: Request>) -> ServiceResult> { - let mut out_answer = None; - if let Ok(question) = request.message().sole_question() { - let qname = question.qname(); - let num_labels = qname.label_count(); - if num_labels >= 5 { - let mut iter = qname.iter_labels(); - let a = iter.nth(num_labels - 5).unwrap(); - let b = iter.next().unwrap(); - let c = iter.next().unwrap(); - let d = iter.next().unwrap(); - let a_rec: Result = format!("{a}.{b}.{c}.{d}").parse(); - if let Ok(a_rec) = a_rec { - let builder = mk_builder_for_target(); - let mut answer = builder - .start_answer(request.message(), Rcode::NOERROR) - .unwrap(); - answer - .push((Name::root_ref(), Class::IN, 86400, a_rec)) - .unwrap(); - out_answer = Some(answer); - } - } - } - - if out_answer.is_none() { - let builder = mk_builder_for_target(); - eprintln!("Refusing request, only requests for A records in IPv4 dotted quad format are accepted by this service."); - out_answer = Some( - builder - .start_answer(request.message(), Rcode::REFUSED) - .unwrap(), - ); - } - - let additional = out_answer.unwrap().additional(); - Ok(CallResult::new(additional)) -} - -//--- query() - -/// This function shows how to implement [`Service`] logic by matching the -/// function signature required by [`service_fn`]. -/// -/// The function signature is slightly simpler to write than when not using -/// [`service_fn`] and supports passing in meta data without any extra -/// boilerplate. -fn query( - request: Request>, - count: Arc, -) -> ServiceResult> { - let cnt = count - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| { - Some(if x > 0 { x - 1 } else { 0 }) - }) - .unwrap(); - - // Note: A real service would have application logic here to process - // the request and generate an response. - - let idle_timeout = Duration::from_millis((50 * cnt).into()); - let cmd = ServiceFeedback::Reconfigure { - idle_timeout: Some(idle_timeout), - }; - eprintln!("Setting idle timeout to {idle_timeout:?}"); - - let builder = mk_builder_for_target(); - let answer = mk_answer(&request, builder)?; - Ok(CallResult::new(answer).with_feedback(cmd)) -} - //----------- Example socket trait implementations --------------------------- //--- DoubleListener @@ -671,7 +591,7 @@ async fn main() { SocketAddr::new(IpAddr::from_str("1.1.1.1").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSrService::new(dgram_conn); + let conn_service = ClientTransportToSingleService::new(dgram_conn); qr.add(Name::>::from_str(".").unwrap(), conn_service); // Queries to .com go to 8.8.8.8 @@ -679,7 +599,7 @@ async fn main() { SocketAddr::new(IpAddr::from_str("8.8.8.8").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSrService::new(dgram_conn); + let conn_service = ClientTransportToSingleService::new(dgram_conn); qr.add(Name::>::from_str("com").unwrap(), conn_service); // Queries to .nl go to 9.9.9.9 @@ -687,7 +607,7 @@ async fn main() { SocketAddr::new(IpAddr::from_str("9.9.9.9").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSrService::new(dgram_conn); + let conn_service = ClientTransportToSingleService::new(dgram_conn); qr.add(Name::>::from_str("nl").unwrap(), conn_service); let srv = SingleServiceToService::new(qr); From 2731da8edcae6cbcbc2f7f039337880278158915 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 5 Sep 2024 14:55:28 +0200 Subject: [PATCH 16/38] Docs and cleanup --- examples/query-routing.rs | 4 +--- src/net/server/adapter.rs | 15 +++++++++++---- src/net/server/message.rs | 6 ++++++ src/net/server/qname_router.rs | 25 +++++++++++++++---------- src/net/server/single_service.rs | 24 +++++++++++++++++++++--- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index cf1f1ac36..26a345f74 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -22,9 +22,7 @@ use domain::net::server::middleware::stream::{ MiddlewareStream, PostprocessingStream, }; use domain::net::server::qname_router::QnameRouter; -use domain::net::server::service::{ - CallResult, Service, ServiceResult, -}; +use domain::net::server::service::{CallResult, Service, ServiceResult}; use domain::net::server::single_service::ReplyMessage; use domain::net::server::sock::AsyncAccept; use domain::net::server::stream::StreamServer; diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 7ed8e6d69..f59845f22 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -26,8 +26,13 @@ use std::vec::Vec; /// Provide a [Service] trait for an object that implements [SingleService]. pub struct SingleServiceToService { + /// Service that is wrapped by this object. service: SVC, + + /// Phantom field for RequestOcts. ro_phantom: PhantomData, + + /// Phantom field for CR. cr_phantom: PhantomData, } @@ -72,7 +77,10 @@ where RequestOcts: AsRef<[u8]>, SR: SendRequest>, { + /// The client transport to use. conn: SR, + + /// Phantom data for RequestOcts. _phantom: PhantomData, } @@ -97,8 +105,6 @@ where SR: SendRequest> + Sync, CR: ComposeReply + Send + Sync + 'static, { - type Target = Vec; - fn call( &self, request: RequestNG, @@ -124,7 +130,10 @@ pub struct BoxClientTransportToSingleService where RequestOcts: AsRef<[u8]>, { + /// The client transport to use. conn: Box> + Send + Sync>, + + /// Phantom data for RequestOcts. _phantom: PhantomData, } @@ -149,8 +158,6 @@ where RequestOcts: AsRef<[u8]> + Clone + Debug + Octets + Send + Sync, CR: ComposeReply + Send + Sync + 'static, { - type Target = Vec; - fn call( &self, request: RequestNG, diff --git a/src/net/server/message.rs b/src/net/server/message.rs index e7f06b04e..ca5f2453e 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -1,4 +1,8 @@ //! Support for working with DNS messages in servers. + +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] + use bytes::Bytes; //use core::ops::ControlFlow; use core::time::Duration; @@ -350,6 +354,7 @@ impl> RequestNG { } } + /// Convert a Request to a RequestNG. pub fn from_request(request: Request) -> Self where Octs: Octets + Send + Sync + Unpin, @@ -381,6 +386,7 @@ impl> RequestNG { req } + /// Convert the Request to a Message. pub fn to_request_message(&self) -> Result, Error> where Octs: Clone + Debug + Octets + Send + Sync, diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 233098fba..04f328982 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -1,4 +1,7 @@ -// Qname Router +//! This module provides an example query router using the Qname field. + +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] use super::message::RequestNG; use super::single_service::SingleService; @@ -10,31 +13,35 @@ use std::future::Future; use std::pin::Pin; use std::vec::Vec; +/// A service that routes requests to other services based on the Qname in the +/// request. pub struct QnameRouter { + /// List of names and services for routing requests. list: Vec>, } +/// Element in the name space for the Qname router. struct Element { + /// Name to match for this element. name: Name, - service: Box< - dyn SingleService> + Send + Sync, - >, + + /// Service to call for this element. + service: Box + Send + Sync>, } impl QnameRouter { + /// Create a new empty router. pub fn new() -> Self { Self { list: Vec::new() } } + /// Add a name and service to the router. pub fn add(&mut self, name: TN, service: SVC) where Octs: FromBuilder, ::Builder: EmptyBuilder, TN: ToName, - SVC: SingleService> - + Send - + Sync - + 'static, + SVC: SingleService + Send + Sync + 'static, { let el = Element { name: name.try_to_name().ok().unwrap(), @@ -55,8 +62,6 @@ impl SingleService where Octs: AsRef<[u8]>, { - type Target = (); - fn call( &self, request: RequestNG, diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index d3d108cd7..47febd966 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -1,4 +1,13 @@ -// Single reply service +//! This module provides the as simple service interface for services that +//! provide (at most) a single response. +//! +//! The simple service is represented by the trait [SingleService]. +//! Additionally, this module provide a new trait [ComposeReply] that +//! helps generating reply messages and [ReplyMessage] an implementation of +//! ComposeReply. + +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] use super::message::RequestNG; use crate::base::message_builder::AdditionalBuilder; @@ -12,9 +21,11 @@ use std::future::Future; use std::pin::Pin; use std::vec::Vec; +/// Trait for a service that results in a single response. pub trait SingleService { - type Target; - + /// Call the service with a request message. + /// + /// The service returns a boxed future. fn call( &self, request: RequestNG, @@ -23,16 +34,22 @@ pub trait SingleService { RequestOcts: AsRef<[u8]> + Octets; } +/// Trait for creating a reply message. pub trait ComposeReply { + /// Start a reply from an existing message. fn from_message(msg: &Message) -> Self where Octs: AsRef<[u8]>; + + /// Return the reply message as an AdditionalBuilder with a StreamTarget. fn additional_builder_stream_target( &self, ) -> AdditionalBuilder>>; } +/// Record changes to a Message for generating a reply message. pub struct ReplyMessage { + /// Field to store the underlying Message. msg: Message>, /// The OPT record to add if required. @@ -40,6 +57,7 @@ pub struct ReplyMessage { } impl ReplyMessage { + /// Add an option that is to be included in the final message. fn add_opt( &mut self, opt: &impl ComposeOptData, From 21c930466a9fe3623d955c0e7d89ac2110aa1d29 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 5 Sep 2024 16:11:02 +0200 Subject: [PATCH 17/38] Cleanup use. --- src/net/server/message.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/net/server/message.rs b/src/net/server/message.rs index ca5f2453e..d03087e19 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -4,32 +4,19 @@ #![warn(clippy::missing_docs_in_private_items)] use bytes::Bytes; -//use core::ops::ControlFlow; use core::time::Duration; use std::fmt::Debug; -//use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::vec::Vec; use tokio::time::Instant; use crate::base::opt::AllOptData; -use crate::base::Message; -use crate::base::Name; +use crate::base::{Message, Name}; use crate::dep::octseq::Octets; -//use crate::base::opt::OptRecord; -//use crate::base::opt::subnet::ClientSubnet; -//use crate::dep::octseq::OctetsFrom; use crate::net::client::request::ComposeRequest; use crate::net::client::request::{Error, RequestMessage}; -//use crate::net::server::buf::BufSource; -//use crate::net::server::metrics::ServerMetrics; -//use crate::net::server::middleware::chain::MiddlewareChain; - -//use super::service::{Service, }; -//use super::util::start_reply; -//use crate::base::wire::Composer; //------------ UdpTransportContext ------------------------------------------- From fb2f7ecb105cc9cf0615ee2d66eb53f1a111fbac Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 5 Sep 2024 16:20:00 +0200 Subject: [PATCH 18/38] Document RequestNG --- src/net/server/message.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/net/server/message.rs b/src/net/server/message.rs index d03087e19..6fc16289f 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -298,6 +298,9 @@ where //------------ RequestNG ------------------------------------------------------ +/// RequestNG is very similar to Request. The main changes are: +/// * to_request_message to convert to a [RequestMessage]. +/// /// A DNS message with additional properties describing its context. /// /// DNS messages don't exist in isolation, they are received from somewhere or From 2c779d4e8fc0af40daea40c1e2c95f21ab3504c0 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 5 Sep 2024 16:23:49 +0200 Subject: [PATCH 19/38] Remove some dead code. --- src/net/server/single_service.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 47febd966..2bf21f0e7 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -151,28 +151,6 @@ impl ComposeReply for ReplyMessage { for rr in source { let rr = rr.unwrap(); if rr.rtype() == Rtype::OPT { - /* - let rr = rr.into_record::>().unwrap().unwrap(); - let opt_record = OptRecord::from_record(rr); - target - .opt(|newopt| { - newopt - .set_udp_payload_size(opt_record.udp_payload_size()); - newopt.set_version(opt_record.version()); - newopt.set_dnssec_ok(opt_record.dnssec_ok()); - - // Copy the transitive options that we support. - for option in opt_record.opt().iter::>() - { - let option = option.unwrap(); - if let AllOptData::ExtendedError(_) = option { - newopt.push(&option).unwrap(); - } - } - Ok(()) - }) - .unwrap(); - */ } else { let rr = rr .into_record::>>() From c2bac0321d4eed68e724e1b06cb6b7b8d1e0fea7 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Thu, 10 Oct 2024 14:16:18 +0200 Subject: [PATCH 20/38] Improvements by Ximon --- src/net/server/adapter.rs | 23 +++--- src/net/server/message.rs | 119 ++++--------------------------- src/net/server/qname_router.rs | 6 +- src/net/server/single_service.rs | 6 +- 4 files changed, 29 insertions(+), 125 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index f59845f22..3e4703a84 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -11,7 +11,7 @@ #![warn(missing_docs)] #![warn(clippy::missing_docs_in_private_items)] -use super::message::{Request, RequestNG}; +use super::message::Request; use super::service::{CallResult, Service, ServiceResult}; use super::single_service::{ComposeReply, SingleService}; use crate::dep::octseq::Octets; @@ -29,11 +29,8 @@ pub struct SingleServiceToService { /// Service that is wrapped by this object. service: SVC, - /// Phantom field for RequestOcts. - ro_phantom: PhantomData, - - /// Phantom field for CR. - cr_phantom: PhantomData, + /// Phantom field for RequestOcts and CR. + _phantom: PhantomData<(RequestOcts, CR)>, } impl SingleServiceToService { @@ -41,8 +38,7 @@ impl SingleServiceToService { pub fn new(service: SVC) -> Self { Self { service, - ro_phantom: PhantomData, - cr_phantom: PhantomData, + _phantom: PhantomData, } } } @@ -59,8 +55,7 @@ where type Future = Pin + Send>>; fn call(&self, request: Request) -> Self::Future { - let req = RequestNG::from_request(request); - let fut = self.service.call(req); + let fut = self.service.call(request); let fut = async move { let reply = fut.await.unwrap(); let abs = reply.additional_builder_stream_target(); @@ -107,12 +102,12 @@ where { fn call( &self, - request: RequestNG, + request: Request, ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]>, { - let req = match request.to_request_message() { + let req = match request.try_into() { Ok(req) => req, Err(e) => return Box::pin(ready(Err(e))), }; @@ -160,12 +155,12 @@ where { fn call( &self, - request: RequestNG, + request: Request, ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]>, { - let req = match request.to_request_message() { + let req = match request.try_into() { Ok(req) => req, Err(e) => return Box::pin(ready(Err(e))), }; diff --git a/src/net/server/message.rs b/src/net/server/message.rs index 6fc16289f..8fa50e485 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -296,142 +296,49 @@ where } } -//------------ RequestNG ------------------------------------------------------ +//--- TryFrom> for RequestMessage> -/// RequestNG is very similar to Request. The main changes are: -/// * to_request_message to convert to a [RequestMessage]. -/// -/// A DNS message with additional properties describing its context. -/// -/// DNS messages don't exist in isolation, they are received from somewhere or -/// created by something. This type wraps a message with additional context -/// about its origins so that decisions can be taken based not just on the -/// message itself but also on the circumstances surrounding its creation and -/// delivery. -#[derive(Debug)] -pub struct RequestNG> { - /// The network address of the connected client. - client_addr: std::net::SocketAddr, - - /// The instant when the request was received. - received_at: Instant, - - /// The message that was received. - message: Arc>, - - /// Properties of the request specific to the server and transport - /// protocol via which it was received. - transport_specific: TransportSpecificContext, - - /// Options that should be used upstream in providing the service. - opt: Vec>>, -} - -impl> RequestNG { - /// Creates a new request wrapper around a message along with its context. - pub fn new( - client_addr: std::net::SocketAddr, - received_at: Instant, - message: Message, - transport_specific: TransportSpecificContext, - ) -> Self { - Self { - client_addr, - received_at, - message: Arc::new(message), - transport_specific, - opt: Vec::new(), - } - } - - /// Convert a Request to a RequestNG. - pub fn from_request(request: Request) -> Self - where - Octs: Octets + Send + Sync + Unpin, - { - let mut req = Self { - client_addr: request.client_addr, - received_at: request.received_at, - message: request.message, - transport_specific: request.transport_specific, - opt: Vec::new(), - }; +impl TryFrom> + for RequestMessage +{ + type Error = Error; + fn try_from(req: Request) -> Result { // Copy the ECS option from the message. This is just an example, // there should be a separate plugin that deals with ECS. // We want the ECS options in Bytes. No clue how to do this. Just // convert the message to Bytes and use that. + let mut extra_opts: Vec>> = vec![]; + let bytes = Bytes::copy_from_slice(req.message.as_slice()); let bytes_msg = Message::from_octets(bytes).unwrap(); if let Some(optrec) = bytes_msg.opt() { for opt in optrec.opt().iter::>() { let opt = opt.unwrap(); if let AllOptData::ClientSubnet(_ecs) = opt { - req.opt.push(opt); + extra_opts.push(opt); } } } - req - } - - /// Convert the Request to a Message. - pub fn to_request_message(&self) -> Result, Error> - where - Octs: Clone + Debug + Octets + Send + Sync, - { // We need to make a copy of message. Somehow we can't use the // message in the Arc directly. - let msg = - Message::from_octets(self.message.as_octets().clone()).unwrap(); + let set_do = dnssec_ok(&req.message); + let msg = Message::from_octets(req.message.as_octets().clone()).unwrap(); let mut reqmsg = RequestMessage::new(msg)?; // Copy DO bit - if dnssec_ok(&self.message) { + if set_do { reqmsg.set_dnssec_ok(true); } // Copy options - for opt in &self.opt { + for opt in &extra_opts { reqmsg.add_opt(opt).unwrap(); } Ok(reqmsg) } - - /// When was this message received? - pub fn received_at(&self) -> Instant { - self.received_at - } - - /// Get a reference to the transport specific context - pub fn transport_ctx(&self) -> &TransportSpecificContext { - &self.transport_specific - } - - /// From which IP address and port number was this message received? - pub fn client_addr(&self) -> std::net::SocketAddr { - self.client_addr - } - - /// Read access to the inner message - pub fn message(&self) -> &Arc> { - &self.message - } -} - -//--- Clone - -impl> Clone for RequestNG { - fn clone(&self) -> Self { - Self { - client_addr: self.client_addr, - received_at: self.received_at, - message: Arc::clone(&self.message), - transport_specific: self.transport_specific.clone(), - opt: self.opt.clone(), - } - } } /// Return whether the DO flag is set. This should move to Message. diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 04f328982..aa1fdee5b 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -3,7 +3,7 @@ #![warn(missing_docs)] #![warn(clippy::missing_docs_in_private_items)] -use super::message::RequestNG; +use super::message::Request; use super::single_service::SingleService; use crate::base::{Name, ToName}; use crate::dep::octseq::{EmptyBuilder, FromBuilder, Octets}; @@ -41,6 +41,7 @@ impl QnameRouter { Octs: FromBuilder, ::Builder: EmptyBuilder, TN: ToName, + RequestOcts: Send + Sync, SVC: SingleService + Send + Sync + 'static, { let el = Element { @@ -61,10 +62,11 @@ impl SingleService for QnameRouter where Octs: AsRef<[u8]>, + RequestOcts: Send + Sync + Unpin, { fn call( &self, - request: RequestNG, + request: Request, ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets, diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 2bf21f0e7..6aabb1c0e 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -9,7 +9,7 @@ #![warn(missing_docs)] #![warn(clippy::missing_docs_in_private_items)] -use super::message::RequestNG; +use super::message::Request; use crate::base::message_builder::AdditionalBuilder; use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord}; use crate::base::{Message, MessageBuilder, ParsedName, Rtype, StreamTarget}; @@ -22,13 +22,13 @@ use std::pin::Pin; use std::vec::Vec; /// Trait for a service that results in a single response. -pub trait SingleService { +pub trait SingleService { /// Call the service with a request message. /// /// The service returns a boxed future. fn call( &self, - request: RequestNG, + request: Request, ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets; From 9d4ae49c061893b0b3e13a535deb4b4db0514408 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 11 Oct 2024 15:06:26 +0200 Subject: [PATCH 21/38] Docs --- src/net/server/mod.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index 24c3f5387..9e3ca32dc 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -13,6 +13,15 @@ //! DNS server that answers requests based on the application logic you //! specify. //! +//! In addtion, this module provides a less complex service interface called +//! [SingleService][`single_service::SingleService`]. This interface supports +//! only a single response per request. +//! In other words, it does not support the AXFR and IXFR requests. +//! Adaptors are avaiable to connect SingleServer to [`Service`] and to the +//! [Client][`crate::net::client`] transports. See the +//! Section [Single Service][crate::net::server#single-service] for +//! more details. +//! //! # Architecture //! //! A layered stack of components is responsible for handling incoming @@ -173,6 +182,46 @@ //! https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html //! [`tokio::net::UdpSocket`]: //! https://docs.rs/tokio/latest/tokio/net/struct.UdpSocket.html +//! +//! # Single Service +//! +//! The [SingleService][single_service::SingleService] trait has a single +//! method [call()][single_service::SingleService::call()] that takes a +//! [Request][message::Request] and returns a Future that results in +//! either an error or a reply. +//! To assist building reply messages there is the trait +//! [ComposeReply][single_service::ComposeReply]. +//! The [ComposeReply][single_service::ComposeReply] trait is implemented by +//! [ReplyMessage][single_service::ReplyMessage] +//! +//! To assist in connecting [SingleService][single_service::SingleService] +//! to the rest of the ecosystem, there are three adapters: +//! 1) The first adapter, +//! [SingleServiceToService][adapter::SingleServiceToService] implements +//! [Service][service::Service] for +//! [SingleService][single_service::SingleService]. This allows any +//! object that implements [SingleService][single_service::SingleService] +//! to connect to a place where [Service][service::Service] is required. +//! 2) The second adapter, +//! [ClientTransportToSingleService][adapter::ClientTransportToSingleService] +//! implements [SingleService][single_service::SingleService] for an +//! object that implements +//! [SendRequest][crate::net::client::request::SendRequest]. This +//! allows any [Client][crate::net::client] transport connection to be +//! used as a [SingleService][single_service::SingleService]. +//! 3) The third adapter, +//! [BoxClientTransportToSingleService][adapter::BoxClientTransportToSingleService] +//! is similar to the second one, except that it implements +//! [SingleService][single_service::SingleService] for a boxed +//! [SendRequest][crate::net::client::request::SendRequest] trait object. +//! +//! This module provides a simple query router called +//! [QnameRouter][qname_router::QnameRouter]. This router uses the query +//! name to decide with upstream [SingleService][single_service::SingleService] +//! has to handle the request. +//! This router is deliberately kept very simple. It is assumed that +//! applications that need more complex routers implement them themselves +//! in the application. #![cfg(feature = "unstable-server-transport")] #![cfg_attr(docsrs, doc(cfg(feature = "unstable-server-transport")))] From df74b3dcf0ff903ac0cae69b770f8dec1d4148c5 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 11 Oct 2024 16:47:35 +0200 Subject: [PATCH 22/38] Improved example. --- examples/query-routing.rs | 841 ++++---------------------------------- 1 file changed, 69 insertions(+), 772 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 26a345f74..d004f3371 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -1,526 +1,28 @@ -use core::fmt; -use core::future::{ready, Future, Ready}; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::{Context, Poll}; -use core::time::Duration; -use domain::base::iana::{Class, Rcode}; -use domain::base::message_builder::{AdditionalBuilder, PushError}; -use domain::base::wire::Composer; -use domain::base::{MessageBuilder, Name, Rtype, Serial, StreamTarget, Ttl}; -use domain::net::client::dgram as client_dgram; -use domain::net::client::protocol::UdpConnect; +use domain::base::Name; +use domain::net::client::protocol::{TcpConnect, UdpConnect}; +use domain::net::client::{dgram_stream, redundant}; use domain::net::server::adapter::{ ClientTransportToSingleService, SingleServiceToService, }; use domain::net::server::buf::VecBufSource; use domain::net::server::dgram::DgramServer; -use domain::net::server::message::Request; -use domain::net::server::middleware::cookies::CookiesMiddlewareSvc; -use domain::net::server::middleware::edns::EdnsMiddlewareSvc; use domain::net::server::middleware::mandatory::MandatoryMiddlewareSvc; -use domain::net::server::middleware::stream::{ - MiddlewareStream, PostprocessingStream, -}; use domain::net::server::qname_router::QnameRouter; -use domain::net::server::service::{CallResult, Service, ServiceResult}; use domain::net::server::single_service::ReplyMessage; -use domain::net::server::sock::AsyncAccept; use domain::net::server::stream::StreamServer; -use domain::net::server::util::mk_builder_for_target; -use domain::rdata::{Soa, A}; -use futures::channel::mpsc::unbounded; -use futures::stream::{once, Empty, Once, Stream}; -use octseq::{FreezeBuilder, Octets}; -use std::fs::File; -use std::io; -use std::io::BufReader; use std::net::{IpAddr, SocketAddr}; -use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; -use std::sync::RwLock; use std::vec::Vec; -use tokio::net::{TcpListener, TcpSocket, TcpStream, UdpSocket}; -use tokio::time::Instant; -use tokio_rustls::{rustls, TlsAcceptor}; -use tokio_tfo::{TfoListener, TfoStream}; +use tokio::net::{TcpSocket, UdpSocket}; use tracing_subscriber::EnvFilter; -//----------- mk_answer() ---------------------------------------------------- - -// Helper fn to create a dummy response to send back to the client -fn mk_answer( - msg: &Request>, - builder: MessageBuilder>, -) -> Result>, PushError> -where - Target: Octets + Composer + FreezeBuilder, - ::AppendError: fmt::Debug, -{ - let mut answer = - builder.start_answer(msg.message(), Rcode::NOERROR).unwrap(); - answer.push(( - Name::root_ref(), - Class::IN, - 86400, - A::from_octets(192, 0, 2, 1), - ))?; - Ok(answer.additional()) -} - -fn mk_soa_answer( - msg: &Request>, - builder: MessageBuilder>, -) -> Result>, PushError> -where - Target: Octets + Composer + FreezeBuilder, - ::AppendError: fmt::Debug, -{ - let mname: Name> = "a.root-servers.net".parse().unwrap(); - let rname = "nstld.verisign-grs.com".parse().unwrap(); - let mut answer = - builder.start_answer(msg.message(), Rcode::NOERROR).unwrap(); - answer.push(( - Name::root_slice(), - 86390, - Soa::new( - mname, - rname, - Serial(2020081701), - Ttl::from_secs(1800), - Ttl::from_secs(900), - Ttl::from_secs(604800), - Ttl::from_secs(86400), - ), - ))?; - Ok(answer.additional()) -} - -//----------- Example Service trait implementations -------------------------- - -//--- MySingleResultService - -struct MySingleResultService; - -/// This example shows how to implement the [`Service`] trait directly. -/// -/// By implementing the trait directly you can do async calls with .await by -/// returning an async block, and can control the type of stream used and how -/// and when it gets populated. Neither are possible if implementing a service -/// via a simple compatible function signature or via service_fn, examples of -/// which can be seen below. -/// -/// For readability this example uses nonsensical future and stream types, -/// nonsensical because the future doesn't do any waiting and the stream -/// doesn't do any streaming. See the example below for a more complex case. -/// -/// See [`query`] and [`name_to_ip`] for ways of implementing the [`Service`] -/// trait for a function instead of a struct. -impl Service> for MySingleResultService { - type Target = Vec; - type Stream = Once>>; - type Future = Ready; - - fn call(&self, request: Request>) -> Self::Future { - let builder = mk_builder_for_target(); - let additional = mk_answer(&request, builder).unwrap(); - let item = Ok(CallResult::new(additional)); - ready(once(ready(item))) - } -} - -//--- MyAsyncStreamingService - -struct MyAsyncStreamingService; - -/// This example also shows how to implement the [`Service`] trait directly. -/// -/// It implements a very simplistic dummy AXFR responder which can be tested -/// using `dig AXFR `. -/// -/// Unlike the simpler example above which returns a fixed type of future and -/// stream which are neither waiting nor streaming, this example goes to the -/// other extreme of returning future and stream types which are determined at -/// runtime (and thus involve Box'ing). -/// -/// There is a middle ground not shown here whereby you return concrete Future -/// and/or Stream implementations that actually wait and/or stream, e.g. -/// making the Stream type be UnboundedReceiver instead of Pin>. -impl Service> for MyAsyncStreamingService { - type Target = Vec; - type Stream = - Pin> + Send>>; - type Future = Pin + Send>>; - - fn call(&self, request: Request>) -> Self::Future { - Box::pin(async move { - if !matches!( - request - .message() - .sole_question() - .map(|q| q.qtype() == Rtype::AXFR), - Ok(true) - ) { - let builder = mk_builder_for_target(); - let additional = builder - .start_answer(request.message(), Rcode::NOTIMP) - .unwrap() - .additional(); - let item = Ok(CallResult::new(additional)); - let immediate_result = once(ready(item)); - return Box::pin(immediate_result) as Self::Stream; - } - - let (sender, receiver) = unbounded(); - let cloned_sender = sender.clone(); - - tokio::spawn(async move { - // Dummy AXFR response: SOA, record, SOA - tokio::time::sleep(Duration::from_millis(100)).await; - let builder = mk_builder_for_target(); - let additional = mk_soa_answer(&request, builder).unwrap(); - let item = Ok(CallResult::new(additional)); - cloned_sender.unbounded_send(item).unwrap(); - - tokio::time::sleep(Duration::from_millis(100)).await; - let builder = mk_builder_for_target(); - let additional = mk_answer(&request, builder).unwrap(); - let item = Ok(CallResult::new(additional)); - cloned_sender.unbounded_send(item).unwrap(); - - tokio::time::sleep(Duration::from_millis(100)).await; - let builder = mk_builder_for_target(); - let additional = mk_soa_answer(&request, builder).unwrap(); - let item = Ok(CallResult::new(additional)); - cloned_sender.unbounded_send(item).unwrap(); - }); - - Box::pin(receiver) as Self::Stream - }) - } -} - -//----------- Example socket trait implementations --------------------------- - -//--- DoubleListener - -struct DoubleListener { - a: TcpListener, - b: TcpListener, - alt: AtomicBool, -} - -impl DoubleListener { - fn new(a: TcpListener, b: TcpListener) -> Self { - let alt = AtomicBool::new(false); - Self { a, b, alt } - } -} - -/// Combine two streams into one by interleaving the output of both as it is -/// produced. -impl AsyncAccept for DoubleListener { - type Error = io::Error; - type StreamType = TcpStream; - type Future = Ready>; - - fn poll_accept( - &self, - cx: &mut Context, - ) -> Poll> { - let (x, y) = match self.alt.fetch_xor(true, Ordering::SeqCst) { - false => (&self.a, &self.b), - true => (&self.b, &self.a), - }; - - match TcpListener::poll_accept(x, cx) - .map(|res| res.map(|(stream, addr)| (ready(Ok(stream)), addr))) - { - Poll::Ready(res) => Poll::Ready(res), - Poll::Pending => TcpListener::poll_accept(y, cx).map(|res| { - res.map(|(stream, addr)| (ready(Ok(stream)), addr)) - }), - } - } -} - -//--- LocalTfoListener - -struct LocalTfoListener(TfoListener); - -impl std::ops::DerefMut for LocalTfoListener { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl std::ops::Deref for LocalTfoListener { - type Target = TfoListener; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsyncAccept for LocalTfoListener { - type Error = io::Error; - type StreamType = TfoStream; - type Future = Ready>; - - fn poll_accept( - &self, - cx: &mut Context, - ) -> Poll> { - TfoListener::poll_accept(self, cx) - .map(|res| res.map(|(stream, addr)| (ready(Ok(stream)), addr))) - } -} - -//--- BufferedTcpListener - -struct BufferedTcpListener(TcpListener); - -impl std::ops::DerefMut for BufferedTcpListener { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl std::ops::Deref for BufferedTcpListener { - type Target = TcpListener; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsyncAccept for BufferedTcpListener { - type Error = io::Error; - type StreamType = tokio::io::BufReader; - type Future = Ready>; - - fn poll_accept( - &self, - cx: &mut Context, - ) -> Poll> { - match TcpListener::poll_accept(self, cx) { - Poll::Ready(Ok((stream, addr))) => { - let stream = tokio::io::BufReader::new(stream); - Poll::Ready(Ok((ready(Ok(stream)), addr))) - } - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => Poll::Pending, - } - } -} - -//--- RustlsTcpListener - -pub struct RustlsTcpListener { - listener: TcpListener, - acceptor: tokio_rustls::TlsAcceptor, -} - -impl RustlsTcpListener { - pub fn new( - listener: TcpListener, - acceptor: tokio_rustls::TlsAcceptor, - ) -> Self { - Self { listener, acceptor } - } -} - -impl AsyncAccept for RustlsTcpListener { - type Error = io::Error; - type StreamType = tokio_rustls::server::TlsStream; - type Future = tokio_rustls::Accept; - - #[allow(clippy::type_complexity)] - fn poll_accept( - &self, - cx: &mut Context, - ) -> Poll> { - TcpListener::poll_accept(&self.listener, cx).map(|res| { - res.map(|(stream, addr)| (self.acceptor.accept(stream), addr)) - }) - } -} - -//----------- CustomMiddleware ----------------------------------------------- - -#[derive(Default)] -pub struct Stats { - slowest_req: Option, - fastest_req: Option, - num_req_bytes: u32, - num_resp_bytes: u32, - num_reqs: u32, - num_ipv4: u32, - num_ipv6: u32, - num_udp: u32, -} - -impl std::fmt::Display for Stats { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "# Reqs={} [UDP={}, IPv4={}, IPv6={}] Bytes [rx={}, tx={}] Speed [fastest={}, slowest={}]", - self.num_reqs, - self.num_udp, - self.num_ipv4, - self.num_ipv6, - self.num_req_bytes, - self.num_resp_bytes, - self.fastest_req.map(|v| format!("{}μs", v.as_micros())).unwrap_or_else(|| "-".to_string()), - self.slowest_req.map(|v| format!("{}ms", v.as_millis())).unwrap_or_else(|| "-".to_string()), - ) - } -} - -pub struct StatsMiddlewareSvc { - svc: Svc, - stats: Arc>, -} - -impl StatsMiddlewareSvc { - /// Creates an instance of this processor. - #[must_use] - pub fn new(svc: Svc, stats: Arc>) -> Self { - Self { svc, stats } - } - - fn preprocess(&self, request: &Request) - where - RequestOctets: Octets + Send + Sync + Unpin, - { - let mut stats = self.stats.write().unwrap(); - - stats.num_reqs += 1; - stats.num_req_bytes += request.message().as_slice().len() as u32; - - if request.transport_ctx().is_udp() { - stats.num_udp += 1; - } - - if request.client_addr().is_ipv4() { - stats.num_ipv4 += 1; - } else { - stats.num_ipv6 += 1; - } - } - - fn postprocess( - request: &Request, - response: &AdditionalBuilder>, - stats: Arc>, - ) where - RequestOctets: Octets + Send + Sync + Unpin, - Svc: Service, - Svc::Target: AsRef<[u8]>, - { - let duration = Instant::now().duration_since(request.received_at()); - let mut stats = stats.write().unwrap(); - - stats.num_resp_bytes += response.as_slice().len() as u32; - - if duration < stats.fastest_req.unwrap_or(Duration::MAX) { - stats.fastest_req = Some(duration); - } - if duration > stats.slowest_req.unwrap_or(Duration::ZERO) { - stats.slowest_req = Some(duration); - } - } - - fn map_stream_item( - request: Request, - stream_item: ServiceResult, - stats: Arc>, - ) -> ServiceResult - where - RequestOctets: Octets + Send + Sync + Unpin, - Svc: Service, - Svc::Target: AsRef<[u8]>, - { - if let Ok(cr) = &stream_item { - if let Some(response) = cr.response() { - Self::postprocess(&request, response, stats); - } - } - stream_item - } -} - -impl Service for StatsMiddlewareSvc -where - RequestOctets: Octets + Send + Sync + 'static + Unpin, - Svc: Service, - Svc::Target: AsRef<[u8]>, - Svc::Future: Unpin, -{ - type Target = Svc::Target; - type Stream = MiddlewareStream< - Svc::Future, - Svc::Stream, - PostprocessingStream< - RequestOctets, - Svc::Future, - Svc::Stream, - (), - Arc>, - >, - Empty>, - ServiceResult, - >; - type Future = Ready; - - fn call(&self, request: Request) -> Self::Future { - self.preprocess(&request); - let svc_call_fut = self.svc.call(request.clone()); - let map = PostprocessingStream::new( - svc_call_fut, - request, - self.stats.clone(), - Self::map_stream_item, - ); - ready(MiddlewareStream::Map(map)) - } -} - -//------------ build_middleware_chain() -------------------------------------- - -#[allow(clippy::type_complexity)] -fn build_middleware_chain( - svc: Svc, - stats: Arc>, -) -> StatsMiddlewareSvc< - MandatoryMiddlewareSvc< - Vec, - EdnsMiddlewareSvc< - Vec, - CookiesMiddlewareSvc, Svc, ()>, - (), - >, - (), - >, -> { - #[cfg(feature = "siphasher")] - let svc = CookiesMiddlewareSvc::, _, _>::with_random_secret(svc); - let svc = EdnsMiddlewareSvc::, _, _>::new(svc); - let svc = MandatoryMiddlewareSvc::, _, _>::new(svc); - StatsMiddlewareSvc::new(svc, stats.clone()) -} - //----------- main() --------------------------------------------------------- #[tokio::main(flavor = "multi_thread")] async fn main() { eprintln!("Test with commands such as:"); - eprintln!(" dig +short -4 @127.0.0.1 -p 8053 A 1.2.3.4"); - eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8053 A google.com"); - eprintln!(" dig +short -4 @127.0.0.1 -p 8054 A google.com"); - eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8080 AXFR google.com"); - eprintln!(" dig +short -6 @::1 +tcp -p 8080 AXFR google.com"); - eprintln!(" dig +short -4 @127.0.0.1 +tcp -p 8081 A google.com"); - eprintln!(" dig +short -4 @127.0.0.1 +tls -p 8443 A google.com"); + eprintln!(" dnsi query --server ::1 -p 8053 google.com"); // ----------------------------------------------------------------------- // Setup logging. You can override the log level by setting environment @@ -532,308 +34,103 @@ async fn main() { .try_init() .ok(); - // ----------------------------------------------------------------------- - // Inject a custom statistics middleware service (defined above) at the - // start of each middleware chain constructed below so that it can time - // the request processing time from as early till as late as possible - // (excluding time spent in the servers that receive the requests and send - // the responses). Each chain needs its own copy of the stats middleware - // but they can share a single set of statistic counters. - let stats = Arc::new(RwLock::new(Stats::default())); - - // ----------------------------------------------------------------------- - // Create services with accompanying middleware chains to answer incoming - // requests. - - /* - // 1. MySingleResultService: a struct that implements the `Service` trait - // directly. - let my_svc = Arc::new(build_middleware_chain( - MySingleResultService, - stats.clone(), - )); - - // 2. MyAsyncStreamingService: another struct that implements the - // `Service` trait directly. - let my_async_svc = Arc::new(build_middleware_chain( - MyAsyncStreamingService, - stats.clone(), - )); - - // 2. name_to_ip: a service impl defined as a function compatible with the - // `Service` trait. - let name_into_ip_svc = - Arc::new(build_middleware_chain(name_to_ip, stats.clone())); - - // 3. query: a service impl defined as a function converted to a `Service` - // impl via the `service_fn()` helper function. - // Show that we don't have to use the same middleware with every server by - // creating a separate middleware chain for use just by this server. - let count = Arc::new(AtomicU8::new(5)); - let svc = service_fn(query, count); - let svc = MandatoryMiddlewareSvc::, _>::new(svc); - #[cfg(feature = "siphasher")] - let svc = { - let server_secret = "server12secret34".as_bytes().try_into().unwrap(); - CookiesMiddlewareSvc::, _>::new(svc, server_secret) - }; - let svc = StatsMiddlewareSvc::new(svc, stats.clone()); - let query_svc = Arc::new(svc); - */ // Start building the query router plus upstreams. let mut qr: QnameRouter, Vec, ReplyMessage> = QnameRouter::new(); - // Queries to the root go to 1.1.1.1 + // Queries to the root go to 2606:4700:4700::1111 and 1.1.1.1. + let (redun, transport) = redundant::Connection::new(); + tokio::spawn(transport.run()); + let server_addr = SocketAddr::new( + IpAddr::from_str("2606:4700:4700::1111").unwrap(), + 53, + ); + let udp_connect = UdpConnect::new(server_addr); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); let server_addr = SocketAddr::new(IpAddr::from_str("1.1.1.1").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); - let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSingleService::new(dgram_conn); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); + let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::>::from_str(".").unwrap(), conn_service); - // Queries to .com go to 8.8.8.8 + // Queries to .com go to 2001:4860:4860::8888 and 8.8.8.8. + let (redun, transport) = redundant::Connection::new(); + tokio::spawn(transport.run()); + let server_addr = SocketAddr::new( + IpAddr::from_str("2001:4860:4860::8888").unwrap(), + 53, + ); + let udp_connect = UdpConnect::new(server_addr); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); let server_addr = SocketAddr::new(IpAddr::from_str("8.8.8.8").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); - let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSingleService::new(dgram_conn); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); + let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::>::from_str("com").unwrap(), conn_service); - // Queries to .nl go to 9.9.9.9 + // Queries to .nl go to 2620:fe::9 and 9.9.9.9. + let (redun, transport) = redundant::Connection::new(); + tokio::spawn(transport.run()); + let server_addr = + SocketAddr::new(IpAddr::from_str("2620:fe::9").unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); let server_addr = SocketAddr::new(IpAddr::from_str("9.9.9.9").unwrap(), 53); let udp_connect = UdpConnect::new(server_addr); - let dgram_conn = client_dgram::Connection::new(udp_connect); - let conn_service = ClientTransportToSingleService::new(dgram_conn); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); + let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::>::from_str("nl").unwrap(), conn_service); let srv = SingleServiceToService::new(qr); - let my_svc = Arc::new(build_middleware_chain(srv, stats.clone())); - - // ----------------------------------------------------------------------- - // Run a DNS server on UDP port 8053 on 127.0.0.1 using the name_to_ip - // service defined above and accompanying middleware. Test it like so: - // dig +short -4 @127.0.0.1 -p 8053 A google.com + let srv = MandatoryMiddlewareSvc::, _, _>::new(srv); + let my_svc = Arc::new(srv); - let udpsocket = UdpSocket::bind("127.0.0.1:8053").await.unwrap(); + let udpsocket = UdpSocket::bind("[::1]:8053").await.unwrap(); let buf = Arc::new(VecBufSource); let srv = DgramServer::new(udpsocket, buf.clone(), my_svc.clone()); let udp_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- - // Create an instance of our MyService `Service` impl with accompanying - // middleware. - - // ----------------------------------------------------------------------- - // Run a DNS server on TCP port 8053 on 127.0.0.1. Test it like so: - // dig +short +keepopen +tcp -4 @127.0.0.1 -p 8053 A google.com - let v4socket = TcpSocket::new_v4().unwrap(); - v4socket.set_reuseaddr(true).unwrap(); - v4socket.bind("127.0.0.1:8053".parse().unwrap()).unwrap(); - let v4listener = v4socket.listen(1024).unwrap(); - let buf = Arc::new(VecBufSource); - let srv = StreamServer::new(v4listener, buf.clone(), my_svc.clone()); - let srv = srv.with_pre_connect_hook(|stream| { - // Demonstrate one way without having access to the code that creates - // the socket initially to enable TCP keep alive, - eprintln!("TCP connection detected: enabling socket TCP keepalive."); - - let keep_alive = socket2::TcpKeepalive::new() - .with_time(Duration::from_secs(20)) - .with_interval(Duration::from_secs(20)); - let socket = socket2::SockRef::from(&stream); - socket.set_tcp_keepalive(&keep_alive).unwrap(); - - // Sleep to give us time to run a command like - // `ss -nte` to see the keep-alive is set. It - // shows up in the ss output like this: - // timer:(keepalive,18sec,0) - eprintln!("Waiting for 5 seconds so you can run a command like:"); - eprintln!(" ss -nte | grep 8053 | grep keepalive"); - eprintln!("and see `timer:(keepalive,20sec,0) or similar."); - std::thread::sleep(Duration::from_secs(5)); - }); - - let tcp_join_handle = tokio::spawn(async move { srv.run().await }); - - // ----------------------------------------------------------------------- - // This UDP example sets IP_MTU_DISCOVER via setsockopt(), using the libc - // crate (as the nix crate doesn't support IP_MTU_DISCOVER at the time of - // writing). This example is inspired by: - // - // - https://www.ietf.org/archive/id/draft-ietf-dnsop-avoid-fragmentation-17.html#name-recommendations-for-udp-res - // - https://mailarchive.ietf.org/arch/msg/dnsop/Zy3wbhHephubsy2uJesGeDst4F4/ - // - https://man7.org/linux/man-pages/man7/ip.7.html - // - // Some other good reading on sending faster via UDP with Rust: - // - https://devork.be/blog/2023/11/modern-linux-sockets/ - // - // We could also try the following settings that the Unbound man page - // mentions: - // - SO_RCVBUF - Unbound advises setting so-rcvbuf to 4m on busy - // servers to prevent short request spikes causing - // packet drops, - // - SO_SNDBUF - Unbound advises setting so-sndbuf to 4m on busy - // servers to avoid resource temporarily unavailable - // errors, - // - SO_REUSEPORT - Unbound advises to turn it off at extreme load to - // distribute queries evenly, - // - IP_TRANSPARENT - Allows to bind to non-existent IP addresses that - // are going to exist later on. Unbound uses - // IP_BINDANY on FreeBSD and SO_BINDANY on OpenBSD. - // - IP_FREEBIND - Linux only, similar to IP_TRANSPARENT. Allows to - // bind to IP addresses that are nonlocal or do not - // exist, like when the network interface is down. - // - TCP_MAXSEG - Value lower than common MSS on Ethernet (1220 for - // example) will address path MTU problem. - // - A means to control the value of the Differentiated Services - // Codepoint (DSCP) in the differentiated services field (DS) of the - // outgoing IP packet headers. - #[cfg(target_os = "linux")] - let udp_mtu_join_handle = { - fn setsockopt(socket: libc::c_int, flag: libc::c_int) -> libc::c_int { - unsafe { - libc::setsockopt( - socket, - libc::IPPROTO_UDP, - libc::IP_MTU_DISCOVER, - &flag as *const libc::c_int as *const libc::c_void, - std::mem::size_of_val(&flag) as libc::socklen_t, - ) - } - } - - let udpsocket = UdpSocket::bind("127.0.0.1:8054").await.unwrap(); - let fd = ::as_raw_fd(&udpsocket); - if setsockopt(fd, libc::IP_PMTUDISC_OMIT) == -1 { - eprintln!( - "setsockopt error when setting IP_MTU_DISCOVER to IP_PMTUDISC_OMIT, will retry with IP_PMTUDISC_DONT: {}", - std::io::Error::last_os_error() - ); - - if setsockopt(fd, libc::IP_PMTUDISC_DONT) == -1 { - eprintln!( - "setsockopt error when setting IP_MTU_DISCOVER to IP_PMTUDISC_DONT: {}", - std::io::Error::last_os_error() - ); - } - } - - let srv = DgramServer::new(udpsocket, buf.clone(), my_svc.clone()); - - tokio::spawn(async move { srv.run().await }) - }; - - // ----------------------------------------------------------------------- - // Demonstrate manually binding to two separate IPv4 and IPv6 sockets and - // then listening on both at once using a single server instance. (e.g. - // for on platforms that don't support binding to IPv4 and IPv6 at once - // using a single socket). - let v4socket = TcpSocket::new_v4().unwrap(); - v4socket.set_reuseaddr(true).unwrap(); - v4socket.bind("127.0.0.1:8080".parse().unwrap()).unwrap(); - let v4listener = v4socket.listen(1024).unwrap(); - + // Run a DNS server on TCP port 8053 on ::1. Test it like so: + // dnsi query -t --server 127.0.0.1 -p 8053 google.com let v6socket = TcpSocket::new_v6().unwrap(); v6socket.set_reuseaddr(true).unwrap(); - v6socket.bind("[::1]:8080".parse().unwrap()).unwrap(); + v6socket.bind("[::1]:8053".parse().unwrap()).unwrap(); let v6listener = v6socket.listen(1024).unwrap(); - - let listener = DoubleListener::new(v4listener, v6listener); - let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); - let double_tcp_join_handle = tokio::spawn(async move { srv.run().await }); - - // ----------------------------------------------------------------------- - // Demonstrate listening with TCP Fast Open enabled (via the tokio-tfo - // crate). On Linux strace can be used to show that the socket options are - // indeed set as expected, e.g.: - // - // > strace -e trace=setsockopt cargo run --example serve \ - // --features serve,tokio-tfo --release - // Finished release [optimized] target(s) in 0.12s - // Running `target/release/examples/serve` - // setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 setsockopt(7, - // SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 setsockopt(8, SOL_SOCKET, - // SO_REUSEADDR, [1], 4) = 0 setsockopt(8, SOL_TCP, TCP_FASTOPEN, - // [1024], 4) = 0 - - let listener = TfoListener::bind("127.0.0.1:8081".parse().unwrap()) - .await - .unwrap(); - let listener = LocalTfoListener(listener); - let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); - let tfo_join_handle = tokio::spawn(async move { srv.run().await }); - - // ----------------------------------------------------------------------- - // Demonstrate using a simple function instead of a struct as the service - // Note that this service reduces its connection timeout on each subsequent - // query handled on the same connection, so try someting like this and you - // should see later queries getting communication errors: - // - // > dig +short +keepopen +tcp -4 @127.0.0.1 -p 8082 A google.com A \ - // google.com A google.com A google.com A google.com A google.com \ - // A google.com - // .. - // 192.0.2.1 - // 192.0.2.1 - // .. - // ;; communications error to 127.0.0.1#8082: end of file - // - // This example also demonstrates wrapping the TcpStream inside a - // BufReader to minimize overhead from system I/O calls. - - let listener = TcpListener::bind("127.0.0.1:8082").await.unwrap(); - let listener = BufferedTcpListener(listener); - let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); - let fn_join_handle = tokio::spawn(async move { srv.run().await }); - - // ----------------------------------------------------------------------- - // Demonstrate using a TLS secured TCP DNS server. - - // Credit: The sample.(pem|rsa) files used here were taken from - // https://github.com/rustls/hyper-rustls/blob/main/examples/ - let certs = rustls_pemfile::certs(&mut BufReader::new( - File::open("examples/sample.pem").unwrap(), - )) - .collect::, _>>() - .unwrap(); - let key = rustls_pemfile::private_key(&mut BufReader::new( - File::open("examples/sample.rsa").unwrap(), - )) - .unwrap() - .unwrap(); - - let config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(certs, key) - .unwrap(); - let acceptor = TlsAcceptor::from(Arc::new(config)); - let listener = TcpListener::bind("127.0.0.1:8443").await.unwrap(); - let listener = RustlsTcpListener::new(listener, acceptor); - let srv = StreamServer::new(listener, buf.clone(), my_svc.clone()); - - let tls_join_handle = tokio::spawn(async move { srv.run().await }); - - // ----------------------------------------------------------------------- - // Print statistics periodically - tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(5)); - loop { - interval.tick().await; - println!("Statistics report: {}", stats.read().unwrap()); - } - }); + let buf = Arc::new(VecBufSource); + let srv = StreamServer::new(v6listener, buf.clone(), my_svc.clone()); + let tcp_join_handle = tokio::spawn(async move { srv.run().await }); // ----------------------------------------------------------------------- // Keep the services running in the background udp_join_handle.await.unwrap(); tcp_join_handle.await.unwrap(); - #[cfg(target_os = "linux")] - udp_mtu_join_handle.await.unwrap(); - double_tcp_join_handle.await.unwrap(); - tfo_join_handle.await.unwrap(); - fn_join_handle.await.unwrap(); - tls_join_handle.await.unwrap(); } From 27f213c46e97186991a682343cf0000ae1284def Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 11 Oct 2024 16:48:19 +0200 Subject: [PATCH 23/38] Fmt --- src/net/server/message.rs | 3 ++- src/net/server/mod.rs | 2 +- src/net/server/qname_router.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/net/server/message.rs b/src/net/server/message.rs index 8fa50e485..411476850 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -325,7 +325,8 @@ impl TryFrom> // We need to make a copy of message. Somehow we can't use the // message in the Arc directly. let set_do = dnssec_ok(&req.message); - let msg = Message::from_octets(req.message.as_octets().clone()).unwrap(); + let msg = + Message::from_octets(req.message.as_octets().clone()).unwrap(); let mut reqmsg = RequestMessage::new(msg)?; // Copy DO bit diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index 9e3ca32dc..b467b3b87 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -188,7 +188,7 @@ //! The [SingleService][single_service::SingleService] trait has a single //! method [call()][single_service::SingleService::call()] that takes a //! [Request][message::Request] and returns a Future that results in -//! either an error or a reply. +//! either an error or a reply. //! To assist building reply messages there is the trait //! [ComposeReply][single_service::ComposeReply]. //! The [ComposeReply][single_service::ComposeReply] trait is implemented by diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index aa1fdee5b..040ee3925 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -41,7 +41,7 @@ impl QnameRouter { Octs: FromBuilder, ::Builder: EmptyBuilder, TN: ToName, - RequestOcts: Send + Sync, + RequestOcts: Send + Sync, SVC: SingleService + Send + Sync + 'static, { let el = Element { From c2fd2f4279eb510813bc441431905301eb0e0088 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 11 Oct 2024 16:48:29 +0200 Subject: [PATCH 24/38] Add Debug --- src/net/server/single_service.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 6aabb1c0e..3a0fff176 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -48,6 +48,7 @@ pub trait ComposeReply { } /// Record changes to a Message for generating a reply message. +#[derive(Debug)] pub struct ReplyMessage { /// Field to store the underlying Message. msg: Message>, From 64919fb903236df3d373e678f691f463f2128038 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 14 Oct 2024 11:30:42 +0200 Subject: [PATCH 25/38] Remove unwraps. --- src/net/server/adapter.rs | 29 +++++++++---- src/net/server/single_service.rs | 71 +++++++++++++++++--------------- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 3e4703a84..fa0f3a67b 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -12,7 +12,7 @@ #![warn(clippy::missing_docs_in_private_items)] use super::message::Request; -use super::service::{CallResult, Service, ServiceResult}; +use super::service::{CallResult, Service, ServiceError, ServiceResult}; use super::single_service::{ComposeReply, SingleService}; use crate::dep::octseq::Octets; use crate::net::client::request::{Error, RequestMessage, SendRequest}; @@ -57,8 +57,23 @@ where fn call(&self, request: Request) -> Self::Future { let fut = self.service.call(request); let fut = async move { - let reply = fut.await.unwrap(); - let abs = reply.additional_builder_stream_target(); + let reply = match fut.await { + Ok(reply) => reply, + Err(_) => { + // Every error gets mapped to InternalError. + // Should we add an EDE here? + return once(ready(Err(ServiceError::InternalError))); + } + }; + let abs = match reply.additional_builder_stream_target() { + Ok(reply) => reply, + Err(_) => { + // Every error gets mapped to InternalError. + // There is probably not much we could do here. + // The error results from a bad reply message. + return once(ready(Err(ServiceError::InternalError))); + } + }; once(ready(Ok(CallResult::new(abs)))) }; Box::pin(fut) @@ -113,8 +128,8 @@ where }; let mut gr = self.conn.send_request(req); let fut = async move { - let msg = gr.get_response().await.unwrap(); - Ok(CR::from_message(&msg)) + let msg = gr.get_response().await?; + Ok(CR::from_message(&msg)?) }; Box::pin(fut) } @@ -166,8 +181,8 @@ where }; let mut gr = self.conn.send_request(req); let fut = async move { - let msg = gr.get_response().await.unwrap(); - Ok(CR::from_message(&msg)) + let msg = gr.get_response().await?; + Ok(CR::from_message(&msg)?) }; Box::pin(fut) } diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 3a0fff176..9eaa1a872 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -37,14 +37,15 @@ pub trait SingleService { /// Trait for creating a reply message. pub trait ComposeReply { /// Start a reply from an existing message. - fn from_message(msg: &Message) -> Self + fn from_message(msg: &Message) -> Result where - Octs: AsRef<[u8]>; + Octs: AsRef<[u8]>, + Self: Sized; /// Return the reply message as an AdditionalBuilder with a StreamTarget. fn additional_builder_stream_target( &self, - ) -> AdditionalBuilder>>; + ) -> Result>>, Error>; } /// Record changes to a Message for generating a reply message. @@ -75,12 +76,12 @@ impl ReplyMessage { } impl ComposeReply for ReplyMessage { - fn from_message(msg: &Message) -> Self + fn from_message(msg: &Message) -> Result where Octs: AsRef<[u8]>, { let vec = msg.as_slice().to_vec(); - let msg = Message::from_octets(vec).unwrap(); + let msg = Message::from_octets(vec)?; let mut repl = Self { msg, opt: None }; // As an example, copy any ECS option from the message. @@ -94,27 +95,28 @@ impl ComposeReply for ReplyMessage { opt.set_dnssec_ok(optrec.dnssec_ok()); for opt in optrec.opt().iter::>() { - let opt = opt.unwrap(); + let opt = opt?; if let AllOptData::ClientSubnet(_ecs) = opt { - repl.add_opt(&opt).unwrap(); + repl.add_opt(&opt)?; } if let AllOptData::ExtendedError(ref _ede) = opt { - repl.add_opt(&opt).unwrap(); + repl.add_opt(&opt)?; } } } - repl + Ok(repl) } fn additional_builder_stream_target( &self, - ) -> AdditionalBuilder>> { + ) -> Result>>, Error> { let source = &self.msg; let mut target = MessageBuilder::from_target( - StreamTarget::>::new(Default::default()).unwrap(), + StreamTarget::>::new(Default::default()) + .expect("new StreamTarget should not fail"), ) - .unwrap(); + .expect("new MessageBuilder should not fail"); let header = source.header(); *target.header_mut() = header; @@ -122,48 +124,49 @@ impl ComposeReply for ReplyMessage { let source = source.question(); let mut target = target.additional().builder().question(); for rr in source { - let rr = rr.unwrap(); - target.push(rr).unwrap(); + let rr = rr?; + target.push(rr).expect("push should not fail"); } - let mut source = source.answer().unwrap(); + let mut source = source.answer()?; let mut target = target.answer(); for rr in &mut source { - let rr = rr.unwrap(); + let rr = rr?; let rr = rr - .into_record::>>() - .unwrap() - .unwrap(); - target.push(rr).unwrap(); + .into_record::>>()? + .expect("AllRecordData should not fail"); + target.push(rr).expect("push should not fail"); } - let mut source = source.next_section().unwrap().unwrap(); + let mut source = source + .next_section()? + .expect("authority section should be present"); let mut target = target.authority(); for rr in &mut source { - let rr = rr.unwrap(); + let rr = rr?; let rr = rr - .into_record::>>() - .unwrap() - .unwrap(); - target.push(rr).unwrap(); + .into_record::>>()? + .expect("AllRecordData should not fail"); + target.push(rr).expect("push should not fail"); } - let source = source.next_section().unwrap().unwrap(); + let source = source + .next_section()? + .expect("additional section should be present"); let mut target = target.additional(); for rr in source { - let rr = rr.unwrap(); + let rr = rr?; if rr.rtype() == Rtype::OPT { } else { let rr = rr - .into_record::>>() - .unwrap() - .unwrap(); - target.push(rr).unwrap(); + .into_record::>>()? + .expect("AllRecordData should not fail"); + target.push(rr).expect("push should not fail"); } } if let Some(opt) = self.opt.as_ref() { - target.push(opt.as_record()).unwrap(); + target.push(opt.as_record()).expect("push should not fail"); } - target + Ok(target) } } From 4b147a6a76dbe8c6a73b88711f112f438e5d4125 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 14 Oct 2024 14:12:41 +0200 Subject: [PATCH 26/38] Remove unwraps --- src/net/server/qname_router.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 040ee3925..3858bd41f 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -6,9 +6,12 @@ use super::message::Request; use super::single_service::SingleService; use crate::base::{Name, ToName}; +use crate::dep::octseq::OctetsBuilder; use crate::dep::octseq::{EmptyBuilder, FromBuilder, Octets}; use crate::net::client::request::Error; use std::boxed::Box; +use std::convert::Infallible; +use std::future::ready; use std::future::Future; use std::pin::Pin; use std::vec::Vec; @@ -39,13 +42,14 @@ impl QnameRouter { pub fn add(&mut self, name: TN, service: SVC) where Octs: FromBuilder, - ::Builder: EmptyBuilder, + ::Builder: + EmptyBuilder + OctetsBuilder, TN: ToName, RequestOcts: Send + Sync, SVC: SingleService + Send + Sync + 'static, { let el = Element { - name: name.try_to_name().ok().unwrap(), + name: name.to_name(), service: Box::new(service), }; self.list.push(el); @@ -63,6 +67,7 @@ impl SingleService where Octs: AsRef<[u8]>, RequestOcts: Send + Sync + Unpin, + CR: Send + Sync + 'static, { fn call( &self, @@ -76,15 +81,23 @@ where .question() .into_iter() .next() - .unwrap() - .unwrap(); + .expect("the caller need to make sure that there is question") + .expect("the caller need to make sure that the question can be parsed") + ; let name = question.qname(); - self.list + let el = match self + .list .iter() .filter(|l| name.ends_with(&l.name)) .max_by_key(|l| l.name.label_count()) - .unwrap() - .service - .call(request.clone()) + { + Some(el) => el, + None => { + // Nothing, return InternalError + return Box::pin(ready(Err(Error::NoTransportAvailable))); + } + }; + + el.service.call(request.clone()) } } From 784b5388041ffa7442fdc0ee77592ea38f82435f Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 14 Oct 2024 14:44:20 +0200 Subject: [PATCH 27/38] Remove unwraps --- src/net/server/message.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/net/server/message.rs b/src/net/server/message.rs index 411476850..dd3732b67 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -312,10 +312,10 @@ impl TryFrom> let mut extra_opts: Vec>> = vec![]; let bytes = Bytes::copy_from_slice(req.message.as_slice()); - let bytes_msg = Message::from_octets(bytes).unwrap(); + let bytes_msg = Message::from_octets(bytes)?; if let Some(optrec) = bytes_msg.opt() { for opt in optrec.opt().iter::>() { - let opt = opt.unwrap(); + let opt = opt?; if let AllOptData::ClientSubnet(_ecs) = opt { extra_opts.push(opt); } @@ -325,8 +325,7 @@ impl TryFrom> // We need to make a copy of message. Somehow we can't use the // message in the Arc directly. let set_do = dnssec_ok(&req.message); - let msg = - Message::from_octets(req.message.as_octets().clone()).unwrap(); + let msg = Message::from_octets(req.message.as_octets().clone())?; let mut reqmsg = RequestMessage::new(msg)?; // Copy DO bit @@ -336,7 +335,7 @@ impl TryFrom> // Copy options for opt in &extra_opts { - reqmsg.add_opt(opt).unwrap(); + reqmsg.add_opt(opt)?; } Ok(reqmsg) } From 26df1a76814f177425f2dbe8d3e5af9bfd1e1924 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 14 Oct 2024 15:39:48 +0200 Subject: [PATCH 28/38] Avoid futures --- Cargo.toml | 2 +- src/net/server/adapter.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e62d93632..cf0bf4088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ required-features = ["zonefile", "net", "unstable-client-transport", "unstable-z [[example]] name = "query-routing" -required-features = ["net", "unstable-client-transport", "unstable-server-transport","futures", "tracing-subscriber"] +required-features = ["net", "unstable-client-transport", "unstable-server-transport", "tracing-subscriber"] # This example is commented out because it is difficult, if not impossible, # when including the sqlx dependency, to make the dependency tree compatible diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index fa0f3a67b..4704fada6 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -16,7 +16,7 @@ use super::service::{CallResult, Service, ServiceError, ServiceResult}; use super::single_service::{ComposeReply, SingleService}; use crate::dep::octseq::Octets; use crate::net::client::request::{Error, RequestMessage, SendRequest}; -use futures::stream::{once, Once}; +use futures_util::stream::{once, Once}; use std::boxed::Box; use std::fmt::Debug; use std::future::{ready, Future, Ready}; From d71c76633f006e10ab0266077c7eadd6cf859ea6 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 14 Oct 2024 15:52:31 +0200 Subject: [PATCH 29/38] Remove Unpin bounds --- src/net/server/adapter.rs | 2 +- src/net/server/qname_router.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 4704fada6..5839724b3 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -46,7 +46,7 @@ impl SingleServiceToService { impl Service for SingleServiceToService where - RequestOcts: Octets + Send + Sync + Unpin, + RequestOcts: Octets + Send + Sync, SVC: SingleService, CR: ComposeReply + 'static, { diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 3858bd41f..e52815805 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -66,7 +66,7 @@ impl SingleService for QnameRouter where Octs: AsRef<[u8]>, - RequestOcts: Send + Sync + Unpin, + RequestOcts: Send + Sync, CR: Send + Sync + 'static, { fn call( From 64209563de91d4e0493bb753a61e06c654db3aa2 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 15 Oct 2024 15:49:03 +0200 Subject: [PATCH 30/38] Switch to ServiceError, include EDE to report error. --- src/net/server/adapter.rs | 76 ++++++++++++++++++++++++++++---- src/net/server/message.rs | 6 +-- src/net/server/qname_router.rs | 34 +++++++++----- src/net/server/single_service.rs | 38 ++++++++++------ 4 files changed, 118 insertions(+), 36 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 5839724b3..b40c07ed6 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -14,14 +14,22 @@ use super::message::Request; use super::service::{CallResult, Service, ServiceError, ServiceResult}; use super::single_service::{ComposeReply, SingleService}; +use super::util::mk_error_response; +use crate::base::iana::{ExtendedErrorCode, OptRcode}; +use crate::base::message_builder::AdditionalBuilder; +use crate::base::opt::ExtendedError; +use crate::base::StreamTarget; use crate::dep::octseq::Octets; -use crate::net::client::request::{Error, RequestMessage, SendRequest}; +use crate::net::client::request::{ + ComposeRequest, RequestMessage, SendRequest, +}; use futures_util::stream::{once, Once}; use std::boxed::Box; use std::fmt::Debug; use std::future::{ready, Future, Ready}; use std::marker::PhantomData; use std::pin::Pin; +use std::string::ToString; use std::vec::Vec; /// Provide a [Service] trait for an object that implements [SingleService]. @@ -118,17 +126,42 @@ where fn call( &self, request: Request, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]>, { - let req = match request.try_into() { + let req: RequestMessage = match request.try_into() { Ok(req) => req, - Err(e) => return Box::pin(ready(Err(e))), + Err(_) => { + // Can this fail? Should the request be checked earlier. + // Just return ServFail. + return Box::pin(ready(Err(ServiceError::InternalError))); + } }; + + // Prepare for an error. It is best to borrow req here before we + // pass it to send_request. + let builder: AdditionalBuilder>> = + mk_error_response(&req.to_message().unwrap(), OptRcode::SERVFAIL); + let mut gr = self.conn.send_request(req); let fut = async move { - let msg = gr.get_response().await?; + let msg = match gr.get_response().await { + Ok(msg) => msg, + Err(e) => { + // The request failed. Create a ServFail response and + // add an EDE that describes the error. + let msg = builder.as_message(); + let mut cr = CR::from_message(&msg).unwrap(); + if let Ok(ede) = ExtendedError::>::new_with_str( + ExtendedErrorCode::OTHER, + &e.to_string(), + ) { + cr.add_opt(&ede).unwrap(); + } + return Ok(cr); + } + }; Ok(CR::from_message(&msg)?) }; Box::pin(fut) @@ -171,17 +204,42 @@ where fn call( &self, request: Request, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]>, { - let req = match request.try_into() { + let req: RequestMessage = match request.try_into() { Ok(req) => req, - Err(e) => return Box::pin(ready(Err(e))), + Err(_) => { + // Can this fail? Should the request be checked earlier. + // Just return ServFail. + return Box::pin(ready(Err(ServiceError::InternalError))); + } }; + + // Prepare for an error. It is best to borrow req here before we + // pass it to send_request. + let builder: AdditionalBuilder>> = + mk_error_response(&req.to_message().unwrap(), OptRcode::SERVFAIL); + let mut gr = self.conn.send_request(req); let fut = async move { - let msg = gr.get_response().await?; + let msg = match gr.get_response().await { + Ok(msg) => msg, + Err(e) => { + // The request failed. Create a ServFail response and + // add an EDE that describes the error. + let msg = builder.as_message(); + let mut cr = CR::from_message(&msg).unwrap(); + if let Ok(ede) = ExtendedError::>::new_with_str( + ExtendedErrorCode::OTHER, + &e.to_string(), + ) { + cr.add_opt(&ede).unwrap(); + } + return Ok(cr); + } + }; Ok(CR::from_message(&msg)?) }; Box::pin(fut) diff --git a/src/net/server/message.rs b/src/net/server/message.rs index ff9f65c7f..197ab0718 100644 --- a/src/net/server/message.rs +++ b/src/net/server/message.rs @@ -15,8 +15,8 @@ use tokio::time::Instant; use crate::base::opt::AllOptData; use crate::base::{Message, Name}; use crate::dep::octseq::Octets; -use crate::net::client::request::ComposeRequest; -use crate::net::client::request::{Error, RequestMessage}; +use crate::net::client::request; +use crate::net::client::request::{ComposeRequest, RequestMessage}; //------------ UdpTransportContext ------------------------------------------- @@ -301,7 +301,7 @@ where impl TryFrom> for RequestMessage { - type Error = Error; + type Error = request::Error; fn try_from(req: Request) -> Result { // Copy the ECS option from the message. This is just an example, diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index e52815805..90a4dd185 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -4,15 +4,18 @@ #![warn(clippy::missing_docs_in_private_items)] use super::message::Request; -use super::single_service::SingleService; +use super::service::ServiceError; +use super::single_service::{ComposeReply, SingleService}; +use super::util::mk_error_response; +use crate::base::iana::{ExtendedErrorCode, OptRcode}; +use crate::base::message_builder::AdditionalBuilder; +use crate::base::opt::ExtendedError; +use crate::base::StreamTarget; use crate::base::{Name, ToName}; -use crate::dep::octseq::OctetsBuilder; -use crate::dep::octseq::{EmptyBuilder, FromBuilder, Octets}; -use crate::net::client::request::Error; +use crate::dep::octseq::{EmptyBuilder, FromBuilder, Octets, OctetsBuilder}; use std::boxed::Box; use std::convert::Infallible; -use std::future::ready; -use std::future::Future; +use std::future::{ready, Future}; use std::pin::Pin; use std::vec::Vec; @@ -67,12 +70,12 @@ impl SingleService where Octs: AsRef<[u8]>, RequestOcts: Send + Sync, - CR: Send + Sync + 'static, + CR: ComposeReply + Send + Sync + 'static, { fn call( &self, request: Request, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets, { @@ -93,8 +96,19 @@ where { Some(el) => el, None => { - // Nothing, return InternalError - return Box::pin(ready(Err(Error::NoTransportAvailable))); + // We can't find a suitable upstream. Generate a SERVFAIL + // reply with an EDE. + let builder: AdditionalBuilder>> = + mk_error_response(&request.message(), OptRcode::SERVFAIL); + let msg = builder.as_message(); + let mut cr = CR::from_message(&msg).unwrap(); + if let Ok(ede) = ExtendedError::>::new_with_str( + ExtendedErrorCode::OTHER, + "no upstream for request", + ) { + cr.add_opt(&ede).unwrap(); + } + return Box::pin(ready(Ok(cr))); } }; diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 9eaa1a872..30a59b3cc 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -10,11 +10,11 @@ #![warn(clippy::missing_docs_in_private_items)] use super::message::Request; +use super::service::ServiceError; use crate::base::message_builder::AdditionalBuilder; use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord}; use crate::base::{Message, MessageBuilder, ParsedName, Rtype, StreamTarget}; use crate::dep::octseq::Octets; -use crate::net::client::request::Error; use crate::rdata::AllRecordData; use std::boxed::Box; use std::future::Future; @@ -29,7 +29,7 @@ pub trait SingleService { fn call( &self, request: Request, - ) -> Pin> + Send + Sync>> + ) -> Pin> + Send + Sync>> where RequestOcts: AsRef<[u8]> + Octets; } @@ -37,15 +37,21 @@ pub trait SingleService { /// Trait for creating a reply message. pub trait ComposeReply { /// Start a reply from an existing message. - fn from_message(msg: &Message) -> Result + fn from_message(msg: &Message) -> Result where Octs: AsRef<[u8]>, Self: Sized; + /// Add an EDNS option. + fn add_opt( + &mut self, + opt: &impl ComposeOptData, + ) -> Result<(), LongOptData>; + /// Return the reply message as an AdditionalBuilder with a StreamTarget. fn additional_builder_stream_target( &self, - ) -> Result>>, Error>; + ) -> Result>>, ServiceError>; } /// Record changes to a Message for generating a reply message. @@ -60,11 +66,8 @@ pub struct ReplyMessage { impl ReplyMessage { /// Add an option that is to be included in the final message. - fn add_opt( - &mut self, - opt: &impl ComposeOptData, - ) -> Result<(), LongOptData> { - self.opt_mut().push(opt).map_err(|e| e.unlimited_buf()) + fn add_opt_impl(&mut self, opt: &impl ComposeOptData) { + self.opt_mut().push(opt).expect("push should not fail"); } /// Returns a mutable reference to the OPT record. @@ -76,12 +79,12 @@ impl ReplyMessage { } impl ComposeReply for ReplyMessage { - fn from_message(msg: &Message) -> Result + fn from_message(msg: &Message) -> Result where Octs: AsRef<[u8]>, { let vec = msg.as_slice().to_vec(); - let msg = Message::from_octets(vec)?; + let msg = Message::from_octets(vec).unwrap(); let mut repl = Self { msg, opt: None }; // As an example, copy any ECS option from the message. @@ -97,19 +100,26 @@ impl ComposeReply for ReplyMessage { for opt in optrec.opt().iter::>() { let opt = opt?; if let AllOptData::ClientSubnet(_ecs) = opt { - repl.add_opt(&opt)?; + repl.add_opt_impl(&opt); } if let AllOptData::ExtendedError(ref _ede) = opt { - repl.add_opt(&opt)?; + repl.add_opt_impl(&opt); } } } Ok(repl) } + fn add_opt( + &mut self, + opt: &impl ComposeOptData, + ) -> Result<(), LongOptData> { + Ok(self.add_opt_impl(opt)) + } + fn additional_builder_stream_target( &self, - ) -> Result>>, Error> { + ) -> Result>>, ServiceError> { let source = &self.msg; let mut target = MessageBuilder::from_target( From 50946384f1c343d969a0ab6bab7dfb2c44e3f96b Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 15 Oct 2024 16:03:09 +0200 Subject: [PATCH 31/38] Remove unwraps --- src/net/server/adapter.rs | 26 +++++++++++--------------- src/net/server/qname_router.rs | 7 ++++--- src/net/server/single_service.rs | 3 ++- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index b40c07ed6..4d0f6e9ee 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -20,9 +20,7 @@ use crate::base::message_builder::AdditionalBuilder; use crate::base::opt::ExtendedError; use crate::base::StreamTarget; use crate::dep::octseq::Octets; -use crate::net::client::request::{ - ComposeRequest, RequestMessage, SendRequest, -}; +use crate::net::client::request::{RequestMessage, SendRequest}; use futures_util::stream::{once, Once}; use std::boxed::Box; use std::fmt::Debug; @@ -130,7 +128,11 @@ where where RequestOcts: AsRef<[u8]>, { - let req: RequestMessage = match request.try_into() { + // Prepare for an error. It is best to borrow request here. + let builder: AdditionalBuilder>> = + mk_error_response(&request.message(), OptRcode::SERVFAIL); + + let req = match request.try_into() { Ok(req) => req, Err(_) => { // Can this fail? Should the request be checked earlier. @@ -139,11 +141,6 @@ where } }; - // Prepare for an error. It is best to borrow req here before we - // pass it to send_request. - let builder: AdditionalBuilder>> = - mk_error_response(&req.to_message().unwrap(), OptRcode::SERVFAIL); - let mut gr = self.conn.send_request(req); let fut = async move { let msg = match gr.get_response().await { @@ -208,7 +205,11 @@ where where RequestOcts: AsRef<[u8]>, { - let req: RequestMessage = match request.try_into() { + // Prepare for an error. It is best to borrow request here. + let builder: AdditionalBuilder>> = + mk_error_response(&request.message(), OptRcode::SERVFAIL); + + let req = match request.try_into() { Ok(req) => req, Err(_) => { // Can this fail? Should the request be checked earlier. @@ -217,11 +218,6 @@ where } }; - // Prepare for an error. It is best to borrow req here before we - // pass it to send_request. - let builder: AdditionalBuilder>> = - mk_error_response(&req.to_message().unwrap(), OptRcode::SERVFAIL); - let mut gr = self.conn.send_request(req); let fut = async move { let msg = match gr.get_response().await { diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 90a4dd185..055497228 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -101,12 +101,13 @@ where let builder: AdditionalBuilder>> = mk_error_response(&request.message(), OptRcode::SERVFAIL); let msg = builder.as_message(); - let mut cr = CR::from_message(&msg).unwrap(); + let mut cr = CR::from_message(&msg) + .expect("CR should handle an error response"); if let Ok(ede) = ExtendedError::>::new_with_str( ExtendedErrorCode::OTHER, - "no upstream for request", + "No upstream for request", ) { - cr.add_opt(&ede).unwrap(); + cr.add_opt(&ede).expect("Adding an ede should not fail"); } return Box::pin(ready(Ok(cr))); } diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 30a59b3cc..33d40f71b 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -84,7 +84,8 @@ impl ComposeReply for ReplyMessage { Octs: AsRef<[u8]>, { let vec = msg.as_slice().to_vec(); - let msg = Message::from_octets(vec).unwrap(); + let msg = Message::from_octets(vec) + .expect("creating a Message from a Message should not fail"); let mut repl = Self { msg, opt: None }; // As an example, copy any ECS option from the message. From e371c28262c519318e45a2067800fb443e626068 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 15 Oct 2024 16:07:26 +0200 Subject: [PATCH 32/38] Remove unwraps --- src/net/server/adapter.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 4d0f6e9ee..a0482bcd0 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -149,12 +149,15 @@ where // The request failed. Create a ServFail response and // add an EDE that describes the error. let msg = builder.as_message(); - let mut cr = CR::from_message(&msg).unwrap(); + let mut cr = CR::from_message(&msg).expect( + "CR should be able to handle an error response", + ); if let Ok(ede) = ExtendedError::>::new_with_str( ExtendedErrorCode::OTHER, &e.to_string(), ) { - cr.add_opt(&ede).unwrap(); + cr.add_opt(&ede) + .expect("Adding an ede should not fail"); } return Ok(cr); } @@ -226,12 +229,15 @@ where // The request failed. Create a ServFail response and // add an EDE that describes the error. let msg = builder.as_message(); - let mut cr = CR::from_message(&msg).unwrap(); + let mut cr = CR::from_message(&msg).expect( + "CR should be able to handle an error response", + ); if let Ok(ede) = ExtendedError::>::new_with_str( ExtendedErrorCode::OTHER, &e.to_string(), ) { - cr.add_opt(&ede).unwrap(); + cr.add_opt(&ede) + .expect("Adding an ede should not fail"); } return Ok(cr); } From 6df7acecba9853e666acce24fb1d0863ff05bcf0 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Tue, 15 Oct 2024 18:17:14 +0200 Subject: [PATCH 33/38] Clippy --- src/net/server/adapter.rs | 8 ++++---- src/net/server/qname_router.rs | 2 +- src/net/server/single_service.rs | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index a0482bcd0..c63a1ae94 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -130,7 +130,7 @@ where { // Prepare for an error. It is best to borrow request here. let builder: AdditionalBuilder>> = - mk_error_response(&request.message(), OptRcode::SERVFAIL); + mk_error_response(request.message(), OptRcode::SERVFAIL); let req = match request.try_into() { Ok(req) => req, @@ -162,7 +162,7 @@ where return Ok(cr); } }; - Ok(CR::from_message(&msg)?) + CR::from_message(&msg) }; Box::pin(fut) } @@ -210,7 +210,7 @@ where { // Prepare for an error. It is best to borrow request here. let builder: AdditionalBuilder>> = - mk_error_response(&request.message(), OptRcode::SERVFAIL); + mk_error_response(request.message(), OptRcode::SERVFAIL); let req = match request.try_into() { Ok(req) => req, @@ -242,7 +242,7 @@ where return Ok(cr); } }; - Ok(CR::from_message(&msg)?) + CR::from_message(&msg) }; Box::pin(fut) } diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index 055497228..a0b27e5a6 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -99,7 +99,7 @@ where // We can't find a suitable upstream. Generate a SERVFAIL // reply with an EDE. let builder: AdditionalBuilder>> = - mk_error_response(&request.message(), OptRcode::SERVFAIL); + mk_error_response(request.message(), OptRcode::SERVFAIL); let msg = builder.as_message(); let mut cr = CR::from_message(&msg) .expect("CR should handle an error response"); diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 33d40f71b..28c6d19fe 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -115,7 +115,8 @@ impl ComposeReply for ReplyMessage { &mut self, opt: &impl ComposeOptData, ) -> Result<(), LongOptData> { - Ok(self.add_opt_impl(opt)) + self.add_opt_impl(opt); + Ok(()) } fn additional_builder_stream_target( From d513f7803d87b467c9a469861622ac152c2e4108 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Fri, 18 Oct 2024 15:46:58 +0200 Subject: [PATCH 34/38] Add tracing and more dnsi query examples --- examples/query-routing.rs | 3 +++ src/net/server/qname_router.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index d004f3371..629740300 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -22,7 +22,10 @@ use tracing_subscriber::EnvFilter; #[tokio::main(flavor = "multi_thread")] async fn main() { eprintln!("Test with commands such as:"); + eprintln!(" dnsi query --server ::1 -p 8053 ietf.org"); + eprintln!(" dnsi query --server ::1 -p 8053 nlnetlabs.nl"); eprintln!(" dnsi query --server ::1 -p 8053 google.com"); + eprintln!("Enabled tracing with 'RUST_LOG=trace' before the command"); // ----------------------------------------------------------------------- // Setup logging. You can override the log level by setting environment diff --git a/src/net/server/qname_router.rs b/src/net/server/qname_router.rs index a0b27e5a6..749b8f7a5 100644 --- a/src/net/server/qname_router.rs +++ b/src/net/server/qname_router.rs @@ -18,6 +18,7 @@ use std::convert::Infallible; use std::future::{ready, Future}; use std::pin::Pin; use std::vec::Vec; +use tracing::trace; /// A service that routes requests to other services based on the Qname in the /// request. @@ -112,7 +113,7 @@ where return Box::pin(ready(Ok(cr))); } }; - + trace!("Routing request to '{}'", el.name); el.service.call(request.clone()) } } From 9568c0a1b84a270a319d4689edcdda75ddc86f22 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 28 Oct 2024 11:25:45 +0100 Subject: [PATCH 35/38] Some comments from @bal-e and @tertsdiepraam. --- examples/query-routing.rs | 4 ++-- src/net/server/adapter.rs | 18 +++++++----------- src/net/server/mod.rs | 2 +- src/net/server/single_service.rs | 19 ++++++++++--------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index 629740300..d4b54514c 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -25,7 +25,7 @@ async fn main() { eprintln!(" dnsi query --server ::1 -p 8053 ietf.org"); eprintln!(" dnsi query --server ::1 -p 8053 nlnetlabs.nl"); eprintln!(" dnsi query --server ::1 -p 8053 google.com"); - eprintln!("Enabled tracing with 'RUST_LOG=trace' before the command"); + eprintln!("Enable tracing with 'RUST_LOG=trace' before the command"); // ----------------------------------------------------------------------- // Setup logging. You can override the log level by setting environment @@ -63,7 +63,7 @@ async fn main() { tokio::spawn(transport.run()); redun.add(Box::new(conn)).await.unwrap(); let conn_service = ClientTransportToSingleService::new(redun); - qr.add(Name::>::from_str(".").unwrap(), conn_service); + qr.add(Name::root_slice(), conn_service); // Queries to .com go to 2001:4860:4860::8888 and 8.8.8.8. let (redun, transport) = redundant::Connection::new(); diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index c63a1ae94..6b1baf24a 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -143,8 +143,8 @@ where let mut gr = self.conn.send_request(req); let fut = async move { - let msg = match gr.get_response().await { - Ok(msg) => msg, + match gr.get_response().await { + Ok(msg) => CR::from_message(&msg), Err(e) => { // The request failed. Create a ServFail response and // add an EDE that describes the error. @@ -161,8 +161,7 @@ where } return Ok(cr); } - }; - CR::from_message(&msg) + } }; Box::pin(fut) } @@ -212,13 +211,10 @@ where let builder: AdditionalBuilder>> = mk_error_response(request.message(), OptRcode::SERVFAIL); - let req = match request.try_into() { - Ok(req) => req, - Err(_) => { - // Can this fail? Should the request be checked earlier. - // Just return ServFail. - return Box::pin(ready(Err(ServiceError::InternalError))); - } + let Ok(req) = request.try_into() else { + // Can this fail? Should the request be checked earlier. + // Just return ServFail. + return Box::pin(ready(Err(ServiceError::InternalError))); }; let mut gr = self.conn.send_request(req); diff --git a/src/net/server/mod.rs b/src/net/server/mod.rs index e9fb4a962..0cf38e232 100644 --- a/src/net/server/mod.rs +++ b/src/net/server/mod.rs @@ -17,7 +17,7 @@ //! [SingleService][`single_service::SingleService`]. This interface supports //! only a single response per request. //! In other words, it does not support the AXFR and IXFR requests. -//! Adaptors are avaiable to connect SingleServer to [`Service`] and to the +//! Adaptors are available to connect SingleServer to [`Service`] and to the //! [Client][`crate::net::client`] transports. See the //! Section [Single Service][crate::net::server#single-service] for //! more details. diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 28c6d19fe..365422bfe 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -12,10 +12,11 @@ use super::message::Request; use super::service::ServiceError; use crate::base::message_builder::AdditionalBuilder; -use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord}; -use crate::base::{Message, MessageBuilder, ParsedName, Rtype, StreamTarget}; +use crate::base::opt::{ + AllOptData, ComposeOptData, LongOptData, OptRecord, UnknownRecordData, +}; +use crate::base::{Message, MessageBuilder, Rtype, StreamTarget}; use crate::dep::octseq::Octets; -use crate::rdata::AllRecordData; use std::boxed::Box; use std::future::Future; use std::pin::Pin; @@ -144,8 +145,8 @@ impl ComposeReply for ReplyMessage { for rr in &mut source { let rr = rr?; let rr = rr - .into_record::>>()? - .expect("AllRecordData should not fail"); + .into_record::>()? + .expect("UnknownRecordData should not fail"); target.push(rr).expect("push should not fail"); } @@ -156,8 +157,8 @@ impl ComposeReply for ReplyMessage { for rr in &mut source { let rr = rr?; let rr = rr - .into_record::>>()? - .expect("AllRecordData should not fail"); + .into_record::>()? + .expect("UnknownRecordData should not fail"); target.push(rr).expect("push should not fail"); } @@ -170,8 +171,8 @@ impl ComposeReply for ReplyMessage { if rr.rtype() == Rtype::OPT { } else { let rr = rr - .into_record::>>()? - .expect("AllRecordData should not fail"); + .into_record::>()? + .expect("UnknownRecordData should not fail"); target.push(rr).expect("push should not fail"); } } From 1729ff3c2ece7f8b50fae54c52e30d88f58ee905 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 28 Oct 2024 11:30:44 +0100 Subject: [PATCH 36/38] Small fix. --- src/net/server/single_service.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/net/server/single_service.rs b/src/net/server/single_service.rs index 365422bfe..64ef0aeff 100644 --- a/src/net/server/single_service.rs +++ b/src/net/server/single_service.rs @@ -12,10 +12,10 @@ use super::message::Request; use super::service::ServiceError; use crate::base::message_builder::AdditionalBuilder; -use crate::base::opt::{ - AllOptData, ComposeOptData, LongOptData, OptRecord, UnknownRecordData, +use crate::base::opt::{AllOptData, ComposeOptData, LongOptData, OptRecord}; +use crate::base::{ + Message, MessageBuilder, Rtype, StreamTarget, UnknownRecordData, }; -use crate::base::{Message, MessageBuilder, Rtype, StreamTarget}; use crate::dep::octseq::Octets; use std::boxed::Box; use std::future::Future; From b159484fa401cfa3987c519c25f7b143756ae8cf Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 28 Oct 2024 11:32:58 +0100 Subject: [PATCH 37/38] Clippy --- src/net/server/adapter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/server/adapter.rs b/src/net/server/adapter.rs index 6b1baf24a..a28c1d80d 100644 --- a/src/net/server/adapter.rs +++ b/src/net/server/adapter.rs @@ -159,7 +159,7 @@ where cr.add_opt(&ede) .expect("Adding an ede should not fail"); } - return Ok(cr); + Ok(cr) } } }; From 20558da83ab5f24def59f1893973b7a394593f16 Mon Sep 17 00:00:00 2001 From: Philip Homburg Date: Mon, 28 Oct 2024 12:00:46 +0100 Subject: [PATCH 38/38] Move boilerplate out of the way --- examples/query-routing.rs | 86 +++++++++++++-------------------------- 1 file changed, 28 insertions(+), 58 deletions(-) diff --git a/examples/query-routing.rs b/examples/query-routing.rs index d4b54514c..a0b829133 100644 --- a/examples/query-routing.rs +++ b/examples/query-routing.rs @@ -1,5 +1,6 @@ use domain::base::Name; use domain::net::client::protocol::{TcpConnect, UdpConnect}; +use domain::net::client::request::RequestMessage; use domain::net::client::{dgram_stream, redundant}; use domain::net::server::adapter::{ ClientTransportToSingleService, SingleServiceToService, @@ -42,72 +43,17 @@ async fn main() { QnameRouter::new(); // Queries to the root go to 2606:4700:4700::1111 and 1.1.1.1. - let (redun, transport) = redundant::Connection::new(); - tokio::spawn(transport.run()); - let server_addr = SocketAddr::new( - IpAddr::from_str("2606:4700:4700::1111").unwrap(), - 53, - ); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); - let server_addr = - SocketAddr::new(IpAddr::from_str("1.1.1.1").unwrap(), 53); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); + let redun = example_redundant("2606:4700:4700::1111 ", "1.1.1.1").await; let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::root_slice(), conn_service); // Queries to .com go to 2001:4860:4860::8888 and 8.8.8.8. - let (redun, transport) = redundant::Connection::new(); - tokio::spawn(transport.run()); - let server_addr = SocketAddr::new( - IpAddr::from_str("2001:4860:4860::8888").unwrap(), - 53, - ); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); - let server_addr = - SocketAddr::new(IpAddr::from_str("8.8.8.8").unwrap(), 53); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); + let redun = example_redundant("2001:4860:4860::8888", "8.8.8.8").await; let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::>::from_str("com").unwrap(), conn_service); // Queries to .nl go to 2620:fe::9 and 9.9.9.9. - let (redun, transport) = redundant::Connection::new(); - tokio::spawn(transport.run()); - let server_addr = - SocketAddr::new(IpAddr::from_str("2620:fe::9").unwrap(), 53); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); - let server_addr = - SocketAddr::new(IpAddr::from_str("9.9.9.9").unwrap(), 53); - let udp_connect = UdpConnect::new(server_addr); - let tcp_connect = TcpConnect::new(server_addr); - let (conn, transport) = - dgram_stream::Connection::new(udp_connect, tcp_connect); - tokio::spawn(transport.run()); - redun.add(Box::new(conn)).await.unwrap(); + let redun = example_redundant("2620:fe::9", "9.9.9.9").await; let conn_service = ClientTransportToSingleService::new(redun); qr.add(Name::>::from_str("nl").unwrap(), conn_service); @@ -137,3 +83,27 @@ async fn main() { udp_join_handle.await.unwrap(); tcp_join_handle.await.unwrap(); } + +async fn example_redundant( + dst1: &str, + dst2: &str, +) -> redundant::Connection>> { + let (redun, transport) = redundant::Connection::new(); + tokio::spawn(transport.run()); + let server_addr = SocketAddr::new(IpAddr::from_str(dst1).unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); + let server_addr = SocketAddr::new(IpAddr::from_str(dst2).unwrap(), 53); + let udp_connect = UdpConnect::new(server_addr); + let tcp_connect = TcpConnect::new(server_addr); + let (conn, transport) = + dgram_stream::Connection::new(udp_connect, tcp_connect); + tokio::spawn(transport.run()); + redun.add(Box::new(conn)).await.unwrap(); + + redun +}