Add OpenWrt ipk packaging + split client/server binaries
Some checks failed
CI / test (push) Failing after 1m27s
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:
14
Cargo.toml
14
Cargo.toml
@@ -16,6 +16,14 @@ path = "src/lib.rs"
|
||||
name = "btest"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "btest-client"
|
||||
path = "src/bin/client_only.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "btest-server"
|
||||
path = "src/bin/server_only.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
@@ -38,3 +46,9 @@ opt-level = 3
|
||||
lto = true
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
|
||||
# Minimal size profile for embedded/OpenWrt targets
|
||||
[profile.release-small]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
panic = "abort"
|
||||
|
||||
57
deploy/openwrt/Makefile
Normal file
57
deploy/openwrt/Makefile
Normal file
@@ -0,0 +1,57 @@
|
||||
# OpenWrt package Makefile for btest-rs
|
||||
#
|
||||
# To build:
|
||||
# 1. Clone the OpenWrt SDK for your target
|
||||
# 2. Copy this directory to package/btest-rs/ in the SDK
|
||||
# 3. Run: make package/btest-rs/compile V=s
|
||||
#
|
||||
# Or use the pre-built binary approach (see build-ipk.sh)
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=btest-rs
|
||||
PKG_VERSION:=0.6.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/manawenuz/btest-rs/archive/refs/tags/v$(PKG_VERSION).tar.gz
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_BUILD_DEPENDS:=rust/host
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/btest-rs
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=MikroTik Bandwidth Test server and client
|
||||
URL:=https://github.com/manawenuz/btest-rs
|
||||
DEPENDS:=
|
||||
PKGARCH:=$(ARCH)
|
||||
endef
|
||||
|
||||
define Package/btest-rs/description
|
||||
A Rust reimplementation of the MikroTik Bandwidth Test (btest) protocol.
|
||||
Supports TCP/UDP, IPv4/IPv6, EC-SRP5 and MD5 authentication,
|
||||
multi-connection, syslog, CSV output, and CPU monitoring.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
cd $(PKG_BUILD_DIR) && \
|
||||
CARGO_TARGET_DIR=$(PKG_BUILD_DIR)/target \
|
||||
cargo build --release --target $(RUSTC_TARGET)
|
||||
endef
|
||||
|
||||
define Package/btest-rs/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET)/release/btest $(1)/usr/bin/btest
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/btest.init $(1)/etc/init.d/btest
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/btest.config $(1)/etc/config/btest
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,btest-rs))
|
||||
117
deploy/openwrt/build-ipk.sh
Executable file
117
deploy/openwrt/build-ipk.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build an OpenWrt .ipk package from a pre-built static binary.
|
||||
# No OpenWrt SDK needed — just packages the binary with metadata.
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy/openwrt/build-ipk.sh <arch> [binary-path]
|
||||
#
|
||||
# Examples:
|
||||
# ./deploy/openwrt/build-ipk.sh x86_64 dist/btest # from cross-compiled binary
|
||||
# ./deploy/openwrt/build-ipk.sh aarch64 dist/btest # for RPi/ARM64 routers
|
||||
# ./deploy/openwrt/build-ipk.sh mipsel target/release/btest # for MIPS little-endian
|
||||
#
|
||||
# Supported architectures: x86_64, aarch64, arm_cortex-a7, mipsel_24kc, mips_24kc
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
ARCH="${1:?Usage: $0 <arch> [binary-path]}"
|
||||
BINARY="${2:-dist/btest}"
|
||||
VERSION="0.6.0"
|
||||
PKG_NAME="btest-rs"
|
||||
OUTPUT_DIR="dist"
|
||||
|
||||
if [ ! -f "$BINARY" ]; then
|
||||
echo "Error: binary not found at $BINARY"
|
||||
echo "Build it first: cargo build --release --target <target>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
WORKDIR=$(mktemp -d)
|
||||
trap "rm -rf $WORKDIR" EXIT
|
||||
|
||||
echo "=== Building ${PKG_NAME}_${VERSION}_${ARCH}.ipk ==="
|
||||
|
||||
# Create package structure
|
||||
mkdir -p "$WORKDIR/data/usr/bin"
|
||||
mkdir -p "$WORKDIR/data/etc/init.d"
|
||||
mkdir -p "$WORKDIR/data/etc/config"
|
||||
mkdir -p "$WORKDIR/control"
|
||||
|
||||
# Install files
|
||||
cp "$BINARY" "$WORKDIR/data/usr/bin/btest"
|
||||
chmod 755 "$WORKDIR/data/usr/bin/btest"
|
||||
cp deploy/openwrt/files/btest.init "$WORKDIR/data/etc/init.d/btest"
|
||||
chmod 755 "$WORKDIR/data/etc/init.d/btest"
|
||||
cp deploy/openwrt/files/btest.config "$WORKDIR/data/etc/config/btest"
|
||||
|
||||
# Calculate installed size
|
||||
INSTALLED_SIZE=$(du -sk "$WORKDIR/data" | awk '{print $1}')
|
||||
|
||||
# Control file
|
||||
cat > "$WORKDIR/control/control" << EOF
|
||||
Package: ${PKG_NAME}
|
||||
Version: ${VERSION}-1
|
||||
Depends: libc
|
||||
Source: https://github.com/manawenuz/btest-rs
|
||||
License: MIT AND Apache-2.0
|
||||
Section: net
|
||||
SourceName: ${PKG_NAME}
|
||||
Maintainer: Siavash Sameni <manwe@manko.yoga>
|
||||
Architecture: ${ARCH}
|
||||
Installed-Size: ${INSTALLED_SIZE}
|
||||
Description: MikroTik Bandwidth Test server and client
|
||||
A Rust reimplementation of the MikroTik btest protocol.
|
||||
Supports TCP/UDP, EC-SRP5 and MD5 auth, IPv4/IPv6.
|
||||
EOF
|
||||
|
||||
# Post-install script
|
||||
cat > "$WORKDIR/control/postinst" << 'EOF'
|
||||
#!/bin/sh
|
||||
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
|
||||
/etc/init.d/btest enable 2>/dev/null || true
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$WORKDIR/control/postinst"
|
||||
|
||||
# Pre-remove script
|
||||
cat > "$WORKDIR/control/prerm" << 'EOF'
|
||||
#!/bin/sh
|
||||
/etc/init.d/btest stop 2>/dev/null || true
|
||||
/etc/init.d/btest disable 2>/dev/null || true
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$WORKDIR/control/prerm"
|
||||
|
||||
# Conffiles
|
||||
cat > "$WORKDIR/control/conffiles" << EOF
|
||||
/etc/config/btest
|
||||
EOF
|
||||
|
||||
# Build the .ipk (it's just a tar.gz of tar.gz's)
|
||||
cd "$WORKDIR"
|
||||
|
||||
# Create data.tar.gz
|
||||
(cd data && tar czf ../data.tar.gz .)
|
||||
|
||||
# Create control.tar.gz
|
||||
(cd control && tar czf ../control.tar.gz .)
|
||||
|
||||
# Create debian-binary
|
||||
echo "2.0" > debian-binary
|
||||
|
||||
# Package it all
|
||||
tar czf "${PKG_NAME}_${VERSION}-1_${ARCH}.ipk" debian-binary control.tar.gz data.tar.gz
|
||||
|
||||
cd -
|
||||
cp "$WORKDIR/${PKG_NAME}_${VERSION}-1_${ARCH}.ipk" "$OUTPUT_DIR/"
|
||||
|
||||
echo ""
|
||||
echo "Package: $OUTPUT_DIR/${PKG_NAME}_${VERSION}-1_${ARCH}.ipk"
|
||||
ls -lh "$OUTPUT_DIR/${PKG_NAME}_${VERSION}-1_${ARCH}.ipk"
|
||||
echo ""
|
||||
echo "Install on OpenWrt:"
|
||||
echo " scp $OUTPUT_DIR/${PKG_NAME}_${VERSION}-1_${ARCH}.ipk root@router:/tmp/"
|
||||
echo " ssh root@router 'opkg install /tmp/${PKG_NAME}_${VERSION}-1_${ARCH}.ipk'"
|
||||
echo " ssh root@router '/etc/init.d/btest enable && /etc/init.d/btest start'"
|
||||
7
deploy/openwrt/files/btest.config
Normal file
7
deploy/openwrt/files/btest.config
Normal file
@@ -0,0 +1,7 @@
|
||||
config server
|
||||
option enabled '0'
|
||||
option port '2000'
|
||||
option auth_user ''
|
||||
option auth_pass ''
|
||||
option ecsrp5 '0'
|
||||
option syslog ''
|
||||
34
deploy/openwrt/files/btest.init
Executable file
34
deploy/openwrt/files/btest.init
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# btest-rs OpenWrt init script
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
local enabled port auth_user auth_pass ecsrp5 syslog
|
||||
|
||||
config_load btest
|
||||
config_get_bool enabled server enabled 0
|
||||
[ "$enabled" -eq 0 ] && return
|
||||
|
||||
config_get port server port 2000
|
||||
config_get auth_user server auth_user ''
|
||||
config_get auth_pass server auth_pass ''
|
||||
config_get_bool ecsrp5 server ecsrp5 0
|
||||
config_get syslog server syslog ''
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/btest -s -P "$port"
|
||||
|
||||
[ -n "$auth_user" ] && procd_append_param command -a "$auth_user"
|
||||
[ -n "$auth_pass" ] && procd_append_param command -p "$auth_pass"
|
||||
[ "$ecsrp5" -eq 1 ] && procd_append_param command --ecsrp5
|
||||
[ -n "$syslog" ] && procd_append_param command --syslog "$syslog"
|
||||
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
127
src/bin/client_only.rs
Normal file
127
src/bin/client_only.rs
Normal 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
62
src/bin/server_only.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user