karyon_p2p/discovery/kademlia/
refresh.rs1use std::{
2 collections::HashMap,
3 net::{IpAddr, SocketAddr},
4 sync::Arc,
5 time::{Duration, Instant},
6};
7
8use log::{error, info, trace};
9use rand::{rngs::OsRng, TryRngCore};
10
11use karyon_core::{
12 async_runtime::Executor,
13 async_util::{sleep, timeout, Backoff, TaskGroup, TaskResult},
14};
15
16use karyon_net::{udp, Endpoint};
17
18use crate::{
19 access_control::{Action, Subject},
20 discovery::kademlia::{
21 messages::RefreshMsg,
22 routing_table::{BucketEntry, Entry, RoutingTable, PENDING_ENTRY, UNREACHABLE_ENTRY},
23 },
24 message::{pick_endpoint, Protocol},
25 monitor::{ConnectionKind, DiscoveryKind, Monitor},
26 peer::ConnDirection,
27 util::{decode, encode},
28 Config, Error, Result,
29};
30
31pub const MAX_FAILURES: u32 = 3;
33
34const MAX_UDP_BUF: usize = 1024;
36
37const REFRESH_PER_BUCKET: usize = 8;
39
40const RL_CAPACITY: u32 = 5;
43const RL_REFILL_PER_SEC: f64 = 0.5;
44
45struct RateBucket {
47 tokens: f64,
48 last_refill: Instant,
49}
50
51impl RateBucket {
52 fn new() -> Self {
53 Self {
54 tokens: RL_CAPACITY as f64,
55 last_refill: Instant::now(),
56 }
57 }
58
59 fn allow(&mut self) -> bool {
61 let now = Instant::now();
62 let elapsed = now.duration_since(self.last_refill).as_secs_f64();
63 self.tokens = (self.tokens + elapsed * RL_REFILL_PER_SEC).min(RL_CAPACITY as f64);
64 self.last_refill = now;
65 if self.tokens >= 1.0 {
66 self.tokens -= 1.0;
67 true
68 } else {
69 false
70 }
71 }
72}
73
74pub struct RefreshService {
75 table: Arc<RoutingTable>,
77
78 listen_endpoint: Option<Endpoint>,
80
81 task_group: TaskGroup,
83
84 config: Arc<Config>,
86
87 monitor: Arc<Monitor>,
89}
90
91impl RefreshService {
92 pub fn new(
94 config: Arc<Config>,
95 table: Arc<RoutingTable>,
96 monitor: Arc<Monitor>,
97 listen_endpoint: Option<Endpoint>,
98 executor: Executor,
99 ) -> Self {
100 Self {
101 table,
102 listen_endpoint,
103 task_group: TaskGroup::with_executor(executor.clone()),
104 config,
105 monitor,
106 }
107 }
108
109 pub async fn start(self: &Arc<Self>) -> Result<()> {
111 if let Some(endpoint) = self.listen_endpoint.clone() {
112 self.task_group.spawn_then(
113 {
114 let this = self.clone();
115 async move { this.listen_loop(endpoint).await }
116 },
117 |res| async move {
118 if let TaskResult::Completed(Err(err)) = res {
119 error!("Listen loop stopped: {err}");
120 }
121 },
122 );
123 }
124
125 self.task_group.spawn_then(
126 {
127 let this = self.clone();
128 async move { this.refresh_loop().await }
129 },
130 |res| async move {
131 if let TaskResult::Completed(Err(err)) = res {
132 error!("Refresh loop stopped: {err}");
133 }
134 },
135 );
136
137 Ok(())
138 }
139
140 pub async fn shutdown(&self) {
142 self.task_group.cancel().await;
143 }
144
145 async fn refresh_loop(self: Arc<Self>) -> Result<()> {
147 loop {
148 sleep(Duration::from_secs(self.config.refresh_interval)).await;
149 trace!("Start refreshing the routing table...");
150
151 self.monitor.notify(DiscoveryKind::RefreshStarted).await;
152
153 let entries = self.table.refresh_candidates(REFRESH_PER_BUCKET);
154 let succeeded = self.clone().do_refresh(&entries).await;
155
156 if !entries.is_empty() && succeeded == 0 {
159 self.monitor.notify(DiscoveryKind::RefreshFailed).await;
160 } else {
161 self.monitor
162 .notify(DiscoveryKind::RefreshSucceeded(succeeded))
163 .await;
164 }
165 }
166 }
167
168 async fn do_refresh(self: Arc<Self>, entries: &[BucketEntry]) -> usize {
171 use futures_util::stream::{FuturesUnordered, StreamExt};
172 let mut succeeded = 0;
173 for chunk in entries.chunks(16) {
174 let mut tasks = FuturesUnordered::new();
175 for bucket_entry in chunk {
176 if bucket_entry.failures >= MAX_FAILURES {
177 let pid = bucket_entry.entry.key.into();
178 self.table.remove_entry(&bucket_entry.entry.key);
179 self.monitor.notify(DiscoveryKind::EntryEvicted(pid)).await;
180 continue;
181 }
182 tasks.push(self.clone().refresh_entry(bucket_entry.clone()))
183 }
184 while let Some(ok) = tasks.next().await {
185 if ok {
186 succeeded += 1;
187 }
188 }
189 }
190 succeeded
191 }
192
193 async fn refresh_entry(self: Arc<Self>, bucket_entry: BucketEntry) -> bool {
196 let key = &bucket_entry.entry.key;
197 match self.connect(&bucket_entry.entry).await {
198 Ok(_) => {
199 self.table.update_entry(key, PENDING_ENTRY);
200 true
201 }
202 Err(err) => {
203 trace!("Failed to refresh entry {key:?}: {err}");
204 if bucket_entry.failures >= MAX_FAILURES {
205 let pid = (*key).into();
206 self.table.remove_entry(key);
207 self.monitor.notify(DiscoveryKind::EntryEvicted(pid)).await;
208 return false;
209 }
210 self.table.update_entry(key, UNREACHABLE_ENTRY);
211 false
212 }
213 }
214 }
215
216 async fn connect(&self, entry: &Entry) -> Result<()> {
218 let mut retry = 0;
219 let supported = [Protocol::Udp];
220 let endpoint = pick_endpoint(&entry.discovery_addrs, &supported)
221 .ok_or(Error::Lookup("No UDP discovery address available".into()))?;
222
223 if !self.config.access_control.allow(
224 &Subject::Endpoint(&endpoint),
225 Action::Probe(ConnDirection::Outbound),
226 ) {
227 return Err(Error::AccessDenied);
228 }
229
230 let conn = udp::dial(&endpoint, Default::default()).await?;
231 let peer_addr = SocketAddr::try_from(endpoint.clone())?;
232 let backoff = Backoff::new(100, 5000);
233 while retry < self.config.refresh_connect_retries {
234 match self.send_ping_msg(&conn, peer_addr).await {
235 Ok(()) => return Ok(()),
236 Err(Error::Timeout) => {
237 retry += 1;
238 backoff.sleep().await;
239 }
240 Err(err) => {
241 return Err(err);
242 }
243 }
244 }
245 Err(Error::Timeout)
246 }
247
248 async fn listen_loop(self: Arc<Self>, endpoint: Endpoint) -> Result<()> {
250 let conn = match udp::listen(&endpoint, Default::default()).await {
251 Ok(c) => {
252 self.monitor
253 .notify(ConnectionKind::Listening(endpoint.clone()))
254 .await;
255 c
256 }
257 Err(err) => {
258 self.monitor
259 .notify(ConnectionKind::ListenFailed(endpoint.clone()))
260 .await;
261 return Err(err.into());
262 }
263 };
264 info!("Start listening on {endpoint}");
265
266 let mut rate_limiter: HashMap<IpAddr, RateBucket> = HashMap::new();
269
270 loop {
271 let res = self.listen_to_ping_msg(&conn, &mut rate_limiter).await;
272 if let Err(err) = res {
273 trace!("Failed to handle ping msg {err}");
274 self.monitor.notify(ConnectionKind::AcceptFailed).await;
275 }
276 }
277 }
278
279 async fn listen_to_ping_msg(
283 &self,
284 conn: &udp::UdpConn,
285 rate_limiter: &mut HashMap<IpAddr, RateBucket>,
286 ) -> Result<()> {
287 let mut buf = vec![0u8; MAX_UDP_BUF];
288 let (n, sender) = conn.recv_from(&mut buf).await?;
289
290 let sender_ip = sender.ip();
291 if !self.table.has_discovery_ip(&sender_ip) {
292 trace!("Drop refresh ping from unknown source {sender_ip}");
293 return Ok(());
294 }
295
296 let allowed = rate_limiter
297 .entry(sender_ip)
298 .or_insert_with(RateBucket::new)
299 .allow();
300 if !allowed {
301 trace!("Drop rate-limited refresh ping from {sender_ip}");
302 return Ok(());
303 }
304
305 let sender_ep = Endpoint::new_udp_addr(sender);
306
307 if !self.config.access_control.allow(
308 &Subject::Endpoint(&sender_ep),
309 Action::Probe(ConnDirection::Inbound),
310 ) {
311 trace!("Drop refresh ping from denied source {sender_ip}");
312 return Ok(());
313 }
314
315 self.monitor
316 .notify(ConnectionKind::Accepted(sender_ep.clone()))
317 .await;
318
319 let (msg, _) = decode::<RefreshMsg>(&buf[..n])?;
320 match msg {
321 RefreshMsg::Ping(m) => {
322 let pong_msg = RefreshMsg::Pong(m);
323 let encoded = encode(&pong_msg)?;
324 conn.send_to(&encoded, sender).await?;
325 }
326 RefreshMsg::Pong(_) => return Err(Error::InvalidMsg("Unexpected pong msg".into())),
327 }
328
329 self.monitor
330 .notify(ConnectionKind::Disconnected(sender_ep))
331 .await;
332 Ok(())
333 }
334
335 async fn send_ping_msg(&self, conn: &udp::UdpConn, peer_addr: SocketAddr) -> Result<()> {
337 let mut nonce: [u8; 32] = [0; 32];
338 OsRng.try_fill_bytes(&mut nonce)?;
339
340 let ping = RefreshMsg::Ping(nonce);
341 let encoded = encode(&ping)?;
342 conn.send_to(&encoded, peer_addr).await?;
343
344 let t = Duration::from_secs(self.config.refresh_response_timeout);
345 let mut buf = vec![0u8; MAX_UDP_BUF];
346 let (n, _) = timeout(t, conn.recv_from(&mut buf)).await??;
347 let (msg, _) = decode::<RefreshMsg>(&buf[..n])?;
348
349 match msg {
350 RefreshMsg::Pong(n) => {
351 if n != nonce {
352 return Err(Error::InvalidPongMsg);
353 }
354 Ok(())
355 }
356 _ => Err(Error::InvalidMsg("Unexpected ping msg".into())),
357 }
358 }
359}