fix: harden tcp bridge reconnect handling
This commit is contained in:
+33
-2
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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())
|
||||||
Reference in New Issue
Block a user