fix(tcp): MUX模式网口失联 — 对端关闭时用tcp_abort替代tcp_close避免TIME_WAIT耗尽pcb池
根因: tcp_close()将对端关闭的pcb推入TIME_WAIT(120s), 占用MEMP_TCP_PCB池(仅4个), 多连接同时断开后pcb池耗尽, tcp_new()返回NULL, 新连接无法建立直到120s超时释放。 核心修复: - tcp_server/client: 对端关闭(p=NULL)时tcp_abort替代tcp_close, pcb立即释放 - ch390_runtime: PKT_ERR恢复强制OR上RCR_RXEN(与WCH官方一致) - ch390_runtime: TX连续超时3次自动emergency reset - ch390_runtime: 每5秒health_check读VID验证芯片存活 - main: App_StartLinksIfNeeded失败时不标记g_links_started, 允许重试 - main: MUX逐帧RTT printf改为#if DEBUG门控, 减少主循环延迟 - uart_trans: MUX帧解析改为先搜0x7E再消费header, 非法帧只丢1字节
This commit is contained in:
+1
-3
@@ -52,13 +52,11 @@ static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
|
|||||||
tcp_recv(pcb, NULL);
|
tcp_recv(pcb, NULL);
|
||||||
tcp_sent(pcb, NULL);
|
tcp_sent(pcb, NULL);
|
||||||
tcp_err(pcb, NULL);
|
tcp_err(pcb, NULL);
|
||||||
if (tcp_close(pcb) != ERR_OK) {
|
|
||||||
tcp_abort(pcb);
|
tcp_abort(pcb);
|
||||||
}
|
|
||||||
ctx->pcb = NULL;
|
ctx->pcb = NULL;
|
||||||
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED;
|
||||||
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms;
|
||||||
return ERR_OK;
|
return ERR_ABRT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (q = p; q != NULL; q = q->next) {
|
for (q = p; q != NULL; q = q->next) {
|
||||||
|
|||||||
+4
-4
@@ -52,12 +52,10 @@ static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
|
|||||||
tcp_recv(pcb, NULL);
|
tcp_recv(pcb, NULL);
|
||||||
tcp_sent(pcb, NULL);
|
tcp_sent(pcb, NULL);
|
||||||
tcp_err(pcb, NULL);
|
tcp_err(pcb, NULL);
|
||||||
if (tcp_close(pcb) != ERR_OK) {
|
|
||||||
tcp_abort(pcb);
|
tcp_abort(pcb);
|
||||||
}
|
|
||||||
ctx->client_pcb = NULL;
|
ctx->client_pcb = NULL;
|
||||||
ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
|
ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE;
|
||||||
return ERR_OK;
|
return ERR_ABRT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (q = p; q != NULL; q = q->next) {
|
for (q = p; q != NULL; q = q->next) {
|
||||||
@@ -205,7 +203,9 @@ int tcp_server_stop(uint8_t instance)
|
|||||||
if (ctx->listen_pcb != NULL) {
|
if (ctx->listen_pcb != NULL) {
|
||||||
tcp_arg(ctx->listen_pcb, NULL);
|
tcp_arg(ctx->listen_pcb, NULL);
|
||||||
tcp_accept(ctx->listen_pcb, NULL);
|
tcp_accept(ctx->listen_pcb, NULL);
|
||||||
tcp_close(ctx->listen_pcb);
|
if (tcp_close(ctx->listen_pcb) != ERR_OK) {
|
||||||
|
tcp_abort(ctx->listen_pcb);
|
||||||
|
}
|
||||||
ctx->listen_pcb = NULL;
|
ctx->listen_pcb = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-6
@@ -297,7 +297,8 @@ void uart_trans_tx_cplt_handler(uart_channel_t channel)
|
|||||||
|
|
||||||
bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
|
bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
|
||||||
{
|
{
|
||||||
uint8_t header[5];
|
uint8_t sync_byte;
|
||||||
|
uint8_t header[4];
|
||||||
uint16_t available;
|
uint16_t available;
|
||||||
uint16_t payload_len;
|
uint16_t payload_len;
|
||||||
|
|
||||||
@@ -310,14 +311,25 @@ bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uart_trans_read(channel, header, sizeof(header)) != sizeof(header)) {
|
/* Scan for SYNC byte (0x7E) — discard non-matching bytes one at a time */
|
||||||
|
if (uart_trans_read(channel, &sync_byte, 1u) != 1u) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (header[0] != UART_MUX_SYNC) {
|
if (sync_byte != UART_MUX_SYNC) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
payload_len = (uint16_t)(((uint16_t)header[1] << 8) | header[2]);
|
/* Need at least: 2(len) + 1(src) + 1(dst) + payload + 1(tail) = 5 + payload */
|
||||||
|
available = uart_trans_rx_available(channel);
|
||||||
|
if (available < 4u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uart_trans_read(channel, header, sizeof(header)) != sizeof(header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload_len = (uint16_t)(((uint16_t)header[0] << 8) | header[1]);
|
||||||
if (payload_len > sizeof(frame->payload)) {
|
if (payload_len > sizeof(frame->payload)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -325,8 +337,8 @@ bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame->src_id = header[3];
|
frame->src_id = header[2];
|
||||||
frame->dst_mask = header[4];
|
frame->dst_mask = header[3];
|
||||||
frame->payload_len = payload_len;
|
frame->payload_len = payload_len;
|
||||||
if (payload_len > 0u) {
|
if (payload_len > 0u) {
|
||||||
if (uart_trans_read(channel, frame->payload, payload_len) != payload_len) {
|
if (uart_trans_read(channel, frame->payload, payload_len) != payload_len) {
|
||||||
|
|||||||
+28
-2
@@ -39,6 +39,7 @@
|
|||||||
#define LED_PORT GPIOC
|
#define LED_PORT GPIOC
|
||||||
#define APP_ROUTE_BUFFER_SIZE 256u
|
#define APP_ROUTE_BUFFER_SIZE 256u
|
||||||
#define STACK_GUARD_WORD 0xA5A5A5A5u
|
#define STACK_GUARD_WORD 0xA5A5A5A5u
|
||||||
|
#define APP_HEALTH_CHECK_INTERVAL_MS 5000u
|
||||||
/* USER CODE END PD */
|
/* USER CODE END PD */
|
||||||
|
|
||||||
/* Private variables ---------------------------------------------------------*/
|
/* Private variables ---------------------------------------------------------*/
|
||||||
@@ -175,15 +176,27 @@ static void App_ConfigureLinks(const device_config_t *cfg)
|
|||||||
|
|
||||||
static void App_StartLinksIfNeeded(void)
|
static void App_StartLinksIfNeeded(void)
|
||||||
{
|
{
|
||||||
|
int any_failed;
|
||||||
|
|
||||||
if ((g_links_started != 0u) || !netif_is_link_up(&ch390_netif)) {
|
if ((g_links_started != 0u) || !netif_is_link_up(&ch390_netif)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
any_failed = 0;
|
||||||
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
|
for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) {
|
||||||
(void)tcp_server_start(i);
|
if (tcp_server_start(i) != 0) {
|
||||||
|
any_failed = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) {
|
||||||
(void)tcp_client_connect(i);
|
if (tcp_client_connect(i) != 0) {
|
||||||
|
any_failed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any_failed) {
|
||||||
|
SEGGER_RTT_WriteString(0, "NET links start partially failed, will retry\r\n");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_links_started = 1u;
|
g_links_started = 1u;
|
||||||
@@ -343,7 +356,9 @@ static void App_RouteMuxUartTraffic(void)
|
|||||||
const device_config_t *cfg = config_get();
|
const device_config_t *cfg = config_get();
|
||||||
|
|
||||||
while (uart_mux_try_extract_frame(UART_CHANNEL_U0, &frame)) {
|
while (uart_mux_try_extract_frame(UART_CHANNEL_U0, &frame)) {
|
||||||
|
#if defined(DEBUG) && (DEBUG != 0)
|
||||||
SEGGER_RTT_printf(0, "Mux frame from UART0: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
|
SEGGER_RTT_printf(0, "Mux frame from UART0: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
|
||||||
|
#endif
|
||||||
if (frame.dst_mask == 0u) {
|
if (frame.dst_mask == 0u) {
|
||||||
at_result_t result;
|
at_result_t result;
|
||||||
char *response_text = (char *)&g_mux_response_frame[5];
|
char *response_text = (char *)&g_mux_response_frame[5];
|
||||||
@@ -382,7 +397,9 @@ static void App_RouteMuxUartTraffic(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (uart_mux_try_extract_frame(UART_CHANNEL_U1, &frame)) {
|
while (uart_mux_try_extract_frame(UART_CHANNEL_U1, &frame)) {
|
||||||
|
#if defined(DEBUG) && (DEBUG != 0)
|
||||||
SEGGER_RTT_printf(0, "Mux frame from UART1: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
|
SEGGER_RTT_printf(0, "Mux frame from UART1: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len);
|
||||||
|
#endif
|
||||||
if (frame.dst_mask == 0u) {
|
if (frame.dst_mask == 0u) {
|
||||||
at_result_t result;
|
at_result_t result;
|
||||||
char *response_text = (char *)&g_mux_response_frame[5];
|
char *response_text = (char *)&g_mux_response_frame[5];
|
||||||
@@ -423,6 +440,9 @@ static void App_RouteMuxUartTraffic(void)
|
|||||||
|
|
||||||
static void App_Poll(void)
|
static void App_Poll(void)
|
||||||
{
|
{
|
||||||
|
static uint32_t s_health_check_tick;
|
||||||
|
uint32_t now;
|
||||||
|
|
||||||
ethernetif_poll();
|
ethernetif_poll();
|
||||||
ethernetif_check_link();
|
ethernetif_check_link();
|
||||||
sys_check_timeouts();
|
sys_check_timeouts();
|
||||||
@@ -440,6 +460,12 @@ static void App_Poll(void)
|
|||||||
App_RouteRawUartTraffic();
|
App_RouteRawUartTraffic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now = HAL_GetTick();
|
||||||
|
if ((now - s_health_check_tick) >= APP_HEALTH_CHECK_INTERVAL_MS) {
|
||||||
|
s_health_check_tick = now;
|
||||||
|
ch390_runtime_health_check(&ch390_netif);
|
||||||
|
}
|
||||||
|
|
||||||
if (config_is_reset_requested()) {
|
if (config_is_reset_requested()) {
|
||||||
config_clear_reset_requested();
|
config_clear_reset_requested();
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ static uint8_t ch390_runtime_drain_rx(struct netif *netif, uint8_t max_frames)
|
|||||||
static volatile uint8_t g_ch390_irq_pending;
|
static volatile uint8_t g_ch390_irq_pending;
|
||||||
static uint8_t g_ch390_ready;
|
static uint8_t g_ch390_ready;
|
||||||
static ch390_diag_t g_diag;
|
static ch390_diag_t g_diag;
|
||||||
|
static uint8_t g_tx_consecutive_timeout;
|
||||||
|
static uint8_t g_chip_reset_count;
|
||||||
|
|
||||||
|
#define TX_TIMEOUT_THRESHOLD 3u
|
||||||
|
#define CHIP_RESET_MAX 3u
|
||||||
|
|
||||||
|
#define TX_TIMEOUT_THRESHOLD 3u
|
||||||
|
#define CHIP_RESET_MAX 3u
|
||||||
|
|
||||||
static uint8_t ch390_runtime_probe_identity(void)
|
static uint8_t ch390_runtime_probe_identity(void)
|
||||||
{
|
{
|
||||||
@@ -99,7 +107,7 @@ struct pbuf *ch390_runtime_input_frame(struct netif *netif)
|
|||||||
ch390_write_reg(CH390_MPTRCR, 0x01u);
|
ch390_write_reg(CH390_MPTRCR, 0x01u);
|
||||||
ch390_write_reg(CH390_MRRH, 0x0Cu);
|
ch390_write_reg(CH390_MRRH, 0x0Cu);
|
||||||
ch390_delay_us(1000u);
|
ch390_delay_us(1000u);
|
||||||
ch390_write_reg(CH390_RCR, rcr);
|
ch390_write_reg(CH390_RCR, (uint8_t)(rcr | RCR_RXEN));
|
||||||
ethernetif->rx_len = 0u;
|
ethernetif->rx_len = 0u;
|
||||||
LINK_STATS_INC(link.drop);
|
LINK_STATS_INC(link.drop);
|
||||||
g_diag.rx_packets_drop++;
|
g_diag.rx_packets_drop++;
|
||||||
@@ -344,10 +352,16 @@ err_t ch390_runtime_output(struct netif *netif, struct pbuf *p)
|
|||||||
#endif
|
#endif
|
||||||
LINK_STATS_INC(link.drop);
|
LINK_STATS_INC(link.drop);
|
||||||
g_diag.tx_packets_timeout++;
|
g_diag.tx_packets_timeout++;
|
||||||
|
g_tx_consecutive_timeout++;
|
||||||
|
if (g_tx_consecutive_timeout >= TX_TIMEOUT_THRESHOLD) {
|
||||||
|
ch390_runtime_emergency_reset();
|
||||||
|
}
|
||||||
return ERR_TIMEOUT;
|
return ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_tx_consecutive_timeout = 0u;
|
||||||
|
|
||||||
for (q = p; q != NULL; q = q->next) {
|
for (q = p; q != NULL; q = q->next) {
|
||||||
ch390_write_mem((uint8_t *)q->payload, q->len);
|
ch390_write_mem((uint8_t *)q->payload, q->len);
|
||||||
}
|
}
|
||||||
@@ -377,3 +391,55 @@ bool ch390_runtime_is_ready(void)
|
|||||||
{
|
{
|
||||||
return g_ch390_ready != 0u;
|
return g_ch390_ready != 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ch390_runtime_emergency_reset(void)
|
||||||
|
{
|
||||||
|
SEGGER_RTT_printf(0, "ETH emergency reset (tx_timeout=%u resets=%u/%u)\r\n",
|
||||||
|
g_tx_consecutive_timeout, g_chip_reset_count, CHIP_RESET_MAX);
|
||||||
|
|
||||||
|
if (g_chip_reset_count >= CHIP_RESET_MAX) {
|
||||||
|
SEGGER_RTT_WriteString(0, "ETH: max resets reached, giving up\r\n");
|
||||||
|
g_ch390_ready = 0u;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_chip_reset_count++;
|
||||||
|
g_tx_consecutive_timeout = 0u;
|
||||||
|
|
||||||
|
ch390_software_reset();
|
||||||
|
ch390_delay_us(5000u);
|
||||||
|
ch390_default_config();
|
||||||
|
|
||||||
|
ch390_runtime_refresh_diag();
|
||||||
|
g_ch390_ready = g_diag.id_valid;
|
||||||
|
|
||||||
|
if (g_ch390_ready == 0u) {
|
||||||
|
SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEGGER_RTT_WriteString(0, "ETH emergency reset: OK\r\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ch390_runtime_health_check(struct netif *netif)
|
||||||
|
{
|
||||||
|
if (!g_ch390_ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify chip is still responding by reading vendor ID */
|
||||||
|
uint16_t vid = ch390_get_vendor_id();
|
||||||
|
if (vid == 0x0000u || vid == 0xFFFFu) {
|
||||||
|
SEGGER_RTT_printf(0, "ETH health: invalid VID=0x%04X, attempting reset\r\n", vid);
|
||||||
|
netif_set_link_down(netif);
|
||||||
|
if (ch390_runtime_emergency_reset()) {
|
||||||
|
ch390_runtime_check_link(netif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ch390_runtime_get_reset_count(void)
|
||||||
|
{
|
||||||
|
return g_chip_reset_count;
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,5 +58,8 @@ void ch390_runtime_check_link(struct netif *netif);
|
|||||||
err_t ch390_runtime_output(struct netif *netif, struct pbuf *p);
|
err_t ch390_runtime_output(struct netif *netif, struct pbuf *p);
|
||||||
void ch390_runtime_get_diag(ch390_diag_t *diag);
|
void ch390_runtime_get_diag(ch390_diag_t *diag);
|
||||||
bool ch390_runtime_is_ready(void);
|
bool ch390_runtime_is_ready(void);
|
||||||
|
bool ch390_runtime_emergency_reset(void);
|
||||||
|
void ch390_runtime_health_check(struct netif *netif);
|
||||||
|
uint8_t ch390_runtime_get_reset_count(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -417,10 +417,37 @@ python .\tools\tcp_debug_server.py --host 0.0.0.0 --port 8081 --no-stdin
|
|||||||
|
|
||||||
1. PHY 访问无超时,导致永久卡死
|
1. PHY 访问无超时,导致永久卡死
|
||||||
2. 刷新未初始化的 IWDG 句柄导致 HardFault
|
2. 刷新未初始化的 IWDG 句柄导致 HardFault
|
||||||
3. 在长耗时 SPI 路径中错误扩大临界区,导致看似“系统假死”
|
3. 在长耗时 SPI 路径中错误扩大临界区,导致看似"系统假死"
|
||||||
4. 在多个层次同时触达 CH390 / SPI,导致运行时边界混乱
|
4. 在多个层次同时触达 CH390 / SPI,导致运行时边界混乱
|
||||||
5. 配置口命令结束方式不对,导致误判为 parser 无响应
|
5. 配置口命令结束方式不对,导致误判为 parser 无响应
|
||||||
|
|
||||||
|
### 9.4 2026-04-14 MUX 模式网口失联修复记录
|
||||||
|
|
||||||
|
#### 现象
|
||||||
|
|
||||||
|
MUX 模式启动后,一段时间后网口失联。重新插拔网线无法恢复,重启后恢复正常。对端主动关闭 TCP 连接后,120 秒内无法重新建立连接。
|
||||||
|
|
||||||
|
#### 根因
|
||||||
|
|
||||||
|
对端主动关闭 TCP 连接时,`tcp_server_on_recv(p=NULL)` 和 `tcp_client_on_recv(p=NULL)` 调用 `tcp_close()` 关闭本地 pcb。`tcp_close()` 发送 FIN 后将 pcb 推入 TIME_WAIT 状态,持续 `2 × TCP_MSL = 120 秒`。在此期间 pcb 占用 `MEMP_TCP_PCB` 池(总量仅 4 个)。当多条连接同时断开后,pcb 池耗尽,新连接的 `tcp_new()` 返回 NULL。
|
||||||
|
|
||||||
|
#### 修复内容
|
||||||
|
|
||||||
|
| 文件 | 修改 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `App/tcp_server.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 对端关闭时立即释放 pcb,不进入 TIME_WAIT |
|
||||||
|
| `App/tcp_client.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 同上 |
|
||||||
|
| `Drivers/CH390/ch390_runtime.c` | PKT_ERR 恢复时 `rcr` → `rcr \| RCR_RXEN` | 确保 RX 重新使能,与 WCH 官方参考一致 |
|
||||||
|
| `Drivers/CH390/ch390_runtime.c` | TX 连续超时 3 次触发 `ch390_runtime_emergency_reset()` | CH390 TX 引擎卡死时自动复位芯片 |
|
||||||
|
| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_health_check()` | 每 5 秒读 VID 验证芯片存活 |
|
||||||
|
| `Core/Src/main.c` | `App_StartLinksIfNeeded` 失败时不标记 `g_links_started` | 允许下次 poll 自动重试 |
|
||||||
|
| `Core/Src/main.c` | MUX 逐帧 RTT printf 改为 `#if DEBUG` 门控 | 生产固件不输出,减少主循环延迟 |
|
||||||
|
| `App/uart_trans.c` | `uart_mux_try_extract_frame` 先搜 0x7E 再消费 header | 非法帧只丢 1 字节而非 5 字节 |
|
||||||
|
|
||||||
|
#### 构建验证
|
||||||
|
|
||||||
|
Keil MDK-ARM 构建 0 Error(s), 0 Warning(s)。Flash 52.7 KB / 64.0 KB (82.5%),RAM 20.0 KB / 20.0 KB (100%)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 常见误区
|
## 10. 常见误区
|
||||||
|
|||||||
Reference in New Issue
Block a user