1use std::{
56 collections::HashMap,
57 net::IpAddr,
58 sync::{
59 Arc, RwLock,
60 atomic::{AtomicUsize, Ordering},
61 },
62 time::{Duration, SystemTime},
63};
64
65use anyhow::Context;
66use http::StatusCode;
67use sciparse::{
68 address::ip_socket_addr::ScionSocketIpAddr,
69 path::{ScionPath, fingerprint::data_plane::DpPathFingerprint},
70};
71use serde::{Deserialize, Serialize};
72use serde_with::{DurationSeconds, serde_as};
73use tokio_util::sync::CancellationToken;
74
75use crate::pg_wap::{
76 paths::{GrantWatch, PathManager, PathSegmentsGuard, UsedPath},
77 sni::WapSNI,
78};
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub struct UplinkKey {
86 pub path_fp: DpPathFingerprint,
88 pub wag: ScionSocketIpAddr,
90}
91
92#[serde_as]
94#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
95#[serde(deny_unknown_fields)]
96pub struct UplinkManagerConfig {
97 #[serde_as(as = "DurationSeconds<u64>")]
99 #[serde(default = "UplinkManagerConfig::default_min_path_lifetime")]
100 pub min_path_lifetime: Duration,
101 #[serde_as(as = "DurationSeconds<u64>")]
103 #[serde(default = "UplinkManagerConfig::default_maintenance_interval")]
104 pub maintenance_interval: Duration,
105}
106
107impl UplinkManagerConfig {
108 fn default_min_path_lifetime() -> Duration {
109 Duration::from_secs(60)
110 }
111
112 fn default_maintenance_interval() -> Duration {
113 Duration::from_secs(10)
114 }
115
116 pub fn validate(&self) -> anyhow::Result<()> {
120 if self.min_path_lifetime == Duration::ZERO {
121 anyhow::bail!("min_path_lifetime must be greater than 0");
122 }
123
124 if self.maintenance_interval == Duration::ZERO {
125 anyhow::bail!("maintenance_interval must be greater than 0");
126 }
127
128 Ok(())
129 }
130}
131
132impl Default for UplinkManagerConfig {
133 fn default() -> Self {
134 Self {
135 min_path_lifetime: Self::default_min_path_lifetime(),
136 maintenance_interval: Self::default_maintenance_interval(),
137 }
138 }
139}
140
141pub struct UplinkManager<Establisher: UplinkEstablisher>(Arc<UplinkManagerInner<Establisher>>);
145
146impl<Establisher: UplinkEstablisher> Clone for UplinkManager<Establisher> {
148 fn clone(&self) -> Self {
149 Self(self.0.clone())
150 }
151}
152
153struct UplinkManagerInner<Establisher: UplinkEstablisher> {
155 uplinks: RwLock<HashMap<UplinkKey, Arc<UplinkEntry<Establisher::Uplink>>>>,
156 establisher: Establisher,
158 paths: PathManager,
159 config: UplinkManagerConfig,
160}
161
162impl<Establisher: UplinkEstablisher> UplinkManager<Establisher> {
163 pub fn new(
167 establisher: Establisher,
168 paths: PathManager,
169 config: UplinkManagerConfig,
170 ) -> anyhow::Result<Self> {
171 config.validate()?;
172
173 Ok(Self(Arc::new(UplinkManagerInner {
174 uplinks: RwLock::new(HashMap::new()),
175 establisher,
176 paths,
177 config,
178 })))
179 }
180}
181
182#[derive(Debug, thiserror::Error)]
187#[error("{source}")]
188pub struct UplinkError {
189 status: Option<StatusCode>,
191 #[source]
192 source: anyhow::Error,
193}
194
195impl UplinkError {
196 pub fn with_status(status: StatusCode, source: impl Into<anyhow::Error>) -> Self {
198 Self {
199 status: Some(status),
200 source: source.into(),
201 }
202 }
203
204 pub fn status(&self) -> Option<StatusCode> {
206 self.status
207 }
208
209 pub fn context(self, context: impl std::fmt::Display + Send + Sync + 'static) -> Self {
211 Self {
212 status: self.status,
213 source: self.source.context(context),
214 }
215 }
216}
217
218impl From<anyhow::Error> for UplinkError {
219 fn from(source: anyhow::Error) -> Self {
220 Self {
221 status: None,
222 source,
223 }
224 }
225}
226
227#[derive(Debug, thiserror::Error)]
229pub enum EstablishStreamError {
230 #[error("Client is not authorized to use the path")]
232 NotAuthorized,
233 #[error("Uplink error: {0}")]
235 UplinkError(UplinkError),
236}
237
238impl EstablishStreamError {
239 pub fn status(&self) -> Option<StatusCode> {
241 match self {
242 Self::NotAuthorized => None,
243 Self::UplinkError(error) => error.status(),
244 }
245 }
246}
247
248impl<Establisher: UplinkEstablisher> UplinkManager<Establisher> {
249 pub fn establisher(&self) -> &Establisher {
251 &self.0.establisher
252 }
253
254 pub async fn establish_stream(
263 &self,
264 client_ip: IpAddr,
265 sni: &WapSNI,
266 used_path: UsedPath,
267 wag: ScionSocketIpAddr,
268 now: SystemTime,
269 ) -> Result<UplinkStreamGuard<Establisher::Uplink>, EstablishStreamError> {
270 let grants = self
273 .0
274 .paths
275 .watch_grants(client_ip, sni.customer_domain(), &used_path, now)
276 .map_err(|_| EstablishStreamError::NotAuthorized)?;
277
278 let reservation = self
281 .reserve_uplink(used_path, wag, now)
282 .await
283 .map_err(EstablishStreamError::UplinkError)?;
284
285 let stream = reservation
286 .entry()
287 .uplink
288 .establish_stream(sni)
289 .await
290 .map_err(EstablishStreamError::UplinkError)?;
291
292 Ok(reservation.attach(stream, grants))
293 }
294
295 async fn reserve_uplink(
301 &self,
302 used_path: UsedPath,
303 wag: ScionSocketIpAddr,
304 now: SystemTime,
305 ) -> Result<UplinkReservation<Establisher::Uplink>, UplinkError> {
306 let key = UplinkKey {
307 path_fp: used_path.path.fingerprint(),
308 wag,
309 };
310
311 {
316 let uplinks = self.0.uplinks.read().unwrap();
317 if let Some(entry) = uplinks.get(&key).filter(|entry| !entry.is_closed()) {
318 return Ok(UplinkReservation::new(entry.clone()));
319 }
320 }
321
322 let segments_guard = self
325 .0
326 .paths
327 .hold_segments(&used_path, now)
328 .await
329 .context("Failed to guard the segments of the uplink path")?;
330
331 let closed = CancellationToken::new();
334 let uplink = self
335 .0
336 .establisher
337 .establish_connection(used_path.path.clone(), wag, closed.clone())
338 .await
339 .map_err(|e| e.context("Failed to establish uplink"))?;
340
341 let entry = Arc::new(UplinkEntry {
342 uplink,
343 established: now,
344 used_path: RwLock::new(used_path),
345 _segments_guard: segments_guard,
346 active_stream_count: AtomicUsize::new(0),
347 closed,
348 });
349
350 let mut replaced = None;
353
354 let mut uplinks = self.0.uplinks.write().unwrap();
355 let entry = match uplinks.entry(key) {
356 std::collections::hash_map::Entry::Occupied(mut occupied)
357 if occupied.get().is_closed() =>
358 {
359 tracing::debug!(?key, "Replacing a closed uplink");
360 replaced = Some(occupied.insert(entry.clone()));
361
362 entry
363 }
364 std::collections::hash_map::Entry::Occupied(occupied) => {
365 tracing::debug!(?key, "Lost the race to establish an uplink, dropping ours");
366 occupied.get().clone()
367 }
368 std::collections::hash_map::Entry::Vacant(vacant) => {
369 tracing::debug!(?key, "Established a new uplink");
370 vacant.insert(entry).clone()
371 }
372 };
373
374 let reservation = UplinkReservation::new(entry);
375
376 drop(uplinks);
377 drop(replaced);
379
380 Ok(reservation)
381 }
382
383 pub async fn run(&self) {
387 loop {
388 self.maintain(SystemTime::now()).await;
389 tokio::time::sleep(self.0.config.maintenance_interval).await;
390 }
391 }
392
393 pub async fn maintain(&self, now: SystemTime) {
395 let uplinks: Vec<(UplinkKey, Arc<UplinkEntry<Establisher::Uplink>>)> = self
397 .0
398 .uplinks
399 .read()
400 .unwrap()
401 .iter()
402 .map(|(key, entry)| (*key, entry.clone()))
403 .collect();
404
405 for (key, entry) in uplinks {
406 if entry.is_closed() {
408 continue;
409 }
410
411 let used_path = entry.used_path();
412
413 let Some(expiry) = used_path.expiration() else {
415 continue;
416 };
417
418 if expiry > now + self.0.config.min_path_lifetime {
419 continue;
420 }
421
422 self.refresh_uplink_path(key, &entry, &used_path, now).await;
423 }
424
425 self.reap();
426 }
427
428 fn reap(&self) {
434 let mut reaped = Vec::new();
435
436 {
437 let mut uplinks = self.0.uplinks.write().unwrap();
438
439 uplinks.retain(|key, entry| {
440 let keep =
441 !entry.is_closed() && entry.active_stream_count.load(Ordering::Relaxed) > 0;
442
443 if !keep {
444 tracing::debug!(
445 ?key,
446 closed = entry.is_closed(),
447 "Removing uplink from the manager"
448 );
449
450 entry.closed.cancel();
451 reaped.push(entry.clone());
452 }
453
454 keep
455 });
456 }
457
458 drop(reaped);
460 }
461
462 async fn refresh_uplink_path(
464 &self,
465 key: UplinkKey,
466 entry: &UplinkEntry<Establisher::Uplink>,
467 used_path: &UsedPath,
468 now: SystemTime,
469 ) {
470 let refreshed = match self.0.paths.refresh_path(used_path, now).await {
471 Ok(refreshed) => refreshed,
472 Err(e) => {
473 tracing::warn!(
474 ?key,
475 ?e,
476 "Failed to refresh path of uplink, keeping old version"
477 );
478 return;
479 }
480 };
481
482 if refreshed.expiration() <= used_path.expiration() {
483 tracing::debug!(
484 ?key,
485 "Refreshed path does not live longer, keeping the old one"
486 );
487 return;
488 }
489
490 if refreshed.path.fingerprint() != key.path_fp {
493 debug_assert!(
494 false,
495 "Refreshed path has a different fingerprint, this should not happen"
496 );
497 tracing::warn!(
498 ?key,
499 "Refreshed path has a different fingerprint, keeping the old one"
500 );
501 return;
502 }
503
504 if let Err(e) = entry.uplink.replace_path(refreshed.path.clone()) {
505 tracing::warn!(?key, "Uplink rejected the refreshed path: {e:#}");
506 return;
507 }
508
509 tracing::debug!(?key, expiry = ?refreshed.expiration(), "Refreshed the path of an uplink");
510
511 *entry.used_path.write().unwrap() = refreshed;
512 }
513}
514
515pub struct UplinkEntry<UplinkType: GenericUplink> {
517 uplink: UplinkType,
518 established: SystemTime,
519 used_path: RwLock<UsedPath>,
521 _segments_guard: PathSegmentsGuard,
526 active_stream_count: AtomicUsize,
527 closed: CancellationToken,
530}
531
532impl<UplinkType: GenericUplink> UplinkEntry<UplinkType> {
533 pub fn used_path(&self) -> UsedPath {
535 self.used_path.read().unwrap().clone()
536 }
537
538 pub fn established(&self) -> SystemTime {
540 self.established
541 }
542
543 pub fn is_closed(&self) -> bool {
545 self.closed.is_cancelled()
546 }
547
548 pub async fn closed(&self) {
550 self.closed.cancelled().await;
551 }
552
553 pub fn uplink(&self) -> &UplinkType {
555 &self.uplink
556 }
557
558 fn release_stream(&self) {
559 let previous = self.active_stream_count.fetch_sub(1, Ordering::Relaxed);
560 debug_assert!(previous > 0, "released a stream that was never reserved");
561 }
562}
563
564impl<UplinkType: GenericUplink> Drop for UplinkEntry<UplinkType> {
565 fn drop(&mut self) {
566 self.closed.cancel();
568 }
569}
570
571struct UplinkReservation<UplinkType: GenericUplink>(Option<Arc<UplinkEntry<UplinkType>>>);
573
574impl<UplinkType: GenericUplink> UplinkReservation<UplinkType> {
575 fn new(entry: Arc<UplinkEntry<UplinkType>>) -> Self {
578 entry.active_stream_count.fetch_add(1, Ordering::Relaxed);
579 Self(Some(entry))
580 }
581
582 fn entry(&self) -> &Arc<UplinkEntry<UplinkType>> {
583 self.0
584 .as_ref()
585 .expect("the reservation is only taken by `attach`, which consumes self")
586 }
587
588 fn attach(
590 mut self,
591 stream: UplinkType::StreamType,
592 grants: GrantWatch,
593 ) -> UplinkStreamGuard<UplinkType> {
594 UplinkStreamGuard {
595 uplink_entry: self.0.take().expect("attach consumes self"),
596 stream: Some(stream),
597 grants,
598 }
599 }
600}
601
602impl<UplinkType: GenericUplink> Drop for UplinkReservation<UplinkType> {
603 fn drop(&mut self) {
604 if let Some(entry) = self.0.take() {
606 entry.release_stream();
607 }
608 }
609}
610
611pub struct UplinkStreamGuard<UplinkType: GenericUplink> {
615 uplink_entry: Arc<UplinkEntry<UplinkType>>,
616 stream: Option<UplinkType::StreamType>,
617 grants: GrantWatch,
619}
620
621impl<UplinkType: GenericUplink> UplinkStreamGuard<UplinkType> {
622 pub async fn grant_expired(&self) {
628 self.grants.expired().await;
629 }
630
631 pub async fn uplink_closed(&self) {
637 self.uplink_entry.closed().await;
638 }
639
640 pub fn take_stream(&mut self) -> Option<UplinkType::StreamType> {
642 self.stream.take()
643 }
644
645 pub fn uplink_entry(&self) -> &Arc<UplinkEntry<UplinkType>> {
647 &self.uplink_entry
648 }
649}
650
651impl<UplinkType: GenericUplink> Drop for UplinkStreamGuard<UplinkType> {
652 fn drop(&mut self) {
653 self.uplink_entry.release_stream();
654 }
655}
656
657#[async_trait::async_trait]
662pub trait UplinkEstablisher: Send + Sync + 'static {
663 type Uplink: GenericUplink;
665
666 async fn establish_connection(
685 &self,
686 path: ScionPath,
687 dst_addr: ScionSocketIpAddr,
688 closed: CancellationToken,
689 ) -> Result<Self::Uplink, UplinkError>;
690}
691
692#[async_trait::async_trait]
697pub trait GenericUplink: Send + Sync + 'static {
698 type StreamType: Send;
700
701 async fn establish_stream(&self, dst_sni: &WapSNI) -> Result<Self::StreamType, UplinkError>;
705
706 fn replace_path(&self, new_path: ScionPath) -> Result<(), UplinkError>;
708}
709
710#[cfg(test)]
711mod tests {
712 use std::sync::Mutex;
713
714 use futures::FutureExt;
715
716 use super::*;
717 use crate::pg_wap::{
718 paths::UsedPath,
719 test_util::{
720 Fixture, IDLE_EVICTION_TIME, MockFetcher, at, client_ip, core_ia, granted_id, leaf_ia,
721 other_client_ip, other_sni, sni, stranger_ip, up_segment, wag,
722 },
723 };
724
725 const ALWAYS_REPATH: Duration = Duration::from_secs(30 * 24 * 3600);
728 const NEVER_REPATH: Duration = Duration::from_secs(60);
730
731 #[derive(Clone, Default)]
736 struct TestUplinks(Arc<Mutex<TestUplinkState>>);
737
738 #[derive(Default)]
740 struct TestUplinkState {
741 log: Vec<UplinkEvent>,
743 close_tokens: Vec<CancellationToken>,
746 failing: Failures,
748 }
749
750 #[derive(Default)]
751 struct Failures {
752 connect: bool,
753 stream: bool,
754 stream_status: Option<StatusCode>,
756 replace_path: bool,
757 }
758
759 #[derive(Debug, PartialEq, Eq)]
760 enum UplinkEvent {
761 Connected(ScionSocketIpAddr),
762 ConnectionRefused,
763 Stream(String),
764 StreamRefused,
765 PathReplaced,
766 PathReplacementRejected,
767 }
768
769 impl TestUplinks {
770 fn with_state<T>(&self, f: impl FnOnce(&mut TestUplinkState) -> T) -> T {
771 f(&mut self.0.lock().unwrap())
772 }
773
774 fn fail_connections(&self, fail: bool) {
776 self.with_state(|state| state.failing.connect = fail);
777 }
778
779 fn fail_streams(&self, fail: bool) {
781 self.with_state(|state| {
782 state.failing.stream = fail;
783 state.failing.stream_status = None;
784 });
785 }
786
787 fn fail_streams_with_status(&self, status: StatusCode) {
789 self.with_state(|state| {
790 state.failing.stream = true;
791 state.failing.stream_status = Some(status);
792 });
793 }
794
795 fn fail_path_replacements(&self, fail: bool) {
797 self.with_state(|state| state.failing.replace_path = fail);
798 }
799
800 fn close(&self, index: usize) {
802 self.with_state(|state| state.close_tokens[index].cancel());
803 }
804
805 fn is_closed(&self, index: usize) -> bool {
807 self.with_state(|state| state.close_tokens[index].is_cancelled())
808 }
809
810 fn count_events(&self, matching: impl Fn(&UplinkEvent) -> bool) -> usize {
811 self.with_state(|state| state.log.iter().filter(|event| matching(event)).count())
812 }
813
814 fn path_replacements(&self) -> usize {
815 self.count_events(|event| matches!(event, UplinkEvent::PathReplaced))
816 }
817
818 fn path_replacement_attempts(&self) -> usize {
819 self.count_events(|event| {
820 matches!(
821 event,
822 UplinkEvent::PathReplaced | UplinkEvent::PathReplacementRejected
823 )
824 })
825 }
826
827 fn connections(&self) -> usize {
828 self.count_events(|event| matches!(event, UplinkEvent::Connected(_)))
829 }
830
831 fn connection_attempts(&self) -> usize {
833 self.count_events(|event| {
834 matches!(
835 event,
836 UplinkEvent::Connected(_) | UplinkEvent::ConnectionRefused
837 )
838 })
839 }
840
841 fn stream_attempts(&self) -> usize {
843 self.count_events(|event| {
844 matches!(event, UplinkEvent::Stream(_) | UplinkEvent::StreamRefused)
845 })
846 }
847
848 fn streamed_snis(&self) -> Vec<String> {
849 self.with_state(|state| {
850 state
851 .log
852 .iter()
853 .filter_map(|event| {
854 match event {
855 UplinkEvent::Stream(sni) => Some(sni.clone()),
856 _ => None,
857 }
858 })
859 .collect()
860 })
861 }
862 }
863
864 #[async_trait::async_trait]
865 impl UplinkEstablisher for TestUplinks {
866 type Uplink = TestUplink;
867
868 async fn establish_connection(
869 &self,
870 _path: ScionPath,
871 dst_addr: ScionSocketIpAddr,
872 closed: CancellationToken,
873 ) -> Result<Self::Uplink, UplinkError> {
874 self.with_state(|state| {
875 if state.failing.connect {
876 state.log.push(UplinkEvent::ConnectionRefused);
877 return Err(anyhow::anyhow!("the test asked connecting to fail").into());
878 }
879
880 state.log.push(UplinkEvent::Connected(dst_addr));
881 state.close_tokens.push(closed);
882 Ok(TestUplink(self.clone()))
883 })
884 }
885 }
886
887 struct TestUplink(TestUplinks);
889
890 #[async_trait::async_trait]
891 impl GenericUplink for TestUplink {
892 type StreamType = ();
893
894 async fn establish_stream(
895 &self,
896 dst_sni: &WapSNI,
897 ) -> Result<Self::StreamType, UplinkError> {
898 self.0.with_state(|state| {
899 if state.failing.stream {
900 state.log.push(UplinkEvent::StreamRefused);
901 let error = anyhow::anyhow!("the test asked streaming to fail");
902
903 return Err(match state.failing.stream_status {
904 Some(status) => UplinkError::with_status(status, error),
905 None => error.into(),
906 });
907 }
908
909 state
910 .log
911 .push(UplinkEvent::Stream(dst_sni.full_domain().to_string()));
912 Ok(())
913 })
914 }
915
916 fn replace_path(&self, _new_path: ScionPath) -> Result<(), UplinkError> {
917 self.0.with_state(|state| {
918 if state.failing.replace_path {
919 state.log.push(UplinkEvent::PathReplacementRejected);
920 return Err(anyhow::anyhow!("the test asked path replacement to fail").into());
921 }
922
923 state.log.push(UplinkEvent::PathReplaced);
924 Ok(())
925 })
926 }
927 }
928
929 type Harness = (Fixture, TestUplinks, UplinkManager<TestUplinks>, UsedPath);
932
933 fn manager_for(
936 fixture: &Fixture,
937 uplinks: TestUplinks,
938 min_path_lifetime: Duration,
939 ) -> UplinkManager<TestUplinks> {
940 UplinkManager::new(
941 uplinks,
942 fixture.paths.clone(),
943 UplinkManagerConfig {
944 min_path_lifetime,
945 ..UplinkManagerConfig::default()
946 },
947 )
948 .expect("a valid UplinkManagerConfig")
949 }
950
951 async fn uplink_fixture() -> Harness {
954 let fixture = Fixture::new(MockFetcher::empty(), Duration::from_secs(100));
955 fixture.grant_non_core(Vec::new(), at(0));
956
957 let uplinks = TestUplinks::default();
958 let manager = manager_for(&fixture, uplinks.clone(), NEVER_REPATH);
959 let used = fixture
960 .paths
961 .best_path(client_ip(), &sni(), leaf_ia(), leaf_ia(), at(0))
962 .await
963 .expect("path combination succeeds")
964 .expect("an AS local path always exists");
965
966 (fixture, uplinks, manager, used)
967 }
968
969 async fn public_path_fixture(min_path_lifetime: Duration) -> Harness {
972 let fixture = Fixture::new(
973 MockFetcher::with_up_segments(vec![up_segment(0)]),
974 Duration::from_secs(10 * 24 * 3600),
975 );
976 fixture.grant_non_core(Vec::new(), at(0));
978
979 let used = fixture
980 .paths
981 .best_path(client_ip(), &sni(), leaf_ia(), core_ia(), at(0))
982 .await
983 .expect("path combination succeeds")
984 .expect("the public segment yields a path");
985
986 let uplinks = TestUplinks::default();
987 let manager = manager_for(&fixture, uplinks.clone(), min_path_lifetime);
988
989 (fixture, uplinks, manager, used)
990 }
991
992 async fn granted_path_fixture(
995 min_path_lifetime: Duration,
996 grant_duration: Duration,
997 ) -> Harness {
998 let fixture = Fixture::new(MockFetcher::empty(), grant_duration);
999 fixture.grant_non_core(vec![up_segment(0)], at(0));
1000
1001 let used = fixture
1002 .paths
1003 .best_path(client_ip(), &sni(), leaf_ia(), core_ia(), at(0))
1004 .await
1005 .expect("path combination succeeds")
1006 .expect("the granted segment yields a path");
1007
1008 let uplinks = TestUplinks::default();
1009 let manager = manager_for(&fixture, uplinks.clone(), min_path_lifetime);
1010
1011 (fixture, uplinks, manager, used)
1012 }
1013
1014 #[tokio::test]
1015 async fn uplinks_are_shared_per_path_and_wag() {
1016 let (fixture, uplinks, manager, used) = uplink_fixture().await;
1017 fixture.grant_target(client_ip(), other_sni().customer_domain(), at(0));
1018
1019 let first = manager
1020 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1021 .await
1022 .expect("the first stream is established");
1023 let second = manager
1024 .establish_stream(
1025 client_ip(),
1026 &other_sni(),
1027 used.clone(),
1028 wag(leaf_ia()),
1029 at(0),
1030 )
1031 .await
1032 .expect("the second stream is established");
1033
1034 assert_eq!(
1035 uplinks.connections(),
1036 1,
1037 "the same (path, WAG) pair must share one uplink"
1038 );
1039 assert_eq!(
1040 uplinks.streamed_snis(),
1041 vec![sni().to_string(), other_sni().to_string()],
1042 "both SNIs get their own stream on that uplink"
1043 );
1044 assert!(std::ptr::eq(
1045 Arc::as_ptr(first.uplink_entry()),
1046 Arc::as_ptr(second.uplink_entry())
1047 ));
1048
1049 let other_wag = manager
1051 .establish_stream(client_ip(), &sni(), used, wag(core_ia()), at(0))
1052 .await
1053 .expect("the stream to the other WAG is established");
1054 assert_eq!(uplinks.connections(), 2);
1055 assert!(!std::ptr::eq(
1056 Arc::as_ptr(first.uplink_entry()),
1057 Arc::as_ptr(other_wag.uplink_entry())
1058 ));
1059 }
1060
1061 #[tokio::test]
1062 async fn uplinks_without_streams_are_reaped() {
1063 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1064
1065 let stream = manager
1066 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1067 .await
1068 .expect("the stream is established");
1069
1070 manager.maintain(at(0)).await;
1072 let second = manager
1073 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1074 .await
1075 .expect("the second stream is established");
1076 assert_eq!(uplinks.connections(), 1);
1077
1078 drop(stream);
1079 drop(second);
1080
1081 manager.maintain(at(0)).await;
1082 manager
1083 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1084 .await
1085 .expect("a new stream is established");
1086 assert_eq!(
1087 uplinks.connections(),
1088 2,
1089 "the reaped uplink has to be established again"
1090 );
1091 }
1092
1093 async fn shared_uplink_fixture() -> Harness {
1095 let fixture = Fixture::new(MockFetcher::empty(), Duration::from_secs(100));
1096 fixture.grant_non_core(vec![up_segment(0)], at(0));
1097 fixture.grant_non_core_to(other_client_ip(), vec![up_segment(0)], at(0));
1098
1099 let uplinks = TestUplinks::default();
1100 let manager = manager_for(&fixture, uplinks.clone(), ALWAYS_REPATH);
1101
1102 let used = fixture
1103 .paths
1104 .best_path(client_ip(), &sni(), leaf_ia(), core_ia(), at(0))
1105 .await
1106 .expect("path combination succeeds")
1107 .expect("the granted segment yields a path");
1108
1109 (fixture, uplinks, manager, used)
1110 }
1111
1112 #[tokio::test]
1113 async fn a_shared_uplink_outlives_the_grant_of_the_client_that_established_it() {
1114 let (fixture, uplinks, manager, used) = shared_uplink_fixture().await;
1115
1116 let first = manager
1117 .establish_stream(client_ip(), &sni(), used.clone(), wag(core_ia()), at(0))
1118 .await
1119 .expect("the first client's stream is established");
1120 let second = manager
1121 .establish_stream(
1122 other_client_ip(),
1123 &sni(),
1124 used.clone(),
1125 wag(core_ia()),
1126 at(0),
1127 )
1128 .await
1129 .expect("the second client's stream is established");
1130 assert_eq!(uplinks.connections(), 1, "both clients share one uplink");
1131
1132 fixture.grant_non_core_to(other_client_ip(), vec![up_segment(600)], at(90));
1134 fixture.grant.clean(at(101));
1135
1136 assert!(
1139 fixture
1140 .grant
1141 .segment_grant_expiry(
1142 client_ip(),
1143 sni().customer_domain(),
1144 &granted_id(&up_segment(0)),
1145 at(101)
1146 )
1147 .is_none()
1148 );
1149 assert!(
1150 fixture
1151 .grant
1152 .segment(&granted_id(&up_segment(0)), at(101))
1153 .is_some(),
1154 "a segment stays available while any client is granted it"
1155 );
1156
1157 assert!(
1160 first.grant_expired().now_or_never().is_some(),
1161 "the client whose grant lapsed must observe it"
1162 );
1163 assert!(
1164 second.grant_expired().now_or_never().is_none(),
1165 "the client that refreshed must keep its stream"
1166 );
1167
1168 manager.maintain(at(101)).await;
1171 assert_eq!(
1172 uplinks.path_replacements(),
1173 1,
1174 "the uplink is re-pathed off a grant it was not established with"
1175 );
1176 assert_eq!(
1177 second.uplink_entry().used_path().expiration(),
1178 used.expiration().map(|old| old + Duration::from_secs(600))
1179 );
1180 assert_eq!(
1181 uplinks.connections(),
1182 1,
1183 "re-pathing must not reconnect the uplink"
1184 );
1185 }
1186
1187 #[tokio::test]
1188 async fn a_stream_is_refused_without_a_grant_for_the_paths_segments() {
1189 let (fixture, uplinks, manager, used) = shared_uplink_fixture().await;
1190
1191 fixture.grant_target(stranger_ip(), sni().customer_domain(), at(0));
1193
1194 assert!(
1195 manager
1196 .establish_stream(stranger_ip(), &sni(), used, wag(core_ia()), at(0))
1197 .await
1198 .is_err(),
1199 "a client without a grant on the path's segments must not get a stream on it"
1200 );
1201 assert_eq!(
1202 uplinks.connection_attempts(),
1203 0,
1204 "and no uplink is established for it"
1205 );
1206 }
1207
1208 #[tokio::test]
1209 async fn a_stream_is_refused_for_an_ip_without_any_authorization() {
1210 let (fixture, uplinks, manager, used) = uplink_fixture().await;
1211
1212 assert!(!fixture.grant.ip_is_authorized(stranger_ip(), at(0)));
1213 assert!(
1214 manager
1215 .establish_stream(stranger_ip(), &sni(), used, wag(leaf_ia()), at(0))
1216 .await
1217 .is_err(),
1218 "an unauthenticated client must not get a stream"
1219 );
1220 assert_eq!(
1221 uplinks.connection_attempts(),
1222 0,
1223 "and must not cause any dataplane work"
1224 );
1225 }
1226
1227 #[tokio::test]
1228 async fn a_stream_is_refused_once_the_clients_grant_has_expired() {
1229 let (fixture, uplinks, manager, used) = uplink_fixture().await;
1230
1231 assert!(used.segments.is_empty());
1234
1235 assert!(
1238 manager
1239 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(101))
1240 .await
1241 .is_err(),
1242 "a lapsed grant does not authorize a stream, swept or not"
1243 );
1244
1245 fixture.grant.clean(at(101));
1246 assert!(
1247 manager
1248 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(101))
1249 .await
1250 .is_err(),
1251 "and still does not once it has been swept"
1252 );
1253 assert_eq!(
1254 uplinks.connection_attempts(),
1255 0,
1256 "an unauthorized client must not cause any dataplane work"
1257 );
1258 }
1259
1260 #[tokio::test]
1261 async fn an_uplink_is_reused_by_a_stream_that_arrives_before_the_reaper() {
1262 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1263
1264 let stream = manager
1265 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1266 .await
1267 .expect("the stream is established");
1268 let entry = stream.uplink_entry().clone();
1269
1270 drop(stream);
1273 let second = manager
1274 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1275 .await
1276 .expect("the second stream is established");
1277
1278 assert_eq!(
1279 uplinks.connections(),
1280 1,
1281 "an idle uplink is reused rather than replaced"
1282 );
1283 assert!(std::ptr::eq(
1284 Arc::as_ptr(&entry),
1285 Arc::as_ptr(second.uplink_entry())
1286 ));
1287
1288 manager.maintain(at(0)).await;
1290 assert!(
1291 !second.uplink_entry().is_closed(),
1292 "an uplink that was picked up again must survive the reaper"
1293 );
1294 }
1295
1296 #[tokio::test]
1297 async fn a_closed_uplink_tears_down_its_streams_and_is_not_reused() {
1298 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1299
1300 let stream = manager
1301 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1302 .await
1303 .expect("the stream is established");
1304 assert_eq!(uplinks.connections(), 1);
1305
1306 assert!(stream.uplink_closed().now_or_never().is_none());
1308
1309 uplinks.close(0);
1311
1312 assert!(
1313 stream.uplink_closed().now_or_never().is_some(),
1314 "every stream on a closed uplink has to be told to tear down"
1315 );
1316
1317 let replacement = manager
1320 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1321 .await
1322 .expect("the stream is established on a new uplink");
1323 assert_eq!(uplinks.connections(), 2, "the closed uplink is replaced");
1324 assert!(!std::ptr::eq(
1325 Arc::as_ptr(stream.uplink_entry()),
1326 Arc::as_ptr(replacement.uplink_entry())
1327 ));
1328 assert!(
1329 !replacement.uplink_entry().is_closed(),
1330 "the replacement is usable"
1331 );
1332 }
1333
1334 #[tokio::test]
1335 async fn a_closed_uplink_is_reaped_even_though_it_still_has_streams() {
1336 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1337
1338 let stream = manager
1339 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1340 .await
1341 .expect("the stream is established");
1342
1343 uplinks.close(0);
1344 manager.maintain(at(0)).await;
1345
1346 let replacement = manager
1348 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1349 .await
1350 .expect("the stream is established");
1351 assert_eq!(
1352 uplinks.connections(),
1353 2,
1354 "the reaped uplink has to be established again"
1355 );
1356 assert!(!std::ptr::eq(
1357 Arc::as_ptr(stream.uplink_entry()),
1358 Arc::as_ptr(replacement.uplink_entry())
1359 ));
1360 }
1361
1362 #[tokio::test]
1363 async fn reaping_an_unused_uplink_tells_its_holders_it_is_gone() {
1364 let (_fixture, _uplinks, manager, used) = uplink_fixture().await;
1365
1366 let stream = manager
1367 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1368 .await
1369 .expect("the stream is established");
1370 let entry = stream.uplink_entry().clone();
1371
1372 drop(stream);
1375 manager.maintain(at(0)).await;
1376
1377 assert!(
1378 entry.is_closed(),
1379 "a reaped uplink counts as unusable for anything still holding it"
1380 );
1381 }
1382
1383 #[tokio::test]
1384 async fn a_live_uplink_holds_the_segments_of_its_path() {
1385 let (fixture, _uplinks, manager, used) = public_path_fixture(NEVER_REPATH).await;
1386
1387 let stream = manager
1388 .establish_stream(client_ip(), &sni(), used.clone(), wag(core_ia()), at(0))
1389 .await
1390 .expect("the stream is established");
1391
1392 let held_at = at(121);
1395 fixture.segments.maintain(held_at).await;
1396
1397 assert_eq!(
1398 fixture.fetcher.calls(),
1399 1,
1400 "the guarded pair was never evicted, so it never had to be fetched again"
1401 );
1402 assert!(
1403 fixture.paths.refresh_path(&used, held_at).await.is_ok(),
1404 "the segments of a live uplink must stay resolvable"
1405 );
1406
1407 drop(stream);
1409 manager.maintain(held_at).await;
1410
1411 let idle_at = at(242);
1412 fixture.segments.maintain(idle_at).await;
1413 fixture
1414 .segments
1415 .segments(leaf_ia(), core_ia(), idle_at)
1416 .await
1417 .expect("segments are fetched again");
1418 assert_eq!(
1419 fixture.fetcher.calls(),
1420 2,
1421 "an unheld pair is evicted again once it goes idle"
1422 );
1423 }
1424
1425 #[tokio::test]
1426 async fn uplinks_are_re_pathed_before_their_path_expires() {
1427 let (fixture, uplinks, manager, used) =
1429 granted_path_fixture(ALWAYS_REPATH, Duration::from_secs(10 * 24 * 3600)).await;
1430 let expiry = used
1431 .expiration()
1432 .expect("a combined path has an expiration");
1433
1434 let stream = manager
1435 .establish_stream(client_ip(), &sni(), used, wag(core_ia()), at(0))
1436 .await
1437 .expect("the stream is established");
1438
1439 manager.maintain(at(0)).await;
1441 assert_eq!(uplinks.path_replacements(), 0);
1442
1443 fixture.grant_non_core(vec![up_segment(600)], at(0));
1445 manager.maintain(at(0)).await;
1446
1447 assert_eq!(
1448 uplinks.path_replacements(),
1449 1,
1450 "the uplink is given the newer path"
1451 );
1452 assert_eq!(
1453 stream.uplink_entry().used_path().expiration(),
1454 Some(expiry + Duration::from_secs(600))
1455 );
1456 assert_eq!(
1457 uplinks.connections(),
1458 1,
1459 "re-pathing must not reconnect the uplink"
1460 );
1461 }
1462
1463 #[tokio::test]
1464 async fn an_uplink_that_rejects_a_refreshed_path_keeps_the_one_it_has() {
1465 let (fixture, uplinks, manager, used) =
1466 granted_path_fixture(ALWAYS_REPATH, Duration::from_secs(10 * 24 * 3600)).await;
1467 let expiry = used
1468 .expiration()
1469 .expect("a combined path has an expiration");
1470
1471 let stream = manager
1472 .establish_stream(client_ip(), &sni(), used, wag(core_ia()), at(0))
1473 .await
1474 .expect("the stream is established");
1475
1476 fixture.grant_non_core(vec![up_segment(600)], at(0));
1478 uplinks.fail_path_replacements(true);
1479 manager.maintain(at(0)).await;
1480
1481 assert_eq!(
1482 uplinks.path_replacement_attempts(),
1483 1,
1484 "the refresh was offered"
1485 );
1486 assert_eq!(uplinks.path_replacements(), 0, "and was rejected");
1487 assert_eq!(
1488 stream.uplink_entry().used_path().expiration(),
1489 Some(expiry),
1490 "the uplink must keep sending over the path it still has"
1491 );
1492 assert!(
1493 !stream.uplink_entry().is_closed(),
1494 "a rejected refresh is not fatal to the uplink"
1495 );
1496
1497 uplinks.fail_path_replacements(false);
1499 manager.maintain(at(0)).await;
1500 assert_eq!(uplinks.path_replacements(), 1);
1501 assert_eq!(
1502 stream.uplink_entry().used_path().expiration(),
1503 Some(expiry + Duration::from_secs(600))
1504 );
1505 }
1506
1507 #[tokio::test]
1508 async fn an_uplink_whose_segments_are_gone_keeps_the_path_it_has() {
1509 let (fixture, uplinks, manager, used) =
1510 granted_path_fixture(ALWAYS_REPATH, Duration::from_secs(100)).await;
1511 let expiry = used
1512 .expiration()
1513 .expect("a combined path has an expiration");
1514
1515 let stream = manager
1516 .establish_stream(client_ip(), &sni(), used.clone(), wag(core_ia()), at(0))
1517 .await
1518 .expect("the stream is established");
1519
1520 fixture.grant.clean(at(101));
1522 assert!(fixture.paths.refresh_path(&used, at(101)).await.is_err());
1523
1524 manager.maintain(at(101)).await;
1525
1526 assert_eq!(
1527 uplinks.path_replacement_attempts(),
1528 0,
1529 "there is no path to offer the uplink"
1530 );
1531 assert_eq!(
1532 stream.uplink_entry().used_path().expiration(),
1533 Some(expiry),
1534 "a failed refresh leaves the uplink on its current path"
1535 );
1536 assert!(
1537 !stream.uplink_entry().is_closed(),
1538 "the uplink is only torn down once nothing streams over it"
1539 );
1540 }
1541
1542 #[tokio::test]
1543 async fn a_failed_stream_releases_its_reservation_on_the_uplink() {
1544 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1545
1546 uplinks.fail_streams(true);
1547 assert!(
1548 manager
1549 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1550 .await
1551 .is_err(),
1552 "the client gets no stream if the uplink cannot open one"
1553 );
1554 assert_eq!(
1555 uplinks.connections(),
1556 1,
1557 "the uplink itself was established before the stream failed"
1558 );
1559 assert_eq!(uplinks.stream_attempts(), 1);
1560
1561 manager.maintain(at(0)).await;
1564 assert!(
1565 uplinks.is_closed(0),
1566 "an uplink whose only stream failed has to be reaped"
1567 );
1568
1569 uplinks.fail_streams(false);
1570 manager
1571 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1572 .await
1573 .expect("the next stream is established");
1574 assert_eq!(
1575 uplinks.connections(),
1576 2,
1577 "the reaped uplink had to be established again"
1578 );
1579 }
1580
1581 #[tokio::test]
1582 async fn a_refused_stream_reports_the_status_of_the_wag() {
1583 let (_fixture, uplinks, manager, used) = uplink_fixture().await;
1584
1585 uplinks.fail_streams_with_status(StatusCode::FORBIDDEN);
1586 let error = manager
1587 .establish_stream(client_ip(), &sni(), used.clone(), wag(leaf_ia()), at(0))
1588 .await
1589 .err()
1590 .expect("the uplink refused the stream");
1591 assert_eq!(
1592 error.status(),
1593 Some(StatusCode::FORBIDDEN),
1594 "the status of the WAG reaches the caller"
1595 );
1596
1597 uplinks.fail_streams(true);
1598 let error = manager
1599 .establish_stream(client_ip(), &sni(), used, wag(leaf_ia()), at(0))
1600 .await
1601 .err()
1602 .expect("the uplink refused the stream");
1603 assert_eq!(
1604 error.status(),
1605 None,
1606 "a failure without an answer from the WAG carries no status"
1607 );
1608 }
1609
1610 #[tokio::test]
1611 async fn a_failed_uplink_releases_the_segments_of_its_path() {
1612 let (fixture, uplinks, manager, used) = public_path_fixture(NEVER_REPATH).await;
1613
1614 uplinks.fail_connections(true);
1615 assert!(
1616 manager
1617 .establish_stream(client_ip(), &sni(), used.clone(), wag(core_ia()), at(0))
1618 .await
1619 .is_err(),
1620 "the client gets no stream if the uplink cannot be established"
1621 );
1622 assert_eq!(
1623 uplinks.connection_attempts(),
1624 1,
1625 "the connection was attempted"
1626 );
1627
1628 let idle_at = at(IDLE_EVICTION_TIME.as_secs() * 2 + 1);
1631 fixture.segments.maintain(idle_at).await;
1632 fixture
1633 .segments
1634 .segments(leaf_ia(), core_ia(), idle_at)
1635 .await
1636 .expect("segments are fetched again");
1637 assert_eq!(
1638 fixture.fetcher.calls(),
1639 2,
1640 "the pair of a failed uplink must not stay held"
1641 );
1642
1643 uplinks.fail_connections(false);
1645 manager
1646 .establish_stream(client_ip(), &sni(), used, wag(core_ia()), idle_at)
1647 .await
1648 .expect("the next stream is established");
1649 assert_eq!(uplinks.connections(), 1);
1650 }
1651
1652 #[tokio::test]
1653 async fn a_stream_is_refused_when_the_paths_segments_cannot_be_held() {
1654 let (fixture, uplinks, manager, used) = public_path_fixture(NEVER_REPATH).await;
1655
1656 let evicted_at = at(IDLE_EVICTION_TIME.as_secs() + 1);
1658 fixture.segments.maintain(evicted_at).await;
1659 fixture.fetcher.set_failing(true);
1660
1661 assert!(
1662 manager
1663 .establish_stream(client_ip(), &sni(), used, wag(core_ia()), evicted_at)
1664 .await
1665 .is_err(),
1666 "an uplink whose segments cannot be kept available must not be established"
1667 );
1668 assert_eq!(
1669 uplinks.connection_attempts(),
1670 0,
1671 "the segments are secured before anything is connected"
1672 );
1673 }
1674}