1use std::{net::SocketAddr, sync::Arc, time::Duration};
17
18use axum::{BoxError, Router, error_handling::HandleErrorLayer};
19use endhost_api::routes::nest_endhost_api;
20use endhost_api_models::{
21 PathDiscovery,
22 underlays::{ScionRouter, Underlays},
23};
24use http::StatusCode;
25use jsonwebtoken::DecodingKey;
26use scion_proto::address::IsdAsn;
27use scion_sdk_observability::info_trace_layer;
28use tokio::net::TcpListener;
29use tokio_util::sync::CancellationToken;
30use tower::{ServiceBuilder, timeout::TimeoutLayer};
31use url::Url;
32
33use crate::{
34 crpc_api::api_service::{
35 model::{SnapDataPlaneResolver, SnapTunIdentityRegistry},
36 nest_snap_control_api,
37 },
38 model::UnderlayDiscovery,
39 server::{
40 auth::AuthMiddlewareLayer,
41 metrics::{Metrics, PrometheusMiddlewareLayer},
42 },
43};
44
45pub mod auth;
46pub mod identity_registry;
47pub mod metrics;
48pub mod mock_segment_lister;
49pub mod state;
50
51const CONTROL_PLANE_API_TIMEOUT: Duration = Duration::from_secs(30);
52
53const CONTROL_PLANE_RATE_LIMIT: u64 = 20;
55const CONTROL_PLANE_RATE_LIMIT_PERIOD: Duration = Duration::from_secs(1);
56
57pub async fn start<UD, SL, SR, IR>(
59 cancellation_token: CancellationToken,
60 listener: TcpListener,
61 underlay_discovery: UD,
62 segment_lister: SL,
63 snap_resolver: SR,
64 identity_registry: Arc<IR>,
65 snap_token_decoding_key: DecodingKey,
66 metrics: Metrics,
67) -> std::io::Result<()>
68where
69 UD: UnderlayDiscovery + 'static + Send + Sync,
70 SL: PathDiscovery + 'static + Send + Sync,
71 SR: SnapDataPlaneResolver + 'static + Send + Sync,
72 IR: SnapTunIdentityRegistry + 'static + Send + Sync,
73{
74 let router = Router::new();
75
76 let dp_discovery = Arc::new(underlay_discovery);
77 let segment_lister = Arc::new(segment_lister);
78 let snap_resolver = Arc::new(snap_resolver);
79
80 let snap_cp_addr = listener
81 .local_addr()
82 .map_err(|e| std::io::Error::other(format!("Failed to get own local address: {e}")))?;
83
84 let snap_cp_api = match snap_cp_addr {
85 SocketAddr::V4(addr) => {
86 Url::parse(&format!("http://{addr}"))
87 .expect("It is safe to format a SocketAddr as a URL")
88 }
89 SocketAddr::V6(addr) => {
90 Url::parse(&format!("http://[{}]:{}", addr.ip(), addr.port()))
91 .expect("It is safe to format a SocketAddr as a URL")
92 }
93 };
94
95 let router = nest_endhost_api(
96 router,
97 Arc::new(UnderlayDiscoveryAdapter::new(
98 dp_discovery.clone(),
99 snap_cp_api,
100 )),
101 segment_lister.clone(),
102 );
103
104 let router = nest_snap_control_api(router, snap_resolver, identity_registry);
105
106 let router = router.layer(
107 ServiceBuilder::new()
108 .layer(HandleErrorLayer::new(|err: BoxError| {
109 async move {
110 tracing::error!(error=%err, "Control plane API error");
111
112 (
113 StatusCode::INTERNAL_SERVER_ERROR,
114 format!("Unhandled error: {err}"),
115 )
116 }
117 }))
118 .layer(info_trace_layer())
119 .layer(TimeoutLayer::new(CONTROL_PLANE_API_TIMEOUT))
120 .layer(tower::buffer::BufferLayer::new(1024))
121 .layer(tower::limit::RateLimitLayer::new(
122 CONTROL_PLANE_RATE_LIMIT,
123 CONTROL_PLANE_RATE_LIMIT_PERIOD,
124 ))
125 .layer(PrometheusMiddlewareLayer::new(metrics))
126 .layer(AuthMiddlewareLayer::new(snap_token_decoding_key)),
127 );
128
129 tracing::info!(addr=%snap_cp_addr, "Starting control plane API");
130
131 if let Err(e) = axum::serve(
132 listener,
133 router.into_make_service_with_connect_info::<SocketAddr>(),
134 )
135 .with_graceful_shutdown(cancellation_token.cancelled_owned())
136 .await
137 {
138 tracing::error!(error=%e, "Control plane API server unexpectedly stopped");
139 }
140
141 tracing::info!("Shutting down control plane API server");
142
143 Ok(())
144}
145
146struct UnderlayDiscoveryAdapter<T: UnderlayDiscovery> {
148 underlay_discovery: Arc<T>,
149 snap_cp_api: Url,
150}
151
152impl<T: UnderlayDiscovery> UnderlayDiscoveryAdapter<T> {
153 fn new(underlay_discovery: Arc<T>, snap_cp_api: Url) -> Self {
154 Self {
155 underlay_discovery,
156 snap_cp_api,
157 }
158 }
159}
160
161impl<T: UnderlayDiscovery> endhost_api_models::UnderlayDiscovery for UnderlayDiscoveryAdapter<T> {
162 fn list_underlays(&self, isd_as: IsdAsn) -> Underlays {
163 let dps = self.underlay_discovery.list_udp_underlays();
164 let mut udp_underlay = Vec::new();
165 for dp in dps {
166 for router_as in dp.isd_ases {
167 if isd_as != IsdAsn::WILDCARD && router_as.isd_as != isd_as {
168 continue;
169 };
170
171 udp_underlay.push(ScionRouter {
172 isd_as: router_as.isd_as,
173 internal_interface: dp.endpoint,
174 interfaces: router_as.interfaces.clone(),
175 });
176 }
177 }
178
179 let sus = self.underlay_discovery.list_snap_underlays();
180 if sus.is_empty() {
181 return Underlays {
182 udp_underlay,
183 snap_underlay: Vec::new(),
184 };
185 }
186
187 let mut snap_underlay = Vec::new();
188 let all_ases: Vec<IsdAsn> = sus.iter().flat_map(|su| su.isd_ases.clone()).collect();
189 if isd_as == IsdAsn::WILDCARD || all_ases.contains(&isd_as) {
190 snap_underlay.push(endhost_api_models::underlays::Snap {
191 address: self.snap_cp_api.clone(),
192 isd_ases: all_ases,
193 });
194 }
195
196 Underlays {
197 udp_underlay,
198 snap_underlay,
199 }
200 }
201}