Commit Graph

77 Commits

Author SHA1 Message Date
Siavash Sameni
9e3cd6d6d4 Wire data transfer into pro server — full quota enforcement working
The pro server now runs actual bandwidth tests with concurrent quota
enforcement. Data flows through the standard btest TCP/UDP handlers
while the QuotaEnforcer monitors usage every N seconds.

Public API added to btest_rs::server:
- run_tcp_test(stream, cmd, state) — TCP test with external state
- run_udp_test(stream, peer, cmd, state, port) — UDP with external state

These allow the pro server to share BandwidthState between the test
handlers and the enforcer, enabling mid-session quota termination.

Verified end-to-end:
- Test 1: TCP download at 70 Gbps, ran full duration
- Test 2: TCP upload, KILLED mid-session by enforcer after 3 checks
  (user_daily_quota_exceeded at 23.8 GB vs 50 MB limit)
- Test 3: REJECTED at connection time (quota already used up)

64 tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:59:48 +04:00
Siavash Sameni
4403eae4b9 Wire quota enforcement into pro server loop
New server_loop.rs:
- Custom accept loop with pre-connection IP quota check
- DB-based MD5 authentication (verifies user exists + enabled)
- Pre-test user quota check (reject if already exceeded)
- Session tracking in DB (start_session/end_session)
- QuotaEnforcer spawned alongside each test
- Post-test usage recording to both user + IP tables
- Syslog events for auth, quota rejection, test start/end

Full flow:
  1. Accept connection → check IP quota → reject if exceeded
  2. Handshake + auth → verify user in DB → reject if disabled/not found
  3. Check user quota → reject if daily/weekly/monthly exceeded
  4. Start session → spawn enforcer (checks every N seconds)
  5. Run test → enforcer stops it if quota hit or max_duration reached
  6. Record usage → persist to DB → disconnect IP tracker

TODO: Wire actual TX/RX data loops (currently only enforcer runs,
data transfer not yet delegated from pro server to standard handlers)

64 tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:45:45 +04:00
Siavash Sameni
c08bcffaff Add mid-session quota enforcement with 6 tests
New enforcer.rs module runs alongside active tests:
- Periodic quota checks (default every 10s, configurable --quota-check-interval)
- Max duration enforcement — forcefully stops test after limit
- User quotas: daily/weekly/monthly checked against DB + current session
- IP quotas: daily/weekly/monthly checked against DB + current session
- Flush session bytes to DB for accurate cross-session tracking
- Sets state.running=false to gracefully terminate on quota breach

StopReason enum tracks why a test was stopped:
  MaxDuration, UserDailyQuota, UserWeeklyQuota, UserMonthlyQuota,
  IpDailyQuota, IpWeeklyQuota, IpMonthlyQuota, ClientDisconnected

Tests (6 new, all passing):
- test_enforcer_max_duration: stops after max_duration seconds
- test_enforcer_client_disconnect: detects normal client exit
- test_enforcer_user_daily_quota_exceeded: stops when user quota hit
- test_enforcer_ip_daily_quota_exceeded: stops when IP quota hit
- test_enforcer_under_quota_runs_normally: doesn't stop if under limits
- test_enforcer_flush_records_usage: verifies DB persistence

64 total tests (58 standard + 6 enforcer), all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:20:26 +04:00
Siavash Sameni
d61fdb1b94 Add monthly quotas, per-IP limits, user management CLI
Quota system now supports:
- Per-user: daily, weekly, monthly limits
- Per-IP: daily, weekly, monthly limits (abuse prevention)
- Per-IP connection limit
- Max test duration

New CLI flags:
  --monthly-quota, --ip-daily, --ip-weekly, --ip-monthly

User management subcommands:
  btest-server-pro useradd <user> <pass>
  btest-server-pro userdel <user>
  btest-server-pro userlist
  btest-server-pro userset <user> --enabled true/false --daily N --weekly N

New DB tables: ip_usage (per-IP daily tracking)
New methods: get_monthly_usage, get_ip_*_usage, start/end_session,
  delete_user, set_user_enabled, set_user_quota

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:58:19 +04:00
Siavash Sameni
d2fdc9c6ae Scaffold btest-server-pro: multi-user, quotas, LDAP
New binary `btest-server-pro` (build with --features pro):
  cargo build --release --features pro --bin btest-server-pro

Modules:
- server_pro/user_db.rs: SQLite user database with usage tracking
  - Users table (username, password_hash, quotas, enabled)
  - Usage table (daily bytes per user)
  - Sessions table (per-connection tracking)
- server_pro/quota.rs: bandwidth quota enforcement
  - Per-user daily/weekly limits
  - Per-IP connection limits
  - Max test duration
