1use std::collections::HashMap;
13use std::hash::{BuildHasher, Hasher};
14#[cfg(test)]
15use std::io::Write;
16use std::io::{self, Read};
17use std::net::{IpAddr, Shutdown, TcpListener, TcpStream, ToSocketAddrs};
18use std::os::unix::io::AsRawFd;
19use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
20use std::sync::{Arc, Mutex, OnceLock};
21use std::thread;
22use std::time::{Duration, Instant};
23
24use polling::{Event as PollEvent, Events, Poller};
25use socket2::{SockRef, TcpKeepalive};
26
27use rns_core::constants;
28use rns_core::transport::types::{IngressControlConfig, InterfaceId, InterfaceInfo};
29
30use crate::event::{Event, EventSender};
31use crate::hdlc;
32use crate::interface::transmit_buffer::{
33 EgressController, TransmitBuffer, EGRESS_DEAD_TIME, EGRESS_HIGH_WATERMARK,
34};
35use crate::interface::{
36 lock_or_recover, poll_readable, wait_while_active, DynamicInterfaceTemplate, EgressControl,
37 InterfaceConfigData, InterfaceFactory, ListenerControl, StartContext, StartResult, Writer,
38};
39use crate::BackbonePeerStateEntry;
40
41pub(crate) const SERVER_BITRATE_GUESS: u64 = 100_000_000;
43pub(crate) const CLIENT_BITRATE_GUESS: u64 = 100_000_000;
45
46const fn auto_mtu_for_bitrate(bitrate: u64) -> Option<u32> {
47 if bitrate >= 1_000_000_000 {
48 Some(524_288)
49 } else if bitrate >= 750_000_000 {
50 Some(262_144)
51 } else if bitrate >= 400_000_000 {
52 Some(131_072)
53 } else if bitrate >= 200_000_000 {
54 Some(65_536)
55 } else if bitrate >= 100_000_000 {
56 Some(32_768)
57 } else if bitrate >= 10_000_000 {
58 Some(16_384)
59 } else if bitrate >= 5_000_000 {
60 Some(8_192)
61 } else if bitrate >= 2_000_000 {
62 Some(4_096)
63 } else if bitrate >= 1_000_000 {
64 Some(2_048)
65 } else if bitrate >= 62_500 {
66 Some(1_024)
67 } else {
68 None
69 }
70}
71
72pub(crate) const HW_MTU: usize = match auto_mtu_for_bitrate(CLIENT_BITRATE_GUESS) {
74 Some(mtu) => mtu as usize,
75 None => 1_048_576,
76};
77pub(crate) const SERVER_PEER_MTU: u32 = match auto_mtu_for_bitrate(SERVER_BITRATE_GUESS) {
79 Some(mtu) => mtu,
80 None => 1_048_576,
81};
82
83const DP_IC_HIGH_WM_PCT: usize = 90;
84const DP_IC_MID_WM_PCT: usize = 68;
85const DP_IC_LOW_WM_PCT: usize = 10;
86const DP_IC_INTERVAL: Duration = Duration::from_millis(250);
87const DP_IC_RCVBUF: usize = 32_768;
88const DP_IC_IF_HEADROOM: usize = 32;
89const DP_IC_PENALTY: f64 = 1.5;
90
91#[derive(Debug, Clone)]
93pub struct BackboneConfig {
94 pub name: String,
95 pub listen_ip: String,
96 pub listen_port: u16,
97 pub interface_id: InterfaceId,
98 pub mode: u8,
99 pub gravity: i64,
100 pub recursive_prs: bool,
101 pub announces_from_internal: bool,
102 pub announces_to_internal: Option<bool>,
103 pub max_connections: Option<usize>,
104 pub idle_timeout: Option<Duration>,
105 pub write_stall_timeout: Option<Duration>,
106 pub abuse: BackboneAbuseConfig,
107 pub fast_flap: BackboneFastFlapConfig,
108 pub fast_flap_state: Arc<Mutex<BackboneFastFlapMonitor>>,
109 pub ingress_control: IngressControlConfig,
110 pub runtime: Arc<Mutex<BackboneServerRuntime>>,
111 pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
112}
113
114#[derive(Debug, Clone, Default)]
116pub struct BackboneAbuseConfig {
117 pub max_penalty_duration: Option<Duration>,
118}
119
120#[derive(Debug, Clone, Copy, Eq, PartialEq)]
123pub struct BackboneFastFlapConfig {
124 pub enabled: bool,
125 pub connection_threshold: Duration,
126 pub grace: u64,
127 pub block_duration: Duration,
128}
129
130impl Default for BackboneFastFlapConfig {
131 fn default() -> Self {
132 Self {
133 enabled: true,
134 connection_threshold: Duration::from_secs(20),
135 grace: 5,
136 block_duration: Duration::from_secs(12 * 60 * 60),
137 }
138 }
139}
140
141#[derive(Debug, Clone)]
142struct FastFlapPeerState {
143 first_flap_at: Instant,
144 last_flap_at: Instant,
145 flap_count: u64,
146}
147
148#[derive(Debug, Clone, Copy, Eq, PartialEq)]
150pub struct FastFlapRecord {
151 pub flap_count: u64,
152 pub blocked: bool,
153 pub newly_blocked: bool,
154 pub blocked_until: Option<Instant>,
155 pub elapsed_since_first: Duration,
156}
157
158#[derive(Debug, Default)]
161pub struct BackboneFastFlapMonitor {
162 peers: HashMap<IpAddr, FastFlapPeerState>,
163}
164
165impl BackboneFastFlapMonitor {
166 pub fn new() -> Self {
167 Self::default()
168 }
169
170 pub fn record_disconnect(
171 &mut self,
172 peer_ip: IpAddr,
173 connected_for: Duration,
174 config: &BackboneFastFlapConfig,
175 now: Instant,
176 ) -> Option<FastFlapRecord> {
177 if !config.enabled || connected_for >= config.connection_threshold {
178 return None;
179 }
180
181 self.expire_peer(peer_ip, config, now);
182 let state = self.peers.entry(peer_ip).or_insert(FastFlapPeerState {
183 first_flap_at: now,
184 last_flap_at: now,
185 flap_count: 0,
186 });
187 let was_blocked = state.flap_count > config.grace;
188 state.last_flap_at = now;
189 state.flap_count = state.flap_count.saturating_add(1);
190 let blocked = state.flap_count > config.grace;
191
192 Some(FastFlapRecord {
193 flap_count: state.flap_count,
194 blocked,
195 newly_blocked: blocked && !was_blocked,
196 blocked_until: blocked
197 .then(|| now.checked_add(config.block_duration))
198 .flatten(),
199 elapsed_since_first: now.saturating_duration_since(state.first_flap_at),
200 })
201 }
202
203 pub fn is_blocked(
204 &mut self,
205 peer_ip: IpAddr,
206 config: &BackboneFastFlapConfig,
207 now: Instant,
208 ) -> bool {
209 self.blocked_until(peer_ip, config, now).is_some()
210 }
211
212 pub fn blocked_until(
213 &mut self,
214 peer_ip: IpAddr,
215 config: &BackboneFastFlapConfig,
216 now: Instant,
217 ) -> Option<Instant> {
218 if !config.enabled {
219 return None;
220 }
221 self.expire_peer(peer_ip, config, now);
222 self.peers
223 .get(&peer_ip)
224 .filter(|state| state.flap_count > config.grace)
225 .and_then(|state| state.last_flap_at.checked_add(config.block_duration))
226 }
227
228 pub fn blocked_ip_count(&mut self, config: &BackboneFastFlapConfig, now: Instant) -> usize {
229 if !config.enabled {
230 return 0;
231 }
232 self.expire_all(config, now);
233 self.peers
234 .values()
235 .filter(|state| state.flap_count > config.grace)
236 .count()
237 }
238
239 pub fn blocked_ip_list(
240 &mut self,
241 config: &BackboneFastFlapConfig,
242 now: Instant,
243 ) -> Vec<IpAddr> {
244 if !config.enabled {
245 return Vec::new();
246 }
247 self.expire_all(config, now);
248 let mut blocked: Vec<_> = self
249 .peers
250 .iter()
251 .filter(|(_, state)| state.flap_count > config.grace)
252 .map(|(peer_ip, _)| *peer_ip)
253 .collect();
254 blocked.sort_unstable();
255 blocked
256 }
257
258 pub fn flap_count(&self, peer_ip: IpAddr) -> u64 {
259 self.peers
260 .get(&peer_ip)
261 .map(|state| state.flap_count)
262 .unwrap_or(0)
263 }
264
265 pub fn clear(&mut self, peer_ip: IpAddr) -> bool {
266 self.peers.remove(&peer_ip).is_some()
267 }
268
269 fn expire_peer(&mut self, peer_ip: IpAddr, config: &BackboneFastFlapConfig, now: Instant) {
270 let expired = self.peers.get(&peer_ip).is_some_and(|state| {
271 now.saturating_duration_since(state.last_flap_at) > config.block_duration
272 });
273 if expired {
274 self.peers.remove(&peer_ip);
275 }
276 }
277
278 fn expire_all(&mut self, config: &BackboneFastFlapConfig, now: Instant) {
279 self.peers.retain(|_, state| {
280 now.saturating_duration_since(state.last_flap_at) <= config.block_duration
281 });
282 }
283}
284
285fn shared_fast_flap_monitor() -> Arc<Mutex<BackboneFastFlapMonitor>> {
286 static MONITOR: OnceLock<Arc<Mutex<BackboneFastFlapMonitor>>> = OnceLock::new();
287 Arc::clone(MONITOR.get_or_init(|| Arc::new(Mutex::new(BackboneFastFlapMonitor::new()))))
288}
289
290#[derive(Debug, Clone)]
292pub struct BackboneServerRuntime {
293 pub max_connections: Option<usize>,
294 pub idle_timeout: Option<Duration>,
295 pub write_stall_timeout: Option<Duration>,
296 pub abuse: BackboneAbuseConfig,
297}
298
299impl BackboneServerRuntime {
300 pub fn from_config(config: &BackboneConfig) -> Self {
301 Self {
302 max_connections: config.max_connections,
303 idle_timeout: config.idle_timeout,
304 write_stall_timeout: config.write_stall_timeout,
305 abuse: config.abuse.clone(),
306 }
307 }
308}
309
310#[derive(Debug, Clone)]
311pub struct BackboneRuntimeConfigHandle {
312 pub interface_name: String,
313 pub runtime: Arc<Mutex<BackboneServerRuntime>>,
314 pub startup: BackboneServerRuntime,
315}
316
317#[derive(Debug, Clone)]
318pub struct BackbonePeerStateHandle {
319 pub interface_id: InterfaceId,
320 pub interface_name: String,
321 pub mode: u8,
322 pub gravity: i64,
323 pub announces_to_internal: Option<bool>,
324 pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
325 pub fast_flap: BackboneFastFlapConfig,
326 pub fast_flap_state: Arc<Mutex<BackboneFastFlapMonitor>>,
327}
328
329impl Default for BackboneConfig {
330 fn default() -> Self {
331 let mut config = BackboneConfig {
332 name: String::new(),
333 listen_ip: "0.0.0.0".into(),
334 listen_port: 0,
335 interface_id: InterfaceId(0),
336 mode: constants::MODE_FULL,
337 gravity: 0,
338 recursive_prs: false,
339 announces_from_internal: true,
340 announces_to_internal: None,
341 max_connections: None,
342 idle_timeout: None,
343 write_stall_timeout: None,
344 abuse: BackboneAbuseConfig::default(),
345 fast_flap: BackboneFastFlapConfig::default(),
346 fast_flap_state: shared_fast_flap_monitor(),
347 ingress_control: IngressControlConfig::enabled(),
348 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
349 max_connections: None,
350 idle_timeout: None,
351 write_stall_timeout: None,
352 abuse: BackboneAbuseConfig::default(),
353 })),
354 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
355 };
356 let startup = BackboneServerRuntime::from_config(&config);
357 config.runtime = Arc::new(Mutex::new(startup));
358 config
359 }
360}
361
362struct BackboneWriter {
369 stream: TcpStream,
370 runtime: Arc<Mutex<BackboneServerRuntime>>,
371 interface_name: String,
372 interface_id: InterfaceId,
373 event_tx: EventSender,
374 transmit_buffer: TransmitBuffer,
375 egress_controller: EgressController,
376 egress_control: EgressControl,
377 egress_started: Instant,
378 stall_started: Option<Instant>,
379 disconnect_notified: bool,
380 write_stall_flag: Arc<AtomicBool>,
381}
382
383impl Writer for BackboneWriter {
384 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
385 if !self
388 .transmit_buffer
389 .append_with_limit(hdlc::frame(data), Some(EGRESS_HIGH_WATERMARK))
390 {
391 return Err(io::Error::new(
392 io::ErrorKind::InvalidInput,
393 "confirmed frame exceeds backbone buffer",
394 ));
395 }
396 self.transmit_buffer.flush();
397 let timeout = lock_or_recover(&self.runtime, "backbone runtime").write_stall_timeout;
398 self.drain_transmit(timeout)
399 }
400 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
401 self.send_frames(&[data.to_vec()])
402 }
403
404 fn send_frames(&mut self, frames: &[Vec<u8>]) -> io::Result<()> {
405 let write_stall_timeout =
406 lock_or_recover(&self.runtime, "backbone runtime").write_stall_timeout;
407 if self.transmit_buffer.buffered_bytes() > 0 {
408 self.drain_transmit(write_stall_timeout)?;
409 if self.transmit_buffer.buffered_bytes() > 0 {
410 return Err(io::Error::new(
411 io::ErrorKind::WouldBlock,
412 "backbone writer still stalled",
413 ));
414 }
415 }
416
417 let elapsed = self.egress_started.elapsed();
418 let _ = self.egress_controller.evaluate(
419 elapsed,
420 self.transmit_buffer.buffered_bytes(),
421 self.transmit_buffer.sendable_bytes(),
422 self.transmit_buffer.total_sent_bytes(),
423 );
424 self.egress_control
425 .set_stalled(self.egress_controller.stalled());
426 for frame in frames {
427 let encoded = hdlc::frame(frame);
428 let encoded_len = encoded.len();
429 if self.egress_control.stalled()
430 || !self
431 .transmit_buffer
432 .append_with_limit(encoded, Some(EGRESS_HIGH_WATERMARK))
433 {
434 self.egress_control.record_drop(encoded_len);
435 log::debug!(
436 "[{}:{}] egress control dropped outbound frame of {} bytes",
437 self.interface_name,
438 self.interface_id.0,
439 encoded_len
440 );
441 }
442 }
443 self.transmit_buffer.flush();
444 self.drain_transmit(write_stall_timeout)
445 }
446
447 fn flush(&mut self) -> io::Result<()> {
448 let timeout = lock_or_recover(&self.runtime, "backbone runtime").write_stall_timeout;
449 self.drain_transmit(timeout)
450 }
451
452 fn egress_control(&self) -> Option<EgressControl> {
453 Some(self.egress_control.clone())
454 }
455
456 fn shutdown(&mut self) {
457 let _ = self.stream.shutdown(Shutdown::Both);
458 }
459}
460
461impl BackboneWriter {
462 fn drain_transmit(&mut self, write_stall_timeout: Option<Duration>) -> io::Result<()> {
463 if self.transmit_buffer.buffered_bytes() == 0 {
464 return Ok(());
465 }
466
467 let written = self.transmit_buffer.drain_to(&mut self.stream)?;
468 if written > 0 {
469 self.stall_started = None;
470 }
471 let elapsed = self.egress_started.elapsed();
472 let dead = self.egress_controller.evaluate(
473 elapsed,
474 self.transmit_buffer.buffered_bytes(),
475 self.transmit_buffer.sendable_bytes(),
476 self.transmit_buffer.total_sent_bytes(),
477 );
478 self.egress_control
479 .set_stalled(self.egress_controller.stalled());
480 if dead {
481 return Err(self.disconnect_for_write_stall(EGRESS_DEAD_TIME));
482 }
483 if self.transmit_buffer.buffered_bytes() == 0 {
484 return Ok(());
485 }
486
487 let now = Instant::now();
488 let started = self.stall_started.get_or_insert(now);
489 let timeout = write_stall_timeout.unwrap_or(EGRESS_DEAD_TIME);
490 if now.duration_since(*started) >= timeout {
491 return Err(self.disconnect_for_write_stall(timeout));
492 }
493 Err(io::Error::new(
494 io::ErrorKind::WouldBlock,
495 "backbone writer would block",
496 ))
497 }
498
499 fn disconnect_for_write_stall(&mut self, timeout: Duration) -> io::Error {
500 if !self.disconnect_notified {
501 log::warn!(
502 "[{}] backbone client {} disconnected due to write stall timeout ({:?})",
503 self.interface_name,
504 self.interface_id.0,
505 timeout
506 );
507 self.write_stall_flag.store(true, Ordering::Relaxed);
508 let _ = self.stream.shutdown(Shutdown::Both);
509 let _ = self.event_tx.send(Event::InterfaceDown(self.interface_id));
510 self.disconnect_notified = true;
511 }
512 io::Error::new(
513 io::ErrorKind::TimedOut,
514 format!("backbone writer stalled for {:?}", timeout),
515 )
516 }
517}
518
519pub fn start(config: BackboneConfig, tx: EventSender, next_id: Arc<AtomicU64>) -> io::Result<()> {
521 start_with_template(config, tx, next_id, None, None)
522}
523
524fn start_with_template(
525 config: BackboneConfig,
526 tx: EventSender,
527 next_id: Arc<AtomicU64>,
528 dynamic_template: Option<DynamicInterfaceTemplate>,
529 underlay_mark: Option<u32>,
530) -> io::Result<()> {
531 let addr = format!("{}:{}", config.listen_ip, config.listen_port);
532 let listener = TcpListener::bind(&addr)?;
533 super::apply_underlay_mark(listener.as_raw_fd(), underlay_mark)?;
534 listener.set_nonblocking(true)?;
535
536 log::info!(
537 "[{}] backbone server listening on {}",
538 config.name,
539 listener
540 .local_addr()
541 .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], config.listen_port)))
542 );
543
544 let name = config.name.clone();
545 let server_interface_id = config.interface_id;
546 let runtime = Arc::clone(&config.runtime);
547 let peer_state = Arc::clone(&config.peer_state);
548 let fast_flap = config.fast_flap;
549 let fast_flap_state = Arc::clone(&config.fast_flap_state);
550 let ingress_control = config.ingress_control;
551 let accepted_peer_mode = config.mode;
552 let accepted_peer_gravity = config.gravity;
553 let accepted_peer_recursive_prs = config.recursive_prs;
554 let accepted_peer_announces_from_internal = config.announces_from_internal;
555 let accepted_peer_announces_to_internal = config.announces_to_internal;
556 let ifac_size = dynamic_template
557 .as_ref()
558 .and_then(|template| template.ifac.as_ref())
559 .map(|ifac| ifac.size)
560 .unwrap_or(0);
561 thread::Builder::new()
562 .name(format!("backbone-poll-{}", config.interface_id.0))
563 .spawn(move || {
564 if let Err(e) = poll_loop(PollLoopContext {
565 listener,
566 name,
567 server_interface_id,
568 tx,
569 next_id,
570 runtime,
571 peer_state,
572 fast_flap,
573 fast_flap_state,
574 ingress_control,
575 accepted_peer_mode,
576 accepted_peer_gravity,
577 accepted_peer_recursive_prs,
578 accepted_peer_announces_from_internal,
579 accepted_peer_announces_to_internal,
580 dynamic_template,
581 ifac_size,
582 underlay_mark,
583 }) {
584 log::error!("backbone poll loop error: {}", e);
585 }
586 })?;
587
588 Ok(())
589}
590
591struct ClientState {
593 id: InterfaceId,
594 peer_ip: IpAddr,
595 peer_port: u16,
596 stream: TcpStream,
597 decoder: hdlc::Decoder,
598 connected_at: Instant,
599 has_received_data: bool,
600 write_stall_flag: Arc<AtomicBool>,
601 dp_ingress_bytes: u64,
602 dp_ingress_packets: u64,
603 dp_ingress_hold: Option<Instant>,
604 dp_ingress_gated: bool,
605 dp_ingress_throttle_count: u64,
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq)]
609struct IngressThresholds {
610 high: usize,
611 immediate: usize,
612 mid: usize,
613 low: usize,
614}
615
616fn ingress_thresholds(data_capacity: usize) -> IngressThresholds {
617 let high = ((DP_IC_HIGH_WM_PCT * data_capacity) / 100).max(4);
620 IngressThresholds {
621 high,
622 immediate: high.max(128),
623 mid: ((DP_IC_MID_WM_PCT * data_capacity) / 100).max(2),
624 low: (DP_IC_LOW_WM_PCT * data_capacity) / 100,
625 }
626}
627
628fn busiest_ingress_key(producers: impl Iterator<Item = (usize, u64)>) -> Option<usize> {
629 producers
630 .filter(|(_, packets)| *packets > 0)
631 .max_by_key(|(_, packets)| *packets)
632 .map(|(key, _)| key)
633}
634
635fn throttle_busiest_ingress(
636 poller: &Poller,
637 clients: &mut HashMap<usize, ClientState>,
638 sample_span: Duration,
639) -> io::Result<Option<InterfaceId>> {
640 let Some(key) = busiest_ingress_key(
641 clients
642 .iter()
643 .map(|(key, client)| (*key, client.dp_ingress_packets)),
644 ) else {
645 return Ok(None);
646 };
647 if clients
648 .get(&key)
649 .is_some_and(|client| client.dp_ingress_gated)
650 {
651 return Ok(None);
652 }
653
654 let allocation_divisor = clients.len().max(DP_IC_IF_HEADROOM) as f64 * DP_IC_PENALTY;
655 let hold = Duration::from_secs_f64(
656 (sample_span.as_secs_f64().max(0.001) * allocation_divisor).max(0.001),
657 );
658 let client = clients.get_mut(&key).expect("selected client disappeared");
659 poller.modify(&client.stream, PollEvent::none(key))?;
660 client.dp_ingress_gated = true;
661 client.dp_ingress_hold = Some(Instant::now() + hold);
662 client.dp_ingress_throttle_count = client.dp_ingress_throttle_count.saturating_add(1);
663 log::info!(
664 "ingress throttled on BackboneInterface/{} for {:.3}s",
665 client.id.0,
666 hold.as_secs_f64(),
667 );
668 Ok(Some(client.id))
669}
670
671fn release_one_ingress(
672 poller: &Poller,
673 clients: &mut HashMap<usize, ClientState>,
674 now: Instant,
675) -> io::Result<Option<InterfaceId>> {
676 let Some(key) = clients
677 .iter()
678 .find(|(_, client)| {
679 client.dp_ingress_gated
680 && client
681 .dp_ingress_hold
682 .is_none_or(|hold_until| now >= hold_until)
683 })
684 .map(|(key, _)| *key)
685 else {
686 return Ok(None);
687 };
688 let client = clients.get_mut(&key).expect("selected client disappeared");
689 poller.modify(&client.stream, PollEvent::readable(key))?;
690 client.dp_ingress_gated = false;
691 client.dp_ingress_hold = None;
692 log::info!(
693 "released ingress throttle on BackboneInterface/{}",
694 client.id.0
695 );
696 Ok(Some(client.id))
697}
698
699#[derive(Debug, Clone)]
700struct PeerBehaviorState {
701 blacklisted_until: Option<Instant>,
702 blacklist_reason: Option<String>,
703 reject_count: u64,
704 connected_count: usize,
705}
706
707impl PeerBehaviorState {
708 fn new() -> Self {
709 Self {
710 blacklisted_until: None,
711 blacklist_reason: None,
712 reject_count: 0,
713 connected_count: 0,
714 }
715 }
716}
717
718#[derive(Debug, Clone, Default)]
719pub struct BackbonePeerMonitor {
720 peers: HashMap<IpAddr, PeerBehaviorState>,
721}
722
723impl BackbonePeerMonitor {
724 pub fn new() -> Self {
725 Self {
726 peers: HashMap::new(),
727 }
728 }
729
730 fn upsert_snapshot(&mut self, peers: &HashMap<IpAddr, PeerBehaviorState>) {
731 let mut merged = self.peers.clone();
732
733 for (peer_ip, state) in peers {
734 let entry = merged
735 .entry(*peer_ip)
736 .or_insert_with(PeerBehaviorState::new);
737 entry.connected_count = state.connected_count;
738 entry.reject_count = state.reject_count;
739 entry.blacklisted_until = state.blacklisted_until;
740 entry.blacklist_reason = state.blacklist_reason.clone();
741 }
742
743 merged.retain(|peer_ip, state| {
744 peers.contains_key(peer_ip)
745 || state.blacklisted_until.is_some()
746 || state.reject_count > 0
747 });
748 self.peers = merged;
749 }
750
751 fn sync_into(&self, peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
752 for (peer_ip, state) in &self.peers {
753 let entry = peers.entry(*peer_ip).or_insert_with(PeerBehaviorState::new);
754 entry.blacklisted_until = state.blacklisted_until;
755 entry.blacklist_reason = state.blacklist_reason.clone();
756 entry.reject_count = state.reject_count;
757 }
758
759 peers.retain(|peer_ip, state| {
760 if state.connected_count > 0 {
761 return true;
762 }
763 self.peers.contains_key(peer_ip)
764 });
765 }
766
767 pub fn list(&self, interface_name: &str) -> Vec<BackbonePeerStateEntry> {
768 let now = Instant::now();
769 let mut entries: Vec<BackbonePeerStateEntry> = self
770 .peers
771 .iter()
772 .map(|(peer_ip, state)| BackbonePeerStateEntry {
773 interface_name: interface_name.to_string(),
774 peer_ip: *peer_ip,
775 connected_count: state.connected_count,
776 blacklisted_remaining_secs: state
777 .blacklisted_until
778 .and_then(|until| (until > now).then(|| (until - now).as_secs_f64())),
779 blacklist_reason: state.blacklist_reason.clone(),
780 reject_count: state.reject_count,
781 })
782 .collect();
783 entries.sort_by_key(|a| a.peer_ip);
784 entries
785 }
786
787 pub fn clear(&mut self, peer_ip: IpAddr) -> bool {
788 self.peers.remove(&peer_ip).is_some()
789 }
790
791 pub fn blacklist(&mut self, peer_ip: IpAddr, duration: Duration, reason: String) -> bool {
792 let state = self
793 .peers
794 .entry(peer_ip)
795 .or_insert_with(PeerBehaviorState::new);
796 state.blacklisted_until = Some(Instant::now() + duration);
797 state.blacklist_reason = Some(reason);
798 true
799 }
800
801 #[cfg(test)]
802 pub fn seed_entry(&mut self, entry: BackbonePeerStateEntry) {
803 let mut state = PeerBehaviorState::new();
804 state.connected_count = entry.connected_count;
805 state.reject_count = entry.reject_count;
806 state.blacklist_reason = entry.blacklist_reason;
807 if let Some(remaining) = entry.blacklisted_remaining_secs {
808 state.blacklisted_until = Some(Instant::now() + Duration::from_secs_f64(remaining));
809 }
810 self.peers.insert(entry.peer_ip, state);
811 }
812}
813
814#[derive(Clone, Copy)]
815enum DisconnectReason {
816 RemoteClosed,
817 IdleTimeout,
818 WriteStall,
819}
820
821struct PollLoopContext {
823 listener: TcpListener,
824 name: String,
825 server_interface_id: InterfaceId,
826 tx: EventSender,
827 next_id: Arc<AtomicU64>,
828 runtime: Arc<Mutex<BackboneServerRuntime>>,
829 peer_state: Arc<Mutex<BackbonePeerMonitor>>,
830 fast_flap: BackboneFastFlapConfig,
831 fast_flap_state: Arc<Mutex<BackboneFastFlapMonitor>>,
832 ingress_control: IngressControlConfig,
833 accepted_peer_mode: u8,
834 accepted_peer_gravity: i64,
835 accepted_peer_recursive_prs: bool,
836 accepted_peer_announces_from_internal: bool,
837 accepted_peer_announces_to_internal: Option<bool>,
838 dynamic_template: Option<DynamicInterfaceTemplate>,
839 ifac_size: usize,
840 underlay_mark: Option<u32>,
841}
842
843fn poll_loop(context: PollLoopContext) -> io::Result<()> {
844 let PollLoopContext {
845 listener,
846 name,
847 server_interface_id,
848 tx,
849 next_id,
850 runtime,
851 peer_state,
852 fast_flap,
853 fast_flap_state,
854 ingress_control,
855 accepted_peer_mode,
856 accepted_peer_gravity,
857 accepted_peer_recursive_prs,
858 accepted_peer_announces_from_internal,
859 accepted_peer_announces_to_internal,
860 dynamic_template,
861 ifac_size,
862 underlay_mark,
863 } = context;
864 let ingress_thresholds = ingress_thresholds(tx.inbound_queue_snapshot().capacities[0]);
867 let poller = Poller::new()?;
868
869 const LISTENER_KEY: usize = 0;
870
871 unsafe { poller.add(&listener, PollEvent::readable(LISTENER_KEY))? };
873
874 let mut clients: HashMap<usize, ClientState> = HashMap::new();
875 let mut peers: HashMap<IpAddr, PeerBehaviorState> = HashMap::new();
876 let mut events = Events::new();
877 let mut next_key: usize = 1;
878 let mut ingress_snapshot_at = Instant::now();
879
880 loop {
881 let runtime_snapshot = runtime.lock().unwrap().clone();
882 let max_connections = runtime_snapshot.max_connections;
883 let idle_timeout = runtime_snapshot.idle_timeout;
884 {
885 let mut monitor = peer_state.lock().unwrap();
886 monitor.sync_into(&mut peers);
887 cleanup_peer_state(&mut peers);
888 monitor.upsert_snapshot(&peers);
889 }
890
891 events.clear();
892 poller.wait(&mut events, Some(DP_IC_INTERVAL))?;
893
894 for ev in events.iter() {
895 if ev.key == LISTENER_KEY {
896 loop {
898 match listener.accept() {
899 Ok((stream, peer_addr)) => {
900 if let Err(error) =
901 super::apply_underlay_mark(stream.as_raw_fd(), underlay_mark)
902 {
903 log::error!(
904 "[{}] failed to mark accepted underlay socket: {}",
905 name,
906 error
907 );
908 drop(stream);
909 continue;
910 }
911 let peer_ip = peer_addr.ip();
912 let peer_port = peer_addr.port();
913
914 let fast_flap_blocked_until =
915 lock_or_recover(&fast_flap_state, "backbone fast-flap state")
916 .blocked_until(peer_ip, &fast_flap, Instant::now());
917 if is_ip_blacklisted(&mut peers, peer_ip)
918 || fast_flap_blocked_until.is_some()
919 {
920 let state =
921 peers.entry(peer_ip).or_insert_with(PeerBehaviorState::new);
922 if let Some(blocked_until) = fast_flap_blocked_until {
923 state.blacklisted_until = Some(blocked_until);
924 state.blacklist_reason = Some("fast flapping".into());
925 }
926 if let Some(state) = peers.get_mut(&peer_ip) {
927 state.reject_count = state.reject_count.saturating_add(1);
928 }
929 peer_state.lock().unwrap().upsert_snapshot(&peers);
930 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
931 "[{}] rejecting blocked peer {}",
932 name,
933 peer_addr,
934 );
935 drop(stream);
936 continue;
937 }
938
939 if let Some(max) = max_connections {
940 if clients.len() >= max {
941 log::warn!(
942 "[{}] max connections ({}) reached, rejecting {}",
943 name,
944 max,
945 peer_addr
946 );
947 drop(stream);
948 continue;
949 }
950 }
951
952 stream.set_nonblocking(true).ok();
953 stream.set_nodelay(true).ok();
954 set_tcp_keepalive(&stream).ok();
955 SockRef::from(&stream)
956 .set_recv_buffer_size(DP_IC_RCVBUF)
957 .ok();
958
959 #[cfg(target_os = "macos")]
961 {
962 let sock = SockRef::from(&stream);
963 sock.set_nosigpipe(true).ok();
964 }
965
966 let key = next_key;
967 next_key += 1;
968 let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
969
970 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
971 "[{}] backbone client connected: {} → id {}",
972 name,
973 peer_addr,
974 client_id.0
975 );
976
977 if let Err(e) = unsafe { poller.add(&stream, PollEvent::readable(key)) }
980 {
981 log::warn!("[{}] failed to add client to poller: {}", name, e);
982 continue; }
984
985 let writer_stream = match stream.try_clone() {
987 Ok(s) => s,
988 Err(e) => {
989 log::warn!("[{}] failed to clone client stream: {}", name, e);
990 let _ = poller.delete(&stream);
991 continue; }
993 };
994 let write_stall_flag = Arc::new(AtomicBool::new(false));
995 let writer: Box<dyn Writer> = Box::new(BackboneWriter {
996 stream: writer_stream,
997 runtime: Arc::clone(&runtime),
998 interface_name: name.clone(),
999 interface_id: client_id,
1000 event_tx: tx.clone(),
1001 transmit_buffer: TransmitBuffer::new(),
1002 egress_controller: EgressController::default(),
1003 egress_control: EgressControl::new(EGRESS_HIGH_WATERMARK),
1004 egress_started: Instant::now(),
1005 stall_started: None,
1006 disconnect_notified: false,
1007 write_stall_flag: Arc::clone(&write_stall_flag),
1008 });
1009
1010 clients.insert(
1011 key,
1012 ClientState {
1013 id: client_id,
1014 peer_ip,
1015 peer_port,
1016 stream,
1017 decoder: hdlc::Decoder::reticulum(HW_MTU, ifac_size),
1018 connected_at: Instant::now(),
1019 has_received_data: false,
1020 write_stall_flag,
1021 dp_ingress_bytes: 0,
1022 dp_ingress_packets: 0,
1023 dp_ingress_hold: None,
1024 dp_ingress_gated: false,
1025 dp_ingress_throttle_count: 0,
1026 },
1027 );
1028 peers
1029 .entry(peer_ip)
1030 .or_insert_with(PeerBehaviorState::new)
1031 .connected_count += 1;
1032 peer_state.lock().unwrap().upsert_snapshot(&peers);
1033 let _ = tx.send(Event::BackbonePeerConnected {
1034 server_interface_id,
1035 peer_interface_id: client_id,
1036 peer_ip,
1037 peer_port,
1038 });
1039
1040 let info = InterfaceInfo {
1041 id: client_id,
1042 name: format!("BackboneInterface/{}", client_id.0),
1043 mode: accepted_peer_mode,
1044 gravity: accepted_peer_gravity,
1045 recursive_prs: accepted_peer_recursive_prs,
1046 announces_from_internal: accepted_peer_announces_from_internal,
1047 announces_to_internal: accepted_peer_announces_to_internal,
1048 out_capable: true,
1049 in_capable: true,
1050 bitrate: Some(SERVER_BITRATE_GUESS),
1051 airtime_profile: None,
1052 announce_rate_target: None,
1053 announce_rate_grace: 0,
1054 announce_rate_penalty: 0.0,
1055 announce_cap: constants::ANNOUNCE_CAP,
1056 is_local_client: false,
1057 wants_tunnel: false,
1058 tunnel_id: None,
1059 mtu: SERVER_PEER_MTU,
1060 ia_freq: 0.0,
1061 ip_freq: 0.0,
1062 op_freq: 0.0,
1063 op_samples: 0,
1064 started: 0.0,
1065 ingress_control,
1066 };
1067
1068 let event = if let Some(template) = &dynamic_template {
1069 Event::DynamicInterfaceUp {
1070 id: client_id,
1071 writer,
1072 registration: template.registration(info),
1073 }
1074 } else {
1075 Event::InterfaceUp(client_id, Some(writer), Some(info))
1076 };
1077 if tx.send(event).is_err() {
1078 cleanup(&poller, &clients, &listener);
1080 return Ok(());
1081 }
1082 }
1083 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
1084 Err(e) => {
1085 log::warn!("[{}] accept error: {}", name, e);
1086 break;
1087 }
1088 }
1089 }
1090 poller.modify(&listener, PollEvent::readable(LISTENER_KEY))?;
1092 } else if clients.contains_key(&ev.key) {
1093 let key = ev.key;
1094 let mut should_remove = false;
1095 let mut client_id = InterfaceId(0);
1096
1097 let mut buf = [0u8; 4096];
1098 let read_result = {
1099 let client = clients.get_mut(&key).unwrap();
1100 client.stream.read(&mut buf)
1101 };
1102
1103 match read_result {
1104 Ok(0) | Err(_) => {
1105 if let Some(c) = clients.get(&key) {
1106 client_id = c.id;
1107 }
1108 should_remove = true;
1109 }
1110 Ok(n) => {
1111 let client = clients.get_mut(&key).unwrap();
1112 client_id = client.id;
1113 client.has_received_data = true;
1114 client.dp_ingress_bytes = client.dp_ingress_bytes.saturating_add(n as u64);
1115 let decoded = client.decoder.feed_with_diagnostics(&buf[..n]);
1116 client.dp_ingress_packets = client
1117 .dp_ingress_packets
1118 .saturating_add(decoded.frames.len() as u64);
1119 for frame_len in decoded.invalid_frame_lengths {
1120 log::debug!(
1121 "[{}] invalid HDLC frame of {} bytes received from client {}, dropping frame",
1122 name,
1123 frame_len,
1124 client_id.0,
1125 );
1126 }
1127 for frame in decoded.frames {
1128 if tx
1129 .send(Event::Frame {
1130 interface_id: client_id,
1131 data: frame,
1132 rssi: None,
1133 snr: None,
1134 })
1135 .is_err()
1136 {
1137 cleanup(&poller, &clients, &listener);
1138 return Ok(());
1139 }
1140 }
1141 let queue = tx.inbound_queue_snapshot();
1142 if queue.heights[0] >= ingress_thresholds.immediate {
1143 let _ = throttle_busiest_ingress(
1144 &poller,
1145 &mut clients,
1146 ingress_snapshot_at.elapsed(),
1147 )?;
1148 }
1149 }
1150 }
1151
1152 if should_remove {
1153 let reason = if clients
1154 .get(&key)
1155 .is_some_and(|c| c.write_stall_flag.load(Ordering::Relaxed))
1156 {
1157 DisconnectReason::WriteStall
1158 } else {
1159 DisconnectReason::RemoteClosed
1160 };
1161 disconnect_client(DisconnectContext {
1162 poller: &poller,
1163 clients: &mut clients,
1164 peers: &mut peers,
1165 name: &name,
1166 server_interface_id,
1167 tx: &tx,
1168 peer_state: &peer_state,
1169 fast_flap: &fast_flap,
1170 fast_flap_state: &fast_flap_state,
1171 key,
1172 client_id,
1173 reason,
1174 });
1175 } else if let Some(client) = clients.get(&key) {
1176 if !client.dp_ingress_gated {
1178 poller.modify(&client.stream, PollEvent::readable(key))?;
1179 }
1180 }
1181 }
1182 }
1183
1184 if ingress_snapshot_at.elapsed() >= DP_IC_INTERVAL {
1185 let now = Instant::now();
1186 let queue = tx.inbound_queue_snapshot();
1187 if queue.heights[0] > ingress_thresholds.mid {
1188 let _ =
1189 throttle_busiest_ingress(&poller, &mut clients, ingress_snapshot_at.elapsed())?;
1190 } else if queue.heights[0] < ingress_thresholds.low {
1191 let _ = release_one_ingress(&poller, &mut clients, now)?;
1192 }
1193 for client in clients.values_mut() {
1194 client.dp_ingress_bytes = 0;
1195 client.dp_ingress_packets = 0;
1196 }
1197 ingress_snapshot_at = now;
1198 }
1199
1200 if let Some(timeout) = idle_timeout {
1201 let now = Instant::now();
1202 let timed_out: Vec<(usize, InterfaceId)> = clients
1203 .iter()
1204 .filter_map(|(&key, client)| {
1205 if client.has_received_data || now.duration_since(client.connected_at) < timeout
1206 {
1207 None
1208 } else {
1209 Some((key, client.id))
1210 }
1211 })
1212 .collect();
1213
1214 for (key, client_id) in timed_out {
1215 disconnect_client(DisconnectContext {
1216 poller: &poller,
1217 clients: &mut clients,
1218 peers: &mut peers,
1219 name: &name,
1220 server_interface_id,
1221 tx: &tx,
1222 peer_state: &peer_state,
1223 fast_flap: &fast_flap,
1224 fast_flap_state: &fast_flap_state,
1225 key,
1226 client_id,
1227 reason: DisconnectReason::IdleTimeout,
1228 });
1229 }
1230 }
1231 }
1232}
1233
1234fn cleanup_peer_state(peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
1235 let now = Instant::now();
1236 peers.retain(|_, state| {
1237 if matches!(state.blacklisted_until, Some(until) if now >= until) {
1238 state.blacklisted_until = None;
1239 state.blacklist_reason = None;
1240 }
1241 state.blacklisted_until.is_some() || state.connected_count > 0 || state.reject_count > 0
1242 });
1243}
1244
1245fn is_ip_blacklisted(peers: &mut HashMap<IpAddr, PeerBehaviorState>, peer_ip: IpAddr) -> bool {
1246 let now = Instant::now();
1247 if let Some(state) = peers.get_mut(&peer_ip) {
1248 if let Some(until) = state.blacklisted_until {
1249 if now < until {
1250 return true;
1251 }
1252 state.blacklisted_until = None;
1253 }
1254 }
1255 false
1256}
1257
1258struct DisconnectContext<'a> {
1259 poller: &'a Poller,
1260 clients: &'a mut HashMap<usize, ClientState>,
1261 peers: &'a mut HashMap<IpAddr, PeerBehaviorState>,
1262 name: &'a str,
1263 server_interface_id: InterfaceId,
1264 tx: &'a EventSender,
1265 peer_state: &'a Arc<Mutex<BackbonePeerMonitor>>,
1266 fast_flap: &'a BackboneFastFlapConfig,
1267 fast_flap_state: &'a Arc<Mutex<BackboneFastFlapMonitor>>,
1268 key: usize,
1269 client_id: InterfaceId,
1270 reason: DisconnectReason,
1271}
1272
1273fn disconnect_client(context: DisconnectContext<'_>) {
1274 let DisconnectContext {
1275 poller,
1276 clients,
1277 peers,
1278 name,
1279 server_interface_id,
1280 tx,
1281 peer_state,
1282 fast_flap,
1283 fast_flap_state,
1284 key,
1285 client_id,
1286 reason,
1287 } = context;
1288 let Some(client) = clients.remove(&key) else {
1289 return;
1290 };
1291
1292 match reason {
1293 DisconnectReason::RemoteClosed => {
1294 log::trace!(target: crate::logging::PATHING_LOG_TARGET,
1295 "[{}] backbone client {} disconnected",
1296 name,
1297 client_id.0,
1298 );
1299 }
1300 DisconnectReason::IdleTimeout => {
1301 log::info!(
1302 "[{}] backbone client {} disconnected due to idle timeout",
1303 name,
1304 client_id.0
1305 );
1306 }
1307 DisconnectReason::WriteStall => {
1308 }
1310 }
1311
1312 let _ = poller.delete(&client.stream);
1313 let connected_for = client.connected_at.elapsed();
1315 if let Some(record) = lock_or_recover(fast_flap_state, "backbone fast-flap state")
1316 .record_disconnect(client.peer_ip, connected_for, fast_flap, Instant::now())
1317 {
1318 let frequency = if record.elapsed_since_first.is_zero() {
1319 None
1320 } else {
1321 Some(record.flap_count as f64 / record.elapsed_since_first.as_secs_f64())
1322 };
1323 log::debug!(
1324 "[{}] peer {} fast flapped after {:.3}s ({} flaps{})",
1325 name,
1326 client.peer_ip,
1327 connected_for.as_secs_f64(),
1328 record.flap_count,
1329 frequency
1330 .map(|value| format!(", {:.3} Hz", value))
1331 .unwrap_or_default(),
1332 );
1333 if record.blocked {
1334 let state = peers
1335 .entry(client.peer_ip)
1336 .or_insert_with(PeerBehaviorState::new);
1337 state.blacklisted_until = record.blocked_until;
1338 state.blacklist_reason = Some("fast flapping".into());
1339 if record.newly_blocked {
1340 log::warn!(
1341 "[{}] blocking further connections from {} due to fast flapping",
1342 name,
1343 client.peer_ip,
1344 );
1345 }
1346 }
1347 }
1348 let _ = tx.send(Event::BackbonePeerDisconnected {
1349 server_interface_id,
1350 peer_interface_id: client.id,
1351 peer_ip: client.peer_ip,
1352 peer_port: client.peer_port,
1353 connected_for,
1354 had_received_data: client.has_received_data,
1355 });
1356 match reason {
1357 DisconnectReason::IdleTimeout => {
1358 let _ = tx.send(Event::BackbonePeerIdleTimeout {
1359 server_interface_id,
1360 peer_interface_id: client.id,
1361 peer_ip: client.peer_ip,
1362 peer_port: client.peer_port,
1363 connected_for,
1364 });
1365 }
1366 DisconnectReason::WriteStall => {
1367 let _ = tx.send(Event::BackbonePeerWriteStall {
1368 server_interface_id,
1369 peer_interface_id: client.id,
1370 peer_ip: client.peer_ip,
1371 peer_port: client.peer_port,
1372 connected_for,
1373 });
1374 }
1375 DisconnectReason::RemoteClosed => {}
1376 }
1377
1378 if let Some(state) = peers.get_mut(&client.peer_ip) {
1379 state.connected_count = state.connected_count.saturating_sub(1);
1380 }
1381 peer_state.lock().unwrap().upsert_snapshot(peers);
1382 if !matches!(reason, DisconnectReason::WriteStall) {
1384 let _ = tx.send(Event::InterfaceDown(client_id));
1385 }
1386}
1387
1388fn set_tcp_keepalive(stream: &TcpStream) -> io::Result<()> {
1389 let sock = SockRef::from(stream);
1390 let mut keepalive = TcpKeepalive::new()
1391 .with_time(Duration::from_secs(5))
1392 .with_interval(Duration::from_secs(2));
1393 #[cfg(any(target_os = "linux", target_os = "macos"))]
1394 {
1395 keepalive = keepalive.with_retries(12);
1396 }
1397 sock.set_tcp_keepalive(&keepalive)
1398}
1399
1400fn cleanup(poller: &Poller, clients: &HashMap<usize, ClientState>, listener: &TcpListener) {
1401 for client in clients.values() {
1402 let _ = poller.delete(&client.stream);
1403 }
1404 let _ = poller.delete(listener);
1405}
1406
1407#[derive(Debug, Clone)]
1413pub struct BackboneClientConfig {
1414 pub name: String,
1415 pub target_host: String,
1416 pub target_port: u16,
1417 pub interface_id: InterfaceId,
1418 pub reconnect_wait: Duration,
1419 pub max_reconnect_tries: Option<u32>,
1420 pub connect_timeout: Duration,
1421 pub transport_identity: Option<String>,
1422 pub runtime: Arc<Mutex<BackboneClientRuntime>>,
1423}
1424
1425#[derive(Debug, Clone)]
1426pub struct BackboneClientRuntime {
1427 pub reconnect_wait: Duration,
1428 pub max_reconnect_tries: Option<u32>,
1429 pub connect_timeout: Duration,
1430}
1431
1432impl BackboneClientRuntime {
1433 pub fn from_config(config: &BackboneClientConfig) -> Self {
1434 Self {
1435 reconnect_wait: config.reconnect_wait,
1436 max_reconnect_tries: config.max_reconnect_tries,
1437 connect_timeout: config.connect_timeout,
1438 }
1439 }
1440}
1441
1442#[derive(Debug, Clone)]
1443pub struct BackboneClientRuntimeConfigHandle {
1444 pub interface_name: String,
1445 pub runtime: Arc<Mutex<BackboneClientRuntime>>,
1446 pub startup: BackboneClientRuntime,
1447}
1448
1449impl Default for BackboneClientConfig {
1450 fn default() -> Self {
1451 let mut config = BackboneClientConfig {
1452 name: String::new(),
1453 target_host: "127.0.0.1".into(),
1454 target_port: 4242,
1455 interface_id: InterfaceId(0),
1456 reconnect_wait: Duration::from_secs(5),
1457 max_reconnect_tries: None,
1458 connect_timeout: Duration::from_secs(5),
1459 transport_identity: None,
1460 runtime: Arc::new(Mutex::new(BackboneClientRuntime {
1461 reconnect_wait: Duration::from_secs(5),
1462 max_reconnect_tries: None,
1463 connect_timeout: Duration::from_secs(5),
1464 })),
1465 };
1466 let startup = BackboneClientRuntime::from_config(&config);
1467 config.runtime = Arc::new(Mutex::new(startup));
1468 config
1469 }
1470}
1471
1472struct BackboneClientWriter {
1474 stream: TcpStream,
1475 transmit_buffer: TransmitBuffer,
1476 egress_control: EgressControl,
1477}
1478
1479impl Writer for BackboneClientWriter {
1480 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
1481 if !self
1482 .transmit_buffer
1483 .append_with_limit(hdlc::frame(data), Some(EGRESS_HIGH_WATERMARK))
1484 {
1485 return Err(io::Error::new(
1486 io::ErrorKind::InvalidInput,
1487 "confirmed frame exceeds backbone buffer",
1488 ));
1489 }
1490 self.transmit_buffer.flush();
1491 while self.transmit_buffer.sendable_bytes() > 0 {
1492 if self.transmit_buffer.drain_to(&mut self.stream)? == 0 {
1493 return Err(io::Error::new(
1494 io::ErrorKind::WriteZero,
1495 "backbone writer made no progress",
1496 ));
1497 }
1498 }
1499 Ok(())
1500 }
1501 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1502 self.send_frames(&[data.to_vec()])
1503 }
1504
1505 fn send_frames(&mut self, frames: &[Vec<u8>]) -> io::Result<()> {
1506 for frame in frames {
1507 let encoded = hdlc::frame(frame);
1508 let encoded_len = encoded.len();
1509 if !self
1510 .transmit_buffer
1511 .append_with_limit(encoded, Some(EGRESS_HIGH_WATERMARK))
1512 {
1513 self.egress_control.record_drop(encoded_len);
1514 }
1515 }
1516 self.transmit_buffer.flush();
1517 while self.transmit_buffer.sendable_bytes() > 0 {
1518 if self.transmit_buffer.drain_to(&mut self.stream)? == 0 {
1519 return Err(io::Error::new(
1520 io::ErrorKind::WriteZero,
1521 "backbone client writer made no progress",
1522 ));
1523 }
1524 }
1525 Ok(())
1526 }
1527
1528 fn egress_control(&self) -> Option<EgressControl> {
1529 Some(self.egress_control.clone())
1530 }
1531}
1532
1533fn try_connect_client(
1535 config: &BackboneClientConfig,
1536 underlay_mark: Option<u32>,
1537) -> io::Result<TcpStream> {
1538 let runtime = config.runtime.lock().unwrap().clone();
1539 let addr_str = format!("{}:{}", config.target_host, config.target_port);
1540 let addr = addr_str
1541 .to_socket_addrs()?
1542 .next()
1543 .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
1544
1545 #[cfg(target_os = "linux")]
1546 let stream = if underlay_mark.is_some() {
1547 super::connect_tcp_with_options(&addr, None, underlay_mark, runtime.connect_timeout)?
1548 } else {
1549 TcpStream::connect_timeout(&addr, runtime.connect_timeout)?
1550 };
1551 #[cfg(not(target_os = "linux"))]
1552 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
1553 stream.set_nodelay(true)?;
1554 set_tcp_keepalive(&stream).ok();
1555
1556 #[cfg(target_os = "macos")]
1558 {
1559 let sock = SockRef::from(&stream);
1560 sock.set_nosigpipe(true).ok();
1561 }
1562
1563 Ok(stream)
1564}
1565
1566pub fn start_client(config: BackboneClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
1568 start_client_with_ifac(config, tx, 0, None, ListenerControl::new())
1569}
1570
1571pub(crate) fn start_client_with_ifac(
1572 config: BackboneClientConfig,
1573 tx: EventSender,
1574 ifac_size: usize,
1575 underlay_mark: Option<u32>,
1576 control: ListenerControl,
1577) -> io::Result<Box<dyn Writer>> {
1578 let stream = try_connect_client(&config, underlay_mark)?;
1579 let reader_stream = stream.try_clone()?;
1580 let writer_stream = stream.try_clone()?;
1581
1582 let id = config.interface_id;
1583 log::info!(
1584 "[{}] backbone client connected to {}:{}",
1585 config.name,
1586 config.target_host,
1587 config.target_port
1588 );
1589
1590 let _ = tx.send(Event::InterfaceUp(id, None, None));
1592
1593 thread::Builder::new()
1594 .name(format!("backbone-client-{}", id.0))
1595 .spawn(move || {
1596 client_reader_loop(reader_stream, config, tx, ifac_size, underlay_mark, control);
1597 })?;
1598
1599 Ok(Box::new(BackboneClientWriter {
1600 stream: writer_stream,
1601 transmit_buffer: TransmitBuffer::new(),
1602 egress_control: EgressControl::new(EGRESS_HIGH_WATERMARK),
1603 }))
1604}
1605
1606fn client_reader_loop(
1611 mut stream: TcpStream,
1612 config: BackboneClientConfig,
1613 tx: EventSender,
1614 ifac_size: usize,
1615 underlay_mark: Option<u32>,
1616 control: ListenerControl,
1617) {
1618 let id = config.interface_id;
1619 let mut decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
1620 let mut buf = [0u8; 4096];
1621
1622 loop {
1623 if control.should_stop() {
1624 return;
1625 }
1626 match poll_readable(stream.as_raw_fd(), Duration::from_millis(200)) {
1627 Ok(false) => continue,
1628 Ok(true) => {}
1629 Err(error) => {
1630 if control.should_stop() {
1631 return;
1632 }
1633 log::warn!("[{}] backbone poll error: {}", config.name, error);
1634 let _ = tx.send(Event::InterfaceDown(id));
1635 match client_reconnect(&config, &tx, underlay_mark, &control) {
1636 Some(new_stream) => {
1637 stream = new_stream;
1638 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
1639 continue;
1640 }
1641 None => return,
1642 }
1643 }
1644 }
1645 match stream.read(&mut buf) {
1646 Ok(0) => {
1647 if control.should_stop() {
1648 return;
1649 }
1650 log::warn!("[{}] connection closed", config.name);
1651 let _ = tx.send(Event::InterfaceDown(id));
1652 match client_reconnect(&config, &tx, underlay_mark, &control) {
1653 Some(new_stream) => {
1654 stream = new_stream;
1655 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
1656 continue;
1657 }
1658 None => {
1659 log::error!("[{}] reconnection failed, giving up", config.name);
1660 return;
1661 }
1662 }
1663 }
1664 Ok(n) => {
1665 let decoded = decoder.feed_with_diagnostics(&buf[..n]);
1666 for frame_len in decoded.invalid_frame_lengths {
1667 log::debug!(
1668 "[{}] invalid HDLC frame of {} bytes received, dropping frame",
1669 config.name,
1670 frame_len,
1671 );
1672 }
1673 for frame in decoded.frames {
1674 if tx
1675 .send(Event::Frame {
1676 interface_id: id,
1677 data: frame,
1678 rssi: None,
1679 snr: None,
1680 })
1681 .is_err()
1682 {
1683 return;
1684 }
1685 }
1686 }
1687 Err(e) => {
1688 if control.should_stop() {
1689 return;
1690 }
1691 log::warn!("[{}] read error: {}", config.name, e);
1692 let _ = tx.send(Event::InterfaceDown(id));
1693 match client_reconnect(&config, &tx, underlay_mark, &control) {
1694 Some(new_stream) => {
1695 stream = new_stream;
1696 decoder = hdlc::Decoder::reticulum(HW_MTU, ifac_size);
1697 continue;
1698 }
1699 None => {
1700 log::error!("[{}] reconnection failed, giving up", config.name);
1701 return;
1702 }
1703 }
1704 }
1705 }
1706 }
1707}
1708
1709const MAX_BACKOFF_SHIFT: u32 = 6;
1712
1713fn client_reconnect(
1717 config: &BackboneClientConfig,
1718 tx: &EventSender,
1719 underlay_mark: Option<u32>,
1720 control: &ListenerControl,
1721) -> Option<TcpStream> {
1722 let mut attempts = 0u32;
1723 loop {
1724 let runtime = config.runtime.lock().unwrap().clone();
1725
1726 let shift = attempts.min(MAX_BACKOFF_SHIFT);
1727 let backoff = runtime.reconnect_wait * 2u32.pow(shift);
1728 let jitter_range = backoff / 4;
1730 let jitter = if jitter_range.as_nanos() > 0 {
1731 let offset = Duration::from_nanos(
1732 (std::hash::RandomState::new().build_hasher().finish()
1733 % jitter_range.as_nanos() as u64)
1734 * 2,
1735 );
1736 if offset > jitter_range {
1737 backoff + (offset - jitter_range)
1738 } else {
1739 backoff - (jitter_range - offset)
1740 }
1741 } else {
1742 backoff
1743 };
1744 if !wait_while_active(control, jitter) {
1745 return None;
1746 }
1747
1748 attempts += 1;
1749
1750 if let Some(max) = runtime.max_reconnect_tries {
1751 if attempts > max {
1752 let _ = tx.send(Event::InterfaceDown(config.interface_id));
1753 return None;
1754 }
1755 }
1756
1757 log::info!(
1758 "[{}] reconnect attempt {} (backoff {:.1}s) ...",
1759 config.name,
1760 attempts,
1761 jitter.as_secs_f64(),
1762 );
1763
1764 match try_connect_client(config, underlay_mark) {
1765 Ok(new_stream) => {
1766 if control.should_stop() {
1767 return None;
1768 }
1769 let writer_stream = match new_stream.try_clone() {
1770 Ok(s) => s,
1771 Err(e) => {
1772 log::warn!("[{}] failed to clone stream: {}", config.name, e);
1773 continue;
1774 }
1775 };
1776 log::info!(
1777 "[{}] reconnected after {} attempt(s)",
1778 config.name,
1779 attempts
1780 );
1781 let new_writer: Box<dyn Writer> = Box::new(BackboneClientWriter {
1782 stream: writer_stream,
1783 transmit_buffer: TransmitBuffer::new(),
1784 egress_control: EgressControl::new(EGRESS_HIGH_WATERMARK),
1785 });
1786 let _ = tx.send(Event::InterfaceUp(
1787 config.interface_id,
1788 Some(new_writer),
1789 None,
1790 ));
1791 return Some(new_stream);
1792 }
1793 Err(e) => {
1794 log::warn!("[{}] reconnect failed: {}", config.name, e);
1795 }
1796 }
1797 }
1798}
1799
1800#[derive(Clone)]
1807pub(crate) enum BackboneMode {
1808 Server(BackboneConfig),
1809 Client(BackboneClientConfig, u8),
1810}
1811
1812pub struct BackboneInterfaceFactory;
1818
1819pub struct BackboneClientInterfaceFactory;
1822
1823impl InterfaceFactory for BackboneClientInterfaceFactory {
1824 fn type_name(&self) -> &str {
1825 "BackboneClientInterface"
1826 }
1827
1828 fn parse_config(
1829 &self,
1830 name: &str,
1831 id: InterfaceId,
1832 params: &HashMap<String, String>,
1833 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1834 if !params.contains_key("remote") && !params.contains_key("target_host") {
1835 return Err("BackboneClientInterface requires 'remote' or 'target_host'".into());
1836 }
1837 BackboneInterfaceFactory.parse_config(name, id, params)
1838 }
1839
1840 fn start(
1841 &self,
1842 config: Box<dyn InterfaceConfigData>,
1843 ctx: StartContext,
1844 ) -> io::Result<StartResult> {
1845 BackboneInterfaceFactory.start(config, ctx)
1846 }
1847}
1848
1849fn parse_positive_duration_secs(params: &HashMap<String, String>, key: &str) -> Option<Duration> {
1850 params
1851 .get(key)
1852 .and_then(|v| v.parse::<f64>().ok())
1853 .filter(|v| *v > 0.0)
1854 .map(Duration::from_secs_f64)
1855}
1856
1857fn parse_nonnegative_duration(
1858 params: &HashMap<String, String>,
1859 key: &str,
1860 multiplier: f64,
1861 default: Duration,
1862) -> Result<Duration, String> {
1863 let Some(raw) = params.get(key) else {
1864 return Ok(default);
1865 };
1866 let value = raw
1867 .parse::<f64>()
1868 .map_err(|_| format!("invalid {key} '{raw}' (expected a non-negative number)"))?;
1869 let seconds = value * multiplier;
1870 if !value.is_finite() || value < 0.0 || !seconds.is_finite() || seconds > u64::MAX as f64 {
1871 return Err(format!(
1872 "invalid {key} '{raw}' (expected a finite non-negative number)"
1873 ));
1874 }
1875 let duration = Duration::from_secs_f64(seconds);
1876 if Instant::now().checked_add(duration).is_none() {
1877 return Err(format!("invalid {key} '{raw}' (duration is too large)"));
1878 }
1879 Ok(duration)
1880}
1881
1882fn parse_fast_flap_config(
1883 params: &HashMap<String, String>,
1884) -> Result<BackboneFastFlapConfig, String> {
1885 let defaults = BackboneFastFlapConfig::default();
1886 let enabled = match params.get("block_fast_flapping") {
1887 Some(raw) => crate::config::parse_bool_pub(raw)
1888 .ok_or_else(|| format!("invalid block_fast_flapping '{raw}' (expected yes or no)"))?,
1889 None => defaults.enabled,
1890 };
1891 let connection_threshold = parse_nonnegative_duration(
1892 params,
1893 "fast_flapping_threshold",
1894 1.0,
1895 defaults.connection_threshold,
1896 )?;
1897 let grace = match params.get("fast_flapping_grace") {
1898 Some(raw) => raw.parse::<u64>().map_err(|_| {
1899 format!("invalid fast_flapping_grace '{raw}' (expected a non-negative integer)")
1900 })?,
1901 None => defaults.grace,
1902 };
1903 let block_duration = parse_nonnegative_duration(
1904 params,
1905 "fast_flapping_block_time",
1906 60.0,
1907 defaults.block_duration,
1908 )?;
1909
1910 Ok(BackboneFastFlapConfig {
1911 enabled,
1912 connection_threshold,
1913 grace,
1914 block_duration,
1915 })
1916}
1917
1918impl InterfaceFactory for BackboneInterfaceFactory {
1919 fn type_name(&self) -> &str {
1920 "BackboneInterface"
1921 }
1922
1923 fn parse_config(
1924 &self,
1925 name: &str,
1926 id: InterfaceId,
1927 params: &HashMap<String, String>,
1928 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1929 if let Some(target_host) = params.get("remote").or_else(|| params.get("target_host")) {
1930 let target_host = target_host.clone();
1932 let target_port = params
1933 .get("target_port")
1934 .or_else(|| params.get("port"))
1935 .and_then(|v| v.parse().ok())
1936 .unwrap_or(4242);
1937 let transport_identity = params.get("transport_identity").cloned();
1938 let priority = match params.get("priority") {
1939 Some(value) => value.parse::<u8>().map_err(|_| {
1940 format!(
1941 "invalid Backbone peer priority '{}' (expected 0..100)",
1942 value
1943 )
1944 })?,
1945 None => crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1946 };
1947 if priority > 100 {
1948 return Err(format!(
1949 "invalid Backbone peer priority '{}' (expected 0..100)",
1950 priority
1951 ));
1952 }
1953 Ok(Box::new(BackboneMode::Client(
1954 BackboneClientConfig {
1955 name: name.to_string(),
1956 target_host,
1957 target_port,
1958 interface_id: id,
1959 transport_identity,
1960 ..BackboneClientConfig::default()
1961 },
1962 priority,
1963 )))
1964 } else {
1965 let listen_ip = params
1967 .get("listen_ip")
1968 .or_else(|| params.get("device"))
1969 .cloned()
1970 .unwrap_or_else(|| "0.0.0.0".into());
1971 let listen_port = params
1972 .get("listen_port")
1973 .or_else(|| params.get("port"))
1974 .and_then(|v| v.parse().ok())
1975 .unwrap_or(4242);
1976 let max_connections = params.get("max_connections").and_then(|v| v.parse().ok());
1977 let idle_timeout = parse_positive_duration_secs(params, "idle_timeout");
1978 let write_stall_timeout = parse_positive_duration_secs(params, "write_stall_timeout");
1979 let abuse = BackboneAbuseConfig {
1980 max_penalty_duration: parse_positive_duration_secs(params, "max_penalty_duration"),
1981 };
1982 let fast_flap = parse_fast_flap_config(params)?;
1983 let mut config = BackboneConfig {
1984 name: name.to_string(),
1985 listen_ip,
1986 listen_port,
1987 interface_id: id,
1988 mode: constants::MODE_FULL,
1989 gravity: 0,
1990 recursive_prs: false,
1991 announces_from_internal: true,
1992 announces_to_internal: None,
1993 max_connections,
1994 idle_timeout,
1995 write_stall_timeout,
1996 abuse,
1997 fast_flap,
1998 fast_flap_state: shared_fast_flap_monitor(),
1999 ingress_control: IngressControlConfig::enabled(),
2000 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
2001 max_connections: None,
2002 idle_timeout: None,
2003 write_stall_timeout: None,
2004 abuse: BackboneAbuseConfig::default(),
2005 })),
2006 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
2007 };
2008 let startup = BackboneServerRuntime::from_config(&config);
2009 config.runtime = Arc::new(Mutex::new(startup));
2010 Ok(Box::new(BackboneMode::Server(config)))
2011 }
2012 }
2013
2014 fn start(
2015 &self,
2016 config: Box<dyn InterfaceConfigData>,
2017 ctx: StartContext,
2018 ) -> io::Result<StartResult> {
2019 let mode = *config.into_any().downcast::<BackboneMode>().map_err(|_| {
2020 io::Error::new(
2021 io::ErrorKind::InvalidData,
2022 "wrong config type for BackboneInterface",
2023 )
2024 })?;
2025
2026 match mode {
2027 BackboneMode::Client(cfg, _) => {
2028 let id = cfg.interface_id;
2029 let name = cfg.name.clone();
2030 let info = InterfaceInfo {
2031 id,
2032 name,
2033 mode: ctx.mode,
2034 gravity: ctx.gravity,
2035 recursive_prs: ctx.recursive_prs,
2036 announces_from_internal: ctx.announces_from_internal,
2037 announces_to_internal: ctx.announces_to_internal,
2038 out_capable: true,
2039 in_capable: true,
2040 bitrate: Some(CLIENT_BITRATE_GUESS),
2041 airtime_profile: None,
2042 announce_rate_target: None,
2043 announce_rate_grace: 0,
2044 announce_rate_penalty: 0.0,
2045 announce_cap: constants::ANNOUNCE_CAP,
2046 is_local_client: false,
2047 wants_tunnel: false,
2048 tunnel_id: None,
2049 mtu: HW_MTU as u32,
2050 ingress_control: ctx.ingress_control,
2051 ia_freq: 0.0,
2052 ip_freq: 0.0,
2053 op_freq: 0.0,
2054 op_samples: 0,
2055 started: crate::time::now(),
2056 };
2057 let ifac_size = ctx.ifac.as_ref().map(|ifac| ifac.size).unwrap_or(0);
2058 let control = ListenerControl::new();
2059 let writer = start_client_with_ifac(
2060 cfg,
2061 ctx.tx,
2062 ifac_size,
2063 ctx.underlay_mark,
2064 control.clone(),
2065 )?;
2066 Ok(StartResult::Simple {
2067 control: Some(control),
2068 id,
2069 info,
2070 writer,
2071 interface_type_name: "BackboneInterface".to_string(),
2072 })
2073 }
2074 BackboneMode::Server(mut cfg) => {
2075 cfg.ingress_control = ctx.ingress_control;
2076 cfg.mode = ctx.mode;
2077 cfg.gravity = ctx.gravity;
2078 cfg.recursive_prs = ctx.recursive_prs;
2079 cfg.announces_from_internal = ctx.announces_from_internal;
2080 cfg.announces_to_internal = ctx.announces_to_internal;
2081 let parent_id = cfg.interface_id;
2082 start_with_template(
2083 cfg,
2084 ctx.tx,
2085 ctx.next_dynamic_id,
2086 Some(DynamicInterfaceTemplate {
2087 parent_id,
2088 interface_type: "BackboneInterfacePeer".into(),
2089 ifac: ctx.ifac,
2090 mode: ctx.mode,
2091 gravity: ctx.gravity,
2092 recursive_prs: ctx.recursive_prs,
2093 announces_from_internal: ctx.announces_from_internal,
2094 announces_to_internal: ctx.announces_to_internal,
2095 }),
2096 ctx.underlay_mark,
2097 )?;
2098 Ok(StartResult::Listener { control: None })
2099 }
2100 }
2101 }
2102}
2103
2104pub(crate) fn runtime_handle_from_mode(mode: &BackboneMode) -> Option<BackboneRuntimeConfigHandle> {
2105 match mode {
2106 BackboneMode::Server(config) => Some(BackboneRuntimeConfigHandle {
2107 interface_name: config.name.clone(),
2108 runtime: Arc::clone(&config.runtime),
2109 startup: BackboneServerRuntime::from_config(config),
2110 }),
2111 BackboneMode::Client(_, _) => None,
2112 }
2113}
2114
2115pub(crate) fn peer_state_handle_from_mode(mode: &BackboneMode) -> Option<BackbonePeerStateHandle> {
2116 match mode {
2117 BackboneMode::Server(config) => Some(BackbonePeerStateHandle {
2118 interface_id: config.interface_id,
2119 interface_name: config.name.clone(),
2120 mode: config.mode,
2121 gravity: config.gravity,
2122 announces_to_internal: config.announces_to_internal,
2123 peer_state: Arc::clone(&config.peer_state),
2124 fast_flap: config.fast_flap,
2125 fast_flap_state: Arc::clone(&config.fast_flap_state),
2126 }),
2127 BackboneMode::Client(_, _) => None,
2128 }
2129}
2130
2131pub(crate) fn client_runtime_handle_from_mode(
2132 mode: &BackboneMode,
2133) -> Option<BackboneClientRuntimeConfigHandle> {
2134 match mode {
2135 BackboneMode::Client(config, _) => Some(BackboneClientRuntimeConfigHandle {
2136 interface_name: config.name.clone(),
2137 runtime: Arc::clone(&config.runtime),
2138 startup: BackboneClientRuntime::from_config(config),
2139 }),
2140 BackboneMode::Server(_) => None,
2141 }
2142}
2143
2144pub(crate) fn client_config_from_mode(mode: &BackboneMode) -> Option<BackboneClientConfig> {
2145 match mode {
2146 BackboneMode::Client(config, _) => Some(config.clone()),
2147 BackboneMode::Server(_) => None,
2148 }
2149}
2150
2151pub(crate) fn client_priority_from_mode(mode: &BackboneMode) -> Option<u8> {
2152 match mode {
2153 BackboneMode::Client(_, priority) => Some(*priority),
2154 BackboneMode::Server(_) => None,
2155 }
2156}
2157
2158#[cfg(test)]
2159mod tests {
2160 use super::*;
2161 use std::sync::mpsc;
2162 use std::time::Duration;
2163
2164 fn find_free_port() -> u16 {
2165 crate::test_support::port()
2166 }
2167
2168 fn test_fast_flap_config() -> BackboneFastFlapConfig {
2169 BackboneFastFlapConfig {
2170 enabled: true,
2171 connection_threshold: Duration::from_secs(20),
2172 grace: 5,
2173 block_duration: Duration::from_secs(12 * 60 * 60),
2174 }
2175 }
2176
2177 #[test]
2178 fn fast_flap_defaults_match_upstream() {
2179 assert_eq!(BackboneFastFlapConfig::default(), test_fast_flap_config());
2180 }
2181
2182 #[test]
2183 fn sixth_short_connection_blocks_but_fifth_does_not() {
2184 let config = test_fast_flap_config();
2185 let mut monitor = BackboneFastFlapMonitor::new();
2186 let peer: IpAddr = "192.0.2.10".parse().unwrap();
2187 let start = Instant::now();
2188
2189 for flap in 1..=5 {
2190 let record = monitor
2191 .record_disconnect(
2192 peer,
2193 Duration::from_secs(19),
2194 &config,
2195 start + Duration::from_secs(flap),
2196 )
2197 .unwrap();
2198 assert_eq!(record.flap_count, flap);
2199 assert!(!record.blocked);
2200 assert!(!monitor.is_blocked(peer, &config, start + Duration::from_secs(flap)));
2201 }
2202
2203 let sixth = monitor
2204 .record_disconnect(
2205 peer,
2206 Duration::from_secs(1),
2207 &config,
2208 start + Duration::from_secs(6),
2209 )
2210 .unwrap();
2211 assert_eq!(sixth.flap_count, 6);
2212 assert!(sixth.blocked);
2213 assert!(sixth.newly_blocked);
2214 assert!(monitor.is_blocked(peer, &config, start + Duration::from_secs(6)));
2215 }
2216
2217 #[test]
2218 fn every_short_connection_counts_even_after_receiving_data() {
2219 let config = test_fast_flap_config();
2220 let mut monitor = BackboneFastFlapMonitor::new();
2221 let peer: IpAddr = "192.0.2.11".parse().unwrap();
2222 let now = Instant::now();
2223
2224 let record = monitor
2225 .record_disconnect(peer, Duration::from_secs(2), &config, now)
2226 .unwrap();
2227 assert_eq!(record.flap_count, 1);
2228 assert_eq!(monitor.flap_count(peer), 1);
2229 }
2230
2231 #[test]
2232 fn threshold_boundary_and_long_connections_are_not_counted() {
2233 let config = test_fast_flap_config();
2234 let mut monitor = BackboneFastFlapMonitor::new();
2235 let peer: IpAddr = "192.0.2.12".parse().unwrap();
2236 let now = Instant::now();
2237
2238 assert!(monitor
2239 .record_disconnect(peer, Duration::from_secs(20), &config, now)
2240 .is_none());
2241 assert!(monitor
2242 .record_disconnect(peer, Duration::from_secs(21), &config, now)
2243 .is_none());
2244 assert_eq!(monitor.flap_count(peer), 0);
2245 }
2246
2247 #[test]
2248 fn disabled_fast_flapping_neither_tracks_nor_blocks() {
2249 let mut config = test_fast_flap_config();
2250 config.enabled = false;
2251 let mut monitor = BackboneFastFlapMonitor::new();
2252 let peer: IpAddr = "192.0.2.13".parse().unwrap();
2253 let now = Instant::now();
2254
2255 for offset in 0..10 {
2256 assert!(monitor
2257 .record_disconnect(
2258 peer,
2259 Duration::from_secs(1),
2260 &config,
2261 now + Duration::from_secs(offset),
2262 )
2263 .is_none());
2264 }
2265 assert_eq!(monitor.flap_count(peer), 0);
2266 assert!(!monitor.is_blocked(peer, &config, now + Duration::from_secs(10)));
2267 assert_eq!(monitor.blocked_ip_count(&config, now), 0);
2268 }
2269
2270 #[test]
2271 fn flap_blocks_expire_strictly_after_configured_duration() {
2272 let mut config = test_fast_flap_config();
2273 config.grace = 0;
2274 config.block_duration = Duration::from_secs(60);
2275 let mut monitor = BackboneFastFlapMonitor::new();
2276 let peer: IpAddr = "192.0.2.14".parse().unwrap();
2277 let now = Instant::now();
2278
2279 monitor
2280 .record_disconnect(peer, Duration::from_secs(1), &config, now)
2281 .unwrap();
2282 assert!(monitor.is_blocked(peer, &config, now + Duration::from_secs(60)));
2283 assert!(!monitor.is_blocked(
2284 peer,
2285 &config,
2286 now + Duration::from_secs(60) + Duration::from_nanos(1)
2287 ));
2288 assert_eq!(monitor.flap_count(peer), 0);
2289 }
2290
2291 #[test]
2292 fn fast_flap_history_is_isolated_by_ip() {
2293 let mut config = test_fast_flap_config();
2294 config.grace = 1;
2295 let mut monitor = BackboneFastFlapMonitor::new();
2296 let first: IpAddr = "192.0.2.15".parse().unwrap();
2297 let second: IpAddr = "192.0.2.16".parse().unwrap();
2298 let now = Instant::now();
2299
2300 for offset in 0..2 {
2301 monitor
2302 .record_disconnect(
2303 first,
2304 Duration::from_secs(1),
2305 &config,
2306 now + Duration::from_secs(offset),
2307 )
2308 .unwrap();
2309 }
2310 monitor
2311 .record_disconnect(second, Duration::from_secs(1), &config, now)
2312 .unwrap();
2313
2314 assert!(monitor.is_blocked(first, &config, now + Duration::from_secs(2)));
2315 assert!(!monitor.is_blocked(second, &config, now + Duration::from_secs(2)));
2316 assert_eq!(
2317 monitor.blocked_ip_count(&config, now + Duration::from_secs(2)),
2318 1
2319 );
2320 assert_eq!(
2321 monitor.blocked_ip_list(&config, now + Duration::from_secs(2)),
2322 vec![first],
2323 "tracked peers within the grace allowance must not appear as blocked"
2324 );
2325 }
2326
2327 #[test]
2328 fn blocked_ip_list_is_sorted_current_and_empty_when_disabled() {
2329 let mut config = test_fast_flap_config();
2330 config.grace = 0;
2331 config.block_duration = Duration::from_secs(60);
2332 let mut monitor = BackboneFastFlapMonitor::new();
2333 let first: IpAddr = "203.0.113.20".parse().unwrap();
2334 let second: IpAddr = "192.0.2.10".parse().unwrap();
2335 let now = Instant::now();
2336 monitor
2337 .record_disconnect(first, Duration::from_secs(1), &config, now)
2338 .unwrap();
2339 monitor
2340 .record_disconnect(second, Duration::from_secs(1), &config, now)
2341 .unwrap();
2342
2343 assert_eq!(monitor.blocked_ip_list(&config, now), vec![second, first]);
2344 assert!(monitor
2345 .blocked_ip_list(&config, now + Duration::from_secs(61))
2346 .is_empty());
2347 config.enabled = false;
2348 assert!(monitor.blocked_ip_list(&config, now).is_empty());
2349 }
2350
2351 #[test]
2352 fn peer_monitor_snapshot_clears_expired_blacklist_state() {
2353 let peer: IpAddr = "192.0.2.17".parse().unwrap();
2354 let mut monitor = BackbonePeerMonitor::new();
2355 monitor.blacklist(peer, Duration::from_secs(60), "temporary".into());
2356 let mut peers = HashMap::new();
2357 monitor.sync_into(&mut peers);
2358 let state = peers.get_mut(&peer).unwrap();
2359 state.blacklisted_until = None;
2360 state.blacklist_reason = None;
2361
2362 monitor.upsert_snapshot(&peers);
2363
2364 let entry = monitor.list("test").into_iter().next().unwrap();
2365 assert!(entry.blacklisted_remaining_secs.is_none());
2366 assert!(entry.blacklist_reason.is_none());
2367 }
2368
2369 fn recv_non_peer_event(
2370 rx: &crate::event::EventReceiver,
2371 timeout: Duration,
2372 ) -> Result<Event, mpsc::RecvTimeoutError> {
2373 let deadline = Instant::now() + timeout;
2374 loop {
2375 let remaining = deadline.saturating_duration_since(Instant::now());
2376 if remaining.is_zero() {
2377 return Err(mpsc::RecvTimeoutError::Timeout);
2378 }
2379 let event = rx.recv_timeout(remaining)?;
2380 match event {
2381 Event::BackbonePeerConnected { .. }
2382 | Event::BackbonePeerDisconnected { .. }
2383 | Event::BackbonePeerIdleTimeout { .. }
2384 | Event::BackbonePeerWriteStall { .. }
2385 | Event::BackbonePeerPenalty { .. } => continue,
2386 other => return Ok(other),
2387 }
2388 }
2389 }
2390
2391 fn make_server_config(
2392 port: u16,
2393 interface_id: u64,
2394 max_connections: Option<usize>,
2395 idle_timeout: Option<Duration>,
2396 write_stall_timeout: Option<Duration>,
2397 abuse: BackboneAbuseConfig,
2398 ) -> BackboneConfig {
2399 let mut config = BackboneConfig {
2400 name: "test-backbone".into(),
2401 listen_ip: "127.0.0.1".into(),
2402 listen_port: port,
2403 interface_id: InterfaceId(interface_id),
2404 mode: constants::MODE_FULL,
2405 gravity: 0,
2406 recursive_prs: false,
2407 announces_from_internal: true,
2408 announces_to_internal: None,
2409 max_connections,
2410 idle_timeout,
2411 write_stall_timeout,
2412 abuse,
2413 fast_flap: BackboneFastFlapConfig::default(),
2414 fast_flap_state: Arc::new(Mutex::new(BackboneFastFlapMonitor::new())),
2415 ingress_control: IngressControlConfig::enabled(),
2416 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
2417 max_connections: None,
2418 idle_timeout: None,
2419 write_stall_timeout: None,
2420 abuse: BackboneAbuseConfig::default(),
2421 })),
2422 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
2423 };
2424 let startup = BackboneServerRuntime::from_config(&config);
2425 config.runtime = Arc::new(Mutex::new(startup));
2426 config
2427 }
2428
2429 #[test]
2430 fn backbone_accept_connection() {
2431 let port = find_free_port();
2432 let (tx, rx) = crate::event::channel();
2433 let next_id = Arc::new(AtomicU64::new(8000));
2434
2435 let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
2436
2437 start(config, tx, next_id).unwrap();
2438 thread::sleep(Duration::from_millis(50));
2439
2440 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2441
2442 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2443 match event {
2444 Event::InterfaceUp(id, writer, info) => {
2445 assert_eq!(id, InterfaceId(8000));
2446 assert!(writer.is_some());
2447 assert!(info.is_some());
2448 let info = info.unwrap();
2449 assert!(info.out_capable);
2450 assert!(info.in_capable);
2451 assert_eq!(info.bitrate, Some(SERVER_BITRATE_GUESS));
2452 assert_eq!(info.mtu, SERVER_PEER_MTU);
2453 }
2454 other => panic!("expected InterfaceUp, got {:?}", other),
2455 }
2456 }
2457
2458 #[test]
2459 fn backbone_class_defaults_preserve_server_and_client_distinction() {
2460 assert_eq!(SERVER_BITRATE_GUESS, 100_000_000);
2461 assert_eq!(SERVER_PEER_MTU, 32_768);
2462 assert_eq!(CLIENT_BITRATE_GUESS, 100_000_000);
2463 assert_eq!(HW_MTU, 32_768);
2464 }
2465
2466 #[test]
2467 fn auto_mtu_thresholds_include_exact_bitrate_boundaries() {
2468 for (bitrate, mtu) in [
2469 (1_000_000_000, Some(524_288)),
2470 (750_000_000, Some(262_144)),
2471 (400_000_000, Some(131_072)),
2472 (200_000_000, Some(65_536)),
2473 (100_000_000, Some(32_768)),
2474 (10_000_000, Some(16_384)),
2475 (5_000_000, Some(8_192)),
2476 (2_000_000, Some(4_096)),
2477 (1_000_000, Some(2_048)),
2478 (62_500, Some(1_024)),
2479 (62_499, None),
2480 ] {
2481 assert_eq!(auto_mtu_for_bitrate(bitrate), mtu, "bitrate {bitrate}");
2482 }
2483 }
2484
2485 #[test]
2486 fn backbone_accepted_connection_inherits_server_mode() {
2487 let port = find_free_port();
2488 let (tx, rx) = crate::event::channel();
2489 let next_id = Arc::new(AtomicU64::new(8050));
2490
2491 let mut config =
2492 make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
2493 config.mode = constants::MODE_GATEWAY;
2494
2495 start(config, tx, next_id).unwrap();
2496 thread::sleep(Duration::from_millis(50));
2497
2498 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2499
2500 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2501 match event {
2502 Event::InterfaceUp(_, _, Some(info)) => {
2503 assert_eq!(info.mode, constants::MODE_GATEWAY);
2504 }
2505 other => panic!("expected InterfaceUp with info, got {:?}", other),
2506 }
2507 }
2508
2509 #[test]
2510 fn dataplane_ingress_thresholds_match_upstream_defaults() {
2511 assert_eq!(
2512 ingress_thresholds(1024),
2513 IngressThresholds {
2514 high: 921,
2515 immediate: 921,
2516 mid: 696,
2517 low: 102,
2518 }
2519 );
2520 assert_eq!(
2521 ingress_thresholds(8),
2522 IngressThresholds {
2523 high: 7,
2524 immediate: 128,
2525 mid: 5,
2526 low: 0,
2527 }
2528 );
2529 }
2530
2531 #[test]
2532 fn dataplane_ingress_selects_the_largest_packet_producer() {
2533 assert_eq!(
2534 busiest_ingress_key([(7, 12), (8, 41), (9, 3)].into_iter()),
2535 Some(8)
2536 );
2537 assert_eq!(busiest_ingress_key([(7, 0), (8, 0)].into_iter()), None);
2538 }
2539
2540 #[test]
2541 fn backbone_gates_the_largest_producer_before_data_queue_overflow() {
2542 let port = find_free_port();
2543 let capacities = crate::event::InboundQueueCapacities {
2544 data: 200,
2545 announce: 8,
2546 path_request: 8,
2547 ingress_limited: 8,
2548 };
2549 let (tx, rx) = crate::event::channel_with_queue_capacities(16, capacities);
2550 let observer = tx.clone();
2551 let next_id = Arc::new(AtomicU64::new(8075));
2552 let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
2553
2554 start(config, tx, next_id).unwrap();
2555 thread::sleep(Duration::from_millis(50));
2556 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
2557 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2558
2559 let payload = vec![0u8; 32];
2560 let framed = hdlc::frame(&payload);
2561 let mut burst = Vec::with_capacity(framed.len() * 180);
2562 for _ in 0..180 {
2563 burst.extend_from_slice(&framed);
2564 }
2565 client.write_all(&burst).unwrap();
2566
2567 let deadline = Instant::now() + Duration::from_secs(2);
2568 while observer.inbound_queue_snapshot().heights[0] < 180 && Instant::now() < deadline {
2569 thread::sleep(Duration::from_millis(5));
2570 }
2571 thread::sleep(Duration::from_millis(100));
2572 let gated = observer.inbound_queue_snapshot();
2573 assert!(
2574 gated.heights[0] >= 180,
2575 "DATA queue never reached the high watermark"
2576 );
2577 assert_eq!(gated.dropped[0], 0);
2578
2579 client.write_all(&hdlc::frame(&[1u8; 32])).unwrap();
2580 thread::sleep(Duration::from_millis(300));
2581 let after_marker = observer.inbound_queue_snapshot();
2582 assert_eq!(after_marker.heights[0], gated.heights[0]);
2583 assert_eq!(
2584 after_marker.dropped[0], gated.dropped[0],
2585 "the producer remained readable and overflowed the DATA queue"
2586 );
2587
2588 for _ in 0..gated.heights[0] {
2589 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2590 assert!(matches!(event, Event::Frame { .. }));
2591 }
2592 let released = recv_non_peer_event(&rx, Duration::from_secs(5)).unwrap();
2593 assert!(
2594 matches!(released, Event::Frame { data, .. } if data == vec![1u8; 32]),
2595 "held producer was not released after DATA pressure recovered"
2596 );
2597 }
2598
2599 #[test]
2600 fn backbone_receive_frame() {
2601 let port = find_free_port();
2602 let (tx, rx) = crate::event::channel();
2603 let next_id = Arc::new(AtomicU64::new(8100));
2604
2605 let config = make_server_config(port, 81, None, None, None, BackboneAbuseConfig::default());
2606
2607 start(config, tx, next_id).unwrap();
2608 thread::sleep(Duration::from_millis(50));
2609
2610 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2611
2612 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2614
2615 client
2618 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
2619 .unwrap();
2620 let payload: Vec<u8> = (0..32).collect();
2621 client.write_all(&hdlc::frame(&payload)).unwrap();
2622
2623 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2624 match event {
2625 Event::Frame {
2626 interface_id,
2627 data,
2628 rssi: _,
2629 snr: _,
2630 } => {
2631 assert_eq!(interface_id, InterfaceId(8100));
2632 assert_eq!(data, payload);
2633 }
2634 other => panic!("expected Frame, got {:?}", other),
2635 }
2636 }
2637
2638 #[test]
2639 fn backbone_send_to_client() {
2640 let port = find_free_port();
2641 let (tx, rx) = crate::event::channel();
2642 let next_id = Arc::new(AtomicU64::new(8200));
2643
2644 let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
2645
2646 start(config, tx, next_id).unwrap();
2647 thread::sleep(Duration::from_millis(50));
2648
2649 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2650 client
2651 .set_read_timeout(Some(Duration::from_secs(2)))
2652 .unwrap();
2653
2654 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2656 let mut writer = match event {
2657 Event::InterfaceUp(_, Some(w), _) => w,
2658 other => panic!("expected InterfaceUp with writer, got {:?}", other),
2659 };
2660
2661 let payload: Vec<u8> = (0..24).collect();
2663 writer.send_frame(&payload).unwrap();
2664
2665 let mut buf = [0u8; 256];
2667 let n = client.read(&mut buf).unwrap();
2668 let expected = hdlc::frame(&payload);
2669 assert_eq!(&buf[..n], &expected[..]);
2670 }
2671
2672 #[test]
2673 fn backbone_write_backpressure_does_not_starve_reads() {
2674 let port = find_free_port();
2675 let (tx, rx) = crate::event::channel();
2676 let next_id = Arc::new(AtomicU64::new(8250));
2677 let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
2678
2679 start(config, tx, next_id).unwrap();
2680 thread::sleep(Duration::from_millis(50));
2681
2682 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
2683 SockRef::from(&client).set_recv_buffer_size(4096).unwrap();
2684 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2685 let mut writer = match event {
2686 Event::InterfaceUp(_, Some(writer), _) => writer,
2687 other => panic!("expected InterfaceUp with writer, got {other:?}"),
2688 };
2689
2690 let outbound = vec![0x55; 60 * 1024];
2691 let deadline = Instant::now() + Duration::from_secs(3);
2692 let mut backpressured = false;
2693 while Instant::now() < deadline {
2694 match writer.send_frame(&outbound) {
2695 Ok(()) => {}
2696 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
2697 backpressured = true;
2698 break;
2699 }
2700 Err(error) => panic!("unexpected backbone write error: {error}"),
2701 }
2702 }
2703 assert!(
2704 backpressured,
2705 "failed to create outbound write backpressure"
2706 );
2707
2708 let inbound: Vec<u8> = (0..32).collect();
2709 client.write_all(&hdlc::frame(&inbound)).unwrap();
2710 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2711 match event {
2712 Event::Frame { data, .. } => assert_eq!(data, inbound),
2713 other => panic!("inbound frame was starved by outbound backpressure: {other:?}"),
2714 }
2715 }
2716
2717 #[test]
2718 fn backbone_multiple_clients() {
2719 let port = find_free_port();
2720 let (tx, rx) = crate::event::channel();
2721 let next_id = Arc::new(AtomicU64::new(8300));
2722
2723 let config = make_server_config(port, 83, None, None, None, BackboneAbuseConfig::default());
2724
2725 start(config, tx, next_id).unwrap();
2726 thread::sleep(Duration::from_millis(50));
2727
2728 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2729 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2730
2731 let mut ids = Vec::new();
2732 for _ in 0..2 {
2733 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2734 match event {
2735 Event::InterfaceUp(id, _, _) => ids.push(id),
2736 other => panic!("expected InterfaceUp, got {:?}", other),
2737 }
2738 }
2739
2740 assert_eq!(ids.len(), 2);
2741 assert_ne!(ids[0], ids[1]);
2742 }
2743
2744 #[test]
2745 fn backbone_client_disconnect() {
2746 let port = find_free_port();
2747 let (tx, rx) = crate::event::channel();
2748 let next_id = Arc::new(AtomicU64::new(8400));
2749
2750 let config = make_server_config(port, 84, None, None, None, BackboneAbuseConfig::default());
2751
2752 start(config, tx, next_id).unwrap();
2753 thread::sleep(Duration::from_millis(50));
2754
2755 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2756
2757 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2759
2760 drop(client);
2762
2763 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2765 assert!(
2766 matches!(event, Event::InterfaceDown(InterfaceId(8400))),
2767 "expected InterfaceDown(8400), got {:?}",
2768 event
2769 );
2770 }
2771
2772 #[test]
2773 fn backbone_epoll_multiplexing() {
2774 let port = find_free_port();
2775 let (tx, rx) = crate::event::channel();
2776 let next_id = Arc::new(AtomicU64::new(8500));
2777
2778 let config = make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
2779
2780 start(config, tx, next_id).unwrap();
2781 thread::sleep(Duration::from_millis(50));
2782
2783 let mut client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2784 let mut client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2785
2786 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2788 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2789
2790 let payload1: Vec<u8> = (0..24).collect();
2792 let payload2: Vec<u8> = (100..130).collect();
2793 client1.write_all(&hdlc::frame(&payload1)).unwrap();
2794 client2.write_all(&hdlc::frame(&payload2)).unwrap();
2795
2796 let mut received = Vec::new();
2798 for _ in 0..2 {
2799 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2800 match event {
2801 Event::Frame { data, .. } => received.push(data),
2802 other => panic!("expected Frame, got {:?}", other),
2803 }
2804 }
2805 assert!(received.contains(&payload1));
2806 assert!(received.contains(&payload2));
2807 }
2808
2809 #[test]
2810 fn backbone_bind_port() {
2811 let port = find_free_port();
2812 let (tx, _rx) = crate::event::channel();
2813 let next_id = Arc::new(AtomicU64::new(8600));
2814
2815 let config = make_server_config(port, 86, None, None, None, BackboneAbuseConfig::default());
2816
2817 start(config, tx, next_id).unwrap();
2819 }
2820
2821 #[test]
2822 fn backbone_hdlc_fragmented() {
2823 let port = find_free_port();
2824 let (tx, rx) = crate::event::channel();
2825 let next_id = Arc::new(AtomicU64::new(8700));
2826
2827 let config = make_server_config(port, 87, None, None, None, BackboneAbuseConfig::default());
2828
2829 start(config, tx, next_id).unwrap();
2830 thread::sleep(Duration::from_millis(50));
2831
2832 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2833 client.set_nodelay(true).unwrap();
2834
2835 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2837
2838 let payload: Vec<u8> = (0..32).collect();
2840 let framed = hdlc::frame(&payload);
2841 let mid = framed.len() / 2;
2842
2843 client.write_all(&framed[..mid]).unwrap();
2844 thread::sleep(Duration::from_millis(50));
2845 client.write_all(&framed[mid..]).unwrap();
2846
2847 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2849 match event {
2850 Event::Frame { data, .. } => {
2851 assert_eq!(data, payload);
2852 }
2853 other => panic!("expected Frame, got {:?}", other),
2854 }
2855 }
2856
2857 fn make_client_config(port: u16, id: u64) -> BackboneClientConfig {
2862 BackboneClientConfig {
2863 name: format!("test-bb-client-{}", port),
2864 target_host: "127.0.0.1".into(),
2865 target_port: port,
2866 interface_id: InterfaceId(id),
2867 reconnect_wait: Duration::from_millis(100),
2868 max_reconnect_tries: Some(2),
2869 connect_timeout: Duration::from_secs(2),
2870 transport_identity: None,
2871 runtime: Arc::new(Mutex::new(BackboneClientRuntime {
2872 reconnect_wait: Duration::from_millis(100),
2873 max_reconnect_tries: Some(2),
2874 connect_timeout: Duration::from_secs(2),
2875 })),
2876 }
2877 }
2878
2879 #[test]
2880 fn backbone_client_connect() {
2881 let port = find_free_port();
2882 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2883 let (tx, rx) = crate::event::channel();
2884
2885 let config = make_client_config(port, 9000);
2886 let _writer = start_client(config, tx).unwrap();
2887
2888 let _server_stream = listener.accept().unwrap();
2889
2890 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
2891 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9000), _, _)));
2892 }
2893
2894 #[test]
2895 fn backbone_client_receive_frame() {
2896 let port = find_free_port();
2897 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2898 let (tx, rx) = crate::event::channel();
2899
2900 let config = make_client_config(port, 9100);
2901 let _writer = start_client(config, tx).unwrap();
2902
2903 let (mut server_stream, _) = listener.accept().unwrap();
2904
2905 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
2907
2908 server_stream
2910 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
2911 .unwrap();
2912 let payload: Vec<u8> = (0..32).collect();
2913 server_stream.write_all(&hdlc::frame(&payload)).unwrap();
2914
2915 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
2916 match event {
2917 Event::Frame {
2918 interface_id,
2919 data,
2920 rssi: _,
2921 snr: _,
2922 } => {
2923 assert_eq!(interface_id, InterfaceId(9100));
2924 assert_eq!(data, payload);
2925 }
2926 other => panic!("expected Frame, got {:?}", other),
2927 }
2928 }
2929
2930 #[test]
2931 fn backbone_client_send_frame() {
2932 let port = find_free_port();
2933 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2934 let (tx, _rx) = crate::event::channel();
2935
2936 let config = make_client_config(port, 9200);
2937 let mut writer = start_client(config, tx).unwrap();
2938
2939 let (mut server_stream, _) = listener.accept().unwrap();
2940 server_stream
2941 .set_read_timeout(Some(Duration::from_secs(2)))
2942 .unwrap();
2943
2944 let payload: Vec<u8> = (0..24).collect();
2945 writer.send_frame(&payload).unwrap();
2946
2947 let mut buf = [0u8; 256];
2948 let n = server_stream.read(&mut buf).unwrap();
2949 let expected = hdlc::frame(&payload);
2950 assert_eq!(&buf[..n], &expected[..]);
2951 }
2952
2953 #[test]
2954 fn backbone_max_connections_rejects_excess() {
2955 let port = find_free_port();
2956 let (tx, rx) = crate::event::channel();
2957 let next_id = Arc::new(AtomicU64::new(8800));
2958
2959 let config = make_server_config(
2960 port,
2961 88,
2962 Some(2),
2963 None,
2964 None,
2965 BackboneAbuseConfig::default(),
2966 );
2967
2968 start(config, tx, next_id).unwrap();
2969 thread::sleep(Duration::from_millis(50));
2970
2971 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2973 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2974
2975 for _ in 0..2 {
2977 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2978 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
2979 }
2980
2981 let client3 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2983 client3
2984 .set_read_timeout(Some(Duration::from_millis(500)))
2985 .unwrap();
2986
2987 thread::sleep(Duration::from_millis(100));
2989
2990 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
2992 assert!(
2993 result.is_err(),
2994 "expected no InterfaceUp for rejected connection, got {:?}",
2995 result
2996 );
2997 }
2998
2999 #[test]
3000 fn backbone_max_connections_allows_after_disconnect() {
3001 let port = find_free_port();
3002 let (tx, rx) = crate::event::channel();
3003 let next_id = Arc::new(AtomicU64::new(8900));
3004
3005 let config = make_server_config(
3006 port,
3007 89,
3008 Some(1),
3009 None,
3010 None,
3011 BackboneAbuseConfig::default(),
3012 );
3013
3014 start(config, tx, next_id).unwrap();
3015 thread::sleep(Duration::from_millis(50));
3016
3017 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3019 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3020 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
3021
3022 drop(client1);
3024
3025 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3027 assert!(matches!(event, Event::InterfaceDown(_)));
3028
3029 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3031 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3032 assert!(
3033 matches!(event, Event::InterfaceUp(_, _, _)),
3034 "expected InterfaceUp after slot freed, got {:?}",
3035 event
3036 );
3037 }
3038
3039 #[test]
3040 fn backbone_client_reconnect() {
3041 let port = find_free_port();
3042 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
3043 listener.set_nonblocking(false).unwrap();
3044 let (tx, rx) = crate::event::channel();
3045
3046 let config = make_client_config(port, 9300);
3047 let _writer = start_client(config, tx).unwrap();
3048
3049 let (server_stream, _) = listener.accept().unwrap();
3051
3052 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
3054
3055 drop(server_stream);
3056
3057 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3059 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9300))));
3060
3061 let _server_stream2 = listener.accept().unwrap();
3063
3064 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3066 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9300), _, _)));
3067 }
3068
3069 #[test]
3070 fn backbone_client_stop_prevents_reconnect() {
3071 let port = find_free_port();
3072 let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
3073 let (tx, rx) = crate::event::channel();
3074
3075 let config = make_client_config(port, 9350);
3076 let control = ListenerControl::new();
3077 let _writer = start_client_with_ifac(config, tx, 0, None, control.clone()).unwrap();
3078
3079 let (server_stream, _) = listener.accept().unwrap();
3080 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
3082
3083 control.request_stop();
3084 drop(server_stream);
3085
3086 if let Ok(event) = recv_non_peer_event(&rx, Duration::from_millis(700)) {
3088 panic!("unexpected event after stop: {:?}", event);
3089 }
3090 }
3091
3092 #[test]
3093 fn backbone_idle_timeout_disconnects_silent_client() {
3094 let port = find_free_port();
3095 let (tx, rx) = crate::event::channel();
3096 let next_id = Arc::new(AtomicU64::new(9400));
3097
3098 let config = make_server_config(
3099 port,
3100 94,
3101 None,
3102 Some(Duration::from_millis(150)),
3103 None,
3104 BackboneAbuseConfig::default(),
3105 );
3106
3107 start(config, tx, next_id).unwrap();
3108 thread::sleep(Duration::from_millis(50));
3109
3110 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3111
3112 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3113 let client_id = match event {
3114 Event::InterfaceUp(id, _, _) => id,
3115 other => panic!("expected InterfaceUp, got {:?}", other),
3116 };
3117
3118 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3119 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3120 }
3121
3122 #[test]
3123 fn backbone_idle_timeout_ignores_client_after_data() {
3124 let port = find_free_port();
3125 let (tx, rx) = crate::event::channel();
3126 let next_id = Arc::new(AtomicU64::new(9500));
3127
3128 let config = make_server_config(
3129 port,
3130 95,
3131 None,
3132 Some(Duration::from_millis(200)),
3133 None,
3134 BackboneAbuseConfig::default(),
3135 );
3136
3137 start(config, tx, next_id).unwrap();
3138 thread::sleep(Duration::from_millis(50));
3139
3140 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3141
3142 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3143 let client_id = match event {
3144 Event::InterfaceUp(id, _, _) => id,
3145 other => panic!("expected InterfaceUp, got {:?}", other),
3146 };
3147
3148 client.write_all(&hdlc::frame(&[1u8; 24])).unwrap();
3149
3150 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3151 match event {
3152 Event::Frame {
3153 interface_id,
3154 data,
3155 rssi: _,
3156 snr: _,
3157 } => {
3158 assert_eq!(interface_id, client_id);
3159 assert_eq!(data, vec![1u8; 24]);
3160 }
3161 other => panic!("expected Frame, got {:?}", other),
3162 }
3163
3164 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
3165 assert!(
3166 result.is_err(),
3167 "expected no InterfaceDown after client sent data, got {:?}",
3168 result
3169 );
3170 }
3171
3172 #[test]
3173 fn backbone_runtime_idle_timeout_updates_live() {
3174 let port = find_free_port();
3175 let (tx, rx) = crate::event::channel();
3176 let next_id = Arc::new(AtomicU64::new(9650));
3177
3178 let config = make_server_config(port, 97, None, None, None, BackboneAbuseConfig::default());
3179 let runtime = Arc::clone(&config.runtime);
3180
3181 start(config, tx, next_id).unwrap();
3182 thread::sleep(Duration::from_millis(50));
3183
3184 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3185 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3186 let client_id = match event {
3187 Event::InterfaceUp(id, _, _) => id,
3188 other => panic!("expected InterfaceUp, got {:?}", other),
3189 };
3190
3191 {
3192 let mut runtime = runtime.lock().unwrap();
3193 runtime.idle_timeout = Some(Duration::from_millis(150));
3194 }
3195
3196 let event = recv_non_peer_event(&rx, Duration::from_secs(4)).unwrap();
3197 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3198 }
3199
3200 #[test]
3201 fn backbone_write_stall_timeout_disconnects_unwritable_client() {
3202 let port = find_free_port();
3203 let (tx, rx) = crate::event::channel();
3204 let next_id = Arc::new(AtomicU64::new(9660));
3205
3206 let config = make_server_config(
3207 port,
3208 98,
3209 None,
3210 None,
3211 Some(Duration::from_millis(50)),
3212 BackboneAbuseConfig::default(),
3213 );
3214
3215 start(config, tx, next_id).unwrap();
3216 thread::sleep(Duration::from_millis(50));
3217
3218 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3219 client
3220 .set_read_timeout(Some(Duration::from_millis(100)))
3221 .unwrap();
3222 let sock = SockRef::from(&client);
3223 sock.set_recv_buffer_size(4096).ok();
3224
3225 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3226 let (client_id, mut writer) = match event {
3227 Event::InterfaceUp(id, Some(writer), _) => (id, writer),
3228 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3229 };
3230
3231 let payload = vec![0x55; 512 * 1024];
3232 let deadline = Instant::now() + Duration::from_secs(3);
3233 let mut stalled = false;
3234 while Instant::now() < deadline {
3235 match writer.send_frame(&payload) {
3236 Ok(()) => {}
3237 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
3238 thread::sleep(Duration::from_millis(10));
3239 }
3240 Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {
3241 stalled = true;
3242 break;
3243 }
3244 Err(e) => panic!("unexpected send error: {}", e),
3245 }
3246 }
3247
3248 assert!(stalled, "expected writer to time out on persistent stall");
3249 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3250 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3251 }
3252
3253 #[test]
3254 fn shutting_down_backbone_writer_closes_the_client_socket() {
3255 let port = find_free_port();
3256 let (tx, rx) = crate::event::channel();
3257 let next_id = Arc::new(AtomicU64::new(9670));
3258 let config = make_server_config(port, 99, None, None, None, BackboneAbuseConfig::default());
3259
3260 start(config, tx, next_id).unwrap();
3261 thread::sleep(Duration::from_millis(50));
3262
3263 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3264 client
3265 .set_read_timeout(Some(Duration::from_secs(2)))
3266 .unwrap();
3267 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3268 let mut writer = match event {
3269 Event::InterfaceUp(_, Some(writer), _) => writer,
3270 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3271 };
3272
3273 writer.shutdown();
3274
3275 let mut byte = [0u8; 1];
3276 assert_eq!(client.read(&mut byte).unwrap(), 0);
3277 }
3278
3279 fn wait_for<F>(
3281 rx: &crate::event::EventReceiver,
3282 timeout: Duration,
3283 mut pred: F,
3284 ) -> Option<Event>
3285 where
3286 F: FnMut(&Event) -> bool,
3287 {
3288 let deadline = Instant::now() + timeout;
3289 loop {
3290 let remaining = deadline.saturating_duration_since(Instant::now());
3291 if remaining.is_zero() {
3292 return None;
3293 }
3294 match rx.recv_timeout(remaining) {
3295 Ok(event) if pred(&event) => return Some(event),
3296 Ok(_) => continue,
3297 Err(_) => return None,
3298 }
3299 }
3300 }
3301
3302 #[test]
3303 fn backbone_write_stall_emits_peer_events() {
3304 let port = find_free_port();
3305 let (tx, rx) = crate::event::channel();
3306 let next_id = Arc::new(AtomicU64::new(9700));
3307
3308 let config = make_server_config(
3309 port,
3310 97,
3311 None,
3312 None,
3313 Some(Duration::from_millis(50)), BackboneAbuseConfig::default(),
3315 );
3316
3317 start(config, tx, next_id).unwrap();
3318 thread::sleep(Duration::from_millis(50));
3319
3320 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3322 client
3323 .set_read_timeout(Some(Duration::from_millis(100)))
3324 .unwrap();
3325 let sock = SockRef::from(&client);
3326 sock.set_recv_buffer_size(4096).ok();
3327
3328 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3330 let mut writer = match event {
3331 Event::InterfaceUp(_, Some(w), _) => w,
3332 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3333 };
3334
3335 let payload = vec![0x55; 512 * 1024];
3337 let deadline = Instant::now() + Duration::from_secs(3);
3338 while Instant::now() < deadline {
3339 match writer.send_frame(&payload) {
3340 Ok(()) | Err(_) => {
3341 if Instant::now() + Duration::from_millis(10) > deadline {
3342 break;
3343 }
3344 thread::sleep(Duration::from_millis(10));
3345 }
3346 }
3347 }
3348
3349 let stall_event = wait_for(&rx, Duration::from_secs(3), |e| {
3351 matches!(e, Event::BackbonePeerWriteStall { .. })
3352 });
3353 assert!(
3354 stall_event.is_some(),
3355 "expected BackbonePeerWriteStall event"
3356 );
3357 }
3358
3359 #[test]
3360 fn backbone_blacklisted_peer_rejected_on_connect() {
3361 let port = find_free_port();
3362 let (tx, rx) = crate::event::channel();
3363 let next_id = Arc::new(AtomicU64::new(9800));
3364
3365 let config = make_server_config(port, 98, None, None, None, BackboneAbuseConfig::default());
3366 let peer_state = config.peer_state.clone();
3367
3368 start(config, tx.clone(), next_id).unwrap();
3369 thread::sleep(Duration::from_millis(50));
3370
3371 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3373 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3374 assert!(
3375 matches!(event, Event::InterfaceUp(_, _, _)),
3376 "first connection should succeed"
3377 );
3378 drop(client1);
3379
3380 thread::sleep(Duration::from_millis(100));
3382 while rx.try_recv().is_ok() {}
3383
3384 peer_state.lock().unwrap().blacklist(
3386 "127.0.0.1".parse().unwrap(),
3387 Duration::from_secs(60),
3388 "test blacklist".into(),
3389 );
3390
3391 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3393 thread::sleep(Duration::from_millis(200));
3395
3396 let event = rx.try_recv();
3398 if let Ok(Event::InterfaceUp(_, _, _)) = event {
3399 panic!("blacklisted peer should not get InterfaceUp")
3400 }
3401 }
3402
3403 #[test]
3404 fn backbone_six_fast_flaps_reject_before_allocating_seventh_interface() {
3405 let port = find_free_port();
3406 let (tx, rx) = crate::event::channel();
3407 let next_id = Arc::new(AtomicU64::new(9900));
3408 let mut config =
3409 make_server_config(port, 99, None, None, None, BackboneAbuseConfig::default());
3410 config.fast_flap = test_fast_flap_config();
3411 let fast_flap_state = Arc::clone(&config.fast_flap_state);
3412 let peer_state = Arc::clone(&config.peer_state);
3413
3414 start(config, tx, Arc::clone(&next_id)).unwrap();
3415 thread::sleep(Duration::from_millis(50));
3416
3417 for flap in 0..6 {
3418 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
3419 let up = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3420 assert!(matches!(up, Event::InterfaceUp(_, _, _)));
3421
3422 if flap == 0 {
3424 let payload = vec![0x41; 32];
3425 client.write_all(&hdlc::frame(&payload)).unwrap();
3426 let frame = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3427 assert!(matches!(frame, Event::Frame { data, .. } if data == payload));
3428 }
3429
3430 drop(client);
3431 let down = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3432 assert!(matches!(down, Event::InterfaceDown(_)));
3433 }
3434
3435 let peer_ip: IpAddr = "127.0.0.1".parse().unwrap();
3436 assert!(
3437 lock_or_recover(&fast_flap_state, "test fast-flap state").is_blocked(
3438 peer_ip,
3439 &test_fast_flap_config(),
3440 Instant::now(),
3441 )
3442 );
3443 assert_eq!(next_id.load(Ordering::Relaxed), 9906);
3444 let entries = lock_or_recover(&peer_state, "test peer state").list("test-backbone");
3445 let entry = entries
3446 .iter()
3447 .find(|entry| entry.peer_ip == peer_ip)
3448 .expect("fast-flapping peer state");
3449 assert_eq!(entry.blacklist_reason.as_deref(), Some("fast flapping"));
3450 assert!(entry.blacklisted_remaining_secs.is_some());
3451
3452 let _rejected = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
3453 thread::sleep(Duration::from_millis(200));
3454 assert_eq!(
3455 next_id.load(Ordering::Relaxed),
3456 9906,
3457 "rejection must happen before dynamic interface allocation"
3458 );
3459 while let Ok(event) = rx.try_recv() {
3460 assert!(
3461 !matches!(event, Event::InterfaceUp(_, _, _)),
3462 "blocked peer must not register a dynamic interface"
3463 );
3464 }
3465 let entries = lock_or_recover(&peer_state, "test peer state").list("test-backbone");
3466 assert_eq!(
3467 entries
3468 .iter()
3469 .find(|entry| entry.peer_ip == peer_ip)
3470 .unwrap()
3471 .reject_count,
3472 1
3473 );
3474 }
3475
3476 #[test]
3477 fn backbone_parse_config_reads_abuse_settings() {
3478 let factory = BackboneInterfaceFactory;
3479 let mut params = HashMap::new();
3480 params.insert("listen_ip".into(), "127.0.0.1".into());
3481 params.insert("listen_port".into(), "4242".into());
3482 params.insert("idle_timeout".into(), "15".into());
3483 params.insert("write_stall_timeout".into(), "45".into());
3484 params.insert("max_penalty_duration".into(), "3600".into());
3485
3486 let config = factory
3487 .parse_config("test-backbone", InterfaceId(97), ¶ms)
3488 .unwrap();
3489 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3490
3491 match mode {
3492 BackboneMode::Server(config) => {
3493 assert_eq!(config.listen_ip, "127.0.0.1");
3494 assert_eq!(config.listen_port, 4242);
3495 assert_eq!(config.idle_timeout, Some(Duration::from_secs(15)));
3496 assert_eq!(config.write_stall_timeout, Some(Duration::from_secs(45)));
3497 assert_eq!(
3498 config.abuse.max_penalty_duration,
3499 Some(Duration::from_secs(3600))
3500 );
3501 }
3502 BackboneMode::Client(_, _) => panic!("expected server config"),
3503 }
3504 }
3505
3506 #[test]
3507 fn backbone_parse_config_reads_fast_flapping_settings() {
3508 let factory = BackboneInterfaceFactory;
3509 let mut params = HashMap::new();
3510 params.insert("listen_ip".into(), "127.0.0.1".into());
3511 params.insert("listen_port".into(), "4242".into());
3512 params.insert("block_fast_flapping".into(), "no".into());
3513 params.insert("fast_flapping_threshold".into(), "2.5".into());
3514 params.insert("fast_flapping_grace".into(), "9".into());
3515 params.insert("fast_flapping_block_time".into(), "1.5".into());
3516
3517 let config = factory
3518 .parse_config("test-backbone", InterfaceId(93), ¶ms)
3519 .unwrap();
3520 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3521 let BackboneMode::Server(config) = mode else {
3522 panic!("expected server config");
3523 };
3524
3525 assert!(!config.fast_flap.enabled);
3526 assert_eq!(
3527 config.fast_flap.connection_threshold,
3528 Duration::from_secs_f64(2.5)
3529 );
3530 assert_eq!(config.fast_flap.grace, 9);
3531 assert_eq!(config.fast_flap.block_duration, Duration::from_secs(90));
3532 }
3533
3534 #[test]
3535 fn backbone_parse_config_rejects_invalid_fast_flapping_settings() {
3536 let factory = BackboneInterfaceFactory;
3537 for (key, value) in [
3538 ("block_fast_flapping", "perhaps"),
3539 ("fast_flapping_threshold", "-1"),
3540 ("fast_flapping_threshold", "NaN"),
3541 ("fast_flapping_grace", "-1"),
3542 ("fast_flapping_block_time", "infinite"),
3543 ] {
3544 let mut params = HashMap::new();
3545 params.insert("listen_port".into(), "4242".into());
3546 params.insert(key.into(), value.into());
3547
3548 let err = match factory.parse_config("test-backbone", InterfaceId(92), ¶ms) {
3549 Ok(_) => panic!("{key}={value} should be rejected"),
3550 Err(err) => err,
3551 };
3552 assert!(err.contains(key), "unexpected error: {err}");
3553 }
3554 }
3555
3556 #[test]
3557 fn parsed_backbone_listeners_share_fast_flap_history() {
3558 let factory = BackboneInterfaceFactory;
3559 let mut params = HashMap::new();
3560 params.insert("listen_port".into(), "4242".into());
3561
3562 let first = factory
3563 .parse_config("first", InterfaceId(90), ¶ms)
3564 .unwrap();
3565 let second = factory
3566 .parse_config("second", InterfaceId(91), ¶ms)
3567 .unwrap();
3568 let BackboneMode::Server(first) = *first.into_any().downcast::<BackboneMode>().unwrap()
3569 else {
3570 panic!("expected server config");
3571 };
3572 let BackboneMode::Server(second) = *second.into_any().downcast::<BackboneMode>().unwrap()
3573 else {
3574 panic!("expected server config");
3575 };
3576
3577 assert!(Arc::ptr_eq(&first.fast_flap_state, &second.fast_flap_state));
3578 }
3579
3580 #[test]
3581 fn backbone_parse_config_reads_client_priority() {
3582 let factory = BackboneInterfaceFactory;
3583 let mut params = HashMap::new();
3584 params.insert("remote".into(), "example.com".into());
3585 params.insert("target_port".into(), "4242".into());
3586 params.insert("priority".into(), "87".into());
3587
3588 let config = factory
3589 .parse_config("test-backbone-client", InterfaceId(96), ¶ms)
3590 .unwrap();
3591 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3592
3593 match mode {
3594 BackboneMode::Client(_, priority) => assert_eq!(priority, 87),
3595 BackboneMode::Server(_) => panic!("expected client config"),
3596 }
3597 }
3598
3599 #[test]
3600 fn backbone_parse_config_defaults_client_priority() {
3601 let factory = BackboneInterfaceFactory;
3602 let mut params = HashMap::new();
3603 params.insert("remote".into(), "example.com".into());
3604
3605 let config = factory
3606 .parse_config("test-backbone-client", InterfaceId(95), ¶ms)
3607 .unwrap();
3608 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3609
3610 match mode {
3611 BackboneMode::Client(_, priority) => assert_eq!(priority, 60),
3612 BackboneMode::Server(_) => panic!("expected client config"),
3613 }
3614 }
3615
3616 #[test]
3617 fn backbone_parse_config_rejects_invalid_client_priority() {
3618 let factory = BackboneInterfaceFactory;
3619 for value in ["-1", "101", "fast"] {
3620 let mut params = HashMap::new();
3621 params.insert("remote".into(), "example.com".into());
3622 params.insert("priority".into(), value.into());
3623
3624 let err = match factory.parse_config("test-backbone-client", InterfaceId(94), ¶ms) {
3625 Ok(_) => panic!("priority {value} should be rejected"),
3626 Err(err) => err,
3627 };
3628 assert!(
3629 err.contains("priority"),
3630 "unexpected error for {value}: {err}"
3631 );
3632 }
3633 }
3634}