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>
This commit is contained in:
Siavash Sameni
2026-03-31 17:39:12 +04:00
parent 9552cbef1a
commit d8f3b9c189
3 changed files with 147 additions and 4 deletions

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""
Full MITM proxy for btest - forwards TCP control + UDP data.
Captures and logs ALL traffic between MikroTik client and MikroTik server.
Usage:
python3 btest_mitm_full.py --target 172.16.81.1
Then on MikroTik:
/tool/bandwidth-test address=<this_mac_ip> direction=receive protocol=tcp \
user=antar password=antar connection-count=1
"""
import socket
import select
import sys
import argparse
import time
import threading
import struct
def ts():
return time.strftime("%H:%M:%S", time.localtime()) + f".{int(time.time()*1000)%1000:03d}"
def hexline(data, offset=0, max_bytes=16):
chunk = data[offset:offset+max_bytes]
hex_part = " ".join(f"{b:02x}" for b in chunk)
ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
return f" {offset:04x} {hex_part:<48s} {ascii_part}"
def log_data(direction, data, conn_id=""):
label = f"[{ts()}] {direction}"
if conn_id:
label += f" [{conn_id}]"
label += f" ({len(data)} bytes)"
print(label)
# Show first 4 lines of hex
for i in range(0, min(len(data), 64), 16):
print(hexline(data, i))
if len(data) > 64:
print(f" ... ({len(data)} total)")
# Try to annotate
if len(data) == 4:
val = data.hex()
annotations = {
"01000000": "HELLO / AUTH_OK",
"02000000": "AUTH_REQUIRED (MD5)",
"03000000": "AUTH_REQUIRED (EC-SRP5)",
"00000000": "AUTH_FAILED",
}
if val in annotations:
print(f" >>> {annotations[val]}")
if len(data) == 12 and data[0] == 0x07:
# Status message
seq = int.from_bytes(data[1:5], "big")
recv_bytes = int.from_bytes(data[8:12], "little")
mbps = recv_bytes * 8 / 1_000_000
print(f" >>> STATUS: seq={seq} bytes_received={recv_bytes} ({mbps:.2f} Mbps)")
if len(data) == 16:
proto = "UDP" if data[0] == 0 else "TCP"
dirs = {1: "RX", 2: "TX", 3: "BOTH"}
d = dirs.get(data[1], f"0x{data[1]:02x}")
conn = data[3]
print(f" >>> COMMAND: proto={proto} dir={d} conn_count={conn}")
sys.stdout.flush()
def proxy_tcp(client_sock, target_host, target_port, conn_id):
"""Proxy a single TCP connection."""
try:
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.settimeout(30)
server_sock.connect((target_host, target_port))
server_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
except Exception as e:
print(f"[{conn_id}] Failed to connect to target: {e}")
client_sock.close()
return
try:
while True:
readable, _, _ = select.select([client_sock, server_sock], [], [], 30)
if not readable:
break
for sock in readable:
if sock is server_sock:
data = server_sock.recv(65536)
if not data:
return
log_data("SERVER→CLIENT", data, conn_id)
client_sock.sendall(data)
elif sock is client_sock:
data = client_sock.recv(65536)
if not data:
return
log_data("CLIENT→SERVER", data, conn_id)
server_sock.sendall(data)
except Exception as e:
print(f"[{conn_id}] Error: {e}")
finally:
client_sock.close()
server_sock.close()
print(f"[{conn_id}] Closed")
def main():
parser = argparse.ArgumentParser(description="btest full MITM proxy")
parser.add_argument("-t", "--target", required=True, help="Target MikroTik IP")
parser.add_argument("-l", "--listen", type=int, default=2000, help="Listen port")
args = parser.parse_args()
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(("0.0.0.0", args.listen))
listener.listen(50)
print(f"MITM proxy: 0.0.0.0:{args.listen}{args.target}:2000")
print(f"Point MikroTik btest client at this machine")
print()
conn_num = 0
while True:
client_sock, client_addr = listener.accept()
client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
conn_num += 1
conn_id = f"TCP-{conn_num} {client_addr[0]}:{client_addr[1]}"
print(f"\n{'='*60}")
print(f"[{ts()}] New connection: {conn_id}")
t = threading.Thread(
target=proxy_tcp,
args=(client_sock, args.target, 2000, conn_id),
daemon=True,
)
t.start()
if __name__ == "__main__":
main()

View File

@@ -148,8 +148,7 @@ async fn tcp_client_tx_loop(
) {
tokio::time::sleep(Duration::from_millis(100)).await;
let mut packet = vec![0u8; tx_size];
packet[0] = STATUS_MSG_TYPE;
let packet = vec![0u8; tx_size]; // TCP data is all zeros
let mut interval = bandwidth::calc_send_interval(tx_speed, tx_size as u16);
let mut next_send = Instant::now();

View File

@@ -389,8 +389,7 @@ async fn tcp_tx_loop(
) {
tokio::time::sleep(Duration::from_millis(100)).await;
let mut packet = vec![0u8; tx_size];
packet[0] = STATUS_MSG_TYPE;
let packet = vec![0u8; tx_size]; // TCP data is all zeros (no 0x07 header)
let mut interval = bandwidth::calc_send_interval(tx_speed, tx_size as u16);
let mut next_send = Instant::now();