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