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 {
1477 stream: TcpStream,
1478 transmit_buffer: TransmitBuffer,
1479 egress_control: EgressControl,
1480}
1481
1482impl Writer for BackboneClientWriter {
1483 fn send_frame_confirmed(&mut self, data: &[u8]) -> io::Result<()> {
1484 if !self
1485 .transmit_buffer
1486 .append_with_limit(hdlc::frame(data), Some(EGRESS_HIGH_WATERMARK))
1487 {
1488 return Err(io::Error::new(
1489 io::ErrorKind::InvalidInput,
1490 "confirmed frame exceeds backbone buffer",
1491 ));
1492 }
1493 self.transmit_buffer.flush();
1494 while self.transmit_buffer.sendable_bytes() > 0 {
1495 if self.transmit_buffer.drain_to(&mut self.stream)? == 0 {
1496 return Err(io::Error::new(
1497 io::ErrorKind::WriteZero,
1498 "backbone writer made no progress",
1499 ));
1500 }
1501 }
1502 Ok(())
1503 }
1504 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
1505 self.send_frames(&[crate::interface::FrameBytes::Owned(data.to_vec())])
1506 }
1507
1508 fn send_frames(&mut self, frames: &[crate::interface::FrameBytes]) -> io::Result<()> {
1509 for frame in frames {
1510 let encoded = hdlc::frame(frame);
1511 let encoded_len = encoded.len();
1512 if !self
1513 .transmit_buffer
1514 .append_with_limit(encoded, Some(EGRESS_HIGH_WATERMARK))
1515 {
1516 self.egress_control.record_drop(encoded_len);
1517 }
1518 }
1519 self.transmit_buffer.flush();
1520 while self.transmit_buffer.sendable_bytes() > 0 {
1521 if self.transmit_buffer.drain_to(&mut self.stream)? == 0 {
1522 return Err(io::Error::new(
1523 io::ErrorKind::WriteZero,
1524 "backbone client writer made no progress",
1525 ));
1526 }
1527 }
1528 Ok(())
1529 }
1530
1531 fn egress_control(&self) -> Option<EgressControl> {
1532 Some(self.egress_control.clone())
1533 }
1534
1535 fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
1536 Some(self.stream.as_raw_fd())
1537 }
1538
1539 fn loop_egress(&mut self) -> Option<LoopEgress> {
1540 Some(LoopEgress::budget(self.egress_control.clone()))
1541 }
1542}
1543
1544fn try_connect_client(
1546 config: &BackboneClientConfig,
1547 underlay_mark: Option<u32>,
1548) -> io::Result<TcpStream> {
1549 let runtime = lock_or_recover(&config.runtime, "backbone runtime").clone();
1550 let addr_str = format!("{}:{}", config.target_host, config.target_port);
1551 let addr = addr_str
1552 .to_socket_addrs()?
1553 .next()
1554 .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
1555
1556 #[cfg(target_os = "linux")]
1557 let stream = if underlay_mark.is_some() {
1558 super::connect_tcp_with_options(&addr, None, underlay_mark, runtime.connect_timeout)?
1559 } else {
1560 TcpStream::connect_timeout(&addr, runtime.connect_timeout)?
1561 };
1562 #[cfg(not(target_os = "linux"))]
1563 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
1564 stream.set_nodelay(true)?;
1565 set_tcp_keepalive(&stream).ok();
1566
1567 #[cfg(target_os = "macos")]
1569 {
1570 let sock = SockRef::from(&stream);
1571 sock.set_nosigpipe(true).ok();
1572 }
1573
1574 Ok(stream)
1575}
1576
1577pub fn start_client(config: BackboneClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
1579 start_client_with_ifac(config, tx, 0, None, ListenerControl::new())
1580}
1581
1582pub(crate) fn start_client_with_ifac(
1583 config: BackboneClientConfig,
1584 tx: EventSender,
1585 ifac_size: usize,
1586 underlay_mark: Option<u32>,
1587 control: ListenerControl,
1588) -> io::Result<Box<dyn Writer>> {
1589 let stream = try_connect_client(&config, underlay_mark)?;
1590 let writer_stream = stream.try_clone()?;
1591
1592 let id = config.interface_id;
1593 log::info!(
1594 "[{}] backbone client connected to {}:{}",
1595 config.name,
1596 config.target_host,
1597 config.target_port
1598 );
1599
1600 let _ = tx.send(Event::InterfaceUp(id, None, None));
1602
1603 let link = Arc::new(ClientLink {
1604 config,
1605 tx,
1606 ifac_size,
1607 underlay_mark,
1608 control,
1609 });
1610 if let Err(error) = link.serve(stream) {
1611 let _ = link.tx.send(Event::InterfaceDown(id));
1612 return Err(error);
1613 }
1614
1615 Ok(Box::new(BackboneClientWriter {
1616 stream: writer_stream,
1617 transmit_buffer: TransmitBuffer::new(),
1618 egress_control: EgressControl::new(EGRESS_HIGH_WATERMARK),
1619 }))
1620}
1621
1622struct ClientLink {
1624 config: BackboneClientConfig,
1625 tx: EventSender,
1626 ifac_size: usize,
1627 underlay_mark: Option<u32>,
1628 control: ListenerControl,
1629}
1630
1631impl ClientLink {
1632 fn serve(self: &Arc<Self>, stream: TcpStream) -> io::Result<()> {
1634 let source = ClientSource {
1635 stream,
1636 decoder: hdlc::Decoder::reticulum(HW_MTU, self.ifac_size),
1637 link: Arc::clone(self),
1638 };
1639 self.tx.register_source(
1640 crate::interface::loop_writer::read_key(self.config.interface_id),
1641 Box::new(source),
1642 )
1643 }
1644
1645 fn connection_lost(self: &Arc<Self>) {
1649 let id = self.config.interface_id;
1650 let _ = self.tx.push_from_loop([Event::InterfaceDown(id)]);
1651 let link = Arc::clone(self);
1652 let spawned = thread::Builder::new()
1653 .name(format!("bb-reconn-{}", id.0))
1654 .spawn(move || link.reconnect_loop());
1655 if let Err(error) = spawned {
1656 log::error!(
1657 "[{}] cannot start reconnecting, giving up: {}",
1658 self.config.name,
1659 error
1660 );
1661 }
1662 }
1663
1664 fn reconnect_loop(self: Arc<Self>) {
1665 loop {
1666 let Some(stream) =
1667 client_reconnect(&self.config, &self.tx, self.underlay_mark, &self.control)
1668 else {
1669 if !self.control.should_stop() {
1670 log::error!("[{}] reconnection failed, giving up", self.config.name);
1671 }
1672 return;
1673 };
1674 match self.serve(stream) {
1675 Ok(()) => return,
1676 Err(error) => {
1677 log::warn!(
1678 "[{}] cannot read the new connection: {}",
1679 self.config.name,
1680 error
1681 );
1682 let _ = self.tx.send(Event::InterfaceDown(self.config.interface_id));
1683 }
1684 }
1685 }
1686 }
1687}
1688
1689struct ClientSource {
1691 stream: TcpStream,
1692 decoder: hdlc::Decoder,
1693 link: Arc<ClientLink>,
1694}
1695
1696impl LoopSource for ClientSource {
1697 fn fd(&self) -> std::os::fd::RawFd {
1698 self.stream.as_raw_fd()
1699 }
1700
1701 fn stopped(&self) -> bool {
1702 self.link.control.should_stop()
1703 }
1704
1705 fn on_ready(&mut self, cx: &mut LoopContext<'_>) -> SourceStatus {
1706 let link = &self.link;
1707 if link.control.should_stop() {
1708 return SourceStatus::Remove;
1709 }
1710 let name = &link.config.name;
1711 match read_stream_frames(
1712 self.stream.as_raw_fd(),
1713 &mut self.decoder,
1714 cx,
1715 &link.tx,
1716 link.config.interface_id,
1717 name,
1718 |_, _| {},
1719 ) {
1720 StreamRead::Rearm => SourceStatus::Rearm,
1721 StreamRead::Suspend => SourceStatus::Suspend,
1722 StreamRead::Closed => {
1723 log::warn!("[{}] connection closed", name);
1724 link.connection_lost();
1725 SourceStatus::Remove
1726 }
1727 StreamRead::Failed(error) => {
1728 log::warn!("[{}] read error: {}", name, error);
1729 link.connection_lost();
1730 SourceStatus::Remove
1731 }
1732 StreamRead::DriverGone => SourceStatus::Remove,
1733 }
1734 }
1735}
1736
1737const MAX_BACKOFF_SHIFT: u32 = 6;
1740
1741fn client_reconnect(
1745 config: &BackboneClientConfig,
1746 tx: &EventSender,
1747 underlay_mark: Option<u32>,
1748 control: &ListenerControl,
1749) -> Option<TcpStream> {
1750 let mut attempts = 0u32;
1751 loop {
1752 let runtime = lock_or_recover(&config.runtime, "backbone runtime").clone();
1753
1754 let shift = attempts.min(MAX_BACKOFF_SHIFT);
1755 let backoff = runtime.reconnect_wait * 2u32.pow(shift);
1756 let jitter_range = backoff / 4;
1758 let jitter = if jitter_range.as_nanos() > 0 {
1759 let offset = Duration::from_nanos(
1760 (std::hash::RandomState::new().build_hasher().finish()
1761 % jitter_range.as_nanos() as u64)
1762 * 2,
1763 );
1764 if offset > jitter_range {
1765 backoff + (offset - jitter_range)
1766 } else {
1767 backoff - (jitter_range - offset)
1768 }
1769 } else {
1770 backoff
1771 };
1772 if !wait_while_active(control, jitter) {
1773 return None;
1774 }
1775
1776 attempts += 1;
1777
1778 if let Some(max) = runtime.max_reconnect_tries {
1779 if attempts > max {
1780 let _ = tx.send(Event::InterfaceDown(config.interface_id));
1781 return None;
1782 }
1783 }
1784
1785 log::info!(
1786 "[{}] reconnect attempt {} (backoff {:.1}s) ...",
1787 config.name,
1788 attempts,
1789 jitter.as_secs_f64(),
1790 );
1791
1792 match try_connect_client(config, underlay_mark) {
1793 Ok(new_stream) => {
1794 if control.should_stop() {
1795 return None;
1796 }
1797 let writer_stream = match new_stream.try_clone() {
1798 Ok(s) => s,
1799 Err(e) => {
1800 log::warn!("[{}] failed to clone stream: {}", config.name, e);
1801 continue;
1802 }
1803 };
1804 log::info!(
1805 "[{}] reconnected after {} attempt(s)",
1806 config.name,
1807 attempts
1808 );
1809 let new_writer: Box<dyn Writer> = Box::new(BackboneClientWriter {
1810 stream: writer_stream,
1811 transmit_buffer: TransmitBuffer::new(),
1812 egress_control: EgressControl::new(EGRESS_HIGH_WATERMARK),
1813 });
1814 let _ = tx.send(Event::InterfaceUp(
1815 config.interface_id,
1816 Some(new_writer),
1817 None,
1818 ));
1819 return Some(new_stream);
1820 }
1821 Err(e) => {
1822 log::warn!("[{}] reconnect failed: {}", config.name, e);
1823 }
1824 }
1825 }
1826}
1827
1828#[derive(Clone)]
1835pub(crate) enum BackboneMode {
1836 Server(BackboneConfig),
1837 Client(BackboneClientConfig, u8),
1838}
1839
1840pub struct BackboneInterfaceFactory;
1846
1847pub struct BackboneClientInterfaceFactory;
1850
1851impl InterfaceFactory for BackboneClientInterfaceFactory {
1852 fn type_name(&self) -> &str {
1853 "BackboneClientInterface"
1854 }
1855
1856 fn parse_config(
1857 &self,
1858 name: &str,
1859 id: InterfaceId,
1860 params: &HashMap<String, String>,
1861 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1862 if !params.contains_key("remote") && !params.contains_key("target_host") {
1863 return Err("BackboneClientInterface requires 'remote' or 'target_host'".into());
1864 }
1865 BackboneInterfaceFactory.parse_config(name, id, params)
1866 }
1867
1868 fn start(
1869 &self,
1870 config: Box<dyn InterfaceConfigData>,
1871 ctx: StartContext,
1872 ) -> io::Result<StartResult> {
1873 BackboneInterfaceFactory.start(config, ctx)
1874 }
1875}
1876
1877fn parse_positive_duration_secs(params: &HashMap<String, String>, key: &str) -> Option<Duration> {
1878 params
1879 .get(key)
1880 .and_then(|v| v.parse::<f64>().ok())
1881 .filter(|v| *v > 0.0)
1882 .and_then(|v| Duration::try_from_secs_f64(v).ok())
1883 .filter(|duration| Instant::now().checked_add(*duration).is_some())
1884}
1885
1886fn parse_nonnegative_duration(
1887 params: &HashMap<String, String>,
1888 key: &str,
1889 multiplier: f64,
1890 default: Duration,
1891) -> Result<Duration, String> {
1892 let Some(raw) = params.get(key) else {
1893 return Ok(default);
1894 };
1895 let value = raw
1896 .parse::<f64>()
1897 .map_err(|_| format!("invalid {key} '{raw}' (expected a non-negative number)"))?;
1898 let seconds = value * multiplier;
1899 if !value.is_finite() || value < 0.0 || !seconds.is_finite() || seconds > u64::MAX as f64 {
1900 return Err(format!(
1901 "invalid {key} '{raw}' (expected a finite non-negative number)"
1902 ));
1903 }
1904 let duration = Duration::from_secs_f64(seconds);
1905 if Instant::now().checked_add(duration).is_none() {
1906 return Err(format!("invalid {key} '{raw}' (duration is too large)"));
1907 }
1908 Ok(duration)
1909}
1910
1911fn parse_fast_flap_config(
1912 params: &HashMap<String, String>,
1913) -> Result<BackboneFastFlapConfig, String> {
1914 let defaults = BackboneFastFlapConfig::default();
1915 let enabled = match params.get("block_fast_flapping") {
1916 Some(raw) => crate::config::parse_bool_pub(raw)
1917 .ok_or_else(|| format!("invalid block_fast_flapping '{raw}' (expected yes or no)"))?,
1918 None => defaults.enabled,
1919 };
1920 let connection_threshold = parse_nonnegative_duration(
1921 params,
1922 "fast_flapping_threshold",
1923 1.0,
1924 defaults.connection_threshold,
1925 )?;
1926 let grace = match params.get("fast_flapping_grace") {
1927 Some(raw) => raw.parse::<u64>().map_err(|_| {
1928 format!("invalid fast_flapping_grace '{raw}' (expected a non-negative integer)")
1929 })?,
1930 None => defaults.grace,
1931 };
1932 let block_duration = parse_nonnegative_duration(
1933 params,
1934 "fast_flapping_block_time",
1935 60.0,
1936 defaults.block_duration,
1937 )?;
1938
1939 Ok(BackboneFastFlapConfig {
1940 enabled,
1941 connection_threshold,
1942 grace,
1943 block_duration,
1944 })
1945}
1946
1947impl InterfaceFactory for BackboneInterfaceFactory {
1948 fn type_name(&self) -> &str {
1949 "BackboneInterface"
1950 }
1951
1952 fn parse_config(
1953 &self,
1954 name: &str,
1955 id: InterfaceId,
1956 params: &HashMap<String, String>,
1957 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1958 if let Some(target_host) = params.get("remote").or_else(|| params.get("target_host")) {
1959 let target_host = target_host.clone();
1961 let target_port = params
1962 .get("target_port")
1963 .or_else(|| params.get("port"))
1964 .and_then(|v| v.parse().ok())
1965 .unwrap_or(4242);
1966 let transport_identity = params.get("transport_identity").cloned();
1967 let priority = match params.get("priority") {
1968 Some(value) => value.parse::<u8>().map_err(|_| {
1969 format!(
1970 "invalid Backbone peer priority '{}' (expected 0..100)",
1971 value
1972 )
1973 })?,
1974 None => crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1975 };
1976 if priority > 100 {
1977 return Err(format!(
1978 "invalid Backbone peer priority '{}' (expected 0..100)",
1979 priority
1980 ));
1981 }
1982 Ok(Box::new(BackboneMode::Client(
1983 BackboneClientConfig {
1984 name: name.to_string(),
1985 target_host,
1986 target_port,
1987 interface_id: id,
1988 transport_identity,
1989 ..BackboneClientConfig::default()
1990 },
1991 priority,
1992 )))
1993 } else {
1994 let listen_ip = params
1996 .get("listen_ip")
1997 .or_else(|| params.get("device"))
1998 .cloned()
1999 .unwrap_or_else(|| "0.0.0.0".into());
2000 let listen_port = params
2001 .get("listen_port")
2002 .or_else(|| params.get("port"))
2003 .and_then(|v| v.parse().ok())
2004 .unwrap_or(4242);
2005 let max_connections = params.get("max_connections").and_then(|v| v.parse().ok());
2006 let idle_timeout = parse_positive_duration_secs(params, "idle_timeout");
2007 let write_stall_timeout = parse_positive_duration_secs(params, "write_stall_timeout");
2008 let abuse = BackboneAbuseConfig {
2009 max_penalty_duration: parse_positive_duration_secs(params, "max_penalty_duration"),
2010 };
2011 let fast_flap = parse_fast_flap_config(params)?;
2012 let mut config = BackboneConfig {
2013 name: name.to_string(),
2014 listen_ip,
2015 listen_port,
2016 interface_id: id,
2017 mode: constants::MODE_FULL,
2018 gravity: 0,
2019 recursive_prs: false,
2020 announces_from_internal: true,
2021 announces_to_internal: None,
2022 max_connections,
2023 idle_timeout,
2024 write_stall_timeout,
2025 abuse,
2026 fast_flap,
2027 fast_flap_state: shared_fast_flap_monitor(),
2028 ingress_control: IngressControlConfig::enabled(),
2029 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
2030 max_connections: None,
2031 idle_timeout: None,
2032 write_stall_timeout: None,
2033 abuse: BackboneAbuseConfig::default(),
2034 })),
2035 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
2036 };
2037 let startup = BackboneServerRuntime::from_config(&config);
2038 config.runtime = Arc::new(Mutex::new(startup));
2039 Ok(Box::new(BackboneMode::Server(config)))
2040 }
2041 }
2042
2043 fn start(
2044 &self,
2045 config: Box<dyn InterfaceConfigData>,
2046 ctx: StartContext,
2047 ) -> io::Result<StartResult> {
2048 let mode = *config.into_any().downcast::<BackboneMode>().map_err(|_| {
2049 io::Error::new(
2050 io::ErrorKind::InvalidData,
2051 "wrong config type for BackboneInterface",
2052 )
2053 })?;
2054
2055 match mode {
2056 BackboneMode::Client(cfg, _) => {
2057 let id = cfg.interface_id;
2058 let name = cfg.name.clone();
2059 let info = InterfaceInfo {
2060 id,
2061 name,
2062 mode: ctx.mode,
2063 gravity: ctx.gravity,
2064 recursive_prs: ctx.recursive_prs,
2065 announces_from_internal: ctx.announces_from_internal,
2066 announces_to_internal: ctx.announces_to_internal,
2067 out_capable: true,
2068 in_capable: true,
2069 bitrate: Some(CLIENT_BITRATE_GUESS),
2070 airtime_profile: None,
2071 announce_rate_target: None,
2072 announce_rate_grace: 0,
2073 announce_rate_penalty: 0.0,
2074 announce_cap: constants::ANNOUNCE_CAP,
2075 is_local_client: false,
2076 wants_tunnel: false,
2077 tunnel_id: None,
2078 mtu: HW_MTU as u32,
2079 ingress_control: ctx.ingress_control,
2080 ia_freq: 0.0,
2081 ip_freq: 0.0,
2082 op_freq: 0.0,
2083 op_samples: 0,
2084 started: crate::time::now(),
2085 };
2086 let ifac_size = ctx.ifac.as_ref().map(|ifac| ifac.size).unwrap_or(0);
2087 let control = ListenerControl::new();
2088 let writer = start_client_with_ifac(
2089 cfg,
2090 ctx.tx,
2091 ifac_size,
2092 ctx.underlay_mark,
2093 control.clone(),
2094 )?;
2095 Ok(StartResult::Simple {
2096 control: Some(control),
2097 id,
2098 info,
2099 writer,
2100 interface_type_name: "BackboneInterface".to_string(),
2101 })
2102 }
2103 BackboneMode::Server(mut cfg) => {
2104 cfg.ingress_control = ctx.ingress_control;
2105 cfg.mode = ctx.mode;
2106 cfg.gravity = ctx.gravity;
2107 cfg.recursive_prs = ctx.recursive_prs;
2108 cfg.announces_from_internal = ctx.announces_from_internal;
2109 cfg.announces_to_internal = ctx.announces_to_internal;
2110 let parent_id = cfg.interface_id;
2111 start_with_template(
2112 cfg,
2113 ctx.tx,
2114 ctx.next_dynamic_id,
2115 Some(DynamicInterfaceTemplate {
2116 parent_id,
2117 interface_type: "BackboneInterfacePeer".into(),
2118 ifac: ctx.ifac,
2119 mode: ctx.mode,
2120 gravity: ctx.gravity,
2121 recursive_prs: ctx.recursive_prs,
2122 announces_from_internal: ctx.announces_from_internal,
2123 announces_to_internal: ctx.announces_to_internal,
2124 }),
2125 ctx.underlay_mark,
2126 )?;
2127 Ok(StartResult::Listener { control: None })
2128 }
2129 }
2130 }
2131}
2132
2133pub(crate) fn runtime_handle_from_mode(mode: &BackboneMode) -> Option<BackboneRuntimeConfigHandle> {
2134 match mode {
2135 BackboneMode::Server(config) => Some(BackboneRuntimeConfigHandle {
2136 interface_name: config.name.clone(),
2137 runtime: Arc::clone(&config.runtime),
2138 startup: BackboneServerRuntime::from_config(config),
2139 }),
2140 BackboneMode::Client(_, _) => None,
2141 }
2142}
2143
2144pub(crate) fn peer_state_handle_from_mode(mode: &BackboneMode) -> Option<BackbonePeerStateHandle> {
2145 match mode {
2146 BackboneMode::Server(config) => Some(BackbonePeerStateHandle {
2147 interface_id: config.interface_id,
2148 interface_name: config.name.clone(),
2149 mode: config.mode,
2150 gravity: config.gravity,
2151 announces_to_internal: config.announces_to_internal,
2152 peer_state: Arc::clone(&config.peer_state),
2153 fast_flap: config.fast_flap,
2154 fast_flap_state: Arc::clone(&config.fast_flap_state),
2155 }),
2156 BackboneMode::Client(_, _) => None,
2157 }
2158}
2159
2160pub(crate) fn client_runtime_handle_from_mode(
2161 mode: &BackboneMode,
2162) -> Option<BackboneClientRuntimeConfigHandle> {
2163 match mode {
2164 BackboneMode::Client(config, _) => Some(BackboneClientRuntimeConfigHandle {
2165 interface_name: config.name.clone(),
2166 runtime: Arc::clone(&config.runtime),
2167 startup: BackboneClientRuntime::from_config(config),
2168 }),
2169 BackboneMode::Server(_) => None,
2170 }
2171}
2172
2173pub(crate) fn client_config_from_mode(mode: &BackboneMode) -> Option<BackboneClientConfig> {
2174 match mode {
2175 BackboneMode::Client(config, _) => Some(config.clone()),
2176 BackboneMode::Server(_) => None,
2177 }
2178}
2179
2180pub(crate) fn client_priority_from_mode(mode: &BackboneMode) -> Option<u8> {
2181 match mode {
2182 BackboneMode::Client(_, priority) => Some(*priority),
2183 BackboneMode::Server(_) => None,
2184 }
2185}
2186
2187#[cfg(test)]
2188mod tests {
2189 use super::*;
2190 use std::io::Read;
2191 use std::sync::mpsc;
2192 use std::time::Duration;
2193
2194 fn find_free_port() -> u16 {
2195 crate::test_support::port()
2196 }
2197
2198 fn serve_for(rx: &crate::event::EventReceiver, duration: Duration) {
2201 let deadline = Instant::now() + duration;
2202 let mut ready = polling::Events::new();
2203 while Instant::now() < deadline {
2204 ready.clear();
2205 rx.poll_ready(&mut ready).unwrap();
2206 rx.serve_sources(&ready);
2207 thread::sleep(Duration::from_millis(2));
2208 }
2209 }
2210
2211 fn test_fast_flap_config() -> BackboneFastFlapConfig {
2212 BackboneFastFlapConfig {
2213 enabled: true,
2214 connection_threshold: Duration::from_secs(20),
2215 grace: 5,
2216 block_duration: Duration::from_secs(12 * 60 * 60),
2217 }
2218 }
2219
2220 #[test]
2221 fn fast_flap_defaults_match_upstream() {
2222 assert_eq!(BackboneFastFlapConfig::default(), test_fast_flap_config());
2223 }
2224
2225 #[test]
2226 fn sixth_short_connection_blocks_but_fifth_does_not() {
2227 let config = test_fast_flap_config();
2228 let mut monitor = BackboneFastFlapMonitor::new();
2229 let peer: IpAddr = "192.0.2.10".parse().unwrap();
2230 let start = Instant::now();
2231
2232 for flap in 1..=5 {
2233 let record = monitor
2234 .record_disconnect(
2235 peer,
2236 Duration::from_secs(19),
2237 &config,
2238 start + Duration::from_secs(flap),
2239 )
2240 .unwrap();
2241 assert_eq!(record.flap_count, flap);
2242 assert!(!record.blocked);
2243 assert!(!monitor.is_blocked(peer, &config, start + Duration::from_secs(flap)));
2244 }
2245
2246 let sixth = monitor
2247 .record_disconnect(
2248 peer,
2249 Duration::from_secs(1),
2250 &config,
2251 start + Duration::from_secs(6),
2252 )
2253 .unwrap();
2254 assert_eq!(sixth.flap_count, 6);
2255 assert!(sixth.blocked);
2256 assert!(sixth.newly_blocked);
2257 assert!(monitor.is_blocked(peer, &config, start + Duration::from_secs(6)));
2258 }
2259
2260 #[test]
2261 fn every_short_connection_counts_even_after_receiving_data() {
2262 let config = test_fast_flap_config();
2263 let mut monitor = BackboneFastFlapMonitor::new();
2264 let peer: IpAddr = "192.0.2.11".parse().unwrap();
2265 let now = Instant::now();
2266
2267 let record = monitor
2268 .record_disconnect(peer, Duration::from_secs(2), &config, now)
2269 .unwrap();
2270 assert_eq!(record.flap_count, 1);
2271 assert_eq!(monitor.flap_count(peer), 1);
2272 }
2273
2274 #[test]
2275 fn threshold_boundary_and_long_connections_are_not_counted() {
2276 let config = test_fast_flap_config();
2277 let mut monitor = BackboneFastFlapMonitor::new();
2278 let peer: IpAddr = "192.0.2.12".parse().unwrap();
2279 let now = Instant::now();
2280
2281 assert!(monitor
2282 .record_disconnect(peer, Duration::from_secs(20), &config, now)
2283 .is_none());
2284 assert!(monitor
2285 .record_disconnect(peer, Duration::from_secs(21), &config, now)
2286 .is_none());
2287 assert_eq!(monitor.flap_count(peer), 0);
2288 }
2289
2290 #[test]
2291 fn disabled_fast_flapping_neither_tracks_nor_blocks() {
2292 let mut config = test_fast_flap_config();
2293 config.enabled = false;
2294 let mut monitor = BackboneFastFlapMonitor::new();
2295 let peer: IpAddr = "192.0.2.13".parse().unwrap();
2296 let now = Instant::now();
2297
2298 for offset in 0..10 {
2299 assert!(monitor
2300 .record_disconnect(
2301 peer,
2302 Duration::from_secs(1),
2303 &config,
2304 now + Duration::from_secs(offset),
2305 )
2306 .is_none());
2307 }
2308 assert_eq!(monitor.flap_count(peer), 0);
2309 assert!(!monitor.is_blocked(peer, &config, now + Duration::from_secs(10)));
2310 assert_eq!(monitor.blocked_ip_count(&config, now), 0);
2311 }
2312
2313 #[test]
2314 fn flap_blocks_expire_strictly_after_configured_duration() {
2315 let mut config = test_fast_flap_config();
2316 config.grace = 0;
2317 config.block_duration = Duration::from_secs(60);
2318 let mut monitor = BackboneFastFlapMonitor::new();
2319 let peer: IpAddr = "192.0.2.14".parse().unwrap();
2320 let now = Instant::now();
2321
2322 monitor
2323 .record_disconnect(peer, Duration::from_secs(1), &config, now)
2324 .unwrap();
2325 assert!(monitor.is_blocked(peer, &config, now + Duration::from_secs(60)));
2326 assert!(!monitor.is_blocked(
2327 peer,
2328 &config,
2329 now + Duration::from_secs(60) + Duration::from_nanos(1)
2330 ));
2331 assert_eq!(monitor.flap_count(peer), 0);
2332 }
2333
2334 #[test]
2335 fn fast_flap_history_is_isolated_by_ip() {
2336 let mut config = test_fast_flap_config();
2337 config.grace = 1;
2338 let mut monitor = BackboneFastFlapMonitor::new();
2339 let first: IpAddr = "192.0.2.15".parse().unwrap();
2340 let second: IpAddr = "192.0.2.16".parse().unwrap();
2341 let now = Instant::now();
2342
2343 for offset in 0..2 {
2344 monitor
2345 .record_disconnect(
2346 first,
2347 Duration::from_secs(1),
2348 &config,
2349 now + Duration::from_secs(offset),
2350 )
2351 .unwrap();
2352 }
2353 monitor
2354 .record_disconnect(second, Duration::from_secs(1), &config, now)
2355 .unwrap();
2356
2357 assert!(monitor.is_blocked(first, &config, now + Duration::from_secs(2)));
2358 assert!(!monitor.is_blocked(second, &config, now + Duration::from_secs(2)));
2359 assert_eq!(
2360 monitor.blocked_ip_count(&config, now + Duration::from_secs(2)),
2361 1
2362 );
2363 assert_eq!(
2364 monitor.blocked_ip_list(&config, now + Duration::from_secs(2)),
2365 vec![first],
2366 "tracked peers within the grace allowance must not appear as blocked"
2367 );
2368 }
2369
2370 #[test]
2371 fn blocked_ip_list_is_sorted_current_and_empty_when_disabled() {
2372 let mut config = test_fast_flap_config();
2373 config.grace = 0;
2374 config.block_duration = Duration::from_secs(60);
2375 let mut monitor = BackboneFastFlapMonitor::new();
2376 let first: IpAddr = "203.0.113.20".parse().unwrap();
2377 let second: IpAddr = "192.0.2.10".parse().unwrap();
2378 let now = Instant::now();
2379 monitor
2380 .record_disconnect(first, Duration::from_secs(1), &config, now)
2381 .unwrap();
2382 monitor
2383 .record_disconnect(second, Duration::from_secs(1), &config, now)
2384 .unwrap();
2385
2386 assert_eq!(monitor.blocked_ip_list(&config, now), vec![second, first]);
2387 assert!(monitor
2388 .blocked_ip_list(&config, now + Duration::from_secs(61))
2389 .is_empty());
2390 config.enabled = false;
2391 assert!(monitor.blocked_ip_list(&config, now).is_empty());
2392 }
2393
2394 #[test]
2395 fn peer_monitor_snapshot_clears_expired_blacklist_state() {
2396 let peer: IpAddr = "192.0.2.17".parse().unwrap();
2397 let mut monitor = BackbonePeerMonitor::new();
2398 monitor.blacklist(peer, Duration::from_secs(60), "temporary".into());
2399 let mut peers = HashMap::new();
2400 monitor.sync_into(&mut peers);
2401 let state = peers.get_mut(&peer).unwrap();
2402 state.blacklisted_until = None;
2403 state.blacklist_reason = None;
2404
2405 monitor.upsert_snapshot(&peers);
2406
2407 let entry = monitor.list("test").into_iter().next().unwrap();
2408 assert!(entry.blacklisted_remaining_secs.is_none());
2409 assert!(entry.blacklist_reason.is_none());
2410 }
2411
2412 fn recv_non_peer_event(
2413 rx: &crate::event::EventReceiver,
2414 timeout: Duration,
2415 ) -> Result<Event, mpsc::RecvTimeoutError> {
2416 let deadline = Instant::now() + timeout;
2417 loop {
2418 let remaining = deadline.saturating_duration_since(Instant::now());
2419 if remaining.is_zero() {
2420 return Err(mpsc::RecvTimeoutError::Timeout);
2421 }
2422 let event = rx.recv_timeout(remaining)?;
2423 match event {
2424 Event::BackbonePeerConnected { .. }
2425 | Event::BackbonePeerDisconnected { .. }
2426 | Event::BackbonePeerIdleTimeout { .. }
2427 | Event::BackbonePeerWriteStall { .. }
2428 | Event::BackbonePeerPenalty { .. } => continue,
2429 other => return Ok(other),
2430 }
2431 }
2432 }
2433
2434 fn make_server_config(
2435 port: u16,
2436 interface_id: u64,
2437 max_connections: Option<usize>,
2438 idle_timeout: Option<Duration>,
2439 write_stall_timeout: Option<Duration>,
2440 abuse: BackboneAbuseConfig,
2441 ) -> BackboneConfig {
2442 let mut config = BackboneConfig {
2443 name: "test-backbone".into(),
2444 listen_ip: "127.0.0.1".into(),
2445 listen_port: port,
2446 interface_id: InterfaceId(interface_id),
2447 mode: constants::MODE_FULL,
2448 gravity: 0,
2449 recursive_prs: false,
2450 announces_from_internal: true,
2451 announces_to_internal: None,
2452 max_connections,
2453 idle_timeout,
2454 write_stall_timeout,
2455 abuse,
2456 fast_flap: BackboneFastFlapConfig::default(),
2457 fast_flap_state: Arc::new(Mutex::new(BackboneFastFlapMonitor::new())),
2458 ingress_control: IngressControlConfig::enabled(),
2459 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
2460 max_connections: None,
2461 idle_timeout: None,
2462 write_stall_timeout: None,
2463 abuse: BackboneAbuseConfig::default(),
2464 })),
2465 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
2466 };
2467 let startup = BackboneServerRuntime::from_config(&config);
2468 config.runtime = Arc::new(Mutex::new(startup));
2469 config
2470 }
2471
2472 #[test]
2473 fn backbone_accept_connection() {
2474 let port = find_free_port();
2475 let (tx, rx) = crate::event::channel();
2476 let next_id = Arc::new(AtomicU64::new(8000));
2477
2478 let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
2479
2480 start(config, tx, next_id).unwrap();
2481 thread::sleep(Duration::from_millis(50));
2482
2483 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2484
2485 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2486 match event {
2487 Event::InterfaceUp(id, writer, info) => {
2488 assert_eq!(id, InterfaceId(8000));
2489 assert!(writer.is_some());
2490 assert!(info.is_some());
2491 let info = info.unwrap();
2492 assert!(info.out_capable);
2493 assert!(info.in_capable);
2494 assert_eq!(info.bitrate, Some(SERVER_BITRATE_GUESS));
2495 assert_eq!(info.mtu, SERVER_PEER_MTU);
2496 }
2497 other => panic!("expected InterfaceUp, got {:?}", other),
2498 }
2499 }
2500
2501 #[test]
2502 fn backbone_class_defaults_preserve_server_and_client_distinction() {
2503 assert_eq!(SERVER_BITRATE_GUESS, 100_000_000);
2504 assert_eq!(SERVER_PEER_MTU, 32_768);
2505 assert_eq!(CLIENT_BITRATE_GUESS, 100_000_000);
2506 assert_eq!(HW_MTU, 32_768);
2507 }
2508
2509 #[test]
2510 fn auto_mtu_thresholds_include_exact_bitrate_boundaries() {
2511 for (bitrate, mtu) in [
2512 (1_000_000_000, Some(524_288)),
2513 (750_000_000, Some(262_144)),
2514 (400_000_000, Some(131_072)),
2515 (200_000_000, Some(65_536)),
2516 (100_000_000, Some(32_768)),
2517 (10_000_000, Some(16_384)),
2518 (5_000_000, Some(8_192)),
2519 (2_000_000, Some(4_096)),
2520 (1_000_000, Some(2_048)),
2521 (62_500, Some(1_024)),
2522 (62_499, None),
2523 ] {
2524 assert_eq!(auto_mtu_for_bitrate(bitrate), mtu, "bitrate {bitrate}");
2525 }
2526 }
2527
2528 #[test]
2529 fn backbone_accepted_connection_inherits_server_mode() {
2530 let port = find_free_port();
2531 let (tx, rx) = crate::event::channel();
2532 let next_id = Arc::new(AtomicU64::new(8050));
2533
2534 let mut config =
2535 make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
2536 config.mode = constants::MODE_GATEWAY;
2537
2538 start(config, tx, next_id).unwrap();
2539 thread::sleep(Duration::from_millis(50));
2540
2541 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2542
2543 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2544 match event {
2545 Event::InterfaceUp(_, _, Some(info)) => {
2546 assert_eq!(info.mode, constants::MODE_GATEWAY);
2547 }
2548 other => panic!("expected InterfaceUp with info, got {:?}", other),
2549 }
2550 }
2551
2552 #[test]
2553 fn dataplane_ingress_thresholds_match_upstream_defaults() {
2554 assert_eq!(
2555 ingress_thresholds(1024),
2556 IngressThresholds {
2557 high: 921,
2558 immediate: 921,
2559 mid: 696,
2560 low: 102,
2561 }
2562 );
2563 assert_eq!(
2564 ingress_thresholds(8),
2565 IngressThresholds {
2566 high: 7,
2567 immediate: 128,
2568 mid: 5,
2569 low: 0,
2570 }
2571 );
2572 }
2573
2574 #[test]
2575 fn dataplane_ingress_selects_the_largest_packet_producer() {
2576 assert_eq!(
2577 busiest_ingress_key([(7, 12), (8, 41), (9, 3)].into_iter()),
2578 Some(8)
2579 );
2580 assert_eq!(busiest_ingress_key([(7, 0), (8, 0)].into_iter()), None);
2581 }
2582
2583 #[test]
2584 fn backbone_gates_the_largest_producer_before_data_queue_overflow() {
2585 let port = find_free_port();
2586 let capacities = crate::event::InboundQueueCapacities {
2587 data: 200,
2588 announce: 8,
2589 path_request: 8,
2590 ingress_limited: 8,
2591 };
2592 let (tx, rx) = crate::event::channel_with_queue_capacities(16, capacities);
2593 let observer = tx.clone();
2594 let next_id = Arc::new(AtomicU64::new(8075));
2595 let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
2596
2597 start(config, tx, next_id).unwrap();
2598 thread::sleep(Duration::from_millis(50));
2599 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
2600 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2601
2602 let payload = vec![0u8; 32];
2603 let framed = hdlc::frame(&payload);
2604 let mut burst = Vec::with_capacity(framed.len() * 180);
2605 for _ in 0..180 {
2606 burst.extend_from_slice(&framed);
2607 }
2608 client.write_all(&burst).unwrap();
2609
2610 let deadline = Instant::now() + Duration::from_secs(2);
2611 while observer.inbound_queue_snapshot().heights[0] < 180 && Instant::now() < deadline {
2612 serve_for(&rx, Duration::from_millis(5));
2613 }
2614 serve_for(&rx, Duration::from_millis(100));
2615 let gated = observer.inbound_queue_snapshot();
2616 assert!(
2617 gated.heights[0] >= 180,
2618 "DATA queue never reached the high watermark"
2619 );
2620 assert_eq!(gated.dropped[0], 0);
2621
2622 client.write_all(&hdlc::frame(&[1u8; 32])).unwrap();
2623 serve_for(&rx, Duration::from_millis(300));
2624 let after_marker = observer.inbound_queue_snapshot();
2625 assert_eq!(after_marker.heights[0], gated.heights[0]);
2626 assert_eq!(
2627 after_marker.dropped[0], gated.dropped[0],
2628 "the producer remained readable and overflowed the DATA queue"
2629 );
2630
2631 for _ in 0..gated.heights[0] {
2632 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2633 assert!(matches!(event, Event::Frame { .. }));
2634 }
2635 let released = recv_non_peer_event(&rx, Duration::from_secs(5)).unwrap();
2636 assert!(
2637 matches!(released, Event::Frame { data, .. } if data == vec![1u8; 32]),
2638 "held producer was not released after DATA pressure recovered"
2639 );
2640 }
2641
2642 #[test]
2643 fn backbone_receive_frame() {
2644 let port = find_free_port();
2645 let (tx, rx) = crate::event::channel();
2646 let next_id = Arc::new(AtomicU64::new(8100));
2647
2648 let config = make_server_config(port, 81, None, None, None, BackboneAbuseConfig::default());
2649
2650 start(config, tx, next_id).unwrap();
2651 thread::sleep(Duration::from_millis(50));
2652
2653 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2654
2655 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2657
2658 client
2661 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
2662 .unwrap();
2663 let payload: Vec<u8> = (0..32).collect();
2664 client.write_all(&hdlc::frame(&payload)).unwrap();
2665
2666 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2667 match event {
2668 Event::Frame {
2669 interface_id,
2670 data,
2671 rssi: _,
2672 snr: _,
2673 } => {
2674 assert_eq!(interface_id, InterfaceId(8100));
2675 assert_eq!(data, payload);
2676 }
2677 other => panic!("expected Frame, got {:?}", other),
2678 }
2679 }
2680
2681 #[test]
2682 fn backbone_send_to_client() {
2683 let port = find_free_port();
2684 let (tx, rx) = crate::event::channel();
2685 let next_id = Arc::new(AtomicU64::new(8200));
2686
2687 let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
2688
2689 start(config, tx, next_id).unwrap();
2690 thread::sleep(Duration::from_millis(50));
2691
2692 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2693 client
2694 .set_read_timeout(Some(Duration::from_secs(2)))
2695 .unwrap();
2696
2697 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2699 let mut writer = match event {
2700 Event::InterfaceUp(_, Some(w), _) => w,
2701 other => panic!("expected InterfaceUp with writer, got {:?}", other),
2702 };
2703
2704 let payload: Vec<u8> = (0..24).collect();
2706 writer.send_frame(&payload).unwrap();
2707
2708 let mut buf = [0u8; 256];
2710 let n = client.read(&mut buf).unwrap();
2711 let expected = hdlc::frame(&payload);
2712 assert_eq!(&buf[..n], &expected[..]);
2713 }
2714
2715 #[test]
2716 fn backbone_write_backpressure_does_not_starve_reads() {
2717 let port = find_free_port();
2718 let (tx, rx) = crate::event::channel();
2719 let next_id = Arc::new(AtomicU64::new(8250));
2720 let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
2721
2722 start(config, tx, next_id).unwrap();
2723 thread::sleep(Duration::from_millis(50));
2724
2725 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
2726 SockRef::from(&client).set_recv_buffer_size(4096).unwrap();
2727 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2728 let mut writer = match event {
2729 Event::InterfaceUp(_, Some(writer), _) => writer,
2730 other => panic!("expected InterfaceUp with writer, got {other:?}"),
2731 };
2732
2733 let outbound = vec![0x55; 60 * 1024];
2734 let deadline = Instant::now() + Duration::from_secs(3);
2735 let mut backpressured = false;
2736 while Instant::now() < deadline {
2737 match writer.send_frame(&outbound) {
2738 Ok(()) => {}
2739 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
2740 backpressured = true;
2741 break;
2742 }
2743 Err(error) => panic!("unexpected backbone write error: {error}"),
2744 }
2745 }
2746 assert!(
2747 backpressured,
2748 "failed to create outbound write backpressure"
2749 );
2750
2751 let inbound: Vec<u8> = (0..32).collect();
2752 client.write_all(&hdlc::frame(&inbound)).unwrap();
2753 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2754 match event {
2755 Event::Frame { data, .. } => assert_eq!(data, inbound),
2756 other => panic!("inbound frame was starved by outbound backpressure: {other:?}"),
2757 }
2758 }
2759
2760 #[test]
2761 fn backbone_multiple_clients() {
2762 let port = find_free_port();
2763 let (tx, rx) = crate::event::channel();
2764 let next_id = Arc::new(AtomicU64::new(8300));
2765
2766 let config = make_server_config(port, 83, None, None, None, BackboneAbuseConfig::default());
2767
2768 start(config, tx, next_id).unwrap();
2769 thread::sleep(Duration::from_millis(50));
2770
2771 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2772 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2773
2774 let mut ids = Vec::new();
2775 for _ in 0..2 {
2776 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2777 match event {
2778 Event::InterfaceUp(id, _, _) => ids.push(id),
2779 other => panic!("expected InterfaceUp, got {:?}", other),
2780 }
2781 }
2782
2783 assert_eq!(ids.len(), 2);
2784 assert_ne!(ids[0], ids[1]);
2785 }
2786
2787 #[test]
2788 fn backbone_client_disconnect() {
2789 let port = find_free_port();
2790 let (tx, rx) = crate::event::channel();
2791 let next_id = Arc::new(AtomicU64::new(8400));
2792
2793 let config = make_server_config(port, 84, None, None, None, BackboneAbuseConfig::default());
2794
2795 start(config, tx, next_id).unwrap();
2796 thread::sleep(Duration::from_millis(50));
2797
2798 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2799
2800 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2802
2803 drop(client);
2805
2806 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2808 assert!(
2809 matches!(event, Event::InterfaceDown(InterfaceId(8400))),
2810 "expected InterfaceDown(8400), got {:?}",
2811 event
2812 );
2813 }
2814
2815 #[test]
2816 fn backbone_epoll_multiplexing() {
2817 let port = find_free_port();
2818 let (tx, rx) = crate::event::channel();
2819 let next_id = Arc::new(AtomicU64::new(8500));
2820
2821 let config = make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
2822
2823 start(config, tx, next_id).unwrap();
2824 thread::sleep(Duration::from_millis(50));
2825
2826 let mut client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2827 let mut client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2828
2829 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2831 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2832
2833 let payload1: Vec<u8> = (0..24).collect();
2835 let payload2: Vec<u8> = (100..130).collect();
2836 client1.write_all(&hdlc::frame(&payload1)).unwrap();
2837 client2.write_all(&hdlc::frame(&payload2)).unwrap();
2838
2839 let mut received = Vec::new();
2841 for _ in 0..2 {
2842 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2843 match event {
2844 Event::Frame { data, .. } => received.push(data),
2845 other => panic!("expected Frame, got {:?}", other),
2846 }
2847 }
2848 assert!(received.contains(&payload1));
2849 assert!(received.contains(&payload2));
2850 }
2851
2852 #[test]
2853 fn backbone_bind_port() {
2854 let port = find_free_port();
2855 let (tx, _rx) = crate::event::channel();
2856 let next_id = Arc::new(AtomicU64::new(8600));
2857
2858 let config = make_server_config(port, 86, None, None, None, BackboneAbuseConfig::default());
2859
2860 start(config, tx, next_id).unwrap();
2862 }
2863
2864 #[test]
2865 fn backbone_hdlc_fragmented() {
2866 let port = find_free_port();
2867 let (tx, rx) = crate::event::channel();
2868 let next_id = Arc::new(AtomicU64::new(8700));
2869
2870 let config = make_server_config(port, 87, None, None, None, BackboneAbuseConfig::default());
2871
2872 start(config, tx, next_id).unwrap();
2873 thread::sleep(Duration::from_millis(50));
2874
2875 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2876 client.set_nodelay(true).unwrap();
2877
2878 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
2880
2881 let payload: Vec<u8> = (0..32).collect();
2883 let framed = hdlc::frame(&payload);
2884 let mid = framed.len() / 2;
2885
2886 client.write_all(&framed[..mid]).unwrap();
2887 thread::sleep(Duration::from_millis(50));
2888 client.write_all(&framed[mid..]).unwrap();
2889
2890 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2892 match event {
2893 Event::Frame { data, .. } => {
2894 assert_eq!(data, payload);
2895 }
2896 other => panic!("expected Frame, got {:?}", other),
2897 }
2898 }
2899
2900 fn make_client_config(port: u16, id: u64) -> BackboneClientConfig {
2905 BackboneClientConfig {
2906 name: format!("test-bb-client-{}", port),
2907 target_host: "127.0.0.1".into(),
2908 target_port: port,
2909 interface_id: InterfaceId(id),
2910 reconnect_wait: Duration::from_millis(100),
2911 max_reconnect_tries: Some(2),
2912 connect_timeout: Duration::from_secs(2),
2913 transport_identity: None,
2914 runtime: Arc::new(Mutex::new(BackboneClientRuntime {
2915 reconnect_wait: Duration::from_millis(100),
2916 max_reconnect_tries: Some(2),
2917 connect_timeout: Duration::from_secs(2),
2918 })),
2919 }
2920 }
2921
2922 #[test]
2923 fn backbone_client_connect() {
2924 let port = find_free_port();
2925 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2926 let (tx, rx) = crate::event::channel();
2927
2928 let config = make_client_config(port, 9000);
2929 let _writer = start_client(config, tx).unwrap();
2930
2931 let _server_stream = listener.accept().unwrap();
2932
2933 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
2934 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9000), _, _)));
2935 }
2936
2937 #[test]
2938 fn backbone_client_receive_frame() {
2939 let port = find_free_port();
2940 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2941 let (tx, rx) = crate::event::channel();
2942
2943 let config = make_client_config(port, 9100);
2944 let _writer = start_client(config, tx).unwrap();
2945
2946 let (mut server_stream, _) = listener.accept().unwrap();
2947
2948 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
2950
2951 server_stream
2953 .write_all(&hdlc::frame(&[0x11; rns_core::constants::HEADER_MINSIZE]))
2954 .unwrap();
2955 let payload: Vec<u8> = (0..32).collect();
2956 server_stream.write_all(&hdlc::frame(&payload)).unwrap();
2957
2958 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
2959 match event {
2960 Event::Frame {
2961 interface_id,
2962 data,
2963 rssi: _,
2964 snr: _,
2965 } => {
2966 assert_eq!(interface_id, InterfaceId(9100));
2967 assert_eq!(data, payload);
2968 }
2969 other => panic!("expected Frame, got {:?}", other),
2970 }
2971 }
2972
2973 #[test]
2974 fn backbone_client_send_frame() {
2975 let port = find_free_port();
2976 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
2977 let (tx, _rx) = crate::event::channel();
2978
2979 let config = make_client_config(port, 9200);
2980 let mut writer = start_client(config, tx).unwrap();
2981
2982 let (mut server_stream, _) = listener.accept().unwrap();
2983 server_stream
2984 .set_read_timeout(Some(Duration::from_secs(2)))
2985 .unwrap();
2986
2987 let payload: Vec<u8> = (0..24).collect();
2988 writer.send_frame(&payload).unwrap();
2989
2990 let mut buf = [0u8; 256];
2991 let n = server_stream.read(&mut buf).unwrap();
2992 let expected = hdlc::frame(&payload);
2993 assert_eq!(&buf[..n], &expected[..]);
2994 }
2995
2996 #[test]
2997 fn backbone_max_connections_rejects_excess() {
2998 let port = find_free_port();
2999 let (tx, rx) = crate::event::channel();
3000 let next_id = Arc::new(AtomicU64::new(8800));
3001
3002 let config = make_server_config(
3003 port,
3004 88,
3005 Some(2),
3006 None,
3007 None,
3008 BackboneAbuseConfig::default(),
3009 );
3010
3011 start(config, tx, next_id).unwrap();
3012 thread::sleep(Duration::from_millis(50));
3013
3014 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3016 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3017
3018 for _ in 0..2 {
3020 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3021 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
3022 }
3023
3024 let client3 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3026 client3
3027 .set_read_timeout(Some(Duration::from_millis(500)))
3028 .unwrap();
3029
3030 thread::sleep(Duration::from_millis(100));
3032
3033 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
3035 assert!(
3036 result.is_err(),
3037 "expected no InterfaceUp for rejected connection, got {:?}",
3038 result
3039 );
3040 }
3041
3042 #[test]
3043 fn backbone_max_connections_allows_after_disconnect() {
3044 let port = find_free_port();
3045 let (tx, rx) = crate::event::channel();
3046 let next_id = Arc::new(AtomicU64::new(8900));
3047
3048 let config = make_server_config(
3049 port,
3050 89,
3051 Some(1),
3052 None,
3053 None,
3054 BackboneAbuseConfig::default(),
3055 );
3056
3057 start(config, tx, next_id).unwrap();
3058 thread::sleep(Duration::from_millis(50));
3059
3060 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3062 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3063 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
3064
3065 drop(client1);
3067
3068 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3070 assert!(matches!(event, Event::InterfaceDown(_)));
3071
3072 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3074 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3075 assert!(
3076 matches!(event, Event::InterfaceUp(_, _, _)),
3077 "expected InterfaceUp after slot freed, got {:?}",
3078 event
3079 );
3080 }
3081
3082 #[test]
3083 fn backbone_client_reconnect() {
3084 let port = find_free_port();
3085 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
3086 listener.set_nonblocking(false).unwrap();
3087 let (tx, rx) = crate::event::channel();
3088
3089 let config = make_client_config(port, 9300);
3090 let _writer = start_client(config, tx).unwrap();
3091
3092 let (server_stream, _) = listener.accept().unwrap();
3094
3095 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
3097
3098 drop(server_stream);
3099
3100 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3102 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9300))));
3103
3104 let _server_stream2 = listener.accept().unwrap();
3106
3107 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3109 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9300), _, _)));
3110 }
3111
3112 #[test]
3113 fn a_reconnected_backbone_client_is_read_on_the_receiving_thread() {
3114 let port = find_free_port();
3115 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
3116 let (tx, rx) = crate::event::channel();
3117 let _writer = start_client(make_client_config(port, 9310), tx).unwrap();
3118 let (first, _) = listener.accept().unwrap();
3119 assert!(matches!(
3120 rx.recv_timeout(Duration::from_secs(1)).unwrap(),
3121 Event::InterfaceUp(InterfaceId(9310), None, _)
3122 ));
3123 drop(first);
3124 assert!(matches!(
3125 recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap(),
3126 Event::InterfaceDown(InterfaceId(9310))
3127 ));
3128 let (mut second, _) = listener.accept().unwrap();
3129 assert!(matches!(
3130 recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap(),
3131 Event::InterfaceUp(InterfaceId(9310), Some(_), _)
3132 ));
3133 let payload: Vec<u8> = (0..32).collect();
3134 second.write_all(&hdlc::frame(&payload)).unwrap();
3135 match recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap() {
3136 Event::Frame { data, .. } => assert_eq!(data, payload),
3137 other => panic!("expected Frame, got {:?}", other),
3138 }
3139 let reconnecting = || {
3141 std::fs::read_dir("/proc/self/task")
3142 .unwrap()
3143 .filter_map(|task| std::fs::read_to_string(task.ok()?.path().join("comm")).ok())
3144 .any(|name| name.trim_end() == "bb-reconn-9310")
3145 };
3146 let deadline = Instant::now() + Duration::from_secs(2);
3147 while reconnecting() && Instant::now() < deadline {
3148 thread::sleep(Duration::from_millis(10));
3149 }
3150 assert!(!reconnecting());
3151 }
3152
3153 #[test]
3154 fn backbone_client_stop_prevents_reconnect() {
3155 let port = find_free_port();
3156 let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
3157 let (tx, rx) = crate::event::channel();
3158
3159 let config = make_client_config(port, 9350);
3160 let control = ListenerControl::new();
3161 let _writer = start_client_with_ifac(config, tx, 0, None, control.clone()).unwrap();
3162
3163 let (server_stream, _) = listener.accept().unwrap();
3164 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
3166
3167 control.request_stop();
3168 drop(server_stream);
3169
3170 if let Ok(event) = recv_non_peer_event(&rx, Duration::from_millis(700)) {
3172 panic!("unexpected event after stop: {:?}", event);
3173 }
3174 }
3175
3176 #[test]
3177 fn backbone_idle_timeout_disconnects_silent_client() {
3178 let port = find_free_port();
3179 let (tx, rx) = crate::event::channel();
3180 let next_id = Arc::new(AtomicU64::new(9400));
3181
3182 let config = make_server_config(
3183 port,
3184 94,
3185 None,
3186 Some(Duration::from_millis(150)),
3187 None,
3188 BackboneAbuseConfig::default(),
3189 );
3190
3191 start(config, tx, next_id).unwrap();
3192 thread::sleep(Duration::from_millis(50));
3193
3194 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3195
3196 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3197 let client_id = match event {
3198 Event::InterfaceUp(id, _, _) => id,
3199 other => panic!("expected InterfaceUp, got {:?}", other),
3200 };
3201
3202 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3203 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3204 }
3205
3206 #[test]
3207 fn backbone_idle_timeout_ignores_client_after_data() {
3208 let port = find_free_port();
3209 let (tx, rx) = crate::event::channel();
3210 let next_id = Arc::new(AtomicU64::new(9500));
3211
3212 let config = make_server_config(
3213 port,
3214 95,
3215 None,
3216 Some(Duration::from_millis(200)),
3217 None,
3218 BackboneAbuseConfig::default(),
3219 );
3220
3221 start(config, tx, next_id).unwrap();
3222 thread::sleep(Duration::from_millis(50));
3223
3224 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3225
3226 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3227 let client_id = match event {
3228 Event::InterfaceUp(id, _, _) => id,
3229 other => panic!("expected InterfaceUp, got {:?}", other),
3230 };
3231
3232 client.write_all(&hdlc::frame(&[1u8; 24])).unwrap();
3233
3234 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3235 match event {
3236 Event::Frame {
3237 interface_id,
3238 data,
3239 rssi: _,
3240 snr: _,
3241 } => {
3242 assert_eq!(interface_id, client_id);
3243 assert_eq!(data, vec![1u8; 24]);
3244 }
3245 other => panic!("expected Frame, got {:?}", other),
3246 }
3247
3248 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
3249 assert!(
3250 result.is_err(),
3251 "expected no InterfaceDown after client sent data, got {:?}",
3252 result
3253 );
3254 }
3255
3256 #[test]
3257 fn backbone_runtime_idle_timeout_updates_live() {
3258 let port = find_free_port();
3259 let (tx, rx) = crate::event::channel();
3260 let next_id = Arc::new(AtomicU64::new(9650));
3261
3262 let config = make_server_config(port, 97, None, None, None, BackboneAbuseConfig::default());
3263 let runtime = Arc::clone(&config.runtime);
3264
3265 start(config, tx, next_id).unwrap();
3266 thread::sleep(Duration::from_millis(50));
3267
3268 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3269 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3270 let client_id = match event {
3271 Event::InterfaceUp(id, _, _) => id,
3272 other => panic!("expected InterfaceUp, got {:?}", other),
3273 };
3274
3275 {
3276 let mut runtime = runtime.lock().unwrap();
3277 runtime.idle_timeout = Some(Duration::from_millis(150));
3278 }
3279
3280 let event = recv_non_peer_event(&rx, Duration::from_secs(4)).unwrap();
3281 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3282 }
3283
3284 #[test]
3285 fn backbone_write_stall_timeout_disconnects_unwritable_client() {
3286 let port = find_free_port();
3287 let (tx, rx) = crate::event::channel();
3288 let next_id = Arc::new(AtomicU64::new(9660));
3289
3290 let config = make_server_config(
3291 port,
3292 98,
3293 None,
3294 None,
3295 Some(Duration::from_millis(50)),
3296 BackboneAbuseConfig::default(),
3297 );
3298
3299 start(config, tx, next_id).unwrap();
3300 thread::sleep(Duration::from_millis(50));
3301
3302 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3303 client
3304 .set_read_timeout(Some(Duration::from_millis(100)))
3305 .unwrap();
3306 let sock = SockRef::from(&client);
3307 sock.set_recv_buffer_size(4096).ok();
3308
3309 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3310 let (client_id, mut writer) = match event {
3311 Event::InterfaceUp(id, Some(writer), _) => (id, writer),
3312 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3313 };
3314
3315 let payload = vec![0x55; 512 * 1024];
3316 let deadline = Instant::now() + Duration::from_secs(3);
3317 let mut stalled = false;
3318 while Instant::now() < deadline {
3319 match writer.send_frame(&payload) {
3320 Ok(()) => {}
3321 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
3322 thread::sleep(Duration::from_millis(10));
3323 }
3324 Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {
3325 stalled = true;
3326 break;
3327 }
3328 Err(e) => panic!("unexpected send error: {}", e),
3329 }
3330 }
3331
3332 assert!(stalled, "expected writer to time out on persistent stall");
3333 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3334 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
3335 }
3336
3337 #[test]
3338 fn shutting_down_backbone_writer_closes_the_client_socket() {
3339 let port = find_free_port();
3340 let (tx, rx) = crate::event::channel();
3341 let next_id = Arc::new(AtomicU64::new(9670));
3342 let config = make_server_config(port, 99, None, None, None, BackboneAbuseConfig::default());
3343
3344 start(config, tx, next_id).unwrap();
3345 thread::sleep(Duration::from_millis(50));
3346
3347 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3348 client
3349 .set_read_timeout(Some(Duration::from_secs(2)))
3350 .unwrap();
3351 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3352 let mut writer = match event {
3353 Event::InterfaceUp(_, Some(writer), _) => writer,
3354 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3355 };
3356
3357 writer.shutdown();
3358
3359 let mut byte = [0u8; 1];
3360 assert_eq!(client.read(&mut byte).unwrap(), 0);
3361 }
3362
3363 fn wait_for<F>(
3365 rx: &crate::event::EventReceiver,
3366 timeout: Duration,
3367 mut pred: F,
3368 ) -> Option<Event>
3369 where
3370 F: FnMut(&Event) -> bool,
3371 {
3372 let deadline = Instant::now() + timeout;
3373 loop {
3374 let remaining = deadline.saturating_duration_since(Instant::now());
3375 if remaining.is_zero() {
3376 return None;
3377 }
3378 match rx.recv_timeout(remaining) {
3379 Ok(event) if pred(&event) => return Some(event),
3380 Ok(_) => continue,
3381 Err(_) => return None,
3382 }
3383 }
3384 }
3385
3386 #[test]
3387 fn backbone_write_stall_emits_peer_events() {
3388 let port = find_free_port();
3389 let (tx, rx) = crate::event::channel();
3390 let next_id = Arc::new(AtomicU64::new(9700));
3391
3392 let config = make_server_config(
3393 port,
3394 97,
3395 None,
3396 None,
3397 Some(Duration::from_millis(50)), BackboneAbuseConfig::default(),
3399 );
3400
3401 start(config, tx, next_id).unwrap();
3402 thread::sleep(Duration::from_millis(50));
3403
3404 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3406 client
3407 .set_read_timeout(Some(Duration::from_millis(100)))
3408 .unwrap();
3409 let sock = SockRef::from(&client);
3410 sock.set_recv_buffer_size(4096).ok();
3411
3412 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3414 let mut writer = match event {
3415 Event::InterfaceUp(_, Some(w), _) => w,
3416 other => panic!("expected InterfaceUp with writer, got {:?}", other),
3417 };
3418
3419 let payload = vec![0x55; 512 * 1024];
3421 let deadline = Instant::now() + Duration::from_secs(3);
3422 while Instant::now() < deadline {
3423 match writer.send_frame(&payload) {
3424 Ok(()) | Err(_) => {
3425 if Instant::now() + Duration::from_millis(10) > deadline {
3426 break;
3427 }
3428 thread::sleep(Duration::from_millis(10));
3429 }
3430 }
3431 }
3432
3433 let stall_event = wait_for(&rx, Duration::from_secs(3), |e| {
3435 matches!(e, Event::BackbonePeerWriteStall { .. })
3436 });
3437 assert!(
3438 stall_event.is_some(),
3439 "expected BackbonePeerWriteStall event"
3440 );
3441 }
3442
3443 #[test]
3444 fn backbone_blacklisted_peer_rejected_on_connect() {
3445 let port = find_free_port();
3446 let (tx, rx) = crate::event::channel();
3447 let next_id = Arc::new(AtomicU64::new(9800));
3448
3449 let config = make_server_config(port, 98, None, None, None, BackboneAbuseConfig::default());
3450 let peer_state = config.peer_state.clone();
3451
3452 start(config, tx.clone(), next_id).unwrap();
3453 thread::sleep(Duration::from_millis(50));
3454
3455 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3457 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3458 assert!(
3459 matches!(event, Event::InterfaceUp(_, _, _)),
3460 "first connection should succeed"
3461 );
3462 drop(client1);
3463
3464 thread::sleep(Duration::from_millis(100));
3466 while rx.try_recv().is_ok() {}
3467
3468 peer_state.lock().unwrap().blacklist(
3470 "127.0.0.1".parse().unwrap(),
3471 Duration::from_secs(60),
3472 "test blacklist".into(),
3473 );
3474
3475 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
3477 thread::sleep(Duration::from_millis(200));
3479
3480 let event = rx.try_recv();
3482 if let Ok(Event::InterfaceUp(_, _, _)) = event {
3483 panic!("blacklisted peer should not get InterfaceUp")
3484 }
3485 }
3486
3487 #[test]
3488 fn backbone_six_fast_flaps_reject_before_allocating_seventh_interface() {
3489 let port = find_free_port();
3490 let (tx, rx) = crate::event::channel();
3491 let next_id = Arc::new(AtomicU64::new(9900));
3492 let mut config =
3493 make_server_config(port, 99, None, None, None, BackboneAbuseConfig::default());
3494 config.fast_flap = test_fast_flap_config();
3495 let fast_flap_state = Arc::clone(&config.fast_flap_state);
3496 let peer_state = Arc::clone(&config.peer_state);
3497
3498 start(config, tx, Arc::clone(&next_id)).unwrap();
3499 thread::sleep(Duration::from_millis(50));
3500
3501 for flap in 0..6 {
3502 let mut client = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
3503 let up = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3504 assert!(matches!(up, Event::InterfaceUp(_, _, _)));
3505
3506 if flap == 0 {
3508 let payload = vec![0x41; 32];
3509 client.write_all(&hdlc::frame(&payload)).unwrap();
3510 let frame = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3511 assert!(matches!(frame, Event::Frame { data, .. } if data == payload));
3512 }
3513
3514 drop(client);
3515 let down = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
3516 assert!(matches!(down, Event::InterfaceDown(_)));
3517 }
3518
3519 let peer_ip: IpAddr = "127.0.0.1".parse().unwrap();
3520 assert!(
3521 lock_or_recover(&fast_flap_state, "test fast-flap state").is_blocked(
3522 peer_ip,
3523 &test_fast_flap_config(),
3524 Instant::now(),
3525 )
3526 );
3527 assert_eq!(next_id.load(Ordering::Relaxed), 9906);
3528 let entries = lock_or_recover(&peer_state, "test peer state").list("test-backbone");
3529 let entry = entries
3530 .iter()
3531 .find(|entry| entry.peer_ip == peer_ip)
3532 .expect("fast-flapping peer state");
3533 assert_eq!(entry.blacklist_reason.as_deref(), Some("fast flapping"));
3534 assert!(entry.blacklisted_remaining_secs.is_some());
3535
3536 let _rejected = TcpStream::connect(format!("127.0.0.1:{port}")).unwrap();
3537 serve_for(&rx, Duration::from_millis(200));
3538 assert_eq!(
3539 next_id.load(Ordering::Relaxed),
3540 9906,
3541 "rejection must happen before dynamic interface allocation"
3542 );
3543 while let Ok(event) = rx.try_recv() {
3544 assert!(
3545 !matches!(event, Event::InterfaceUp(_, _, _)),
3546 "blocked peer must not register a dynamic interface"
3547 );
3548 }
3549 let entries = lock_or_recover(&peer_state, "test peer state").list("test-backbone");
3550 assert_eq!(
3551 entries
3552 .iter()
3553 .find(|entry| entry.peer_ip == peer_ip)
3554 .unwrap()
3555 .reject_count,
3556 1
3557 );
3558 }
3559
3560 #[test]
3561 fn backbone_parse_config_reads_abuse_settings() {
3562 let factory = BackboneInterfaceFactory;
3563 let mut params = HashMap::new();
3564 params.insert("listen_ip".into(), "127.0.0.1".into());
3565 params.insert("listen_port".into(), "4242".into());
3566 params.insert("idle_timeout".into(), "15".into());
3567 params.insert("write_stall_timeout".into(), "45".into());
3568 params.insert("max_penalty_duration".into(), "3600".into());
3569
3570 let config = factory
3571 .parse_config("test-backbone", InterfaceId(97), ¶ms)
3572 .unwrap();
3573 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3574
3575 match mode {
3576 BackboneMode::Server(config) => {
3577 assert_eq!(config.listen_ip, "127.0.0.1");
3578 assert_eq!(config.listen_port, 4242);
3579 assert_eq!(config.idle_timeout, Some(Duration::from_secs(15)));
3580 assert_eq!(config.write_stall_timeout, Some(Duration::from_secs(45)));
3581 assert_eq!(
3582 config.abuse.max_penalty_duration,
3583 Some(Duration::from_secs(3600))
3584 );
3585 }
3586 BackboneMode::Client(_, _) => panic!("expected server config"),
3587 }
3588 }
3589
3590 #[test]
3591 fn backbone_parse_config_reads_fast_flapping_settings() {
3592 let factory = BackboneInterfaceFactory;
3593 let mut params = HashMap::new();
3594 params.insert("listen_ip".into(), "127.0.0.1".into());
3595 params.insert("listen_port".into(), "4242".into());
3596 params.insert("block_fast_flapping".into(), "no".into());
3597 params.insert("fast_flapping_threshold".into(), "2.5".into());
3598 params.insert("fast_flapping_grace".into(), "9".into());
3599 params.insert("fast_flapping_block_time".into(), "1.5".into());
3600
3601 let config = factory
3602 .parse_config("test-backbone", InterfaceId(93), ¶ms)
3603 .unwrap();
3604 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3605 let BackboneMode::Server(config) = mode else {
3606 panic!("expected server config");
3607 };
3608
3609 assert!(!config.fast_flap.enabled);
3610 assert_eq!(
3611 config.fast_flap.connection_threshold,
3612 Duration::from_secs_f64(2.5)
3613 );
3614 assert_eq!(config.fast_flap.grace, 9);
3615 assert_eq!(config.fast_flap.block_duration, Duration::from_secs(90));
3616 }
3617
3618 #[test]
3619 fn backbone_parse_config_rejects_invalid_fast_flapping_settings() {
3620 let factory = BackboneInterfaceFactory;
3621 for (key, value) in [
3622 ("block_fast_flapping", "perhaps"),
3623 ("fast_flapping_threshold", "-1"),
3624 ("fast_flapping_threshold", "NaN"),
3625 ("fast_flapping_grace", "-1"),
3626 ("fast_flapping_block_time", "infinite"),
3627 ] {
3628 let mut params = HashMap::new();
3629 params.insert("listen_port".into(), "4242".into());
3630 params.insert(key.into(), value.into());
3631
3632 let err = match factory.parse_config("test-backbone", InterfaceId(92), ¶ms) {
3633 Ok(_) => panic!("{key}={value} should be rejected"),
3634 Err(err) => err,
3635 };
3636 assert!(err.contains(key), "unexpected error: {err}");
3637 }
3638 }
3639
3640 #[test]
3641 fn parsed_backbone_listeners_share_fast_flap_history() {
3642 let factory = BackboneInterfaceFactory;
3643 let mut params = HashMap::new();
3644 params.insert("listen_port".into(), "4242".into());
3645
3646 let first = factory
3647 .parse_config("first", InterfaceId(90), ¶ms)
3648 .unwrap();
3649 let second = factory
3650 .parse_config("second", InterfaceId(91), ¶ms)
3651 .unwrap();
3652 let BackboneMode::Server(first) = *first.into_any().downcast::<BackboneMode>().unwrap()
3653 else {
3654 panic!("expected server config");
3655 };
3656 let BackboneMode::Server(second) = *second.into_any().downcast::<BackboneMode>().unwrap()
3657 else {
3658 panic!("expected server config");
3659 };
3660
3661 assert!(Arc::ptr_eq(&first.fast_flap_state, &second.fast_flap_state));
3662 }
3663
3664 #[test]
3665 fn backbone_parse_config_reads_client_priority() {
3666 let factory = BackboneInterfaceFactory;
3667 let mut params = HashMap::new();
3668 params.insert("remote".into(), "example.com".into());
3669 params.insert("target_port".into(), "4242".into());
3670 params.insert("priority".into(), "87".into());
3671
3672 let config = factory
3673 .parse_config("test-backbone-client", InterfaceId(96), ¶ms)
3674 .unwrap();
3675 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3676
3677 match mode {
3678 BackboneMode::Client(_, priority) => assert_eq!(priority, 87),
3679 BackboneMode::Server(_) => panic!("expected client config"),
3680 }
3681 }
3682
3683 #[test]
3684 fn backbone_parse_config_defaults_client_priority() {
3685 let factory = BackboneInterfaceFactory;
3686 let mut params = HashMap::new();
3687 params.insert("remote".into(), "example.com".into());
3688
3689 let config = factory
3690 .parse_config("test-backbone-client", InterfaceId(95), ¶ms)
3691 .unwrap();
3692 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
3693
3694 match mode {
3695 BackboneMode::Client(_, priority) => assert_eq!(priority, 60),
3696 BackboneMode::Server(_) => panic!("expected client config"),
3697 }
3698 }
3699
3700 #[test]
3701 fn backbone_parse_config_rejects_invalid_client_priority() {
3702 let factory = BackboneInterfaceFactory;
3703 for value in ["-1", "101", "fast"] {
3704 let mut params = HashMap::new();
3705 params.insert("remote".into(), "example.com".into());
3706 params.insert("priority".into(), value.into());
3707
3708 let err = match factory.parse_config("test-backbone-client", InterfaceId(94), ¶ms) {
3709 Ok(_) => panic!("priority {value} should be rejected"),
3710 Err(err) => err,
3711 };
3712 assert!(
3713 err.contains("priority"),
3714 "unexpected error for {value}: {err}"
3715 );
3716 }
3717 }
3718
3719 #[test]
3720 fn positive_duration_param_rejects_values_that_are_not_durations() {
3721 for raw in ["inf", "-inf", "NaN", "1e300", "-1", "0"] {
3722 let params = HashMap::from([("idle_timeout".to_string(), raw.to_string())]);
3723 assert_eq!(
3724 parse_positive_duration_secs(¶ms, "idle_timeout"),
3725 None,
3726 "value {raw} should be ignored"
3727 );
3728 }
3729 let params = HashMap::from([("idle_timeout".to_string(), "2.5".to_string())]);
3730 assert_eq!(
3731 parse_positive_duration_secs(¶ms, "idle_timeout"),
3732 Some(Duration::from_millis(2500))
3733 );
3734 }
3735}