Commit Graph

31 Commits

Author SHA1 Message Date
Siavash Sameni
d8f3b9c189 Fix TCP data: send all zeros, not 0x07 header
All checks were successful
CI / test (push) Successful in 1m20s
MITM capture showed MikroTik sends all-zero TCP data streams.
Our server was setting packet[0]=0x07 (STATUS_MSG_TYPE), which
MikroTik rejected. TCP mode has no status headers — just raw
zero-filled data streams in both directions.

Fixed in both server and client TCP TX loops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:39:12 +04:00
Siavash Sameni
9552cbef1a Fix disconnect detection: TX/RX loops set running=false on EOF
All checks were successful
CI / test (push) Successful in 1m20s
When a TCP connection closes (EOF or write error), the loop now sets
the shared running flag to false, which stops the status report loop
and all other tasks. Adds "test ended" log messages.

The TCP multi-conn "MikroTik shows 0 on send" is a separate issue
requiring TCP-level status exchange (MikroTik sends 12-byte status
messages on TCP connections, not just a data stream).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:30:16 +04:00
Siavash Sameni
6c82228dd1 Fix EC-SRP5 server: use stored gamma parity, not hardcoded true
All checks were successful
CI / test (push) Successful in 1m21s
The gamma point's y-parity depends on the random salt. Using hardcoded
parity=true caused ~50% of auth attempts to fail (whenever the actual
parity was 0). Now stored from key derivation and used correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:22:06 +04:00
Siavash Sameni
a87dd7510f Fix TCP multi-connection: TX/RX on ALL streams, not just primary
All checks were successful
CI / test (push) Successful in 1m19s
pcap analysis showed MikroTik sends/receives data across all 20 TCP
connections, but we only used the primary. Now all streams get their
own TX and RX tasks, distributing bandwidth across all connections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:17:00 +04:00
Siavash Sameni
b28c553e10 Fix EC-SRP5 server: use lift_x not redp1 for verification
All checks were successful
CI / test (push) Successful in 1m20s
Server-side shared secret used redp1(x_gamma) which is the hash-to-curve
blinding function, but verification needs lift_x(x_gamma) — the raw
validator public key point. Also fixed prime_mod_sqrt for p ≡ 5 (mod 8)
using Atkin's algorithm instead of Tonelli-Shanks.

Removed unused password parameter from server_authenticate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:10:31 +04:00
Siavash Sameni
58274da859 Add EC-SRP5 authentication (RouterOS >= 6.43)
All checks were successful
CI / test (push) Successful in 1m18s
Client: auto-detects 03 response and performs EC-SRP5 handshake
Server: --ecsrp5 flag enables Curve25519 Weierstrass EC-SRP5 auth
  btest -s -a admin -p password --ecsrp5

Protocol: [len][payload] framing (no 0x06 handler, unlike Winbox)
Crypto: Curve25519 in Weierstrass form, SHA256, SRP key exchange

Based on MarginResearch/mikrotik_authentication (Apache 2.0).
Verified against MikroTik RouterOS 7.x via MITM protocol analysis.

