1use std::{
16 io,
17 net::{self},
18 sync::Arc,
19};
20
21use async_trait::async_trait;
22use sciparse::{
23 address::ip_socket_addr::ScionSocketIpAddr,
24 core::view::View,
25 dataplane_path::view::ScionDpPathViewExt,
26 identifier::isd_asn::IsdAsn,
27 packet::view::{ScionPacketView, ScionRawPacketView},
28 path::metadata::path_interface::PathInterface,
29};
30use tokio::net::UdpSocket;
31
32use crate::{
33 stack::{ScionSocketReceiveError, ScionSocketSendError, UnderlaySocket},
34 underlays::{discovery::UnderlayDiscovery, outbound_ip_towards},
35};
36
37#[async_trait::async_trait]
39pub trait OutboundIpResolver: Send + Sync {
40 async fn outbound_ips(&self) -> Vec<net::IpAddr>;
42}
43
44#[async_trait::async_trait]
45impl OutboundIpResolver for Vec<net::IpAddr> {
46 async fn outbound_ips(&self) -> Vec<net::IpAddr> {
47 self.clone()
48 }
49}
50
51pub struct TargetAddrOutboundIpResolver {
58 url: url::Url,
59 host_overrides: Vec<(String, net::IpAddr)>,
60}
61
62impl TargetAddrOutboundIpResolver {
63 pub fn new(url: url::Url, host_overrides: Vec<(String, net::IpAddr)>) -> Self {
69 Self {
70 url,
71 host_overrides,
72 }
73 }
74}
75
76#[async_trait::async_trait]
77impl OutboundIpResolver for TargetAddrOutboundIpResolver {
78 async fn outbound_ips(&self) -> Vec<net::IpAddr> {
85 let url = &self.url;
86 let host = match url.host_str() {
87 Some(h) => h,
88 None => return vec![],
89 };
90 let port = url.port_or_known_default().unwrap_or(0);
91
92 let target = if let Some((_, ip)) = self.host_overrides.iter().find(|(h, _)| h == host) {
93 net::SocketAddr::new(*ip, port)
94 } else if let Ok(ip) = host.parse::<net::IpAddr>() {
95 net::SocketAddr::new(ip, port)
96 } else {
97 match url
98 .socket_addrs(|| None)
99 .ok()
100 .and_then(|addrs| addrs.into_iter().next())
101 {
102 Some(addr) => addr,
103 None => {
104 tracing::warn!(url = %url, "failed to resolve API URL for local IP detection");
105 return vec![];
106 }
107 }
108 };
109
110 match outbound_ip_towards(target).await {
111 Some(ip) => vec![ip],
112 None => vec![],
113 }
114 }
115}
116
117pub(crate) struct UdpUnderlaySocket {
119 pub(crate) socket: UdpSocket,
120 pub(crate) bind_addr: ScionSocketIpAddr,
121 pub(crate) underlay_discovery: Arc<dyn UnderlayDiscovery>,
122}
123
124impl UdpUnderlaySocket {
125 pub(crate) fn new(
126 socket: UdpSocket,
127 bind_addr: ScionSocketIpAddr,
128 underlay_discovery: Arc<dyn UnderlayDiscovery>,
129 ) -> Self {
130 Self {
131 socket,
132 bind_addr,
133 underlay_discovery,
134 }
135 }
136
137 fn resolve_local_dispatch_addr(
139 &self,
140 packet: &ScionPacketView,
141 ) -> Result<net::SocketAddr, ScionSocketSendError> {
142 let dst_addr = packet
143 .header()
144 .dst_host_addr()
145 .map_err(|e| {
146 crate::stack::ScionSocketSendError::InvalidPacket(
147 format!("Packet for local dispatch had invalid destination address: {e}")
148 .into(),
149 )
150 })?
151 .ip()
152 .ok_or(crate::stack::ScionSocketSendError::InvalidPacket(
153 "Packet for local dispatch dst was not an IP address".into(),
154 ))?;
155
156 let classified = packet.try_classify().map_err(|e| {
157 crate::stack::ScionSocketSendError::InvalidPacket(
158 format!("Failed to classify packet for local dispatch: {e}").into(),
159 )
160 })?;
161
162 let dst_port =
163 classified
164 .dst_port()
165 .ok_or(crate::stack::ScionSocketSendError::InvalidPacket(
166 "Coulldn't determine destination port in packet for local dispatch".into(),
167 ))?;
168
169 Ok(net::SocketAddr::new(dst_addr, dst_port))
170 }
171
172 fn try_dispatch_local(&self, packet: &ScionPacketView) -> Result<(), ScionSocketSendError> {
173 let dst_addr = self.resolve_local_dispatch_addr(packet)?;
174 self.socket
175 .try_send_to(packet.as_slice(), dst_addr)
176 .map_err(|e| Self::map_send_io_error(e, packet.header().src_ia(), 0, dst_addr))?;
177 Ok(())
178 }
179
180 fn map_send_io_error(
182 e: io::Error,
183 src: IsdAsn,
184 interface_id: u16,
185 next_hop: net::SocketAddr,
186 ) -> ScionSocketSendError {
187 use std::io::ErrorKind::{
188 BrokenPipe, ConnectionAborted, ConnectionReset, HostUnreachable, NetworkUnreachable,
189 };
190 match e.kind() {
191 HostUnreachable | NetworkUnreachable => {
192 ScionSocketSendError::UnderlayNextHopUnreachable {
193 isd_as: src,
194 interface_id,
195 address: Some(next_hop),
196 msg: e.to_string(),
197 }
198 }
199 ConnectionAborted | ConnectionReset | BrokenPipe => ScionSocketSendError::Closed,
200 _ => ScionSocketSendError::IoError(e),
201 }
202 }
203}
204
205#[async_trait]
206impl UnderlaySocket for UdpUnderlaySocket {
207 fn try_send(&self, packet: &ScionRawPacketView) -> Result<(), ScionSocketSendError> {
210 let source_ia = packet.header().src_ia();
211
212 if packet.header().dst_ia() == source_ia {
214 return self.try_dispatch_local(packet);
215 }
216
217 let Some(egress_if) = packet.header().path().first_egress_interface() else {
219 return Err(ScionSocketSendError::InvalidPacket(
220 "Can't determine egress interface for packet.".into(),
221 ));
222 };
223
224 let next_hop = match self
226 .underlay_discovery
227 .resolve_udp_underlay_next_hop(PathInterface {
228 isd_asn: source_ia,
229 id: egress_if,
230 }) {
231 Some(next_hop) => next_hop,
232 None => {
233 return Err(ScionSocketSendError::UnderlayNextHopUnreachable {
234 isd_as: source_ia,
235 interface_id: egress_if,
236 address: None,
237 msg: "next hop not found".to_string(),
238 });
239 }
240 };
241
242 self.socket
243 .try_send_to(packet.as_slice(), next_hop)
244 .map_err(|e| Self::map_send_io_error(e, source_ia, egress_if, next_hop))?;
245
246 Ok(())
247 }
248
249 async fn writeable(&self) {
250 let _ = self.socket.writable().await;
252 }
253
254 fn try_recv(&self, buf: &mut [u8]) -> Result<usize, ScionSocketReceiveError> {
255 loop {
256 let n = match self.socket.try_recv_from(buf) {
257 Ok((n, _)) => n,
258 Err(e) => return Err(ScionSocketReceiveError::IoError(e)),
259 };
260
261 match ScionRawPacketView::try_from_slice(&buf[..n]) {
264 Ok((packet, _rest)) => {
265 match packet.dst_scion_addr() {
266 Ok(dst) if dst == self.bind_addr.scion_addr() => return Ok(n),
267 Ok(_) => {
268 tracing::debug!(
269 "Packet destination does not match assigned address, skipping"
270 );
271 }
272 Err(e) => {
273 tracing::error!(error = %e, "Failed to read destination address from packet");
274 }
275 }
276 }
277 Err(e) => {
278 tracing::error!(error = %e, "Failed to decode SCION packet");
279 }
280 }
281 }
282 }
283
284 async fn readable(&self) {
285 let _ = self.socket.readable().await;
287 }
288}