- server_pro/ldap_auth.rs: LDAP/AD authentication via ldap3
  - Simple bind authentication
  - Service account search for user DN

CLI flags: --users-db, --ldap-url, --ldap-base-dn, --ldap-bind-dn,
  --ldap-bind-pass, --daily-quota, --weekly-quota, --max-conn-per-ip,
  --max-duration

Binary sizes: btest=1.8MB, btest-server-pro=3.4MB (SQLite bundled)
Standard btest binary unchanged, 58 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:33:36 +04:00
Siavash Sameni
8c853c3605 Parallel agent work: bandwidth fix, CPU platforms, packaging
All checks were successful
CI / test (push) Successful in 2m8s
5 agents ran in parallel:

1. Fix bandwidth limit (-b): new advance_next_send() prevents drift
   bursts by resetting when >2x interval behind (bandwidth.rs, client.rs, server.rs)

2. Windows + FreeBSD CPU support (cpu.rs):
   - Windows: GetSystemTimes via raw FFI
   - FreeBSD: sysctl kern.cp_time parsing

3. Ubuntu .deb packaging (deploy/deb/):
   - build-deb.sh: creates .deb from pre-built binary
   - test-deb.sh: tests in Ubuntu Docker container

4. Fedora/RHEL RPM packaging (deploy/rpm/):
   - btest-rs.spec: full RPM spec with systemd unit
   - build-rpm.sh + test-rpm.sh

5. Alpine Linux apk packaging (deploy/alpine/):
   - APKBUILD with OpenRC init script
   - test-alpine.sh

58 tests pass, zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:04:00 +04:00
Siavash Sameni
fe28c04c19 Simplify AUR test: use yay like a real user
All checks were successful
CI / test (push) Successful in 2m7s
Instead of manually setting up rust + makepkg, install yay first
then `yay -S btest-rs --noconfirm` — exactly how a user would.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:51:02 +04:00
Siavash Sameni
66be99bef0 Add remote AUR test script
All checks were successful
CI / test (push) Successful in 2m9s
scripts/test-aur-remote.sh: SSHes to a remote x86_64 server, spins up
an Arch Docker container, installs btest-rs from AUR, runs TCP + UDP
loopback tests, and cleans up.

Usage: ./scripts/test-aur-remote.sh root@myserver

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:48:16 +04:00
Siavash Sameni
94b122ac25 Add AUR package (PKGBUILD) with systemd service and test script
All checks were successful
CI / test (push) Successful in 2m11s
- deploy/aur/PKGBUILD: builds from source, installs binary + man page + systemd unit
- deploy/aur/.SRCINFO: AUR metadata
- deploy/aur/test-aur.sh: tests PKGBUILD in Docker Arch container
- Supports x86_64, aarch64, armv7h architectures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:33:55 +04:00
Siavash Sameni
a07158ed22 Fix sync-github-release: merge all checksums into one file
All checks were successful
CI / test (push) Successful in 2m9s
Merges separate .sha256 files (from macOS build) into the main
checksums-sha256.txt, adds missing checksums, deduplicates.
Added macOS to release notes table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:44:18 +04:00
Siavash Sameni
1cd552d2dc Update Docker references to GHCR as primary registry
All checks were successful
CI / test (push) Successful in 2m9s
- docker-compose.yml: ghcr.io/manawenuz/btest-rs
- docs/docker.md: GHCR for pull/run examples, both registries documented
- README: GitHub + Gitea issue tracker links
- Version refs updated to 0.6.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:40:28 +04:00
Siavash Sameni
3af40cb275 Add RPi install docs, GHCR support, push-docker-all script
All checks were successful
CI / test (push) Successful in 2m10s
- README: Raspberry Pi install section with auto-detect architecture
- README: pre-built binary download section for all platforms
- Docker docs: dual registry (Gitea + GHCR)
- scripts/push-docker-all.sh: push to both registries in one command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:29:48 +04:00
Siavash Sameni
f0a48092ed v0.6.0: CPU monitoring, CSV with CPU, docs update, cleanup
Some checks failed
CI / test (push) Failing after 1m27s
Build & Release / release (push) Successful in 3m17s
New in v0.6.0:
- CPU usage: local/remote shown per interval (cpu: 12%/33%)
- Warning indicator (!) when CPU > 70% on either side
- MikroTik CPU encoding: 0x80 | percentage in status byte 1
- CSV includes local_cpu_pct and remote_cpu_pct columns
- Status message format corrected to match MikroTik wire format:
  [type:1][cpu:1][00:2][seq:4 LE][bytes:4 LE]
- Removed btest-opensource submodule (fully reimplemented)
- Deleted research/ecsrp5 branch
- Updated all docs: architecture, user-guide, man page, protocol
- Version bumped to 0.6.0

