Skip to main content

pharos_server/
router.rs

1use std::{collections::HashMap, net::SocketAddr};
2
3use pharos_proto::{Message, Name, NameRef, Opcode, Type};
4
5use crate::{
6    Chain, ListenerKey, Protocol,
7    metrics::{BlockMetrics, PluginMetrics, Registry},
8};
9
10/// A View filter receives the Query and client information without allocating.
11pub type ViewFilter = fn(&Message<'_>, SocketAddr, Protocol) -> bool;
12
13/// A Domain's Plugin Chain and its ordered View filters.
14pub struct ServerBlock<C> {
15    domain: Name,
16    chain: Option<C>,
17    filters: Vec<ViewFilter>,
18    metrics: Option<BlockMetrics>,
19    plugin_metrics: Vec<(&'static str, PluginMetrics)>,
20    plugin_names: Vec<&'static str>,
21}
22
23impl<C> ServerBlock<C> {
24    pub fn domain(&self) -> &Name {
25        &self.domain
26    }
27    pub fn chain(&self) -> Option<&C> {
28        self.chain.as_ref()
29    }
30    pub(crate) fn metrics(&self) -> Option<&BlockMetrics> {
31        self.metrics.as_ref()
32    }
33    pub(crate) fn plugin_metrics(&self, name: &str) -> Option<&PluginMetrics> {
34        self.plugin_metrics
35            .iter()
36            .find(|(candidate, _)| *candidate == name)
37            .map(|(_, metrics)| metrics)
38    }
39    pub(crate) fn plugin_name(&self, name: &str) -> Option<&'static str> {
40        self.plugin_names
41            .iter()
42            .copied()
43            .find(|candidate| *candidate == name)
44    }
45}
46
47/// Server Blocks grouped by their Listener and ordered by Domain specificity.
48pub struct Router<C> {
49    listeners: HashMap<ListenerKey, Vec<ServerBlock<C>>>,
50    metric_contexts: HashMap<ListenerKey, MetricContext<C>>,
51}
52
53struct MetricContext<C> {
54    registry: Registry,
55    server: String,
56    prepare: fn(&mut ServerBlock<C>, &Registry, &str),
57}
58
59impl<C> Default for Router<C> {
60    fn default() -> Self {
61        Self {
62            listeners: HashMap::new(),
63            metric_contexts: HashMap::new(),
64        }
65    }
66}
67
68impl<C> Router<C> {
69    pub fn add(
70        &mut self,
71        key: ListenerKey,
72        domain: Name,
73        chain: Option<C>,
74        filters: Vec<ViewFilter>,
75    ) {
76        let mut block = ServerBlock {
77            domain,
78            chain,
79            filters,
80            metrics: None,
81            plugin_metrics: Vec::new(),
82            plugin_names: Vec::new(),
83        };
84        if let Some(context) = self.metric_contexts.get(&key) {
85            (context.prepare)(&mut block, &context.registry, &context.server);
86        }
87        let blocks = self.listeners.entry(key).or_default();
88        blocks.push(block);
89        blocks.sort_by_key(|block| std::cmp::Reverse(block.domain.label_count()));
90    }
91
92    /// Select a Server Block, or return None when the Query must be refused.
93    pub fn route(
94        &self,
95        key: &ListenerKey,
96        query: Message<'_>,
97        peer: SocketAddr,
98        protocol: Protocol,
99    ) -> Option<&ServerBlock<C>> {
100        let question = query.questions().next()?;
101        let blocks = self.listeners.get(key)?;
102        let ds = query.opcode() == Opcode::QUERY && question.qtype == Type::DS;
103        let labels = question.name.labels().count();
104        let mut selected = None;
105        for block in blocks
106            .iter()
107            .filter(|block| block.domain.label_count() != 0)
108        {
109            let count = block.domain.label_count();
110            if count > labels {
111                continue;
112            }
113            let domain = NameRef::parse(block.domain.as_wire(), 0).expect("validated Domain");
114            // RFC 4343 ยง3: DNS lookup compares ASCII letters case-insensitively.
115            if !question
116                .name
117                .labels()
118                .skip(labels - count)
119                .zip(domain.labels())
120                .all(|(a, b)| a.eq_ignore_ascii_case(b))
121            {
122                continue;
123            }
124            block.chain.as_ref()?;
125            if block
126                .filters
127                .iter()
128                .all(|filter| filter(&query, peer, protocol))
129            {
130                if !ds {
131                    return Some(block);
132                }
133                selected = Some(block);
134            }
135        }
136        if selected.is_some() {
137            return selected;
138        }
139        blocks
140            .iter()
141            .filter(|block| block.domain.label_count() == 0 && block.chain.is_some())
142            .find(|block| {
143                block
144                    .filters
145                    .iter()
146                    .all(|filter| filter(&query, peer, protocol))
147            })
148    }
149}
150
151impl<C: Chain> Router<C> {
152    pub(crate) fn metrics_enabled(&self, key: &ListenerKey) -> bool {
153        self.listeners.get(key).is_some_and(|blocks| {
154            blocks
155                .iter()
156                .any(|block| block.chain.as_ref().is_some_and(Chain::metrics_enabled))
157        })
158    }
159    pub(crate) fn prepare_metrics(&mut self, key: &ListenerKey, registry: &Registry, server: &str) {
160        self.metric_contexts.insert(
161            *key,
162            MetricContext {
163                registry: registry.clone(),
164                server: server.to_owned(),
165                prepare: prepare_block::<C>,
166            },
167        );
168        if let Some(blocks) = self.listeners.get_mut(key) {
169            for block in blocks {
170                prepare_block(block, registry, server);
171            }
172        }
173    }
174}
175
176fn prepare_block<C: Chain>(block: &mut ServerBlock<C>, registry: &Registry, server: &str) {
177    // shortcut: View names are unavailable until named View filters are implemented.
178    block.plugin_metrics.clear();
179    block.plugin_names.clear();
180    if let Some(chain) = &block.chain {
181        if chain.stacktrace() {
182            crate::panic::prepare_stacktrace();
183        }
184        let metrics = chain
185            .metrics_enabled()
186            .then(|| registry.block(server, &block.domain.to_string(), ""));
187        chain.visit_enabled(&mut |name| {
188            block.plugin_names.push(name);
189            if let Some(metrics) = &metrics {
190                metrics.plugin_enabled(name);
191                block.plugin_metrics.push((name, metrics.plugin(name)));
192            }
193        });
194        block.metrics = metrics;
195    }
196}