Skip to content

Commit 40f9378

Browse files
authored
ntcp2: Add support for connection limits (#376)
1 parent 7f1cd6e commit 40f9378

17 files changed

Lines changed: 271 additions & 2 deletions

File tree

docs/embedding-rust.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ let config = Config {
9191
ipv4: true,
9292
ipv6: true,
9393
ml_kem: Some(4),
94+
max_connections: None,
9495
disable_pq: false,
9596
}),
9697
ssu2: Some(Ssu2Config {

docs/router-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Run `emissary-cli --help` to show the built-in help message with all available o
109109
| `publish_ipv6` | - | Publish the IPv6 address in router info for incoming connections. (default: true) |
110110
| `disable_pq` | - | Disable PQ connections (default: false) |
111111
| `ml_kem` | - | ML-KEM preference for inbound connections (default: 4) |
112+
| `max_connections` | - | Maximum number of connections (default: unlimited) |
112113

113114
**ML-KEM preference (`ml_kem`)**
114115

emissary-cli/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use tokio::io::AsyncWriteExt;
3434
use std::{
3535
collections::HashSet,
3636
net::{Ipv4Addr, Ipv6Addr},
37+
num::NonZeroUsize,
3738
path::PathBuf,
3839
};
3940

@@ -89,6 +90,7 @@ pub struct Ntcp2Config {
8990
pub ml_kem: Option<usize>,
9091
pub disable_pq: Option<bool>,
9192
pub publish: Option<bool>,
93+
pub max_connections: Option<usize>,
9294
}
9395

9496
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -315,6 +317,7 @@ impl EmissaryConfig {
315317
publish: None,
316318
disable_pq: None,
317319
ml_kem: Some(4),
320+
max_connections: None,
318321
}),
319322
ssu2: Some(Ssu2Config {
320323
port,
@@ -698,6 +701,7 @@ impl Config {
698701
iv: ntcp2_iv,
699702
ml_kem: config.ml_kem,
700703
disable_pq: config.disable_pq.unwrap_or(false),
704+
max_connections: config.max_connections.and_then(NonZeroUsize::new),
701705
}),
702706
port_forwarding: config.port_forwarding.map(From::from),
703707
profiles: Vec::new(),
@@ -1164,6 +1168,7 @@ mod tests {
11641168
publish: None,
11651169
ml_kem: None,
11661170
disable_pq: None,
1171+
max_connections: None,
11671172
}),
11681173
..EmissaryConfig::new::<TokioRuntime>()
11691174
};