58 tests, all passing. Zero warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.6.0
2026-04-01 11:16:25 +04:00
Siavash Sameni
27354108fc Fix CPU reporting: MikroTik uses 0x80|pct encoding, add CPU to CSV
All checks were successful
CI / test (push) Successful in 2m9s
- MikroTik encodes CPU as 0x80 | percentage (high bit flag)
- Deserialize: mask with 0x7F and cap at 100
- Serialize: set high bit (0x80 | cpu) to match MikroTik format
- CSV now includes local_cpu_pct and remote_cpu_pct columns
- Both client and server write CPU to CSV

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:08:11 +04:00
Siavash Sameni
24f634170d Add CPU usage monitoring, remove btest-opensource submodule
All checks were successful
CI / test (push) Successful in 2m16s
CPU usage feature:
- New cpu.rs module: background sampler thread, cross-platform (macOS + Linux)
- Status message byte 1 now carries CPU load (0-100%), matching MikroTik format
- Status format corrected: [type][cpu][00][00][seq:4 LE][bytes:4 LE]
- Client and server exchange CPU in every status message
- Display format: "cpu: 40%/12%" (local/remote), "!" warning if > 70%
- Both client and server show local + remote CPU per interval
- Syslog TEST_END could include CPU averages (future enhancement)

Removed btest-opensource submodule — we've fully reimplemented the protocol
with EC-SRP5 auth, multi-connection, IPv6, syslog, CSV, and CPU monitoring.
The original project is still credited in LICENSE and README.

58 tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:53:00 +04:00
Siavash Sameni
10dd0c3835 Add KNOWN_ISSUES.md, update architecture docs
Some checks failed
CI / test (push) Failing after 1m32s
KNOWN_ISSUES.md documents:
- IPv6 UDP on macOS (ENOBUFS, server mode)
- macOS UDP send buffer saturation (first 2-3 seconds)
- Windows binaries untested
- IPv6 UDP on Linux untested
- EC-SRP5 occasional auth failure
- MikroTik speed adaptation staircase
- TCP multi-connection bandwidth reporting
- Bandwidth limit (-b) not fully effective
- Platform test matrix

Architecture docs updated with:
- Shared BandwidthState for timeout survival
- IPv6 socket handling details
- Complete file layout including tests, deploy, proto-test

54 tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:39:18 +04:00
Siavash Sameni
5bb224cb3b Remove test CSV from repo, add to gitignore
Some checks failed
CI / test (push) Failing after 1m31s
2026-04-01 10:30:56 +04:00
Siavash Sameni
68eb0c7f96 Add comprehensive integration tests (20 new tests, 54 total)
Some checks failed
CI / test (push) Has been cancelled
New test suite covers:
- TCP IPv4: send, receive, both
- UDP IPv4: send, receive, both
- TCP IPv6: send, receive, both
- UDP IPv6: send, receive, both
- MD5 authentication flow
- EC-SRP5 authentication flow
- EC-SRP5 wrong password rejection
- CSV file creation (client + server)
- Syslog event emission (AUTH_SUCCESS, TEST_START, TEST_END)
- BandwidthState record_interval and running flag

Each test starts a server, runs a client for 2 seconds, verifies
bytes transferred > 0, then cleans up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:30:37 +04:00
Siavash Sameni
23db39a84e Add CSV output to server mode
All checks were successful
CI / test (push) Successful in 1m26s
Server now writes a CSV row for each completed test with peer IP,
protocol, direction, duration, avg speeds, bytes, and lost packets.

Verified on loopback: TX 35 Gbps, RX 51 Gbps captured in CSV.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:19:15 +04:00
Siavash Sameni
d19ad25a3c Fix client stats: shared BandwidthState survives timeout cancellation
All checks were successful
CI / test (push) Successful in 1m24s
The state is now created in main.rs and passed into run_client, so
when --duration timeout cancels the future, the stats are still
accessible via shared_state.summary(). CSV and syslog now show
real speeds and byte counts.

Verified: TCP loopback shows 32 Gbps in CSV output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:05:50 +04:00
Siavash Sameni
5b07a079fe Fix client CSV/syslog: return actual stats from run_client
All checks were successful
CI / test (push) Successful in 1m23s
run_client and sub-functions now return (tx_bytes, rx_bytes, lost, intervals).
BandwidthState::record_interval() called in both TCP and UDP client status
loops. CSV and syslog TEST_END now show real speeds and byte counts.

Also raised client UDP TX error threshold from 1000 to 50000 with
adaptive backoff matching the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:52:46 +04:00
Siavash Sameni
949c4908ad Add client syslog events, fix client UDP TX error threshold
All checks were successful
CI / test (push) Successful in 1m26s
- Client mode now emits TEST_START and TEST_END syslog events
- Client UDP TX threshold raised from 1000 to 50000 with adaptive backoff
  (matching server behavior) — prevents premature TX death on macOS
