Browse Source

proxy: detach lwIP pcb on local close, guard abort against freed pcb

При half-close (exit закрылся первым, браузер держит соединение) pcb уходил в
TIME_WAIT, а conn_finish ждал flow.fin_sent (доставку FIN в exit) — при backpressure
это не наступало, pc->pcb оставался висячим. Таймер TIME_WAIT освобождал pcb без
err_cb, и CLOSE_ALL давал double free. Теперь pcb отсоединяется по завершении
локальной стороны, а tcp_abort/tcp_close защищены проверкой memory_pool_is_freed.
master
evgeny 2 days ago
parent
commit
961a234d7d
  1. 109
      src/proxy/tcp_proxy_client.c

109
src/proxy/tcp_proxy_client.c

@ -45,7 +45,8 @@ static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc); static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc);
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len); static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc); static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc);
static void tcp_proxy_client_conn_finish(struct tcp_proxy_client_conn *pc); static void tcp_proxy_client_conn_detach(struct tcp_proxy_client_conn *pc);
static void tcp_proxy_client_abort_pcb(struct tcp_proxy_client_conn *pc, const char* why);
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force); static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len, int force);
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len, int force); static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len, int force);
static void tcp_proxy_client_tx_queue_drain_cb(struct ll_queue* q, void* arg); static void tcp_proxy_client_tx_queue_drain_cb(struct ll_queue* q, void* arg);
@ -268,8 +269,14 @@ static int tcp_proxy_client_fin_flush(struct tcp_proxy_client_conn* pc) {
pc->flow.fin_pending = 1; pc->flow.fin_pending = 1;
proxy_flow_flush(&pc->flow); proxy_flow_flush(&pc->flow);
} }
// Локальная сторона (браузер) завершена — отсоединяем pcb сразу, не дожидаясь
// доставки FIN в exit (иначе таймер TIME_WAIT освободит pcb без err_cb, pc->pcb
// останется висячим и последующий tcp_abort даст double free).
if (pc->fin_local && pc->fin_remote && !pc->fin_deferred && !pc->to_lwip->head) {
tcp_proxy_client_conn_detach(pc);
}
if (pc->flow.fin_sent && pc->fin_remote && !pc->fin_deferred && !pc->tx_queue->head && !pc->to_lwip->head) { if (pc->flow.fin_sent && pc->fin_remote && !pc->fin_deferred && !pc->tx_queue->head && !pc->to_lwip->head) {
tcp_proxy_client_conn_finish(pc); tcp_proxy_client_conn_free(pc);
return 1; return 1;
} }
return 0; return 0;
@ -422,17 +429,7 @@ static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
if (pc->error) { if (pc->error) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY CLEANUP error sid=%08x", pc->stream_id); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY CLEANUP error sid=%08x", pc->stream_id);
if (pc->pcb) { tcp_proxy_client_abort_pcb(pc, "POLL_ABORT");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY POLL_ABORT pcb=%p state=%s sid=%08x",
(void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), pc->stream_id);
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_abort(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc); tcp_proxy_client_conn_free(pc);
return LERR_ABRT; return LERR_ABRT;
} }
@ -533,22 +530,50 @@ static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
u_free(pc); u_free(pc);
} }
// Завершить локальное TCP-соединение: отвязать коллбэки, сбросить pc->pcb // Отвязать pcb от conn: сбросить коллбэки и pc->pcb. lwIP дальше сам освободит pcb
// (lwIP дальше сам освободит pcb — в т.ч. молча по истечении TIME_WAIT) и освободить conn. // (в т.ч. молча по истечении TIME_WAIT). Контракт: после вызова pc->pcb == NULL.
// Вызывается, когда обе стороны обменялись FIN (fin_local && fin_remote) — релей завершён. static void tcp_proxy_client_conn_detach(struct tcp_proxy_client_conn *pc) {
static void tcp_proxy_client_conn_finish(struct tcp_proxy_client_conn *pc) { if (!pc || !pc->pcb) return;
if (!pc) return; DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY DETACH pcb=%p state=%s sid=%08x",
if (pc->pcb) { (void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), pc->stream_id);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY FINISH pcb=%p state=%s sid=%08x", tcp_arg(pc->pcb, NULL);
(void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), pc->stream_id); tcp_recv(pc->pcb, NULL);
tcp_arg(pc->pcb, NULL); tcp_sent(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL); tcp_err(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL); tcp_poll(pc->pcb, NULL, 0);
tcp_err(pc->pcb, NULL); pc->pcb = NULL;
tcp_poll(pc->pcb, NULL, 0); }
// Проверка, что pc->pcb ещё не освобождён пулом (защита от висячего указателя:
// таймер TIME_WAIT мог освободить pcb без err_cb). Возвращает 0 и сбрасывает
// pc->pcb, если pcb освобождён; иначе 1 (pcb валиден).
static int tcp_proxy_client_pcb_valid(struct tcp_proxy_client_conn *pc, const char* why) {
struct tcp_pcb* pcb = pc->pcb;
struct lwip_tcp_ctx* ctx = pc->proxy ? pc->proxy->lwip : NULL;
if (ctx && memory_pool_is_freed(ctx->pcb_pool, pcb)) {
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "PROXY %s: pcb=%p already freed (sid=%08x) — skip",
why, (void*)pcb, pc->stream_id);
pc->pcb = NULL; pc->pcb = NULL;
return 0;
} }
tcp_proxy_client_conn_free(pc); return 1;
}
// Безопасный abort с защитой от висячего pc->pcb (pcb уже освобождён пулом, например
// таймером TIME_WAIT). Если pcb освобождён — только сбрасываем ссылку, не трогая память.
static void tcp_proxy_client_abort_pcb(struct tcp_proxy_client_conn *pc, const char* why) {
if (!pc || !pc->pcb) return;
struct tcp_pcb* pcb = pc->pcb;
if (!tcp_proxy_client_pcb_valid(pc, why)) return;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY %s pcb=%p state=%s sid=%08x",
why, (void*)pcb, tcp_debug_state_str(pcb->state), pc->stream_id);
tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL);
tcp_sent(pcb, NULL);
tcp_err(pcb, NULL);
tcp_poll(pcb, NULL, 0);
tcp_abort(pcb);
pc->pcb = NULL;
} }
// ==================================================================== // ====================================================================
@ -575,7 +600,7 @@ static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP
if (e) { queue_dgram_free(e); queue_entry_free(e); } if (e) { queue_dgram_free(e); queue_entry_free(e); }
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: receive failed sid=%08x", stream_id); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: receive failed sid=%08x", stream_id);
tcp_proxy_client_send_error(pc); tcp_proxy_client_send_error(pc);
if (pc->pcb) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY DATA_ABORT pcb=%p state=%s sid=%08x", (void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), stream_id); tcp_arg(pc->pcb, NULL); tcp_err(pc->pcb, NULL); tcp_abort(pc->pcb); pc->pcb = NULL; } tcp_proxy_client_abort_pcb(pc, "DATA_ABORT");
tcp_proxy_client_conn_free(pc); tcp_proxy_client_conn_free(pc);
} else { } else {
queue_data_put(pc->to_lwip, e); queue_data_put(pc->to_lwip, e);
@ -588,9 +613,9 @@ static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP
static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) { static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id); struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; } if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb_state=%u", stream_id, pc->fin_local, pc->pcb ? pc->pcb->state : 0); DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb=%p", stream_id, pc->fin_local, (void*)pc->pcb);
pc->rem_closed = 1; pc->rem_closed = 1;
if (pc->pcb) { if (pc->pcb && tcp_proxy_client_pcb_valid(pc, "CLOSE")) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY CLOSE pcb=%p state=%s sid=%08x", DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY CLOSE pcb=%p state=%s sid=%08x",
(void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), stream_id); (void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), stream_id);
tcp_arg(pc->pcb, NULL); tcp_arg(pc->pcb, NULL);
@ -608,17 +633,7 @@ static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t s
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id); struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc) { DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; } if (!pc) { DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; }
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local); DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
if (pc->pcb) { tcp_proxy_client_abort_pcb(pc, "ERROR");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY ERROR pcb=%p state=%s sid=%08x",
(void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), stream_id);
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_abort(pc->pcb);
pc->pcb = NULL;
}
pc->rem_closed = 1; pc->rem_closed = 1;
tcp_proxy_client_conn_free(pc); tcp_proxy_client_conn_free(pc);
} }
@ -664,17 +679,7 @@ void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* en
struct tcp_proxy_client_conn *pc, *next; struct tcp_proxy_client_conn *pc, *next;
for (pc = proxy->conns; pc; pc = next) { for (pc = proxy->conns; pc; pc = next) {
next = pc->next; next = pc->next;
if (pc->pcb) { tcp_proxy_client_abort_pcb(pc, "CLOSE_ALL");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "PROXY CLOSE_ALL pcb=%p state=%s sid=%08x",
(void*)pc->pcb, tcp_debug_state_str(pc->pcb->state), pc->stream_id);
tcp_arg(pc->pcb, NULL);
tcp_recv(pc->pcb, NULL);
tcp_sent(pc->pcb, NULL);
tcp_err(pc->pcb, NULL);
tcp_poll(pc->pcb, NULL, 0);
tcp_abort(pc->pcb);
pc->pcb = NULL;
}
tcp_proxy_client_conn_free(pc); tcp_proxy_client_conn_free(pc);
} }
proxy->conns = NULL; proxy->conn_count = 0; proxy->conns = NULL; proxy->conn_count = 0;

Loading…
Cancel
Save