feat(app/client): expose socket bind options
This commit is contained in:
5 files changed
+87
-4
No files matched your search
Generated
+10
@@ -251,6 +251,15 @@ dependencies = [
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap-num"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "822c4000301ac390e65995c62207501e3ef800a1fc441df913a5e8e4dc374816"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.7"
|
||||
@@ -819,6 +828,7 @@ version = "0.8.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap-num",
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"futures-core",
|
||||
|
||||
@@ -49,6 +49,7 @@ tui =["dep:tokio", "dep:futures-core", "dep:irtt-client", "irtt-client/ancillary
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["clock"], optional = true }
|
||||
clap = { version = "4.6.7", features = ["derive", "env"] }
|
||||
clap-num = "=1.2.0"
|
||||
crossterm = { version = "0.29.0", features = ["event-stream"], optional = true }
|
||||
futures-core = { version = "0.3.34", default-features = false, optional = true }
|
||||
ctrlc = { version = "3.5.2", features = ["termination"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt, time::Duration};
|
||||
use std::{fmt, net::IpAddr, net::SocketAddr, time::Duration};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use irtt_client::{MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH};
|
||||
@@ -70,6 +70,38 @@ pub struct CommonClientArgs {
|
||||
#[arg(long, value_name = "1..=255", value_parser = parse_ttl)]
|
||||
pub ttl: Option<u32>,
|
||||
|
||||
#[arg(
|
||||
long = "bind-addr",
|
||||
value_name = "IP[:PORT]",
|
||||
value_parser = parse_bind_addr,
|
||||
long_help = "Local IP address and optional UDP port to bind. \
|
||||
If no port is specified, the operating system selects \
|
||||
an ephemeral port. IPv6 addresses with an explicit \
|
||||
port must use [ADDR]:PORT syntax."
|
||||
)]
|
||||
pub bind_addr: Option<SocketAddr>,
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
#[arg(
|
||||
long = "bind-device",
|
||||
value_name = "DEVICE",
|
||||
help = "Bind the socket to a network device"
|
||||
)]
|
||||
pub bind_to_device: Option<String>,
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "MARK",
|
||||
value_parser = clap_num::maybe_hex::<u32>,
|
||||
help = "Set the outgoing socket firewall mark"
|
||||
)]
|
||||
pub mark: Option<u32>,
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
#[arg(long, value_name = "FIB", help = "Set the socket routing table (FIB)")]
|
||||
pub fib: Option<u32>,
|
||||
|
||||
/// Accept safe server restrictions during negotiation.
|
||||
#[arg(long)]
|
||||
pub loose: bool,
|
||||
@@ -77,7 +109,8 @@ pub struct CommonClientArgs {
|
||||
|
||||
impl fmt::Debug for CommonClientArgs {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("CommonClientArgs")
|
||||
let mut debug = f.debug_struct("CommonClientArgs");
|
||||
debug
|
||||
.field("ipv4", &self.ipv4)
|
||||
.field("ipv6", &self.ipv6)
|
||||
.field("dual_stack", &self.dual_stack)
|
||||
@@ -90,8 +123,14 @@ impl fmt::Debug for CommonClientArgs {
|
||||
.field("server_fill", &self.server_fill)
|
||||
.field("dscp", &self.dscp)
|
||||
.field("ttl", &self.ttl)
|
||||
.field("loose", &self.loose)
|
||||
.finish()
|
||||
.field("bind_addr", &self.bind_addr);
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
debug
|
||||
.field("bind_to_device", &self.bind_to_device)
|
||||
.field("mark", &self.mark);
|
||||
#[cfg(target_os = "freebsd")]
|
||||
debug.field("fib", &self.fib);
|
||||
debug.field("loose", &self.loose).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +191,13 @@ impl From<ReceivedStatsArg> for ReceivedStats {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_bind_addr(input: &str) -> Result<SocketAddr, String> {
|
||||
input
|
||||
.parse::<SocketAddr>()
|
||||
.or_else(|_| input.parse::<IpAddr>().map(|ip| SocketAddr::new(ip, 0)))
|
||||
.map_err(|_| "expected an IP address, optionally with a port".to_owned())
|
||||
}
|
||||
|
||||
pub fn parse_duration(input: &str) -> Result<Duration, String> {
|
||||
let (number, unit) = split_duration(input)?;
|
||||
let value: u64 = number
|
||||
|
||||
@@ -50,6 +50,13 @@ impl CommonClientArgs {
|
||||
Authentication::Hmac(HmacKey::new(key.as_bytes()))
|
||||
}),
|
||||
socket: SocketConfig {
|
||||
bind_addr: self.bind_addr,
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
bind_to_device: self.bind_to_device.clone(),
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
mark: self.mark,
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fib: self.fib,
|
||||
ttl: self.ttl,
|
||||
..SocketConfig::default()
|
||||
},
|
||||
|
||||
@@ -93,6 +93,25 @@ labels; choose another alias if one collides with an explicit label.
|
||||
ordinary multi-target pacing and each sends its own probes, so dual-stack can
|
||||
double the number of probes per interval compared with one target per hostname.
|
||||
|
||||
## LOCAL SOCKET CONFIGURATION
|
||||
|
||||
The client supports configuring its local UDP sockets using the following options:
|
||||
|
||||
- `--bind-addr IP[:PORT]` binds sockets to the specified local IP address and optional port. If no port is specified, or the port is `0`, the operating system assigns an ephemeral port. IPv6 addresses with an explicit port must use `[ADDR]:PORT` syntax. The address family must match the target's resolved address family.
|
||||
- `--bind-device DEVICE` (Linux, Android, Fuchsia) binds sockets to a network interface using `SO_BINDTODEVICE`.
|
||||
- `--mark MARK` (Linux, Android, Fuchsia) sets the socket's firewall mark using `SO_MARK`, allowing traffic to be selected by policy routing or firewall rules. Accepts a decimal or 0x-prefixed hexadecimal unsigned 32-bit integer.
|
||||
- `--fib FIB` (FreeBSD) selects a routing table using `SO_SETFIB`. The FIB is specified as a decimal unsigned 32-bit integer.
|
||||
|
||||
For example, bind IPv4 probes to a specific source address and interface, with a firewall mark:
|
||||
|
||||
```sh
|
||||
irtt-client -4 --bind-addr 192.0.2.10 --bind-device wan0 --mark 1234 host.example
|
||||
```
|
||||
|
||||
Setting a firewall mark or binding to a network device may require elevated privileges or appropriate capabilities. If the operating system rejects a socket option, socket setup fails.
|
||||
|
||||
A fixed local port may cause binding conflicts when multiple target sockets are created. Use an ephemeral port unless a specific source port is required. A family-specific bind address is also incompatible with targets using the other address family.
|
||||
|
||||
## DYNAMIC TARGET SETS FROM STANDARD INPUT
|
||||
|
||||
`--targets-stdin` is available only with continuous mode (`--duration 0`).
|
||||
|
||||
Reference in new issue
Block a user