fix: harden tcp bridge reconnect handling

This commit is contained in:
2026-04-03 05:57:52 +08:00
parent fd1fae8ad7
commit 9fd748c512
5 changed files with 247 additions and 10 deletions
+33 -2
View File
@@ -7,6 +7,8 @@
#include "main.h" #include "main.h"
#include "SEGGER_RTT.h"
#include "lwip/ip_addr.h" #include "lwip/ip_addr.h"
#include "lwip/pbuf.h" #include "lwip/pbuf.h"
#include "lwip/tcp.h" #include "lwip/tcp.h"
@@ -85,11 +87,15 @@ static err_t tcp_client_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
static void tcp_client_on_err(void *arg, err_t err) static void tcp_client_on_err(void *arg, err_t err)
{ {
tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg; tcp_client_ctx_t *ctx = (tcp_client_ctx_t *)arg;
(void)err; if (ctx == NULL) {
return;
}
ctx->pcb = NULL; ctx->pcb = NULL;
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
ctx->status.errors++; ctx->status.errors++;
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms; ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
SEGGER_RTT_printf(0, "TCP client error=%d, reconnect scheduled\r\n", (int)err);
} }
static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err) static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
@@ -101,11 +107,17 @@ static err_t tcp_client_on_connected(void *arg, struct tcp_pcb *pcb, err_t err)
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
ctx->status.errors++; ctx->status.errors++;
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms; ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
SEGGER_RTT_printf(0, "TCP client connect callback failed err=%d\r\n", (int)err);
return err; return err;
} }
ctx->pcb = pcb; ctx->pcb = pcb;
ctx->status.state = TCP_CLIENT_STATE_CONNECTED; ctx->status.state = TCP_CLIENT_STATE_CONNECTED;
SEGGER_RTT_printf(0,
"TCP client connected to %u.%u.%u.%u:%u\r\n",
ctx->config.server_ip[0], ctx->config.server_ip[1],
ctx->config.server_ip[2], ctx->config.server_ip[3],
ctx->config.server_port);
tcp_nagle_disable(pcb); tcp_nagle_disable(pcb);
tcp_arg(pcb, ctx); tcp_arg(pcb, ctx);
tcp_recv(pcb, tcp_client_on_recv); tcp_recv(pcb, tcp_client_on_recv);
@@ -147,6 +159,8 @@ int tcp_client_connect(void)
pcb = tcp_new_ip_type(IPADDR_TYPE_V4); pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
if (pcb == NULL) { if (pcb == NULL) {
g_client.status.errors++; g_client.status.errors++;
g_client.status.state = TCP_CLIENT_STATE_ERROR;
SEGGER_RTT_WriteString(0, "TCP client connect failed: no PCB\r\n");
return -1; return -1;
} }
@@ -157,6 +171,7 @@ int tcp_client_connect(void)
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED;
g_client.status.errors++; g_client.status.errors++;
g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms; g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms;
SEGGER_RTT_printf(0, "TCP client bind failed err=%d\r\n", (int)err);
return -1; return -1;
} }
} }
@@ -169,16 +184,24 @@ int tcp_client_connect(void)
g_client.status.state = TCP_CLIENT_STATE_CONNECTING; g_client.status.state = TCP_CLIENT_STATE_CONNECTING;
tcp_arg(pcb, &g_client); tcp_arg(pcb, &g_client);
tcp_err(pcb, tcp_client_on_err);
err = tcp_connect(pcb, &remote_addr, g_client.config.server_port, tcp_client_on_connected); err = tcp_connect(pcb, &remote_addr, g_client.config.server_port, tcp_client_on_connected);
if (err != ERR_OK) { if (err != ERR_OK) {
tcp_err(pcb, NULL);
tcp_abort(pcb); tcp_abort(pcb);
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED;
g_client.status.errors++; g_client.status.errors++;
g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms; g_client.next_retry_ms = HAL_GetTick() + g_client.config.reconnect_interval_ms;
SEGGER_RTT_printf(0, "TCP client connect start failed err=%d\r\n", (int)err);
return -1; return -1;
} }
g_client.pcb = pcb; g_client.pcb = pcb;
SEGGER_RTT_printf(0,
"TCP client connecting to %u.%u.%u.%u:%u\r\n",
g_client.config.server_ip[0], g_client.config.server_ip[1],
g_client.config.server_ip[2], g_client.config.server_ip[3],
g_client.config.server_port);
return 0; return 0;
} }
@@ -194,6 +217,7 @@ int tcp_client_disconnect(void)
} }
g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED; g_client.status.state = TCP_CLIENT_STATE_DISCONNECTED;
SEGGER_RTT_WriteString(0, "TCP client disconnected\r\n");
return 0; return 0;
} }
@@ -288,7 +312,11 @@ void tcp_client_poll(void)
{ {
uint32_t now; uint32_t now;
if (!g_client.config.auto_reconnect || g_client.pcb != NULL) { if (!g_client.config.auto_reconnect || tcp_client_is_connected()) {
return;
}
if ((g_client.pcb != NULL) && (g_client.status.state == TCP_CLIENT_STATE_CONNECTING)) {
return; return;
} }
@@ -296,6 +324,9 @@ void tcp_client_poll(void)
if (now >= g_client.next_retry_ms) { if (now >= g_client.next_retry_ms) {
g_client.status.reconnect_count++; g_client.status.reconnect_count++;
g_client.next_retry_ms = now + g_client.config.reconnect_interval_ms; g_client.next_retry_ms = now + g_client.config.reconnect_interval_ms;
SEGGER_RTT_printf(0,
"TCP client reconnect attempt %lu\r\n",
g_client.status.reconnect_count);
(void)tcp_client_connect(); (void)tcp_client_connect();
} }
} }
+6
View File
@@ -8,6 +8,8 @@
#include "lwip/pbuf.h" #include "lwip/pbuf.h"
#include "lwip/tcp.h" #include "lwip/tcp.h"
#include "SEGGER_RTT.h"
#include <string.h> #include <string.h>
typedef struct { typedef struct {
@@ -49,6 +51,7 @@ static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
} }
ctx->client_pcb = NULL; ctx->client_pcb = NULL;
ctx->status.state = TCP_SERVER_STATE_LISTENING; ctx->status.state = TCP_SERVER_STATE_LISTENING;
SEGGER_RTT_WriteString(0, "TCP server peer disconnected\r\n");
return ERR_OK; return ERR_OK;
} }
@@ -85,6 +88,7 @@ static void tcp_server_on_err(void *arg, err_t err)
ctx->client_pcb = NULL; ctx->client_pcb = NULL;
ctx->status.state = TCP_SERVER_STATE_LISTENING; ctx->status.state = TCP_SERVER_STATE_LISTENING;
ctx->status.errors++; ctx->status.errors++;
SEGGER_RTT_printf(0, "TCP server connection error=%d\r\n", (int)err);
} }
static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err) static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
@@ -103,6 +107,7 @@ static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
ctx->client_pcb = newpcb; ctx->client_pcb = newpcb;
ctx->status.state = TCP_SERVER_STATE_CONNECTED; ctx->status.state = TCP_SERVER_STATE_CONNECTED;
ctx->status.connections++; ctx->status.connections++;
SEGGER_RTT_WriteString(0, "TCP server client connected\r\n");
tcp_nagle_disable(newpcb); tcp_nagle_disable(newpcb);
@@ -158,6 +163,7 @@ int tcp_server_start(void)
tcp_arg(g_server.listen_pcb, &g_server); tcp_arg(g_server.listen_pcb, &g_server);
tcp_accept(g_server.listen_pcb, tcp_server_on_accept); tcp_accept(g_server.listen_pcb, tcp_server_on_accept);
g_server.status.state = TCP_SERVER_STATE_LISTENING; g_server.status.state = TCP_SERVER_STATE_LISTENING;
SEGGER_RTT_printf(0, "TCP server listening on %u\r\n", g_server.config.port);
return 0; return 0;
} }
+33 -8
View File
@@ -68,19 +68,44 @@ static volatile uint16_t g_led_blink_ticks = 0;
static uint8_t g_clock_fallback_to_hsi = 0u; static uint8_t g_clock_fallback_to_hsi = 0u;
volatile uint8_t g_uart1_rx_probe_byte = 0u; volatile uint8_t g_uart1_rx_probe_byte = 0u;
typedef struct {
uint8_t data[256];
uint16_t len;
} tcp_bridge_buffer_t;
static tcp_bridge_buffer_t g_server_to_client;
static tcp_bridge_buffer_t g_client_to_server;
static void App_ForwardTcpPair(void) static void App_ForwardTcpPair(void)
{ {
uint8_t buffer[256]; int rc;
int len;
len = tcp_server_recv(buffer, sizeof(buffer), 0u); if ((g_server_to_client.len == 0u) && tcp_server_is_connected()) {
if (len > 0) { rc = tcp_server_recv(g_server_to_client.data, sizeof(g_server_to_client.data), 0u);
(void)tcp_client_send(buffer, (uint16_t)len); if (rc > 0) {
g_server_to_client.len = (uint16_t)rc;
}
} }
len = tcp_client_recv(buffer, sizeof(buffer), 0u); if ((g_server_to_client.len != 0u) && tcp_client_is_connected()) {
if (len > 0) { rc = tcp_client_send(g_server_to_client.data, g_server_to_client.len);
(void)tcp_server_send(buffer, (uint16_t)len); if (rc == (int)g_server_to_client.len) {
g_server_to_client.len = 0u;
}
}
if ((g_client_to_server.len == 0u) && tcp_client_is_connected()) {
rc = tcp_client_recv(g_client_to_server.data, sizeof(g_client_to_server.data), 0u);
if (rc > 0) {
g_client_to_server.len = (uint16_t)rc;
}
}
if ((g_client_to_server.len != 0u) && tcp_server_is_connected()) {
rc = tcp_server_send(g_client_to_server.data, g_client_to_server.len);
if (rc == (int)g_client_to_server.len) {
g_client_to_server.len = 0u;
}
} }
} }
+32
View File
@@ -0,0 +1,32 @@
param(
[string]$BindHost = "0.0.0.0",
[int]$Port = 8081,
[switch]$Echo,
[switch]$NoStdin
)
$listeners = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
if ($listeners) {
$pids = $listeners | Select-Object -ExpandProperty OwningProcess -Unique
Write-Host "Stopping existing listeners on TCP ${Port}: $($pids -join ', ')"
foreach ($procId in $pids) {
try {
Stop-Process -Id $procId -Force -ErrorAction Stop
}
catch {
Write-Warning "Failed to stop process $procId : $_"
}
}
Start-Sleep -Milliseconds 300
}
$args = @("tools/tcp_debug_server.py", "--host", $BindHost, "--port", "$Port")
if ($Echo) {
$args += "--echo"
}
if ($NoStdin) {
$args += "--no-stdin"
}
Write-Host "Starting TCP debug server on ${BindHost}:${Port}"
python @args
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Simple TCP debug server for validating raw payload forwarding.
Usage examples:
python tools/tcp_debug_server.py --host 0.0.0.0 --port 8081
python tools/tcp_debug_server.py --port 8081 --echo
Features:
- Listen as a plain TCP server
- Print connect/disconnect events
- Print received payload as text and hex
- Optional echo mode
- Optional stdin -> socket sender for manual testing
"""
from __future__ import annotations
import argparse
import select
import socket
import sys
import threading
from datetime import datetime
def ts() -> str:
return datetime.now().strftime("%H:%M:%S.%f")[:-3]
def hex_bytes(data: bytes) -> str:
return " ".join(f"{b:02X}" for b in data)
def text_view(data: bytes) -> str:
return "".join(chr(b) if 32 <= b < 127 else "." for b in data)
def sender_loop(conn: socket.socket, stop_event: threading.Event) -> None:
print(f"[{ts()}] stdin sender ready. Type text and press Enter to send.")
while not stop_event.is_set():
line = sys.stdin.readline()
if line == "":
stop_event.set()
break
payload = line.encode("utf-8", errors="replace")
try:
conn.sendall(payload)
print(
f"[{ts()}] TX {len(payload)} bytes | text={payload!r} | hex={hex_bytes(payload)}"
)
except OSError as exc:
print(f"[{ts()}] TX failed: {exc}")
stop_event.set()
break
def serve(host: str, port: int, echo: bool, no_stdin: bool) -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host, port))
server.listen(1)
print(f"[{ts()}] Listening on {host}:{port}")
conn, addr = server.accept()
with conn:
print(f"[{ts()}] Client connected from {addr[0]}:{addr[1]}")
stop_event = threading.Event()
sender_thread = None
if not no_stdin:
sender_thread = threading.Thread(
target=sender_loop, args=(conn, stop_event), daemon=True
)
sender_thread.start()
conn.setblocking(False)
while not stop_event.is_set():
ready, _, _ = select.select([conn], [], [], 0.2)
if not ready:
continue
try:
data = conn.recv(4096)
except BlockingIOError:
continue
except OSError as exc:
print(f"[{ts()}] RX failed: {exc}")
break
if not data:
print(f"[{ts()}] Client disconnected")
break
print(
f"[{ts()}] RX {len(data)} bytes | text={text_view(data)} | hex={hex_bytes(data)}"
)
if echo:
try:
conn.sendall(data)
print(f"[{ts()}] ECHO {len(data)} bytes")
except OSError as exc:
print(f"[{ts()}] Echo failed: {exc}")
break
stop_event.set()
if sender_thread is not None:
sender_thread.join(timeout=0.5)
return 0
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Minimal raw TCP debug server")
parser.add_argument(
"--host", default="0.0.0.0", help="Listen host, default: 0.0.0.0"
)
parser.add_argument(
"--port", type=int, default=8081, help="Listen port, default: 8081"
)
parser.add_argument(
"--echo", action="store_true", help="Echo received payload back to client"
)
parser.add_argument(
"--no-stdin",
action="store_true",
help="Disable stdin sender thread (receive-only mode)",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
return serve(args.host, args.port, args.echo, args.no_stdin)
except KeyboardInterrupt:
print(f"\n[{ts()}] Stopped by user")
return 0
if __name__ == "__main__":
raise SystemExit(main())