1use std::{
16 future::Future,
17 io,
18 net::SocketAddr,
19 pin::Pin,
20 sync::{
21 Arc, Mutex,
22 atomic::{AtomicBool, AtomicU64, Ordering},
23 },
24 time::{Duration, Instant},
25};
26
27use ana_gotatun::{
28 noise::{Tunn, TunnResult, errors::WireGuardError, rate_limiter::RateLimiter},
29 packet::{Packet, PacketBufPool, WgKind},
30 x25519::{self},
31};
32use bytes::{Bytes, BytesMut};
33use scion_sdk_utils::backoff::ExponentialBackoff;
34use tokio::{select, task::JoinHandle, time::Interval};
35use tracing::instrument;
36use zerocopy::IntoBytes as _;
37
38use super::{PACKET_BUF_POOL_SIZE, TunnelGuard};
39use crate::udp_batch::{QueuePacketError, RecvBatchError, UdpBatchReceiver, UdpBatchSender};
40
41const HANDSHAKE_RATE_LIMIT: u64 = 20;
42const RECEIVE_BATCH_SIZE: usize = 64;
43
44const REHANDSHAKE_BUDGET: Duration = Duration::from_secs(600);
46
47#[derive(Debug, thiserror::Error)]
49pub enum SnapTunnelDriverError {
50 #[error("send i/o error: {0}")]
52 SendIoError(#[from] std::io::Error),
53 #[error("receive i/o error: {0}")]
55 ReceiveIoError(std::io::Error),
56 #[error("receive queue closed")]
58 ReceiveQueueClosed,
59 #[error("connection expired")]
61 ConnectionExpired,
62 #[error("error receiving a Wireguard packet: {0:?}")]
65 WireguardError(WireGuardError),
66}
67
68impl SnapTunnelDriverError {
69 #[must_use]
74 pub fn is_transient(&self) -> bool {
75 match self {
76 Self::SendIoError(_) | Self::ReceiveIoError(_) => true,
79 Self::ConnectionExpired => true,
82 Self::ReceiveQueueClosed => false,
84 Self::WireguardError(error) => {
94 match error {
95 WireGuardError::NoCurrentSession
96 | WireGuardError::WrongIndex
97 | WireGuardError::UnexpectedPacket
98 | WireGuardError::WrongPacketType
99 | WireGuardError::IncorrectPacketLength
100 | WireGuardError::InvalidPacket
101 | WireGuardError::InvalidCounter
102 | WireGuardError::DuplicateCounter
103 | WireGuardError::InvalidMac
104 | WireGuardError::InvalidAeadTag
105 | WireGuardError::InvalidTai64nTimestamp
106 | WireGuardError::WrongTai64nTimestamp
107 | WireGuardError::ConnectionExpired => true,
108 WireGuardError::WrongKey
109 | WireGuardError::LockFailed
110 | WireGuardError::DestinationBufferTooSmall => false,
111 }
112 }
113 }
114 }
115}
116
117struct SnapTunnelDriver {
118 pub tunn: Arc<Mutex<Tunn>>,
119 pub static_private: x25519::StaticSecret,
120 pub peer_public: x25519::PublicKey,
121 pub underlay_socket: Arc<tokio::net::UdpSocket>,
122 pub dataplane_address: SocketAddr,
123 pub persistent_keepalive_seconds: Option<u16>,
124 pub update_timers_interval: Interval,
125 pub packet_sender: async_channel::Sender<BytesMut>,
126 pub local_sockaddr: Option<SocketAddr>,
127 pub pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
128 pub receiver: UdpBatchReceiver<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
129 pub sender: UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
130 pub discarded_datagrams: Arc<AtomicU64>,
132 pub closed: Arc<AtomicBool>,
134}
135
136impl SnapTunnelDriver {
137 fn new(
138 static_private: x25519::StaticSecret,
139 peer_public: x25519::PublicKey,
140 underlay_socket: Arc<tokio::net::UdpSocket>,
141 dataplane_address: SocketAddr,
142 persistent_keepalive_seconds: Option<u16>,
143 packet_sender: async_channel::Sender<BytesMut>,
144 pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
145 ) -> io::Result<Self> {
146 let update_timers_interval = tokio::time::interval_at(
147 tokio::time::Instant::now() + Duration::from_millis(250),
148 Duration::from_millis(250),
149 );
150 let receiver = UdpBatchReceiver::<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>::new(
151 underlay_socket.as_ref(),
152 &pool,
153 )?;
154 let sender = UdpBatchSender::<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>::new(
155 underlay_socket.as_ref(),
156 )?;
157 Ok(Self {
158 tunn: Arc::new(Mutex::new(Self::create_tunn(
159 static_private.clone(),
160 peer_public,
161 dataplane_address,
162 persistent_keepalive_seconds,
163 ))),
164 static_private,
165 peer_public,
166 underlay_socket,
167 dataplane_address,
168 persistent_keepalive_seconds,
169 update_timers_interval,
170 packet_sender,
171 local_sockaddr: None,
172 receiver,
173 sender,
174 pool,
175 discarded_datagrams: Arc::new(AtomicU64::new(0)),
176 closed: Arc::new(AtomicBool::new(false)),
177 })
178 }
179
180 fn try_flush(
188 socket: &tokio::net::UdpSocket,
189 sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
190 discarded_datagrams: &AtomicU64,
191 ) {
192 let _ = sender.try_flush_best_effort(socket);
193 Self::account_discarded_datagrams(sender, discarded_datagrams);
194 }
195
196 async fn flush(
198 socket: &tokio::net::UdpSocket,
199 sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
200 discarded_datagrams: &AtomicU64,
201 ) -> io::Result<()> {
202 let result = sender.flush(socket).await;
203 Self::account_discarded_datagrams(sender, discarded_datagrams);
204 result
205 }
206
207 fn account_discarded_datagrams(
212 sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
213 discarded_datagrams: &AtomicU64,
214 ) {
215 let discarded = sender.take_discarded_datagrams();
216 if discarded > 0 {
217 discarded_datagrams.fetch_add(discarded, Ordering::Relaxed);
218 }
219 }
220
221 #[instrument(name = "st-client", skip(self), fields(socket_addr= ?self.local_sockaddr))]
222 async fn initiate_connection(&mut self) -> Result<SocketAddr, SnapTunnelDriverError> {
223 let handshake_init = self.tunn.lock().unwrap().format_handshake_initiation(false);
224 if let Some(wg_init) = handshake_init
225 && let Err(e) = self
226 .underlay_socket
227 .send_to(
228 to_bytes(WgKind::HandshakeInit(wg_init)).as_bytes(),
229 self.dataplane_address,
230 )
231 .await
232 {
233 return Err(SnapTunnelDriverError::SendIoError(e));
234 }
235 loop {
237 self.drive_once().await?;
238 if let Some(sockaddr) = self.tunn.lock().unwrap().get_initiator_remote_sockaddr() {
239 if self.local_sockaddr.is_none() {
240 self.local_sockaddr = Some(sockaddr);
241 }
242 tracing::debug!(local_addr=?sockaddr, "handshake completed, local address assigned");
243 return Ok(sockaddr);
244 }
245 }
246 }
247
248 #[instrument(name = "st-client", skip(self), fields(socket_addr= ?self.local_sockaddr))]
250 async fn main_loop(mut self) {
251 self.run().await;
252 self.closed.store(true, Ordering::Release);
253 tracing::info!("snap tunnel driver stopped");
254 }
255
256 async fn run(&mut self) {
259 let local_sockaddr = self
260 .local_sockaddr
261 .expect("local address must be set before main_loop()");
262 loop {
263 match self.drive_once().await {
264 Err(SnapTunnelDriverError::ReceiveQueueClosed) => {
265 tracing::info!("receive queue closed, snap tunnel driver shutting down");
266 return;
267 }
268 Err(SnapTunnelDriverError::ConnectionExpired) => {
269 if !self.rehandshake(local_sockaddr).await {
270 return;
271 }
272 }
273 Err(ref e) => tracing::error!(err=?e, "error driving tunnel"),
274 _ => {}
275 }
276 }
277 }
278
279 async fn rehandshake(&mut self, local_sockaddr: SocketAddr) -> bool {
285 let started = Instant::now();
286 let mut backoff = BackoffState::new();
287 loop {
288 *self.tunn.lock().expect("poison") = Self::create_tunn(
289 self.static_private.clone(),
290 self.peer_public,
291 self.dataplane_address,
292 self.persistent_keepalive_seconds,
293 );
294 let result = self.initiate_connection().await;
295 match rehandshake_outcome(local_sockaddr, &result, started.elapsed()) {
296 Recovery::Recovered => return true,
297 Recovery::Retry => {
298 tracing::warn!(?result, "re-handshake failed, retrying");
299 }
300 Recovery::Stop(reason) => {
301 tracing::error!(
302 ?reason,
303 ?result,
304 "cannot re-establish snap tunnel, stopping driver"
305 );
306 return false;
307 }
308 }
309 backoff.backoff().await;
310 }
311 }
312
313 async fn drive_once(&mut self) -> Result<(), SnapTunnelDriverError> {
317 select! {
318 biased;
320 _ = self.update_timers_interval.tick() => {
321 let p = match self.tunn.lock().unwrap().update_timers() {
322 Ok(Some(wg)) => { Some(wg) },
323 Ok(None) => None,
324 Err(WireGuardError::ConnectionExpired) => {
325 return Err(SnapTunnelDriverError::ConnectionExpired);
326 }
327 Err(e) => {
328 tracing::error!(err=?e, "unexpected error updating timers on tunnel");
331 None
332 }
333 };
334 if let Some(wg) = p && let Err(e) = self.underlay_socket.send_to(to_bytes(wg).as_bytes(), self.dataplane_address).await {
335 return Err(SnapTunnelDriverError::SendIoError(e));
336 }
337 },
338 recv = self.receiver.recv_batch(&self.underlay_socket, &self.pool, |buf, sender_addr| {
339 if sender_addr != self.dataplane_address {
340 return Ok(());
341 }
342 let Ok(wg) = buf.try_into_wg() else {
343 tracing::debug!("received packet that is not a valid WireGuard packet, ignoring");
344 return Ok(());
345 };
346 let result = self.tunn.lock().unwrap().handle_incoming_packet(wg);
347 match result {
348 TunnResult::Done => {}
349 TunnResult::Err(e) => {
350 let error = SnapTunnelDriverError::WireguardError(e);
351 if error.is_transient() {
353 tracing::debug!(err=?error, "dropping invalid WireGuard packet");
354 return Ok(());
355 }
356 return Err(error);
357 }
358 TunnResult::WriteToNetwork(p) => {
359 if let Err(error) = self
360 .sender
361 .try_queue_packet(to_bytes(p), self.dataplane_address)
362 {
363 match error {
364 QueuePacketError::Full { packet, target } => {
365 Self::try_flush(
366 &self.underlay_socket,
367 &mut self.sender,
368 &self.discarded_datagrams,
369 );
370 if self.sender.try_queue_packet(packet, target).is_err() {
371 tracing::debug!(?target, "dropping outbound packet because batched sender remains full");
372 }
373 }
374 QueuePacketError::PacketTooLarge {
375 packet_len,
376 max_packet_size,
377 ..
378 } => {
379 return Err(SnapTunnelDriverError::SendIoError(io::Error::new(
380 io::ErrorKind::InvalidInput,
381 format!(
382 "outbound packet length {packet_len} exceeds batched sender max of {max_packet_size}"
383 ),
384 )));
385 }
386 }
387 }
388 for queued in self.tunn.lock().unwrap().get_queued_packets() {
389 if let Err(error) = self
390 .sender
391 .try_queue_packet(to_bytes(queued), self.dataplane_address)
392 {
393 match error {
394 QueuePacketError::Full { packet, target } => {
395 Self::try_flush(
396 &self.underlay_socket,
397 &mut self.sender,
398 &self.discarded_datagrams,
399 );
400 if self.sender.try_queue_packet(packet, target).is_err() {
401 tracing::debug!(?target, "dropping queued outbound packet because batched sender remains full");
402 }
403 }
404 QueuePacketError::PacketTooLarge {
405 packet_len,
406 max_packet_size,
407 ..
408 } => {
409 return Err(SnapTunnelDriverError::SendIoError(io::Error::new(
410 io::ErrorKind::InvalidInput,
411 format!(
412 "queued outbound packet length {packet_len} exceeds batched sender max of {max_packet_size}"
413 ),
414 )));
415 }
416 }
417 }
418 }
419 }
420 TunnResult::WriteToTunnel(mut p) => {
421 let buf = p.buf_mut().to_owned();
422 if !buf.is_empty() {
423 match self.packet_sender.try_send(buf) {
424 Ok(()) => {}
425 Err(async_channel::TrySendError::Full(_)) => {
426 tracing::debug!("receive channel is full, dropping packet");
427 }
428 Err(_) => {
429 return Err(SnapTunnelDriverError::ReceiveQueueClosed);
430 }
431 }
432 }
433 }
434 }
435 Ok(())
436 }) => {
437 match recv {
438 Ok(()) => {
439 Self::flush(
440 &self.underlay_socket,
441 &mut self.sender,
442 &self.discarded_datagrams,
443 )
444 .await?;
445 }
446 Err(RecvBatchError::Io(e)) => {
447 return Err(SnapTunnelDriverError::ReceiveIoError(e));
448 }
449 Err(RecvBatchError::Handler(e)) => {
450 return Err(e);
451 }
452 }
453 }
454 }
455 Ok(())
456 }
457
458 fn create_tunn(
459 static_private: x25519::StaticSecret,
460 peer_public: x25519::PublicKey,
461 dataplane_address: SocketAddr,
462 persistent_keepalive_seconds: Option<u16>,
463 ) -> Tunn {
464 let local_public = x25519::PublicKey::from(&static_private);
465 Tunn::new(
466 static_private,
467 peer_public,
468 None,
469 persistent_keepalive_seconds,
470 0,
471 Arc::new(RateLimiter::new(&local_public, HANDSHAKE_RATE_LIMIT)),
472 dataplane_address,
473 )
474 }
475}
476
477#[derive(Debug, PartialEq, Eq)]
479enum Recovery {
480 Recovered,
482 Retry,
484 Stop(StopReason),
486}
487
488#[derive(Debug, PartialEq, Eq)]
490enum StopReason {
491 AddressChanged { new_addr: SocketAddr },
495 PermanentError,
497 BudgetExhausted,
499}
500
501fn rehandshake_outcome(
504 expected_addr: SocketAddr,
505 result: &Result<SocketAddr, SnapTunnelDriverError>,
506 elapsed: Duration,
507) -> Recovery {
508 match result {
509 Ok(addr) if *addr == expected_addr => Recovery::Recovered,
510 Ok(addr) => Recovery::Stop(StopReason::AddressChanged { new_addr: *addr }),
511 Err(e) if !e.is_transient() => Recovery::Stop(StopReason::PermanentError),
512 Err(_) if elapsed >= REHANDSHAKE_BUDGET => Recovery::Stop(StopReason::BudgetExhausted),
513 Err(_) => Recovery::Retry,
514 }
515}
516
517#[derive(Debug, thiserror::Error)]
519pub enum SnapTunnelReceiveError {
520 #[error("receive queue closed")]
522 ReceiveQueueClosed,
523}
524
525type RecvFuture = Pin<Box<dyn Future<Output = Result<BytesMut, async_channel::RecvError>> + Send>>;
526
527pub struct SnapTunnel {
534 _guard: TunnelGuard,
535 tunn: Arc<Mutex<Tunn>>,
536 underlay_socket: Arc<tokio::net::UdpSocket>,
537 dataplane_address: SocketAddr,
538 local_sockaddr: SocketAddr,
539 receive_queue: async_channel::Receiver<BytesMut>,
540 recv_future: Mutex<Option<RecvFuture>>,
542 driver_task: JoinHandle<()>,
545 discarded_datagrams: Arc<AtomicU64>,
546 closed: Arc<AtomicBool>,
547}
548
549impl Drop for SnapTunnel {
550 fn drop(&mut self) {
551 self.driver_task.abort();
552 }
553}
554
555impl SnapTunnel {
556 pub(super) async fn new(
567 guard: TunnelGuard,
568 static_private: x25519::StaticSecret,
569 peer_public: x25519::PublicKey,
570 underlay_socket: Arc<tokio::net::UdpSocket>,
571 dataplane_address: SocketAddr,
572 receive_queue_capacity: usize,
573 persistent_keepalive_seconds: Option<u16>,
574 pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
575 ) -> Result<Self, SnapTunnelDriverError> {
576 let (packet_sender, packet_receiver) = async_channel::bounded(receive_queue_capacity);
577 let mut driver = SnapTunnelDriver::new(
578 static_private,
579 peer_public,
580 underlay_socket.clone(),
581 dataplane_address,
582 persistent_keepalive_seconds,
583 packet_sender,
584 pool.clone(),
585 )?;
586 let socket_addr = driver.initiate_connection().await?;
587 Ok(Self {
588 _guard: guard,
589 tunn: driver.tunn.clone(),
590 discarded_datagrams: driver.discarded_datagrams.clone(),
591 closed: driver.closed.clone(),
592 underlay_socket,
593 dataplane_address,
594 local_sockaddr: socket_addr,
595 receive_queue: packet_receiver,
596 recv_future: Mutex::new(None),
597 driver_task: tokio::spawn(driver.main_loop()),
598 })
599 }
600
601 #[instrument(name = "st-client", skip_all, fields(socket_addr= ?self.local_sockaddr, payload_len= packet.len()))]
606 pub async fn send(&self, packet: Packet) -> io::Result<()> {
607 if self.is_closed() {
608 return Err(closed_error());
609 }
610 let encapsulated_packet = self.tunn.lock().unwrap().handle_outgoing_packet(packet);
611 match encapsulated_packet {
612 Some(wg) => {
613 let bytes = match wg {
614 WgKind::HandshakeInit(p) => p.into_bytes(),
615 WgKind::HandshakeResp(p) => p.into_bytes(),
616 WgKind::CookieReply(p) => p.into_bytes(),
617 WgKind::Data(p) => p.into_bytes(),
618 };
619 tracing::trace!(dataplane_address=?self.dataplane_address, "sending packet");
620 self.underlay_socket
621 .send_to(bytes.as_bytes(), self.dataplane_address)
622 .await?;
623 Ok(())
624 }
625 None => {
626 tracing::trace!("handshake ongoing, queueing packet");
630 Ok(())
631 }
632 }
633 }
634
635 #[instrument(name = "st-client", skip_all, fields(socket_addr= ?self.local_sockaddr, payload_len= packet.len()))]
639 pub fn try_send(&self, packet: Packet) -> io::Result<()> {
640 if self.is_closed() {
641 return Err(closed_error());
642 }
643 match self.tunn.lock().unwrap().handle_outgoing_packet(packet) {
644 Some(wg) => {
645 let bytes = match wg {
646 WgKind::HandshakeInit(p) => p.into_bytes(),
647 WgKind::HandshakeResp(p) => p.into_bytes(),
648 WgKind::CookieReply(p) => p.into_bytes(),
649 WgKind::Data(p) => p.into_bytes(),
650 };
651 tracing::trace!(dataplane_address=?self.dataplane_address, "trying to send packet");
652 self.underlay_socket
653 .try_send_to(bytes.as_bytes(), self.dataplane_address)?;
654 Ok(())
655 }
656 None => {
657 Ok(())
661 }
662 }
663 }
664
665 pub async fn recv(&self) -> Result<Bytes, SnapTunnelReceiveError> {
669 match self.receive_queue.recv().await {
670 Ok(packet) => Ok(packet.into()),
671 Err(_) => Err(SnapTunnelReceiveError::ReceiveQueueClosed),
672 }
673 }
674
675 pub fn try_recv(&self) -> Result<Option<Bytes>, SnapTunnelReceiveError> {
679 match self.receive_queue.try_recv() {
680 Ok(packet) => Ok(Some(packet.into())),
681 Err(async_channel::TryRecvError::Empty) => Ok(None),
682 Err(async_channel::TryRecvError::Closed) => {
683 Err(SnapTunnelReceiveError::ReceiveQueueClosed)
684 }
685 }
686 }
687
688 pub fn poll_recv(
690 &self,
691 cx: &mut std::task::Context<'_>,
692 ) -> std::task::Poll<Result<Bytes, SnapTunnelReceiveError>> {
693 let mut fut_guard = self.recv_future.lock().expect("lock poisoned");
694
695 if fut_guard.is_none() {
697 let receiver = self.receive_queue.clone();
699 *fut_guard = Some(Box::pin(async move { receiver.recv().await }));
700 }
701
702 let fut = fut_guard.as_mut().expect("future cannot be none");
704 match fut.as_mut().poll(cx) {
705 std::task::Poll::Ready(Ok(packet)) => {
706 *fut_guard = None;
708 std::task::Poll::Ready(Ok(packet.into()))
709 }
710 std::task::Poll::Ready(Err(_)) => {
711 tracing::trace!("receive queue closed, returning error");
712 *fut_guard = None;
713 std::task::Poll::Ready(Err(SnapTunnelReceiveError::ReceiveQueueClosed))
714 }
715 std::task::Poll::Pending => std::task::Poll::Pending,
716 }
717 }
718
719 pub fn local_addr(&self) -> SocketAddr {
721 self.local_sockaddr
722 }
723
724 pub fn is_closed(&self) -> bool {
733 self.closed.load(Ordering::Acquire)
734 }
735
736 pub async fn writable(&self) -> io::Result<()> {
738 self.underlay_socket.writable().await
739 }
740
741 pub fn data_plane_address(&self) -> SocketAddr {
743 self.dataplane_address
744 }
745
746 pub fn discarded_datagrams(&self) -> u64 {
754 self.discarded_datagrams.load(Ordering::Relaxed)
755 }
756}
757
758struct BackoffState {
759 last: Instant,
760 exp_backoff: ExponentialBackoff,
761 attempt: usize,
762}
763
764impl BackoffState {
765 fn new() -> Self {
766 Self {
767 last: Instant::now(),
768 exp_backoff: ExponentialBackoff::new(
769 5.0, 180.0, 1.3, 0.5,
771 ),
772 attempt: 0,
773 }
774 }
775
776 fn next_delay(&mut self) -> Option<Duration> {
778 let now = Instant::now();
779 let until_next = (self.last + self.exp_backoff.duration(self.attempt as u32))
780 .checked_duration_since(now);
781 self.attempt += 1;
782 self.last = now;
783 until_next
784 }
785
786 fn backoff(&mut self) -> impl Future<Output = ()> {
787 let until_next = self.next_delay();
788 async move {
789 if let Some(d) = until_next {
790 tokio::time::sleep(d).await;
791 }
792 }
793 }
794}
795
796fn closed_error() -> io::Error {
797 io::Error::new(io::ErrorKind::ConnectionReset, "SNAP tunnel closed")
798}
799
800fn to_bytes(wg: WgKind) -> Packet<[u8]> {
801 match wg {
802 WgKind::HandshakeInit(p) => p.into_bytes(),
803 WgKind::HandshakeResp(p) => p.into_bytes(),
804 WgKind::CookieReply(p) => p.into_bytes(),
805 WgKind::Data(p) => p.into_bytes(),
806 }
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 #[test]
814 fn driver_error_transient_classification() {
815 assert!(SnapTunnelDriverError::SendIoError(io::Error::other("boom")).is_transient());
817 assert!(SnapTunnelDriverError::ReceiveIoError(io::Error::other("boom")).is_transient());
818 assert!(SnapTunnelDriverError::ConnectionExpired.is_transient());
819 for error in [
822 WireGuardError::NoCurrentSession,
823 WireGuardError::WrongIndex,
824 WireGuardError::UnexpectedPacket,
825 WireGuardError::InvalidCounter,
826 WireGuardError::DuplicateCounter,
827 ] {
828 assert!(
829 SnapTunnelDriverError::WireguardError(error).is_transient(),
830 "a handshake-timing condition was reported as permanent"
831 );
832 }
833 assert!(!SnapTunnelDriverError::WireguardError(WireGuardError::WrongKey).is_transient());
836 assert!(!SnapTunnelDriverError::ReceiveQueueClosed.is_transient());
837 }
838
839 fn addr(s: &str) -> SocketAddr {
840 s.parse().unwrap()
841 }
842
843 #[test]
844 fn rehandshake_recovers_on_the_same_address() {
845 let expected = addr("192.0.2.1:4000");
846 assert_eq!(
847 rehandshake_outcome(expected, &Ok(expected), Duration::ZERO),
848 Recovery::Recovered
849 );
850 }
851
852 #[test]
853 fn rehandshake_stops_when_the_address_changed() {
854 let expected = addr("192.0.2.1:4000");
855 let new_addr = addr("198.51.100.7:4000");
856 assert_eq!(
857 rehandshake_outcome(expected, &Ok(new_addr), Duration::ZERO),
858 Recovery::Stop(StopReason::AddressChanged { new_addr })
859 );
860 }
861
862 #[test]
863 fn rehandshake_retries_transient_errors_within_the_budget() {
864 let expected = addr("192.0.2.1:4000");
865 let expired = Err(SnapTunnelDriverError::ConnectionExpired);
866 assert_eq!(
867 rehandshake_outcome(expected, &expired, Duration::ZERO),
868 Recovery::Retry
869 );
870 assert_eq!(
871 rehandshake_outcome(
872 expected,
873 &expired,
874 REHANDSHAKE_BUDGET - Duration::from_secs(1)
875 ),
876 Recovery::Retry
877 );
878 let send_failed = Err(SnapTunnelDriverError::SendIoError(io::Error::other("down")));
879 assert_eq!(
880 rehandshake_outcome(expected, &send_failed, Duration::ZERO),
881 Recovery::Retry
882 );
883 }
884
885 #[test]
886 fn rehandshake_stops_when_the_budget_is_spent() {
887 let expected = addr("192.0.2.1:4000");
888 let expired = Err(SnapTunnelDriverError::ConnectionExpired);
889 assert_eq!(
890 rehandshake_outcome(expected, &expired, REHANDSHAKE_BUDGET),
891 Recovery::Stop(StopReason::BudgetExhausted)
892 );
893 }
894
895 #[test]
896 fn rehandshake_stops_on_a_permanent_error() {
897 let expected = addr("192.0.2.1:4000");
898 let gone = Err(SnapTunnelDriverError::ReceiveQueueClosed);
899 assert_eq!(
900 rehandshake_outcome(expected, &gone, Duration::ZERO),
901 Recovery::Stop(StopReason::PermanentError)
902 );
903 }
904
905 #[test]
906 fn backoff_delay_grows_across_attempts() {
907 let mut backoff = BackoffState::new();
910 let first = backoff.next_delay().expect("first attempt waits");
911 let second = backoff.next_delay().expect("second attempt waits");
912 let third = backoff.next_delay().expect("third attempt waits");
913 assert!(
914 first < second && second < third,
915 "{first:?} {second:?} {third:?}"
916 );
917 }
918}