34 tests (10 unit, 6 EC-SRP5 integration, 8 base integration, 10 doc-tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:56:38 +04:00
Siavash Sameni
8fe4e72bb3 Fix TCP multi-conn auth: secondary connections skip auth
All checks were successful
CI / test (push) Successful in 1m9s
Build & Release / release (push) Successful in 2m41s
Secondary connections send [TOKEN_HI, TOKEN_LO, 0x02, 0x00, ...]
as their command — they don't do auth. Server verifies the session
token matches a pending session from the same IP, sends OK with
token, and lets them join. No auth challenge/response needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.2.0
2026-03-31 15:47:40 +04:00
Siavash Sameni
9853d74c4a Debug TCP multi-conn: log raw bytes from secondary connections
All checks were successful
CI / test (push) Successful in 1m9s
Secondary connections were rejected at recv_command with "Invalid command"
because they don't send a standard 16-byte command. Now we read raw bytes
first, check if there's a pending session from the same IP, and handle
secondary connections before validating the command format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:44:51 +04:00
Siavash Sameni
1659f10d62 Add TCP multi-connection support with session tokens
All checks were successful
CI / test (push) Successful in 1m7s
When tcp_conn_count > 0, the auth OK response includes a session
token in bytes 1-2: [01, HI, LO, 00] instead of [01, 00, 00, 00].
MikroTik checks these bytes to determine multi-connection support.

Primary connection: full handshake, receives session token
Secondary connections: auth with same token, join the session
Server waits up to 10s for all connections to join before starting.

This fixes MikroTik showing "test unsupported" for TCP multi-conn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:40:33 +04:00
Siavash Sameni
3dfd0185e5 Update docs: multi-connection is now supported
All checks were successful
CI / test (push) Successful in 1m7s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:28:02 +04:00
Siavash Sameni
28e553bc5f Fix multi-connection: don't connect() UDP socket when conn_count > 1
Some checks failed
CI / test (push) Has been cancelled
Root cause found via pcap analysis: MikroTik with connection-count=N
sends UDP from N different source ports (2257, 2258, 2259, ...) all
to our single server port 2001. A connect()'d UDP socket only accepts
packets from the one connected address, silently dropping ~75% of
traffic with conn_count=4.

Fix: when tcp_conn_count > 0, leave the UDP socket unconnected and
use send_to()/recv_from() instead of send()/recv(). This accepts
packets from all MikroTik source ports.

This bug also exists in the original C btest-opensource.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:27:33 +04:00
Siavash Sameni
4dddf21f2f Fix: remove auto docker login that overwrites working credentials
All checks were successful
CI / test (push) Successful in 1m8s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:09:09 +04:00
Siavash Sameni
1be3cb82dc Multi-arch Docker push: amd64 + arm64 with manifest
All checks were successful
CI / test (push) Successful in 1m8s
- Dockerfile.static: takes pre-built binary, no compilation
- push-docker.sh: downloads x86_64 from CI release, builds arm64
  natively, creates multi-arch manifest and pushes both
- docker pull works on both Intel and Apple Silicon / RPi

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:06:56 +04:00
Siavash Sameni
f1f597d308 Fix Docker registry auth: use GITEA_USER instead of 'token'
All checks were successful
CI / test (push) Successful in 1m5s
Gitea container registry requires actual username, not 'token'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:59:23 +04:00
Siavash Sameni
091222fbd4 Docs: emphasize Connection Count must be 1 for server mode
All checks were successful
CI / test (push) Successful in 1m6s
Multi-connection mode is not supported and causes near-zero throughput.
Updated README, user guide, MikroTik CLI examples, and troubleshooting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:57:11 +04:00
Siavash Sameni
aa663f6b38 Remove Docker build from CI, add local push-docker.sh script
All checks were successful
CI / test (push) Successful in 1m9s
Build & Release / release (push) Successful in 2m53s
crane can't pull scratch in CI. Docker images are built locally
on Mac where Docker is available, then pushed to Gitea registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.1.0
2026-03-31 14:53:21 +04:00
Siavash Sameni
9a6be68e62 Fix crane: use mutate for entrypoint/cmd instead of append flags
Some checks failed
CI / test (push) Successful in 1m6s
Build & Release / release (push) Failing after 2m44s
crane append doesn't support --set-entrypoint. Use crane mutate
as a separate step to set entrypoint and cmd on the pushed image.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:46:49 +04:00
Siavash Sameni
e686e66ded Add Docker image push to release workflow
Some checks failed
CI / test (push) Successful in 1m7s
Build & Release / release (push) Failing after 2m44s
Uses crane (no Docker daemon needed) to build a minimal scratch-based
OCI image from the static musl binary and push it to the Gitea
container registry. Tags both vX.Y.Z and latest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:39:40 +04:00
Siavash Sameni
53a9795445 Add Windows x86_64 cross-compilation to release workflow
All checks were successful
CI / test (push) Successful in 1m7s
Build & Release / release (push) Successful in 2m49s
Uses gcc-mingw-w64 to cross-compile btest.exe from Linux.
Release now includes 4 targets: Linux x86_64/aarch64/armv7 + Windows x86_64.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:36:27 +04:00
Siavash Sameni
67ff26b5f8 Add .env support for release scripts, add .env.example
All checks were successful
CI / test (push) Successful in 1m6s
Scripts now source .env automatically for GITEA_TOKEN and other config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:35:14 +04:00
Siavash Sameni
fd148acffe Remove all Node.js actions, use pure shell for CI/CD
All checks were successful
CI / test (push) Successful in 1m10s
Build & Release / release (push) Successful in 2m11s
The act runner has no Node.js in container jobs. Replace
actions/upload-artifact and actions/download-artifact with
direct Gitea API uploads from a single job that builds all
three architectures sequentially.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:31:39 +04:00
Siavash Sameni
83f2b4c275 Fix release workflow: add git clone fallback for all build jobs
Some checks failed
CI / test (push) Has been cancelled
Build & Release / build-linux-x86_64 (push) Failing after 1m4s
Build & Release / build-linux-aarch64 (push) Failing after 1m12s
Build & Release / build-linux-armv7 (push) Failing after 58s
Build & Release / release (push) Has been skipped
Same fix as CI — workspace may be empty when using container jobs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:28:11 +04:00
Siavash Sameni
67e1f53572 CI: add debug step + fallback git clone if workspace is empty
Some checks failed
CI / test (push) Successful in 1m9s
Build & Release / build-linux-x86_64 (push) Failing after 12s
Build & Release / release (push) Has been cancelled
Build & Release / build-linux-aarch64 (push) Has been cancelled
Build & Release / build-linux-armv7 (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:25:39 +04:00
Siavash Sameni
7f701d8442 Fix CI: use act runner workspace directly, no manual clone
Some checks failed
CI / test (push) Failing after 2s
Build & Release / build-linux-x86_64 (push) Failing after 14s
Build & Release / build-linux-aarch64 (push) Failing after 16s
Build & Release / build-linux-armv7 (push) Failing after 15s
Build & Release / release (push) Has been skipped
The act runner already mounts the repo at the workspace path.
Removed manual git clone and working_directory overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:23:33 +04:00
Siavash Sameni
99a751fa28 Fix CI: replace actions/checkout with manual git clone
Some checks failed
Build & Release / build-linux-x86_64 (push) Failing after 24s
CI / test (push) Successful in 1m6s
Build & Release / build-linux-aarch64 (push) Failing after 28s
Build & Release / build-linux-armv7 (push) Failing after 32s
Build & Release / release (push) Has been skipped
The act runner executes actions/checkout inside the job container,
but that action is a Node.js script and rust:1.86-slim has no node.
Use plain git clone instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:10:15 +04:00
Siavash Sameni
f97ee1387f Fix: rename GITEA_TOKEN secret to RELEASE_TOKEN
Some checks failed
Build & Release / build-linux-x86_64 (push) Failing after 17s
Build & Release / build-linux-aarch64 (push) Failing after 19s
Build & Release / build-linux-armv7 (push) Failing after 4s
Build & Release / build-macos (push) Failing after 4s
Build & Release / release (push) Has been skipped
CI / test (push) Failing after 3s
Gitea reserves the GITEA_ prefix for secrets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:04:48 +04:00
Siavash Sameni
de193dc9f5 Add Gitea Actions CI/CD workflows
Some checks failed
CI / test (push) Failing after 13s
- .gitea/workflows/ci.yml: run tests on every push/PR
- .gitea/workflows/release.yml: build Linux binaries on tag push
  - x86_64 (musl static)
  - aarch64 / RPi 64-bit (musl static)
  - armv7 / RPi 32-bit (musl static)
  - Auto-creates Gitea release with all artifacts
- scripts/build-macos-release.sh: build macOS binary locally and
  upload to an existing Gitea release

Release flow:
  git tag v0.1.0 && git push origin v0.1.0
  # CI builds Linux + RPi, creates release
  # Then on Mac: ./scripts/build-macos-release.sh --upload v0.1.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:00:58 +04:00
Siavash Sameni
6a70e05454 Add comprehensive documentation
- docs/architecture.md: module structure, data flow, threading model (Mermaid diagrams)
- docs/protocol.md: complete wire protocol specification with packet formats
- docs/user-guide.md: server & client usage, CLI reference, troubleshooting
- docs/docker.md: Docker, Compose, registry push, deployment options
- Update docker-compose.yml with Gitea registry image tags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 13:06:14 +04:00
Siavash Sameni
d71a3a4e71 Rename to btest-rs, add LICENSE and README with full credits
- Rename package to btest-rs (Rust convention for reimplementations)
- MIT license matching the original btest-opensource license
- LICENSE explicitly credits Alex Samorukov's original work
- Comprehensive README with usage, performance numbers, and credits
- CLI --help references the original project

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 12:58:04 +04:00
Siavash Sameni
e604fdb2e7 Add cross-compilation, Linux binary build, and systemd service installer
- Dockerfile.cross: builds static x86_64 musl binary from macOS via Docker
- scripts/build-linux.sh: one-command cross-compilation
- scripts/install-service.sh: systemd service with security hardening
- Bump Rust Docker images to 1.86 for edition2024 support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 12:52:20 +04:00
Siavash Sameni
d9007dc169 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>
2026-03-31 11:56:34 +04:00