Initial commit: MikroTik btest server & client in Rust

Full reimplementation of the MikroTik Bandwidth Test protocol:
- Server mode: accepts connections from MikroTik devices on port 2000
- Client mode: connects to MikroTik btest servers
- TCP and UDP protocols with bidirectional support
- MD5 challenge-response authentication
- Dynamic speed adjustment (1.5x algorithm)
- Status exchange matching original C pselect() behavior
- Docker support with multi-stage build

Tested against MikroTik RouterOS achieving:
- 1.05 Gbps server RX (single connection)
- 530 Mbps client TCP download
- 840 Mbps client TCP upload
- 433 Mbps client UDP download

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-31 11:56:34 +04:00
commit d9007dc169
18 changed files with 2801 additions and 0 deletions

43
scripts/test-docker.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Test using Docker
set -euo pipefail
echo "=== Building Docker image ==="
docker build -t btest .
echo ""
echo "=== Running btest server in Docker ==="
docker run -d --name btest-test -p 2000:2000/tcp -p 2001-2100:2001-2100/udp -p 2257-2356:2257-2356/udp btest -s -v
sleep 2
cleanup() {
echo "Stopping Docker container..."
docker stop btest-test 2>/dev/null || true
docker rm btest-test 2>/dev/null || true
}
trap cleanup EXIT
BTEST="./target/release/btest"
cargo build --release
run_timed() {
local desc="$1"; shift
echo ""
echo "--- $desc ---"
$BTEST "$@" &
local pid=$!
sleep 5
kill $pid 2>/dev/null || true
wait $pid 2>/dev/null || true
}
run_timed "TCP Download from Docker server" -c 127.0.0.1 -r
run_timed "TCP Upload to Docker server" -c 127.0.0.1 -t
run_timed "TCP Bidirectional" -c 127.0.0.1 -t -r
echo ""
echo "=== Docker server logs ==="
docker logs btest-test
echo ""
echo "=== Docker tests completed ==="

63
scripts/test-local.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Local loopback tests - run server and client against each other
set -euo pipefail
BTEST="cargo run --release --"
PORT=2000
echo "=== Building release binary ==="
cargo build --release
BTEST="./target/release/btest"
cleanup() {
echo "Stopping server..."
kill $SERVER_PID 2>/dev/null || true
wait $SERVER_PID 2>/dev/null || true
}
echo ""
echo "=== Starting btest server on port $PORT ==="
$BTEST -s -P $PORT -v &
SERVER_PID=$!
trap cleanup EXIT
sleep 1
TIMEOUT_CMD="timeout"
if ! command -v timeout &>/dev/null; then
if command -v gtimeout &>/dev/null; then
TIMEOUT_CMD="gtimeout"
else
# Fallback: background + sleep + kill
TIMEOUT_CMD=""
fi
fi
run_test() {
local desc="$1"
shift
echo ""
echo "--- Test: $desc ---"
if [ -n "$TIMEOUT_CMD" ]; then
$TIMEOUT_CMD 5 $BTEST "$@" || true
else
$BTEST "$@" &
local pid=$!
sleep 5
kill $pid 2>/dev/null || true
wait $pid 2>/dev/null || true
fi
echo "--- Done: $desc ---"
sleep 1
}
run_test "TCP Download (RX)" -c 127.0.0.1 -P $PORT -r
run_test "TCP Upload (TX)" -c 127.0.0.1 -P $PORT -t
run_test "TCP Bidirectional" -c 127.0.0.1 -P $PORT -t -r
run_test "TCP Download 100Mbps limited" -c 127.0.0.1 -P $PORT -r -b 100M
run_test "UDP Download" -c 127.0.0.1 -P $PORT -r -u
run_test "UDP Upload" -c 127.0.0.1 -P $PORT -t -u
run_test "UDP Bidirectional" -c 127.0.0.1 -P $PORT -t -r -u
echo ""
echo "=== All local tests completed ==="

70
scripts/test-mikrotik.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Test against a MikroTik device
# Usage: ./scripts/test-mikrotik.sh <mikrotik_ip> [username] [password]
set -euo pipefail
MIKROTIK_IP="${1:?Usage: $0 <mikrotik_ip> [username] [password]}"
USERNAME="${2:-}"
PASSWORD="${3:-}"
BTEST="./target/release/btest"
echo "=== Building release binary ==="
cargo build --release
AUTH_ARGS=""
if [ -n "$USERNAME" ]; then
AUTH_ARGS="-a $USERNAME"
fi
if [ -n "$PASSWORD" ]; then
AUTH_ARGS="$AUTH_ARGS -p $PASSWORD"
fi
echo ""
echo "=== Testing against MikroTik at $MIKROTIK_IP ==="
echo ""
TIMEOUT_CMD="timeout"
if ! command -v timeout &>/dev/null; then
if command -v gtimeout &>/dev/null; then
TIMEOUT_CMD="gtimeout"
else
TIMEOUT_CMD=""
fi
fi
run_test() {
local desc="$1"
shift
echo "--- $desc ---"
if [ -n "$TIMEOUT_CMD" ]; then
$TIMEOUT_CMD 10 $BTEST "$@" $AUTH_ARGS || echo "(test ended)"
else
$BTEST "$@" $AUTH_ARGS &
local pid=$!
sleep 10
kill $pid 2>/dev/null || true
wait $pid 2>/dev/null || true
echo "(test ended)"
fi
echo ""
sleep 1
}
echo "=== Mode 1: Our client -> MikroTik btest server ==="
echo "(Make sure btest server is enabled on your MikroTik: /tool/bandwidth-server set enabled=yes)"
echo ""
run_test "TCP Download from MikroTik" -c "$MIKROTIK_IP" -r
run_test "TCP Upload to MikroTik" -c "$MIKROTIK_IP" -t
run_test "TCP Bidirectional with MikroTik" -c "$MIKROTIK_IP" -t -r
run_test "UDP Download from MikroTik" -c "$MIKROTIK_IP" -r -u
run_test "UDP Upload to MikroTik" -c "$MIKROTIK_IP" -t -u
echo ""
echo "=== Mode 2: Our server <- MikroTik connects to us ==="
echo "To test this mode:"
echo " 1. Run: $BTEST -s -v $AUTH_ARGS"
echo " 2. On MikroTik, run:"
echo " /tool/bandwidth-test address=<this_server_ip> direction=both protocol=tcp"
echo ""
echo "=== All MikroTik tests completed ==="