Add OpenWrt ipk packaging + split client/server binaries
Some checks failed
CI / test (push) Failing after 1m27s

OpenWrt package (deploy/openwrt/):
- build-ipk.sh: creates .ipk from pre-built binary (no SDK needed)
- Makefile: for OpenWrt SDK integration
- ProCD init script with UCI config
- Supports all architectures (x86_64, aarch64, mipsel, mips)

Split binaries for embedded (src/bin/):
- btest-client: client-only, no server/syslog/csv
- btest-server: server-only, no client
- release-small profile: opt-level=z + panic=abort

Sizes (compressed .tar.gz):
  Full btest:    ~1 MB
  btest-client:  ~500 KB (release-small)
  btest-server:  ~550 KB (release-small)

Install on OpenWrt:
  opkg install btest-rs_0.6.0-1_x86_64.ipk

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-01 14:44:57 +04:00
parent 8c853c3605
commit 89391e1781
7 changed files with 418 additions and 0 deletions

127
src/bin/client_only.rs Normal file
View File

@@ -0,0 +1,127 @@
//! btest-client: minimal bandwidth test client for embedded/OpenWrt systems.
//!
//! Stripped-down client that connects to MikroTik btest servers.
//! No server mode, no syslog, smaller binary footprint.
//!
//! Build: cargo build --profile release-small --bin btest-client
use clap::Parser;
use std::sync::atomic::Ordering;
#[derive(Parser)]
#[command(name = "btest-client", about = "MikroTik Bandwidth Test client", version)]
struct Cli {
/// Server address to connect to
#[arg(short = 'c', long = "client", required = true)]
host: String,
/// Transmit data (upload)
#[arg(short = 't', long = "transmit")]
transmit: bool,
/// Receive data (download)
#[arg(short = 'r', long = "receive")]
receive: bool,
/// Use UDP
#[arg(short = 'u', long = "udp")]
udp: bool,
/// Bandwidth limit (e.g., 100M)
#[arg(short = 'b', long = "bandwidth")]
bandwidth: Option<String>,
/// Port
#[arg(short = 'P', long = "port", default_value_t = 2000)]
port: u16,
/// Username
#[arg(short = 'a', long = "authuser")]
auth_user: Option<String>,
/// Password
#[arg(short = 'p', long = "authpass")]
auth_pass: Option<String>,
/// NAT mode
#[arg(short = 'n', long = "nat")]
nat: bool,
/// Duration in seconds (0=unlimited)
#[arg(short = 'd', long = "duration", default_value_t = 0)]
duration: u64,
/// Verbose
#[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
verbose: u8,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let filter = match cli.verbose {
0 => "info",
1 => "debug",
_ => "trace",
};
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter)),
)
.with_target(false)
.init();
btest_rs::cpu::start_sampler();
if !cli.transmit && !cli.receive {
eprintln!("Error: specify -t (transmit) and/or -r (receive)");
std::process::exit(1);
}
let direction = match (cli.transmit, cli.receive) {
(true, false) => btest_rs::protocol::CMD_DIR_RX,
(false, true) => btest_rs::protocol::CMD_DIR_TX,
(true, true) => btest_rs::protocol::CMD_DIR_BOTH,
_ => unreachable!(),
};
let bw = match &cli.bandwidth {
Some(b) => btest_rs::bandwidth::parse_bandwidth(b)?,
None => 0,
};
let (tx_speed, rx_speed) = match direction {
btest_rs::protocol::CMD_DIR_TX => (bw, 0),
btest_rs::protocol::CMD_DIR_RX => (0, bw),
_ => (bw, bw),
};
let state = btest_rs::bandwidth::BandwidthState::new();
let state_clone = state.clone();
let host = cli.host.clone();
let client_fut = btest_rs::client::run_client(
&host, cli.port, direction, cli.udp,
tx_speed, rx_speed,
cli.auth_user, cli.auth_pass, cli.nat,
state_clone,
);
if cli.duration > 0 {
match tokio::time::timeout(
std::time::Duration::from_secs(cli.duration),
client_fut,
).await {
Ok(r) => { let _ = r?; }
Err(_) => {
state.running.store(false, Ordering::SeqCst);
}
}
} else {
let _ = client_fut.await?;
}
Ok(())
}

62
src/bin/server_only.rs Normal file
View File

@@ -0,0 +1,62 @@
//! btest-server: minimal bandwidth test server for embedded/OpenWrt systems.
//!
//! Stripped-down server that accepts MikroTik client connections.
//! No client mode, no syslog, no CSV, smaller binary footprint.
//!
//! Build: cargo build --profile release-small --bin btest-server
use clap::Parser;
#[derive(Parser)]
#[command(name = "btest-server", about = "MikroTik Bandwidth Test server", version)]
struct Cli {
/// Port
#[arg(short = 'P', long = "port", default_value_t = 2000)]
port: u16,
/// IPv4 listen address
#[arg(long = "listen", default_value = "0.0.0.0")]
listen_addr: String,
/// Username
#[arg(short = 'a', long = "authuser")]
auth_user: Option<String>,
/// Password
#[arg(short = 'p', long = "authpass")]
auth_pass: Option<String>,
/// Use EC-SRP5 authentication
#[arg(long = "ecsrp5")]
ecsrp5: bool,
/// Verbose
#[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
verbose: u8,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let filter = match cli.verbose {
0 => "info",
1 => "debug",
_ => "trace",
};
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter)),
)
.with_target(false)
.init();
btest_rs::cpu::start_sampler();
let v4 = if cli.listen_addr.eq_ignore_ascii_case("none") { None } else { Some(cli.listen_addr) };
tracing::info!("btest-server starting on port {}", cli.port);
btest_rs::server::run_server(cli.port, cli.auth_user, cli.auth_pass, cli.ecsrp5, v4, None).await?;
Ok(())
}