Skip to content

Commit 006abeb

Browse files
committed
Add support for individual timeout overrides in ressources
1 parent 4aafa39 commit 006abeb

6 files changed

Lines changed: 59 additions & 14 deletions

File tree

doc/CONFIGURATION.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ If a `users` list is given, then only these users can knock the port open.
241241
The `users` list is just a comma separated list of user identifiers.
242242
See `[KEYS]` section above for more information about user identifiers.
243243

244+
Optionally a resource specific `timeout` override can be specified.
245+
The `timeout` is specified in seconds and will override the default timeout from `[NFTABLES] timeout=...`.
246+
244247
If a client wants to knock a port open on a server, the client and the server must share a compatible resource entry for the port.
245248

246249
Example resources:
@@ -260,6 +263,9 @@ Example resources:
260263
261264
# Resource: TCP and UDP port 1234. Only for users 00000005 and 00000006
262265
00000001 = port: 1234 / tcp,udp / users: 00000005,00000006
266+
267+
# Resource: TCP port 1234. Timeout 42 seconds instead of default timeout.
268+
00000001 = port: 1234, timeout: 42
263269
```
264270

265271
# Server specific configuration parts
@@ -326,7 +332,7 @@ This option has no default and must be specified in the server configuration.
326332

327333
### `timeout`
328334

329-
The `timeout` option specifies the knock-open firewall rule timeout, in seconds.
335+
The `timeout` option specifies the default knock-open firewall rule timeout, in seconds.
330336
Knocked-open ports will be closed again this many seconds after the knocking.
331337

332338
This is the time you have to connect to the opened port.
@@ -337,4 +343,6 @@ Established connections will stay active when the port is closed.
337343

338344
It is recommended to set this to a small duration of e.g. one minute `timeout=60` or ten minutes `timeout=600`.
339345

346+
This timeout can be overridden in the individual `[RESOURCES]`.
347+
340348
This option defaults to `timeout=600`, if it is absent from the configuration.

letmein-conf/src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub enum Resource {
130130
port: u16,
131131
tcp: bool,
132132
udp: bool,
133+
timeout: Option<Duration>,
133134
users: Vec<UserId>,
134135
},
135136
}
@@ -339,6 +340,7 @@ fn extract_resource_port(
339340
map: &Map,
340341
) -> ah::Result<()> {
341342
let mut port: Option<u16> = None;
343+
let mut timeout: Option<Duration> = None;
342344
let mut users: Vec<String> = vec![];
343345
let mut tcp = false;
344346
let mut udp = false;
@@ -355,6 +357,15 @@ fn extract_resource_port(
355357
} else {
356358
return Err(err!("[RESOURCE] invalid 'port' option"));
357359
}
360+
} else if k == "timeout" {
361+
if vs.len() == 1 {
362+
if timeout.is_some() {
363+
return Err(err!("[RESOURCE] multiple 'timeout' values"));
364+
}
365+
timeout = Some(parse_duration(&vs[0]).context("[RESOURCES] timeout")?);
366+
} else {
367+
return Err(err!("[RESOURCE] invalid 'timeout' option"));
368+
}
358369
} else if k == "users" {
359370
if !users.is_empty() {
360371
return Err(err!("[RESOURCE] multiple 'users' values"));
@@ -409,6 +420,7 @@ fn extract_resource_port(
409420
port,
410421
tcp,
411422
udp,
423+
timeout,
412424
users,
413425
},
414426
);
@@ -847,6 +859,7 @@ mod tests {
847859
port: 4096,
848860
tcp: true,
849861
udp: false,
862+
timeout: None,
850863
users: vec![]
851864
}
852865
);
@@ -861,6 +874,7 @@ mod tests {
861874
port: 4096,
862875
tcp: true,
863876
udp: false,
877+
timeout: None,
864878
users: vec![]
865879
}
866880
);
@@ -875,12 +889,13 @@ mod tests {
875889
port: 4096,
876890
tcp: false,
877891
udp: true,
892+
timeout: None,
878893
users: vec![1.into(), 2.into(), 3.into()]
879894
}
880895
);
881896

882897
let mut ini = Ini::new();
883-
ini.parse_str("[RESOURCES]\n9876ABCD = port : 4096 / udp, tcp / users: 4\n")
898+
ini.parse_str("[RESOURCES]\n9876ABCD = port : 4096 / udp, tcp / users: 4 / timeout:42\n")
884899
.unwrap();
885900
let resources = get_resources(&ini).unwrap();
886901
assert_eq!(
@@ -889,6 +904,7 @@ mod tests {
889904
port: 4096,
890905
tcp: true,
891906
udp: true,
907+
timeout: Some(Duration::from_secs(42)),
892908
users: vec![4.into()]
893909
}
894910
);

letmeind/letmeind.conf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ family = inet
6565
table = filter
6666
chain-input = LETMEIN-INPUT
6767

68-
# Timeout of installed knock-open rules.
68+
# Default timeout of installed knock-open rules.
6969
# Knocked-open ports will be closed again this many seconds after the knocking.
70+
# This timeout can be overridden in the individual RESSOURCES (see below).
7071
timeout = 600
7172

7273

@@ -102,3 +103,6 @@ timeout = 600
102103

103104
# Open port 6500 for TCP and UDP.
104105
#0000001E = port: 6500 / tcp,udp
106+
107+
# Open port 7500 for TCP and override the default timeout with 60 seconds.
108+
#0000001F = port: 7500 / timeout: 60

letmeinfwd/src/firewall.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ pub mod nftables;
1010

1111
use anyhow as ah;
1212
use letmein_conf::Config;
13-
use std::{collections::HashMap, net::IpAddr, time::Instant};
13+
use std::{
14+
collections::HashMap,
15+
net::IpAddr,
16+
time::{Duration, Instant},
17+
};
1418

1519
/// TCP and/or UDP port number.
1620
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
@@ -61,7 +65,7 @@ struct Lease {
6165

6266
impl Lease {
6367
/// Create a new lease with maximum timeout.
64-
pub fn new(conf: &Config, addr: IpAddr, port: LeasePort) -> Self {
68+
pub fn new(conf: &Config, addr: IpAddr, port: LeasePort, timeout: Option<Duration>) -> Self {
6569
// The upper layers must never give us a lease request for the control port.
6670
assert_ne!(
6771
conf.port().port,
@@ -71,7 +75,7 @@ impl Lease {
7175
LeasePort::TcpUdp(p) => p,
7276
}
7377
);
74-
let timeout = Instant::now() + conf.nft_timeout();
78+
let timeout = Instant::now() + timeout.unwrap_or_else(|| conf.nft_timeout());
7579
Self {
7680
addr,
7781
port,
@@ -154,6 +158,7 @@ pub trait FirewallOpen {
154158
conf: &Config,
155159
remote_addr: IpAddr,
156160
port: LeasePort,
161+
timeout: Option<Duration>,
157162
) -> ah::Result<()>;
158163
}
159164

letmeinfwd/src/firewall/nftables.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use nftables::{
2020
stmt::{Match, Operator, Statement},
2121
types::NfFamily,
2222
};
23-
use std::{borrow::Cow, fmt::Write as _, net::IpAddr};
23+
use std::{borrow::Cow, fmt::Write as _, net::IpAddr, time::Duration};
2424

2525
const NFTNL_UDATA_COMMENT_MAXLEN: usize = 128;
2626

@@ -474,13 +474,14 @@ impl FirewallOpen for NftFirewall {
474474
conf: &Config,
475475
remote_addr: IpAddr,
476476
port: LeasePort,
477+
timeout: Option<Duration>,
477478
) -> ah::Result<()> {
478479
assert!(!self.shutdown);
479480
let id = (remote_addr, port);
480481
if let Some(lease) = self.leases.get_mut(&id) {
481482
lease.refresh_timeout(conf);
482483
} else {
483-
let lease = Lease::new(conf, remote_addr, port);
484+
let lease = Lease::new(conf, remote_addr, port, timeout);
484485
self.nftables_add_lease(conf, &lease).await?;
485486
self.leases.insert(id, lease);
486487
self.print_total_rule_count(conf);

letmeinfwd/src/server.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,14 @@ impl FirewallConnection {
158158
let resource = self.get_resource(conf, &msg).await?;
159159

160160
// Get the port information.
161-
let lease_port;
162-
match resource {
163-
Resource::Port { port, tcp, udp, .. } => {
161+
let (lease_port, timeout) = match resource {
162+
Resource::Port {
163+
port,
164+
tcp,
165+
udp,
166+
timeout,
167+
users: _,
168+
} => {
164169
// Don't allow the user to manage the control port.
165170
if port == conf.port().port {
166171
// Whoops, letmeind should never send us a request for the
@@ -170,7 +175,7 @@ impl FirewallConnection {
170175
return self.send_result(res).await;
171176
}
172177

173-
lease_port = match (tcp, udp) {
178+
let lease_port = match (tcp, udp) {
174179
(true, false) => LeasePort::Tcp(port),
175180
(false, true) => LeasePort::Udp(port),
176181
(true, true) => LeasePort::TcpUdp(port),
@@ -179,11 +184,17 @@ impl FirewallConnection {
179184
return self.send_result(res).await;
180185
}
181186
};
187+
188+
(lease_port, timeout)
182189
}
183-
}
190+
};
184191

185192
// Open the firewall.
186-
let res = fw.lock().await.open_port(conf, addr, lease_port).await;
193+
let res = fw
194+
.lock()
195+
.await
196+
.open_port(conf, addr, lease_port, timeout)
197+
.await;
187198
self.send_result(res).await
188199
}
189200
FirewallOperation::Ack | FirewallOperation::Nack => {

0 commit comments

Comments
 (0)