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