Skip to main content

nlink/netlink/genl/ovpn/
mod.rs

1//! OpenVPN data-channel offload (DCO) Generic Netlink family.
2//!
3//! The kernel's `ovpn` GENL family (stabilized in **Linux 6.16**)
4//! lets userspace push OpenVPN 2.7 data-channel processing into
5//! the kernel. The TLS handshake stays in userspace; the
6//! pre-derived AEAD keys + per-peer socket descriptors are handed
7//! to the kernel via this family, and packets are encrypted /
8//! decrypted in-kernel from then on. This eliminates the
9//! per-packet user/kernel boundary crossings that bottlenecked
10//! pre-2.7 OpenVPN.
11//!
12//! # Status — Plan 197
13//!
14//! | Phase | Ships |
15//! |---|---|
16//! | Family marker + module scaffold | ✓ |
17//! | Command + attribute + value enums | ✓ |
18//! | Imperative `Connection<Ovpn>` methods | ✓ |
19//! | Multicast `peers` group + `OvpnEvent` | ✓ |
20//! | Declarative `OvpnConfig` + diff + apply | ✓ |
21//! | `attach_socket` SCM_RIGHTS cross-netns | deferred |
22//!
23//! The interface itself is created via RTNL — see
24//! [`OvpnLink`][crate::netlink::link::OvpnLink] (Plan 190 §2.3b).
25//! The GENL family operates on an already-existing ovpn interface
26//! by `ifindex`.
27//!
28//! # Construction
29//!
30//! ```ignore
31//! use nlink::netlink::{Connection, genl::ovpn::Ovpn};
32//!
33//! let conn = Connection::<Ovpn>::new_async().await?;
34//! // Family ID resolved against the kernel "ovpn" registration;
35//! // FamilyNotFound on kernels without CONFIG_OVPN.
36//! ```
37//!
38//! Resolution failure is the common case on stock distro kernels
39//! that don't load the `ovpn` module. Handle via
40//! [`Error::is_not_found`](crate::Error::is_not_found):
41//!
42//! ```ignore
43//! match Connection::<Ovpn>::new_async().await {
44//!     Ok(conn) => { /* use it */ }
45//!     Err(e) if e.is_not_found() => {
46//!         tracing::warn!("OVPN DCO not available on this kernel; skipping");
47//!     }
48//!     Err(e) => return Err(e),
49//! }
50//! ```
51//!
52//! # Cipher constraints
53//!
54//! The kernel accepts AES-GCM (128 or 256-bit) and
55//! ChaCha20-Poly1305 only — the TLS handshake in OpenVPN 2.7
56//! must negotiate one of these. Legacy CBC + non-AEAD modes are
57//! intentionally not supported in DCO mode.
58
59use crate::macros::genl_family;
60
61pub mod config;
62pub mod connection;
63pub mod events;
64pub mod messages;
65pub mod types;
66
67pub use config::{
68    OvpnConfig, OvpnDiff, OvpnInterfaceConfig, OvpnInterfaceConfigBuilder, OvpnKeyConfig,
69    OvpnPeerConfig, OvpnPeerConfigBuilder,
70};
71pub use events::OvpnEvent;
72pub use messages::{
73    OvpnKeyDelRequest, OvpnKeyGetRequest, OvpnKeyNewRequest, OvpnKeyReply, OvpnKeySwapRequest,
74    OvpnKeyconf, OvpnKeydir, OvpnPeer, OvpnPeerDelRequest, OvpnPeerGetRequest, OvpnPeerNewRequest,
75    OvpnPeerReply, OvpnPeerSetRequest,
76};
77pub use types::{
78    OvpnAttr, OvpnCipherAlg, OvpnCmd, OvpnDelPeerReason, OvpnKeyconfAttr, OvpnKeySlot,
79    OvpnKeydirAttr, OvpnPeerAttr, OVPN_MAX_CIPHER_KEY_LEN, OVPN_MAX_KEY_ID, OVPN_MAX_PEER_ID,
80    OVPN_NONCE_TAIL_SIZE,
81};
82
83/// OVPN Generic Netlink family marker.
84///
85/// Constructed via [`Connection::<Ovpn>::new_async()`][Connection]
86/// — the family ID is resolved against the kernel at connection
87/// time. Returns
88/// [`Error::FamilyNotFound`](crate::Error::FamilyNotFound) on
89/// kernels without OVPN support (kernel < 6.16 or `ovpn` module
90/// not loaded).
91///
92/// [Connection]: crate::netlink::Connection
93#[genl_family(name = "ovpn", version = 1)]
94pub struct Ovpn;
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::netlink::{
100        construction::AsyncConstructible, AsyncProtocolInit, Protocol, ProtocolState,
101    };
102
103    #[test]
104    fn family_marker_carries_expected_name_and_version() {
105        assert_eq!(Ovpn::NAME, "ovpn");
106        assert_eq!(Ovpn::VERSION, 1);
107    }
108
109    #[test]
110    fn default_marker_has_zero_family_id_before_resolution() {
111        let d = Ovpn::default();
112        assert_eq!(d.family_id(), 0);
113    }
114
115    #[test]
116    fn protocol_state_routes_to_generic() {
117        const _: () = {
118            assert!(matches!(Ovpn::PROTOCOL, Protocol::Generic));
119        };
120    }
121
122    fn assert_async_constructible<P: AsyncConstructible>() {}
123    fn assert_async_protocol_init<P: AsyncProtocolInit>() {}
124
125    #[test]
126    fn ovpn_satisfies_async_construction_bounds() {
127        assert_async_constructible::<Ovpn>();
128        assert_async_protocol_init::<Ovpn>();
129    }
130}