Skip to content

Commit f2a4988

Browse files
committed
feat: add port extraction and deduplication for per-IP probe targets
1 parent a012b5c commit f2a4988

1 file changed

Lines changed: 51 additions & 7 deletions

File tree

src/proxy/trunk_health.rs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,52 @@ pub fn dest_name(dest: &str) -> &str {
158158
dest.strip_prefix("sip:").unwrap_or(dest)
159159
}
160160

161+
/// Extract port from a normalized (sip: stripped) target like `host:port`.
162+
/// Returns `None` if no port is present.
163+
fn extract_port(target: &str) -> Option<String> {
164+
let target = target.trim();
165+
if let Some(pos) = target.rfind(':') {
166+
let port_str = &target[pos + 1..];
167+
if port_str.parse::<u16>().is_ok() {
168+
return Some(port_str.to_string());
169+
}
170+
}
171+
None
172+
}
173+
174+
/// Build a deduplicated list of probe targets for per-IP mode.
175+
/// Each inbound_host inherits the port from `dest` if it does not already have one.
176+
/// Duplicate (host:port) combinations are removed.
177+
pub fn build_per_ip_targets(dest: &str, inbound_hosts: &[String]) -> Vec<String> {
178+
let dest_clean = dest_name(dest).to_string();
179+
let default_port = extract_port(&dest_clean);
180+
181+
let mut seen = std::collections::HashSet::new();
182+
let mut targets = Vec::new();
183+
184+
let add_target = |t: &str, targets: &mut Vec<String>, seen: &mut std::collections::HashSet<String>| {
185+
if seen.insert(t.to_string()) {
186+
targets.push(t.to_string());
187+
}
188+
};
189+
190+
add_target(&dest_clean, &mut targets, &mut seen);
191+
192+
for host in inbound_hosts {
193+
let host = host.trim();
194+
if extract_port(host).is_some() {
195+
add_target(host, &mut targets, &mut seen);
196+
} else if let Some(ref port) = default_port {
197+
let normalized = format!("{}:{}", host, port);
198+
add_target(&normalized, &mut targets, &mut seen);
199+
} else {
200+
add_target(host, &mut targets, &mut seen);
201+
}
202+
}
203+
204+
targets
205+
}
206+
161207
/// Spawn the background health-check loop.
162208
///
163209
/// It reads `trunks_fn` (which returns the current trunk map) on each tick,
@@ -219,13 +265,11 @@ fn spawn_health_loop_with_timeout(
219265
let has_hosts = !cfg.inbound_hosts.is_empty();
220266

221267
if per_ip && has_hosts {
222-
// ── Per-IP mode: probe each inbound_host individually.
223-
// Dest is probed as part of the targets along with each inbound host
224-
// so we can report per-peer health. Trunk is healthy only when ALL
225-
// targets are healthy (AND logic), since each peer should be reachable.
226-
let mut targets: Vec<String> = Vec::with_capacity(1 + cfg.inbound_hosts.len());
227-
targets.push(cfg.dest.clone());
228-
targets.extend(cfg.inbound_hosts.clone());
268+
// ── Per-IP mode: probe each unique (host:port) target.
269+
// Inbound hosts inherit the port from dest if they don't have one.
270+
// Duplicates are removed: if dest and inbound_host resolve to the
271+
// same host:port, only one probe is sent.
272+
let targets = build_per_ip_targets(&cfg.dest, &cfg.inbound_hosts);
229273

230274
let mut per_ip_results: Vec<PerIpHealthState> =
231275
Vec::with_capacity(targets.len());

0 commit comments

Comments
 (0)