Module domain::net::server

source ·
Available on crate features net and unstable-server-transport only.
Expand description

Receiving requests and sending responses.

This module provides skeleton asynchronous server implementations based on the Tokio async runtime. In combination with an appropriate network source, optional MiddlewareChain and your own Service implementation they can be used to run a standards compliant DNS server that answers requests based on the application logic you specify.

§Architecture

A layered stack of components is responsible for handling incoming requests and outgoing responses:

--> network source                      - reads bytes from the client
    --> server                          - deserializes requests
        --> (optional) middleware chain - pre-processes requests
            --> service                 - processes requests &
            <--                           generates responses
        <-- (optional) middleware chain - post-processes responses
    <-- server                          - serializes responses
<-- network source                      - writes bytes to the client

§Getting started

Servers are implemented by combining a server transport (see dgram and stream), BufSource, (optional) MiddlewareChain and Service together.

Whether using DgramServer or StreamServer the required steps are the same.

  • Create an appropriate network source (more on this below).
  • Construct a server transport with new() passing in the network source as an argument.
  • Tune the server behaviour via builder functions such as with_middleware().
  • run() the server.
  • shutdown() the server, explicitly or on drop.

See DgramServer and StreamServer for example code to help you get started.

§Core concepts

§Network transports

Historically DNS servers communicated primarily via datagram based connection-less network transport protocols, and used stream based connection-oriented network transport protocols only for zone transfers.

Modern DNS servers increasingly need to support stream based connection-oriented network transport protocols for additional response capacity and connection security.

This module provides support for both datagram and stream based network transport protocols via the DgramServer and StreamServer types respectively.

§Datagram (e.g. UDP) servers

DgramServer can communicate via any “network source” type that implements the AsyncDgramSock trait, with an implementation provided for tokio::net::UdpSocket.

The type alias UdpServer is provided for convenience for implementations based on tokio::net::UdpSocket.

§Stream (e.g. TCP) servers

StreamServer can communicate via any “network source” type that implements the AsyncAccept trait, and whose associated stream type implements the tokio::io::AsyncRead and tokio::io::AsyncWrite traits, with an implementation provided for tokio::net::TcpListener and associated stream type tokio::net::TcpStream.

The type alias TcpServer is provided for convenience for implementations based on tokio::net::TcpListener.

§Middleware

Middleware provides a means to add logic for request pre-processing and response post-processing which doesn’t belong in the outermost transport specific layer of a server nor does it constitute part of the core application logic.

With Middleware mandatory functionality and logic required by all standards compliant DNS servers can be incorporated into your server by building a MiddlewareChain starting from MiddlewareBuilder::default.

You can also opt to incorporate additional behaviours into your DNS server from a selection of pre-supplied implementations via MiddlewareBuilder::push. See the various implementations of MiddlewareProcessor for more information.

And if the existing middleware processors don’t meet your needs, maybe you have specific access control or rate limiting requirements for example, you can implement MiddlewareProcessor yourself to add your own pre- and post- processing stages into your DNS server.

§Application logic

With the basic work of handling DNS requests and responses taken care of, the actual application logic that differentiates your DNS server from other DNS servers is left for you to define by implementing the Service trait.

§Advanced

§Memory allocation

The allocation of buffers, e.g. for receiving DNS messages, is delegated to an implementation of the BufSource trait, giving you some control over the memory allocation strategy in use.

§Dynamic reconfiguration

Servers in principle support the ability to dynamically reconfigure themselves in response to ServerCommand::Reconfigure while running, though the actual degree of support for this is server implementation dependent.

§Performance

Both DgramServer and StreamServer use CommonMessageFlow to pre-process the request, invoke Service::call, and post-process the response.

  • Pre-processing and Service::call invocation are done from the Tokio task handling the request. For DgramServer this is the main task that receives incoming messages. For StreamServer this is a dedicated task per accepted connection.
  • Post-processing is done in a new task request within which each future resulting from invoking Service::call is awaited and the resulting response is post-processed.

The initial work done by Service::call should therefore complete as quickly as possible, delegating as much of the work as it can to the future(s) it returns. Until then it blocks the server from receiving new messages, or in the case of StreamServer, new messages for the connection on which the current message was received.

§Clone, Arc, and shared state

Both DgramServer and StreamServer take ownership of the Service impl passed to them.

While this may work for some scenarios, real DNS server applications will likely need to accept client requests over multiple transports, will require multiple instances of DgramServer and StreamServer, and the Service impl will likely need to have its own state.

In these more complex scenarios it becomes more important to understand how the servers work with the Service impl and the Clone and Arc traits.

DgramServer uses a single copy of the Service impl that it receives but StreamServer requires that Service be Clone because it clones it for each new connection that it accepts.

There are various approaches you can take to manage the sharing of state between server instances and processing tasks, for example:

#DifficultySummaryDescription
1Easy#[derive(Clone)]Add #[derive(Clone)] to your Service impl. If your Service impl has no state that needs to be shared amongst instances of itself then this may be good enough for you.
2MediumArc wrapperWrap your Service impl instance inside an Arc via Arc::new. This crate implements the Service trait for Arc<Service> so you can pass an Arc<Service> to both DgramServer and StreamServer and they will Clone the Arc rather than the Service instance itself.
3HardDo it yourselfManually implement Clone and/or your own locking and interior mutability strategy for your Service impl, giving you complete control over how state is shared by your server instances.

Modules§

  • Buffer types and allocation strategies.
  • Support for datagram based server transports.
  • Server related errors.
  • Support for working with DNS messages in servers.
  • DNS server related metrics.
  • Request pre-processing and response post-processing.
  • The application logic of a DNS server.
  • Network socket abstractions.
  • Support for stream based server transports.
  • Small utilities for building and working with servers.

Structs§

Enums§