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