Skip to main content

sidedns_core/
store.rs

1use std::{collections::HashMap, sync::Arc};
2
3use arc_swap::ArcSwap;
4
5use crate::rule::DnsRule;
6
7/// In-memory DNS rule store.
8///
9/// Resolution priority (first match wins):
10/// 1. Ephemeral exact
11/// 2. Ephemeral wildcard
12/// 3. Persistent exact
13/// 4. Persistent wildcard
14///
15
16#[derive(Debug, Default, Clone)]
17pub struct InnerRuleStore {
18    ephemeral_exact: HashMap<String, DnsRule>,
19    persistent_exact: HashMap<String, DnsRule>,
20    ephemeral_wildcard: Vec<DnsRule>,
21    persistent_wildcard: Vec<DnsRule>,
22}
23
24impl InnerRuleStore {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Resolve a domain to its matching rule.
30    ///
31    /// Checks ephemeral before persistent, exact before wildcard.
32    pub fn resolve(&self, domain: &str) -> Option<&DnsRule> {
33        if let Some(r) = self.ephemeral_exact.get(domain) {
34            return Some(r);
35        }
36        if let Some(r) = self.find_wildcard(&self.ephemeral_wildcard, domain) {
37            return Some(r);
38        }
39        if let Some(r) = self.persistent_exact.get(domain) {
40            return Some(r);
41        }
42        if let Some(r) = self.find_wildcard(&self.persistent_wildcard, domain) {
43            return Some(r);
44        }
45        None
46    }
47
48    /// Add or replace a persistent rule.
49    pub fn add(&mut self, rule: DnsRule) {
50        if rule.is_wildcard() {
51            self.persistent_wildcard.retain(|r| r.domain != rule.domain);
52            self.persistent_wildcard.push(rule);
53        } else {
54            self.persistent_exact.insert(rule.domain.clone(), rule);
55        }
56    }
57
58    /// Add or replace persistent rules.
59    pub fn add_all(&mut self, rules: Vec<DnsRule>) {
60        rules.into_iter().for_each(|rule| self.add(rule));
61    }
62
63    /// Add or replace an ephemeral rule.
64    pub fn add_ephemeral(&mut self, rule: DnsRule) {
65        if rule.is_wildcard() {
66            self.ephemeral_wildcard.retain(|r| r.domain != rule.domain);
67            self.ephemeral_wildcard.push(rule);
68        } else {
69            self.ephemeral_exact.insert(rule.domain.clone(), rule);
70        }
71    }
72
73    /// Remove the persistent rule for `domain`.
74    pub fn remove(&mut self, domain: &str) -> Option<DnsRule> {
75        let rule = self.persistent_exact.remove(domain);
76        if let Some(pos) = self
77            .persistent_wildcard
78            .iter()
79            .position(|x| x.domain == domain)
80        {
81            let item = self.persistent_wildcard.swap_remove(pos);
82            return Some(item);
83        }
84        rule
85    }
86
87    /// Remove the ephemeral rule for `domain`.
88    pub fn remove_ephemeral(&mut self, domain: &str) -> Option<DnsRule> {
89        let rule = self.ephemeral_exact.remove(domain);
90        if let Some(pos) = self
91            .ephemeral_wildcard
92            .iter()
93            .position(|x| x.domain == domain)
94        {
95            let item = self.ephemeral_wildcard.swap_remove(pos);
96            return Some(item);
97        }
98        rule
99    }
100
101    /// Iterate over persistent rules only.
102    pub fn persistent_rules(&self) -> impl Iterator<Item = &DnsRule> {
103        self.persistent_exact
104            .values()
105            .chain(self.persistent_wildcard.iter())
106    }
107
108    /// Iterate over all rules (ephemeral + persistent).
109    pub fn all_rules(&self) -> impl Iterator<Item = &DnsRule> {
110        self.ephemeral_exact
111            .values()
112            .chain(self.ephemeral_wildcard.iter())
113            .chain(self.persistent_exact.values())
114            .chain(self.persistent_wildcard.iter())
115    }
116
117    pub fn len(&self) -> usize {
118        self.ephemeral_exact.len()
119            + self.ephemeral_wildcard.len()
120            + self.persistent_exact.len()
121            + self.persistent_wildcard.len()
122    }
123
124    pub fn is_empty(&self) -> bool {
125        self.len() == 0
126    }
127
128    fn find_wildcard<'a>(&self, list: &'a [DnsRule], domain: &str) -> Option<&'a DnsRule> {
129        list.iter().find(|r| r.matches(domain))
130    }
131}
132
133/// Concurrent, lock-free access to the [`InnerRuleStore`].
134///
135/// Reads are fully lock-free — they clone an `Arc` (~10ns) and read
136/// from an immutable snapshot. Writers clone the current store, apply
137/// the mutation, and swap atomically via [`ArcSwap::rcu`].
138///
139/// This is the correct abstraction for SideDNS because reads (DNS server,
140/// HTTP proxy, WebSocket proxy) vastly outnumber writes (IPC add/remove).
141///
142/// # Cloning
143///
144/// [`RuleStore`] wraps an `Arc` internally — cloning it is cheap and
145/// shares the same underlying data. All clones see writes immediately.
146#[derive(Clone, Debug)]
147pub struct RuleStore {
148    inner: Arc<ArcSwap<InnerRuleStore>>,
149}
150
151impl Default for RuleStore {
152    fn default() -> Self {
153        Self {
154            inner: Arc::new(ArcSwap::from_pointee(InnerRuleStore::new())),
155        }
156    }
157}
158
159impl RuleStore {
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    /// Resolve a domain to its matching rule.
165    ///
166    /// Lock-free. Safe to call from hot paths (DNS, proxy).
167    pub fn resolve(&self, domain: &str) -> Option<DnsRule> {
168        self.inner.load().resolve(domain).cloned()
169    }
170
171    /// Add or replace a persistent rule.
172    pub fn add(&self, rule: DnsRule) -> DnsRule {
173        self.inner.rcu(|current| {
174            let mut next = (**current).clone();
175            next.add(rule.clone());
176            next
177        });
178        rule
179    }
180
181    /// Add or replace persistent rules.
182    pub fn add_all(&self, rules: Vec<DnsRule>) -> usize {
183        self.inner.rcu(|current| {
184            let mut next = (**current).clone();
185            next.add_all(rules.clone());
186            next
187        });
188        rules.len()
189    }
190
191    /// Add or replace an ephemeral rule.
192    pub fn add_ephemeral(&self, rule: DnsRule) -> DnsRule {
193        self.inner.rcu(|current| {
194            let mut next = (**current).clone();
195            next.add_ephemeral(rule.clone());
196            next
197        });
198        rule
199    }
200
201    /// Remove the persistent rule for `domain`.
202    ///
203    /// Returns `true` if a rule was removed.
204    pub fn remove(&self, domain: &str) -> Option<DnsRule> {
205        let mut removed = None;
206        self.inner.rcu(|current| {
207            let mut next = (**current).clone();
208            removed = next.remove(domain);
209            next
210        });
211        removed
212    }
213
214    /// Remove the ephemeral rule for `domain`.
215    ///
216    /// Returns `true` if a rule was removed.
217    pub fn remove_ephemeral(&self, domain: &str) -> Option<DnsRule> {
218        let mut removed = None;
219        self.inner.rcu(|current| {
220            let mut next = (**current).clone();
221            removed = next.remove_ephemeral(domain);
222            next
223        });
224        removed
225    }
226
227    /// Return a snapshot of all persistent rules.
228    ///
229    /// Snapshot is consistent but may be stale by the time you use it.
230    /// Only use for persistence (confy) and IPC list responses.
231    pub fn snapshot_persistent(&self) -> Vec<DnsRule> {
232        self.inner.load().persistent_rules().cloned().collect()
233    }
234
235    /// Return a snapshot of all rules (ephemeral + persistent).
236    pub fn snapshot_all(&self) -> Vec<DnsRule> {
237        self.inner.load().all_rules().cloned().collect()
238    }
239
240    pub fn len(&self) -> usize {
241        self.inner.load().len()
242    }
243
244    pub fn is_empty(&self) -> bool {
245        self.inner.load().is_empty()
246    }
247}