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"
|
name = "btest"
|
||||||
path = "src/main.rs"
|
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]
|
[dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
@@ -38,3 +46,9 @@ opt-level = 3
|
|||||||
lto = true
|
lto = true
|
||||||
strip = true
|
strip = true
|
||||||
codegen-units = 1
|
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