Skip to main content

krabka_raft/connection_limiter/
overrides.rs

1//! The addresses of `max.connections.per.ip.overrides`.
2//!
3//! Kafka resolves each host with `InetAddress.getByName` when the config
4//! changes (`ConnectionQuotas.updateMaxConnectionsPerIpOverride`), not per
5//! connection, and it does so in the reconfiguration, off its acceptor
6//! threads. Here a set of address literals applies at once, and a set that
7//! names a host is resolved by a task of its own, so an accept loop never
8//! waits on DNS: the addresses in force before the change stay in force until
9//! the lookup ends. A set with a host that does not resolve leaves the
10//! previous addresses in force, as Kafka's update throws before it replaces
11//! them.
12
13use {
14    std::{
15        collections::{BTreeMap, HashMap},
16        net::IpAddr,
17        sync::{Arc, Mutex, MutexGuard, PoisonError},
18    },
19    tokio::net,
20    tracing::warn,
21};
22
23/// The cap of each override address.
24pub type IpOverrides = Arc<HashMap<IpAddr, usize>>;
25
26/// The host set last applied, the one being resolved, and the addresses in
27/// force.
28#[derive(Debug, Default)]
29struct State {
30    applied: BTreeMap<String, usize>,
31    pending: Option<BTreeMap<String, usize>>,
32    addresses: IpOverrides,
33}
34
35/// The resolved override addresses, keyed by the host set they came from.
36#[derive(Debug, Default)]
37pub(super) struct OverrideAddresses {
38    state: Mutex<State>,
39}
40
41/// The addresses of `hosts` when every host is an address literal.
42fn literals(hosts: &BTreeMap<String, usize>) -> Option<HashMap<IpAddr, usize>> {
43    hosts
44        .iter()
45        .map(|(host, cap)| Some((host.parse::<IpAddr>().ok()?.to_canonical(), *cap)))
46        .collect()
47}
48
49/// `InetAddress.getByName`: an address literal as it is, else the first
50/// address the name resolves to.
51async fn resolve_host(host: &str) -> Option<IpAddr> {
52    if let Ok(address) = host.parse::<IpAddr>() {
53        return Some(address.to_canonical());
54    }
55    net::lookup_host((host, 0))
56        .await
57        .ok()?
58        .next()
59        .map(|address| address.ip().to_canonical())
60}
61
62impl OverrideAddresses {
63    fn state(&self) -> MutexGuard<'_, State> {
64        self.state.lock().unwrap_or_else(PoisonError::into_inner)
65    }
66
67    /// The override addresses in force for `hosts`. A new set of literals
68    /// applies at once; a new set that names a host starts its lookup and
69    /// leaves the previous addresses in force meanwhile. It must be called
70    /// within a Tokio runtime.
71    pub(super) fn addresses(self: &Arc<Self>, hosts: &BTreeMap<String, usize>) -> IpOverrides {
72        let mut state = self.state();
73        if state.applied == *hosts || state.pending.as_ref() == Some(hosts) {
74            return Arc::clone(&state.addresses);
75        }
76        if let Some(addresses) = literals(hosts) {
77            state.applied.clone_from(hosts);
78            state.pending = None;
79            state.addresses = Arc::new(addresses);
80            return Arc::clone(&state.addresses);
81        }
82        state.pending = Some(hosts.clone());
83        let this = Arc::clone(self);
84        let hosts = hosts.clone();
85        tokio::spawn(async move { this.resolve(hosts).await });
86        Arc::clone(&state.addresses)
87    }
88
89    /// Resolve `hosts` and apply them, unless a newer set replaced them
90    /// meanwhile.
91    async fn resolve(&self, hosts: BTreeMap<String, usize>) {
92        let mut addresses = HashMap::with_capacity(hosts.len());
93        let mut unresolved = Vec::new();
94        for (host, cap) in &hosts {
95            match resolve_host(host).await {
96                Some(address) => {
97                    addresses.insert(address, *cap);
98                }
99                None => unresolved.push(host.clone()),
100            }
101        }
102        let mut state = self.state();
103        if state.pending.as_ref() != Some(&hosts) {
104            return;
105        }
106        state.pending = None;
107        state.applied = hosts;
108        if unresolved.is_empty() {
109            state.addresses = Arc::new(addresses);
110            return;
111        }
112        drop(state);
113        warn!(
114            ?unresolved,
115            "max.connections.per.ip.overrides names hosts that do not resolve; keeping the \
116             previous overrides"
117        );
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use {
124        super::*,
125        assert2::assert,
126        std::time::{Duration, Instant},
127        tokio::time,
128    };
129
130    fn hosts(entries: &[(&str, usize)]) -> BTreeMap<String, usize> {
131        entries
132            .iter()
133            .map(|(host, cap)| ((*host).to_owned(), *cap))
134            .collect()
135    }
136
137    fn addresses(entries: &[(&str, usize)]) -> HashMap<IpAddr, usize> {
138        entries
139            .iter()
140            .map(|(address, cap)| (address.parse().unwrap(), *cap))
141            .collect()
142    }
143
144    /// Poll until the lookup `hosts` started has been applied.
145    async fn applied(cache: &Arc<OverrideAddresses>, hosts: &BTreeMap<String, usize>) {
146        let deadline = Instant::now() + Duration::from_secs(10);
147        while cache.state().applied != *hosts {
148            assert!(Instant::now() < deadline, "the lookup never finished");
149            time::sleep(Duration::from_millis(10)).await;
150        }
151    }
152
153    #[tokio::test]
154    async fn literals_apply_at_once_and_a_v4_mapped_address_canonicalises() {
155        let cache = Arc::new(OverrideAddresses::default());
156        let got = cache.addresses(&hosts(&[
157            ("127.0.0.2", 3),
158            ("::ffff:10.0.0.1", 1),
159            ("::1", 2),
160        ]));
161        assert!(*got == addresses(&[("127.0.0.2", 3), ("10.0.0.1", 1), ("::1", 2)]));
162    }
163
164    #[tokio::test]
165    async fn a_host_name_resolves_in_the_background_and_the_previous_set_holds_meanwhile() {
166        let cache = Arc::new(OverrideAddresses::default());
167        let previous = cache.addresses(&hosts(&[("127.0.0.2", 3)]));
168        let named = hosts(&[("localhost", 4)]);
169        assert!(cache.addresses(&named) == previous);
170        applied(&cache, &named).await;
171        let got = cache.addresses(&named);
172        assert!(got.values().copied().collect::<Vec<_>>() == vec![4]);
173        assert!(got.keys().all(IpAddr::is_loopback));
174    }
175
176    #[tokio::test]
177    async fn an_unresolvable_set_keeps_the_previous_addresses() {
178        let cache = Arc::new(OverrideAddresses::default());
179        let previous = cache.addresses(&hosts(&[("127.0.0.2", 3)]));
180        let unresolvable = hosts(&[("no-such-host.invalid", 1)]);
181        drop(cache.addresses(&unresolvable));
182        applied(&cache, &unresolvable).await;
183        assert!(cache.addresses(&unresolvable) == previous);
184    }
185}