Skip to content

Commit ae36d14

Browse files
committed
client: interface labels
1 parent e76cd42 commit ae36d14

6 files changed

Lines changed: 229 additions & 87 deletions

File tree

crates/cli/src/lib.rs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,70 @@ pub enum SecretArg {
1717
pub struct InterfaceSpec {
1818
pub name: String,
1919
pub secret: Option<SecretKey>,
20+
pub label: Option<String>,
2021
}
2122

2223
pub struct InterfaceConfig {
2324
pub binding: InterfaceBinding,
2425
pub endpoint_id: String,
2526
pub secret_key: SecretKey,
27+
pub label: String,
2628
}
2729

2830
impl FromStr for InterfaceSpec {
2931
type Err = String;
3032

3133
fn from_str(s: &str) -> Result<Self, Self::Err> {
32-
if let Some((name, secret)) = s.rsplit_once(':') {
33-
if name.is_empty() {
34-
return Err("interface name cannot be empty".to_string());
34+
let parts = s.split(':').collect::<Vec<_>>();
35+
match parts.as_slice() {
36+
[name] => {
37+
if name.is_empty() {
38+
return Err("interface name cannot be empty".to_string());
39+
}
40+
Ok(Self {
41+
name: (*name).to_string(),
42+
secret: None,
43+
label: None,
44+
})
3545
}
36-
37-
let secret = SecretKey::from_str(secret).map_err(|_| {
38-
"invalid interface secret; expected iroh secret key hex".to_string()
39-
})?;
40-
return Ok(Self {
41-
name: name.to_string(),
42-
secret: Some(secret),
43-
});
44-
}
45-
46-
if s.is_empty() {
47-
return Err("interface name cannot be empty".to_string());
46+
[name, secret] => {
47+
if name.is_empty() {
48+
return Err("interface name cannot be empty".to_string());
49+
}
50+
let secret = SecretKey::from_str(secret).map_err(|_| {
51+
"invalid interface secret; expected iroh secret key hex".to_string()
52+
})?;
53+
Ok(Self {
54+
name: (*name).to_string(),
55+
secret: Some(secret),
56+
label: None,
57+
})
58+
}
59+
[name, secret, label] => {
60+
if name.is_empty() {
61+
return Err("interface name cannot be empty".to_string());
62+
}
63+
if label.is_empty() {
64+
return Err("interface label cannot be empty".to_string());
65+
}
66+
let secret = if secret.is_empty() {
67+
None
68+
} else {
69+
Some(SecretKey::from_str(secret).map_err(|_| {
70+
"invalid interface secret; expected iroh secret key hex".to_string()
71+
})?)
72+
};
73+
Ok(Self {
74+
name: (*name).to_string(),
75+
secret,
76+
label: Some((*label).to_string()),
77+
})
78+
}
79+
_ => Err(
80+
"interface spec must be interface, interface:secret, or interface:secret:label"
81+
.to_string(),
82+
),
4883
}
49-
50-
Ok(Self {
51-
name: s.to_string(),
52-
secret: None,
53-
})
5484
}
5585
}
5686

@@ -107,11 +137,14 @@ pub fn parse_interface_configs(specs: &[InterfaceSpec]) -> Result<Vec<InterfaceC
107137

108138
fn build_interface_config(spec: &InterfaceSpec) -> Result<InterfaceConfig> {
109139
let secret_key = spec.secret.clone().unwrap_or_else(|| SecretKey::generate());
110-
let binding = resolve_interface_ipv4(&spec.name)?;
140+
let mut binding = resolve_interface_ipv4(&spec.name)?;
141+
let label = spec.label.clone().unwrap_or_else(|| spec.name.clone());
142+
binding.display_name.clone_from(&label);
111143
let endpoint_id = secret_key.public().to_string();
112144
Ok(InterfaceConfig {
113145
binding,
114146
endpoint_id,
115147
secret_key,
148+
label,
116149
})
117150
}

crates/client/src/main.rs

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ async fn main() -> Result<()> {
120120
"-".to_string(),
121121
interface_configs
122122
.iter()
123-
.map(|config| config.binding.name.clone())
123+
.map(|config| config.label.clone())
124124
.collect(),
125125
Some(ui_command_tx),
126126
)
@@ -153,6 +153,7 @@ async fn main() -> Result<()> {
153153
binding,
154154
endpoint_id,
155155
secret_key,
156+
label: _,
156157
} = config;
157158
let path = match connect_path_with_secret(
158159
binding.clone(),
@@ -167,13 +168,13 @@ async fn main() -> Result<()> {
167168
.connection()
168169
.expect("newly connected path has a live connection");
169170
ctx.record_connection_paths(
170-
path.interface_name.clone(),
171+
path.display_name.clone(),
171172
&endpoint_id,
172173
&connection,
173174
true,
174175
);
175176
ctx.connected_path(
176-
&path.interface_name,
177+
&path.display_name,
177178
&endpoint_id,
178179
SocketAddr::V4(
179180
path.current_bound_addr()
@@ -184,10 +185,10 @@ async fn main() -> Result<()> {
184185
}
185186
Err(err) => {
186187
ctx.record_send_error(
187-
binding.name.clone(),
188+
binding.display_name.clone(),
188189
format!("initial connect failed, retrying: {err}"),
189190
);
190-
ctx.reconnect_failed(&binding.name, &err.to_string());
191+
ctx.reconnect_failed(&binding.display_name, &err.to_string());
191192
PathConnection::pending(binding, server_addr.clone(), secret_key, &cli.relays)
192193
}
193194
};
@@ -208,7 +209,7 @@ async fn main() -> Result<()> {
208209
paths
209210
.iter()
210211
.zip(health_endpoint_ids.iter())
211-
.map(|(path, endpoint_id)| (path.interface_name.clone(), endpoint_id.clone()))
212+
.map(|(path, endpoint_id)| (path.display_name.clone(), endpoint_id.clone()))
212213
.collect(),
213214
Duration::from_millis(cli.tc_backlog_poll_ms),
214215
cli.tc_backlog_degrade_bytes,
@@ -218,12 +219,12 @@ async fn main() -> Result<()> {
218219
let health = spawn_health_receivers(&paths, ctx.clone(), strategy.clone());
219220
for path in &paths {
220221
if path.reconnect_requested() {
221-
strategy.record_interface_reconnecting(&path.interface_name);
222+
strategy.record_interface_reconnecting(&path.display_name);
222223
}
223224
}
224225
let path_names = paths
225226
.iter()
226-
.map(|path| path.interface_name.clone())
227+
.map(|path| path.display_name.clone())
227228
.collect::<Vec<_>>();
228229
spawn_interface_watchers(&paths, strategy.clone(), ctx.clone());
229230
spawn_reconnect_loops(&paths, &health_endpoint_ids, strategy.clone(), ctx.clone());
@@ -255,6 +256,9 @@ async fn main() -> Result<()> {
255256
"tui hotkey",
256257
);
257258
}
259+
tui::UiCommand::ToggleWeightedAuto => {
260+
strategy.toggle_weighted_auto_split(&ctx);
261+
}
258262
}
259263
}
260264
});
@@ -324,7 +328,7 @@ async fn main() -> Result<()> {
324328

325329
for path in &paths {
326330
let path = path.clone();
327-
let interface_name = path.interface_name.clone();
331+
let interface_name = path.display_name.clone();
328332
let listen_socket = listen_socket.clone();
329333
let last_ingest_peer = last_ingest_peer.clone();
330334
let ctx = ctx.clone();
@@ -429,11 +433,12 @@ fn spawn_interface_watchers(
429433
tokio::time::sleep(INTERFACE_WATCH_INTERVAL).await;
430434

431435
match resolve_interface_ipv4(&path.interface_name) {
432-
Ok(binding) => {
436+
Ok(mut binding) => {
437+
binding.display_name.clone_from(&path.display_name);
433438
if let Some(endpoint) = path.mark_interface_changed(binding.clone()) {
434-
strategy.record_interface_reconnecting(&path.interface_name);
439+
strategy.record_interface_reconnecting(&path.display_name);
435440
ctx.record_send_error(
436-
path.interface_name.clone(),
441+
path.display_name.clone(),
437442
format!(
438443
"interface address changed, reconnecting: {}",
439444
binding.bind_addr
@@ -452,7 +457,7 @@ fn spawn_interface_watchers(
452457
} else {
453458
path.request_reconnect();
454459
}
455-
strategy.record_interface_dead(&path.interface_name);
460+
strategy.record_interface_dead(&path.display_name);
456461
}
457462
}
458463
}
@@ -478,7 +483,7 @@ fn spawn_connection_liveness(
478483
};
479484

480485
ctx.record_connection_paths(
481-
path.interface_name.clone(),
486+
path.display_name.clone(),
482487
&endpoint_id,
483488
&connection,
484489
false,
@@ -488,9 +493,9 @@ fn spawn_connection_liveness(
488493
};
489494

490495
let connection_id = connection.stable_id();
491-
strategy.record_interface_reconnecting(&path.interface_name);
496+
strategy.record_interface_reconnecting(&path.display_name);
492497
ctx.record_send_error(
493-
path.interface_name.clone(),
498+
path.display_name.clone(),
494499
format!("connection closed: {reason}"),
495500
);
496501
if let Some(endpoint) = path.mark_failed(Some(connection_id)) {
@@ -530,9 +535,9 @@ fn spawn_split_display_updates(
530535
!matches!(effective, PathStrategy::Split)
531536
|| split_interface_names
532537
.iter()
533-
.any(|name| name == &path.interface_name)
538+
.any(|name| name == &path.display_name)
534539
})
535-
.map(|path| path.interface_name.clone())
540+
.map(|path| path.display_name.clone())
536541
.collect::<Vec<_>>();
537542
ctx.record_split_percentages(
538543
&strategy.effective_split_percentages_for(&displayed_split_names),
@@ -558,47 +563,47 @@ fn spawn_reconnect_loops(
558563
continue;
559564
}
560565

561-
strategy.record_interface_reconnecting(&path.interface_name);
566+
strategy.record_interface_reconnecting(&path.display_name);
562567
match tokio::time::timeout(RECONNECT_ATTEMPT_TIMEOUT, path.reconnect()).await {
563568
Ok(Ok(connection)) => {
564569
retry_delay = Duration::from_millis(250);
565-
strategy.record_interface_success(&path.interface_name);
570+
strategy.record_interface_success(&path.display_name);
566571
ctx.record_connection_paths(
567-
path.interface_name.clone(),
572+
path.display_name.clone(),
568573
&endpoint_id,
569574
&connection,
570575
true,
571576
);
572577
if let Some(bound_addr) = path.current_bound_addr() {
573578
ctx.connected_path(
574-
&path.interface_name,
579+
&path.display_name,
575580
&endpoint_id,
576581
SocketAddr::V4(bound_addr),
577582
);
578583
}
579584
}
580585
Ok(Err(err)) => {
581-
strategy.record_interface_dead(&path.interface_name);
586+
strategy.record_interface_dead(&path.display_name);
582587
let error = err.to_string();
583588
ctx.record_send_error(
584-
path.interface_name.clone(),
589+
path.display_name.clone(),
585590
format!("reconnect failed: {error}"),
586591
);
587-
ctx.reconnect_failed(&path.interface_name, &error);
592+
ctx.reconnect_failed(&path.display_name, &error);
588593
tokio::time::sleep(retry_delay).await;
589594
retry_delay = (retry_delay * 2).min(Duration::from_secs(5));
590595
}
591596
Err(_) => {
592-
strategy.record_interface_dead(&path.interface_name);
597+
strategy.record_interface_dead(&path.display_name);
593598
let error = format!(
594599
"reconnect timed out after {}s",
595600
RECONNECT_ATTEMPT_TIMEOUT.as_secs()
596601
);
597602
ctx.record_send_error(
598-
path.interface_name.clone(),
603+
path.display_name.clone(),
599604
format!("reconnect failed: {error}"),
600605
);
601-
ctx.reconnect_failed(&path.interface_name, &error);
606+
ctx.reconnect_failed(&path.display_name, &error);
602607
tokio::time::sleep(retry_delay).await;
603608
retry_delay = (retry_delay * 2).min(Duration::from_secs(5));
604609
}
@@ -639,18 +644,18 @@ fn send_packet(
639644
.filter(|path| {
640645
split_interface_names
641646
.iter()
642-
.any(|name| name == &path.interface_name)
647+
.any(|name| name == &path.display_name)
643648
})
644649
.collect::<Vec<_>>();
645650
let displayed_split_names = if matches!(effective, PathStrategy::Split) {
646651
split_paths
647652
.iter()
648-
.map(|path| path.interface_name.clone())
653+
.map(|path| path.display_name.clone())
649654
.collect::<Vec<_>>()
650655
} else {
651656
active_paths
652657
.iter()
653-
.map(|path| path.interface_name.clone())
658+
.map(|path| path.display_name.clone())
654659
.collect::<Vec<_>>()
655660
};
656661
ctx.record_split_percentages(&strategy.effective_split_percentages_for(&displayed_split_names));
@@ -663,7 +668,7 @@ fn send_packet(
663668
.filter(|path| {
664669
rescue_interface_names
665670
.iter()
666-
.any(|name| name == &path.interface_name)
671+
.any(|name| name == &path.display_name)
667672
})
668673
.collect::<Vec<_>>()
669674
};
@@ -677,11 +682,11 @@ fn send_packet(
677682
) {
678683
let split_names = split_paths
679684
.iter()
680-
.map(|path| path.interface_name.clone())
685+
.map(|path| path.display_name.clone())
681686
.collect::<Vec<_>>();
682687
let fragments = u8::try_from(split_paths.len()).expect("path count fits in u8");
683688
let split_ranges =
684-
weighted_split_ranges(payload.len(), &strategy.split_weights(&split_names));
689+
weighted_split_ranges(payload.len(), &strategy.active_split_weights(&split_names));
685690
for (fragment, path) in split_paths.iter().enumerate() {
686691
let (start, end) = split_ranges[fragment];
687692
let packet = Arc::new(encode_packet(
@@ -758,24 +763,24 @@ fn send_on_path(
758763
let connection_id = path.connection_id();
759764
match path.send(packet) {
760765
Ok(()) => {
761-
strategy.record_interface_success(&path.interface_name);
762-
strategy.record_interface_send(&path.interface_name, packet_len);
763-
ctx.record_send(path.interface_name.clone(), payload_len);
766+
strategy.record_interface_success(&path.display_name);
767+
strategy.record_interface_send(&path.display_name, packet_len);
768+
ctx.record_send(path.display_name.clone(), payload_len);
764769
}
765770
Err(err) => {
766-
strategy.record_interface_failure(&path.interface_name);
771+
strategy.record_interface_failure(&path.display_name);
767772
if let Some(endpoint) = path.mark_failed(connection_id) {
768773
tokio::spawn(async move {
769774
endpoint.close().await;
770775
});
771776
}
772-
ctx.record_send_error(path.interface_name.clone(), err.to_string());
773-
ctx.send_failure(&path.interface_name, seq, &err.to_string());
777+
ctx.record_send_error(path.display_name.clone(), err.to_string());
778+
ctx.send_failure(&path.display_name, seq, &err.to_string());
774779
strategy.degrade_to_redundant(
775780
ctx,
776781
format!(
777782
"send error interface={} sequence={} error={err}",
778-
path.interface_name, seq
783+
path.display_name, seq
779784
),
780785
);
781786
}

0 commit comments

Comments
 (0)