1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// SPDX-License-Identifier: MIT
use crate::{
Cookie, IfName, L2SpecType, PseudowireType, SessionId, SessionStats,
TunnelId,
};
/// Parameters for creating an L2TP session.
#[derive(Debug, Clone)]
pub struct SessionConfig {
/// Parent tunnel identifier.
pub tunnel_id: TunnelId,
/// Local session identifier.
pub session_id: SessionId,
/// Peer session identifier.
pub peer_session_id: SessionId,
/// Session pseudowire type.
pub pseudowire_type: PseudowireType,
/// Session L2-specific header type.
pub l2spec_type: L2SpecType,
/// Local session cookie.
pub cookie: Cookie,
/// Peer session cookie.
pub peer_cookie: Cookie,
/// Enable receive sequence checking.
pub recv_seq: bool,
/// Enable transmit sequence numbers.
pub send_seq: bool,
/// Enable LNS mode.
pub lns_mode: bool,
/// Receive timeout in milliseconds.
pub recv_timeout_ms: Option<u64>,
/// Optional interface name for `SESSION_CREATE`.
///
/// For Ethernet pseudowires, this names the `l2tpeth*` netdevice created
/// by the kernel. Tunnel objects do not have an `ifname`.
pub ifname: Option<IfName>,
}
impl SessionConfig {
/// Creates a basic Ethernet pseudowire session configuration.
pub fn eth(
tunnel_id: TunnelId,
session_id: SessionId,
peer_session_id: SessionId,
) -> Self {
Self {
tunnel_id,
session_id,
peer_session_id,
pseudowire_type: PseudowireType::Eth,
l2spec_type: L2SpecType::None,
cookie: Cookie::none(),
peer_cookie: Cookie::none(),
recv_seq: false,
send_seq: false,
lns_mode: false,
recv_timeout_ms: None,
ifname: None,
}
}
}
/// Runtime information returned by the kernel for a session.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SessionInfo {
/// Parent tunnel identifier.
pub tunnel_id: TunnelId,
/// Local session identifier.
pub session_id: SessionId,
/// Peer session identifier.
pub peer_session_id: SessionId,
/// Session pseudowire type.
pub pseudowire_type: PseudowireType,
/// Session L2-specific header type.
pub l2spec_type: L2SpecType,
/// Receive sequence checking state.
pub recv_seq: bool,
/// Transmit sequence number state.
pub send_seq: bool,
/// LNS mode state.
pub lns_mode: bool,
/// Receive timeout in milliseconds.
pub recv_timeout_ms: Option<u64>,
/// Kernel-assigned or requested session interface name.
pub ifname: Option<IfName>,
/// Whether IPsec offload/protection is in use.
pub using_ipsec: bool,
}
/// Mutable session parameters.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SessionModify {
/// Optional receive sequence toggle.
pub recv_seq: Option<bool>,
/// Optional send sequence toggle.
pub send_seq: Option<bool>,
/// Optional LNS mode toggle.
pub lns_mode: Option<bool>,
/// Optional receive timeout update in milliseconds.
pub recv_timeout_ms: Option<u64>,
}
impl SessionModify {
/// Returns `true` when no parameter would be changed.
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
}
/// Handle for managing a session lifecycle.
#[derive(Debug)]
pub struct SessionHandle {
pub(crate) tunnel_id: TunnelId,
pub(crate) session_id: SessionId,
pub(crate) ifname: Option<IfName>,
pub(crate) auto_delete: bool,
pub(crate) handle: crate::handle::L2tpHandle,
}
impl SessionHandle {
/// Returns the parent tunnel identifier.
pub fn tunnel_id(&self) -> TunnelId {
self.tunnel_id
}
/// Returns this handle's session identifier.
pub fn session_id(&self) -> SessionId {
self.session_id
}
/// Returns the interface name, if one was assigned.
pub fn ifname(&self) -> Option<&IfName> {
self.ifname.as_ref()
}
/// Enables or disables best-effort session deletion on drop.
///
/// The deletion runs on a task spawned onto the current Tokio runtime
/// after the handle is dropped, so it can race with creating a new session
/// that reuses this ID; use [`SessionHandle::delete`] to delete
/// deterministically. Without a Tokio runtime, nothing is deleted.
pub fn set_auto_delete(&mut self, v: bool) {
self.auto_delete = v;
}
/// Fetches current session information from the kernel.
pub async fn get(&self) -> crate::Result<SessionInfo> {
self.handle
.get_session(self.tunnel_id, self.session_id)
.await
}
/// Fetches session statistics from the kernel.
pub async fn stats(&self) -> crate::Result<SessionStats> {
self.handle
.session_stats(self.tunnel_id, self.session_id)
.await
}
/// Applies mutable session parameters.
pub async fn modify(&self, params: SessionModify) -> crate::Result<()> {
self.handle
.modify_session(self.tunnel_id, self.session_id, params)
.await
}
/// Deletes the session and waits for the kernel to acknowledge it.
/// Once polled inside a Tokio runtime, deletion continues even if the
/// caller cancels this future.
pub async fn delete(mut self) -> crate::Result<()> {
crate::handle::run_cleanup(async move {
// Once the delete is sent, a failure must not queue another one.
self.auto_delete = false;
self.handle
.delete_session(self.tunnel_id, self.session_id)
.await?;
drop(self);
Ok(())
})
.await
}
}
impl Drop for SessionHandle {
fn drop(&mut self) {
if !self.auto_delete {
return;
}
let handle = self.handle.clone();
let tunnel_id = self.tunnel_id;
let session_id = self.session_id;
if let Ok(runtime) = tokio::runtime::Handle::try_current() {
runtime.spawn(async move {
let _ = handle.delete_session(tunnel_id, session_id).await;
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_eth_constructor_sets_expected_defaults() {
let config =
SessionConfig::eth(TunnelId(1), SessionId(2), SessionId(3));
assert_eq!(config.tunnel_id, TunnelId(1));
assert_eq!(config.session_id, SessionId(2));
assert_eq!(config.peer_session_id, SessionId(3));
assert_eq!(config.pseudowire_type, PseudowireType::Eth);
assert_eq!(config.l2spec_type, L2SpecType::None);
assert!(config.cookie.as_bytes().is_empty());
assert!(config.peer_cookie.as_bytes().is_empty());
assert!(!config.recv_seq);
assert!(!config.send_seq);
assert!(!config.lns_mode);
assert_eq!(config.recv_timeout_ms, None);
assert_eq!(config.ifname, None);
}
#[test]
fn session_modify_is_empty_only_without_changes() {
assert!(SessionModify::default().is_empty());
assert!(
!SessionModify {
lns_mode: Some(false),
..Default::default()
}
.is_empty()
);
}
}