1use std::collections::HashMap;
5use std::net::SocketAddr;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8
9use yip_membership::Record;
10
11use crate::proto::{Message, NodeId};
12
13pub const REGISTRATION_SKEW_SECS: u64 = 300;
20
21pub const REG_TTL_MS: u64 = 60_000;
23pub const MAX_REGISTRATIONS: usize = 65_536;
25pub const MAX_RATE_ENTRIES: usize = 131_072;
31pub const RATE_WINDOW_MS: u64 = 1_000;
33pub const MAX_MSGS_PER_WINDOW: usize = 64;
34
35struct Reg {
36 addr: SocketAddr,
37 expiry_ms: u64,
38 last_counter: u64,
39 record: Option<Record>,
44}
45
46struct Rate {
47 window_start_ms: u64,
48 count: usize,
49}
50
51struct TlsSeen {
56 last_counter: u64,
57 expiry_ms: u64,
58}
59
60pub struct RendezvousServer {
62 regs: HashMap<NodeId, Reg>,
63 rates: HashMap<SocketAddr, Rate>,
64 tls_seen: HashMap<NodeId, TlsSeen>,
65 forwarded: Arc<AtomicU64>,
71 roots_cfg: Option<(Vec<[u8; 32]>, [u8; 16])>,
78}
79
80impl RendezvousServer {
81 pub fn new(_now_ms: u64) -> Self {
82 Self {
83 regs: HashMap::new(),
84 rates: HashMap::new(),
85 tls_seen: HashMap::new(),
86 forwarded: Arc::new(AtomicU64::new(0)),
87 roots_cfg: None,
88 }
89 }
90
91 pub fn new_with_roots(_now_ms: u64, ca_pubkeys: Vec<[u8; 32]>, network_id: [u8; 16]) -> Self {
95 Self {
96 regs: HashMap::new(),
97 rates: HashMap::new(),
98 tls_seen: HashMap::new(),
99 forwarded: Arc::new(AtomicU64::new(0)),
100 roots_cfg: Some((ca_pubkeys, network_id)),
101 }
102 }
103
104 pub fn forwarded_count(&self) -> u64 {
105 self.forwarded.load(Ordering::Relaxed)
106 }
107
108 pub fn forwarded_handle(&self) -> Arc<AtomicU64> {
115 Arc::clone(&self.forwarded)
116 }
117
118 pub fn record_relay_forward(&self) {
129 self.forwarded.fetch_add(1, Ordering::Relaxed);
130 }
131
132 pub fn is_registered(&self, node: &NodeId, now_ms: u64) -> bool {
135 self.regs.get(node).is_some_and(|r| r.expiry_ms > now_ms)
136 }
137
138 pub fn roots_cfg(&self) -> Option<&(Vec<[u8; 32]>, [u8; 16])> {
141 self.roots_cfg.as_ref()
142 }
143
144 pub fn verify_signed_record(&self, record: &Record, now_secs: u64) -> bool {
147 let Some((ca_pubkeys, network_id)) = self.roots_cfg.as_ref() else {
148 return false;
149 };
150 if record
151 .verify(ca_pubkeys, network_id, now_secs, REGISTRATION_SKEW_SECS)
152 .is_err()
153 {
154 return false;
155 }
156 let now_wall_ms = now_secs.saturating_mul(1000);
157 let skew_ms = REGISTRATION_SKEW_SECS.saturating_mul(1000);
158 let min_seq = now_wall_ms.saturating_sub(REG_TTL_MS.saturating_add(skew_ms));
159 let max_seq = now_wall_ms.saturating_add(skew_ms);
160
161 if record.seq < min_seq || record.seq > max_seq {
162 return false;
163 }
164 true
165 }
166
167 fn rate_ok(&mut self, src: SocketAddr, now_ms: u64) -> bool {
169 if self.rates.len() >= MAX_RATE_ENTRIES && !self.rates.contains_key(&src) {
176 return false;
177 }
178 let r = self.rates.entry(src).or_insert(Rate {
179 window_start_ms: now_ms,
180 count: 0,
181 });
182 if now_ms.saturating_sub(r.window_start_ms) >= RATE_WINDOW_MS {
183 r.window_start_ms = now_ms;
184 r.count = 0;
185 }
186 if r.count >= MAX_MSGS_PER_WINDOW {
187 return false;
188 }
189 r.count += 1;
190 true
191 }
192
193 pub fn sweep(&mut self, now_ms: u64) {
195 self.regs.retain(|_, reg| reg.expiry_ms > now_ms);
196 self.rates
198 .retain(|_, r| now_ms.saturating_sub(r.window_start_ms) < RATE_WINDOW_MS);
199 self.tls_seen.retain(|_, s| s.expiry_ms > now_ms);
201 }
202
203 pub fn register_if_fresh(
210 &mut self,
211 node: NodeId,
212 counter: u64,
213 src: SocketAddr,
214 now_ms: u64,
215 ) -> bool {
216 if let Some(existing) = self.regs.get(&node) {
217 if existing.expiry_ms > now_ms && counter <= existing.last_counter {
218 return false; }
220 }
221 if self.regs.len() >= MAX_REGISTRATIONS && !self.regs.contains_key(&node) {
222 return false; }
224 self.regs.insert(
225 node,
226 Reg {
227 addr: src,
228 expiry_ms: now_ms.saturating_add(REG_TTL_MS),
229 last_counter: counter,
230 record: None,
237 },
238 );
239 true
240 }
241
242 pub fn register_if_fresh_tls(&mut self, node: NodeId, counter: u64, now_ms: u64) -> bool {
248 if let Some(seen) = self.tls_seen.get(&node) {
249 if seen.expiry_ms > now_ms && counter <= seen.last_counter {
250 return false;
251 }
252 }
253 self.tls_seen.insert(
254 node,
255 TlsSeen {
256 last_counter: counter,
257 expiry_ms: now_ms.saturating_add(REG_TTL_MS),
258 },
259 );
260 true
261 }
262
263 pub fn handle(
271 &mut self,
272 src: SocketAddr,
273 msg: Message,
274 now_ms: u64,
275 now_secs: u64,
276 ) -> Vec<(SocketAddr, Message)> {
277 if !self.rate_ok(src, now_ms) {
278 return Vec::new();
279 }
280 match msg {
281 Message::Register { node, counter } => {
282 if self.roots_cfg.is_some() {
286 return Vec::new();
287 }
288 self.register_if_fresh(node, counter, src, now_ms);
289 Vec::new()
290 }
291 Message::RegisterSigned { record } => {
292 if !self.verify_signed_record(&record, now_secs) {
293 return Vec::new();
294 }
295
296 let node = record.node_id;
297 if self.register_if_fresh(node, record.seq, src, now_ms) {
298 if let Some(reg) = self.regs.get_mut(&node) {
299 reg.record = Some(record);
300 }
301 }
302 Vec::new()
303 }
304 Message::RegisterAuth { .. } => Vec::new(),
307 Message::Lookup { node } => match self.regs.get(&node) {
308 Some(reg) if reg.expiry_ms > now_ms => {
309 let peer_addr = reg.addr;
310 let mut out = vec![(
311 src,
312 Message::PeerInfo {
313 node,
314 reflexive: peer_addr,
315 record: reg.record.clone(),
316 },
317 )];
318 out.push((
320 peer_addr,
321 Message::PunchHint {
322 node,
323 reflexive: src,
324 },
325 ));
326 out
327 }
328 _ => vec![(src, Message::NotFound { node })],
329 },
330 Message::RelaySend {
331 src: sender,
332 dst,
333 payload,
334 } => match self.regs.get(&dst) {
335 Some(reg) if reg.expiry_ms > now_ms => {
336 self.record_relay_forward();
337 vec![(
338 reg.addr,
339 Message::RelayDeliver {
340 src: sender,
341 payload,
342 },
343 )]
344 }
345 _ => Vec::new(), },
347 Message::PeerInfo { .. }
349 | Message::NotFound { .. }
350 | Message::PunchHint { .. }
351 | Message::RelayDeliver { .. } => Vec::new(),
352 }
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::proto::{node_id, Message};
360 use ed25519_dalek::{Signer, SigningKey};
361 use getrandom::SysRng;
362 use rand_core::UnwrapErr;
363 use std::net::SocketAddr;
364 use yip_membership::Cert;
365
366 fn addr(s: &str) -> SocketAddr {
367 s.parse().unwrap()
368 }
369
370 fn now_secs(t: u64) -> u64 {
374 t
375 }
376
377 fn synth_addr(i: u32) -> SocketAddr {
380 let a = u8::try_from((i >> 24) & 0xff).expect("byte in range");
381 let b = u8::try_from((i >> 16) & 0xff).expect("byte in range");
382 let c = u8::try_from((i >> 8) & 0xff).expect("byte in range");
383 let d = u8::try_from(i & 0xff).expect("byte in range");
384 SocketAddr::from((std::net::Ipv4Addr::new(a, b, c, d), 40_000))
385 }
386
387 #[test]
388 fn register_then_lookup_returns_observed_reflexive() {
389 let mut s = RendezvousServer::new(0);
390 let a = node_id(&[1u8; 32]);
391 let _b = node_id(&[2u8; 32]); let out = s.handle(
394 addr("198.51.100.7:41000"),
395 Message::Register {
396 node: a,
397 counter: 1,
398 },
399 0,
400 now_secs(0),
401 );
402 assert!(out.is_empty(), "register produces no reply");
403 let out = s.handle(
406 addr("203.0.113.9:52000"),
407 Message::Lookup { node: a },
408 10,
409 now_secs(10),
410 );
411 assert!(out.iter().any(|(d, m)| *d == addr("203.0.113.9:52000")
413 && matches!(m, Message::PeerInfo { node, reflexive, .. } if *node == a && *reflexive == addr("198.51.100.7:41000"))));
414 assert!(out.iter().any(|(d, m)| *d == addr("198.51.100.7:41000")
415 && matches!(m, Message::PunchHint { reflexive, .. } if *reflexive == addr("203.0.113.9:52000"))));
416 }
417
418 #[test]
419 fn lookup_unregistered_returns_notfound() {
420 let mut s = RendezvousServer::new(0);
421 let a = node_id(&[1u8; 32]);
422 let out = s.handle(
423 addr("203.0.113.9:52000"),
424 Message::Lookup { node: a },
425 0,
426 now_secs(0),
427 );
428 assert_eq!(
429 out,
430 vec![(addr("203.0.113.9:52000"), Message::NotFound { node: a })]
431 );
432 }
433
434 #[test]
435 fn ttl_expiry_evicts_registration() {
436 let mut s = RendezvousServer::new(0);
437 let a = node_id(&[1u8; 32]);
438 s.handle(
439 addr("198.51.100.7:41000"),
440 Message::Register {
441 node: a,
442 counter: 1,
443 },
444 0,
445 now_secs(0),
446 );
447 s.sweep(REG_TTL_MS + 1);
448 let out = s.handle(
449 addr("203.0.113.9:52000"),
450 Message::Lookup { node: a },
451 REG_TTL_MS + 2,
452 now_secs(REG_TTL_MS + 2),
453 );
454 assert!(matches!(out.as_slice(), [(_, Message::NotFound { .. })]));
455 }
456
457 #[test]
458 fn relay_forwards_to_registered_dst_and_counts() {
459 let mut s = RendezvousServer::new(0);
460 let a = node_id(&[1u8; 32]);
461 let b = node_id(&[2u8; 32]);
462 s.handle(
463 addr("198.51.100.7:41000"),
464 Message::Register {
465 node: a,
466 counter: 1,
467 },
468 0,
469 now_secs(0),
470 ); let out = s.handle(
473 addr("203.0.113.9:52000"),
474 Message::RelaySend {
475 src: b,
476 dst: a,
477 payload: vec![9, 9],
478 },
479 5,
480 now_secs(5),
481 );
482 assert_eq!(
483 out,
484 vec![(
485 addr("198.51.100.7:41000"),
486 Message::RelayDeliver {
487 src: b,
488 payload: vec![9, 9]
489 }
490 )]
491 );
492 assert_eq!(s.forwarded_count(), 1);
493 }
494
495 #[test]
496 fn relay_to_unregistered_dst_drops_no_forward() {
497 let mut s = RendezvousServer::new(0);
498 let a = node_id(&[1u8; 32]);
499 let b = node_id(&[2u8; 32]);
500 let out = s.handle(
501 addr("203.0.113.9:52000"),
502 Message::RelaySend {
503 src: b,
504 dst: a,
505 payload: vec![1],
506 },
507 0,
508 now_secs(0),
509 );
510 assert!(out.is_empty());
511 assert_eq!(s.forwarded_count(), 0);
512 }
513
514 #[test]
515 fn rate_limit_caps_messages_per_source_window() {
516 let mut s = RendezvousServer::new(0);
517 let a = node_id(&[1u8; 32]);
518 let src = addr("203.0.113.9:52000");
519 let mut replies = 0;
521 for _ in 0..(MAX_MSGS_PER_WINDOW + 10) {
522 replies += s
523 .handle(src, Message::Lookup { node: a }, 0, now_secs(0))
524 .len();
525 }
526 assert!(
528 replies <= MAX_MSGS_PER_WINDOW,
529 "rate limit must drop excess"
530 );
531 }
532
533 #[test]
534 fn rates_map_grows_with_distinct_sources_but_stays_within_cap() {
535 let mut s = RendezvousServer::new(0);
536 let a = node_id(&[1u8; 32]);
537 for i in 0..2_000u32 {
540 s.handle(synth_addr(i), Message::Lookup { node: a }, 0, now_secs(0));
541 }
542 assert_eq!(s.rates.len(), 2_000);
543 assert!(s.rates.len() <= MAX_RATE_ENTRIES);
544 }
545
546 #[test]
547 fn register_rejects_stale_or_equal_counter() {
548 let mut s = RendezvousServer::new(0);
549 let n = node_id(&[1u8; 32]);
550 let a = addr("10.0.0.1:41000");
551 s.handle(
553 a,
554 Message::Register {
555 node: n,
556 counter: 5,
557 },
558 0,
559 now_secs(0),
560 );
561 assert!(s.is_registered(&n, 0), "counter 5 accepted");
562 let a2 = addr("10.0.0.2:41000");
565 s.handle(
566 a2,
567 Message::Register {
568 node: n,
569 counter: 5,
570 },
571 1,
572 now_secs(1),
573 );
574 let out = s.handle(a, Message::Lookup { node: n }, 2, now_secs(2));
575 match &out[0].1 {
576 Message::PeerInfo { reflexive, .. } => assert_eq!(*reflexive, a),
577 other => panic!("expected PeerInfo, got {other:?}"),
578 }
579 s.handle(
581 a2,
582 Message::Register {
583 node: n,
584 counter: 6,
585 },
586 3,
587 now_secs(3),
588 );
589 let out = s.handle(a, Message::Lookup { node: n }, 4, now_secs(4));
590 match &out[0].1 {
591 Message::PeerInfo { reflexive, .. } => assert_eq!(*reflexive, a2),
592 other => panic!("expected PeerInfo, got {other:?}"),
593 }
594 }
595
596 #[test]
597 fn register_if_fresh_rejects_stale_and_same_counter() {
598 let mut s = RendezvousServer::new(0);
599 let n = node_id(&[1u8; 32]);
600 let a = addr("10.0.0.1:41000");
601 assert!(s.register_if_fresh(n, 5, a, 0), "first-seen node accepted");
603 assert!(
605 !s.register_if_fresh(n, 5, a, 0),
606 "equal counter at same now_ms is a replay"
607 );
608 assert!(!s.register_if_fresh(n, 4, a, 1), "lower counter is stale");
610 assert!(
612 s.register_if_fresh(n, 6, a, 2),
613 "higher counter advances the registration"
614 );
615 }
616
617 #[test]
618 fn register_if_fresh_refuses_new_node_at_capacity() {
619 let mut s = RendezvousServer::new(0);
620 for i in 0..MAX_REGISTRATIONS {
621 let idx = u32::try_from(i).expect("index fits u32");
622 let mut id = [0u8; 32];
623 id[..4].copy_from_slice(&idx.to_be_bytes());
624 let node = node_id(&id);
625 assert!(
626 s.register_if_fresh(node, 1, synth_addr(idx), 0),
627 "filling to capacity must accept each distinct node"
628 );
629 }
630 assert_eq!(s.regs.len(), MAX_REGISTRATIONS);
631
632 let new_node = node_id(&[0xffu8; 32]);
634 assert!(
635 !s.register_if_fresh(new_node, 1, addr("198.51.100.50:9000"), 0),
636 "new node over capacity must be refused"
637 );
638 assert_eq!(
639 s.regs.len(),
640 MAX_REGISTRATIONS,
641 "map must not grow past the cap"
642 );
643
644 let mut existing_id = [0u8; 32];
647 existing_id[..4].copy_from_slice(&0u32.to_be_bytes());
648 let existing_node = node_id(&existing_id);
649 assert!(
650 s.register_if_fresh(existing_node, 2, synth_addr(0), 0),
651 "existing node's refresh must still succeed at capacity"
652 );
653 }
654
655 #[test]
660 fn register_if_fresh_tls_does_not_populate_regs() {
661 let mut s = RendezvousServer::new(0);
662 let node = node_id(&[1u8; 32]);
663
664 assert!(
665 s.register_if_fresh_tls(node, 1, 0),
666 "first-seen node on the TLS path is accepted"
667 );
668
669 let out = s.handle(
671 addr("203.0.113.9:52000"),
672 Message::Lookup { node },
673 0,
674 now_secs(0),
675 );
676 assert_eq!(
677 out,
678 vec![(addr("203.0.113.9:52000"), Message::NotFound { node })],
679 "a TLS-only registration must not be visible to UDP Lookup"
680 );
681 assert!(
682 !s.is_registered(&node, 0),
683 "TLS registration must not count as a UDP-servable registration"
684 );
685 }
686
687 #[test]
690 fn register_if_fresh_tls_rejects_stale_and_accepts_advance() {
691 let mut s = RendezvousServer::new(0);
692 let n = node_id(&[2u8; 32]);
693 assert!(s.register_if_fresh_tls(n, 5, 0), "first-seen accepted");
694 assert!(
695 !s.register_if_fresh_tls(n, 5, 1),
696 "equal counter rejected (replay)"
697 );
698 assert!(!s.register_if_fresh_tls(n, 3, 1), "lower counter rejected");
699 assert!(
700 s.register_if_fresh_tls(n, 6, 1),
701 "strictly-greater counter accepted"
702 );
703 }
704
705 #[test]
708 fn handle_ignores_server_bound_only_messages() {
709 let mut s = RendezvousServer::new(0);
710 let n = node_id(&[3u8; 32]);
711 let a = addr("10.0.0.9:40000");
712 assert!(
713 s.handle(
714 a,
715 Message::RelayDeliver {
716 src: n,
717 payload: vec![1, 2, 3]
718 },
719 0,
720 now_secs(0),
721 )
722 .is_empty(),
723 "RelayDeliver is server->client only"
724 );
725 assert!(
726 s.handle(a, Message::NotFound { node: n }, 0, now_secs(0))
727 .is_empty(),
728 "NotFound is server->client only"
729 );
730 }
731
732 #[test]
735 fn rate_window_resets_after_interval() {
736 let mut s = RendezvousServer::new(0);
737 let a = addr("10.0.0.5:40000");
738 let n = node_id(&[4u8; 32]);
739 for _ in 0..MAX_MSGS_PER_WINDOW {
741 s.handle(a, Message::Lookup { node: n }, 0, now_secs(0));
742 }
743 assert!(
744 s.handle(a, Message::Lookup { node: n }, 0, now_secs(0))
745 .is_empty(),
746 "over budget within the window ⇒ dropped"
747 );
748 assert!(
749 !s.handle(
750 a,
751 Message::Lookup { node: n },
752 RATE_WINDOW_MS,
753 now_secs(RATE_WINDOW_MS)
754 )
755 .is_empty(),
756 "window reset after RATE_WINDOW_MS ⇒ reply flows again"
757 );
758 }
759
760 #[test]
761 fn rate_capacity_guard_blocks_new_source_but_services_existing() {
762 let mut s = RendezvousServer::new(0);
763 for i in 0..MAX_RATE_ENTRIES {
767 let idx = u32::try_from(i).expect("index fits u32");
768 s.rates.insert(
769 synth_addr(idx),
770 Rate {
771 window_start_ms: 0,
772 count: 0,
773 },
774 );
775 }
776 assert_eq!(s.rates.len(), MAX_RATE_ENTRIES);
777
778 let a = node_id(&[1u8; 32]);
779
780 let new_src = addr("198.51.100.50:9000");
786 let out = s.handle(new_src, Message::Lookup { node: a }, 0, now_secs(0));
787 assert!(out.is_empty(), "new source over capacity must be dropped");
788 assert_eq!(
789 s.rates.len(),
790 MAX_RATE_ENTRIES,
791 "map must not grow past the cap"
792 );
793
794 let existing_src = synth_addr(0);
797 let out = s.handle(existing_src, Message::Lookup { node: a }, 0, now_secs(0));
798 assert!(
799 !out.is_empty(),
800 "already-tracked source must still be serviced"
801 );
802 }
803
804 fn ca_signing_key() -> SigningKey {
815 SigningKey::from_bytes(&[0x42u8; 32])
816 }
817
818 fn mk_cert(
823 ca: &SigningKey,
824 member_pubkey: [u8; 32],
825 member_sign_pub: [u8; 32],
826 network_id: [u8; 16],
827 ) -> Cert {
828 let mut c = Cert {
829 version: 1,
830 member_pubkey,
831 member_sign_pubkey: member_sign_pub,
832 network_id,
833 not_before: 0,
834 not_after: u64::MAX,
835 tags: vec![],
836 ca_sig: [0u8; 64],
837 };
838 c.ca_sig = ca
839 .sign(&yip_membership::cert::cert_signing_body(&c))
840 .to_bytes();
841 c
842 }
843
844 fn build_signed_record(
848 cert: Cert,
849 endpoints: Vec<SocketAddr>,
850 seq: u64,
851 member_sign_priv: &[u8; 32],
852 ) -> Record {
853 let mut r = Record {
854 node_id: node_id(&cert.member_pubkey),
855 cert,
856 endpoints,
857 seq,
858 sig: [0u8; 64],
859 };
860 let body = yip_membership::record::record_signing_body(&r);
861 r.sig = yip_membership::record::sign(&body, member_sign_priv);
862 r
863 }
864
865 fn valid_registration() -> ([u8; 32], [u8; 16], Record, SocketAddr) {
869 let ca = ca_signing_key();
870 let ca_pub = ca.verifying_key().to_bytes();
871 let network_id = [7u8; 16];
872 let member_pub = [1u8; 32];
873 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
874 let member_sign_pub = member_sign_key.verifying_key().to_bytes();
875 let cert = mk_cert(&ca, member_pub, member_sign_pub, network_id);
876 let member_sign_priv: [u8; 32] = member_sign_key.to_bytes();
877 let rec = build_signed_record(cert, vec![addr("192.0.2.1:9")], 1, &member_sign_priv);
878 (ca_pub, network_id, rec, addr("198.51.100.42:41000"))
879 }
880
881 fn registration_for_other_member(
892 ca_pub: [u8; 32],
893 network_id: [u8; 16],
894 claim_node_id: NodeId,
895 ) -> Record {
896 let ca = ca_signing_key();
897 assert_eq!(
898 ca.verifying_key().to_bytes(),
899 ca_pub,
900 "test helper CA must match the CA `valid_registration` used"
901 );
902 let attacker_pub = [2u8; 32]; let attacker_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
904 let attacker_sign_pub = attacker_sign_key.verifying_key().to_bytes();
905 let cert = mk_cert(&ca, attacker_pub, attacker_sign_pub, network_id);
906 let mut r = Record {
907 node_id: claim_node_id, cert,
909 endpoints: vec![addr("203.0.113.66:9")],
910 seq: 2, sig: [0u8; 64],
912 };
913 let body = yip_membership::record::record_signing_body(&r);
914 let attacker_sign_priv: [u8; 32] = attacker_sign_key.to_bytes();
915 r.sig = yip_membership::record::sign(&body, &attacker_sign_priv);
916 r
917 }
918
919 #[test]
920 fn rooted_server_accepts_valid_signed_register_and_serves_it() {
921 let (ca_pub, network_id, rec, member_src) = valid_registration();
922 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
923 let _ = s.handle(
924 member_src,
925 Message::RegisterSigned {
926 record: rec.clone(),
927 },
928 0,
929 now_secs(0),
930 );
931 let out = s.handle(
932 addr("203.0.113.9:5"),
933 Message::Lookup { node: rec.node_id },
934 10,
935 now_secs(10),
936 );
937 assert!(out.iter().any(|(_, m)| matches!(m,
938 Message::PeerInfo { reflexive, record: Some(r), .. }
939 if *reflexive == member_src && r.node_id == rec.node_id)));
940 }
941
942 #[test]
943 fn rooted_server_rejects_forged_signature() {
944 let (ca_pub, network_id, mut rec, member_src) = valid_registration();
945 rec.sig[0] ^= 0xFF; let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
947 let _ = s.handle(
948 member_src,
949 Message::RegisterSigned {
950 record: rec.clone(),
951 },
952 0,
953 now_secs(0),
954 );
955 let out = s.handle(
957 addr("203.0.113.9:5"),
958 Message::Lookup { node: rec.node_id },
959 10,
960 now_secs(10),
961 );
962 assert!(out
963 .iter()
964 .all(|(_, m)| !matches!(m, Message::PeerInfo { .. })));
965 }
966
967 #[test]
968 fn rooted_server_rejects_overwrite_by_non_holder() {
969 let (ca_pub, network_id, victim, victim_src) = valid_registration();
973 let attacker = registration_for_other_member(ca_pub, network_id, victim.node_id);
974 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
975 let _ = s.handle(
976 victim_src,
977 Message::RegisterSigned {
978 record: victim.clone(),
979 },
980 0,
981 now_secs(0),
982 );
983 let _ = s.handle(
984 addr("203.0.113.66:9"),
985 Message::RegisterSigned { record: attacker },
986 1,
987 now_secs(1),
988 );
989 let out = s.handle(
990 addr("203.0.113.9:5"),
991 Message::Lookup {
992 node: victim.node_id,
993 },
994 2,
995 now_secs(2),
996 );
997 assert!(
998 out.iter()
999 .any(|(_, m)| matches!(m, Message::PeerInfo { reflexive, .. } if *reflexive == victim_src)),
1000 "victim's real registration must survive the overwrite attempt"
1001 );
1002 }
1003
1004 #[test]
1005 fn rooted_server_drops_legacy_unsigned_register() {
1006 let (ca_pub, network_id, _rec, _src) = valid_registration();
1007 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
1008 let _ = s.handle(
1009 addr("203.0.113.66:9"),
1010 Message::Register {
1011 node: [1u8; 16],
1012 counter: 9,
1013 },
1014 0,
1015 now_secs(0),
1016 );
1017 let out = s.handle(
1018 addr("203.0.113.9:5"),
1019 Message::Lookup { node: [1u8; 16] },
1020 1,
1021 now_secs(1),
1022 );
1023 assert!(
1024 out.iter()
1025 .all(|(_, m)| !matches!(m, Message::PeerInfo { .. })),
1026 "a rooted (mesh) server must not accept unsigned registrations"
1027 );
1028 }
1029
1030 #[test]
1031 fn rootless_server_keeps_legacy_register() {
1032 let mut s = RendezvousServer::new(0); let a = [1u8; 16];
1034 let _ = s.handle(
1035 addr("198.51.100.7:41000"),
1036 Message::Register {
1037 node: a,
1038 counter: 1,
1039 },
1040 0,
1041 now_secs(0),
1042 );
1043 let out = s.handle(
1044 addr("203.0.113.9:5"),
1045 Message::Lookup { node: a },
1046 1,
1047 now_secs(1),
1048 );
1049 assert!(out
1050 .iter()
1051 .any(|(_, m)| matches!(m, Message::PeerInfo { .. })));
1052 }
1053
1054 #[test]
1055 fn register_signed_rejects_historical_stale_record() {
1056 let (ca_pub, network_id, _rec, member_src) = valid_registration();
1057 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
1058 let now_s: u64 = 1_700_000_000;
1059 let now_wall_ms = now_s.saturating_mul(1000);
1060 let stale_seq = now_wall_ms.saturating_sub(600_000);
1061
1062 let ca = ca_signing_key();
1064 let member_pub = [1u8; 32];
1065 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
1066 let member_sign_pub = member_sign_key.verifying_key().to_bytes();
1067 let cert = mk_cert(&ca, member_pub, member_sign_pub, network_id);
1068 let member_sign_priv: [u8; 32] = member_sign_key.to_bytes();
1069 let stale_rec = build_signed_record(
1070 cert,
1071 vec![addr("192.0.2.1:9")],
1072 stale_seq,
1073 &member_sign_priv,
1074 );
1075
1076 let out = s.handle(
1077 member_src,
1078 Message::RegisterSigned {
1079 record: stale_rec.clone(),
1080 },
1081 1_000,
1082 now_s,
1083 );
1084 assert!(out.is_empty(), "stale register produces no reply");
1085
1086 let lookup = s.handle(
1087 addr("203.0.113.9:5"),
1088 Message::Lookup {
1089 node: stale_rec.node_id,
1090 },
1091 1_010,
1092 now_s,
1093 );
1094 assert_eq!(
1095 lookup,
1096 vec![(
1097 addr("203.0.113.9:5"),
1098 Message::NotFound {
1099 node: stale_rec.node_id
1100 }
1101 )],
1102 "stale registration must yield NotFound"
1103 );
1104 }
1105
1106 #[test]
1107 fn register_signed_rejects_post_ttl_replay_after_sweep() {
1108 let (ca_pub, network_id, _, _) = valid_registration();
1109 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
1110 let now_s: u64 = 1_700_000_000;
1111 let now_wall_ms = now_s.saturating_mul(1000);
1112
1113 let ca = ca_signing_key();
1115 let member_pub = [1u8; 32];
1116 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
1117 let member_sign_pub = member_sign_key.verifying_key().to_bytes();
1118 let cert = mk_cert(&ca, member_pub, member_sign_pub, network_id);
1119 let member_sign_priv: [u8; 32] = member_sign_key.to_bytes();
1120 let rec = build_signed_record(
1121 cert,
1122 vec![addr("192.0.2.1:9")],
1123 now_wall_ms,
1124 &member_sign_priv,
1125 );
1126
1127 let member_src = addr("198.51.100.42:41000");
1128 let _ = s.handle(
1129 member_src,
1130 Message::RegisterSigned {
1131 record: rec.clone(),
1132 },
1133 0,
1134 now_s,
1135 );
1136
1137 let lookup1 = s.handle(
1138 addr("203.0.113.9:5"),
1139 Message::Lookup { node: rec.node_id },
1140 10,
1141 now_s,
1142 );
1143 assert!(lookup1.iter().any(|(_, m)| matches!(m,
1144 Message::PeerInfo { reflexive, record: Some(r), .. }
1145 if *reflexive == member_src && r.node_id == rec.node_id)));
1146
1147 s.sweep(600_000);
1149 let lookup_swept = s.handle(
1150 addr("203.0.113.9:5"),
1151 Message::Lookup { node: rec.node_id },
1152 600_001,
1153 now_s + 600,
1154 );
1155 assert_eq!(
1156 lookup_swept,
1157 vec![(
1158 addr("203.0.113.9:5"),
1159 Message::NotFound { node: rec.node_id }
1160 )]
1161 );
1162
1163 let attacker_src = addr("203.0.113.66:9");
1165 let out = s.handle(
1166 attacker_src,
1167 Message::RegisterSigned {
1168 record: rec.clone(),
1169 },
1170 600_002,
1171 now_s + 600,
1172 );
1173 assert!(out.is_empty());
1174
1175 let lookup2 = s.handle(
1176 addr("203.0.113.9:5"),
1177 Message::Lookup { node: rec.node_id },
1178 600_003,
1179 now_s + 600,
1180 );
1181 assert_eq!(
1182 lookup2,
1183 vec![(
1184 addr("203.0.113.9:5"),
1185 Message::NotFound { node: rec.node_id }
1186 )],
1187 "replayed registration after sweep must yield NotFound"
1188 );
1189 }
1190
1191 #[test]
1192 fn register_signed_rejects_future_seq_record() {
1193 let (ca_pub, network_id, _rec, member_src) = valid_registration();
1194 let mut s = RendezvousServer::new_with_roots(0, vec![ca_pub], network_id);
1195 let now_s: u64 = 1_700_000_000;
1196 let now_wall_ms = now_s.saturating_mul(1000);
1197 let future_seq = now_wall_ms + 600_000;
1199
1200 let ca = ca_signing_key();
1201 let member_pub = [1u8; 32];
1202 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
1203 let member_sign_pub = member_sign_key.verifying_key().to_bytes();
1204 let cert = mk_cert(&ca, member_pub, member_sign_pub, network_id);
1205 let member_sign_priv: [u8; 32] = member_sign_key.to_bytes();
1206 let future_rec = build_signed_record(
1207 cert,
1208 vec![addr("192.0.2.1:9")],
1209 future_seq,
1210 &member_sign_priv,
1211 );
1212
1213 let out = s.handle(
1214 member_src,
1215 Message::RegisterSigned {
1216 record: future_rec.clone(),
1217 },
1218 1_000,
1219 now_s,
1220 );
1221 assert!(out.is_empty(), "future seq register produces no reply");
1222
1223 let lookup = s.handle(
1224 addr("203.0.113.9:5"),
1225 Message::Lookup {
1226 node: future_rec.node_id,
1227 },
1228 1_010,
1229 now_s,
1230 );
1231 assert_eq!(
1232 lookup,
1233 vec![(
1234 addr("203.0.113.9:5"),
1235 Message::NotFound {
1236 node: future_rec.node_id
1237 }
1238 )],
1239 "future seq record must not be registered"
1240 );
1241 }
1242
1243 #[test]
1244 fn test_server_handle_register_auth() {
1245 let mut s = RendezvousServer::new(0);
1246 let src = addr("198.51.100.7:41000");
1247 let node = [42u8; 16];
1248 let counter = 1234u64;
1249 let mac = [0x99u8; 16];
1250 let msg = Message::RegisterAuth { node, counter, mac };
1251 let out = s.handle(src, msg, 100, 10);
1252 assert!(
1253 out.is_empty(),
1254 "RegisterAuth over UDP must be silently dropped"
1255 );
1256
1257 assert!(!s.is_registered(&node, 100));
1259 let lookup_src = addr("203.0.113.9:52000");
1260 let lookup_out = s.handle(lookup_src, Message::Lookup { node }, 200, 10);
1261 assert_eq!(lookup_out, vec![(lookup_src, Message::NotFound { node })]);
1262 }
1263
1264 #[test]
1265 fn test_server_forwarded_handle() {
1266 let s = RendezvousServer::new(0);
1267 assert_eq!(s.forwarded_count(), 0);
1268 let handle = s.forwarded_handle();
1269 handle.fetch_add(5, Ordering::Relaxed);
1270 assert_eq!(s.forwarded_count(), 5);
1271 s.record_relay_forward();
1272 assert_eq!(s.forwarded_count(), 6);
1273 }
1274
1275 #[test]
1276 fn test_server_roots_cfg_getter() {
1277 let s_rootless = RendezvousServer::new(0);
1278 assert!(s_rootless.roots_cfg().is_none());
1279
1280 let ca_pubs = vec![[7u8; 32]];
1281 let net_id = [9u8; 16];
1282 let s_rooted = RendezvousServer::new_with_roots(0, ca_pubs.clone(), net_id);
1283 let roots = s_rooted.roots_cfg().expect("roots_cfg should be Some");
1284 assert_eq!(roots.0, ca_pubs);
1285 assert_eq!(roots.1, net_id);
1286 }
1287
1288 #[test]
1289 fn test_server_is_registered_boundary() {
1290 let mut s = RendezvousServer::new(0);
1291 let node = [1u8; 16];
1292 let now = 1_000;
1293 s.register_if_fresh(node, 1, addr("192.0.2.1:1234"), now);
1294 assert!(s.is_registered(&node, 60_999));
1296 assert!(!s.is_registered(&node, 61_000));
1297 assert!(!s.is_registered(&node, 61_001));
1298 }
1299
1300 #[test]
1301 fn test_server_sweep() {
1302 let mut s = RendezvousServer::new(0);
1303 let node1 = [1u8; 16];
1304 let node2 = [2u8; 16];
1305 let src = addr("192.0.2.1:1234");
1306 s.register_if_fresh(node1, 1, src, 1_000);
1307 s.register_if_fresh_tls(node2, 1, 1_000);
1308 s.handle(src, Message::Lookup { node: node1 }, 1_000, 1);
1310 assert_eq!(s.rates.len(), 1);
1311 assert_eq!(s.tls_seen.len(), 1);
1312 assert!(s.is_registered(&node1, 1_000));
1313
1314 s.sweep(1_999);
1316 assert_eq!(s.rates.len(), 1); assert_eq!(s.tls_seen.len(), 1);
1318
1319 s.sweep(2_000);
1321 assert_eq!(s.rates.len(), 0);
1322 assert_eq!(s.tls_seen.len(), 1);
1323
1324 s.sweep(60_999);
1326 assert_eq!(s.tls_seen.len(), 1);
1327 assert_eq!(s.regs.len(), 1);
1328
1329 s.sweep(61_000);
1331 assert_eq!(s.tls_seen.len(), 0);
1332 assert_eq!(s.regs.len(), 0);
1333 assert!(!s.is_registered(&node1, 1_000));
1334 }
1335
1336 #[test]
1337 fn test_register_if_fresh_expiry_boundaries() {
1338 let mut s = RendezvousServer::new(0);
1339 let node = [1u8; 16];
1340 let src = addr("192.0.2.1:1234");
1341 assert!(s.register_if_fresh(node, 10, src, 1_000));
1342 assert!(!s.register_if_fresh(node, 10, src, 60_999));
1345 assert!(s.register_if_fresh(node, 10, src, 61_000));
1347
1348 let node_tls = [2u8; 16];
1350 assert!(s.register_if_fresh_tls(node_tls, 10, 1_000));
1351 assert!(!s.register_if_fresh_tls(node_tls, 10, 60_999));
1352 assert!(s.register_if_fresh_tls(node_tls, 10, 61_000));
1353 }
1354
1355 #[test]
1356 fn test_verify_signed_record_boundaries() {
1357 let network_id = [9u8; 16];
1358 let ca = ca_signing_key();
1359 let ca_pub = ca.verifying_key().to_bytes();
1360 let ca_pubs = vec![ca_pub];
1361 let s = RendezvousServer::new_with_roots(0, ca_pubs, network_id);
1362
1363 let member_pub = [1u8; 32];
1364 let member_sign_key = SigningKey::generate(&mut UnwrapErr(SysRng));
1365 let member_sign_pub = member_sign_key.verifying_key().to_bytes();
1366 let cert = mk_cert(&ca, member_pub, member_sign_pub, network_id);
1367 let member_sign_priv: [u8; 32] = member_sign_key.to_bytes();
1368
1369 let now_s = 100_000;
1370 let now_ms = now_s * 1000;
1371 let skew_ms = REGISTRATION_SKEW_SECS * 1000;
1372 let min_seq = now_ms - (REG_TTL_MS + skew_ms);
1373 let max_seq = now_ms + skew_ms;
1374
1375 let rec_min = build_signed_record(
1377 cert.clone(),
1378 vec![addr("192.0.2.1:9")],
1379 min_seq,
1380 &member_sign_priv,
1381 );
1382 assert!(s.verify_signed_record(&rec_min, now_s));
1383
1384 let rec_below = build_signed_record(
1386 cert.clone(),
1387 vec![addr("192.0.2.1:9")],
1388 min_seq - 1,
1389 &member_sign_priv,
1390 );
1391 assert!(!s.verify_signed_record(&rec_below, now_s));
1392
1393 let rec_max = build_signed_record(
1395 cert.clone(),
1396 vec![addr("192.0.2.1:9")],
1397 max_seq,
1398 &member_sign_priv,
1399 );
1400 assert!(s.verify_signed_record(&rec_max, now_s));
1401
1402 let rec_above = build_signed_record(
1404 cert,
1405 vec![addr("192.0.2.1:9")],
1406 max_seq + 1,
1407 &member_sign_priv,
1408 );
1409 assert!(!s.verify_signed_record(&rec_above, now_s));
1410 }
1411
1412 #[test]
1413 fn test_handle_lookup_and_relay_expiry_boundary() {
1414 let mut s = RendezvousServer::new(0);
1415 let node = [1u8; 16];
1416 let src = addr("192.0.2.1:1234");
1417 s.register_if_fresh(node, 1, src, 1_000);
1418 let lookup_ok = s.handle(addr("192.0.2.2:5000"), Message::Lookup { node }, 60_999, 60);
1422 assert!(matches!(
1423 lookup_ok.first(),
1424 Some((_, Message::PeerInfo { .. }))
1425 ));
1426
1427 let relay_ok = s.handle(
1428 addr("192.0.2.2:5000"),
1429 Message::RelaySend {
1430 src: [2u8; 16],
1431 dst: node,
1432 payload: vec![1, 2],
1433 },
1434 60_999,
1435 60,
1436 );
1437 assert_eq!(relay_ok.len(), 1);
1438
1439 let lookup_exp = s.handle(addr("192.0.2.2:5000"), Message::Lookup { node }, 61_000, 61);
1441 assert_eq!(
1442 lookup_exp,
1443 vec![(addr("192.0.2.2:5000"), Message::NotFound { node })]
1444 );
1445
1446 let relay_exp = s.handle(
1447 addr("192.0.2.2:5000"),
1448 Message::RelaySend {
1449 src: [2u8; 16],
1450 dst: node,
1451 payload: vec![1, 2],
1452 },
1453 61_000,
1454 61,
1455 );
1456 assert!(relay_exp.is_empty());
1457 }
1458}