refactor(platform): restructure platform-specific abstractions and add synthetic backends
This commit is contained in:
8 files changed
+228
-172
No files matched your search
@@ -12,5 +12,15 @@ fn main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match target_os.as_str() {
|
||||||
|
"linux" => {}
|
||||||
|
"macos" => println!(
|
||||||
|
"cargo::warning=traffic control is unavailable on macOS; requested rate changes will be calculated but not applied"
|
||||||
|
),
|
||||||
|
_ => println!(
|
||||||
|
"cargo::warning=target `{target}` has no native interface-statistics or traffic-control backend; synthetic interface load will be used and requested rate changes will not be applied"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
}
|
}
|
||||||
+2
-8
@@ -11,8 +11,6 @@ mod baseliner;
|
|||||||
mod config;
|
mod config;
|
||||||
mod log;
|
mod log;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
mod netlink;
|
|
||||||
mod pinger;
|
mod pinger;
|
||||||
mod pinger_icmp;
|
mod pinger_icmp;
|
||||||
mod pinger_icmp_ts;
|
mod pinger_icmp_ts;
|
||||||
@@ -39,7 +37,7 @@ use crate::config::{Config, MeasurementType};
|
|||||||
use crate::pinger::{InFlightProbeCache, PingListener, PingSender};
|
use crate::pinger::{InFlightProbeCache, PingListener, PingSender};
|
||||||
use crate::pinger_icmp::{PingerICMPEchoListener, PingerICMPEchoSender};
|
use crate::pinger_icmp::{PingerICMPEchoListener, PingerICMPEchoSender};
|
||||||
use crate::pinger_icmp_ts::{PingerICMPTimestampListener, PingerICMPTimestampSender};
|
use crate::pinger_icmp_ts::{PingerICMPTimestampListener, PingerICMPTimestampSender};
|
||||||
use crate::platform::{TrafficControlBackend, traffic_control_backend};
|
use crate::platform::{TrafficControlBackend, traffic_control_backend, warn_platform_limitations};
|
||||||
use crate::ratecontroller::{Ratecontroller, StatsDirection};
|
use crate::ratecontroller::{Ratecontroller, StatsDirection};
|
||||||
use crate::reflector_selector::ReflectorSelector;
|
use crate::reflector_selector::ReflectorSelector;
|
||||||
|
|
||||||
@@ -337,11 +335,6 @@ fn run(config: Config) -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut main_traffic_control = traffic_control_backend();
|
let mut main_traffic_control = traffic_control_backend();
|
||||||
if main_traffic_control.is_observe_only() {
|
|
||||||
warn!(
|
|
||||||
"Traffic control is observe-only on this platform; requested rate changes will not be applied."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let (down_shaper, up_shaper) = initialize_shaper(&config, &mut main_traffic_control)?;
|
let (down_shaper, up_shaper) = initialize_shaper(&config, &mut main_traffic_control)?;
|
||||||
|
|
||||||
let err_tx = error_tx.clone();
|
let err_tx = error_tx.clone();
|
||||||
@@ -483,6 +476,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let config = Config::new()?;
|
let config = Config::new()?;
|
||||||
log::init(config.log_level)?;
|
log::init(config.log_level)?;
|
||||||
|
warn_platform_limitations();
|
||||||
|
|
||||||
run(config)
|
run(config)
|
||||||
}
|
}
|
||||||
+27
-151
@@ -2,37 +2,21 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
use log::debug;
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
use std::convert::Infallible;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
#[cfg(any(not(any(target_os = "linux", target_os = "macos")), test))]
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use crate::netlink::Netlink;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
mod macos;
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub use macos::MacOsInterfaceStats;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct InterfaceStats {
|
pub(crate) struct InterfaceStats {
|
||||||
pub rx_bytes: u64,
|
pub(crate) rx_bytes: u64,
|
||||||
pub tx_bytes: u64,
|
pub(crate) tx_bytes: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InterfaceStatsProvider: Send {
|
pub(crate) trait InterfaceStatsProvider: Send {
|
||||||
type Error: Error + Send + Sync + 'static;
|
type Error: Error + Send + Sync + 'static;
|
||||||
|
|
||||||
fn read_stats(&mut self, interface: &str) -> Result<InterfaceStats, Self::Error>;
|
fn read_stats(&mut self, interface: &str) -> Result<InterfaceStats, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TrafficControlBackend: Send {
|
pub(crate) trait TrafficControlBackend: Send {
|
||||||
type Error: Error + Send + Sync + 'static;
|
type Error: Error + Send + Sync + 'static;
|
||||||
type Handle: Send + 'static;
|
type Handle: Send + 'static;
|
||||||
|
|
||||||
@@ -44,138 +28,30 @@ pub trait TrafficControlBackend: Send {
|
|||||||
bandwidth_kbit: u64,
|
bandwidth_kbit: u64,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
) -> Result<(), Self::Error>;
|
) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
fn is_observe_only(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(not(any(target_os = "linux", target_os = "macos")), test))]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct UnsupportedInterfaceStats;
|
|
||||||
|
|
||||||
#[cfg(any(not(any(target_os = "linux", target_os = "macos")), test))]
|
|
||||||
#[derive(Debug, Error, PartialEq, Eq)]
|
|
||||||
#[error("interface statistics are unsupported on target OS `{target_os}`")]
|
|
||||||
pub struct UnsupportedInterfaceStatsError {
|
|
||||||
target_os: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(not(any(target_os = "linux", target_os = "macos")), test))]
|
|
||||||
impl InterfaceStatsProvider for UnsupportedInterfaceStats {
|
|
||||||
type Error = UnsupportedInterfaceStatsError;
|
|
||||||
|
|
||||||
fn read_stats(&mut self, _: &str) -> Result<InterfaceStats, Self::Error> {
|
|
||||||
Err(UnsupportedInterfaceStatsError {
|
|
||||||
target_os: std::env::consts::OS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ObserveOnlyShaper {
|
|
||||||
interface: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
struct ObserveOnlyRequest {
|
|
||||||
bandwidth_kbit: u64,
|
|
||||||
dry_run: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ObserveOnlyTrafficControl {
|
|
||||||
last_requests: HashMap<String, ObserveOnlyRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(not(target_os = "linux"), test))]
|
|
||||||
impl TrafficControlBackend for ObserveOnlyTrafficControl {
|
|
||||||
type Error = Infallible;
|
|
||||||
type Handle = ObserveOnlyShaper;
|
|
||||||
|
|
||||||
fn find_shaper(&mut self, interface: &str) -> Result<Self::Handle, Self::Error> {
|
|
||||||
Ok(ObserveOnlyShaper {
|
|
||||||
interface: interface.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_rate(
|
|
||||||
&mut self,
|
|
||||||
shaper: &Self::Handle,
|
|
||||||
bandwidth_kbit: u64,
|
|
||||||
dry_run: bool,
|
|
||||||
) -> Result<(), Self::Error> {
|
|
||||||
debug!(
|
|
||||||
"Observe-only traffic control requested rate {} kbit/s for interface {} (dry_run={})",
|
|
||||||
bandwidth_kbit, shaper.interface, dry_run
|
|
||||||
);
|
|
||||||
self.last_requests.insert(
|
|
||||||
shaper.interface.clone(),
|
|
||||||
ObserveOnlyRequest {
|
|
||||||
bandwidth_kbit,
|
|
||||||
dry_run,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_observe_only(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct UnsupportedTrafficControl;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct UnsupportedShaper;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(Debug, Error, PartialEq, Eq)]
|
|
||||||
#[error("traffic control is unsupported on target OS `{target_os}`")]
|
|
||||||
pub struct UnsupportedTrafficControlError {
|
|
||||||
target_os: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl TrafficControlBackend for UnsupportedTrafficControl {
|
|
||||||
type Error = UnsupportedTrafficControlError;
|
|
||||||
type Handle = UnsupportedShaper;
|
|
||||||
|
|
||||||
fn find_shaper(&mut self, _: &str) -> Result<Self::Handle, Self::Error> {
|
|
||||||
Err(UnsupportedTrafficControlError {
|
|
||||||
target_os: std::env::consts::OS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_rate(&mut self, _: &UnsupportedShaper, _: u64, _: bool) -> Result<(), Self::Error> {
|
|
||||||
Err(UnsupportedTrafficControlError {
|
|
||||||
target_os: std::env::consts::OS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub type PlatformInterfaceStats = Netlink;
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub type PlatformInterfaceStats = MacOsInterfaceStats;
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
||||||
pub type PlatformInterfaceStats = UnsupportedInterfaceStats;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub type PlatformTrafficControl = Netlink;
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
pub type PlatformTrafficControl = ObserveOnlyTrafficControl;
|
mod noop;
|
||||||
|
|
||||||
pub fn interface_stats_provider() -> PlatformInterfaceStats {
|
#[cfg(target_os = "linux")]
|
||||||
PlatformInterfaceStats::default()
|
mod linux;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn traffic_control_backend() -> PlatformTrafficControl {
|
#[cfg(target_os = "macos")]
|
||||||
PlatformTrafficControl::default()
|
mod macos;
|
||||||
}
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
|
mod unsupported;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use linux as imp;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use macos as imp;
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
|
use unsupported as imp;
|
||||||
|
|
||||||
|
pub(crate) use imp::{
|
||||||
|
PlatformInterfaceStats, PlatformTrafficControl, interface_stats_provider,
|
||||||
|
traffic_control_backend, warn_platform_limitations,
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026-Present Nils Andreas Svee mailto:contact@lochnair.net (github @Lochnair)
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
mod netlink;
|
||||||
|
|
||||||
|
pub(crate) type PlatformInterfaceStats = netlink::Netlink;
|
||||||
|
pub(crate) type PlatformTrafficControl = netlink::Netlink;
|
||||||
|
|
||||||
|
pub(crate) fn interface_stats_provider() -> PlatformInterfaceStats {
|
||||||
|
netlink::Netlink::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn traffic_control_backend() -> PlatformTrafficControl {
|
||||||
|
netlink::Netlink::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn warn_platform_limitations() {}
|
||||||
@@ -13,7 +13,7 @@ use thiserror::Error;
|
|||||||
use crate::platform::{InterfaceStats, InterfaceStatsProvider, TrafficControlBackend};
|
use crate::platform::{InterfaceStats, InterfaceStatsProvider, TrafficControlBackend};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum NetlinkError {
|
pub(crate) enum NetlinkError {
|
||||||
#[error("Couldn't find interface `{0}`")]
|
#[error("Couldn't find interface `{0}`")]
|
||||||
InterfaceNotFound(String),
|
InterfaceNotFound(String),
|
||||||
|
|
||||||
@@ -37,16 +37,16 @@ pub enum NetlinkError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Qdisc {
|
pub(crate) struct Qdisc {
|
||||||
pub ifindex: i32,
|
ifindex: i32,
|
||||||
pub parent: u32,
|
parent: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Netlink {}
|
pub(crate) struct Netlink {}
|
||||||
|
|
||||||
impl Netlink {
|
impl Netlink {
|
||||||
pub fn find_interface(ifname: &str) -> Result<i32, NetlinkError> {
|
fn find_interface(ifname: &str) -> Result<i32, NetlinkError> {
|
||||||
let mut socket = NetlinkSocket::new();
|
let mut socket = NetlinkSocket::new();
|
||||||
|
|
||||||
let mut request = rt_link::Request::new().op_getlink_do(&Default::default());
|
let mut request = rt_link::Request::new().op_getlink_do(&Default::default());
|
||||||
@@ -59,7 +59,7 @@ impl Netlink {
|
|||||||
Ok(header.ifi_index)
|
Ok(header.ifi_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_interface_stats(ifname: &str) -> Result<(u64, u64), NetlinkError> {
|
fn get_interface_stats(ifname: &str) -> Result<(u64, u64), NetlinkError> {
|
||||||
let mut socket = NetlinkSocket::new();
|
let mut socket = NetlinkSocket::new();
|
||||||
|
|
||||||
let mut request = rt_link::Request::new().op_getlink_do(&Default::default());
|
let mut request = rt_link::Request::new().op_getlink_do(&Default::default());
|
||||||
@@ -79,7 +79,7 @@ impl Netlink {
|
|||||||
Err(NetlinkError::NoInterfaceStatsFound(ifname.to_string()))
|
Err(NetlinkError::NoInterfaceStatsFound(ifname.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn qdisc_from_ifindex(ifindex: i32, ifname: &str) -> Result<Qdisc, NetlinkError> {
|
fn qdisc_from_ifindex(ifindex: i32, ifname: &str) -> Result<Qdisc, NetlinkError> {
|
||||||
let mut socket = NetlinkSocket::new();
|
let mut socket = NetlinkSocket::new();
|
||||||
let header = tc::Tcmsg::new();
|
let header = tc::Tcmsg::new();
|
||||||
let request = tc::Request::new().op_getqdisc_dump(&header);
|
let request = tc::Request::new().op_getqdisc_dump(&header);
|
||||||
@@ -105,12 +105,12 @@ impl Netlink {
|
|||||||
Err(NetlinkError::NoQdiscFound(ifname.to_string()))
|
Err(NetlinkError::NoQdiscFound(ifname.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn qdisc_from_ifname(ifname: &str) -> Result<Qdisc, NetlinkError> {
|
fn qdisc_from_ifname(ifname: &str) -> Result<Qdisc, NetlinkError> {
|
||||||
let ifindex = Netlink::find_interface(ifname)?;
|
let ifindex = Netlink::find_interface(ifname)?;
|
||||||
Netlink::qdisc_from_ifindex(ifindex, ifname)
|
Netlink::qdisc_from_ifindex(ifindex, ifname)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_qdisc_rate(
|
fn set_qdisc_rate(
|
||||||
qdisc: Qdisc,
|
qdisc: Qdisc,
|
||||||
bandwidth_kbit: u64,
|
bandwidth_kbit: u64,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
+22
-3
@@ -7,15 +7,17 @@ use std::io;
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{InterfaceStats, InterfaceStatsProvider};
|
use super::noop::NoopTrafficControl;
|
||||||
|
use crate::platform::{InterfaceStats, InterfaceStatsProvider};
|
||||||
|
|
||||||
const ROUTE_MESSAGE_PREFIX_LEN: usize = size_of::<libc::c_ushort>() + 2;
|
const ROUTE_MESSAGE_PREFIX_LEN: usize = size_of::<libc::c_ushort>() + 2;
|
||||||
const SYSCTL_FETCH_ATTEMPTS: usize = 3;
|
const SYSCTL_FETCH_ATTEMPTS: usize = 3;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MacOsInterfaceStatsError {
|
pub(crate) enum MacOsInterfaceStatsError {
|
||||||
#[error("interface name contains a NUL byte")]
|
#[error("interface name contains a NUL byte")]
|
||||||
InterfaceNameContainsNul(#[source] NulError),
|
InterfaceNameContainsNul(#[source] NulError),
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ pub enum MacOsInterfaceStatsError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MacOsInterfaceStats;
|
pub(crate) struct MacOsInterfaceStats;
|
||||||
|
|
||||||
impl InterfaceStatsProvider for MacOsInterfaceStats {
|
impl InterfaceStatsProvider for MacOsInterfaceStats {
|
||||||
type Error = MacOsInterfaceStatsError;
|
type Error = MacOsInterfaceStatsError;
|
||||||
@@ -197,3 +199,20 @@ fn parse_route_messages(
|
|||||||
interface_index: requested_interface_index,
|
interface_index: requested_interface_index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) type PlatformInterfaceStats = MacOsInterfaceStats;
|
||||||
|
pub(crate) type PlatformTrafficControl = NoopTrafficControl;
|
||||||
|
|
||||||
|
pub(crate) fn interface_stats_provider() -> PlatformInterfaceStats {
|
||||||
|
MacOsInterfaceStats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn traffic_control_backend() -> PlatformTrafficControl {
|
||||||
|
NoopTrafficControl
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn warn_platform_limitations() {
|
||||||
|
warn!(
|
||||||
|
"Traffic control is unavailable on macOS; requested rate changes will be calculated but not applied."
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026-Present Nils Andreas Svee mailto:contact@lochnair.net (github @Lochnair)
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::platform::TrafficControlBackend;
|
||||||
|
use log::debug;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) struct NoopShaper {
|
||||||
|
interface: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct NoopTrafficControl;
|
||||||
|
|
||||||
|
impl TrafficControlBackend for NoopTrafficControl {
|
||||||
|
type Error = Infallible;
|
||||||
|
type Handle = NoopShaper;
|
||||||
|
|
||||||
|
fn find_shaper(&mut self, interface: &str) -> Result<Self::Handle, Self::Error> {
|
||||||
|
Ok(NoopShaper {
|
||||||
|
interface: interface.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_rate(
|
||||||
|
&mut self,
|
||||||
|
shaper: &Self::Handle,
|
||||||
|
bandwidth_kbit: u64,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
debug!(
|
||||||
|
"Ignoring traffic-control rate request of {} kbit/s for interface {} (dry_run={})",
|
||||||
|
bandwidth_kbit, shaper.interface, dry_run
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026-Present Nils Andreas Svee mailto:contact@lochnair.net (github @Lochnair)
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
|
use crate::platform::noop::NoopTrafficControl;
|
||||||
|
use crate::platform::{InterfaceStats, InterfaceStatsProvider};
|
||||||
|
|
||||||
|
// Synthetic rates are fixed per interface and intentionally conservative. Counter increments are
|
||||||
|
// derived from elapsed monotonic time, so changing the polling frequency does not change the
|
||||||
|
// apparent traffic rate.
|
||||||
|
const SYNTHETIC_RX_KBIT_RANGE: std::ops::RangeInclusive<u64> = 500..=25_000;
|
||||||
|
const SYNTHETIC_TX_KBIT_RANGE: std::ops::RangeInclusive<u64> = 250..=10_000;
|
||||||
|
const INITIAL_COUNTER_BYTES: u64 = 1;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SyntheticInterfaceState {
|
||||||
|
rx_bytes: u64,
|
||||||
|
tx_bytes: u64,
|
||||||
|
rx_kbit: u64,
|
||||||
|
tx_kbit: u64,
|
||||||
|
last_update: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyntheticInterfaceState {
|
||||||
|
fn new(now: Instant) -> Self {
|
||||||
|
Self {
|
||||||
|
rx_bytes: INITIAL_COUNTER_BYTES,
|
||||||
|
tx_bytes: INITIAL_COUNTER_BYTES,
|
||||||
|
rx_kbit: fastrand::u64(SYNTHETIC_RX_KBIT_RANGE),
|
||||||
|
tx_kbit: fastrand::u64(SYNTHETIC_TX_KBIT_RANGE),
|
||||||
|
last_update: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, now: Instant) {
|
||||||
|
let elapsed = now.saturating_duration_since(self.last_update);
|
||||||
|
self.rx_bytes = self
|
||||||
|
.rx_bytes
|
||||||
|
.saturating_add(bytes_for_elapsed(elapsed, self.rx_kbit));
|
||||||
|
self.tx_bytes = self
|
||||||
|
.tx_bytes
|
||||||
|
.saturating_add(bytes_for_elapsed(elapsed, self.tx_kbit));
|
||||||
|
self.last_update = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bytes_for_elapsed(elapsed: Duration, bitrate_kbit: u64) -> u64 {
|
||||||
|
let bytes = elapsed
|
||||||
|
.as_nanos()
|
||||||
|
.saturating_mul(u128::from(bitrate_kbit))
|
||||||
|
.saturating_mul(1_000)
|
||||||
|
/ 8_000_000_000;
|
||||||
|
u64::try_from(bytes).unwrap_or(u64::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct SyntheticInterfaceStats {
|
||||||
|
interfaces: HashMap<String, SyntheticInterfaceState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceStatsProvider for SyntheticInterfaceStats {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn read_stats(&mut self, interface: &str) -> Result<InterfaceStats, Self::Error> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let state = self
|
||||||
|
.interfaces
|
||||||
|
.entry(interface.to_string())
|
||||||
|
.or_insert_with(|| SyntheticInterfaceState::new(now));
|
||||||
|
state.update(now);
|
||||||
|
|
||||||
|
Ok(InterfaceStats {
|
||||||
|
rx_bytes: state.rx_bytes,
|
||||||
|
tx_bytes: state.tx_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type PlatformInterfaceStats = SyntheticInterfaceStats;
|
||||||
|
pub(crate) type PlatformTrafficControl = NoopTrafficControl;
|
||||||
|
|
||||||
|
pub(crate) fn interface_stats_provider() -> PlatformInterfaceStats {
|
||||||
|
SyntheticInterfaceStats::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn traffic_control_backend() -> PlatformTrafficControl {
|
||||||
|
NoopTrafficControl
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn warn_platform_limitations() {
|
||||||
|
warn!(
|
||||||
|
"This operating system has no native interface-statistics or traffic-control backend; synthetic interface load will be used and requested rate changes will not be applied."
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user