feat(app): add address-family selection and dual-stack targets

This commit is contained in:
Nils Andreas Svee committed 2026-10-07 14:17:11 +07:00
1 parent 988a360fef
commit da7472e10a
16 files changed
+334 -44

No files matched your search

+9 -1
View File
@@ -59,9 +59,17 @@ Configuration is grouped by ownership:
probe tracking policy.
Clone one config to connect to multiple endpoints. Managed targets retain their
own ID, endpoint, and authentication inheritance/override; shared
own ID, endpoint, address-family override, and authentication inheritance/override; shared
`ManagedClientConfig.client` is reusable without endpoint mutation.
`ManagedTargetConfig.address_family` defaults to `None`, inheriting the shared
family policy. `Some(AddressFamily::Ipv4)`, `Some(AddressFamily::Ipv6)`, or
`Some(AddressFamily::Any)` overrides it for that target. Every new generation
resolves the original endpoint again; callers can constrain a hostname without
replacing it with a resolved IP. Changing the family specification replaces
that target's generation, including switching between inheritance and an equal
explicit override. Hostname expansion and discovery are frontend policies.
Blocking receive policy belongs to `Client`:
`client.set_recv_timeout(Some(duration))?` sets it before or after opening,
and `client.set_recv_timeout(None)?` restores the default indefinite wait.
+9 -3
View File
@@ -2,7 +2,7 @@ use std::{fmt, net::SocketAddr, sync::Arc, time::Duration};
use thiserror::Error;
use crate::{Authentication, ClientConfig, ClientError, ClientEvent};
use crate::{AddressFamily, Authentication, ClientConfig, ClientError, ClientEvent};
use super::TargetId;
@@ -48,11 +48,16 @@ impl TargetAuth {
}
}
/// Endpoint and authentication specification for one managed target.
/// Endpoint, address-family and authentication specification for one managed target.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ManagedTargetConfig {
pub id: TargetId,
pub server_addr: String,
/// Override the shared address-family policy. `None` inherits it.
///
/// The endpoint remains a hostname when supplied as one, so every new
/// generation resolves it again using the effective family policy.
pub address_family: Option<AddressFamily>,
pub auth: TargetAuth,
}
@@ -61,6 +66,7 @@ impl ManagedTargetConfig {
Self {
id: id.into(),
server_addr: server_addr.into(),
address_family: None,
auth: TargetAuth::Inherit,
}
}
@@ -86,7 +92,7 @@ pub enum ManagedCompletionPolicy {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ManagedClientConfig {
/// Reusable session/socket configuration shared across target generations.
/// Each target supplies its endpoint and resolves its authentication policy.
/// Each target supplies its endpoint and resolves its family and authentication policies.
pub client: ClientConfig,
pub pacing: ManagedPacing,
pub completion: ManagedCompletionPolicy,
@@ -397,6 +397,9 @@ fn build_task(
.checked_add(1)
.ok_or(ManagedConfigError::GenerationExhausted)?;
let mut client_config = config.client.clone();
client_config.address_family = target
.address_family
.unwrap_or(config.client.address_family);
client_config.auth = target.auth.resolve(&config.client.auth);
validate_target_config(&client_config).map_err(|source| {
ManagedConfigError::InvalidTarget {
@@ -191,6 +191,9 @@ impl ManagedClientTask {
return Err(ManagedCommandApplyError::DuplicateTargetId { id: target.id });
}
let mut client_config = self.config.client.clone();
client_config.address_family = target
.address_family
.unwrap_or(self.config.client.address_family);
client_config.auth = target.auth.resolve(&self.config.client.auth);
validate_target_config(&client_config).map_err(|source| {
ManagedCommandApplyError::InvalidTarget {