Skip to main content

l2tp/
handle.rs

1// SPDX-License-Identifier: MIT
2
3use std::{
4    fmt, io,
5    os::fd::AsRawFd,
6    sync::{Arc, PoisonError},
7};
8
9use futures::{Stream, StreamExt, channel::mpsc::UnboundedReceiver};
10use genetlink::{GenetlinkHandle, message::RawGenlMessage};
11use netlink_packet_core::{
12    NLM_F_ACK, NLM_F_DUMP, NLM_F_DUMP_INTR, NLM_F_MULTIPART, NLM_F_REQUEST,
13    NetlinkMessage, NetlinkPayload,
14};
15use netlink_packet_generic::GenlMessage;
16use netlink_packet_l2tp::{L2tpAttribute, L2tpMessage};
17use netlink_proto::{
18    Connection,
19    sys::{AsyncSocket, SocketAddr},
20};
21
22use crate::{
23    Events, L2tpIfName, L2tpIfNameExt, L2tpVersion, NetNs, PseudowireType,
24    SessionConfig, SessionHandle, SessionId, SessionInfo, SessionModify,
25    SessionStats, TunnelConfig, TunnelHandle, TunnelId, TunnelInfo,
26    TunnelSocket, TunnelStats,
27    link::LinkMonitor,
28    netlink::{self, SessionRef},
29};
30
31/// Name of the generic-netlink multicast group the kernel announces L2TP
32/// changes on (`L2TP_GENL_MCGROUP`).
33const EVENTS_GROUP: &str = "l2tp";
34
35type Unsolicited =
36    UnboundedReceiver<(NetlinkMessage<RawGenlMessage>, SocketAddr)>;
37
38/// Shared async handle for L2TP generic-netlink operations.
39#[derive(Clone)]
40pub struct L2tpHandle {
41    inner: Arc<L2tpInner>,
42}
43
44struct L2tpInner {
45    genl: GenetlinkHandle,
46    /// The resolved family, refreshed when the kernel no longer knows its
47    /// identifier (the `l2tp_netlink` module was reloaded).
48    family: std::sync::Mutex<Option<FamilyInfo>>,
49    /// Namespace the connection was opened in, for further connections.
50    netns: NetNs,
51}
52
53impl L2tpInner {
54    fn cached_family(&self) -> Option<FamilyInfo> {
55        *self.family.lock().unwrap_or_else(PoisonError::into_inner)
56    }
57
58    fn set_family(&self, family: FamilyInfo) {
59        *self.family.lock().unwrap_or_else(PoisonError::into_inner) =
60            Some(family);
61    }
62}
63
64#[derive(Debug, Clone, Copy)]
65struct FamilyInfo {
66    id: u16,
67    events_group: Option<u32>,
68}
69
70impl fmt::Debug for L2tpHandle {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.debug_struct("L2tpHandle")
73            .field("family", &self.inner.cached_family())
74            .field("netns", &self.inner.netns)
75            .finish_non_exhaustive()
76    }
77}
78
79/// Opens a generic-netlink connection in its owning namespace.
80fn open_connection(
81    netns: &NetNs,
82) -> crate::Result<(Connection<RawGenlMessage>, GenetlinkHandle, Unsolicited)> {
83    fn open()
84    -> io::Result<(Connection<RawGenlMessage>, GenetlinkHandle, Unsolicited)>
85    {
86        // netlink-sys registers its socket with Tokio's I/O driver through
87        // `AsyncFd`, which panics instead of failing when the runtime has no
88        // I/O driver. Tokio offers no way to check for the driver first.
89        std::panic::catch_unwind(genetlink::new_connection).map_err(|_| {
90            io::Error::other(
91                "tokio runtime with I/O enabled is required for L2tpHandle",
92            )
93        })?
94    }
95
96    // Avoid setns when already in the owning namespace, including inside
97    // a container. Otherwise create the socket on a temporary namespace
98    // thread and register it with this runtime's I/O driver.
99    let runtime = tokio::runtime::Handle::current();
100    Ok(netns.run_if_needed(move || {
101        let _guard = runtime.enter();
102        open()
103    })??)
104}
105
106impl L2tpHandle {
107    /// Create a new L2TP Generic Netlink handle.
108    ///
109    /// The calling thread's network namespace is retained, including when
110    /// inherited from a container. Further event connections use that same
111    /// namespace; opening them from another namespace requires the
112    /// privileges needed by `setns(2)`.
113    ///
114    /// This must be awaited while running inside a Tokio runtime with I/O
115    /// enabled, because the internal Generic Netlink connection task is
116    /// spawned onto the current Tokio executor. Without a runtime, or with a
117    /// runtime built without `enable_io`, this returns
118    /// [`Error::Io`](crate::Error::Io). Tokio reports a missing I/O driver by
119    /// panicking inside the netlink socket registration; that panic is caught
120    /// and turned into the error, so the panic hook still runs. The handle is
121    /// cheap to clone; clones share one netlink connection.
122    ///
123    /// Requests that refer to a tunnel or session the kernel does not know
124    /// fail with [`Error::KernelError`](crate::Error::KernelError) carrying
125    /// `ENODEV`. Every L2TP command needs `CAP_NET_ADMIN`; without it the
126    /// kernel replies `EPERM`.
127    pub async fn new() -> crate::Result<Self> {
128        Self::connect(None).await
129    }
130
131    /// Like [`new`](Self::new), but controls the L2TP subsystem of the
132    /// network namespace `netns`.
133    ///
134    /// Entering a different namespace needs `CAP_SYS_ADMIN`; see [`NetNs`].
135    /// Tunnel sockets for managed tunnels must be created in the same
136    /// namespace, for example inside [`NetNs::run`].
137    pub async fn new_in(netns: &NetNs) -> crate::Result<Self> {
138        Self::connect(Some(netns.try_clone()?)).await
139    }
140
141    async fn connect(netns: Option<NetNs>) -> crate::Result<Self> {
142        let runtime = tokio::runtime::Handle::try_current().map_err(|_| {
143            io::Error::other("tokio runtime is required for L2tpHandle")
144        })?;
145        let netns = match netns {
146            Some(netns) => netns,
147            None => NetNs::current()?,
148        };
149        let (mut connection, genl, _unsolicited) = open_connection(&netns)?;
150        connection.set_forward_ack(true);
151        connection.set_forward_done(true);
152        runtime.spawn(connection);
153
154        Ok(Self {
155            inner: Arc::new(L2tpInner {
156                genl,
157                family: std::sync::Mutex::new(None),
158                netns,
159            }),
160        })
161    }
162
163    /// Creates a managed tunnel using an owned socket fd.
164    ///
165    /// The returned [`TunnelHandle`] owns `socket`; when
166    /// that socket is dropped, the kernel tears down the tunnel.
167    /// UDP checksum settings from `config` are applied to `socket` before
168    /// registering it with the kernel.
169    ///
170    /// For IP encapsulation, `socket` must be bound to the same tunnel ID as
171    /// `config`; otherwise this returns
172    /// [`Error::TunnelIdMismatch`](crate::Error::TunnelIdMismatch).
173    /// [`TunnelSocket::for_config`] creates a matching socket, and
174    /// [`TunnelConfig::for_socket`] describes an existing one.
175    ///
176    /// Once started inside a Tokio runtime, creation continues through the
177    /// kernel's acknowledgement even if the caller cancels this future. The
178    /// socket stays owned until then; an unclaimed tunnel is dropped with
179    /// auto-delete enabled. The runtime must keep running for cleanup to
180    /// finish.
181    pub async fn create_tunnel(
182        &self,
183        config: TunnelConfig,
184        socket: TunnelSocket,
185    ) -> crate::Result<TunnelHandle> {
186        socket.configure(&config.encapsulation)?;
187        if let Some(conn_id) = socket.conn_id()
188            && conn_id != config.tunnel_id.0
189        {
190            return Err(crate::Error::TunnelIdMismatch {
191                socket: TunnelId(conn_id),
192                config: config.tunnel_id,
193            });
194        }
195        let message =
196            netlink::encode_tunnel_create(&config, Some(socket.as_raw_fd()))?;
197        let handle = self.clone();
198        run_creation(async move {
199            handle.send_ack(message).await?;
200            Ok(TunnelHandle {
201                tunnel_id: config.tunnel_id,
202                socket: Some(socket),
203                auto_delete: true,
204                handle,
205            })
206        })
207        .await
208    }
209
210    /// Creates an unmanaged tunnel (no owned socket fd in this process).
211    ///
212    /// The kernel creates/manages the underlying socket using netlink tunnel
213    /// address attributes. The tunnel outlives this process; it is removed
214    /// when the returned handle is dropped (unless auto-delete is disabled),
215    /// by [`TunnelHandle::delete`], or by [`L2tpHandle::delete_tunnel`].
216    /// Disabling UDP checksums for an IPv4-mapped peer returns
217    /// [`Error::UnmanagedUdpChecksum`](crate::Error::UnmanagedUdpChecksum),
218    /// and link-local endpoints
219    /// [`Error::UnmanagedLinkLocal`](crate::Error::UnmanagedLinkLocal); use a
220    /// managed socket for these.
221    ///
222    /// Once started inside a Tokio runtime, creation continues through the
223    /// kernel's acknowledgement even if the caller cancels this future. An
224    /// unclaimed tunnel is dropped with auto-delete enabled. The runtime must
225    /// keep running for this best-effort cleanup to finish.
226    pub async fn create_unmanaged_tunnel(
227        &self,
228        config: TunnelConfig,
229    ) -> crate::Result<TunnelHandle> {
230        let message = netlink::encode_tunnel_create(&config, None)?;
231        let handle = self.clone();
232        run_creation(async move {
233            handle.send_ack(message).await?;
234            Ok(TunnelHandle {
235                tunnel_id: config.tunnel_id,
236                socket: None,
237                auto_delete: true,
238                handle,
239            })
240        })
241        .await
242    }
243
244    /// Returns a handle for an existing tunnel after checking that the
245    /// kernel knows it.
246    ///
247    /// The handle has auto-delete disabled and no socket, so dropping it
248    /// leaves the tunnel alone; enable auto-delete or call
249    /// [`TunnelHandle::delete`] to take ownership.
250    pub async fn open_tunnel(
251        &self,
252        tunnel_id: TunnelId,
253    ) -> crate::Result<TunnelHandle> {
254        self.get_tunnel(tunnel_id).await?;
255        Ok(TunnelHandle {
256            tunnel_id,
257            socket: None,
258            auto_delete: false,
259            handle: self.clone(),
260        })
261    }
262
263    /// Creates a session in an existing tunnel.
264    ///
265    /// Session identifiers above `65535` are rejected with
266    /// [`Error::V2IdTooLarge`](crate::Error::V2IdTooLarge) when the parent
267    /// tunnel uses L2TPv2. Such identifiers require a parent-tunnel lookup
268    /// before creation; L2TPv3 accepts the full 32-bit range.
269    ///
270    /// Once started inside a Tokio runtime, creation continues through the
271    /// kernel's acknowledgement and follow-up lookup even if the caller cancels
272    /// this future. An unclaimed session is dropped with auto-delete enabled.
273    /// The runtime must keep running for this best-effort cleanup to finish.
274    ///
275    /// For Ethernet pseudowires the handle also carries the interface index
276    /// of the new `l2tpeth` device, captured from the kernel's registration
277    /// broadcast; see [`SessionHandle::ifindex`]. Give the session an
278    /// explicit, unique [`SessionConfig::ifname`] where interfaces may be
279    /// renamed by other software.
280    pub async fn create_session(
281        &self,
282        config: SessionConfig,
283    ) -> crate::Result<SessionHandle> {
284        let message = netlink::encode_session_create(&config)?;
285        let handle = self.clone();
286        // Subscribe before the request: the kernel queues the device's
287        // registration before it acknowledges the session.
288        let monitor = match config.pseudowire_type {
289            PseudowireType::Eth | PseudowireType::EthVlan => {
290                match LinkMonitor::open(&self.inner.netns) {
291                    Ok(monitor) => Some(monitor),
292                    Err(err) => {
293                        log::debug!(
294                            "l2tp: cannot watch link registrations, the \
295                             interface index will be looked up by name: {err}"
296                        );
297                        None
298                    }
299                }
300            }
301            _ => None,
302        };
303        run_creation(async move {
304            // The kernel stores 32-bit session IDs even for V2, whose wire
305            // format truncates them to 16 bits. Only query the parent when an
306            // ID is outside the range shared by both protocol versions.
307            if let Some(id) = [config.session_id, config.peer_session_id]
308                .into_iter()
309                .find(|id| u16::try_from(id.0).is_err())
310                && handle.get_tunnel(config.tunnel_id).await?.version
311                    == L2tpVersion::V2
312            {
313                return Err(crate::Error::V2IdTooLarge(id.0));
314            }
315            handle.send_ack(message).await?;
316            handle.get_created_session(config, monitor).await
317        })
318        .await
319    }
320
321    async fn get_created_session(
322        &self,
323        config: SessionConfig,
324        monitor: Option<LinkMonitor>,
325    ) -> crate::Result<SessionHandle> {
326        // Own the acknowledged session before the next await, so cancellation
327        // also triggers best-effort deletion.
328        let mut session = SessionHandle {
329            tunnel_id: config.tunnel_id,
330            session_id: config.session_id,
331            ifname: None,
332            ifindex: None,
333            auto_delete: true,
334            handle: self.clone(),
335        };
336        match self.get_session(config.tunnel_id, config.session_id).await {
337            Ok(info) => {
338                session.ifname = info.ifname;
339                session.ifindex = match (monitor, &info.ifname) {
340                    (Some(monitor), Some(ifname)) => {
341                        self.registered_index(&monitor, ifname)
342                    }
343                    (None, Some(ifname)) => self.index_by_name(ifname),
344                    (_, None) => None,
345                };
346                Ok(session)
347            }
348            Err(err) => {
349                let _ = session.delete().await;
350                Err(err)
351            }
352        }
353    }
354
355    /// The index the registration broadcast reported for `ifname`, or the
356    /// index the name resolves to now if the notification was lost.
357    fn registered_index(
358        &self,
359        monitor: &LinkMonitor,
360        ifname: &L2tpIfName,
361    ) -> Option<u32> {
362        match monitor.registered_index(ifname) {
363            Ok(Some(index)) => return Some(index),
364            Ok(None) => log::debug!(
365                "l2tp: no registration notification for {ifname:?}; looking \
366                 the index up by name"
367            ),
368            Err(err) => log::debug!(
369                "l2tp: link notifications were lost ({err}); looking the \
370                 index of {ifname:?} up by name"
371            ),
372        }
373        // The monitor's socket lives in the handle's namespace.
374        match crate::socket::interface_index(monitor.as_raw_fd(), ifname) {
375            Ok(index) => Some(index),
376            Err(err) => {
377                log::debug!("l2tp: cannot resolve {ifname:?}: {err}");
378                None
379            }
380        }
381    }
382
383    /// Resolves `ifname` in the handle's namespace, which is only correct
384    /// while the interface still bears that name.
385    fn index_by_name(&self, ifname: &L2tpIfName) -> Option<u32> {
386        let name = *ifname;
387        match self.inner.netns.run_if_needed(move || name.index()) {
388            Ok(Ok(index)) => Some(index),
389            Ok(Err(err)) => {
390                log::debug!("l2tp: cannot resolve {ifname:?}: {err}");
391                None
392            }
393            Err(err) => {
394                log::debug!(
395                    "l2tp: cannot enter the namespace to resolve {ifname:?}: \
396                     {err}"
397                );
398                None
399            }
400        }
401    }
402
403    /// Returns a handle for an existing session after checking that the
404    /// kernel knows it; auto-delete is disabled.
405    ///
406    /// The interface index is looked up by the session's creation-time
407    /// name; see [`SessionHandle::ifindex`].
408    pub async fn open_session(
409        &self,
410        tunnel_id: TunnelId,
411        session_id: SessionId,
412    ) -> crate::Result<SessionHandle> {
413        let info = self.get_session(tunnel_id, session_id).await?;
414        Ok(SessionHandle {
415            tunnel_id,
416            session_id,
417            ifindex: info.ifname.as_ref().and_then(|n| self.index_by_name(n)),
418            ifname: info.ifname,
419            auto_delete: false,
420            handle: self.clone(),
421        })
422    }
423
424    /// Returns a handle for the existing Ethernet pseudowire session whose
425    /// interface is `ifname`; auto-delete is disabled.
426    ///
427    /// `ifname` is the name the interface had when the session was created;
428    /// see [`get_session_by_ifname`](Self::get_session_by_ifname). The
429    /// interface index is looked up by that name too.
430    pub async fn open_session_by_ifname(
431        &self,
432        ifname: &L2tpIfName,
433    ) -> crate::Result<SessionHandle> {
434        let info = self.get_session_by_ifname(ifname).await?;
435        Ok(SessionHandle {
436            tunnel_id: info.tunnel_id,
437            session_id: info.session_id,
438            ifindex: info.ifname.as_ref().and_then(|n| self.index_by_name(n)),
439            ifname: info.ifname,
440            auto_delete: false,
441            handle: self.clone(),
442        })
443    }
444
445    /// Returns information for a single tunnel.
446    pub async fn get_tunnel(
447        &self,
448        tunnel_id: TunnelId,
449    ) -> crate::Result<TunnelInfo> {
450        let replies = self
451            .send_and_collect(
452                netlink::encode_tunnel_get(tunnel_id),
453                NLM_F_REQUEST,
454            )
455            .await?;
456        let attrs = first_attributes(&replies, "empty reply to tunnel get")?;
457        netlink::decode_tunnel_info(attrs)
458    }
459
460    /// Returns information for a single session.
461    pub async fn get_session(
462        &self,
463        tunnel_id: TunnelId,
464        session_id: SessionId,
465    ) -> crate::Result<SessionInfo> {
466        self.get_session_ref(SessionRef::Ids(tunnel_id, session_id))
467            .await
468    }
469
470    /// Returns information for the Ethernet pseudowire session whose
471    /// interface is `ifname`.
472    ///
473    /// The kernel matches `ifname` against the name it recorded when the
474    /// session was created. It does not follow later renames of the
475    /// `l2tpeth` device, so a renamed interface is still found under its
476    /// original name and never under its current one. An application that
477    /// renames interfaces should resolve the reported name to its interface
478    /// index right after creation with
479    /// [`L2tpIfNameExt::index`](crate::L2tpIfNameExt::index) and address
480    /// the device by that index from then on. Only L2TPv3 sessions are
481    /// searched.
482    pub async fn get_session_by_ifname(
483        &self,
484        ifname: &L2tpIfName,
485    ) -> crate::Result<SessionInfo> {
486        self.get_session_ref(SessionRef::IfName(*ifname)).await
487    }
488
489    async fn get_session_ref(
490        &self,
491        session: SessionRef,
492    ) -> crate::Result<SessionInfo> {
493        let replies = self
494            .send_and_collect(
495                netlink::encode_session_get(session),
496                NLM_F_REQUEST,
497            )
498            .await?;
499        let attrs = first_attributes(&replies, "empty reply to session get")?;
500        netlink::decode_session_info(attrs)
501    }
502
503    /// Lists all tunnels.
504    ///
505    /// If the kernel's tables change during the dump, this fails with an error
506    /// whose [`kind`](crate::Error::kind) is [`io::ErrorKind::Interrupted`];
507    /// retrying usually succeeds.
508    pub async fn list_tunnels(&self) -> crate::Result<Vec<TunnelInfo>> {
509        let replies = self
510            .send_and_collect(
511                netlink::encode_tunnel_get_dump(),
512                NLM_F_REQUEST | NLM_F_DUMP,
513            )
514            .await?;
515        let mut out = Vec::with_capacity(replies.len());
516        for reply in replies {
517            out.push(netlink::decode_tunnel_info(&reply.payload.attributes)?);
518        }
519        Ok(out)
520    }
521
522    /// Lists all sessions that belong to a tunnel.
523    pub async fn list_sessions(
524        &self,
525        tunnel_id: TunnelId,
526    ) -> crate::Result<Vec<SessionInfo>> {
527        // The kernel's session dump ignores L2TP_ATTR_CONN_ID, so filter here.
528        let mut sessions = self.list_all_sessions().await?;
529        sessions.retain(|session| session.tunnel_id == tunnel_id);
530        Ok(sessions)
531    }
532
533    /// Lists all sessions across all tunnels.
534    ///
535    /// If the kernel's tables change during the dump, this fails with an error
536    /// whose [`kind`](crate::Error::kind) is [`io::ErrorKind::Interrupted`];
537    /// retrying usually succeeds.
538    pub async fn list_all_sessions(&self) -> crate::Result<Vec<SessionInfo>> {
539        let replies = self
540            .send_and_collect(
541                netlink::encode_session_get_dump(),
542                NLM_F_REQUEST | NLM_F_DUMP,
543            )
544            .await?;
545        let mut out = Vec::with_capacity(replies.len());
546        for reply in replies {
547            out.push(netlink::decode_session_info(&reply.payload.attributes)?);
548        }
549        Ok(out)
550    }
551
552    /// Deletes a tunnel and its sessions.
553    ///
554    /// The kernel acknowledges the request and then tears the tunnel down
555    /// on a work queue, during which lookups still find it and creating a
556    /// tunnel with the same identifier fails with `EEXIST`. This therefore
557    /// also waits, for up to about two seconds, until a lookup reports
558    /// `ENODEV`. Once the deletion is acknowledged it is not undone: a
559    /// lookup that fails otherwise is returned as is, and a tunnel still
560    /// listed after the wait is reported as
561    /// [`Error::DeletionPending`](crate::Error::DeletionPending), which
562    /// callers should treat as deleted, because repeating the request then
563    /// fails with `ENODEV`. The wait needs neither a Tokio timer nor a free
564    /// blocking thread.
565    pub async fn delete_tunnel(
566        &self,
567        tunnel_id: TunnelId,
568    ) -> crate::Result<()> {
569        self.send_ack(netlink::encode_tunnel_delete(tunnel_id))
570            .await?;
571        self.wait_tunnel_removed(tunnel_id).await
572    }
573
574    async fn wait_tunnel_removed(
575        &self,
576        tunnel_id: TunnelId,
577    ) -> crate::Result<()> {
578        const POLLS: u32 = 200;
579        const INTERVAL: std::time::Duration =
580            std::time::Duration::from_millis(10);
581        // Most tunnels are gone by the first lookup; only pace the rest.
582        if self.tunnel_removed(tunnel_id).await? {
583            return Ok(());
584        }
585        // A thread of its own paces the polls and exits once the receiver is
586        // gone, so the wait depends neither on the runtime's timer driver
587        // nor on a spare thread in its blocking pool.
588        let (tick_tx, mut tick_rx) = tokio::sync::mpsc::unbounded_channel();
589        std::thread::Builder::new()
590            .name("l2tp-delete-wait".to_string())
591            .spawn(move || {
592                loop {
593                    std::thread::sleep(INTERVAL);
594                    if tick_tx.send(()).is_err() {
595                        break;
596                    }
597                }
598            })?;
599        for _ in 1..POLLS {
600            if tick_rx.recv().await.is_none() {
601                return Err(crate::Error::other("the deletion wait stopped"));
602            }
603            if self.tunnel_removed(tunnel_id).await? {
604                return Ok(());
605            }
606        }
607        Err(crate::Error::DeletionPending(tunnel_id))
608    }
609
610    async fn tunnel_removed(&self, tunnel_id: TunnelId) -> crate::Result<bool> {
611        match self.get_tunnel(tunnel_id).await {
612            Ok(_) => Ok(false),
613            Err(err) if err.raw_os_error() == Some(libc::ENODEV) => Ok(true),
614            Err(err) => Err(err),
615        }
616    }
617
618    /// Deletes a session from a tunnel.
619    pub async fn delete_session(
620        &self,
621        tunnel_id: TunnelId,
622        session_id: SessionId,
623    ) -> crate::Result<()> {
624        self.send_ack(netlink::encode_session_delete(SessionRef::Ids(
625            tunnel_id, session_id,
626        )))
627        .await
628    }
629
630    /// Deletes the Ethernet pseudowire session whose interface is `ifname`.
631    ///
632    /// `ifname` is the name the interface had when the session was created;
633    /// see [`get_session_by_ifname`](Self::get_session_by_ifname).
634    pub async fn delete_session_by_ifname(
635        &self,
636        ifname: &L2tpIfName,
637    ) -> crate::Result<()> {
638        self.send_ack(netlink::encode_session_delete(SessionRef::IfName(
639            *ifname,
640        )))
641        .await
642    }
643
644    /// Returns tunnel statistics.
645    pub async fn tunnel_stats(
646        &self,
647        tunnel_id: TunnelId,
648    ) -> crate::Result<TunnelStats> {
649        let replies = self
650            .send_and_collect(
651                netlink::encode_tunnel_get(tunnel_id),
652                NLM_F_REQUEST,
653            )
654            .await?;
655        let attrs = first_attributes(&replies, "empty reply to tunnel get")?;
656        netlink::decode_tunnel_stats(attrs)
657    }
658
659    /// Returns session statistics.
660    pub async fn session_stats(
661        &self,
662        tunnel_id: TunnelId,
663        session_id: SessionId,
664    ) -> crate::Result<SessionStats> {
665        self.session_stats_ref(SessionRef::Ids(tunnel_id, session_id))
666            .await
667    }
668
669    /// Returns the statistics of the Ethernet pseudowire session whose
670    /// interface is `ifname`.
671    ///
672    /// `ifname` is the name the interface had when the session was created;
673    /// see [`get_session_by_ifname`](Self::get_session_by_ifname).
674    pub async fn session_stats_by_ifname(
675        &self,
676        ifname: &L2tpIfName,
677    ) -> crate::Result<SessionStats> {
678        self.session_stats_ref(SessionRef::IfName(*ifname)).await
679    }
680
681    async fn session_stats_ref(
682        &self,
683        session: SessionRef,
684    ) -> crate::Result<SessionStats> {
685        let replies = self
686            .send_and_collect(
687                netlink::encode_session_get(session),
688                NLM_F_REQUEST,
689            )
690            .await?;
691        let attrs = first_attributes(&replies, "empty reply to session get")?;
692        netlink::decode_session_stats(attrs)
693    }
694
695    /// Applies mutable session parameters.
696    pub async fn modify_session(
697        &self,
698        tunnel_id: TunnelId,
699        session_id: SessionId,
700        params: SessionModify,
701    ) -> crate::Result<()> {
702        self.send_ack(netlink::encode_session_modify(
703            SessionRef::Ids(tunnel_id, session_id),
704            &params,
705        ))
706        .await
707    }
708
709    /// Applies mutable session parameters to the Ethernet pseudowire session
710    /// whose interface is `ifname`.
711    ///
712    /// `ifname` is the name the interface had when the session was created;
713    /// see [`get_session_by_ifname`](Self::get_session_by_ifname).
714    pub async fn modify_session_by_ifname(
715        &self,
716        ifname: &L2tpIfName,
717        params: SessionModify,
718    ) -> crate::Result<()> {
719        self.send_ack(netlink::encode_session_modify(
720            SessionRef::IfName(*ifname),
721            &params,
722        ))
723        .await
724    }
725
726    /// Subscribes to the kernel's L2TP notifications.
727    ///
728    /// The stream reports kernel multicast notifications about tunnels and
729    /// sessions created, modified or deleted through netlink commands.
730    /// Its socket belongs to the handle's original network namespace.
731    ///
732    /// Some kernels, including Linux 6.8, multicast L2TP events into every
733    /// network namespace, including containers, without identifying the
734    /// originating namespace. Such events cannot be reliably filtered by
735    /// tunnel/session IDs, which can collide across namespaces. Treat events
736    /// as hints to reconcile state through this handle's query methods,
737    /// rather than instructions to mutate the named objects.
738    ///
739    /// The kernel announces nothing for the teardown it performs
740    /// on its own: a tunnel whose managed socket was closed, the sessions
741    /// removed together with a deleted tunnel, or a PPP session whose
742    /// `PPPoL2TP` socket was released. Reconcile with
743    /// [`list_tunnels`](Self::list_tunnels) and
744    /// [`list_all_sessions`](Self::list_all_sessions) to catch those.
745    ///
746    /// The stream uses its own netlink connection on the current runtime;
747    /// dropping it leaves the multicast group. Joining needs no privilege.
748    /// Opening the socket from another namespace requires permission to
749    /// enter the handle's original namespace. The subscription is bound to
750    /// the family and group identifiers of the loaded `l2tp_netlink`
751    /// module; if the module is reloaded, subscribe again.
752    pub async fn events(&self) -> crate::Result<Events> {
753        let runtime = tokio::runtime::Handle::try_current().map_err(|_| {
754            io::Error::other("tokio runtime is required for events")
755        })?;
756        let family = self.resolve_family().await?;
757        let group = family.events_group.ok_or_else(|| {
758            crate::Error::FamilyResolution(format!(
759                "the l2tp family has no `{EVENTS_GROUP}` multicast group"
760            ))
761        })?;
762        let (mut connection, genl, receiver) =
763            open_connection(&self.inner.netns)?;
764        // The kernel sends these notifications without a sender port and
765        // skips every socket whose own port is 0, so the socket must be
766        // bound before it can receive them; it never sends, so nothing else
767        // would bind it.
768        let socket = connection.socket_mut().socket_mut();
769        socket.bind_auto()?;
770        socket.add_membership(group)?;
771        // Requests are never sent on this connection; it only listens.
772        drop(genl);
773        let task = runtime.spawn(connection);
774        Ok(Events::new(receiver, task, family.id))
775    }
776
777    async fn resolve_family(&self) -> crate::Result<FamilyInfo> {
778        match self.inner.cached_family() {
779            Some(family) => Ok(family),
780            None => self.refresh_family().await,
781        }
782    }
783
784    /// Queries the family again, bypassing every cache. Generic netlink
785    /// assigns family identifiers dynamically, so a reloaded `l2tp_netlink`
786    /// module may come back under a different one.
787    async fn refresh_family(&self) -> crate::Result<FamilyInfo> {
788        self.inner.genl.clear_family_cache().await;
789        let family = self
790            .inner
791            .genl
792            .resolve_family::<L2tpMessage>()
793            .await
794            .map_err(|e| crate::Error::FamilyResolution(e.to_string()))?;
795        let info = FamilyInfo {
796            id: family.id,
797            events_group: family.multicast_groups.get(EVENTS_GROUP).copied(),
798        };
799        self.inner.set_family(info);
800        Ok(info)
801    }
802
803    async fn send_ack(&self, payload: L2tpMessage) -> crate::Result<()> {
804        let _ = self
805            .send_and_collect(payload, NLM_F_REQUEST | NLM_F_ACK)
806            .await?;
807        Ok(())
808    }
809
810    async fn send_and_collect(
811        &self,
812        payload: L2tpMessage,
813        flags: u16,
814    ) -> crate::Result<Vec<GenlMessage<L2tpMessage>>> {
815        let family_id = self.resolve_family().await?.id;
816        match self.send_to_family(payload.clone(), flags, family_id).await {
817            // Generic netlink answers ENOENT for a family identifier it no
818            // longer knows. The L2TP commands themselves never produce it,
819            // but only retry once the identifier has actually changed.
820            Err(err) if err.raw_os_error() == Some(libc::ENOENT) => {
821                let refreshed = self.refresh_family().await?.id;
822                if refreshed == family_id {
823                    return Err(err);
824                }
825                log::debug!(
826                    "l2tp: family id {family_id} became {refreshed}; retrying"
827                );
828                self.send_to_family(payload, flags, refreshed).await
829            }
830            result => result,
831        }
832    }
833
834    async fn send_to_family(
835        &self,
836        payload: L2tpMessage,
837        flags: u16,
838        family_id: u16,
839    ) -> crate::Result<Vec<GenlMessage<L2tpMessage>>> {
840        let cmd = payload.cmd;
841        log::debug!("l2tp: sending {cmd:?} with flags {flags:#06x}");
842        let mut genl = GenlMessage::from_payload(payload);
843        genl.set_resolved_family_id(family_id);
844
845        let mut request = NetlinkMessage::from(genl);
846        request.header.flags = flags;
847        request.finalize();
848
849        let mut handle = self.inner.genl.clone();
850        let mut stream = handle.send_request(request).map_err(to_io_error)?;
851
852        match collect_responses(&mut stream, flags).await {
853            Ok(replies) => {
854                log::trace!("l2tp: {cmd:?} returned {} replies", replies.len());
855                Ok(replies)
856            }
857            Err(err) => {
858                log::debug!("l2tp: {cmd:?} failed: {err}");
859                Err(err)
860            }
861        }
862    }
863}
864
865async fn collect_responses<S>(
866    stream: S,
867    flags: u16,
868) -> crate::Result<Vec<GenlMessage<L2tpMessage>>>
869where
870    S: Stream<
871        Item = Result<
872            NetlinkMessage<GenlMessage<L2tpMessage>>,
873            netlink_packet_core::DecodeError,
874        >,
875    >,
876{
877    futures::pin_mut!(stream);
878    let expect_ack = flags & NLM_F_ACK != 0;
879    let mut expect_done = flags & NLM_F_DUMP == NLM_F_DUMP;
880    let mut responses = Vec::new();
881    while let Some(item) = stream.next().await {
882        let message = item?;
883        if message.header.flags & NLM_F_DUMP_INTR != 0 {
884            // The kernel changed its tables mid-dump; a retry usually works.
885            return Err(io::Error::new(
886                io::ErrorKind::Interrupted,
887                "netlink dump was interrupted by concurrent changes",
888            )
889            .into());
890        }
891        let multipart = message.header.flags & NLM_F_MULTIPART != 0;
892        match message.payload {
893            NetlinkPayload::InnerMessage(inner) => {
894                responses.push(inner);
895                expect_done |= multipart;
896                if !expect_ack && !expect_done {
897                    return Ok(responses);
898                }
899            }
900            NetlinkPayload::Error(err) => {
901                if let Some(code) = err.code {
902                    return Err(crate::Error::KernelError {
903                        code: normalize_errno(code.get()),
904                        message: err.to_string(),
905                    });
906                }
907                if expect_ack && !expect_done {
908                    return Ok(responses);
909                }
910            }
911            NetlinkPayload::Done(done) => {
912                if done.code != 0 {
913                    return Err(crate::Error::KernelError {
914                        code: normalize_errno(done.code),
915                        message: format!("netlink dump failed: {}", done.code),
916                    });
917                }
918                if expect_done {
919                    return Ok(responses);
920                }
921                return Err(crate::Error::UnexpectedReply(
922                    "unexpected netlink DONE".to_string(),
923                ));
924            }
925            NetlinkPayload::Overrun(_) => {
926                return Err(crate::Error::Netlink(
927                    "netlink overrun: reply was truncated".to_string(),
928                ));
929            }
930            _ => {}
931        }
932    }
933    Err(io::Error::new(
934        io::ErrorKind::UnexpectedEof,
935        "netlink reply stream closed before request completion",
936    )
937    .into())
938}
939
940struct CreationResult<T> {
941    value: Option<crate::Result<T>>,
942    runtime: tokio::runtime::Handle,
943}
944
945impl<T> Drop for CreationResult<T> {
946    fn drop(&mut self) {
947        // A completed JoinHandle's output can be dropped by a cancelling
948        // caller outside Tokio. Restore the creating runtime so an unclaimed
949        // tunnel/session can still schedule its best-effort deletion.
950        let _guard = self.runtime.enter();
951        drop(self.value.take());
952    }
953}
954
955// The task owns the socket/request until ACK, then owns the resulting RAII
956// handle. Dropping the caller's JoinHandle detaches the task; its unclaimed
957// result is dropped with auto-delete enabled. Do not construct that handle
958// before a successful ACK: deleting after EEXIST could remove someone else's
959// tunnel or session.
960async fn run_creation<F, T>(creation: F) -> crate::Result<T>
961where
962    F: std::future::Future<Output = crate::Result<T>> + Send + 'static,
963    T: Send + 'static,
964{
965    let runtime = tokio::runtime::Handle::try_current().map_err(|_| {
966        io::Error::other("tokio runtime is required for creation")
967    })?;
968    let cleanup_runtime = runtime.clone();
969    let mut result = runtime
970        .spawn(async move {
971            CreationResult {
972                value: Some(creation.await),
973                runtime: cleanup_runtime,
974            }
975        })
976        .await
977        .map_err(|err| io::Error::other(err.to_string()))?;
978    result.value.take().unwrap_or_else(|| {
979        Err(crate::Error::other("creation task lost its result"))
980    })
981}
982
983// Keep the owning handle in a separate task: cancelling the caller must not
984// cancel a deletion after ownership has been consumed.
985pub(crate) async fn run_cleanup<F>(cleanup: F) -> crate::Result<()>
986where
987    F: std::future::Future<Output = crate::Result<()>> + Send + 'static,
988{
989    let runtime = tokio::runtime::Handle::try_current().map_err(|_| {
990        io::Error::other("tokio runtime is required for deletion")
991    })?;
992    runtime
993        .spawn(cleanup)
994        .await
995        .map_err(|err| io::Error::other(err.to_string()))?
996}
997
998fn first_attributes<'a>(
999    replies: &'a [GenlMessage<L2tpMessage>],
1000    context: &str,
1001) -> crate::Result<&'a [L2tpAttribute]> {
1002    replies
1003        .first()
1004        .map(|msg| msg.payload.attributes.as_slice())
1005        .ok_or_else(|| crate::Error::UnexpectedReply(context.to_string()))
1006}
1007
1008fn to_io_error(err: genetlink::GenetlinkError) -> crate::Error {
1009    io::Error::other(err.to_string()).into()
1010}
1011
1012fn normalize_errno(code: i32) -> i32 {
1013    if code < 0 { -code } else { code }
1014}
1015
1016#[cfg(test)]
1017mod tests {
1018    use netlink_packet_core::{DoneMessage, ErrorMessage};
1019    use netlink_packet_l2tp::L2tpCmd;
1020
1021    use super::*;
1022
1023    type Reply = NetlinkMessage<GenlMessage<L2tpMessage>>;
1024
1025    fn reply() -> Reply {
1026        NetlinkMessage::from(GenlMessage::from_payload(L2tpMessage::new(
1027            L2tpCmd::TunnelGet,
1028        )))
1029    }
1030
1031    fn ack() -> Reply {
1032        NetlinkMessage::new(
1033            Default::default(),
1034            NetlinkPayload::Error(ErrorMessage::default()),
1035        )
1036    }
1037
1038    fn done(code: i32) -> Reply {
1039        let mut message = DoneMessage::default();
1040        message.code = code;
1041        NetlinkMessage::new(Default::default(), NetlinkPayload::Done(message))
1042    }
1043
1044    fn collect(
1045        messages: Vec<Reply>,
1046        flags: u16,
1047    ) -> crate::Result<Vec<GenlMessage<L2tpMessage>>> {
1048        futures::executor::block_on(collect_responses(
1049            futures::stream::iter(messages.into_iter().map(Ok)),
1050            flags,
1051        ))
1052    }
1053
1054    pub(super) const TEST_FAMILY_ID: u16 = 1234;
1055
1056    pub(super) fn test_handle(genl: GenetlinkHandle) -> L2tpHandle {
1057        L2tpHandle {
1058            inner: Arc::new(L2tpInner {
1059                genl,
1060                family: std::sync::Mutex::new(Some(FamilyInfo {
1061                    id: TEST_FAMILY_ID,
1062                    events_group: None,
1063                })),
1064                netns: NetNs::current().unwrap(),
1065            }),
1066        }
1067    }
1068
1069    #[test]
1070    fn missing_ack_or_dump_completion_is_an_error() {
1071        for flags in [
1072            NLM_F_REQUEST,
1073            NLM_F_REQUEST | NLM_F_ACK,
1074            NLM_F_REQUEST | NLM_F_DUMP,
1075        ] {
1076            let err = collect(vec![], flags).unwrap_err();
1077            assert_eq!(err.kind(), Some(io::ErrorKind::UnexpectedEof));
1078        }
1079        // An ordinary reply cannot substitute for the requested ACK.
1080        assert!(collect(vec![reply()], NLM_F_REQUEST | NLM_F_ACK).is_err());
1081        // A partial dump cannot substitute for DONE.
1082        let mut partial = reply();
1083        partial.header.flags = NLM_F_MULTIPART;
1084        assert!(
1085            collect(vec![partial.clone()], NLM_F_REQUEST | NLM_F_DUMP).is_err()
1086        );
1087        assert!(collect(vec![partial], NLM_F_REQUEST).is_err());
1088    }
1089
1090    #[test]
1091    fn request_completion_matches_request_type() {
1092        assert!(
1093            collect(vec![ack()], NLM_F_REQUEST | NLM_F_ACK)
1094                .unwrap()
1095                .is_empty()
1096        );
1097        assert_eq!(collect(vec![reply()], NLM_F_REQUEST).unwrap().len(), 1);
1098        let mut partial = reply();
1099        partial.header.flags = NLM_F_MULTIPART;
1100        assert_eq!(
1101            collect(vec![partial, done(0)], NLM_F_REQUEST | NLM_F_DUMP)
1102                .unwrap()
1103                .len(),
1104            1
1105        );
1106        assert!(
1107            collect(vec![done(0)], NLM_F_REQUEST | NLM_F_DUMP)
1108                .unwrap()
1109                .is_empty()
1110        );
1111        assert!(collect(vec![ack()], NLM_F_REQUEST | NLM_F_DUMP).is_err());
1112        assert!(collect(vec![done(0)], NLM_F_REQUEST | NLM_F_ACK).is_err());
1113    }
1114
1115    #[test]
1116    fn dump_errors_and_interruptions_are_not_success() {
1117        assert_eq!(
1118            collect(vec![done(-libc::EIO)], NLM_F_REQUEST | NLM_F_DUMP)
1119                .unwrap_err()
1120                .raw_os_error(),
1121            Some(libc::EIO)
1122        );
1123        for mut message in [reply(), done(0)] {
1124            message.header.flags = NLM_F_DUMP_INTR;
1125            assert_eq!(
1126                collect(vec![message], NLM_F_REQUEST | NLM_F_DUMP)
1127                    .unwrap_err()
1128                    .kind(),
1129                Some(io::ErrorKind::Interrupted)
1130            );
1131        }
1132    }
1133
1134    #[test]
1135    fn kernel_ack_errors_are_preserved() {
1136        let mut error = ErrorMessage::default();
1137        error.code = std::num::NonZeroI32::new(-libc::EPERM);
1138        let message = NetlinkMessage::new(
1139            Default::default(),
1140            NetlinkPayload::Error(error),
1141        );
1142        assert_eq!(
1143            collect(vec![message], NLM_F_REQUEST | NLM_F_ACK)
1144                .unwrap_err()
1145                .raw_os_error(),
1146            Some(libc::EPERM)
1147        );
1148    }
1149
1150    #[test]
1151    fn deletion_continues_after_caller_cancellation() {
1152        let runtime = tokio::runtime::Builder::new_current_thread()
1153            .enable_all()
1154            .build()
1155            .unwrap();
1156        runtime.block_on(async {
1157            let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1158            let (finish_tx, finish_rx) = tokio::sync::oneshot::channel();
1159            let (completed_tx, completed_rx) = tokio::sync::oneshot::channel();
1160            let mut deletion = Box::pin(run_cleanup(async move {
1161                started_tx.send(()).unwrap();
1162                finish_rx.await.unwrap();
1163                completed_tx.send(()).unwrap();
1164                Ok(())
1165            }));
1166            assert!(futures::poll!(deletion.as_mut()).is_pending());
1167            started_rx.await.unwrap();
1168            drop(deletion);
1169            finish_tx.send(()).unwrap();
1170            completed_rx
1171                .await
1172                .expect("deletion must finish after cancellation");
1173        });
1174    }
1175
1176    #[test]
1177    fn deletion_propagates_failure() {
1178        let runtime = tokio::runtime::Builder::new_current_thread()
1179            .enable_all()
1180            .build()
1181            .unwrap();
1182        runtime.block_on(async {
1183            let err = run_cleanup(async {
1184                Err(crate::Error::KernelError {
1185                    code: libc::EPERM,
1186                    message: "denied".into(),
1187                })
1188            })
1189            .await
1190            .unwrap_err();
1191            assert_eq!(err.raw_os_error(), Some(libc::EPERM));
1192        });
1193    }
1194
1195    #[test]
1196    fn connection_shutdown_does_not_acknowledge_request() {
1197        let runtime = tokio::runtime::Builder::new_current_thread()
1198            .enable_all()
1199            .build()
1200            .unwrap();
1201        runtime.block_on(async {
1202            let (connection, genl, _unsolicited) =
1203                match genetlink::new_connection() {
1204                    Ok(connection) => connection,
1205                    Err(err)
1206                        if err.kind() == io::ErrorKind::PermissionDenied =>
1207                    {
1208                        return;
1209                    }
1210                    Err(err) => panic!("{err}"),
1211                };
1212            let handle = test_handle(genl);
1213            let mut request = Box::pin(handle.delete_tunnel(TunnelId(123)));
1214            assert!(futures::poll!(request.as_mut()).is_pending());
1215            // No connection task ran, so the request never reached the kernel.
1216            drop(connection);
1217            assert_eq!(
1218                request.await.unwrap_err().kind(),
1219                Some(io::ErrorKind::UnexpectedEof)
1220            );
1221        });
1222    }
1223
1224    #[test]
1225    fn cancelled_session_lookup_keeps_cleanup_ownership() {
1226        let runtime = tokio::runtime::Builder::new_current_thread()
1227            .enable_all()
1228            .build()
1229            .unwrap();
1230        runtime.block_on(async {
1231            let (_connection, genl, _unsolicited) =
1232                match genetlink::new_connection() {
1233                    Ok(connection) => connection,
1234                    Err(err)
1235                        if err.kind() == io::ErrorKind::PermissionDenied =>
1236                    {
1237                        return;
1238                    }
1239                    Err(err) => panic!("{err}"),
1240                };
1241            let handle = test_handle(genl);
1242            // This is the post-ACK phase; the idle connection leaves GET
1243            // pending.
1244            let mut lookup = Box::pin(handle.get_created_session(
1245                SessionConfig::eth(TunnelId(1), SessionId(2), SessionId(3)),
1246                None,
1247            ));
1248            assert!(futures::poll!(lookup.as_mut()).is_pending());
1249            drop(lookup);
1250            // On this single-thread runtime the cleanup task cannot run until
1251            // we yield. Its reference proves Drop scheduled cleanup rather than
1252            // discarding ownership of the acknowledged session.
1253            assert_eq!(Arc::strong_count(&handle.inner), 2);
1254        });
1255    }
1256
1257    #[test]
1258    fn normalize_errno_converts_negative_to_positive() {
1259        assert_eq!(normalize_errno(-libc::EEXIST), libc::EEXIST);
1260        assert_eq!(normalize_errno(-libc::EINVAL), libc::EINVAL);
1261    }
1262
1263    #[test]
1264    fn normalize_errno_keeps_non_negative_unchanged() {
1265        assert_eq!(normalize_errno(0), 0);
1266        assert_eq!(normalize_errno(libc::ENOENT), libc::ENOENT);
1267    }
1268
1269    #[test]
1270    fn new_fails_without_tokio_runtime() {
1271        let result = futures::executor::block_on(L2tpHandle::new());
1272        match result {
1273            Ok(_) => panic!("expected failure without tokio runtime"),
1274            Err(crate::Error::Io(e)) => {
1275                assert!(
1276                    e.to_string().contains("tokio runtime is required"),
1277                    "unexpected error message: {e}"
1278                );
1279            }
1280            Err(e) => panic!("unexpected error type: {e:?}"),
1281        }
1282    }
1283
1284    #[test]
1285    fn new_fails_without_io_driver() {
1286        // No `enable_io`: Tokio panics when the netlink socket registers.
1287        let runtime = tokio::runtime::Builder::new_current_thread()
1288            .build()
1289            .unwrap();
1290        match runtime.block_on(L2tpHandle::new()) {
1291            Ok(_) => panic!("expected failure without an I/O driver"),
1292            Err(crate::Error::Io(e)) => {
1293                assert!(
1294                    e.to_string().contains("I/O enabled"),
1295                    "unexpected error message: {e}"
1296                );
1297            }
1298            Err(e) => panic!("unexpected error type: {e:?}"),
1299        }
1300    }
1301
1302    #[tokio::test]
1303    async fn events_subscription_joins_the_multicast_group() {
1304        let handle = L2tpHandle::new().await.unwrap();
1305        match handle.events().await {
1306            Ok(events) => drop(events),
1307            // Without the l2tp module the family cannot be resolved.
1308            Err(crate::Error::FamilyResolution(_)) => {}
1309            Err(err) => panic!("unexpected error: {err:?}"),
1310        }
1311    }
1312
1313    #[tokio::test]
1314    async fn opening_unknown_objects_fails_without_a_handle() {
1315        // ENODEV as root, EPERM unprivileged, or no `l2tp` family at all
1316        // where the module is not loaded (CI runners).
1317        fn refused(err: &crate::Error) -> bool {
1318            err.kind().is_some()
1319                || matches!(err, crate::Error::FamilyResolution(_))
1320        }
1321        let handle = L2tpHandle::new().await.unwrap();
1322        let err = handle.open_tunnel(TunnelId(u32::MAX)).await.unwrap_err();
1323        assert!(refused(&err), "{err:?}");
1324        let name = L2tpIfName::new("l2tp-no-such0").unwrap();
1325        let err = handle.open_session_by_ifname(&name).await.unwrap_err();
1326        assert!(refused(&err), "{err:?}");
1327    }
1328
1329    #[test]
1330    fn new_in_the_current_namespace_needs_no_setns_privileges() {
1331        let runtime = tokio::runtime::Builder::new_current_thread()
1332            .enable_all()
1333            .build()
1334            .unwrap();
1335        runtime.block_on(async {
1336            let netns = NetNs::current().unwrap();
1337            let handle = L2tpHandle::new_in(&netns).await.unwrap();
1338            assert!(handle.inner.netns.same_as(&netns).unwrap());
1339        });
1340    }
1341}
1342
1343#[cfg(test)]
1344mod creation_tests;