1use std::{net, sync::Arc};
17
18use ana_gotatun::packet::PacketBufPool;
19use reqwest_connect_rpc::token_source::TokenSource;
20use sciparse::{address::ip_socket_addr::ScionSocketIpAddr, identifier::isd_asn::IsdAsn};
21use snap_tun::client::{PACKET_BUF_POOL_SIZE, SnapTunEndpoint};
22use socket2::{Domain, Protocol, Socket, Type};
23use tokio::net::UdpSocket;
24use url::Url;
25use x25519_dalek::StaticSecret;
26
27use crate::{
28 stack::{
29 BoundUnderlaySocket, DynUnderlayStack, InvalidBindAddressError, ScionSocketBindError,
30 SnapConnectionError, UnderlaySocket, builder::PreferredUnderlay,
31 },
32 underlays::{
33 discovery::{UnderlayDiscovery, UnderlayInfo},
34 udp::{OutboundIpResolver, UdpUnderlaySocket},
35 },
36};
37
38pub mod discovery;
39pub mod snap;
40pub mod udp;
41
42pub(crate) struct SnapSocketConfig {
44 pub crpc_client: Option<reqwest::Client>,
46 pub snap_token_source: Option<Arc<dyn TokenSource>>,
49}
50
51pub(crate) struct UnderlayStack {
53 preferred_underlay: PreferredUnderlay,
54 underlay_discovery: Arc<dyn UnderlayDiscovery>,
55 outbound_ip_resolver: Arc<dyn OutboundIpResolver>,
57 snap_socket_config: SnapSocketConfig,
58 snap_tunnel_manager: Option<SnapTunEndpoint>,
59 pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
60}
61
62impl UnderlayStack {
63 pub fn new(
65 preferred_underlay: PreferredUnderlay,
66 underlay_discovery: Arc<dyn UnderlayDiscovery>,
67 outbound_ip_resolver: Arc<dyn OutboundIpResolver>,
68 static_identity: StaticSecret,
69 default_snap_socket_config: SnapSocketConfig,
70 ) -> Self {
71 let snap_tunnel_manager = default_snap_socket_config
72 .snap_token_source
73 .as_ref()
74 .map(|token_source| SnapTunEndpoint::new(token_source.clone(), static_identity));
75 Self {
76 preferred_underlay,
77 underlay_discovery,
78 outbound_ip_resolver,
79 snap_socket_config: default_snap_socket_config,
80 snap_tunnel_manager,
81 pool: PacketBufPool::new(64),
82 }
83 }
84
85 fn select_underlay(&self, requested_isd_as: IsdAsn) -> Option<(IsdAsn, UnderlayInfo)> {
92 let underlays = self.underlay_discovery.underlays(requested_isd_as);
93 match self.preferred_underlay {
94 PreferredUnderlay::Snap => {
95 if let Some(underlay) = underlays
96 .iter()
97 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Snap(_)))
98 {
99 return Some(underlay.clone());
100 }
101 }
102 PreferredUnderlay::Udp => {
103 if let Some(underlay) = underlays
104 .iter()
105 .find(|(_, underlay)| matches!(underlay, UnderlayInfo::Udp(_)))
106 {
107 return Some(underlay.clone());
108 }
109 }
110 }
111 underlays.into_iter().next()
112 }
113
114 async fn bind_snap_socket(
115 &self,
116 requested_addr: Option<ScionSocketIpAddr>,
117 isd_as: IsdAsn,
118 cp_url: Url,
119 ) -> Result<snap::SnapUnderlaySocket, ScionSocketBindError> {
120 let (Some(token_source), Some(snap_tunnel_manager)) = (
121 self.snap_socket_config.snap_token_source.as_ref(),
122 self.snap_tunnel_manager.as_ref(),
123 ) else {
124 return Err(ScionSocketBindError::SnapConnectionError(
125 SnapConnectionError::SnapTokenSourceMissing,
126 ))?;
127 };
128
129 let local_addr = match requested_addr {
130 Some(addr) => addr.socket_addr(),
131 None => {
132 if let Some(cp_addr) = cp_url
133 .socket_addrs(|| None)
134 .ok()
135 .and_then(|addrs| addrs.first().copied())
136 && let Some(ip) = outbound_ip_towards(cp_addr).await
137 {
138 Ok(net::SocketAddr::new(ip, 0))
139 } else {
140 Err(ScionSocketBindError::InvalidBindAddress(
141 InvalidBindAddressError::NoLocalIpAddressFound,
142 ))
143 }?
144 }
145 };
146
147 let bind_addr = ScionSocketIpAddr::new(isd_as, local_addr.ip(), local_addr.port());
148
149 let udp_socket = bind_udp_underlay_socket(local_addr)?;
150
151 let socket = snap::SnapUnderlaySocket::new(
152 bind_addr,
153 cp_url,
154 udp_socket,
155 snap_tunnel_manager,
156 token_source.clone(),
157 1024,
158 self.pool.clone(),
159 self.snap_socket_config.crpc_client.clone(),
160 )
161 .await?;
162
163 let assigned_addr = socket.local_addr();
164
165 if let Some(requested_addr) = requested_addr
168 && requested_addr.isd_asn().matches(assigned_addr.isd_asn())
170 && let requested_socket_addr = requested_addr.socket_addr()
172 && let assigned_socket_addr = assigned_addr.socket_addr()
173 && ((!requested_socket_addr.ip().is_unspecified() && assigned_socket_addr.ip() != requested_socket_addr.ip())
174 || (requested_socket_addr.port() != 0 && assigned_socket_addr.port() != requested_socket_addr.port()))
176 {
177 return Err(crate::stack::ScionSocketBindError::InvalidBindAddress(
180 crate::stack::InvalidBindAddressError::AddressMismatch {
181 assigned_addr: ScionSocketIpAddr::new(
182 bind_addr.isd_asn(),
183 requested_socket_addr.ip(),
184 requested_socket_addr.port(),
185 ),
186 bind_addr,
187 },
188 ));
189 }
190
191 Ok(socket)
192 }
193
194 async fn resolve_udp_bind_addr(
199 &self,
200 isd_as: IsdAsn,
201 override_addr: Option<ScionSocketIpAddr>,
202 ) -> Result<ScionSocketIpAddr, ScionSocketBindError> {
203 if let Some(addr) = override_addr {
204 return Ok(addr);
205 }
206
207 let local_address = *self
210 .outbound_ip_resolver
211 .outbound_ips()
212 .await
213 .first()
214 .ok_or(ScionSocketBindError::InvalidBindAddress(
215 InvalidBindAddressError::NoLocalIpAddressFound,
216 ))?;
217
218 Ok(ScionSocketIpAddr::new(isd_as, local_address, 0))
219 }
220
221 async fn bind_udp_socket(
222 &self,
223 isd_as: IsdAsn,
224 bind_addr: Option<ScionSocketIpAddr>,
225 ) -> Result<(ScionSocketIpAddr, UdpSocket), ScionSocketBindError> {
226 let bind_addr = self.resolve_udp_bind_addr(isd_as, bind_addr).await?;
229 let local_addr: net::SocketAddr = bind_addr.socket_addr();
230
231 let socket = bind_udp_underlay_socket(local_addr)?;
233 let local_addr = socket.local_addr().map_err(|e| {
234 ScionSocketBindError::Other(
235 anyhow::anyhow!("failed to get local address: {e}").into_boxed_dyn_error(),
236 )
237 })?;
238
239 let bind_addr =
242 ScionSocketIpAddr::new(bind_addr.isd_asn(), local_addr.ip(), local_addr.port());
243
244 Ok((bind_addr, socket))
245 }
246}
247
248impl DynUnderlayStack for UnderlayStack {
249 fn bind_socket(
250 &self,
251 _kind: crate::stack::SocketKind,
252 bind_addr: Option<ScionSocketIpAddr>,
253 ) -> futures::future::BoxFuture<'_, Result<BoundUnderlaySocket, ScionSocketBindError>> {
254 Box::pin(async move {
255 let requested_isd_as = bind_addr.map_or(IsdAsn::WILDCARD, |addr| addr.isd_asn());
256 match self.select_underlay(requested_isd_as) {
257 Some((isd_as, UnderlayInfo::Snap(cp_url))) => {
258 let socket = self.bind_snap_socket(bind_addr, isd_as, cp_url).await?;
259 Ok(BoundUnderlaySocket {
260 local_addr: socket.local_addr(),
261 snap_data_plane: socket.snap_data_plane(),
262 socket: Box::new(socket) as Box<dyn UnderlaySocket>,
263 })
264 }
265 Some((isd_as, UnderlayInfo::Udp(_))) => {
266 let (bind_addr, socket) = self.bind_udp_socket(isd_as, bind_addr).await?;
267 Ok(BoundUnderlaySocket {
268 local_addr: bind_addr,
269 snap_data_plane: None,
270 socket: Box::new(UdpUnderlaySocket::new(
271 socket,
272 bind_addr,
273 self.underlay_discovery.clone(),
274 )) as Box<dyn UnderlaySocket>,
275 })
276 }
277 None => {
278 Err(ScionSocketBindError::NoUnderlayAvailable(
279 requested_isd_as.isd(),
280 ))
281 }
282 }
283 })
284 }
285
286 fn local_ases(&self) -> Vec<IsdAsn> {
287 let mut isd_ases: Vec<IsdAsn> = self.underlay_discovery.isd_ases().into_iter().collect();
288 isd_ases.sort();
289 isd_ases
290 }
291}
292
293#[cfg(windows)]
294fn set_exclusive_addr_use(sock: &Socket, enable: bool) -> std::io::Result<()> {
295 use std::{mem, os::windows::io::AsRawSocket};
296
297 use windows_sys::Win32::Networking::WinSock;
298
299 let val: u32 = if enable { 1 } else { 0 };
301
302 let rc = unsafe {
303 WinSock::setsockopt(
304 sock.as_raw_socket() as usize,
305 WinSock::SOL_SOCKET,
306 WinSock::SO_EXCLUSIVEADDRUSE,
307 &val as *const _ as *const _,
308 mem::size_of_val(&val) as _,
309 )
310 };
311
312 if rc == 0 {
313 Ok(())
314 } else {
315 Err(std::io::Error::last_os_error())
316 }
317}
318
319fn bind_udp_underlay_socket(
324 addr: net::SocketAddr,
325) -> Result<tokio::net::UdpSocket, ScionSocketBindError> {
326 let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))
327 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
328 socket
329 .set_nonblocking(true)
330 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
331 if addr.is_ipv6()
332 && let Err(e) = socket.set_only_v6(false)
333 {
334 tracing::debug!(%e, "unable to make socket dual-stack");
335 }
336
337 #[cfg(windows)]
340 set_exclusive_addr_use(&socket, true).map_err(|e| ScionSocketBindError::Other(Box::new(e)))?;
341
342 socket.bind(&addr.into()).map_err(|e| {
343 match e.kind() {
344 std::io::ErrorKind::AddrInUse => ScionSocketBindError::PortAlreadyInUse(addr.port()),
345 std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::InvalidInput => {
346 ScionSocketBindError::InvalidBindAddress(
347 InvalidBindAddressError::CannotBindToRequestedAddress(
348 ScionSocketIpAddr::new(IsdAsn::WILDCARD, addr.ip(), addr.port()),
349 format!("Failed to bind socket: {e:#}").into(),
350 ),
351 )
352 }
353 #[cfg(windows)]
354 std::io::ErrorKind::PermissionDenied => {
358 ScionSocketBindError::PortAlreadyInUse(addr.port())
359 }
360 _ => ScionSocketBindError::Other(Box::new(e)),
361 }
362 })?;
363
364 tokio::net::UdpSocket::from_std(std::net::UdpSocket::from(socket))
365 .map_err(|e| ScionSocketBindError::Other(Box::new(e)))
366}
367
368pub(crate) async fn outbound_ip_towards(dst: net::SocketAddr) -> Option<net::IpAddr> {
370 let bind_addr = match dst.ip() {
371 net::IpAddr::V4(_) => net::Ipv4Addr::UNSPECIFIED.into(),
372 net::IpAddr::V6(_) => net::Ipv6Addr::UNSPECIFIED.into(),
373 };
374 if let Ok(socket) = tokio::net::UdpSocket::bind(net::SocketAddr::new(bind_addr, 0)).await
375 && socket.connect(dst).await.is_ok()
376 && let Ok(addr) = socket.local_addr()
377 {
378 return Some(addr.ip());
379 }
380 None
381}