pharos-plugins 0.1.0

CoreDNS-compatible Plugins for the Pharos DNS server
Documentation
#![forbid(unsafe_code)]
#![cfg(feature = "plugin-whoami")]
#![recursion_limit = "512"]

use pharos_corefile::{configure, parse};
use pharos_plugins::{Chain, directives, servers};
use pharos_proto::{Message, Rcode};
use pharos_server::{Action, Handler, Listener, Protocol, Server};
use stats_alloc::{INSTRUMENTED_SYSTEM, Stats, StatsAlloc};
use std::{
    alloc::System,
    net::SocketAddr,
    sync::{Arc, Mutex},
};

#[global_allocator]
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;

#[derive(Default)]
struct Counts {
    queries: usize,
    before: Option<Stats>,
    after: Option<Stats>,
}
struct Counting {
    server: Server<Arc<Chain>>,
    counts: Arc<Mutex<Counts>>,
}

impl Handler for Counting {
    fn parallel_query(
        &self,
        query: &[u8],
        peer: SocketAddr,
    ) -> Option<tokio::sync::watch::Receiver<bool>> {
        self.server.parallel_query(query, peer)
    }

    async fn handle(
        &self,
        query: &[u8],
        peer: SocketAddr,
        protocol: Protocol,
        response: &mut Vec<u8>,
    ) -> Action {
        {
            let mut counts = self.counts.lock().unwrap();
            counts.queries += 1;
            if counts.queries == 101 {
                counts.before = Some(GLOBAL.stats());
            }
            if counts.queries == 201 {
                counts.after = Some(GLOBAL.stats());
            }
        }
        self.server.handle(query, peer, protocol, response).await
    }
}

#[tokio::test(flavor = "current_thread")]
async fn routed_udp_queries_do_not_allocate() {
    check_routed_udp_queries_do_not_allocate(".:0 {\n whoami\n}").await;
    #[cfg(feature = "plugin-errors")]
    check_routed_udp_queries_do_not_allocate(".:0 {\n errors\n whoami\n}").await;
    #[cfg(feature = "plugin-prometheus")]
    check_routed_udp_queries_do_not_allocate(".:0 {\n prometheus\n whoami\n}").await;
    #[cfg(feature = "plugin-health")]
    check_routed_udp_queries_do_not_allocate(".:0 {\n health 127.0.0.1:0\n whoami\n}").await;
    #[cfg(feature = "plugin-loop")]
    {
        check_routed_udp_queries_do_not_allocate(".:0 {\n loop\n whoami\n}").await;
        check_routed_udp_queries_of_type(".:0 {\n loop\n whoami\n}", 13).await;
    }
}

async fn check_routed_udp_queries_do_not_allocate(corefile: &str) {
    check_routed_udp_queries_of_type(corefile, 1).await;
}

async fn check_routed_udp_queries_of_type(corefile: &str, qtype: u8) {
    let blocks = parse("Corefile", corefile).unwrap();
    let (mut servers, _) = servers(configure::<Chain>(&blocks, directives()).unwrap()).unwrap();
    let (key, server) = servers.drain().next().unwrap();
    let counts = Arc::new(Mutex::new(Counts::default()));
    let listener = Listener::bind(
        key.address,
        Counting {
            server,
            counts: Arc::clone(&counts),
        },
    )
    .await
    .unwrap();
    let address = SocketAddr::from(([127, 0, 0, 1], listener.local_addr().port()));
    let (done, finished) = tokio::sync::oneshot::channel();
    let client = std::thread::spawn(move || {
        let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
        socket
            .set_read_timeout(Some(std::time::Duration::from_secs(5)))
            .unwrap();
        let query = [
            0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, b'a', 0, 0, qtype, 0, 1, 0, 0, 41, 2, 0, 0, 0,
            0x80, 0, 0, 0,
        ];
        let mut buf = [0; 512];
        for _ in 0..201 {
            socket.send_to(&query, address).unwrap();
            let (n, _) = socket.recv_from(&mut buf).unwrap();
            let response = Message::parse(&buf[..n]).unwrap();
            assert!(response.qr());
            assert_eq!(response.rcode(), Rcode::NOERROR);
        }
        let _ = done.send(());
    });
    finished.await.unwrap();
    client.join().unwrap();
    let (before, after) = {
        let counts = counts.lock().unwrap();
        (counts.before.unwrap(), counts.after.unwrap())
    };
    assert_eq!(after.allocations - before.allocations, 0);
    assert_eq!(after.reallocations - before.reallocations, 0);
    listener.shutdown().await.unwrap();
}