- Updated all docs (README, user-guide, architecture, protocol, docker)
- Added results.csv to gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:40:52 +04:00
Siavash Sameni
751a9d5f13 Add --duration, --csv, --quiet flags for automated testing
All checks were successful
CI / test (push) Successful in 1m27s
- --duration N: run client test for N seconds then exit
- --csv <file>: append results to CSV (creates with headers if new)
- --quiet/-q: suppress terminal output (for scripted/machine use)

CSV columns: timestamp, host, port, protocol, direction, duration_s,
  tx_avg_mbps, rx_avg_mbps, tx_bytes, rx_bytes, lost_packets, auth_type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:30:58 +04:00
Siavash Sameni
ce01d514b2 Add speed/bytes/duration to syslog TEST_END events
All checks were successful
CI / test (push) Successful in 1m24s
TEST_END now includes: duration, avg TX/RX Mbps, total bytes, lost packets.
All test functions track cumulative totals via BandwidthState::record_interval()
and return summary stats.

Example:
  TEST_END peer=172.16.81.1:59070 proto=UDP dir=TX duration=6s
    tx_avg=275.00Mbps rx_avg=0.00Mbps tx_bytes=206250000 rx_bytes=0 lost=0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 09:23:11 +04:00
Siavash Sameni
7bc54a977c Fix syslog-ng filter: match on MESSAGE not program()
All checks were successful
CI / test (push) Successful in 1m29s
With flags(no-parse) on the source, syslog-ng doesn't extract
the program name. Use match("btest-rs:" value("MESSAGE")) instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 08:56:48 +04:00
Siavash Sameni
a925a7778d Fix syslog format + add syslog-ng config
All checks were successful
CI / test (push) Successful in 1m30s
- Syslog now uses RFC 3164 (BSD) format with proper timestamps
  and facility=local0 for easy filtering
- Added deploy/syslog-ng-btest.conf with filters for:
  - All btest events (all.log + daily rotation)
  - Auth events only (auth.log)
  - Test events only (tests.log)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 08:48:35 +04:00
Siavash Sameni
a28fc1dc08 v0.5.0: IPv6 off by default, mark as experimental
All checks were successful
CI / test (push) Successful in 1m25s
Build & Release / release (push) Successful in 3m0s
IPv6 listener now requires explicit --listen6 flag (disabled by default).
TCP over IPv6 works fully. UDP over IPv6 has macOS kernel limitations
(ENOBUFS on send_to). On Linux, IPv6 UDP works fine.

Usage:
  btest -s                    # IPv4 only (default)
  btest -s --listen6          # IPv4 + IPv6 on ::
  btest -s --listen6 ::1      # IPv4 + IPv6 on specific address

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.5.0
2026-03-31 20:54:53 +04:00
Siavash Sameni
29643e7589 Revert: always report rx_bytes in UDP status, not tx_bytes
All checks were successful
CI / test (push) Successful in 1m27s
Reporting tx_bytes in TX-only mode caused MikroTik to show speed on
the wrong side (Tx instead of Rx). MikroTik tracks its own Rx by
counting UDP arrivals — the status bytes_received is for the OTHER
direction (how much we received from the client).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:52:14 +04:00
Siavash Sameni
0c14e6cf5b Fix UDP TX-only status: report tx_bytes instead of rx_bytes
All checks were successful
CI / test (push) Successful in 1m26s
Build & Release / release (push) Successful in 3m8s
In TX-only mode (MikroTik receives), we sent rx_bytes=0 in status
because we weren't receiving anything. But MikroTik client needs
to see non-zero bytes in the status to know data is flowing.
Now report tx_bytes when in TX-only mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:39:28 +04:00
Siavash Sameni
b8fa6d4580 Fix IPv6 UDP server TX: use connected socket for single-connection
All checks were successful
CI / test (push) Successful in 1m27s
pcap analysis proved: connected send() achieves 462k pps on IPv6,
while unconnected send_to() hits ENOBUFS at 5k pps then stalls.

Reverted the "always unconnected for IPv6" workaround. Now only
multi-connection mode uses unconnected sockets. Single-connection
always connects, which works for both IPv4 and IPv6 TX and RX.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:28:42 +04:00
Siavash Sameni
6288fe9f25 Fix IPv6 UDP TX: reset consecutive_errors after yield, pace every 16 pkts
All checks were successful
CI / test (push) Successful in 1m27s
ENOBUFS hits every send on macOS IPv6 because the interface output queue
is full. The adaptive backoff never recovered because consecutive_errors
never reset. Now reset after sleeping, and yield more frequently (every
16 packets instead of 64).

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