Skip to main content

snap_tun/
server.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//! The server of the SNAPtun protocol.
15//!
16//! As the underlying protocol is symmetric (both peers can act as
17//! initiator/responders that establish a session), technically, there is no
18//! server. The term "server" here just refers to and endpoint that manages
19//! multiple peers.
20
21use std::{
22    collections::{HashMap, VecDeque},
23    net::SocketAddr,
24    sync::Arc,
25    time::Instant,
26};
27
28use ana_gotatun::{
29    noise::{Tunn, TunnResult, handshake::parse_handshake_anon, rate_limiter::RateLimiter},
30    packet::{Packet, WgKind},
31    x25519,
32};
33
34/// The [SnapTunServer] manages one [Tunn] per remote socket address.
35///
36/// The main structural difference between WireGuard (R) and snaptun-ng is that
37/// there is a one-to-one relation between a remote socket address (of the
38/// initiator) and a tunnel. The [SnapTunServer] manages that relation.
39///
40/// ## Socket address and identity
41///
42/// The remote socket address is the address of an endhost, so the server binds
43/// the tunnel to that address and to the static identity that established it.
44/// Only that identity keeps the address. A peer that spoofs the address of
45/// another endhost can therefore neither take the address over nor tear the
46/// tunnel down.
47///
48/// ## Scaling
49///
50/// The main methods [SnapTunServer::handle_incoming_packet],
51/// [SnapTunServer::handle_outgoing_packet], and
52/// [SnapTunServer::update_timers] all require an exclusive reference to the
53/// internal state. The reason is that processing both, incoming and outgoing
54/// packets requires access to the session state.
55///
56/// One simple way to achieve load distribution across different cores/threads
57/// is to shard over multiple [SnapTunServer]-instances based on a hash of the
58/// remote socket address.
59///
60/// ## Future improvements
61///
62/// * Separate incoming and outgoing code paths and optimistically lock the session state.
63///
64/// ## How to use
65///
66/// The [SnapTunServer] is i/o-free; i.e. it only manages state. The following
67/// is a pseudo-code like description of the simplest i/o-layer integration:
68///
69/// ```text
70/// let mut server = SnapTunServer::new(/*...*/);
71/// let mut send_to_network = VecDequeue::new();
72/// let mut current_sockaddr = ;
73/// loop {
74///   switch {
75///     (network_packet, sockaddr) = network_socket => {
76///       server.handle_incoming_packet(/*...*/);
77///       /* dispatch packets to tunnel if necessary */
78///     }
79///     tunnel_packet = tunnel_socket => {
80///       server.handle_outgoing_packet(/*...*/);
81///     }
82///     timer = tick(250ms) => {
83///       server.update_timers();
84///     }
85///   }
86///   // dispatch packets to network
87///   for p in send_to_network {
88///     network_socket.send(sockaddr, p);
89///   }
90/// }
91/// ```
92pub struct SnapTunServer<T: SnapTunAuthorization> {
93    static_private: x25519::StaticSecret,
94    static_public: x25519::PublicKey,
95    active_tunnels: HashMap<SocketAddr, ActiveTunnel>,
96    rate_limiter: Arc<RateLimiter>,
97    authz: Arc<T>,
98}
99
100struct ActiveTunnel {
101    peer_static: x25519::PublicKey,
102    tunn: Tunn,
103}
104
105/// Packet-processing output for callers that also need the resolved tunnel session.
106///
107/// This is an opt-in extension of the original `TunnResult`-based API so the
108/// dataplane can reuse the resolved session without forcing existing snap-tun
109/// callers to change their control flow.
110pub enum HandleIncomingPacketResult<S> {
111    /// The result returned by the underlying WireGuard tunnel state machine
112    /// when no tunneled SCION payload was forwarded.
113    Result {
114        /// The result returned by the underlying WireGuard tunnel state
115        /// machine.
116        result: TunnResult,
117    },
118    /// A forwarded tunneled SCION payload together with the processing
119    /// metadata captured while the tunnel entry was already borrowed.
120    Forwarded {
121        /// The packet forwarded to the caller.
122        packet: Packet,
123        /// The timestamp captured after rate-limiter verification.
124        processed_at: Instant,
125        /// The active session data resolved while processing the packet.
126        session_data: Arc<S>,
127    },
128}
129
130impl<S> HandleIncomingPacketResult<S> {
131    /// Converts the result back into the underlying WireGuard tunnel result.
132    pub fn into_result(self) -> TunnResult {
133        match self {
134            HandleIncomingPacketResult::Result { result } => result,
135            HandleIncomingPacketResult::Forwarded { packet, .. } => {
136                TunnResult::WriteToTunnel(packet)
137            }
138        }
139    }
140}
141
142/// Packet-processing output for callers that need the active session once an
143/// outbound payload is accepted into the tunnel pipeline.
144pub struct HandleOutgoingPacketResult<S> {
145    /// The WireGuard packet to send to the network, if the tunnel emitted one
146    /// immediately.
147    pub network_packet: Option<WgKind>,
148    /// The timestamp captured for the authorization check that gated this
149    /// outgoing packet.
150    pub processed_at: Instant,
151    /// The session data resolved for the tunneled SCION payload.
152    pub session_data: Arc<S>,
153}
154
155impl<S> HandleOutgoingPacketResult<S> {
156    /// Converts the result into the immediate network packet, if one exists.
157    pub fn into_packet(self) -> Option<WgKind> {
158        self.network_packet
159    }
160}
161
162impl<T: SnapTunAuthorization> SnapTunServer<T> {
163    // The caller captures this timestamp after rate-limiter verification and
164    // reuses it for both authorization and forwarded-packet metadata.
165    fn incoming_packet_result(
166        result: TunnResult,
167        session_data: Arc<T::SessionData>,
168        now: Instant,
169    ) -> HandleIncomingPacketResult<T::SessionData> {
170        match result {
171            TunnResult::WriteToTunnel(packet) => {
172                HandleIncomingPacketResult::Forwarded {
173                    packet,
174                    processed_at: now,
175                    session_data,
176                }
177            }
178            result => HandleIncomingPacketResult::Result { result },
179        }
180    }
181
182    fn outgoing_packet_result(
183        network_packet: Option<WgKind>,
184        now: Instant,
185        session_data: Arc<T::SessionData>,
186    ) -> HandleOutgoingPacketResult<T::SessionData> {
187        HandleOutgoingPacketResult {
188            network_packet,
189            processed_at: now,
190            session_data,
191        }
192    }
193
194    /// Creates a new [SnapTunServer] instance.
195    pub fn new(
196        static_private: x25519::StaticSecret,
197        rate_limiter: Arc<RateLimiter>,
198        authz: Arc<T>,
199    ) -> Self {
200        let static_public = x25519::PublicKey::from(&static_private);
201        Self {
202            static_private,
203            static_public,
204            active_tunnels: Default::default(),
205            rate_limiter,
206            authz,
207        }
208    }
209
210    /// Handle incoming packet for a tunnel assocated with remote socket address
211    /// `from`.
212    ///
213    /// This method _never_ returns [TunnResult::WriteToNetwork]. Instead,
214    /// it codifies the expected protocol behavior which is that, upon receiving
215    /// a packet from the remote, the queue of outgoing packets is completely
216    /// drained.
217    ///
218    /// If the rate limiter signals that the server is under load, at most one
219    /// packet is added to the queue.
220    ///
221    /// This compatibility wrapper preserves the original public API for callers
222    /// that only care about the tunnel result.
223    pub fn handle_incoming_packet(
224        &mut self,
225        packet: Packet,
226        from: SocketAddr,
227        send_to_network: &mut VecDeque<WgKind>,
228    ) -> TunnResult {
229        self.handle_incoming_packet_with_session(packet, from, send_to_network)
230            .into_result()
231    }
232
233    /// Handles an incoming packet and also returns the active session when one
234    /// was resolved while processing the packet.
235    ///
236    /// Callers on the dataplane hot path can use this to observe forwarded
237    /// packets without re-hashing `from` for a second active-tunnel lookup,
238    /// while existing callers can keep using [`SnapTunServer::handle_incoming_packet`].
239    ///
240    /// The method resolves the tunnel by `from` before it reads the kind of the
241    /// packet, and it authorizes the identity of that tunnel. It parses and
242    /// authorizes the identity that a handshake initiation carries only for an
243    /// address that holds no tunnel yet. An address that holds a tunnel
244    /// therefore stays with the identity of that tunnel.
245    #[tracing::instrument(skip_all, fields(remote = %from))]
246    pub fn handle_incoming_packet_with_session(
247        &mut self,
248        packet: Packet,
249        from: SocketAddr,
250        send_to_network: &mut VecDeque<WgKind>,
251    ) -> HandleIncomingPacketResult<T::SessionData> {
252        let parsed_packet = match self.rate_limiter.verify_packet(from.ip(), packet) {
253            Ok(p) => p,
254            Err(TunnResult::WriteToNetwork(c)) => {
255                tracing::debug!(remote = ?from, "rate limiter issued cookie reply");
256                send_to_network.push_back(c);
257                return HandleIncomingPacketResult::Result {
258                    result: TunnResult::Done,
259                };
260            }
261            Err(e) => {
262                tracing::debug!(remote = ?from, err = ?e, "rate limiter rejected packet");
263                return HandleIncomingPacketResult::Result { result: e };
264            }
265        };
266        // Capture one shared timestamp after rate-limiter verification so the
267        // authorization decision and any forwarded-packet metadata use the same
268        // instant without an extra clock read on the hot path.
269        let packet_now = Instant::now();
270
271        use std::collections::hash_map::Entry;
272
273        use ana_gotatun::noise::errors::WireGuardError;
274        match (self.active_tunnels.entry(from), parsed_packet) {
275            (Entry::Occupied(mut occupied_entry), p) => {
276                let active_tunnel = occupied_entry.get_mut();
277                // TODO(dsd): At the moment, this keeps a tunnel alive even
278                // though the processing might fail, but gives the authorization
279                // layer a chance to block incomding packets in case an identity
280                // is unauthorized.
281                //
282                // Will fix later.
283                let Some(session_data) = self
284                    .authz
285                    .is_authorized(packet_now, active_tunnel.peer_static.as_bytes())
286                else {
287                    tracing::debug!(remote = ?from, peer_static = ?active_tunnel.peer_static, "rejected packet from unauthorized peer");
288                    return HandleIncomingPacketResult::Result {
289                        result: TunnResult::Err(WireGuardError::UnexpectedPacket),
290                    };
291                };
292                let result = Self::handle_incoming_and_drain_queue(
293                    send_to_network,
294                    p,
295                    &mut active_tunnel.tunn,
296                );
297                Self::incoming_packet_result(result, session_data, packet_now)
298            }
299            (e, WgKind::HandshakeInit(wg_init)) => {
300                let peer = match parse_handshake_anon(
301                    &self.static_private,
302                    &self.static_public,
303                    &wg_init,
304                ) {
305                    Ok(v) => v,
306                    Err(e) => {
307                        tracing::debug!(remote = ?from, err = ?e, "failed to parse handshake init");
308                        return HandleIncomingPacketResult::Result {
309                            result: TunnResult::from(e),
310                        };
311                    }
312                };
313
314                // TODO(dsd): if the socket is occupied, and tunnel.identity !=
315                // peer.identity, then send a cookie and abort
316
317                // TODO(dsd): extend ana-gotatun::Tunn such that peer static
318                // identity can be retrieved
319                let Some(session_data) = self
320                    .authz
321                    .is_authorized(packet_now, &peer.peer_static_public)
322                else {
323                    tracing::debug!(remote = ?from, "rejected handshake from unauthorized peer");
324                    return HandleIncomingPacketResult::Result {
325                        result: TunnResult::Err(WireGuardError::UnexpectedPacket),
326                    };
327                };
328                tracing::debug!(remote = ?from, "accepted new handshake, inserting tunnel");
329                let peer_static = x25519::PublicKey::from(peer.peer_static_public);
330                let mut tunn = Tunn::new(
331                    self.static_private.clone(),
332                    peer_static,
333                    None,
334                    None,
335                    0,
336                    self.rate_limiter.clone(),
337                    from,
338                );
339                let res = Self::handle_incoming_and_drain_queue(
340                    send_to_network,
341                    WgKind::HandshakeInit(wg_init),
342                    &mut tunn,
343                );
344                // Derive the caller-visible forwarded-packet result before
345                // moving `session_data` into the active-tunnel entry below.
346                let handled = Self::incoming_packet_result(res, session_data.clone(), packet_now);
347                e.insert_entry(ActiveTunnel { peer_static, tunn });
348                handled
349            }
350            (_, _p) => {
351                tracing::debug!(remote = ?from, "received unexpected packet kind for new entry");
352                HandleIncomingPacketResult::Result {
353                    result: TunnResult::Err(WireGuardError::InvalidPacket),
354                }
355            }
356        }
357    }
358
359    /// Handles an outgoing packet sent through the tunnel identified by the
360    /// remote socket address `to`.
361    pub fn handle_outgoing_packet(&mut self, packet: Packet, to: SocketAddr) -> Option<WgKind> {
362        self.handle_outgoing_packet_with_session(packet, to)
363            .and_then(HandleOutgoingPacketResult::into_packet)
364    }
365
366    /// Handles an outgoing packet and returns the active tunnel session used to
367    /// admit the payload into the tunnel pipeline.
368    ///
369    /// This re-checks authorization on the outgoing path. If the active tunnel
370    /// no longer has current authorization, the packet is dropped and `None` is
371    /// returned even when the tunnel state itself still exists.
372    #[tracing::instrument(skip_all, fields(remote = %to))]
373    pub fn handle_outgoing_packet_with_session(
374        &mut self,
375        packet: Packet,
376        to: SocketAddr,
377    ) -> Option<HandleOutgoingPacketResult<T::SessionData>> {
378        let Some(active_tunnel) = self.active_tunnels.get_mut(&to) else {
379            tracing::error!(to=?to, "No tunnel for outgoing packet found.");
380            return None;
381        };
382        let packet_now = Instant::now();
383        let Some(session_data) = self
384            .authz
385            .is_authorized(packet_now, active_tunnel.peer_static.as_bytes())
386        else {
387            tracing::debug!(remote = ?to, peer_static = ?active_tunnel.peer_static, "dropping outgoing packet for unauthorized peer");
388            return None;
389        };
390        Some(Self::outgoing_packet_result(
391            active_tunnel
392                .tunn
393                .handle_outgoing_packet(packet.into_bytes()),
394            packet_now,
395            session_data,
396        ))
397    }
398
399    /// Update timers of all tunnels. Generate corresponding keepalive or
400    /// session handshake initializations.
401    ///
402    /// As a result of this call, all expired tunnels are removed. Note that
403    /// this is not the same as unauthorized tunnels.
404    ///
405    /// Callers are expected to invoke this periodically; it is also where the rate limiter's
406    /// under-load counter is reset. Without that reset the counter only ever grows and the
407    /// server eventually treats itself as permanently under load.
408    pub fn update_timers(&mut self) -> Vec<(SocketAddr, WgKind)> {
409        // Self-throttled to the rate limiter's own reset period, so calling this more often
410        // than once per second is harmless.
411        self.rate_limiter.try_reset_count();
412
413        let mut res = vec![];
414        self.active_tunnels.retain(|k, active_tunnel| {
415            match active_tunnel.tunn.update_timers() {
416                Ok(Some(wg)) => res.push((*k, wg)),
417                Ok(None) => {},
418                Err(e) => tracing::error!(err=?e, remote_sockaddr=?k, "error when updating timers on tunnel"),
419            }
420
421            !active_tunnel.tunn.is_expired()
422        });
423        res
424    }
425
426    fn handle_incoming_and_drain_queue(
427        q: &mut VecDeque<WgKind>,
428        p: WgKind,
429        tunn: &mut Tunn,
430    ) -> TunnResult {
431        let r = match tunn.handle_incoming_packet(p) {
432            TunnResult::WriteToNetwork(p) => {
433                q.push_back(p);
434                TunnResult::Done
435            }
436            // keep alive
437            TunnResult::WriteToTunnel(p) if p.is_empty() => TunnResult::Done,
438            r => r,
439        };
440        for p in tunn.get_queued_packets() {
441            q.push_back(p);
442        }
443        r
444    }
445}
446
447/// Authorization layer for the snaptun server.
448pub trait SnapTunAuthorization: Send + Sync {
449    /// Immutable session data that downstream dataplane consumers may read.
450    type SessionData: Clone + Send + Sync + 'static;
451
452    /// Returns the current session data iff the peer is allowed to send traffic.
453    fn is_authorized(&self, now: Instant, identity: &[u8; 32]) -> Option<Arc<Self::SessionData>>;
454}
455
456#[cfg(test)]
457mod tests {
458    use std::{
459        collections::{HashMap, VecDeque},
460        net::SocketAddr,
461        sync::{Arc, Mutex},
462        time::Duration,
463    };
464
465    use ana_gotatun::{
466        noise::{Tunn, TunnResult, errors::WireGuardError, rate_limiter::RateLimiter},
467        packet::{IpNextProtocol, Packet, WgKind},
468        x25519,
469    };
470    use zerocopy::IntoBytes;
471
472    use crate::{
473        scion_packet::{Scion, ScionHeader},
474        server::{
475            HandleIncomingPacketResult, HandleOutgoingPacketResult, SnapTunAuthorization,
476            SnapTunServer,
477        },
478    };
479
480    type ResultT = Result<(), Box<dyn std::error::Error>>;
481
482    struct TrivialAuthz;
483
484    impl SnapTunAuthorization for TrivialAuthz {
485        type SessionData = ();
486
487        fn is_authorized(
488            &self,
489            _now: std::time::Instant,
490            _ident: &[u8; 32],
491        ) -> Option<Arc<Self::SessionData>> {
492            Some(Arc::new(()))
493        }
494    }
495
496    #[derive(Debug, Clone, PartialEq, Eq)]
497    struct MutableSessionData {
498        jti: &'static str,
499        pssid: &'static str,
500        tags: Vec<(&'static str, &'static str)>,
501    }
502
503    #[derive(Default)]
504    struct MutableAuthz {
505        sessions: Mutex<HashMap<[u8; 32], Arc<MutableSessionData>>>,
506    }
507
508    impl MutableAuthz {
509        fn set_session_data(&self, identity: [u8; 32], session_data: MutableSessionData) {
510            self.sessions
511                .lock()
512                .unwrap()
513                .insert(identity, Arc::new(session_data));
514        }
515    }
516
517    impl SnapTunAuthorization for MutableAuthz {
518        type SessionData = MutableSessionData;
519
520        fn is_authorized(
521            &self,
522            _now: std::time::Instant,
523            ident: &[u8; 32],
524        ) -> Option<Arc<Self::SessionData>> {
525            self.sessions.lock().unwrap().get(ident).cloned()
526        }
527    }
528
529    fn test_packet<const N: usize>(payload: [u8; N]) -> Packet {
530        let packet = Scion {
531            header: ScionHeader::new(
532                0,
533                0xAA,
534                0xABCDE,
535                payload.len() as _,
536                IpNextProtocol::Udp,
537                7,
538                0x0123_4567_89AB_CDEF,
539                0xFEDC_BA98_7654_3210,
540            ),
541            payload,
542        };
543        Packet::copy_from(packet.as_bytes())
544    }
545
546    fn establish_tunnel<T: SnapTunAuthorization>(
547        snaptun_server: &mut SnapTunServer<T>,
548        tunn_client: &mut Tunn,
549        packet: &Packet,
550        sockaddr_client: SocketAddr,
551        send_to_network: &mut VecDeque<WgKind>,
552    ) {
553        let Some(WgKind::HandshakeInit(hs_init)) =
554            tunn_client.handle_outgoing_packet(Packet::copy_from(packet))
555        else {
556            panic!("expected handshake init")
557        };
558
559        snaptun_server.handle_incoming_packet(
560            Packet::copy_from(hs_init.as_bytes()),
561            sockaddr_client,
562            send_to_network,
563        );
564        dispatch_one(tunn_client, send_to_network);
565    }
566
567    #[test]
568    fn connect_with_multiple_clients() -> ResultT {
569        let sockaddr_client0: SocketAddr = "192.168.1.1:1234".parse().unwrap();
570        let static_client0 = x25519::StaticSecret::from([0u8; 32]);
571        let sockaddr_client1: SocketAddr = "192.168.1.2:4321".parse().unwrap();
572        let static_client1 = x25519::StaticSecret::from([1u8; 32]);
573        let sockaddr_server: SocketAddr = "10.0.0.1:5001".parse().unwrap();
574        let static_server = x25519::StaticSecret::from([2u8; 32]);
575        let static_server_public = x25519::PublicKey::from(&static_server);
576
577        let rate_limiter = Arc::new(RateLimiter::new(&static_server_public, 100));
578        let mut snaptun_server =
579            SnapTunServer::new(static_server, rate_limiter.clone(), Arc::new(TrivialAuthz));
580
581        let mut send_to_network = VecDeque::<WgKind>::new();
582
583        let test_packet0 = test_packet(*b"TEST0");
584        let test_packet1 = test_packet(*b"TEST1");
585
586        let mut tunn_client0 = Tunn::new(
587            static_client0,
588            static_server_public,
589            None,
590            None,
591            0,
592            rate_limiter.clone(),
593            sockaddr_server,
594        );
595
596        let mut tunn_client1 = Tunn::new(
597            static_client1,
598            static_server_public,
599            None,
600            None,
601            0,
602            rate_limiter,
603            sockaddr_server,
604        );
605
606        /* handshake 0 */
607        establish_tunnel(
608            &mut snaptun_server,
609            &mut tunn_client0,
610            &test_packet0,
611            sockaddr_client0,
612            &mut send_to_network,
613        );
614        assert_eq!(
615            tunn_client0.get_initiator_remote_sockaddr(),
616            Some(sockaddr_client0)
617        );
618
619        /* handshake 1 */
620        establish_tunnel(
621            &mut snaptun_server,
622            &mut tunn_client1,
623            &test_packet1,
624            sockaddr_client1,
625            &mut send_to_network,
626        );
627        assert_eq!(
628            tunn_client1.get_initiator_remote_sockaddr(),
629            Some(sockaddr_client1)
630        );
631
632        /* send C0 -> S */
633        let Some(WgKind::Data(p)) = tunn_client0.get_queued_packets().next() else {
634            panic!("expected packet to be queued");
635        };
636
637        let TunnResult::WriteToTunnel(p) = snaptun_server.handle_incoming_packet(
638            Packet::copy_from(p.as_bytes()),
639            sockaddr_client0,
640            &mut send_to_network,
641        ) else {
642            panic!("Expected packet to be processed")
643        };
644        assert_eq!(p.as_bytes(), test_packet0.as_bytes());
645
646        /* send C1 -> S */
647        // before we can send a packet to client1, we need to send a packet from
648        // client1 so the server starts using the session.
649        let Some(WgKind::Data(p1)) = tunn_client1.get_queued_packets().next() else {
650            panic!("expected packet to be queued");
651        };
652
653        let TunnResult::WriteToTunnel(p1) = snaptun_server.handle_incoming_packet(
654            Packet::copy_from(p1.as_bytes()),
655            sockaddr_client1,
656            &mut send_to_network,
657        ) else {
658            panic!("expected packet to be received on server side");
659        };
660        assert_eq!(p1.as_bytes(), test_packet1.as_bytes());
661
662        /* send S -> C1 */
663        let res = snaptun_server.handle_outgoing_packet(p, sockaddr_client1);
664        let Some(p @ WgKind::Data(_)) = res else {
665            panic!("expected packet to be sent back to client")
666        };
667
668        let TunnResult::WriteToTunnel(p) = tunn_client1.handle_incoming_packet(p) else {
669            panic!("expected packet to be sent back to client")
670        };
671
672        assert_eq!(p.as_bytes(), test_packet0.as_bytes());
673
674        Ok(())
675    }
676
677    #[test]
678    fn outgoing_packet_with_session_returns_active_session() {
679        let sockaddr_client: SocketAddr = "192.168.1.1:1234".parse().unwrap();
680        let static_client = x25519::StaticSecret::from([0u8; 32]);
681        let sockaddr_server: SocketAddr = "10.0.0.1:5001".parse().unwrap();
682        let static_server = x25519::StaticSecret::from([2u8; 32]);
683        let static_server_public = x25519::PublicKey::from(&static_server);
684
685        let rate_limiter = Arc::new(RateLimiter::new(&static_server_public, 100));
686        let mut snaptun_server =
687            SnapTunServer::new(static_server, rate_limiter.clone(), Arc::new(TrivialAuthz));
688        let mut send_to_network = VecDeque::<WgKind>::new();
689
690        let test_packet = test_packet(*b"TEST");
691
692        let mut tunn_client = Tunn::new(
693            static_client,
694            static_server_public,
695            None,
696            None,
697            0,
698            rate_limiter,
699            sockaddr_server,
700        );
701
702        establish_tunnel(
703            &mut snaptun_server,
704            &mut tunn_client,
705            &test_packet,
706            sockaddr_client,
707            &mut send_to_network,
708        );
709
710        let Some(WgKind::Data(client_data)) = tunn_client.get_queued_packets().next() else {
711            panic!("expected packet to be queued");
712        };
713        let TunnResult::WriteToTunnel(server_plaintext) = snaptun_server.handle_incoming_packet(
714            Packet::copy_from(client_data.as_bytes()),
715            sockaddr_client,
716            &mut send_to_network,
717        ) else {
718            panic!("expected packet to be processed")
719        };
720
721        let handled = snaptun_server
722            .handle_outgoing_packet_with_session(server_plaintext, sockaddr_client)
723            .expect("expected packet to be encapsulated");
724        let HandleOutgoingPacketResult {
725            network_packet: Some(WgKind::Data(encapsulated)),
726            processed_at: _,
727            session_data,
728        } = handled
729        else {
730            panic!("expected encapsulated data packet")
731        };
732        assert_eq!(session_data.as_ref(), &());
733
734        let TunnResult::WriteToTunnel(plaintext) =
735            tunn_client.handle_incoming_packet(WgKind::Data(encapsulated))
736        else {
737            panic!("expected packet to be delivered back to client")
738        };
739        assert_eq!(plaintext.as_bytes(), test_packet.as_bytes());
740    }
741
742    #[test]
743    fn established_tunnel_refreshes_session_data_for_later_packets() {
744        let sockaddr_client: SocketAddr = "192.168.1.1:1234".parse().unwrap();
745        let static_client = x25519::StaticSecret::from([0u8; 32]);
746        let client_identity = x25519::PublicKey::from(&static_client);
747        let sockaddr_server: SocketAddr = "10.0.0.1:5001".parse().unwrap();
748        let static_server = x25519::StaticSecret::from([2u8; 32]);
749        let static_server_public = x25519::PublicKey::from(&static_server);
750        let rate_limiter = Arc::new(RateLimiter::new(&static_server_public, 100));
751        let authz = Arc::new(MutableAuthz::default());
752        let original_session = MutableSessionData {
753            jti: "original-jti",
754            pssid: "original-pssid",
755            tags: vec![("subject_id", "subject-1"), ("scope", "basic")],
756        };
757        authz.set_session_data(*client_identity.as_bytes(), original_session);
758
759        let mut snaptun_server =
760            SnapTunServer::new(static_server, rate_limiter.clone(), authz.clone());
761        let mut send_to_network = VecDeque::<WgKind>::new();
762        let test_packet = test_packet(*b"TEST");
763
764        let mut tunn_client = Tunn::new(
765            static_client,
766            static_server_public,
767            None,
768            None,
769            0,
770            rate_limiter,
771            sockaddr_server,
772        );
773
774        establish_tunnel(
775            &mut snaptun_server,
776            &mut tunn_client,
777            &test_packet,
778            sockaddr_client,
779            &mut send_to_network,
780        );
781
782        let refreshed_session = MutableSessionData {
783            jti: "refreshed-jti",
784            pssid: "refreshed-pssid",
785            tags: vec![("subject_id", "subject-2"), ("scope", "premium")],
786        };
787        authz.set_session_data(*client_identity.as_bytes(), refreshed_session.clone());
788
789        let Some(WgKind::Data(client_data)) = tunn_client.get_queued_packets().next() else {
790            panic!("expected packet to be queued");
791        };
792        let HandleIncomingPacketResult::Forwarded {
793            packet: server_plaintext,
794            session_data,
795            ..
796        } = snaptun_server.handle_incoming_packet_with_session(
797            Packet::copy_from(client_data.as_bytes()),
798            sockaddr_client,
799            &mut send_to_network,
800        )
801        else {
802            panic!("expected forwarded packet with refreshed session data")
803        };
804        assert_eq!(session_data.as_ref(), &refreshed_session);
805
806        let Some(HandleOutgoingPacketResult {
807            network_packet: Some(WgKind::Data(encapsulated)),
808            processed_at: _,
809            session_data,
810        }) = snaptun_server.handle_outgoing_packet_with_session(server_plaintext, sockaddr_client)
811        else {
812            panic!("expected encapsulated data packet with refreshed session data")
813        };
814        assert_eq!(session_data.as_ref(), &refreshed_session);
815
816        let TunnResult::WriteToTunnel(plaintext) =
817            tunn_client.handle_incoming_packet(WgKind::Data(encapsulated))
818        else {
819            panic!("expected packet to be delivered back to client")
820        };
821        assert_eq!(plaintext.as_bytes(), test_packet.as_bytes());
822    }
823
824    #[test]
825    fn outgoing_packet_with_session_returns_none_without_tunnel() {
826        let sockaddr_client: SocketAddr = "192.168.1.1:1234".parse().unwrap();
827        let static_server = x25519::StaticSecret::from([2u8; 32]);
828        let static_server_public = x25519::PublicKey::from(&static_server);
829        let rate_limiter = Arc::new(RateLimiter::new(&static_server_public, 100));
830        let mut snaptun_server =
831            SnapTunServer::new(static_server, rate_limiter, Arc::new(TrivialAuthz));
832
833        let payload = *b"TEST";
834        let test_packet = Scion {
835            header: ScionHeader::new(
836                0,
837                0xAA,
838                0xABCDE,
839                payload.len() as _,
840                IpNextProtocol::Udp,
841                7,
842                0x0123_4567_89AB_CDEF,
843                0xFEDC_BA98_7654_3210,
844            ),
845            payload,
846        };
847
848        assert!(
849            snaptun_server
850                .handle_outgoing_packet_with_session(
851                    Packet::copy_from(test_packet.as_bytes()),
852                    sockaddr_client
853                )
854                .is_none()
855        );
856    }
857
858    /// The rate limiter counts every handshake it verifies and demands a cookie once its limit
859    /// is reached, so the counter has to be reset periodically.
860    ///
861    /// [`Tunn::update_timers`] resets the shared limiter, which covers a server that has
862    /// tunnels to iterate over. This pins the case with no tunnel to piggyback on: handshakes
863    /// from unauthorized peers are counted but establish nothing, so without the reset in
864    /// [`SnapTunServer::update_timers`] the counter only ever grows and an idle server keeps
865    /// demanding cookies from every peer.
866    #[test]
867    fn update_timers_resets_the_rate_limiter_without_active_tunnels() {
868        let static_server = x25519::StaticSecret::from([2u8; 32]);
869        let static_server_public = x25519::PublicKey::from(&static_server);
870        let sockaddr_server: SocketAddr = "10.0.0.1:5001".parse().unwrap();
871
872        // A limit of one means the second handshake the server verifies is already under load.
873        // The empty `MutableAuthz` authorizes nobody, so no handshake creates a tunnel.
874        let mut snaptun_server = SnapTunServer::new(
875            static_server,
876            Arc::new(RateLimiter::new(&static_server_public, 1)),
877            Arc::new(MutableAuthz::default()),
878        );
879        // The clients get their own limiter so that only server-side handshakes are counted
880        // against the limit under test.
881        let client_rate_limiter = Arc::new(RateLimiter::new(&static_server_public, u64::MAX));
882
883        let handshake_from_new_client = |server: &mut SnapTunServer<MutableAuthz>, index: u8| {
884            let mut client = Tunn::new(
885                x25519::StaticSecret::from([index; 32]),
886                static_server_public,
887                None,
888                None,
889                0,
890                client_rate_limiter.clone(),
891                sockaddr_server,
892            );
893            let Some(WgKind::HandshakeInit(hs_init)) =
894                client.handle_outgoing_packet(test_packet([index]))
895            else {
896                panic!("expected handshake init")
897            };
898
899            // Each client uses its own address so every handshake is a fresh one rather than
900            // a repeat handshake on an existing tunnel.
901            let mut send_to_network = VecDeque::<WgKind>::new();
902            server.handle_incoming_packet(
903                Packet::copy_from(hs_init.as_bytes()),
904                SocketAddr::new("192.168.1.1".parse().unwrap(), 1234 + u16::from(index)),
905                &mut send_to_network,
906            );
907            send_to_network.pop_front()
908        };
909
910        // Below the limit the handshake reaches the authorization check, which rejects it
911        // without a reply. At the limit the rate limiter answers with a cookie first.
912        assert!(
913            handshake_from_new_client(&mut snaptun_server, 1).is_none(),
914            "the first handshake is below the limit and must reach authorization"
915        );
916        assert!(
917            matches!(
918                handshake_from_new_client(&mut snaptun_server, 2),
919                Some(WgKind::CookieReply(_))
920            ),
921            "the second handshake reaches the limit and must be answered with a cookie"
922        );
923        assert!(
924            snaptun_server.active_tunnels.is_empty(),
925            "no tunnel may exist, otherwise its timers would reset the limiter"
926        );
927
928        // The rate limiter resets against the monotonic clock, which neither tokio's paused
929        // time nor a mocked instant reaches from here, so this waits out the reset period.
930        #[allow(clippy::disallowed_methods)]
931        std::thread::sleep(Duration::from_millis(1_100));
932        snaptun_server.update_timers();
933
934        assert!(
935            handshake_from_new_client(&mut snaptun_server, 3).is_none(),
936            "after the reset period an idle server must stop demanding cookies"
937        );
938    }
939
940    fn dispatch_one(tunn: &mut Tunn, packets: &mut VecDeque<WgKind>) -> TunnResult {
941        if let Some(packet) = packets.pop_front() {
942            return tunn.handle_incoming_packet(packet);
943        }
944        TunnResult::Done
945    }
946
947    /// Builds a server, a rate limiter shared with the clients, and an authorization layer that
948    /// holds the given identities. Each identity gets session data named after its index.
949    fn server_with_authorized_identities(
950        identities: &[&x25519::StaticSecret],
951    ) -> (
952        SnapTunServer<MutableAuthz>,
953        Arc<RateLimiter>,
954        x25519::PublicKey,
955    ) {
956        let static_server = x25519::StaticSecret::from([2u8; 32]);
957        let static_server_public = x25519::PublicKey::from(&static_server);
958        let rate_limiter = Arc::new(RateLimiter::new(&static_server_public, 100));
959
960        let authz = Arc::new(MutableAuthz::default());
961        for (index, identity) in identities.iter().enumerate() {
962            authz.set_session_data(
963                *x25519::PublicKey::from(*identity).as_bytes(),
964                session_data_of(index),
965            );
966        }
967
968        (
969            SnapTunServer::new(static_server, rate_limiter.clone(), authz),
970            rate_limiter,
971            static_server_public,
972        )
973    }
974
975    fn session_data_of(index: usize) -> MutableSessionData {
976        let names = ["first", "second"];
977        MutableSessionData {
978            jti: names[index],
979            pssid: names[index],
980            tags: vec![],
981        }
982    }
983
984    fn client_tunn(
985        static_client: x25519::StaticSecret,
986        static_server_public: x25519::PublicKey,
987        rate_limiter: Arc<RateLimiter>,
988    ) -> Tunn {
989        let sockaddr_server: SocketAddr = "10.0.0.1:5001".parse().unwrap();
990        Tunn::new(
991            static_client,
992            static_server_public,
993            None,
994            None,
995            0,
996            rate_limiter,
997            sockaddr_server,
998        )
999    }
1000
1001    fn handshake_init_of(tunn: &mut Tunn, packet: Packet) -> Packet {
1002        let Some(WgKind::HandshakeInit(hs_init)) = tunn.handle_outgoing_packet(packet) else {
1003            panic!("expected handshake init")
1004        };
1005        Packet::copy_from(hs_init.as_bytes())
1006    }
1007
1008    /// A socket address belongs to the identity that established the tunnel on it. A second
1009    /// identity must not take that address over, because the address is the address of an
1010    /// endhost, and an initiator can spoof it.
1011    ///
1012    /// The handshake initiation of the second identity goes to the tunnel of the first
1013    /// identity, and that tunnel rejects the static key of the initiator with
1014    /// [`WireGuardError::WrongKey`]. The server answers nothing and keeps the tunnel. The
1015    /// second identity is authorized here, so authorization alone does not earn an address.
1016    #[test]
1017    fn a_new_identity_cannot_take_over_the_address_of_an_active_tunnel() {
1018        let sockaddr_client: SocketAddr = "192.168.1.1:1234".parse().unwrap();
1019        let static_client_old = x25519::StaticSecret::from([0u8; 32]);
1020        let static_client_new = x25519::StaticSecret::from([1u8; 32]);
1021        let identity_old = x25519::PublicKey::from(&static_client_old);
1022
1023        let (mut snaptun_server, rate_limiter, static_server_public) =
1024            server_with_authorized_identities(&[&static_client_old, &static_client_new]);
1025        let mut send_to_network = VecDeque::<WgKind>::new();
1026
1027        let mut tunn_old = client_tunn(
1028            static_client_old,
1029            static_server_public,
1030            rate_limiter.clone(),
1031        );
1032        establish_tunnel(
1033            &mut snaptun_server,
1034            &mut tunn_old,
1035            &test_packet(*b"OLD"),
1036            sockaddr_client,
1037            &mut send_to_network,
1038        );
1039        send_to_network.clear();
1040
1041        let mut tunn_new = client_tunn(static_client_new, static_server_public, rate_limiter);
1042        let hs_init = handshake_init_of(&mut tunn_new, test_packet(*b"NEW"));
1043        let result =
1044            snaptun_server.handle_incoming_packet(hs_init, sockaddr_client, &mut send_to_network);
1045
1046        assert!(
1047            matches!(result, TunnResult::Err(WireGuardError::WrongKey)),
1048            "the handshake of the new identity must not be accepted, got {result:?}"
1049        );
1050        assert!(
1051            send_to_network.is_empty(),
1052            "the server must not answer the handshake of the new identity"
1053        );
1054        assert_eq!(
1055            snaptun_server.active_tunnels[&sockaddr_client].peer_static, identity_old,
1056            "the address must still hold the tunnel of the first identity"
1057        );
1058    }
1059
1060    /// A restarted client that pins its identity gets a new tunnel session on the same address at
1061    /// once, and does not have to wait for the old tunnel to expire.
1062    #[test]
1063    fn the_same_identity_re_establishes_a_tunnel_on_the_same_address() {
1064        let sockaddr_client: SocketAddr = "192.168.1.1:1234".parse().unwrap();
1065        let static_client = x25519::StaticSecret::from([0u8; 32]);
1066        let identity = x25519::PublicKey::from(&static_client);
1067
1068        let (mut snaptun_server, rate_limiter, static_server_public) =
1069            server_with_authorized_identities(&[&static_client]);
1070        let mut send_to_network = VecDeque::<WgKind>::new();
1071
1072        let mut tunn_first = client_tunn(
1073            static_client.clone(),
1074            static_server_public,
1075            rate_limiter.clone(),
1076        );
1077        establish_tunnel(
1078            &mut snaptun_server,
1079            &mut tunn_first,
1080            &test_packet(*b"ONE"),
1081            sockaddr_client,
1082            &mut send_to_network,
1083        );
1084        send_to_network.clear();
1085
1086        // The client restarts with the same static identity and reuses its source port.
1087        let second_packet = test_packet(*b"TWO");
1088        let mut tunn_second = client_tunn(static_client, static_server_public, rate_limiter);
1089        establish_tunnel(
1090            &mut snaptun_server,
1091            &mut tunn_second,
1092            &second_packet,
1093            sockaddr_client,
1094            &mut send_to_network,
1095        );
1096
1097        let Some(WgKind::Data(client_data)) = tunn_second.get_queued_packets().next() else {
1098            panic!("expected the restarted client to complete the handshake and queue a packet");
1099        };
1100        let HandleIncomingPacketResult::Forwarded {
1101            packet,
1102            session_data,
1103            ..
1104        } = snaptun_server.handle_incoming_packet_with_session(
1105            Packet::copy_from(client_data.as_bytes()),
1106            sockaddr_client,
1107            &mut send_to_network,
1108        )
1109        else {
1110            panic!("expected the packet of the restarted client to be forwarded")
1111        };
1112        assert_eq!(packet.as_bytes(), second_packet.as_bytes());
1113        assert_eq!(session_data.as_ref(), &session_data_of(0));
1114        assert_eq!(
1115            snaptun_server.active_tunnels[&sockaddr_client].peer_static, identity,
1116            "the address must still hold the tunnel of the same identity"
1117        );
1118    }
1119}