emissary-cli/src/tools/devnet.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ async fn make_router(
5656
disable_pq: false,
5757
publish_ipv4: true,
5858
publish_ipv6: true,
59+
max_connections: None,
5960
}),
6061
routers,
6162
transit: Some(TransitConfig {

emissary-cli/src/ui/dioxus/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct Ntcp2Config {
3535
pub ipv6: Option<bool>,
3636
pub ml_kem: Option<String>,
3737
pub disable_pq: Option<bool>,
38+
pub max_connections: Option<String>,
3839
pub enabled: bool,
3940
}
4041

@@ -53,6 +54,7 @@ impl From<&EmissaryConfig> for Ntcp2Config {
5354
ipv6_host: config.ipv6_host.map(|address| address.to_string()),
5455
publish_ipv4: config.publish_ipv4,
5556
publish_ipv6: config.publish_ipv6,
57+
max_connections: config.max_connections.map(|max| max.to_string()),
5658
ipv4: config.ipv4,
5759
ipv6: config.ipv6,
5860
ml_kem: config.ml_kem.map(|ml_kem| ml_kem.to_string()),
@@ -96,6 +98,15 @@ impl TryInto<Option<crate::config::Ntcp2Config>> for Ntcp2Config {
9698
ipv6: self.ipv6,
9799
publish_ipv4: self.publish_ipv4,
98100
publish_ipv6: self.publish_ipv6,
101+
max_connections: match self.max_connections {
102+
None => None,
103+
Some(max) if max.is_empty() => None,
104+
Some(max) => Some(
105+
max.parse::<NonZeroUsize>()
106+
.map_err(|_| String::from("Invalid NTCP2 maximum connections"))?
107+
.get(),
108+
),
109+
},
99110
publish: None,
100111
disable_pq: self.disable_pq,
101112
ml_kem: match self.ml_kem {

emissary-cli/src/ui/dioxus/settings/transports.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub fn TransportsTab() -> Element {
3636
ntcp2_ipv6_enabled,
3737
ntcp_disable_pq,
3838
ntcp2_ml_kem,
39+
ntcp2_max_connections,
3940
ntcp2_enabled,
4041
) = {
4142
let state = state.read();
@@ -49,6 +50,7 @@ pub fn TransportsTab() -> Element {
4950
state.settings.ntcp2.ipv6.unwrap_or(true),
5051
state.settings.ntcp2.disable_pq.unwrap_or(false),
5152
state.settings.ntcp2.ml_kem.clone().unwrap_or_default(),
53+
state.settings.ntcp2.max_connections.clone().unwrap_or_default(),
5254
state.settings.ntcp2.enabled,
5355
)
5456
};
@@ -136,6 +138,22 @@ pub fn TransportsTab() -> Element {
136138
state.settings.dirty = true;
137139
}
138140
}
141+
span { class: "sf-label", "Max connections" }
142+
input {
143+
r#type: "text",
144+
class: if !ntcp2_max_connections.is_empty() && ntcp2_max_connections.parse::<u16>().is_err() {
145+
"sf-input-short input-error"
146+
} else {
147+
""
148+
},
149+
value: "{ntcp2_max_connections}",
150+
placeholder: "Max connections",
151+
oninput: move |e: Event<FormData>| {
152+
let mut state = state.write();
153+
state.settings.ntcp2.max_connections = Some(e.value());
154+
state.settings.dirty = true;
155+
}
156+
}
139157
span { class: "sf-label", "ML-KEM" }
140158
input {
141159
r#type: "text",

emissary-core/src/config.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
use crate::{primitives::Str, profile::Profile, tunnel::TunnelPoolConfig};
2020

2121
use alloc::{string::String, vec::Vec};
22-
use core::net::{Ipv4Addr, Ipv6Addr};
22+
use core::{
23+
net::{Ipv4Addr, Ipv6Addr},
24+
num::NonZeroUsize,
25+
};
2326

2427
/// Exploratory tunnel pool config.
2528
#[derive(Clone, PartialEq, Eq)]
@@ -93,6 +96,11 @@ pub struct Ntcp2Config {
9396

9497
/// Should NTCP2 IPv6 address be published in router info.
9598
pub publish_ipv6: bool,
99+
100+
/// Maximum number of connections.
101+
///
102+
/// `None` for unlimited connections.
103+
pub max_connections: Option<NonZeroUsize>,
96104
}
97105

98106
/// SSU2 configuration.

emissary-core/src/primitives/router_info.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ pub(crate) mod builder {
520520
publish_ipv6,
521521
ml_kem,
522522
disable_pq,
523+
..
523524
}) = self.ntcp2.take()
524525
{
525526
if ipv4 {

emissary-core/src/transport/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,7 @@ mod tests {
17011701

17021702
async fn external_address_discovered_ntcp2(ipv4: bool) {
17031703
let context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
1704+
max_connections: None,
17041705
port: 0,
17051706
ipv4_host: ipv4.then_some("127.0.0.1".parse().unwrap()),
17061707
ipv6_host: (!ipv4).then_some("::1".parse().unwrap()),
@@ -1776,6 +1777,7 @@ mod tests {
17761777

17771778
async fn external_address_discovered_ntcp2_unpublished(ipv4: bool) {
17781779
let context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
1780+
max_connections: None,
17791781
port: 0,
17801782
ipv4_host: None,
17811783
ipv6_host: None,
@@ -1998,6 +2000,7 @@ mod tests {
19982000
.0
19992001
.unwrap();
20002002
let ntcp2_context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
2003+
max_connections: None,
20012004
port: 0,
20022005
ipv4_host: None,
20032006
ipv6_host: None,
@@ -2091,6 +2094,7 @@ mod tests {
20912094

20922095
async fn discovered_address_doesnt_match_published_address_ntcp2(ipv4: bool) {
20932096
let context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
2097+
max_connections: None,
20942098
port: 0,
20952099
ipv4_host: ipv4.then_some("127.0.0.1".parse().unwrap()),
20962100
ipv6_host: (!ipv4).then_some("::1".parse().unwrap()),
@@ -2660,6 +2664,7 @@ mod tests {
26602664
event_handle.clone(),
26612665
);
26622666
let context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
2667+
max_connections: None,
26632668
port: 0,
26642669
ipv4_host: Some("192.168.0.1".parse().unwrap()),
26652670
ipv6_host: None,
@@ -2866,6 +2871,7 @@ mod tests {
28662871
);
28672872
builder.register_netdb_handle(handle);
28682873
let context = Ntcp2Transport::<MockRuntime>::initialize(Some(Ntcp2Config {
2874+
max_connections: None,
28692875
port: 0,
28702876
ipv4_host: Some("192.168.0.1".parse().unwrap()),
28712877
ipv6_host: None,
@@ -3598,6 +3604,7 @@ mod tests {
35983604
ipv6_mtu: None,
35993605
});
36003606
let ntcp2_config = self.ntcp2.is_some().then(|| Ntcp2Config {
3607+
max_connections: None,
36013608
port: 8889,
36023609
ipv4_host: None,
36033610
ipv6_host: None,
@@ -3726,6 +3733,7 @@ mod tests {
37263733

37273734
let (router_info, ..) = RouterInfoBuilder::default()
37283735
.with_ntcp2(Ntcp2Config {
3736+
max_connections: None,
37293737
port: 9999,
37303738
ipv4_host: ipv4.then_some("127.0.0.1".parse().unwrap()),
37313739
ipv6_host: (!ipv4).then_some("::1".parse().unwrap()),
@@ -3787,6 +3795,7 @@ mod tests {
37873795

37883796
let (router_info, ..) = RouterInfoBuilder::default()
37893797
.with_ntcp2(Ntcp2Config {
3798+
max_connections: None,
37903799
port: 9999,
37913800
ipv4_host: remote_ipv4.then_some("127.0.0.1".parse().unwrap()),
37923801
ipv6_host: (!remote_ipv4).then_some("::1".parse().unwrap()),
@@ -3915,6 +3924,7 @@ mod tests {
39153924

39163925
let (router_info, ..) = RouterInfoBuilder::default()
39173926
.with_ntcp2(Ntcp2Config {
3927+
max_connections: None,
39183928
port: 9999,
39193929
ipv4_host: Some("127.0.0.1".parse().unwrap()),
39203930
ipv6_host: None,

emissary-core/src/transport/ntcp2/metrics.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub const INBOUND_BW: &str = "ntcp2_inbound_bw_count";
3131
pub const OUTBOUND_BW: &str = "ntcp2_outbound_bw_count";
3232
pub const CONNECTIONS_OPENED: &str = "ntcp2_connections_opened_count";
3333
pub const CONNECTIONS_CLOSED: &str = "ntcp2_connections_closed_count";
34+
pub const CONNECTIONS_DROPPED: &str = "ntcp2_connections_dropped";
3435

3536
// active connection
3637
pub const NUM_BLOCKS_PER_MSG: &str = "ntcp2_blocks_per_msg";
@@ -73,6 +74,10 @@ pub fn register_metrics(mut metrics: Vec<MetricType>) -> Vec<MetricType> {
7374
name: CONNECTIONS_CLOSED,
7475
description: "how many connections have been closed",
7576
});
77+
metrics.push(MetricType::Counter {
78+
name: CONNECTIONS_DROPPED,
79+
description: "how many connections have been dropped",
80+
});
7681

7782
// gauges
7883
metrics.push(MetricType::Gauge {

0 commit comments

Comments
 (0)