1use std::collections::HashMap;
13use std::hash::{BuildHasher, Hasher};
14use std::io::{self, Read, Write};
15use std::net::{IpAddr, Shutdown, TcpListener, TcpStream, ToSocketAddrs};
16use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
17use std::sync::{Arc, Mutex};
18use std::thread;
19use std::time::{Duration, Instant};
20
21use polling::{Event as PollEvent, Events, Poller};
22use socket2::{SockRef, TcpKeepalive};
23
24use rns_core::constants;
25use rns_core::transport::types::{IngressControlConfig, InterfaceId, InterfaceInfo};
26
27use crate::event::{Event, EventSender};
28use crate::hdlc;
29use crate::interface::{
30 lock_or_recover, InterfaceConfigData, InterfaceFactory, StartContext, StartResult, Writer,
31};
32use crate::BackbonePeerStateEntry;
33
34#[allow(dead_code)]
36const HW_MTU: usize = 1_048_576;
37
38#[derive(Debug, Clone)]
40pub struct BackboneConfig {
41 pub name: String,
42 pub listen_ip: String,
43 pub listen_port: u16,
44 pub interface_id: InterfaceId,
45 pub mode: u8,
46 pub recursive_prs: bool,
47 pub announces_from_internal: bool,
48 pub max_connections: Option<usize>,
49 pub idle_timeout: Option<Duration>,
50 pub write_stall_timeout: Option<Duration>,
51 pub abuse: BackboneAbuseConfig,
52 pub ingress_control: IngressControlConfig,
53 pub runtime: Arc<Mutex<BackboneServerRuntime>>,
54 pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
55}
56
57#[derive(Debug, Clone, Default)]
59pub struct BackboneAbuseConfig {
60 pub max_penalty_duration: Option<Duration>,
61}
62
63#[derive(Debug, Clone)]
65pub struct BackboneServerRuntime {
66 pub max_connections: Option<usize>,
67 pub idle_timeout: Option<Duration>,
68 pub write_stall_timeout: Option<Duration>,
69 pub abuse: BackboneAbuseConfig,
70}
71
72impl BackboneServerRuntime {
73 pub fn from_config(config: &BackboneConfig) -> Self {
74 Self {
75 max_connections: config.max_connections,
76 idle_timeout: config.idle_timeout,
77 write_stall_timeout: config.write_stall_timeout,
78 abuse: config.abuse.clone(),
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct BackboneRuntimeConfigHandle {
85 pub interface_name: String,
86 pub runtime: Arc<Mutex<BackboneServerRuntime>>,
87 pub startup: BackboneServerRuntime,
88}
89
90#[derive(Debug, Clone)]
91pub struct BackbonePeerStateHandle {
92 pub interface_id: InterfaceId,
93 pub interface_name: String,
94 pub peer_state: Arc<Mutex<BackbonePeerMonitor>>,
95}
96
97impl Default for BackboneConfig {
98 fn default() -> Self {
99 let mut config = BackboneConfig {
100 name: String::new(),
101 listen_ip: "0.0.0.0".into(),
102 listen_port: 0,
103 interface_id: InterfaceId(0),
104 mode: constants::MODE_FULL,
105 recursive_prs: false,
106 announces_from_internal: true,
107 max_connections: None,
108 idle_timeout: None,
109 write_stall_timeout: None,
110 abuse: BackboneAbuseConfig::default(),
111 ingress_control: IngressControlConfig::enabled(),
112 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
113 max_connections: None,
114 idle_timeout: None,
115 write_stall_timeout: None,
116 abuse: BackboneAbuseConfig::default(),
117 })),
118 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
119 };
120 let startup = BackboneServerRuntime::from_config(&config);
121 config.runtime = Arc::new(Mutex::new(startup));
122 config
123 }
124}
125
126const MAX_PENDING_BYTES: usize = 512 * 1024;
129
130struct BackboneWriter {
132 stream: TcpStream,
133 runtime: Arc<Mutex<BackboneServerRuntime>>,
134 interface_name: String,
135 interface_id: InterfaceId,
136 event_tx: EventSender,
137 pending: Vec<u8>,
138 stall_started: Option<Instant>,
139 disconnect_notified: bool,
140 write_stall_flag: Arc<AtomicBool>,
141}
142
143impl Writer for BackboneWriter {
144 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
145 let write_stall_timeout =
146 lock_or_recover(&self.runtime, "backbone runtime").write_stall_timeout;
147 if !self.pending.is_empty() {
148 self.flush_pending(write_stall_timeout)?;
149 if !self.pending.is_empty() {
150 return Err(io::Error::new(
151 io::ErrorKind::WouldBlock,
152 "backbone writer still stalled",
153 ));
154 }
155 }
156
157 let frame = hdlc::frame(data);
158 self.write_buffer(&frame, write_stall_timeout)
159 }
160}
161
162impl BackboneWriter {
163 fn write_buffer(
164 &mut self,
165 data: &[u8],
166 write_stall_timeout: Option<Duration>,
167 ) -> io::Result<()> {
168 let mut written = 0usize;
169 while written < data.len() {
170 match self.stream.write(&data[written..]) {
171 Ok(0) => {
172 return Err(io::Error::new(
173 io::ErrorKind::WriteZero,
174 "backbone writer wrote zero bytes",
175 ))
176 }
177 Ok(n) => {
178 written += n;
179 self.stall_started = None;
180 }
181 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
182 let now = Instant::now();
183 let started = self.stall_started.get_or_insert(now);
184 if let Some(timeout) = write_stall_timeout {
185 if now.duration_since(*started) >= timeout {
186 return Err(self.disconnect_for_write_stall(timeout));
187 }
188 }
189 if self.pending.len() + data[written..].len() > MAX_PENDING_BYTES {
190 return Err(self.disconnect_for_write_stall(
191 write_stall_timeout.unwrap_or(Duration::from_secs(30)),
192 ));
193 }
194 self.pending.extend_from_slice(&data[written..]);
195 return Err(io::Error::new(
196 io::ErrorKind::WouldBlock,
197 "backbone writer would block",
198 ));
199 }
200 Err(e) => return Err(e),
201 }
202 }
203 Ok(())
204 }
205
206 fn flush_pending(&mut self, write_stall_timeout: Option<Duration>) -> io::Result<()> {
207 if self.pending.is_empty() {
208 return Ok(());
209 }
210
211 let pending = std::mem::take(&mut self.pending);
212 match self.write_buffer(&pending, write_stall_timeout) {
213 Ok(()) => Ok(()),
214 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(()),
215 Err(e) => Err(e),
216 }
217 }
218
219 fn disconnect_for_write_stall(&mut self, timeout: Duration) -> io::Error {
220 if !self.disconnect_notified {
221 log::warn!(
222 "[{}] backbone client {} disconnected due to write stall timeout ({:?})",
223 self.interface_name,
224 self.interface_id.0,
225 timeout
226 );
227 self.write_stall_flag.store(true, Ordering::Relaxed);
228 let _ = self.stream.shutdown(Shutdown::Both);
229 let _ = self.event_tx.send(Event::InterfaceDown(self.interface_id));
230 self.disconnect_notified = true;
231 }
232 io::Error::new(
233 io::ErrorKind::TimedOut,
234 format!("backbone writer stalled for {:?}", timeout),
235 )
236 }
237}
238
239pub fn start(config: BackboneConfig, tx: EventSender, next_id: Arc<AtomicU64>) -> io::Result<()> {
241 let addr = format!("{}:{}", config.listen_ip, config.listen_port);
242 let listener = TcpListener::bind(&addr)?;
243 listener.set_nonblocking(true)?;
244
245 log::info!(
246 "[{}] backbone server listening on {}",
247 config.name,
248 listener
249 .local_addr()
250 .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], config.listen_port)))
251 );
252
253 let name = config.name.clone();
254 let server_interface_id = config.interface_id;
255 let runtime = Arc::clone(&config.runtime);
256 let peer_state = Arc::clone(&config.peer_state);
257 let ingress_control = config.ingress_control;
258 let accepted_peer_mode = config.mode;
259 let accepted_peer_recursive_prs = config.recursive_prs;
260 let accepted_peer_announces_from_internal = config.announces_from_internal;
261 thread::Builder::new()
262 .name(format!("backbone-poll-{}", config.interface_id.0))
263 .spawn(move || {
264 if let Err(e) = poll_loop(
265 listener,
266 name,
267 server_interface_id,
268 tx,
269 next_id,
270 runtime,
271 peer_state,
272 ingress_control,
273 accepted_peer_mode,
274 accepted_peer_recursive_prs,
275 accepted_peer_announces_from_internal,
276 ) {
277 log::error!("backbone poll loop error: {}", e);
278 }
279 })?;
280
281 Ok(())
282}
283
284struct ClientState {
286 id: InterfaceId,
287 peer_ip: IpAddr,
288 peer_port: u16,
289 stream: TcpStream,
290 decoder: hdlc::Decoder,
291 connected_at: Instant,
292 has_received_data: bool,
293 write_stall_flag: Arc<AtomicBool>,
294}
295
296#[derive(Debug, Clone)]
297struct PeerBehaviorState {
298 blacklisted_until: Option<Instant>,
299 blacklist_reason: Option<String>,
300 reject_count: u64,
301 connected_count: usize,
302}
303
304impl PeerBehaviorState {
305 fn new() -> Self {
306 Self {
307 blacklisted_until: None,
308 blacklist_reason: None,
309 reject_count: 0,
310 connected_count: 0,
311 }
312 }
313}
314
315#[derive(Debug, Clone, Default)]
316pub struct BackbonePeerMonitor {
317 peers: HashMap<IpAddr, PeerBehaviorState>,
318}
319
320impl BackbonePeerMonitor {
321 pub fn new() -> Self {
322 Self {
323 peers: HashMap::new(),
324 }
325 }
326
327 fn upsert_snapshot(&mut self, peers: &HashMap<IpAddr, PeerBehaviorState>) {
328 let mut merged = self.peers.clone();
329
330 for (peer_ip, state) in peers {
331 let entry = merged
332 .entry(*peer_ip)
333 .or_insert_with(PeerBehaviorState::new);
334 entry.connected_count = state.connected_count;
335 entry.reject_count = state.reject_count;
336 if state.blacklisted_until.is_some() {
337 entry.blacklisted_until = state.blacklisted_until;
338 entry.blacklist_reason = state.blacklist_reason.clone();
339 }
340 }
341
342 merged.retain(|peer_ip, state| {
343 peers.contains_key(peer_ip)
344 || state.blacklisted_until.is_some()
345 || state.reject_count > 0
346 });
347 self.peers = merged;
348 }
349
350 fn sync_into(&self, peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
351 for (peer_ip, state) in &self.peers {
352 let entry = peers.entry(*peer_ip).or_insert_with(PeerBehaviorState::new);
353 entry.blacklisted_until = state.blacklisted_until;
354 entry.blacklist_reason = state.blacklist_reason.clone();
355 entry.reject_count = state.reject_count;
356 }
357
358 peers.retain(|peer_ip, state| {
359 if state.connected_count > 0 {
360 return true;
361 }
362 self.peers.contains_key(peer_ip)
363 });
364 }
365
366 pub fn list(&self, interface_name: &str) -> Vec<BackbonePeerStateEntry> {
367 let now = Instant::now();
368 let mut entries: Vec<BackbonePeerStateEntry> = self
369 .peers
370 .iter()
371 .map(|(peer_ip, state)| BackbonePeerStateEntry {
372 interface_name: interface_name.to_string(),
373 peer_ip: *peer_ip,
374 connected_count: state.connected_count,
375 blacklisted_remaining_secs: state
376 .blacklisted_until
377 .and_then(|until| (until > now).then(|| (until - now).as_secs_f64())),
378 blacklist_reason: state.blacklist_reason.clone(),
379 reject_count: state.reject_count,
380 })
381 .collect();
382 entries.sort_by(|a, b| a.peer_ip.cmp(&b.peer_ip));
383 entries
384 }
385
386 pub fn clear(&mut self, peer_ip: IpAddr) -> bool {
387 self.peers.remove(&peer_ip).is_some()
388 }
389
390 pub fn blacklist(&mut self, peer_ip: IpAddr, duration: Duration, reason: String) -> bool {
391 let state = self
392 .peers
393 .entry(peer_ip)
394 .or_insert_with(PeerBehaviorState::new);
395 state.blacklisted_until = Some(Instant::now() + duration);
396 state.blacklist_reason = Some(reason);
397 true
398 }
399
400 #[cfg(test)]
401 pub fn seed_entry(&mut self, entry: BackbonePeerStateEntry) {
402 let mut state = PeerBehaviorState::new();
403 state.connected_count = entry.connected_count;
404 state.reject_count = entry.reject_count;
405 state.blacklist_reason = entry.blacklist_reason;
406 if let Some(remaining) = entry.blacklisted_remaining_secs {
407 state.blacklisted_until = Some(Instant::now() + Duration::from_secs_f64(remaining));
408 }
409 self.peers.insert(entry.peer_ip, state);
410 }
411}
412
413#[derive(Clone, Copy)]
414enum DisconnectReason {
415 RemoteClosed,
416 IdleTimeout,
417 WriteStall,
418}
419
420fn poll_loop(
422 listener: TcpListener,
423 name: String,
424 server_interface_id: InterfaceId,
425 tx: EventSender,
426 next_id: Arc<AtomicU64>,
427 runtime: Arc<Mutex<BackboneServerRuntime>>,
428 peer_state: Arc<Mutex<BackbonePeerMonitor>>,
429 ingress_control: IngressControlConfig,
430 accepted_peer_mode: u8,
431 accepted_peer_recursive_prs: bool,
432 accepted_peer_announces_from_internal: bool,
433) -> io::Result<()> {
434 let poller = Poller::new()?;
435
436 const LISTENER_KEY: usize = 0;
437
438 unsafe { poller.add(&listener, PollEvent::readable(LISTENER_KEY))? };
440
441 let mut clients: HashMap<usize, ClientState> = HashMap::new();
442 let mut peers: HashMap<IpAddr, PeerBehaviorState> = HashMap::new();
443 let mut events = Events::new();
444 let mut next_key: usize = 1;
445
446 loop {
447 let runtime_snapshot = runtime.lock().unwrap().clone();
448 let max_connections = runtime_snapshot.max_connections;
449 let idle_timeout = runtime_snapshot.idle_timeout;
450 cleanup_peer_state(&mut peers);
451 {
452 let mut monitor = peer_state.lock().unwrap();
453 monitor.sync_into(&mut peers);
454 monitor.upsert_snapshot(&peers);
455 }
456
457 events.clear();
458 poller.wait(&mut events, Some(Duration::from_secs(1)))?;
459
460 for ev in events.iter() {
461 if ev.key == LISTENER_KEY {
462 loop {
464 match listener.accept() {
465 Ok((stream, peer_addr)) => {
466 let peer_ip = peer_addr.ip();
467 let peer_port = peer_addr.port();
468
469 if is_ip_blacklisted(&mut peers, peer_ip) {
470 if let Some(state) = peers.get_mut(&peer_ip) {
471 state.reject_count = state.reject_count.saturating_add(1);
472 }
473 peer_state.lock().unwrap().upsert_snapshot(&peers);
474 log::debug!("[{}] rejecting blacklisted peer {}", name, peer_addr);
475 drop(stream);
476 continue;
477 }
478
479 if let Some(max) = max_connections {
480 if clients.len() >= max {
481 log::warn!(
482 "[{}] max connections ({}) reached, rejecting {}",
483 name,
484 max,
485 peer_addr
486 );
487 drop(stream);
488 continue;
489 }
490 }
491
492 stream.set_nonblocking(true).ok();
493 stream.set_nodelay(true).ok();
494 set_tcp_keepalive(&stream).ok();
495
496 #[cfg(target_os = "macos")]
498 {
499 let sock = SockRef::from(&stream);
500 sock.set_nosigpipe(true).ok();
501 }
502
503 let key = next_key;
504 next_key += 1;
505 let client_id = InterfaceId(next_id.fetch_add(1, Ordering::Relaxed));
506
507 log::info!(
508 "[{}] backbone client connected: {} → id {}",
509 name,
510 peer_addr,
511 client_id.0
512 );
513
514 if let Err(e) = unsafe { poller.add(&stream, PollEvent::readable(key)) }
517 {
518 log::warn!("[{}] failed to add client to poller: {}", name, e);
519 continue; }
521
522 let writer_stream = match stream.try_clone() {
524 Ok(s) => s,
525 Err(e) => {
526 log::warn!("[{}] failed to clone client stream: {}", name, e);
527 let _ = poller.delete(&stream);
528 continue; }
530 };
531 let write_stall_flag = Arc::new(AtomicBool::new(false));
532 let writer: Box<dyn Writer> = Box::new(BackboneWriter {
533 stream: writer_stream,
534 runtime: Arc::clone(&runtime),
535 interface_name: name.clone(),
536 interface_id: client_id,
537 event_tx: tx.clone(),
538 pending: Vec::new(),
539 stall_started: None,
540 disconnect_notified: false,
541 write_stall_flag: Arc::clone(&write_stall_flag),
542 });
543
544 clients.insert(
545 key,
546 ClientState {
547 id: client_id,
548 peer_ip,
549 peer_port,
550 stream,
551 decoder: hdlc::Decoder::new(),
552 connected_at: Instant::now(),
553 has_received_data: false,
554 write_stall_flag,
555 },
556 );
557 peers
558 .entry(peer_ip)
559 .or_insert_with(PeerBehaviorState::new)
560 .connected_count += 1;
561 peer_state.lock().unwrap().upsert_snapshot(&peers);
562 let _ = tx.send(Event::BackbonePeerConnected {
563 server_interface_id,
564 peer_interface_id: client_id,
565 peer_ip,
566 peer_port,
567 });
568
569 let info = InterfaceInfo {
570 id: client_id,
571 name: format!("BackboneInterface/{}", client_id.0),
572 mode: accepted_peer_mode,
573 recursive_prs: accepted_peer_recursive_prs,
574 announces_from_internal: accepted_peer_announces_from_internal,
575 out_capable: true,
576 in_capable: true,
577 bitrate: Some(1_000_000_000), airtime_profile: None,
579 announce_rate_target: None,
580 announce_rate_grace: 0,
581 announce_rate_penalty: 0.0,
582 announce_cap: constants::ANNOUNCE_CAP,
583 is_local_client: false,
584 wants_tunnel: false,
585 tunnel_id: None,
586 mtu: 65535,
587 ia_freq: 0.0,
588 ip_freq: 0.0,
589 op_freq: 0.0,
590 op_samples: 0,
591 started: 0.0,
592 ingress_control,
593 };
594
595 if tx
596 .send(Event::InterfaceUp(client_id, Some(writer), Some(info)))
597 .is_err()
598 {
599 cleanup(&poller, &clients, &listener);
601 return Ok(());
602 }
603 }
604 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
605 Err(e) => {
606 log::warn!("[{}] accept error: {}", name, e);
607 break;
608 }
609 }
610 }
611 poller.modify(&listener, PollEvent::readable(LISTENER_KEY))?;
613 } else if clients.contains_key(&ev.key) {
614 let key = ev.key;
615 let mut should_remove = false;
616 let mut client_id = InterfaceId(0);
617
618 let mut buf = [0u8; 4096];
619 let read_result = {
620 let client = clients.get_mut(&key).unwrap();
621 client.stream.read(&mut buf)
622 };
623
624 match read_result {
625 Ok(0) | Err(_) => {
626 if let Some(c) = clients.get(&key) {
627 client_id = c.id;
628 }
629 should_remove = true;
630 }
631 Ok(n) => {
632 let client = clients.get_mut(&key).unwrap();
633 client_id = client.id;
634 client.has_received_data = true;
635 for frame in client.decoder.feed(&buf[..n]) {
636 if tx
637 .send(Event::Frame {
638 interface_id: client_id,
639 data: frame,
640 rssi: None,
641 snr: None,
642 })
643 .is_err()
644 {
645 cleanup(&poller, &clients, &listener);
646 return Ok(());
647 }
648 }
649 }
650 }
651
652 if should_remove {
653 let reason = if clients
654 .get(&key)
655 .is_some_and(|c| c.write_stall_flag.load(Ordering::Relaxed))
656 {
657 DisconnectReason::WriteStall
658 } else {
659 DisconnectReason::RemoteClosed
660 };
661 disconnect_client(
662 &poller,
663 &mut clients,
664 &mut peers,
665 &name,
666 server_interface_id,
667 &tx,
668 &peer_state,
669 key,
670 client_id,
671 reason,
672 );
673 } else if let Some(client) = clients.get(&key) {
674 poller.modify(&client.stream, PollEvent::readable(key))?;
676 }
677 }
678 }
679
680 if let Some(timeout) = idle_timeout {
681 let now = Instant::now();
682 let timed_out: Vec<(usize, InterfaceId)> = clients
683 .iter()
684 .filter_map(|(&key, client)| {
685 if client.has_received_data || now.duration_since(client.connected_at) < timeout
686 {
687 None
688 } else {
689 Some((key, client.id))
690 }
691 })
692 .collect();
693
694 for (key, client_id) in timed_out {
695 disconnect_client(
696 &poller,
697 &mut clients,
698 &mut peers,
699 &name,
700 server_interface_id,
701 &tx,
702 &peer_state,
703 key,
704 client_id,
705 DisconnectReason::IdleTimeout,
706 );
707 }
708 }
709 }
710}
711
712fn cleanup_peer_state(peers: &mut HashMap<IpAddr, PeerBehaviorState>) {
713 let now = Instant::now();
714 peers.retain(|_, state| {
715 if matches!(state.blacklisted_until, Some(until) if now >= until) {
716 state.blacklisted_until = None;
717 state.blacklist_reason = None;
718 }
719 state.blacklisted_until.is_some() || state.connected_count > 0 || state.reject_count > 0
720 });
721}
722
723fn is_ip_blacklisted(peers: &mut HashMap<IpAddr, PeerBehaviorState>, peer_ip: IpAddr) -> bool {
724 let now = Instant::now();
725 if let Some(state) = peers.get_mut(&peer_ip) {
726 if let Some(until) = state.blacklisted_until {
727 if now < until {
728 return true;
729 }
730 state.blacklisted_until = None;
731 }
732 }
733 false
734}
735
736fn disconnect_client(
737 poller: &Poller,
738 clients: &mut HashMap<usize, ClientState>,
739 peers: &mut HashMap<IpAddr, PeerBehaviorState>,
740 name: &str,
741 server_interface_id: InterfaceId,
742 tx: &EventSender,
743 peer_state: &Arc<Mutex<BackbonePeerMonitor>>,
744 key: usize,
745 client_id: InterfaceId,
746 reason: DisconnectReason,
747) {
748 let Some(client) = clients.remove(&key) else {
749 return;
750 };
751
752 match reason {
753 DisconnectReason::RemoteClosed => {
754 log::info!("[{}] backbone client {} disconnected", name, client_id.0);
755 }
756 DisconnectReason::IdleTimeout => {
757 log::info!(
758 "[{}] backbone client {} disconnected due to idle timeout",
759 name,
760 client_id.0
761 );
762 }
763 DisconnectReason::WriteStall => {
764 }
766 }
767
768 let _ = poller.delete(&client.stream);
769 let connected_for = client.connected_at.elapsed();
771 let _ = tx.send(Event::BackbonePeerDisconnected {
772 server_interface_id,
773 peer_interface_id: client.id,
774 peer_ip: client.peer_ip,
775 peer_port: client.peer_port,
776 connected_for,
777 had_received_data: client.has_received_data,
778 });
779 match reason {
780 DisconnectReason::IdleTimeout => {
781 let _ = tx.send(Event::BackbonePeerIdleTimeout {
782 server_interface_id,
783 peer_interface_id: client.id,
784 peer_ip: client.peer_ip,
785 peer_port: client.peer_port,
786 connected_for,
787 });
788 }
789 DisconnectReason::WriteStall => {
790 let _ = tx.send(Event::BackbonePeerWriteStall {
791 server_interface_id,
792 peer_interface_id: client.id,
793 peer_ip: client.peer_ip,
794 peer_port: client.peer_port,
795 connected_for,
796 });
797 }
798 DisconnectReason::RemoteClosed => {}
799 }
800
801 if let Some(state) = peers.get_mut(&client.peer_ip) {
802 state.connected_count = state.connected_count.saturating_sub(1);
803 }
804 peer_state.lock().unwrap().upsert_snapshot(peers);
805 if !matches!(reason, DisconnectReason::WriteStall) {
807 let _ = tx.send(Event::InterfaceDown(client_id));
808 }
809}
810
811fn set_tcp_keepalive(stream: &TcpStream) -> io::Result<()> {
812 let sock = SockRef::from(stream);
813 let mut keepalive = TcpKeepalive::new()
814 .with_time(Duration::from_secs(5))
815 .with_interval(Duration::from_secs(2));
816 #[cfg(any(target_os = "linux", target_os = "macos"))]
817 {
818 keepalive = keepalive.with_retries(12);
819 }
820 sock.set_tcp_keepalive(&keepalive)
821}
822
823fn cleanup(poller: &Poller, clients: &HashMap<usize, ClientState>, listener: &TcpListener) {
824 for (_, client) in clients {
825 let _ = poller.delete(&client.stream);
826 }
827 let _ = poller.delete(listener);
828}
829
830#[derive(Debug, Clone)]
836pub struct BackboneClientConfig {
837 pub name: String,
838 pub target_host: String,
839 pub target_port: u16,
840 pub interface_id: InterfaceId,
841 pub reconnect_wait: Duration,
842 pub max_reconnect_tries: Option<u32>,
843 pub connect_timeout: Duration,
844 pub transport_identity: Option<String>,
845 pub runtime: Arc<Mutex<BackboneClientRuntime>>,
846}
847
848#[derive(Debug, Clone)]
849pub struct BackboneClientRuntime {
850 pub reconnect_wait: Duration,
851 pub max_reconnect_tries: Option<u32>,
852 pub connect_timeout: Duration,
853}
854
855impl BackboneClientRuntime {
856 pub fn from_config(config: &BackboneClientConfig) -> Self {
857 Self {
858 reconnect_wait: config.reconnect_wait,
859 max_reconnect_tries: config.max_reconnect_tries,
860 connect_timeout: config.connect_timeout,
861 }
862 }
863}
864
865#[derive(Debug, Clone)]
866pub struct BackboneClientRuntimeConfigHandle {
867 pub interface_name: String,
868 pub runtime: Arc<Mutex<BackboneClientRuntime>>,
869 pub startup: BackboneClientRuntime,
870}
871
872impl Default for BackboneClientConfig {
873 fn default() -> Self {
874 let mut config = BackboneClientConfig {
875 name: String::new(),
876 target_host: "127.0.0.1".into(),
877 target_port: 4242,
878 interface_id: InterfaceId(0),
879 reconnect_wait: Duration::from_secs(5),
880 max_reconnect_tries: None,
881 connect_timeout: Duration::from_secs(5),
882 transport_identity: None,
883 runtime: Arc::new(Mutex::new(BackboneClientRuntime {
884 reconnect_wait: Duration::from_secs(5),
885 max_reconnect_tries: None,
886 connect_timeout: Duration::from_secs(5),
887 })),
888 };
889 let startup = BackboneClientRuntime::from_config(&config);
890 config.runtime = Arc::new(Mutex::new(startup));
891 config
892 }
893}
894
895struct BackboneClientWriter {
897 stream: TcpStream,
898}
899
900impl Writer for BackboneClientWriter {
901 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
902 self.stream.write_all(&hdlc::frame(data))
903 }
904}
905
906fn try_connect_client(config: &BackboneClientConfig) -> io::Result<TcpStream> {
908 let runtime = config.runtime.lock().unwrap().clone();
909 let addr_str = format!("{}:{}", config.target_host, config.target_port);
910 let addr = addr_str
911 .to_socket_addrs()?
912 .next()
913 .ok_or_else(|| io::Error::new(io::ErrorKind::AddrNotAvailable, "no addresses resolved"))?;
914
915 let stream = TcpStream::connect_timeout(&addr, runtime.connect_timeout)?;
916 stream.set_nodelay(true)?;
917 set_tcp_keepalive(&stream).ok();
918
919 #[cfg(target_os = "macos")]
921 {
922 let sock = SockRef::from(&stream);
923 sock.set_nosigpipe(true).ok();
924 }
925
926 Ok(stream)
927}
928
929pub fn start_client(config: BackboneClientConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
931 let stream = try_connect_client(&config)?;
932 let reader_stream = stream.try_clone()?;
933 let writer_stream = stream.try_clone()?;
934
935 let id = config.interface_id;
936 log::info!(
937 "[{}] backbone client connected to {}:{}",
938 config.name,
939 config.target_host,
940 config.target_port
941 );
942
943 let _ = tx.send(Event::InterfaceUp(id, None, None));
945
946 thread::Builder::new()
947 .name(format!("backbone-client-{}", id.0))
948 .spawn(move || {
949 client_reader_loop(reader_stream, config, tx);
950 })?;
951
952 Ok(Box::new(BackboneClientWriter {
953 stream: writer_stream,
954 }))
955}
956
957fn client_reader_loop(mut stream: TcpStream, config: BackboneClientConfig, tx: EventSender) {
960 let id = config.interface_id;
961 let mut decoder = hdlc::Decoder::new();
962 let mut buf = [0u8; 4096];
963
964 loop {
965 match stream.read(&mut buf) {
966 Ok(0) => {
967 log::warn!("[{}] connection closed", config.name);
968 let _ = tx.send(Event::InterfaceDown(id));
969 match client_reconnect(&config, &tx) {
970 Some(new_stream) => {
971 stream = new_stream;
972 decoder = hdlc::Decoder::new();
973 continue;
974 }
975 None => {
976 log::error!("[{}] reconnection failed, giving up", config.name);
977 return;
978 }
979 }
980 }
981 Ok(n) => {
982 for frame in decoder.feed(&buf[..n]) {
983 if tx
984 .send(Event::Frame {
985 interface_id: id,
986 data: frame,
987 rssi: None,
988 snr: None,
989 })
990 .is_err()
991 {
992 return;
993 }
994 }
995 }
996 Err(e) => {
997 log::warn!("[{}] read error: {}", config.name, e);
998 let _ = tx.send(Event::InterfaceDown(id));
999 match client_reconnect(&config, &tx) {
1000 Some(new_stream) => {
1001 stream = new_stream;
1002 decoder = hdlc::Decoder::new();
1003 continue;
1004 }
1005 None => {
1006 log::error!("[{}] reconnection failed, giving up", config.name);
1007 return;
1008 }
1009 }
1010 }
1011 }
1012 }
1013}
1014
1015const MAX_BACKOFF_SHIFT: u32 = 6;
1018
1019fn client_reconnect(config: &BackboneClientConfig, tx: &EventSender) -> Option<TcpStream> {
1023 let mut attempts = 0u32;
1024 loop {
1025 let runtime = config.runtime.lock().unwrap().clone();
1026
1027 let shift = attempts.min(MAX_BACKOFF_SHIFT);
1028 let backoff = runtime.reconnect_wait * 2u32.pow(shift);
1029 let jitter_range = backoff / 4;
1031 let jitter = if jitter_range.as_nanos() > 0 {
1032 let offset = Duration::from_nanos(
1033 (std::hash::RandomState::new().build_hasher().finish()
1034 % jitter_range.as_nanos() as u64)
1035 * 2,
1036 );
1037 if offset > jitter_range {
1038 backoff + (offset - jitter_range)
1039 } else {
1040 backoff - (jitter_range - offset)
1041 }
1042 } else {
1043 backoff
1044 };
1045 thread::sleep(jitter);
1046
1047 attempts += 1;
1048
1049 if let Some(max) = runtime.max_reconnect_tries {
1050 if attempts > max {
1051 let _ = tx.send(Event::InterfaceDown(config.interface_id));
1052 return None;
1053 }
1054 }
1055
1056 log::info!(
1057 "[{}] reconnect attempt {} (backoff {:.1}s) ...",
1058 config.name,
1059 attempts,
1060 jitter.as_secs_f64(),
1061 );
1062
1063 match try_connect_client(config) {
1064 Ok(new_stream) => {
1065 let writer_stream = match new_stream.try_clone() {
1066 Ok(s) => s,
1067 Err(e) => {
1068 log::warn!("[{}] failed to clone stream: {}", config.name, e);
1069 continue;
1070 }
1071 };
1072 log::info!(
1073 "[{}] reconnected after {} attempt(s)",
1074 config.name,
1075 attempts
1076 );
1077 let new_writer: Box<dyn Writer> = Box::new(BackboneClientWriter {
1078 stream: writer_stream,
1079 });
1080 let _ = tx.send(Event::InterfaceUp(
1081 config.interface_id,
1082 Some(new_writer),
1083 None,
1084 ));
1085 return Some(new_stream);
1086 }
1087 Err(e) => {
1088 log::warn!("[{}] reconnect failed: {}", config.name, e);
1089 }
1090 }
1091 }
1092}
1093
1094#[derive(Clone)]
1101pub(crate) enum BackboneMode {
1102 Server(BackboneConfig),
1103 Client(BackboneClientConfig, u8),
1104}
1105
1106pub struct BackboneInterfaceFactory;
1112
1113fn parse_positive_duration_secs(params: &HashMap<String, String>, key: &str) -> Option<Duration> {
1114 params
1115 .get(key)
1116 .and_then(|v| v.parse::<f64>().ok())
1117 .filter(|v| *v > 0.0)
1118 .map(Duration::from_secs_f64)
1119}
1120
1121impl InterfaceFactory for BackboneInterfaceFactory {
1122 fn type_name(&self) -> &str {
1123 "BackboneInterface"
1124 }
1125
1126 fn parse_config(
1127 &self,
1128 name: &str,
1129 id: InterfaceId,
1130 params: &HashMap<String, String>,
1131 ) -> Result<Box<dyn InterfaceConfigData>, String> {
1132 if let Some(target_host) = params.get("remote").or_else(|| params.get("target_host")) {
1133 let target_host = target_host.clone();
1135 let target_port = params
1136 .get("target_port")
1137 .or_else(|| params.get("port"))
1138 .and_then(|v| v.parse().ok())
1139 .unwrap_or(4242);
1140 let transport_identity = params.get("transport_identity").cloned();
1141 let priority = match params.get("priority") {
1142 Some(value) => value.parse::<u8>().map_err(|_| {
1143 format!(
1144 "invalid Backbone peer priority '{}' (expected 0..100)",
1145 value
1146 )
1147 })?,
1148 None => crate::driver::BACKBONE_PEER_POOL_CONFIGURED_DEFAULT_PRIORITY,
1149 };
1150 if priority > 100 {
1151 return Err(format!(
1152 "invalid Backbone peer priority '{}' (expected 0..100)",
1153 priority
1154 ));
1155 }
1156 Ok(Box::new(BackboneMode::Client(
1157 BackboneClientConfig {
1158 name: name.to_string(),
1159 target_host,
1160 target_port,
1161 interface_id: id,
1162 transport_identity,
1163 ..BackboneClientConfig::default()
1164 },
1165 priority,
1166 )))
1167 } else {
1168 let listen_ip = params
1170 .get("listen_ip")
1171 .or_else(|| params.get("device"))
1172 .cloned()
1173 .unwrap_or_else(|| "0.0.0.0".into());
1174 let listen_port = params
1175 .get("listen_port")
1176 .or_else(|| params.get("port"))
1177 .and_then(|v| v.parse().ok())
1178 .unwrap_or(4242);
1179 let max_connections = params.get("max_connections").and_then(|v| v.parse().ok());
1180 let idle_timeout = parse_positive_duration_secs(params, "idle_timeout");
1181 let write_stall_timeout = parse_positive_duration_secs(params, "write_stall_timeout");
1182 let abuse = BackboneAbuseConfig {
1183 max_penalty_duration: parse_positive_duration_secs(params, "max_penalty_duration"),
1184 };
1185 let mut config = BackboneConfig {
1186 name: name.to_string(),
1187 listen_ip,
1188 listen_port,
1189 interface_id: id,
1190 mode: constants::MODE_FULL,
1191 recursive_prs: false,
1192 announces_from_internal: true,
1193 max_connections,
1194 idle_timeout,
1195 write_stall_timeout,
1196 abuse,
1197 ingress_control: IngressControlConfig::enabled(),
1198 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
1199 max_connections: None,
1200 idle_timeout: None,
1201 write_stall_timeout: None,
1202 abuse: BackboneAbuseConfig::default(),
1203 })),
1204 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
1205 };
1206 let startup = BackboneServerRuntime::from_config(&config);
1207 config.runtime = Arc::new(Mutex::new(startup));
1208 Ok(Box::new(BackboneMode::Server(config)))
1209 }
1210 }
1211
1212 fn start(
1213 &self,
1214 config: Box<dyn InterfaceConfigData>,
1215 ctx: StartContext,
1216 ) -> io::Result<StartResult> {
1217 let mode = *config.into_any().downcast::<BackboneMode>().map_err(|_| {
1218 io::Error::new(
1219 io::ErrorKind::InvalidData,
1220 "wrong config type for BackboneInterface",
1221 )
1222 })?;
1223
1224 match mode {
1225 BackboneMode::Client(cfg, _) => {
1226 let id = cfg.interface_id;
1227 let name = cfg.name.clone();
1228 let info = InterfaceInfo {
1229 id,
1230 name,
1231 mode: ctx.mode,
1232 recursive_prs: ctx.recursive_prs,
1233 announces_from_internal: ctx.announces_from_internal,
1234 out_capable: true,
1235 in_capable: true,
1236 bitrate: Some(1_000_000_000),
1237 airtime_profile: None,
1238 announce_rate_target: None,
1239 announce_rate_grace: 0,
1240 announce_rate_penalty: 0.0,
1241 announce_cap: constants::ANNOUNCE_CAP,
1242 is_local_client: false,
1243 wants_tunnel: false,
1244 tunnel_id: None,
1245 mtu: 65535,
1246 ingress_control: ctx.ingress_control,
1247 ia_freq: 0.0,
1248 ip_freq: 0.0,
1249 op_freq: 0.0,
1250 op_samples: 0,
1251 started: crate::time::now(),
1252 };
1253 let writer = start_client(cfg, ctx.tx)?;
1254 Ok(StartResult::Simple {
1255 id,
1256 info,
1257 writer,
1258 interface_type_name: "BackboneInterface".to_string(),
1259 })
1260 }
1261 BackboneMode::Server(mut cfg) => {
1262 cfg.ingress_control = ctx.ingress_control;
1263 cfg.mode = ctx.mode;
1264 cfg.recursive_prs = ctx.recursive_prs;
1265 start(cfg, ctx.tx, ctx.next_dynamic_id)?;
1266 Ok(StartResult::Listener { control: None })
1267 }
1268 }
1269 }
1270}
1271
1272pub(crate) fn runtime_handle_from_mode(mode: &BackboneMode) -> Option<BackboneRuntimeConfigHandle> {
1273 match mode {
1274 BackboneMode::Server(config) => Some(BackboneRuntimeConfigHandle {
1275 interface_name: config.name.clone(),
1276 runtime: Arc::clone(&config.runtime),
1277 startup: BackboneServerRuntime::from_config(config),
1278 }),
1279 BackboneMode::Client(_, _) => None,
1280 }
1281}
1282
1283pub(crate) fn peer_state_handle_from_mode(mode: &BackboneMode) -> Option<BackbonePeerStateHandle> {
1284 match mode {
1285 BackboneMode::Server(config) => Some(BackbonePeerStateHandle {
1286 interface_id: config.interface_id,
1287 interface_name: config.name.clone(),
1288 peer_state: Arc::clone(&config.peer_state),
1289 }),
1290 BackboneMode::Client(_, _) => None,
1291 }
1292}
1293
1294pub(crate) fn client_runtime_handle_from_mode(
1295 mode: &BackboneMode,
1296) -> Option<BackboneClientRuntimeConfigHandle> {
1297 match mode {
1298 BackboneMode::Client(config, _) => Some(BackboneClientRuntimeConfigHandle {
1299 interface_name: config.name.clone(),
1300 runtime: Arc::clone(&config.runtime),
1301 startup: BackboneClientRuntime::from_config(config),
1302 }),
1303 BackboneMode::Server(_) => None,
1304 }
1305}
1306
1307pub(crate) fn client_config_from_mode(mode: &BackboneMode) -> Option<BackboneClientConfig> {
1308 match mode {
1309 BackboneMode::Client(config, _) => Some(config.clone()),
1310 BackboneMode::Server(_) => None,
1311 }
1312}
1313
1314pub(crate) fn client_priority_from_mode(mode: &BackboneMode) -> Option<u8> {
1315 match mode {
1316 BackboneMode::Client(_, priority) => Some(*priority),
1317 BackboneMode::Server(_) => None,
1318 }
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323 use super::*;
1324 use std::sync::mpsc;
1325 use std::time::Duration;
1326
1327 fn find_free_port() -> u16 {
1328 TcpListener::bind("127.0.0.1:0")
1329 .unwrap()
1330 .local_addr()
1331 .unwrap()
1332 .port()
1333 }
1334
1335 fn recv_non_peer_event(
1336 rx: &mpsc::Receiver<Event>,
1337 timeout: Duration,
1338 ) -> Result<Event, mpsc::RecvTimeoutError> {
1339 let deadline = Instant::now() + timeout;
1340 loop {
1341 let remaining = deadline.saturating_duration_since(Instant::now());
1342 if remaining.is_zero() {
1343 return Err(mpsc::RecvTimeoutError::Timeout);
1344 }
1345 let event = rx.recv_timeout(remaining)?;
1346 match event {
1347 Event::BackbonePeerConnected { .. }
1348 | Event::BackbonePeerDisconnected { .. }
1349 | Event::BackbonePeerIdleTimeout { .. }
1350 | Event::BackbonePeerWriteStall { .. }
1351 | Event::BackbonePeerPenalty { .. } => continue,
1352 other => return Ok(other),
1353 }
1354 }
1355 }
1356
1357 fn make_server_config(
1358 port: u16,
1359 interface_id: u64,
1360 max_connections: Option<usize>,
1361 idle_timeout: Option<Duration>,
1362 write_stall_timeout: Option<Duration>,
1363 abuse: BackboneAbuseConfig,
1364 ) -> BackboneConfig {
1365 let mut config = BackboneConfig {
1366 name: "test-backbone".into(),
1367 listen_ip: "127.0.0.1".into(),
1368 listen_port: port,
1369 interface_id: InterfaceId(interface_id),
1370 mode: constants::MODE_FULL,
1371 recursive_prs: false,
1372 announces_from_internal: true,
1373 max_connections,
1374 idle_timeout,
1375 write_stall_timeout,
1376 abuse,
1377 ingress_control: IngressControlConfig::enabled(),
1378 runtime: Arc::new(Mutex::new(BackboneServerRuntime {
1379 max_connections: None,
1380 idle_timeout: None,
1381 write_stall_timeout: None,
1382 abuse: BackboneAbuseConfig::default(),
1383 })),
1384 peer_state: Arc::new(Mutex::new(BackbonePeerMonitor::new())),
1385 };
1386 let startup = BackboneServerRuntime::from_config(&config);
1387 config.runtime = Arc::new(Mutex::new(startup));
1388 config
1389 }
1390
1391 #[test]
1392 fn backbone_accept_connection() {
1393 let port = find_free_port();
1394 let (tx, rx) = crate::event::channel();
1395 let next_id = Arc::new(AtomicU64::new(8000));
1396
1397 let config = make_server_config(port, 80, None, None, None, BackboneAbuseConfig::default());
1398
1399 start(config, tx, next_id).unwrap();
1400 thread::sleep(Duration::from_millis(50));
1401
1402 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1403
1404 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1405 match event {
1406 Event::InterfaceUp(id, writer, info) => {
1407 assert_eq!(id, InterfaceId(8000));
1408 assert!(writer.is_some());
1409 assert!(info.is_some());
1410 let info = info.unwrap();
1411 assert!(info.out_capable);
1412 assert!(info.in_capable);
1413 }
1414 other => panic!("expected InterfaceUp, got {:?}", other),
1415 }
1416 }
1417
1418 #[test]
1419 fn backbone_accepted_connection_inherits_server_mode() {
1420 let port = find_free_port();
1421 let (tx, rx) = crate::event::channel();
1422 let next_id = Arc::new(AtomicU64::new(8050));
1423
1424 let mut config =
1425 make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
1426 config.mode = constants::MODE_GATEWAY;
1427
1428 start(config, tx, next_id).unwrap();
1429 thread::sleep(Duration::from_millis(50));
1430
1431 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1432
1433 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1434 match event {
1435 Event::InterfaceUp(_, _, Some(info)) => {
1436 assert_eq!(info.mode, constants::MODE_GATEWAY);
1437 }
1438 other => panic!("expected InterfaceUp with info, got {:?}", other),
1439 }
1440 }
1441
1442 #[test]
1443 fn backbone_receive_frame() {
1444 let port = find_free_port();
1445 let (tx, rx) = crate::event::channel();
1446 let next_id = Arc::new(AtomicU64::new(8100));
1447
1448 let config = make_server_config(port, 81, None, None, None, BackboneAbuseConfig::default());
1449
1450 start(config, tx, next_id).unwrap();
1451 thread::sleep(Duration::from_millis(50));
1452
1453 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1454
1455 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1457
1458 let payload: Vec<u8> = (0..32).collect();
1460 client.write_all(&hdlc::frame(&payload)).unwrap();
1461
1462 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1463 match event {
1464 Event::Frame {
1465 interface_id,
1466 data,
1467 rssi: _,
1468 snr: _,
1469 } => {
1470 assert_eq!(interface_id, InterfaceId(8100));
1471 assert_eq!(data, payload);
1472 }
1473 other => panic!("expected Frame, got {:?}", other),
1474 }
1475 }
1476
1477 #[test]
1478 fn backbone_send_to_client() {
1479 let port = find_free_port();
1480 let (tx, rx) = crate::event::channel();
1481 let next_id = Arc::new(AtomicU64::new(8200));
1482
1483 let config = make_server_config(port, 82, None, None, None, BackboneAbuseConfig::default());
1484
1485 start(config, tx, next_id).unwrap();
1486 thread::sleep(Duration::from_millis(50));
1487
1488 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1489 client
1490 .set_read_timeout(Some(Duration::from_secs(2)))
1491 .unwrap();
1492
1493 let event = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1495 let mut writer = match event {
1496 Event::InterfaceUp(_, Some(w), _) => w,
1497 other => panic!("expected InterfaceUp with writer, got {:?}", other),
1498 };
1499
1500 let payload: Vec<u8> = (0..24).collect();
1502 writer.send_frame(&payload).unwrap();
1503
1504 let mut buf = [0u8; 256];
1506 let n = client.read(&mut buf).unwrap();
1507 let expected = hdlc::frame(&payload);
1508 assert_eq!(&buf[..n], &expected[..]);
1509 }
1510
1511 #[test]
1512 fn backbone_multiple_clients() {
1513 let port = find_free_port();
1514 let (tx, rx) = crate::event::channel();
1515 let next_id = Arc::new(AtomicU64::new(8300));
1516
1517 let config = make_server_config(port, 83, None, None, None, BackboneAbuseConfig::default());
1518
1519 start(config, tx, next_id).unwrap();
1520 thread::sleep(Duration::from_millis(50));
1521
1522 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1523 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1524
1525 let mut ids = Vec::new();
1526 for _ in 0..2 {
1527 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1528 match event {
1529 Event::InterfaceUp(id, _, _) => ids.push(id),
1530 other => panic!("expected InterfaceUp, got {:?}", other),
1531 }
1532 }
1533
1534 assert_eq!(ids.len(), 2);
1535 assert_ne!(ids[0], ids[1]);
1536 }
1537
1538 #[test]
1539 fn backbone_client_disconnect() {
1540 let port = find_free_port();
1541 let (tx, rx) = crate::event::channel();
1542 let next_id = Arc::new(AtomicU64::new(8400));
1543
1544 let config = make_server_config(port, 84, None, None, None, BackboneAbuseConfig::default());
1545
1546 start(config, tx, next_id).unwrap();
1547 thread::sleep(Duration::from_millis(50));
1548
1549 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1550
1551 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1553
1554 drop(client);
1556
1557 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1559 assert!(
1560 matches!(event, Event::InterfaceDown(InterfaceId(8400))),
1561 "expected InterfaceDown(8400), got {:?}",
1562 event
1563 );
1564 }
1565
1566 #[test]
1567 fn backbone_epoll_multiplexing() {
1568 let port = find_free_port();
1569 let (tx, rx) = crate::event::channel();
1570 let next_id = Arc::new(AtomicU64::new(8500));
1571
1572 let config = make_server_config(port, 85, None, None, None, BackboneAbuseConfig::default());
1573
1574 start(config, tx, next_id).unwrap();
1575 thread::sleep(Duration::from_millis(50));
1576
1577 let mut client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1578 let mut client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1579
1580 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1582 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1583
1584 let payload1: Vec<u8> = (0..24).collect();
1586 let payload2: Vec<u8> = (100..130).collect();
1587 client1.write_all(&hdlc::frame(&payload1)).unwrap();
1588 client2.write_all(&hdlc::frame(&payload2)).unwrap();
1589
1590 let mut received = Vec::new();
1592 for _ in 0..2 {
1593 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1594 match event {
1595 Event::Frame { data, .. } => received.push(data),
1596 other => panic!("expected Frame, got {:?}", other),
1597 }
1598 }
1599 assert!(received.contains(&payload1));
1600 assert!(received.contains(&payload2));
1601 }
1602
1603 #[test]
1604 fn backbone_bind_port() {
1605 let port = find_free_port();
1606 let (tx, _rx) = crate::event::channel();
1607 let next_id = Arc::new(AtomicU64::new(8600));
1608
1609 let config = make_server_config(port, 86, None, None, None, BackboneAbuseConfig::default());
1610
1611 start(config, tx, next_id).unwrap();
1613 }
1614
1615 #[test]
1616 fn backbone_hdlc_fragmented() {
1617 let port = find_free_port();
1618 let (tx, rx) = crate::event::channel();
1619 let next_id = Arc::new(AtomicU64::new(8700));
1620
1621 let config = make_server_config(port, 87, None, None, None, BackboneAbuseConfig::default());
1622
1623 start(config, tx, next_id).unwrap();
1624 thread::sleep(Duration::from_millis(50));
1625
1626 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1627 client.set_nodelay(true).unwrap();
1628
1629 let _ = recv_non_peer_event(&rx, Duration::from_secs(1)).unwrap();
1631
1632 let payload: Vec<u8> = (0..32).collect();
1634 let framed = hdlc::frame(&payload);
1635 let mid = framed.len() / 2;
1636
1637 client.write_all(&framed[..mid]).unwrap();
1638 thread::sleep(Duration::from_millis(50));
1639 client.write_all(&framed[mid..]).unwrap();
1640
1641 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1643 match event {
1644 Event::Frame { data, .. } => {
1645 assert_eq!(data, payload);
1646 }
1647 other => panic!("expected Frame, got {:?}", other),
1648 }
1649 }
1650
1651 fn make_client_config(port: u16, id: u64) -> BackboneClientConfig {
1656 BackboneClientConfig {
1657 name: format!("test-bb-client-{}", port),
1658 target_host: "127.0.0.1".into(),
1659 target_port: port,
1660 interface_id: InterfaceId(id),
1661 reconnect_wait: Duration::from_millis(100),
1662 max_reconnect_tries: Some(2),
1663 connect_timeout: Duration::from_secs(2),
1664 transport_identity: None,
1665 runtime: Arc::new(Mutex::new(BackboneClientRuntime {
1666 reconnect_wait: Duration::from_millis(100),
1667 max_reconnect_tries: Some(2),
1668 connect_timeout: Duration::from_secs(2),
1669 })),
1670 }
1671 }
1672
1673 #[test]
1674 fn backbone_client_connect() {
1675 let port = find_free_port();
1676 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1677 let (tx, rx) = crate::event::channel();
1678
1679 let config = make_client_config(port, 9000);
1680 let _writer = start_client(config, tx).unwrap();
1681
1682 let _server_stream = listener.accept().unwrap();
1683
1684 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1685 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9000), _, _)));
1686 }
1687
1688 #[test]
1689 fn backbone_client_receive_frame() {
1690 let port = find_free_port();
1691 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1692 let (tx, rx) = crate::event::channel();
1693
1694 let config = make_client_config(port, 9100);
1695 let _writer = start_client(config, tx).unwrap();
1696
1697 let (mut server_stream, _) = listener.accept().unwrap();
1698
1699 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1701
1702 let payload: Vec<u8> = (0..32).collect();
1704 server_stream.write_all(&hdlc::frame(&payload)).unwrap();
1705
1706 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
1707 match event {
1708 Event::Frame {
1709 interface_id,
1710 data,
1711 rssi: _,
1712 snr: _,
1713 } => {
1714 assert_eq!(interface_id, InterfaceId(9100));
1715 assert_eq!(data, payload);
1716 }
1717 other => panic!("expected Frame, got {:?}", other),
1718 }
1719 }
1720
1721 #[test]
1722 fn backbone_client_send_frame() {
1723 let port = find_free_port();
1724 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1725 let (tx, _rx) = crate::event::channel();
1726
1727 let config = make_client_config(port, 9200);
1728 let mut writer = start_client(config, tx).unwrap();
1729
1730 let (mut server_stream, _) = listener.accept().unwrap();
1731 server_stream
1732 .set_read_timeout(Some(Duration::from_secs(2)))
1733 .unwrap();
1734
1735 let payload: Vec<u8> = (0..24).collect();
1736 writer.send_frame(&payload).unwrap();
1737
1738 let mut buf = [0u8; 256];
1739 let n = server_stream.read(&mut buf).unwrap();
1740 let expected = hdlc::frame(&payload);
1741 assert_eq!(&buf[..n], &expected[..]);
1742 }
1743
1744 #[test]
1745 fn backbone_max_connections_rejects_excess() {
1746 let port = find_free_port();
1747 let (tx, rx) = crate::event::channel();
1748 let next_id = Arc::new(AtomicU64::new(8800));
1749
1750 let config = make_server_config(
1751 port,
1752 88,
1753 Some(2),
1754 None,
1755 None,
1756 BackboneAbuseConfig::default(),
1757 );
1758
1759 start(config, tx, next_id).unwrap();
1760 thread::sleep(Duration::from_millis(50));
1761
1762 let _client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1764 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1765
1766 for _ in 0..2 {
1768 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1769 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
1770 }
1771
1772 let client3 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1774 client3
1775 .set_read_timeout(Some(Duration::from_millis(500)))
1776 .unwrap();
1777
1778 thread::sleep(Duration::from_millis(100));
1780
1781 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
1783 assert!(
1784 result.is_err(),
1785 "expected no InterfaceUp for rejected connection, got {:?}",
1786 result
1787 );
1788 }
1789
1790 #[test]
1791 fn backbone_max_connections_allows_after_disconnect() {
1792 let port = find_free_port();
1793 let (tx, rx) = crate::event::channel();
1794 let next_id = Arc::new(AtomicU64::new(8900));
1795
1796 let config = make_server_config(
1797 port,
1798 89,
1799 Some(1),
1800 None,
1801 None,
1802 BackboneAbuseConfig::default(),
1803 );
1804
1805 start(config, tx, next_id).unwrap();
1806 thread::sleep(Duration::from_millis(50));
1807
1808 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1810 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1811 assert!(matches!(event, Event::InterfaceUp(_, _, _)));
1812
1813 drop(client1);
1815
1816 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1818 assert!(matches!(event, Event::InterfaceDown(_)));
1819
1820 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1822 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1823 assert!(
1824 matches!(event, Event::InterfaceUp(_, _, _)),
1825 "expected InterfaceUp after slot freed, got {:?}",
1826 event
1827 );
1828 }
1829
1830 #[test]
1831 fn backbone_client_reconnect() {
1832 let port = find_free_port();
1833 let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
1834 listener.set_nonblocking(false).unwrap();
1835 let (tx, rx) = crate::event::channel();
1836
1837 let config = make_client_config(port, 9300);
1838 let _writer = start_client(config, tx).unwrap();
1839
1840 let (server_stream, _) = listener.accept().unwrap();
1842
1843 let _ = rx.recv_timeout(Duration::from_secs(1)).unwrap();
1845
1846 drop(server_stream);
1847
1848 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1850 assert!(matches!(event, Event::InterfaceDown(InterfaceId(9300))));
1851
1852 let _server_stream2 = listener.accept().unwrap();
1854
1855 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1857 assert!(matches!(event, Event::InterfaceUp(InterfaceId(9300), _, _)));
1858 }
1859
1860 #[test]
1861 fn backbone_idle_timeout_disconnects_silent_client() {
1862 let port = find_free_port();
1863 let (tx, rx) = crate::event::channel();
1864 let next_id = Arc::new(AtomicU64::new(9400));
1865
1866 let config = make_server_config(
1867 port,
1868 94,
1869 None,
1870 Some(Duration::from_millis(150)),
1871 None,
1872 BackboneAbuseConfig::default(),
1873 );
1874
1875 start(config, tx, next_id).unwrap();
1876 thread::sleep(Duration::from_millis(50));
1877
1878 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1879
1880 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1881 let client_id = match event {
1882 Event::InterfaceUp(id, _, _) => id,
1883 other => panic!("expected InterfaceUp, got {:?}", other),
1884 };
1885
1886 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1887 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
1888 }
1889
1890 #[test]
1891 fn backbone_idle_timeout_ignores_client_after_data() {
1892 let port = find_free_port();
1893 let (tx, rx) = crate::event::channel();
1894 let next_id = Arc::new(AtomicU64::new(9500));
1895
1896 let config = make_server_config(
1897 port,
1898 95,
1899 None,
1900 Some(Duration::from_millis(200)),
1901 None,
1902 BackboneAbuseConfig::default(),
1903 );
1904
1905 start(config, tx, next_id).unwrap();
1906 thread::sleep(Duration::from_millis(50));
1907
1908 let mut client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1909
1910 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1911 let client_id = match event {
1912 Event::InterfaceUp(id, _, _) => id,
1913 other => panic!("expected InterfaceUp, got {:?}", other),
1914 };
1915
1916 client.write_all(&hdlc::frame(&[1u8; 24])).unwrap();
1917
1918 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1919 match event {
1920 Event::Frame {
1921 interface_id,
1922 data,
1923 rssi: _,
1924 snr: _,
1925 } => {
1926 assert_eq!(interface_id, client_id);
1927 assert_eq!(data, vec![1u8; 24]);
1928 }
1929 other => panic!("expected Frame, got {:?}", other),
1930 }
1931
1932 let result = recv_non_peer_event(&rx, Duration::from_millis(500));
1933 assert!(
1934 result.is_err(),
1935 "expected no InterfaceDown after client sent data, got {:?}",
1936 result
1937 );
1938 }
1939
1940 #[test]
1941 fn backbone_runtime_idle_timeout_updates_live() {
1942 let port = find_free_port();
1943 let (tx, rx) = crate::event::channel();
1944 let next_id = Arc::new(AtomicU64::new(9650));
1945
1946 let config = make_server_config(port, 97, None, None, None, BackboneAbuseConfig::default());
1947 let runtime = Arc::clone(&config.runtime);
1948
1949 start(config, tx, next_id).unwrap();
1950 thread::sleep(Duration::from_millis(50));
1951
1952 let _client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1953 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1954 let client_id = match event {
1955 Event::InterfaceUp(id, _, _) => id,
1956 other => panic!("expected InterfaceUp, got {:?}", other),
1957 };
1958
1959 {
1960 let mut runtime = runtime.lock().unwrap();
1961 runtime.idle_timeout = Some(Duration::from_millis(150));
1962 }
1963
1964 let event = recv_non_peer_event(&rx, Duration::from_secs(4)).unwrap();
1965 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
1966 }
1967
1968 #[test]
1969 fn backbone_write_stall_timeout_disconnects_unwritable_client() {
1970 let port = find_free_port();
1971 let (tx, rx) = crate::event::channel();
1972 let next_id = Arc::new(AtomicU64::new(9660));
1973
1974 let config = make_server_config(
1975 port,
1976 98,
1977 None,
1978 None,
1979 Some(Duration::from_millis(50)),
1980 BackboneAbuseConfig::default(),
1981 );
1982
1983 start(config, tx, next_id).unwrap();
1984 thread::sleep(Duration::from_millis(50));
1985
1986 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
1987 client
1988 .set_read_timeout(Some(Duration::from_millis(100)))
1989 .unwrap();
1990 let sock = SockRef::from(&client);
1991 sock.set_recv_buffer_size(4096).ok();
1992
1993 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
1994 let (client_id, mut writer) = match event {
1995 Event::InterfaceUp(id, Some(writer), _) => (id, writer),
1996 other => panic!("expected InterfaceUp with writer, got {:?}", other),
1997 };
1998
1999 let payload = vec![0x55; 512 * 1024];
2000 let deadline = Instant::now() + Duration::from_secs(3);
2001 let mut stalled = false;
2002 while Instant::now() < deadline {
2003 match writer.send_frame(&payload) {
2004 Ok(()) => {}
2005 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
2006 thread::sleep(Duration::from_millis(10));
2007 }
2008 Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {
2009 stalled = true;
2010 break;
2011 }
2012 Err(e) => panic!("unexpected send error: {}", e),
2013 }
2014 }
2015
2016 assert!(stalled, "expected writer to time out on persistent stall");
2017 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2018 assert!(matches!(event, Event::InterfaceDown(id) if id == client_id));
2019 }
2020
2021 fn wait_for<F>(rx: &mpsc::Receiver<Event>, timeout: Duration, mut pred: F) -> Option<Event>
2023 where
2024 F: FnMut(&Event) -> bool,
2025 {
2026 let deadline = Instant::now() + timeout;
2027 loop {
2028 let remaining = deadline.saturating_duration_since(Instant::now());
2029 if remaining.is_zero() {
2030 return None;
2031 }
2032 match rx.recv_timeout(remaining) {
2033 Ok(event) if pred(&event) => return Some(event),
2034 Ok(_) => continue,
2035 Err(_) => return None,
2036 }
2037 }
2038 }
2039
2040 #[test]
2041 fn backbone_write_stall_emits_peer_events() {
2042 let port = find_free_port();
2043 let (tx, rx) = crate::event::channel();
2044 let next_id = Arc::new(AtomicU64::new(9700));
2045
2046 let config = make_server_config(
2047 port,
2048 97,
2049 None,
2050 None,
2051 Some(Duration::from_millis(50)), BackboneAbuseConfig::default(),
2053 );
2054
2055 start(config, tx, next_id).unwrap();
2056 thread::sleep(Duration::from_millis(50));
2057
2058 let client = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2060 client
2061 .set_read_timeout(Some(Duration::from_millis(100)))
2062 .unwrap();
2063 let sock = SockRef::from(&client);
2064 sock.set_recv_buffer_size(4096).ok();
2065
2066 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2068 let mut writer = match event {
2069 Event::InterfaceUp(_, Some(w), _) => w,
2070 other => panic!("expected InterfaceUp with writer, got {:?}", other),
2071 };
2072
2073 let payload = vec![0x55; 512 * 1024];
2075 let deadline = Instant::now() + Duration::from_secs(3);
2076 while Instant::now() < deadline {
2077 match writer.send_frame(&payload) {
2078 Ok(()) | Err(_) => {
2079 if Instant::now() + Duration::from_millis(10) > deadline {
2080 break;
2081 }
2082 thread::sleep(Duration::from_millis(10));
2083 }
2084 }
2085 }
2086
2087 let stall_event = wait_for(&rx, Duration::from_secs(3), |e| {
2089 matches!(e, Event::BackbonePeerWriteStall { .. })
2090 });
2091 assert!(
2092 stall_event.is_some(),
2093 "expected BackbonePeerWriteStall event"
2094 );
2095 }
2096
2097 #[test]
2098 fn backbone_blacklisted_peer_rejected_on_connect() {
2099 let port = find_free_port();
2100 let (tx, rx) = crate::event::channel();
2101 let next_id = Arc::new(AtomicU64::new(9800));
2102
2103 let config = make_server_config(port, 98, None, None, None, BackboneAbuseConfig::default());
2104 let peer_state = config.peer_state.clone();
2105
2106 start(config, tx.clone(), next_id).unwrap();
2107 thread::sleep(Duration::from_millis(50));
2108
2109 let client1 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2111 let event = recv_non_peer_event(&rx, Duration::from_secs(2)).unwrap();
2112 assert!(
2113 matches!(event, Event::InterfaceUp(_, _, _)),
2114 "first connection should succeed"
2115 );
2116 drop(client1);
2117
2118 thread::sleep(Duration::from_millis(100));
2120 while rx.try_recv().is_ok() {}
2121
2122 peer_state.lock().unwrap().blacklist(
2124 "127.0.0.1".parse().unwrap(),
2125 Duration::from_secs(60),
2126 "test blacklist".into(),
2127 );
2128
2129 let _client2 = TcpStream::connect(format!("127.0.0.1:{}", port)).unwrap();
2131 thread::sleep(Duration::from_millis(200));
2133
2134 let event = rx.try_recv();
2136 match event {
2137 Ok(Event::InterfaceUp(_, _, _)) => {
2138 panic!("blacklisted peer should not get InterfaceUp")
2139 }
2140 _ => {} }
2142 }
2143
2144 #[test]
2145 fn backbone_parse_config_reads_abuse_settings() {
2146 let factory = BackboneInterfaceFactory;
2147 let mut params = HashMap::new();
2148 params.insert("listen_ip".into(), "127.0.0.1".into());
2149 params.insert("listen_port".into(), "4242".into());
2150 params.insert("idle_timeout".into(), "15".into());
2151 params.insert("write_stall_timeout".into(), "45".into());
2152 params.insert("max_penalty_duration".into(), "3600".into());
2153
2154 let config = factory
2155 .parse_config("test-backbone", InterfaceId(97), ¶ms)
2156 .unwrap();
2157 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2158
2159 match mode {
2160 BackboneMode::Server(config) => {
2161 assert_eq!(config.listen_ip, "127.0.0.1");
2162 assert_eq!(config.listen_port, 4242);
2163 assert_eq!(config.idle_timeout, Some(Duration::from_secs(15)));
2164 assert_eq!(config.write_stall_timeout, Some(Duration::from_secs(45)));
2165 assert_eq!(
2166 config.abuse.max_penalty_duration,
2167 Some(Duration::from_secs(3600))
2168 );
2169 }
2170 BackboneMode::Client(_, _) => panic!("expected server config"),
2171 }
2172 }
2173
2174 #[test]
2175 fn backbone_parse_config_reads_client_priority() {
2176 let factory = BackboneInterfaceFactory;
2177 let mut params = HashMap::new();
2178 params.insert("remote".into(), "example.com".into());
2179 params.insert("target_port".into(), "4242".into());
2180 params.insert("priority".into(), "87".into());
2181
2182 let config = factory
2183 .parse_config("test-backbone-client", InterfaceId(96), ¶ms)
2184 .unwrap();
2185 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2186
2187 match mode {
2188 BackboneMode::Client(_, priority) => assert_eq!(priority, 87),
2189 BackboneMode::Server(_) => panic!("expected client config"),
2190 }
2191 }
2192
2193 #[test]
2194 fn backbone_parse_config_defaults_client_priority() {
2195 let factory = BackboneInterfaceFactory;
2196 let mut params = HashMap::new();
2197 params.insert("remote".into(), "example.com".into());
2198
2199 let config = factory
2200 .parse_config("test-backbone-client", InterfaceId(95), ¶ms)
2201 .unwrap();
2202 let mode = *config.into_any().downcast::<BackboneMode>().unwrap();
2203
2204 match mode {
2205 BackboneMode::Client(_, priority) => assert_eq!(priority, 60),
2206 BackboneMode::Server(_) => panic!("expected client config"),
2207 }
2208 }
2209
2210 #[test]
2211 fn backbone_parse_config_rejects_invalid_client_priority() {
2212 let factory = BackboneInterfaceFactory;
2213 for value in ["-1", "101", "fast"] {
2214 let mut params = HashMap::new();
2215 params.insert("remote".into(), "example.com".into());
2216 params.insert("priority".into(), value.into());
2217
2218 let err = match factory.parse_config("test-backbone-client", InterfaceId(94), ¶ms) {
2219 Ok(_) => panic!("priority {value} should be rejected"),
2220 Err(err) => err,
2221 };
2222 assert!(
2223 err.contains("priority"),
2224 "unexpected error for {value}: {err}"
2225 );
2226 }
2227 }
2228}