Merge branch 'feature/authentication-types'
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 32s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 29s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo doc (push) Successful in 21s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m12s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 10s
Supply chain / cargo-deny bans licenses sources (push) Successful in 9s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m5s
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo clippy (push) Successful in 32s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 29s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 9s
Rust CI / cargo doc (push) Successful in 21s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m12s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 10s
Supply chain / cargo-deny bans licenses sources (push) Successful in 9s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m5s
This commit is contained in:
16 files changed
+184
-119
No files matched your search
@@ -1,10 +1,10 @@
|
|||||||
use std::time::Duration;
|
use std::{fmt, time::Duration};
|
||||||
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use irtt_client::{MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH};
|
use irtt_client::{MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH};
|
||||||
use irtt_proto::{Clock, ReceivedStats, StampAt};
|
use irtt_proto::{Clock, ReceivedStats, StampAt};
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Args)]
|
#[derive(Clone, clap::Args)]
|
||||||
pub struct CommonClientArgs {
|
pub struct CommonClientArgs {
|
||||||
/// Probe interval.
|
/// Probe interval.
|
||||||
#[arg(long, default_value = "1s", value_parser = parse_duration)]
|
#[arg(long, default_value = "1s", value_parser = parse_duration)]
|
||||||
@@ -63,6 +63,23 @@ pub struct CommonClientArgs {
|
|||||||
pub loose: bool,
|
pub loose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for CommonClientArgs {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("CommonClientArgs")
|
||||||
|
.field("interval", &self.interval)
|
||||||
|
.field("length", &self.length)
|
||||||
|
.field("hmac", &self.hmac.as_ref().map(|_| "[REDACTED]"))
|
||||||
|
.field("clock", &self.clock)
|
||||||
|
.field("tstamp", &self.tstamp)
|
||||||
|
.field("stats", &self.stats)
|
||||||
|
.field("server_fill", &self.server_fill)
|
||||||
|
.field("dscp", &self.dscp)
|
||||||
|
.field("ttl", &self.ttl)
|
||||||
|
.field("loose", &self.loose)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||||
pub enum ClockArg {
|
pub enum ClockArg {
|
||||||
Wall,
|
Wall,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use irtt_client::{ClientConfig, NegotiationPolicy, SocketConfig};
|
use irtt_client::{Authentication, ClientConfig, HmacKey, NegotiationPolicy, SocketConfig};
|
||||||
|
|
||||||
use super::args::CommonClientArgs;
|
use super::args::CommonClientArgs;
|
||||||
|
|
||||||
@@ -21,7 +21,12 @@ impl CommonClientArgs {
|
|||||||
stamp_at: self.tstamp.into(),
|
stamp_at: self.tstamp.into(),
|
||||||
clock: self.clock.into(),
|
clock: self.clock.into(),
|
||||||
dscp: self.dscp,
|
dscp: self.dscp,
|
||||||
hmac_key: self.hmac.as_ref().map(|key| key.as_bytes().to_vec()),
|
auth: self
|
||||||
|
.hmac
|
||||||
|
.as_ref()
|
||||||
|
.map_or(Authentication::Unauthenticated, |key| {
|
||||||
|
Authentication::Hmac(HmacKey::new(key.as_bytes()))
|
||||||
|
}),
|
||||||
server_fill: self.server_fill.clone(),
|
server_fill: self.server_fill.clone(),
|
||||||
negotiation_policy: if self.loose {
|
negotiation_policy: if self.loose {
|
||||||
NegotiationPolicy::Loose
|
NegotiationPolicy::Loose
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::{collections::HashSet, fmt};
|
|||||||
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use irtt_client::{
|
use irtt_client::{
|
||||||
managed::{ManagedPacing, ManagedTargetConfig, TargetId},
|
managed::{ManagedPacing, ManagedTargetConfig, TargetAuth, TargetId},
|
||||||
ClientAuthConfig,
|
Authentication, HmacKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// One raw positional target captured by Clap.
|
/// One raw positional target captured by Clap.
|
||||||
@@ -35,23 +35,6 @@ pub fn parse_target(input: &str) -> Result<TargetArg, String> {
|
|||||||
Ok(TargetArg::new(input))
|
Ok(TargetArg::new(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
pub enum TargetAuth {
|
|
||||||
Inherit,
|
|
||||||
Override(Vec<u8>),
|
|
||||||
Disable,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for TargetAuth {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Inherit => f.write_str("Inherit"),
|
|
||||||
Self::Override(_) => f.write_str("Override(<redacted>)"),
|
|
||||||
Self::Disable => f.write_str("Disable"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct TargetSpec {
|
pub struct TargetSpec {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
@@ -80,16 +63,7 @@ impl fmt::Debug for PreparedTarget {
|
|||||||
f.debug_struct("PreparedTarget")
|
f.debug_struct("PreparedTarget")
|
||||||
.field("label", &self.label)
|
.field("label", &self.label)
|
||||||
.field("server_addr", &self.managed.server_addr)
|
.field("server_addr", &self.managed.server_addr)
|
||||||
.field(
|
.field("auth", &self.managed.auth)
|
||||||
"auth",
|
|
||||||
&match &self.managed.auth {
|
|
||||||
None => TargetAuth::Inherit,
|
|
||||||
Some(ClientAuthConfig { hmac_key: Some(_) }) => {
|
|
||||||
TargetAuth::Override(Vec::new())
|
|
||||||
}
|
|
||||||
Some(ClientAuthConfig { hmac_key: None }) => TargetAuth::Disable,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,9 +190,9 @@ fn parse_target_syntax(
|
|||||||
Some(index) => {
|
Some(index) => {
|
||||||
let value = unescape(&input[index + "@hmac=".len()..])?;
|
let value = unescape(&input[index + "@hmac=".len()..])?;
|
||||||
let auth = if value.is_empty() {
|
let auth = if value.is_empty() {
|
||||||
TargetAuth::Disable
|
TargetAuth::Override(Authentication::Unauthenticated)
|
||||||
} else {
|
} else {
|
||||||
TargetAuth::Override(value.into_bytes())
|
TargetAuth::Override(Authentication::Hmac(HmacKey::new(value.into_bytes())))
|
||||||
};
|
};
|
||||||
(&input[..index], auth)
|
(&input[..index], auth)
|
||||||
}
|
}
|
||||||
@@ -287,13 +261,7 @@ pub fn prepare_managed_targets(specs: Vec<TargetSpec>) -> Result<Vec<PreparedTar
|
|||||||
let mut targets = Vec::with_capacity(specs.len());
|
let mut targets = Vec::with_capacity(specs.len());
|
||||||
for spec in specs {
|
for spec in specs {
|
||||||
let mut managed = ManagedTargetConfig::new(TargetId::from(spec.label.clone()), spec.addr);
|
let mut managed = ManagedTargetConfig::new(TargetId::from(spec.label.clone()), spec.addr);
|
||||||
managed.auth = match spec.auth {
|
managed.auth = spec.auth;
|
||||||
TargetAuth::Inherit => None,
|
|
||||||
TargetAuth::Override(hmac_key) => Some(ClientAuthConfig {
|
|
||||||
hmac_key: Some(hmac_key),
|
|
||||||
}),
|
|
||||||
TargetAuth::Disable => Some(ClientAuthConfig { hmac_key: None }),
|
|
||||||
};
|
|
||||||
targets.push(PreparedTarget {
|
targets.push(PreparedTarget {
|
||||||
label: spec.label,
|
label: spec.label,
|
||||||
managed,
|
managed,
|
||||||
|
|||||||
@@ -149,29 +149,37 @@ fn default_client_output_emits_a_packet_summary() {
|
|||||||
fn target_authentication_inherits_overrides_or_disables_the_global_key() {
|
fn target_authentication_inherits_overrides_or_disables_the_global_key() {
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use irtt_app::cmd::client::args::ClientArgs;
|
use irtt_app::cmd::client::args::ClientArgs;
|
||||||
|
use irtt_client::{managed::TargetAuth, Authentication, HmacKey};
|
||||||
let args = ClientArgs::try_parse_from([
|
let args = ClientArgs::try_parse_from([
|
||||||
"irtt-client",
|
"irtt-client",
|
||||||
"--hmac",
|
"--hmac",
|
||||||
"global-test-key",
|
"CONSPICUOUS_GLOBAL_HMAC_KEY",
|
||||||
"inherit=127.0.0.1:2112",
|
"inherit=127.0.0.1:2112",
|
||||||
"override=127.0.0.1:2113@hmac=target-test-key",
|
"override=127.0.0.1:2113@hmac=CONSPICUOUS_TARGET_HMAC_KEY",
|
||||||
"disable=127.0.0.1:2114@hmac=",
|
"disable=127.0.0.1:2114@hmac=",
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let setup = args.prepare().unwrap();
|
let setup = args.prepare().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
setup.managed_config().client.hmac_key.as_deref(),
|
setup.managed_config().client.auth,
|
||||||
Some(b"global-test-key".as_slice())
|
Authentication::Hmac(HmacKey::new(b"CONSPICUOUS_GLOBAL_HMAC_KEY".as_slice()))
|
||||||
);
|
);
|
||||||
let targets = setup.managed_targets();
|
let targets = setup.managed_targets();
|
||||||
let authentication: Vec<_> = targets
|
let authentication: Vec<_> = targets.iter().map(|target| target.auth.clone()).collect();
|
||||||
.iter()
|
|
||||||
.map(|target| target.auth.as_ref().map(|auth| auth.hmac_key.as_deref()))
|
|
||||||
.collect();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
authentication,
|
authentication,
|
||||||
[None, Some(Some(b"target-test-key".as_slice())), Some(None)]
|
[
|
||||||
|
TargetAuth::Inherit,
|
||||||
|
TargetAuth::Override(Authentication::Hmac(HmacKey::new(
|
||||||
|
b"CONSPICUOUS_TARGET_HMAC_KEY".as_slice()
|
||||||
|
))),
|
||||||
|
TargetAuth::Override(Authentication::Unauthenticated)
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
for output in [format!("{args:?}"), format!("{setup:?}")] {
|
||||||
|
assert!(!output.contains("CONSPICUOUS_GLOBAL_HMAC_KEY"));
|
||||||
|
assert!(!output.contains("CONSPICUOUS_TARGET_HMAC_KEY"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A real authenticated server detects broken client argument-to-config mapping.
|
// A real authenticated server detects broken client argument-to-config mapping.
|
||||||
|
|||||||
@@ -34,6 +34,25 @@ dependency at all.
|
|||||||
|
|
||||||
See `examples/` in the repository for runnable examples of each tier.
|
See `examples/` in the repository for runnable examples of each tier.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
`ClientConfig.auth` is concrete: `Authentication::Unauthenticated` or
|
||||||
|
`Authentication::Hmac(HmacKey::new(bytes))`. Keys share their bytes across
|
||||||
|
clones and print only `HmacKey([REDACTED])` through `Debug`. Borrowing key
|
||||||
|
material requires an explicit `as_bytes()` call. An empty key still selects
|
||||||
|
HMAC authentication; textual key syntax belongs to the application.
|
||||||
|
|
||||||
|
For managed targets, `ManagedTargetConfig.auth` defaults to `TargetAuth::Inherit`.
|
||||||
|
`TargetAuth::Override(Authentication::Unauthenticated)` disables shared
|
||||||
|
authentication, and `TargetAuth::Override(Authentication::Hmac(key))` replaces
|
||||||
|
the shared key. Initial construction and update planning resolve inheritance
|
||||||
|
into concrete session authentication before execution. Switching between
|
||||||
|
inheritance and an override replaces the target generation even when their
|
||||||
|
resolved authentication is equal.
|
||||||
|
|
||||||
|
This replaces the former `ClientConfig.hmac_key` and `ClientAuthConfig` API;
|
||||||
|
there are no compatibility aliases.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Full API documentation is on [docs.rs/irtt-client](https://docs.rs/irtt-client).
|
Full API documentation is on [docs.rs/irtt-client](https://docs.rs/irtt-client).
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
|
/// Shared HMAC key bytes. Debug output never includes the bytes or a fingerprint.
|
||||||
|
///
|
||||||
|
/// An empty key is still an explicitly configured HMAC key.
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct HmacKey(Arc<[u8]>);
|
||||||
|
|
||||||
|
impl HmacKey {
|
||||||
|
/// Store key bytes without interpreting textual key syntax.
|
||||||
|
pub fn new(bytes: impl Into<Arc<[u8]>>) -> Self {
|
||||||
|
Self(bytes.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly borrow the key material for authentication.
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HmacKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("HmacKey([REDACTED])")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concrete authentication for a low-level client session.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum Authentication {
|
||||||
|
/// Send and accept packets without HMAC authentication.
|
||||||
|
#[default]
|
||||||
|
Unauthenticated,
|
||||||
|
/// Authenticate packets with this key, including when it is empty.
|
||||||
|
Hmac(HmacKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Authentication {
|
||||||
|
pub(crate) fn hmac_key(&self) -> Option<&[u8]> {
|
||||||
|
match self {
|
||||||
|
Self::Unauthenticated => None,
|
||||||
|
Self::Hmac(key) => Some(key.as_bytes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::{net::SocketAddr, time::Duration};
|
use std::{net::SocketAddr, time::Duration};
|
||||||
|
|
||||||
|
use crate::Authentication;
|
||||||
|
|
||||||
/// Protocol compatibility bound for a requested `server_fill` value, in UTF-8
|
/// Protocol compatibility bound for a requested `server_fill` value, in UTF-8
|
||||||
/// bytes.
|
/// bytes.
|
||||||
///
|
///
|
||||||
@@ -118,11 +120,10 @@ pub struct ClientConfig {
|
|||||||
/// This is the six-bit DSCP value, not the full traffic-class byte, and
|
/// This is the six-bit DSCP value, not the full traffic-class byte, and
|
||||||
/// must be less than or equal to [`MAX_DSCP_CODEPOINT`].
|
/// must be less than or equal to [`MAX_DSCP_CODEPOINT`].
|
||||||
pub dscp: u8,
|
pub dscp: u8,
|
||||||
/// Optional HMAC key used to authenticate IRTT packets.
|
/// Concrete authentication for open, echo, and close packets.
|
||||||
///
|
///
|
||||||
/// When present, open, echo, and close packets are encoded and decoded with
|
/// HMAC peers must use the same key. An empty HMAC key remains authenticated.
|
||||||
/// HMAC authentication. The peer must be configured with the same key.
|
pub auth: Authentication,
|
||||||
pub hmac_key: Option<Vec<u8>>,
|
|
||||||
/// Optional server payload fill request.
|
/// Optional server payload fill request.
|
||||||
///
|
///
|
||||||
/// `None` leaves server fill behavior unspecified. `Some(value)` requests
|
/// `None` leaves server fill behavior unspecified. `Some(value)` requests
|
||||||
@@ -155,30 +156,6 @@ pub struct ClientConfig {
|
|||||||
pub max_pending_probes: usize,
|
pub max_pending_probes: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authentication settings used by client sessions.
|
|
||||||
///
|
|
||||||
/// `ClientConfig` keeps its existing top-level HMAC field for source
|
|
||||||
/// compatibility. Managed multi-target callers can use this smaller type for
|
|
||||||
/// per-target auth overrides without carrying a full independent
|
|
||||||
/// [`ClientConfig`].
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
||||||
pub struct ClientAuthConfig {
|
|
||||||
/// Optional HMAC key used to authenticate IRTT packets.
|
|
||||||
pub hmac_key: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Option<Vec<u8>>> for ClientAuthConfig {
|
|
||||||
fn from(hmac_key: Option<Vec<u8>>) -> Self {
|
|
||||||
Self { hmac_key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ClientAuthConfig> for Option<Vec<u8>> {
|
|
||||||
fn from(auth: ClientAuthConfig) -> Self {
|
|
||||||
auth.hmac_key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClientConfig {
|
impl Default for ClientConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -190,7 +167,7 @@ impl Default for ClientConfig {
|
|||||||
stamp_at: StampAt::Both,
|
stamp_at: StampAt::Both,
|
||||||
clock: Clock::Both,
|
clock: Clock::Both,
|
||||||
dscp: 0,
|
dscp: 0,
|
||||||
hmac_key: None,
|
auth: Authentication::Unauthenticated,
|
||||||
server_fill: None,
|
server_fill: None,
|
||||||
open_timeouts: DEFAULT_OPEN_TIMEOUTS.to_vec(),
|
open_timeouts: DEFAULT_OPEN_TIMEOUTS.to_vec(),
|
||||||
run_mode: RunMode::Normal,
|
run_mode: RunMode::Normal,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
mod async_client;
|
mod async_client;
|
||||||
|
mod auth;
|
||||||
mod client;
|
mod client;
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
@@ -49,10 +50,11 @@ mod timing;
|
|||||||
|
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub use async_client::AsyncClient;
|
pub use async_client::AsyncClient;
|
||||||
|
pub use auth::{Authentication, HmacKey};
|
||||||
pub use client::Client;
|
pub use client::Client;
|
||||||
pub use config::{
|
pub use config::{
|
||||||
ClientAuthConfig, ClientConfig, NegotiationPolicy, RecvBudget, RunMode, SocketConfig,
|
ClientConfig, NegotiationPolicy, RecvBudget, RunMode, SocketConfig, MAX_DSCP_CODEPOINT,
|
||||||
MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH,
|
MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH,
|
||||||
};
|
};
|
||||||
pub use error::ClientError;
|
pub use error::ClientError;
|
||||||
pub use event::{
|
pub use event::{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{fmt, net::SocketAddr, sync::Arc, time::Duration};
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ClientAuthConfig, ClientConfig, ClientError, ClientEvent};
|
use crate::{Authentication, ClientConfig, ClientError, ClientEvent};
|
||||||
|
|
||||||
use super::TargetId;
|
use super::TargetId;
|
||||||
|
|
||||||
@@ -26,12 +26,34 @@ pub struct TargetInstance {
|
|||||||
pub generation: u64,
|
pub generation: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Endpoint and optional authentication override for one managed target.
|
/// Authentication specification for a managed target.
|
||||||
|
///
|
||||||
|
/// Equality preserves the specification: inheritance and an explicit override
|
||||||
|
/// remain distinct even when they currently resolve to the same authentication.
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub enum TargetAuth {
|
||||||
|
/// Use the shared client configuration's authentication.
|
||||||
|
#[default]
|
||||||
|
Inherit,
|
||||||
|
/// Replace shared authentication. `Unauthenticated` explicitly disables it.
|
||||||
|
Override(Authentication),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TargetAuth {
|
||||||
|
pub(crate) fn resolve(&self, shared: &Authentication) -> Authentication {
|
||||||
|
match self {
|
||||||
|
Self::Inherit => shared.clone(),
|
||||||
|
Self::Override(auth) => auth.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Endpoint and authentication specification for one managed target.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct ManagedTargetConfig {
|
pub struct ManagedTargetConfig {
|
||||||
pub id: TargetId,
|
pub id: TargetId,
|
||||||
pub server_addr: String,
|
pub server_addr: String,
|
||||||
pub auth: Option<ClientAuthConfig>,
|
pub auth: TargetAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedTargetConfig {
|
impl ManagedTargetConfig {
|
||||||
@@ -39,7 +61,7 @@ impl ManagedTargetConfig {
|
|||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
server_addr: server_addr.into(),
|
server_addr: server_addr.into(),
|
||||||
auth: None,
|
auth: TargetAuth::Inherit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -945,9 +945,7 @@ impl ManagedClientTask {
|
|||||||
}
|
}
|
||||||
let mut client_config = self.config.client.clone();
|
let mut client_config = self.config.client.clone();
|
||||||
client_config.server_addr.clone_from(&target.server_addr);
|
client_config.server_addr.clone_from(&target.server_addr);
|
||||||
if let Some(auth) = &target.auth {
|
client_config.auth = target.auth.resolve(&self.config.client.auth);
|
||||||
client_config.hmac_key.clone_from(&auth.hmac_key);
|
|
||||||
}
|
|
||||||
validate_target_config(&client_config).map_err(|source| {
|
validate_target_config(&client_config).map_err(|source| {
|
||||||
ManagedCommandApplyError::InvalidTarget {
|
ManagedCommandApplyError::InvalidTarget {
|
||||||
id: target.id.clone(),
|
id: target.id.clone(),
|
||||||
@@ -2251,9 +2249,7 @@ fn build_task(
|
|||||||
.ok_or(ManagedConfigError::GenerationExhausted)?;
|
.ok_or(ManagedConfigError::GenerationExhausted)?;
|
||||||
let mut client_config = config.client.clone();
|
let mut client_config = config.client.clone();
|
||||||
client_config.server_addr.clone_from(&target.server_addr);
|
client_config.server_addr.clone_from(&target.server_addr);
|
||||||
if let Some(auth) = &target.auth {
|
client_config.auth = target.auth.resolve(&config.client.auth);
|
||||||
client_config.hmac_key.clone_from(&auth.hmac_key);
|
|
||||||
}
|
|
||||||
validate_target_config(&client_config).map_err(|source| {
|
validate_target_config(&client_config).map_err(|source| {
|
||||||
ManagedConfigError::InvalidTarget {
|
ManagedConfigError::InvalidTarget {
|
||||||
id: target.id.clone(),
|
id: target.id.clone(),
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ impl SessionMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_hmac(&self) -> bool {
|
pub(crate) fn has_hmac(&self) -> bool {
|
||||||
self.config.hmac_key.is_some()
|
self.config.auth.hmac_key().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn prepare_open_request(&self) -> Result<PreparedOpenRequest, ClientError> {
|
pub(crate) fn prepare_open_request(&self) -> Result<PreparedOpenRequest, ClientError> {
|
||||||
@@ -211,7 +211,7 @@ impl SessionMachine {
|
|||||||
params: &self.requested,
|
params: &self.requested,
|
||||||
no_test: self.config.run_mode == RunMode::NoTest,
|
no_test: self.config.run_mode == RunMode::NoTest,
|
||||||
},
|
},
|
||||||
self.config.hmac_key.as_deref(),
|
self.config.auth.hmac_key(),
|
||||||
)?;
|
)?;
|
||||||
Ok(PreparedOpenRequest {
|
Ok(PreparedOpenRequest {
|
||||||
bytes: bytes.into_boxed_slice(),
|
bytes: bytes.into_boxed_slice(),
|
||||||
@@ -223,7 +223,7 @@ impl SessionMachine {
|
|||||||
packet: &[u8],
|
packet: &[u8],
|
||||||
) -> Result<OpenDatagramDisposition, ClientError> {
|
) -> Result<OpenDatagramDisposition, ClientError> {
|
||||||
self.ensure_connected()?;
|
self.ensure_connected()?;
|
||||||
match irtt_proto::decode_open_reply(packet, self.config.hmac_key.as_deref()) {
|
match irtt_proto::decode_open_reply(packet, self.config.auth.hmac_key()) {
|
||||||
Ok(reply) => Ok(OpenDatagramDisposition::Trusted(reply)),
|
Ok(reply) => Ok(OpenDatagramDisposition::Trusted(reply)),
|
||||||
Err(error @ irtt_proto::ProtoError::ZeroToken) => Err(ClientError::Protocol(error)),
|
Err(error @ irtt_proto::ProtoError::ZeroToken) => Err(ClientError::Protocol(error)),
|
||||||
Err(
|
Err(
|
||||||
@@ -250,7 +250,7 @@ impl SessionMachine {
|
|||||||
let cleanup_close = if !reply_is_close && reply.token != 0 {
|
let cleanup_close = if !reply_is_close && reply.token != 0 {
|
||||||
let bytes = encode_request(
|
let bytes = encode_request(
|
||||||
RequestToEncode::Close { token: reply.token },
|
RequestToEncode::Close { token: reply.token },
|
||||||
self.config.hmac_key.as_deref(),
|
self.config.auth.hmac_key(),
|
||||||
)
|
)
|
||||||
.map_err(ClientError::from)
|
.map_err(ClientError::from)
|
||||||
.map_err(OpenAcceptanceFailure::without_cleanup)?;
|
.map_err(OpenAcceptanceFailure::without_cleanup)?;
|
||||||
@@ -313,7 +313,7 @@ impl SessionMachine {
|
|||||||
params: &session.negotiated.params,
|
params: &session.negotiated.params,
|
||||||
payload: &[],
|
payload: &[],
|
||||||
},
|
},
|
||||||
self.config.hmac_key.as_deref(),
|
self.config.auth.hmac_key(),
|
||||||
)?;
|
)?;
|
||||||
Ok(PreparedProbe {
|
Ok(PreparedProbe {
|
||||||
bytes: bytes.into_boxed_slice(),
|
bytes: bytes.into_boxed_slice(),
|
||||||
@@ -679,7 +679,7 @@ impl SessionMachine {
|
|||||||
decode_echo_reply(
|
decode_echo_reply(
|
||||||
packet,
|
packet,
|
||||||
&session.negotiated.params,
|
&session.negotiated.params,
|
||||||
self.config.hmac_key.as_deref(),
|
self.config.auth.hmac_key(),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use irtt_client::{Authentication, HmacKey};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hmac_open_ignores_missing_hmac_before_valid_reply() {
|
fn hmac_open_ignores_missing_hmac_before_valid_reply() {
|
||||||
let key = b"secret".to_vec();
|
let key = b"secret".to_vec();
|
||||||
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
||||||
config.hmac_key = Some(key.clone());
|
config.auth = Authentication::Hmac(HmacKey::new(key.clone()));
|
||||||
let params = default_params();
|
let params = default_params();
|
||||||
let server = start_fake_server(move |socket, tx| {
|
let server = start_fake_server(move |socket, tx| {
|
||||||
let (_, peer) = recv_request(&socket, &tx);
|
let (_, peer) = recv_request(&socket, &tx);
|
||||||
@@ -26,7 +27,7 @@ fn hmac_open_ignores_bad_hmac_before_valid_reply() {
|
|||||||
let key = b"secret".to_vec();
|
let key = b"secret".to_vec();
|
||||||
let wrong_key = b"wrong".to_vec();
|
let wrong_key = b"wrong".to_vec();
|
||||||
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
||||||
config.hmac_key = Some(key.clone());
|
config.auth = Authentication::Hmac(HmacKey::new(key.clone()));
|
||||||
let params = default_params();
|
let params = default_params();
|
||||||
let server = start_fake_server(move |socket, tx| {
|
let server = start_fake_server(move |socket, tx| {
|
||||||
let (_, peer) = recv_request(&socket, &tx);
|
let (_, peer) = recv_request(&socket, &tx);
|
||||||
@@ -47,7 +48,7 @@ fn hmac_open_ignores_bad_hmac_before_valid_reply() {
|
|||||||
fn post_token_hmac_negotiation_failure_sends_authenticated_cleanup_close() {
|
fn post_token_hmac_negotiation_failure_sends_authenticated_cleanup_close() {
|
||||||
let key = b"secret".to_vec();
|
let key = b"secret".to_vec();
|
||||||
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
||||||
config.hmac_key = Some(key.clone());
|
config.auth = Authentication::Hmac(HmacKey::new(key.clone()));
|
||||||
let mut returned = default_params();
|
let mut returned = default_params();
|
||||||
returned.interval_ns += 1;
|
returned.interval_ns += 1;
|
||||||
let server_key = key.clone();
|
let server_key = key.clone();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use irtt_client::{Authentication, HmacKey};
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
@@ -54,7 +55,9 @@ fn config(addr: SocketAddr, key: Option<Vec<u8>>, dscp: u8) -> ClientConfig {
|
|||||||
received_stats: ReceivedStats::None,
|
received_stats: ReceivedStats::None,
|
||||||
stamp_at: StampAt::None,
|
stamp_at: StampAt::None,
|
||||||
dscp,
|
dscp,
|
||||||
hmac_key: key,
|
auth: key.map_or(Authentication::Unauthenticated, |key| {
|
||||||
|
Authentication::Hmac(HmacKey::new(key))
|
||||||
|
}),
|
||||||
open_timeouts: vec![Duration::from_millis(200)],
|
open_timeouts: vec![Duration::from_millis(200)],
|
||||||
socket_config: SocketConfig {
|
socket_config: SocketConfig {
|
||||||
recv_timeout: Some(Duration::from_millis(200)),
|
recv_timeout: Some(Duration::from_millis(200)),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use irtt_client::{Authentication, HmacKey};
|
||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -27,7 +28,7 @@ fn hmac_open_success_negotiates_without_warnings() {
|
|||||||
TimestampFields::default(),
|
TimestampFields::default(),
|
||||||
Some(key),
|
Some(key),
|
||||||
|addr| ClientConfig {
|
|addr| ClientConfig {
|
||||||
hmac_key: Some(config_key),
|
auth: Authentication::Hmac(HmacKey::new(config_key)),
|
||||||
..config_for_params(addr, &config_params)
|
..config_for_params(addr, &config_params)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -52,7 +53,7 @@ fn hmac_echo_success_verifies_request_and_accepts_reply() {
|
|||||||
|
|
||||||
let run = run_one_probe_with_config(params.clone(), standard_timestamps(), Some(key), |addr| {
|
let run = run_one_probe_with_config(params.clone(), standard_timestamps(), Some(key), |addr| {
|
||||||
ClientConfig {
|
ClientConfig {
|
||||||
hmac_key: Some(config_key),
|
auth: Authentication::Hmac(HmacKey::new(config_key)),
|
||||||
..config_for_params(addr, &config_params)
|
..config_for_params(addr, &config_params)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -94,7 +95,7 @@ fn hmac_close_success_sends_authenticated_close_and_closes_session() {
|
|||||||
let params = default_params();
|
let params = default_params();
|
||||||
let server = start_hmac_close_server(params.clone(), key.clone());
|
let server = start_hmac_close_server(params.clone(), key.clone());
|
||||||
let mut config = config_for_params(server.addr, ¶ms);
|
let mut config = config_for_params(server.addr, ¶ms);
|
||||||
config.hmac_key = Some(key);
|
config.auth = Authentication::Hmac(HmacKey::new(key));
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
let outcome = client.open().unwrap();
|
let outcome = client.open().unwrap();
|
||||||
@@ -135,7 +136,9 @@ fn hmac_required_server_rejects_missing_or_wrong_client_key() {
|
|||||||
let server = start_hmac_required_open_drop_server(server_key, Duration::from_millis(250));
|
let server = start_hmac_required_open_drop_server(server_key, Duration::from_millis(250));
|
||||||
let mut config = config_for_params(server.addr, &default_params());
|
let mut config = config_for_params(server.addr, &default_params());
|
||||||
config.open_timeouts = vec![Duration::from_millis(200)];
|
config.open_timeouts = vec![Duration::from_millis(200)];
|
||||||
config.hmac_key = hmac_key;
|
config.auth = hmac_key.map_or(Authentication::Unauthenticated, |key| {
|
||||||
|
Authentication::Hmac(HmacKey::new(key))
|
||||||
|
});
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
||||||
@@ -158,7 +161,7 @@ fn bad_hmac_echo_reply_is_rejected_without_echo_reply_event() {
|
|||||||
let params = default_params();
|
let params = default_params();
|
||||||
let server = start_bad_hmac_echo_reply_server(params.clone(), key.clone());
|
let server = start_bad_hmac_echo_reply_server(params.clone(), key.clone());
|
||||||
let mut config = config_for_params(server.addr, ¶ms);
|
let mut config = config_for_params(server.addr, ¶ms);
|
||||||
config.hmac_key = Some(key);
|
config.auth = Authentication::Hmac(HmacKey::new(key));
|
||||||
config.socket_config = SocketConfig {
|
config.socket_config = SocketConfig {
|
||||||
recv_timeout: Some(Duration::from_millis(500)),
|
recv_timeout: Some(Duration::from_millis(500)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -205,7 +208,7 @@ fn hmac_open_reply_with_bad_hmac_is_ignored_until_timeout() {
|
|||||||
let params = default_params();
|
let params = default_params();
|
||||||
let server = support::start_bad_hmac_open_reply_server(params.clone(), key.clone(), wrong_key);
|
let server = support::start_bad_hmac_open_reply_server(params.clone(), key.clone(), wrong_key);
|
||||||
let mut config = config_for_params(server.addr, ¶ms);
|
let mut config = config_for_params(server.addr, ¶ms);
|
||||||
config.hmac_key = Some(key);
|
config.auth = Authentication::Hmac(HmacKey::new(key));
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
||||||
@@ -221,7 +224,7 @@ fn non_hmac_client_open_does_not_set_hmac_flag() {
|
|||||||
);
|
);
|
||||||
let server = support::start_open_server(params.clone(), None);
|
let server = support::start_open_server(params.clone(), None);
|
||||||
let mut config = config_for_params(server.addr, ¶ms);
|
let mut config = config_for_params(server.addr, ¶ms);
|
||||||
config.hmac_key = None;
|
config.auth = Authentication::Unauthenticated;
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
let outcome = client.open().unwrap();
|
let outcome = client.open().unwrap();
|
||||||
@@ -278,7 +281,7 @@ fn backend_hmac_correct_key_succeeds() {
|
|||||||
let params = default_params();
|
let params = default_params();
|
||||||
let peer = BackendPeer::start_open_echo(Some(key.clone()));
|
let peer = BackendPeer::start_open_echo(Some(key.clone()));
|
||||||
let mut config = config_for_params(peer.addr(), ¶ms);
|
let mut config = config_for_params(peer.addr(), ¶ms);
|
||||||
config.hmac_key = Some(key);
|
config.auth = Authentication::Hmac(HmacKey::new(key));
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
let outcome = client.open().unwrap();
|
let outcome = client.open().unwrap();
|
||||||
@@ -301,7 +304,9 @@ fn backend_hmac_required_rejects_missing_or_wrong_client_key() {
|
|||||||
let peer = BackendPeer::start_hmac_required(server_key);
|
let peer = BackendPeer::start_hmac_required(server_key);
|
||||||
let mut config = config_for_params(peer.addr(), &default_params());
|
let mut config = config_for_params(peer.addr(), &default_params());
|
||||||
config.open_timeouts = vec![Duration::from_millis(200)];
|
config.open_timeouts = vec![Duration::from_millis(200)];
|
||||||
config.hmac_key = hmac_key;
|
config.auth = hmac_key.map_or(Authentication::Unauthenticated, |key| {
|
||||||
|
Authentication::Hmac(HmacKey::new(key))
|
||||||
|
});
|
||||||
|
|
||||||
let mut client = Client::connect(config).unwrap();
|
let mut client = Client::connect(config).unwrap();
|
||||||
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use irtt_client::{Authentication, HmacKey};
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
net::{SocketAddr, UdpSocket},
|
net::{SocketAddr, UdpSocket},
|
||||||
@@ -14,8 +15,8 @@ use irtt_proto::{
|
|||||||
};
|
};
|
||||||
use tokio::runtime::{Builder, Runtime};
|
use tokio::runtime::{Builder, Runtime};
|
||||||
|
|
||||||
|
use irtt_client::RunMode;
|
||||||
use irtt_client::{managed::*, ClientConfig};
|
use irtt_client::{managed::*, ClientConfig};
|
||||||
use irtt_client::{ClientAuthConfig, RunMode};
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
const TOKEN: u64 = 0x1234_5678_90ab_cdef;
|
const TOKEN: u64 = 0x1234_5678_90ab_cdef;
|
||||||
@@ -355,9 +356,7 @@ fn authenticated_peer_close_outcome() {
|
|||||||
let key = b"managed-peer-close".to_vec();
|
let key = b"managed-peer-close".to_vec();
|
||||||
let server = start_server(ServerBehavior::PeerClose, Some(key.clone()));
|
let server = start_server(ServerBehavior::PeerClose, Some(key.clone()));
|
||||||
let mut configured = target("peer", server.addr);
|
let mut configured = target("peer", server.addr);
|
||||||
configured.auth = Some(ClientAuthConfig {
|
configured.auth = TargetAuth::Override(Authentication::Hmac(HmacKey::new(key)));
|
||||||
hmac_key: Some(key),
|
|
||||||
});
|
|
||||||
let (task, _) =
|
let (task, _) =
|
||||||
ManagedClient::task(config(ManagedPacing::Staggered), vec![configured]).unwrap();
|
ManagedClient::task(config(ManagedPacing::Staggered), vec![configured]).unwrap();
|
||||||
let outcome = runtime().block_on(task);
|
let outcome = runtime().block_on(task);
|
||||||
@@ -418,9 +417,7 @@ fn pending_limit_failure_drains_reply_and_closes_session() {
|
|||||||
managed.client.max_pending_probes = 1;
|
managed.client.max_pending_probes = 1;
|
||||||
managed.final_drain = Duration::from_millis(50);
|
managed.final_drain = Duration::from_millis(50);
|
||||||
let mut configured = target("one", server.addr);
|
let mut configured = target("one", server.addr);
|
||||||
configured.auth = Some(ClientAuthConfig {
|
configured.auth = TargetAuth::Override(Authentication::Hmac(HmacKey::new(key)));
|
||||||
hmac_key: Some(key),
|
|
||||||
});
|
|
||||||
let (task, _) = ManagedClient::task(managed, vec![configured]).unwrap();
|
let (task, _) = ManagedClient::task(managed, vec![configured]).unwrap();
|
||||||
let outcome = runtime().block_on(async {
|
let outcome = runtime().block_on(async {
|
||||||
tokio::time::timeout(Duration::from_secs(1), task)
|
tokio::time::timeout(Duration::from_secs(1), task)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use irtt_client::{Authentication, HmacKey};
|
||||||
mod support;
|
mod support;
|
||||||
mod protocol_options {
|
mod protocol_options {
|
||||||
pub mod backend_smoke;
|
pub mod backend_smoke;
|
||||||
@@ -148,7 +149,7 @@ fn hmac_rich_mode_uses_negotiated_echo_layout_and_decodes_reply() {
|
|||||||
let config_key = key.clone();
|
let config_key = key.clone();
|
||||||
let run = run_one_probe_with_config(params.clone(), standard_timestamps(), Some(key), |addr| {
|
let run = run_one_probe_with_config(params.clone(), standard_timestamps(), Some(key), |addr| {
|
||||||
ClientConfig {
|
ClientConfig {
|
||||||
hmac_key: Some(config_key),
|
auth: Authentication::Hmac(HmacKey::new(config_key)),
|
||||||
..config_for_params(addr, &config_params)
|
..config_for_params(addr, &config_params)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user