Skip to main content

snap_tun/client/
tunnel.rs

1// Copyright 2026 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    future::Future,
17    io,
18    net::SocketAddr,
19    pin::Pin,
20    sync::{
21        Arc, Mutex,
22        atomic::{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
44/// Error when sending or receiving packets on the SNAP tunnel.
45#[derive(Debug, thiserror::Error)]
46pub enum SnapTunnelDriverError {
47    /// I/O error when sending packets on the underlay socket.
48    #[error("send i/o error: {0}")]
49    SendIoError(#[from] std::io::Error),
50    /// I/O error when receiving packets on the underlay socket.
51    #[error("receive i/o error: {0}")]
52    ReceiveIoError(std::io::Error),
53    /// Receive queue closed.
54    #[error("receive queue closed")]
55    ReceiveQueueClosed,
56    /// Connection expired.
57    #[error("connection expired")]
58    ConnectionExpired,
59    /// Error receiving a Wireguard packet.
60    /// This will never be WireGuardError::ConnectionExpired.
61    #[error("error receiving a Wireguard packet: {0:?}")]
62    WireguardError(WireGuardError),
63}
64
65impl SnapTunnelDriverError {
66    /// Returns whether the failure is transient, so that a retry may help.
67    ///
68    /// Prefer this over matching the variants: a new variant would silently fall into a caller's
69    /// wildcard arm.
70    #[must_use]
71    pub fn is_transient(&self) -> bool {
72        match self {
73            // The underlay socket could not carry the datagram, which is a condition of the local
74            // network or of the route to the data plane.
75            Self::SendIoError(_) | Self::ReceiveIoError(_) => true,
76            // The peer did not complete the handshake within the WireGuard time window, so a new
77            // handshake may still succeed.
78            Self::ConnectionExpired => true,
79            // The consumer of the tunnel is gone; there is nothing left to deliver packets to.
80            Self::ReceiveQueueClosed => false,
81            // A WireGuard failure describes either the datagram or the configuration, and only
82            // the second survives a retry. Ordering, duplication, corruption and replay are all
83            // properties of one datagram that a fresh handshake, or simply the next packet,
84            // leaves behind. A peer key that does not match, a poisoned lock, and a buffer this
85            // side sized too small answer the same way every time.
86            //
87            // Spelled out rather than reached through a wildcard: `WireGuardError` is not
88            // `#[non_exhaustive]`, so naming every variant makes an `ana-gotatun` upgrade a
89            // compile error here instead of a silent reclassification.
90            Self::WireguardError(error) => {
91                match error {
92                    WireGuardError::NoCurrentSession
93                    | WireGuardError::WrongIndex
94                    | WireGuardError::UnexpectedPacket
95                    | WireGuardError::WrongPacketType
96                    | WireGuardError::IncorrectPacketLength
97                    | WireGuardError::InvalidPacket
98                    | WireGuardError::InvalidCounter
99                    | WireGuardError::DuplicateCounter
100                    | WireGuardError::InvalidMac
101                    | WireGuardError::InvalidAeadTag
102                    | WireGuardError::InvalidTai64nTimestamp
103                    | WireGuardError::WrongTai64nTimestamp
104                    | WireGuardError::ConnectionExpired => true,
105                    WireGuardError::WrongKey
106                    | WireGuardError::LockFailed
107                    | WireGuardError::DestinationBufferTooSmall => false,
108                }
109            }
110        }
111    }
112}
113
114struct SnapTunnelDriver {
115    pub tunn: Arc<Mutex<Tunn>>,
116    pub static_private: x25519::StaticSecret,
117    pub peer_public: x25519::PublicKey,
118    pub underlay_socket: Arc<tokio::net::UdpSocket>,
119    pub dataplane_address: SocketAddr,
120    pub persistent_keepalive_seconds: Option<u16>,
121    pub update_timers_interval: Interval,
122    pub packet_sender: async_channel::Sender<BytesMut>,
123    pub local_sockaddr: Option<SocketAddr>,
124    pub pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
125    pub receiver: UdpBatchReceiver<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
126    pub sender: UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
127    /// Shared with the [`SnapTunnel`] handle, which exposes it to the application.
128    pub discarded_datagrams: Arc<AtomicU64>,
129}
130
131impl SnapTunnelDriver {
132    fn new(
133        static_private: x25519::StaticSecret,
134        peer_public: x25519::PublicKey,
135        underlay_socket: Arc<tokio::net::UdpSocket>,
136        dataplane_address: SocketAddr,
137        persistent_keepalive_seconds: Option<u16>,
138        packet_sender: async_channel::Sender<BytesMut>,
139        pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
140    ) -> io::Result<Self> {
141        let update_timers_interval = tokio::time::interval_at(
142            tokio::time::Instant::now() + Duration::from_millis(250),
143            Duration::from_millis(250),
144        );
145        let receiver = UdpBatchReceiver::<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>::new(
146            underlay_socket.as_ref(),
147            &pool,
148        )?;
149        let sender = UdpBatchSender::<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>::new(
150            underlay_socket.as_ref(),
151        )?;
152        Ok(Self {
153            tunn: Arc::new(Mutex::new(Self::create_tunn(
154                static_private.clone(),
155                peer_public,
156                dataplane_address,
157                persistent_keepalive_seconds,
158            ))),
159            static_private,
160            peer_public,
161            underlay_socket,
162            dataplane_address,
163            persistent_keepalive_seconds,
164            update_timers_interval,
165            packet_sender,
166            local_sockaddr: None,
167            receiver,
168            sender,
169            pool,
170            discarded_datagrams: Arc::new(AtomicU64::new(0)),
171        })
172    }
173
174    /// Flushes the send queue as far as the socket allows right now.
175    ///
176    /// Back pressure needs no handling here: the datagrams stay queued and the next flush
177    /// picks them up.
178    ///
179    /// Takes the fields it needs one by one instead of `&mut self` so that it stays callable
180    /// from the receive closure, which borrows the driver field by field.
181    fn try_flush(
182        socket: &tokio::net::UdpSocket,
183        sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
184        discarded_datagrams: &AtomicU64,
185    ) {
186        let _ = sender.try_flush_best_effort(socket);
187        Self::account_discarded_datagrams(sender, discarded_datagrams);
188    }
189
190    /// Flushes the send queue, waiting for the socket to become writable.
191    async fn flush(
192        socket: &tokio::net::UdpSocket,
193        sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
194        discarded_datagrams: &AtomicU64,
195    ) -> io::Result<()> {
196        let result = sender.flush(socket).await;
197        Self::account_discarded_datagrams(sender, discarded_datagrams);
198        result
199    }
200
201    /// Publishes the datagrams the sender had to discard to the shared counter.
202    ///
203    /// Every flush goes through [`Self::try_flush`] or [`Self::flush`] so that no discard
204    /// escapes this accounting.
205    fn account_discarded_datagrams(
206        sender: &mut UdpBatchSender<RECEIVE_BATCH_SIZE, PACKET_BUF_POOL_SIZE>,
207        discarded_datagrams: &AtomicU64,
208    ) {
209        let discarded = sender.take_discarded_datagrams();
210        if discarded > 0 {
211            discarded_datagrams.fetch_add(discarded, Ordering::Relaxed);
212        }
213    }
214
215    #[instrument(name = "st-client", skip(self), fields(socket_addr= ?self.local_sockaddr))]
216    async fn initiate_connection(&mut self) -> Result<SocketAddr, SnapTunnelDriverError> {
217        let handshake_init = self.tunn.lock().unwrap().format_handshake_initiation(false);
218        if let Some(wg_init) = handshake_init
219            && let Err(e) = self
220                .underlay_socket
221                .send_to(
222                    to_bytes(WgKind::HandshakeInit(wg_init)).as_bytes(),
223                    self.dataplane_address,
224                )
225                .await
226        {
227            return Err(SnapTunnelDriverError::SendIoError(e));
228        }
229        // Drive the tunnel until any error occurs or the handshake is completed.
230        loop {
231            self.drive_once().await?;
232            if let Some(sockaddr) = self.tunn.lock().unwrap().get_initiator_remote_sockaddr() {
233                if self.local_sockaddr.is_none() {
234                    self.local_sockaddr = Some(sockaddr);
235                }
236                tracing::debug!(local_addr=?sockaddr, "handshake completed, local address assigned");
237                return Ok(sockaddr);
238            }
239        }
240    }
241
242    #[instrument(name = "st-client", skip(self), fields(socket_addr= ?self.local_sockaddr))]
243    async fn main_loop(mut self) {
244        let local_sockaddr = self
245            .local_sockaddr
246            .expect("local address must be set before main_loop()");
247        loop {
248            match self.drive_once().await {
249                Err(SnapTunnelDriverError::ReceiveQueueClosed) => {
250                    tracing::info!("receive queue closed, snap tunnel driver shutting down");
251                    return;
252                }
253                Err(SnapTunnelDriverError::ConnectionExpired) => {
254                    loop {
255                        let mut backoff = BackoffState::new();
256                        // reset tunnel
257                        *self.tunn.lock().expect("poison") = Self::create_tunn(
258                            self.static_private.clone(),
259                            self.peer_public,
260                            self.dataplane_address,
261                            self.persistent_keepalive_seconds,
262                        );
263                        match self.initiate_connection().await {
264                            Ok(addr) if addr == local_sockaddr => break,
265                            Ok(addr) => {
266                                tracing::error!(expected_addr=?local_sockaddr, new_addr=?addr, "local socket address changed");
267                            }
268                            Err(err) => {
269                                tracing::error!(?err, "error driving tunnel");
270                            }
271                        }
272                        backoff.backoff().await;
273                    }
274                }
275                Err(ref e) => tracing::error!(err=?e, "error driving tunnel"),
276                _ => {}
277            }
278        }
279    }
280
281    /// Drives the tunnel once. Returns Ok(()) if no error occured in the drive, otherwise returns
282    /// the error. This method is called periodically by the main loop to update the timers and
283    /// receive packets.
284    async fn drive_once(&mut self) -> Result<(), SnapTunnelDriverError> {
285        select! {
286            // bias to ensure that high receive load cannot starve the timer
287            biased;
288            _ = self.update_timers_interval.tick() => {
289                let p = match self.tunn.lock().unwrap().update_timers() {
290                    Ok(Some(wg)) => { Some(wg) },
291                    Ok(None) => None,
292                    Err(WireGuardError::ConnectionExpired) => {
293                        return Err(SnapTunnelDriverError::ConnectionExpired);
294                    }
295                    Err(e) => {
296                        // At the time of writing, update_timers does not return any error
297                        // other than ConnectionExpired.
298                        tracing::error!(err=?e, "unexpected error updating timers on tunnel");
299                        None
300                    }
301                };
302                if let Some(wg) = p && let Err(e) = self.underlay_socket.send_to(to_bytes(wg).as_bytes(), self.dataplane_address).await {
303                    return Err(SnapTunnelDriverError::SendIoError(e));
304                }
305            },
306            recv = self.receiver.recv_batch(&self.underlay_socket, &self.pool, |buf, sender_addr| {
307                if sender_addr != self.dataplane_address {
308                    return Ok(());
309                }
310                let Ok(wg) = buf.try_into_wg() else {
311                    tracing::debug!("received packet that is not a valid WireGuard packet, ignoring");
312                    return Ok(());
313                };
314                let result = self.tunn.lock().unwrap().handle_incoming_packet(wg);
315                match result {
316                    TunnResult::Done => {}
317                    TunnResult::Err(e) => {
318                        return Err(SnapTunnelDriverError::WireguardError(e));
319                    }
320                    TunnResult::WriteToNetwork(p) => {
321                        if let Err(error) = self
322                            .sender
323                            .try_queue_packet(to_bytes(p), self.dataplane_address)
324                        {
325                            match error {
326                                QueuePacketError::Full { packet, target } => {
327                                    Self::try_flush(
328                                        &self.underlay_socket,
329                                        &mut self.sender,
330                                        &self.discarded_datagrams,
331                                    );
332                                    if self.sender.try_queue_packet(packet, target).is_err() {
333                                        tracing::debug!(?target, "dropping outbound packet because batched sender remains full");
334                                    }
335                                }
336                                QueuePacketError::PacketTooLarge {
337                                    packet_len,
338                                    max_packet_size,
339                                    ..
340                                } => {
341                                    return Err(SnapTunnelDriverError::SendIoError(io::Error::new(
342                                        io::ErrorKind::InvalidInput,
343                                        format!(
344                                            "outbound packet length {packet_len} exceeds batched sender max of {max_packet_size}"
345                                        ),
346                                    )));
347                                }
348                            }
349                        }
350                        for queued in self.tunn.lock().unwrap().get_queued_packets() {
351                            if let Err(error) = self
352                                .sender
353                                .try_queue_packet(to_bytes(queued), self.dataplane_address)
354                            {
355                                match error {
356                                    QueuePacketError::Full { packet, target } => {
357                                        Self::try_flush(
358                                            &self.underlay_socket,
359                                            &mut self.sender,
360                                            &self.discarded_datagrams,
361                                        );
362                                        if self.sender.try_queue_packet(packet, target).is_err() {
363                                            tracing::debug!(?target, "dropping queued outbound packet because batched sender remains full");
364                                        }
365                                    }
366                                    QueuePacketError::PacketTooLarge {
367                                        packet_len,
368                                        max_packet_size,
369                                        ..
370                                    } => {
371                                        return Err(SnapTunnelDriverError::SendIoError(io::Error::new(
372                                            io::ErrorKind::InvalidInput,
373                                            format!(
374                                                "queued outbound packet length {packet_len} exceeds batched sender max of {max_packet_size}"
375                                            ),
376                                        )));
377                                    }
378                                }
379                            }
380                        }
381                    }
382                    TunnResult::WriteToTunnel(mut p) => {
383                        let buf = p.buf_mut().to_owned();
384                        if !buf.is_empty() {
385                            match self.packet_sender.try_send(buf) {
386                                Ok(()) => {}
387                                Err(async_channel::TrySendError::Full(_)) => {
388                                    tracing::debug!("receive channel is full, dropping packet");
389                                }
390                                Err(_) => {
391                                    return Err(SnapTunnelDriverError::ReceiveQueueClosed);
392                                }
393                            }
394                        }
395                    }
396                }
397                Ok(())
398            }) => {
399                match recv {
400                    Ok(()) => {
401                        Self::flush(
402                            &self.underlay_socket,
403                            &mut self.sender,
404                            &self.discarded_datagrams,
405                        )
406                        .await?;
407                    }
408                    Err(RecvBatchError::Io(e)) => {
409                        return Err(SnapTunnelDriverError::ReceiveIoError(e));
410                    }
411                    Err(RecvBatchError::Handler(e)) => {
412                        return Err(e);
413                    }
414                }
415            }
416        }
417        Ok(())
418    }
419
420    fn create_tunn(
421        static_private: x25519::StaticSecret,
422        peer_public: x25519::PublicKey,
423        dataplane_address: SocketAddr,
424        persistent_keepalive_seconds: Option<u16>,
425    ) -> Tunn {
426        let local_public = x25519::PublicKey::from(&static_private);
427        Tunn::new(
428            static_private,
429            peer_public,
430            None,
431            persistent_keepalive_seconds,
432            0,
433            Arc::new(RateLimiter::new(&local_public, HANDSHAKE_RATE_LIMIT)),
434            dataplane_address,
435        )
436    }
437}
438
439/// Error when receiving a packet from the SNAP tunnel connection.
440#[derive(Debug, thiserror::Error)]
441pub enum SnapTunnelReceiveError {
442    /// The receive queue is closed.
443    #[error("receive queue closed")]
444    ReceiveQueueClosed,
445}
446
447type RecvFuture = Pin<Box<dyn Future<Output = Result<BytesMut, async_channel::RecvError>> + Send>>;
448
449/// A SNAP tunnel connection.
450pub struct SnapTunnel {
451    _guard: TunnelGuard,
452    tunn: Arc<Mutex<Tunn>>,
453    underlay_socket: Arc<tokio::net::UdpSocket>,
454    dataplane_address: SocketAddr,
455    local_sockaddr: SocketAddr,
456    receive_queue: async_channel::Receiver<BytesMut>,
457    /// Stored receive future for poll_recv. Protected by Mutex for interior mutability.
458    recv_future: Mutex<Option<RecvFuture>>,
459    /// Tasks that drives the SNAP tunnel.
460    /// Cancelled when the socket is dropped.
461    driver_task: JoinHandle<()>,
462    discarded_datagrams: Arc<AtomicU64>,
463}
464
465impl Drop for SnapTunnel {
466    fn drop(&mut self) {
467        self.driver_task.abort();
468    }
469}
470
471impl SnapTunnel {
472    /// Creates a new SNAP tunnel and waits for the handshake to complete.
473    ///
474    /// # Arguments
475    ///
476    /// * `static_private` - The client's static private key
477    /// * `peer_public` - The server's static public key (needed for handshake)
478    /// * `rate_limiter` - Rate limiter for the tunnel
479    /// * `underlay_socket` - UDP socket for sending/receiving packets
480    /// * `dataplane_address` - Address of the remote server
481    /// * `receive_queue_capacity` - Capacity of the receive queue
482    pub(super) async fn new(
483        guard: TunnelGuard,
484        static_private: x25519::StaticSecret,
485        peer_public: x25519::PublicKey,
486        underlay_socket: Arc<tokio::net::UdpSocket>,
487        dataplane_address: SocketAddr,
488        receive_queue_capacity: usize,
489        persistent_keepalive_seconds: Option<u16>,
490        pool: PacketBufPool<PACKET_BUF_POOL_SIZE>,
491    ) -> Result<Self, SnapTunnelDriverError> {
492        let (packet_sender, packet_receiver) = async_channel::bounded(receive_queue_capacity);
493        let mut driver = SnapTunnelDriver::new(
494            static_private,
495            peer_public,
496            underlay_socket.clone(),
497            dataplane_address,
498            persistent_keepalive_seconds,
499            packet_sender,
500            pool.clone(),
501        )?;
502        let socket_addr = driver.initiate_connection().await?;
503        Ok(Self {
504            _guard: guard,
505            tunn: driver.tunn.clone(),
506            discarded_datagrams: driver.discarded_datagrams.clone(),
507            underlay_socket,
508            dataplane_address,
509            local_sockaddr: socket_addr,
510            receive_queue: packet_receiver,
511            recv_future: Mutex::new(None),
512            driver_task: tokio::spawn(driver.main_loop()),
513        })
514    }
515
516    /// Send a packet to the remote server.
517    // xxx(dsd): during a connection reset, packets will be silently dropped.
518    #[instrument(name = "st-client", skip_all, fields(socket_addr= ?self.local_sockaddr, payload_len= packet.len()))]
519    pub async fn send(&self, packet: Packet) -> io::Result<()> {
520        let encapsulated_packet = self.tunn.lock().unwrap().handle_outgoing_packet(packet);
521        match encapsulated_packet {
522            Some(wg) => {
523                let bytes = match wg {
524                    WgKind::HandshakeInit(p) => p.into_bytes(),
525                    WgKind::HandshakeResp(p) => p.into_bytes(),
526                    WgKind::CookieReply(p) => p.into_bytes(),
527                    WgKind::Data(p) => p.into_bytes(),
528                };
529                tracing::trace!(dataplane_address=?self.dataplane_address, "sending packet");
530                self.underlay_socket
531                    .send_to(bytes.as_bytes(), self.dataplane_address)
532                    .await?;
533                Ok(())
534            }
535            None => {
536                // None is returned if a handshake is ongoing but not yet complete.
537                // In this case the packet is queued and will be sent when the handshake is
538                // complete.
539                tracing::trace!("handshake ongoing, queueing packet");
540                Ok(())
541            }
542        }
543    }
544
545    /// Try to send a packet to the remote server. Returns error of try_send_to.
546    #[instrument(name = "st-client", skip_all, fields(socket_addr= ?self.local_sockaddr, payload_len= packet.len()))]
547    pub fn try_send(&self, packet: Packet) -> io::Result<()> {
548        match self.tunn.lock().unwrap().handle_outgoing_packet(packet) {
549            Some(wg) => {
550                let bytes = match wg {
551                    WgKind::HandshakeInit(p) => p.into_bytes(),
552                    WgKind::HandshakeResp(p) => p.into_bytes(),
553                    WgKind::CookieReply(p) => p.into_bytes(),
554                    WgKind::Data(p) => p.into_bytes(),
555                };
556                tracing::trace!(dataplane_address=?self.dataplane_address, "trying to send packet");
557                self.underlay_socket
558                    .try_send_to(bytes.as_bytes(), self.dataplane_address)?;
559                Ok(())
560            }
561            None => {
562                // None is returned if a handshake is ongoing but not yet complete.
563                // In this case the packet is queued and will be sent when the handshake is
564                // complete.
565                Ok(())
566            }
567        }
568    }
569
570    /// Receive a packet from the remote server.
571    pub async fn recv(&self) -> Result<Bytes, SnapTunnelReceiveError> {
572        match self.receive_queue.recv().await {
573            Ok(packet) => Ok(packet.into()),
574            Err(_) => Err(SnapTunnelReceiveError::ReceiveQueueClosed),
575        }
576    }
577
578    /// Try to receive a packet from the remote server without blocking.
579    ///
580    /// Returns `Ok(None)` if no packet is currently available.
581    pub fn try_recv(&self) -> Result<Option<Bytes>, SnapTunnelReceiveError> {
582        match self.receive_queue.try_recv() {
583            Ok(packet) => Ok(Some(packet.into())),
584            Err(async_channel::TryRecvError::Empty) => Ok(None),
585            Err(async_channel::TryRecvError::Closed) => {
586                Err(SnapTunnelReceiveError::ReceiveQueueClosed)
587            }
588        }
589    }
590
591    /// Poll for a packet from the remote server.
592    pub fn poll_recv(
593        &self,
594        cx: &mut std::task::Context<'_>,
595    ) -> std::task::Poll<Result<Bytes, SnapTunnelReceiveError>> {
596        let mut fut_guard = self.recv_future.lock().expect("lock poisoned");
597
598        // Create future if it doesn't exist
599        if fut_guard.is_none() {
600            // Clone the receiver (cheap with async-channel) to avoid borrowing self
601            let receiver = self.receive_queue.clone();
602            *fut_guard = Some(Box::pin(async move { receiver.recv().await }));
603        }
604
605        // Poll the stored future
606        let fut = fut_guard.as_mut().expect("future cannot be none");
607        match fut.as_mut().poll(cx) {
608            std::task::Poll::Ready(Ok(packet)) => {
609                // Clear the future so a new one is created on next poll
610                *fut_guard = None;
611                std::task::Poll::Ready(Ok(packet.into()))
612            }
613            std::task::Poll::Ready(Err(_)) => {
614                tracing::trace!("receive queue closed, returning error");
615                *fut_guard = None;
616                std::task::Poll::Ready(Err(SnapTunnelReceiveError::ReceiveQueueClosed))
617            }
618            std::task::Poll::Pending => std::task::Poll::Pending,
619        }
620    }
621
622    /// Get the local socket address. Assigned by the remote server.
623    pub fn local_addr(&self) -> SocketAddr {
624        self.local_sockaddr
625    }
626
627    /// Check if the socket is writable.
628    pub async fn writable(&self) -> io::Result<()> {
629        self.underlay_socket.writable().await
630    }
631
632    /// The data plane the tunnel is connected to.
633    pub fn data_plane_address(&self) -> SocketAddr {
634        self.dataplane_address
635    }
636
637    /// Total number of outbound datagrams the underlay socket refused since the tunnel was
638    /// created.
639    ///
640    /// A refused datagram is dropped rather than retried, because retrying one the socket will
641    /// never accept blocks every packet queued behind it. The tunnel therefore stays up when
642    /// sending fails persistently, for example because the interface went down or a firewall
643    /// answers `EPERM`, and this counter is what tells such a tunnel apart from a healthy one.
644    pub fn discarded_datagrams(&self) -> u64 {
645        self.discarded_datagrams.load(Ordering::Relaxed)
646    }
647}
648
649struct BackoffState {
650    last: Instant,
651    exp_backoff: ExponentialBackoff,
652    attempt: usize,
653}
654
655impl BackoffState {
656    fn new() -> Self {
657        Self {
658            last: Instant::now(),
659            exp_backoff: ExponentialBackoff::new(
660                5.0, 180.0, // max 3 mins
661                1.3, 0.5,
662            ),
663            attempt: 0,
664        }
665    }
666
667    fn backoff(&mut self) -> impl Future<Output = ()> {
668        let now = Instant::now();
669        let until_next = (self.last + self.exp_backoff.duration(self.attempt as u32))
670            .checked_duration_since(now);
671        self.attempt += 1;
672        self.last = now;
673
674        async move {
675            if let Some(d) = until_next {
676                tokio::time::sleep(d).await;
677            }
678        }
679    }
680}
681
682fn to_bytes(wg: WgKind) -> Packet<[u8]> {
683    match wg {
684        WgKind::HandshakeInit(p) => p.into_bytes(),
685        WgKind::HandshakeResp(p) => p.into_bytes(),
686        WgKind::CookieReply(p) => p.into_bytes(),
687        WgKind::Data(p) => p.into_bytes(),
688    }
689}
690
691#[cfg(test)]
692mod tests {
693    use super::*;
694
695    #[test]
696    fn driver_error_transient_classification() {
697        // The socket or the peer did not carry the handshake through, so a retry may.
698        assert!(SnapTunnelDriverError::SendIoError(io::Error::other("boom")).is_transient());
699        assert!(SnapTunnelDriverError::ReceiveIoError(io::Error::other("boom")).is_transient());
700        assert!(SnapTunnelDriverError::ConnectionExpired.is_transient());
701        // Handshake timing and packet ordering: a datagram that arrived late, twice, or for a
702        // session that has since rotated. None of them says anything about the next attempt.
703        for error in [
704            WireGuardError::NoCurrentSession,
705            WireGuardError::WrongIndex,
706            WireGuardError::UnexpectedPacket,
707            WireGuardError::InvalidCounter,
708            WireGuardError::DuplicateCounter,
709        ] {
710            assert!(
711                SnapTunnelDriverError::WireguardError(error).is_transient(),
712                "a handshake-timing condition was reported as permanent"
713            );
714        }
715        // A peer key that does not match, and a gone consumer, answer the same way on every
716        // attempt.
717        assert!(!SnapTunnelDriverError::WireguardError(WireGuardError::WrongKey).is_transient());
718        assert!(!SnapTunnelDriverError::ReceiveQueueClosed.is_transient());
719    }
720}