Commit Graph

46 Commits

Author SHA1 Message Date
Siavash Sameni
50c0ba528d Fix IPv6 UDP: send NDP probe before data to populate neighbor cache
All checks were successful
CI / test (push) Successful in 1m26s
macOS returns ENOBUFS on IPv6 send_to() until NDP neighbor resolution
completes. Send a 1-byte probe packet and wait 200ms for NDP to resolve
before starting the data blast.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:06:41 +04:00
Siavash Sameni
4e3b2939ca Fix IPv6 UDP buffers: create socket with socket2 before tokio
All checks were successful
CI / test (push) Successful in 1m27s
The into_std/from_std conversion lost the buffer settings. Now create
the raw socket with socket2 first, set SO_SNDBUF/SO_RCVBUF to 4MB,
then wrap with tokio. Also logs actual buffer sizes for debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:04:27 +04:00
Siavash Sameni
6ba57864a0 Fix IPv6 UDP TX: enlarge socket buffers to 4MB
All checks were successful
CI / test (push) Successful in 1m25s
macOS IPv6 UDP sockets have tiny default send buffers, causing
immediate ENOBUFS on every send_to(). Set SO_SNDBUF and SO_RCVBUF
to 4MB using socket2, matching what works for high-throughput IPv4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:01:50 +04:00
Siavash Sameni
a1dbc6dc5a Fix client IPv6 UDP: use SocketAddr::new() and bind to [::]
All checks were successful
CI / test (push) Successful in 1m26s
Build & Release / release (push) Successful in 3m10s
Same fix as server side — format!("{}:{}", ipv6, port) fails.
Use SocketAddr::new() for IPv6 and bind to [::] instead of 0.0.0.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:54:45 +04:00
Siavash Sameni
7be6a0d541 Fix IPv6 UDP TX: adaptive backoff on ENOBUFS
All checks were successful
CI / test (push) Successful in 1m28s
Build & Release / release (push) Successful in 3m10s
IPv6 UDP sends hit ENOBUFS much faster than IPv4 (smaller kernel
buffers, NDP overhead). Fixed:
- Adaptive backoff: 200us→10ms as errors accumulate, resets on success
- Higher error threshold: 50k instead of 1k before stopping
- Yield with sleep when errors have been seen recently

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:45:04 +04:00
Siavash Sameni
ba0a8f1b7c Add UDP TX error logging for IPv6 debugging
All checks were successful
CI / test (push) Successful in 1m27s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:41:57 +04:00
Siavash Sameni
176cdae239 Fix IPv6 UDP: use unconnected socket for IPv6 peers
All checks were successful
CI / test (push) Successful in 1m28s
macOS connected IPv6 UDP sockets don't receive properly.
Use unconnected socket (send_to/recv_from) for IPv6 peers,
same as multi-connection mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:37:43 +04:00
Siavash Sameni
0385d2e745 Fix IPv6 UDP: use SocketAddr::new() and bind correct address family
All checks were successful
CI / test (push) Successful in 1m23s
format!("{}:{}", ipv6_addr, port) produces invalid socket address.
Use SocketAddr::new() instead. Also bind UDP to [::] for IPv6 peers
and 0.0.0.0 for IPv4 peers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:31:22 +04:00
Siavash Sameni
7bbb7c9d9b Add dual-stack IPv4+IPv6 listening
All checks were successful
CI / test (push) Successful in 1m24s
Server now binds on both IPv4 (0.0.0.0) and IPv6 (::) by default.
Uses tokio::select! to accept from whichever listener has a connection.

New flags:
  --listen <addr>   IPv4 listen address (default: 0.0.0.0, "none" to disable)
  --listen6 <addr>  IPv6 listen address (default: ::, "none" to disable)

Examples:
  btest -s                          # listen on both v4 and v6
  btest -s --listen6 none           # IPv4 only
  btest -s --listen none            # IPv6 only
  btest -s --listen 192.168.1.1     # specific IPv4 address

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:28:48 +04:00
Siavash Sameni
2dec6cc007 v0.5.0: Add syslog support, fix TCP send/both, EC-SRP5 server auth
All checks were successful
CI / test (push) Successful in 1m22s
New features:
- --syslog <address:port> sends structured events to remote syslog (RFC 5424 UDP)
  Events: AUTH_SUCCESS, AUTH_FAILURE, TEST_START, TEST_END, TEST_RESULT
- EC-SRP5 authentication for both client and server modes
- TCP multi-connection support (session tokens, all 3 directions)

Bug fixes since v0.2.0:
- EC-SRP5 server: fixed gamma parity (was 50% auth failure rate)
- EC-SRP5 server: use lift_x not redp1 for verification
- TCP send direction: server sends 12-byte status messages to client
- TCP both direction: TX loop injects status between data packets
- TCP data: send all zeros (no 0x07 header that MikroTik rejected)
- TCP disconnect detection: running flag set on EOF
- UDP multi-connection: unconnected socket accepts all source ports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:22:31 +04:00
Siavash Sameni
f9289cca55 Add TCP status for bidirectional mode
All checks were successful
CI / test (push) Successful in 1m22s
In BOTH direction, the TX loop now injects 12-byte status messages
every 1 second between data packets, reporting rx_bytes to the client.
Multi-connection mode also updated with same logic for all 3 cases:
- TX only: pure data
- RX only: status sender on writer
- BOTH: TX data + interleaved status messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:10:46 +04:00
Siavash Sameni
8b127d833f Fix TCP status: use swap(0) and skip status_report_loop in RX mode
All checks were successful
CI / test (push) Successful in 1m24s
The status sender and status_report_loop were BOTH calling swap(0)
on rx_bytes, racing each other. Now the status sender owns the swap
and prints stats itself. The report loop is skipped in RX-only TCP mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:03:28 +04:00
Siavash Sameni
cdad23ffa0 Fix TCP status: report delta bytes per interval, not cumulative
All checks were successful
CI / test (push) Successful in 1m20s
Was sending cumulative rx_bytes total which zigzagged because
MikroTik interprets the value as per-interval bandwidth.
Now tracks last_rx and sends (current - last) delta each second.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:58:58 +04:00
Siavash Sameni
51bc4ddf16 Fix TCP send: server sends 12-byte status messages when receiving
All checks were successful
CI / test (push) Successful in 1m19s
pcap of MikroTik-as-server showed it sends periodic 12-byte status
messages back to the client even in RX-only mode. The client needs
these to display speed. Added tcp_status_sender that writes status
messages containing rx_bytes on the TCP write half every 1 second.

Reverted the "always bidirectional" change — TCP direction is
conditional, but RX mode now uses the writer for status instead
of keeping it idle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:56:36 +04:00
Siavash Sameni
fa4fd63fb3 Fix TCP: always send bidirectional data regardless of direction
All checks were successful
CI / test (push) Successful in 1m21s
MITM capture of MikroTik-to-MikroTik showed both sides always send
zero-filled TCP streams, regardless of the direction setting. Direction
only controls what gets measured. Our server wasn't starting a TX thread
when direction=RX, so MikroTik saw no data and reported 0 speed.

Now TCP always starts both TX and RX on every connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:46:16 +04:00
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