889 lines
46 KiB
C
889 lines
46 KiB
C
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
|
|
#include "tcp_proxy_client.h"
|
|
#include "socks_proxy.h"
|
|
#include "lwip_tcp/lwip_tcp.h"
|
|
#include "lwip_tcp/lwip_tcp_priv.h"
|
|
#include "lwip_tcp/lwip_tcp_opts.h"
|
|
#include "config_parser.h"
|
|
#include "tun_if.h"
|
|
#include "utun_instance.h"
|
|
#include "etcp.h"
|
|
#include "etcp_api.h"
|
|
#include "../routing_layer/topo_node.h"
|
|
#include "etcp_router.h"
|
|
#include "tcp_proxy_server.h"
|
|
#include "udp_proxy.h"
|
|
#include "icmp_proxy.h"
|
|
#include "../lib/u_async.h"
|
|
#include "../lib/debug_config.h"
|
|
#include "../lib/ll_queue.h"
|
|
#include "../lib/memory_pool.h"
|
|
#include "../lib/mem.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#ifndef INADDR_ANY
|
|
#define INADDR_ANY 0
|
|
#endif
|
|
|
|
// ====================================================================
|
|
// Предварительные объявления
|
|
// ====================================================================
|
|
static void tcp_proxy_client_flow_wake(void* arg);
|
|
static int tcp_proxy_client_fin_flush(struct tcp_proxy_client_conn* pc);
|
|
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
|
|
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
|
|
static void tcp_proxy_client_err_cb(void *arg, err_t err);
|
|
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb);
|
|
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
|
|
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
|
|
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 void tcp_proxy_client_conn_free(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_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_pause_resume_cb(struct ll_queue* q, void* arg);
|
|
static void tcp_proxy_client_retry_timer_cb(void* arg);
|
|
|
|
// ====================================================================
|
|
// Помощник ll_entry
|
|
// ====================================================================
|
|
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
|
|
struct ll_entry* e = queue_entry_new_from_pool(pool);
|
|
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "tcp_proxy_client_entry_from_data: pool exhausted"); return NULL; }
|
|
e->len = 0; e->dgram = NULL;
|
|
if (len > 0) {
|
|
uint8_t* buf = u_malloc(len);
|
|
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "tcp_proxy_client_entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
|
|
memcpy(buf, data, len); e->dgram = buf; e->len = len;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Протокол: сборка и отправка сообщений прокси через ETCP
|
|
// ====================================================================
|
|
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) {
|
|
(void)group_id;
|
|
return proxy_send(inst, dst, ETCP_RT_ID_TCP_PROXY_SERVER, subcmd, sid, data, len, force);
|
|
}
|
|
|
|
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_conn* pc) {
|
|
uint8_t buf[6];
|
|
memcpy(buf, pc->dest_ip, 4); memcpy(buf + 4, &pc->dest_port, 2);
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
|
|
pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3],
|
|
ntohs(pc->dest_port), (unsigned long long)pc->proxy->via_node_id);
|
|
return tcp_proxy_client_send_msg(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
|
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6, 1);
|
|
}
|
|
|
|
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len, int force) {
|
|
int ret = proxy_flow_send(&pc->flow, data, len, force);
|
|
if (ret == 0) pc->bytes_to_exit += len;
|
|
else {
|
|
pc->bp_count++;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY BP send-fail sid=%08x len=%u ret=%d tot=%u bp#=%u rcv_wnd=%u",
|
|
pc->stream_id, len, ret, pc->bytes_to_exit, pc->bp_count,
|
|
pc->pcb ? pc->pcb->rcv_wnd : 0);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
|
|
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "PROXY SEND sid=%08x", pc->stream_id);
|
|
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
|
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0, 1);
|
|
if (ret < 0) { pc->close_pending = 1; return -1; }
|
|
pc->close_pending = 0;
|
|
pc->close_sent = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int tcp_proxy_client_send_error(struct tcp_proxy_client_conn* pc) {
|
|
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
|
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0, 1);
|
|
if (ret < 0) { pc->close_pending = 1; return -1; }
|
|
pc->close_pending = 0;
|
|
pc->close_sent = 1;
|
|
return 0;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
|
|
// ====================================================================
|
|
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
|
|
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)arg;
|
|
(void)src_ip; (void)dst_ip;
|
|
uint16_t len = p->tot_len;
|
|
if (len > TUN_MAX_PACKET_SIZE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy output too large: len=%u max=%d", len, TUN_MAX_PACKET_SIZE);
|
|
return LERR_BUF;
|
|
}
|
|
uint8_t buf[TUN_MAX_PACKET_SIZE];
|
|
if (proxy->tun) {
|
|
pbuf_copy_partial(p, buf, len, 0);
|
|
if (tun_write(proxy->tun, buf, len) != (ssize_t)len) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy TUN output failed: len=%u", len);
|
|
return LERR_IF;
|
|
}
|
|
}
|
|
return LERR_OK;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Обработчик не-TCP (UDP/ICMP прокси)
|
|
// ====================================================================
|
|
static int tcp_proxy_client_handle_non_tcp(struct tcp_proxy_client* p, uint8_t* buf, size_t len) {
|
|
if (len < 20) return 0;
|
|
uint8_t ip_ver = (buf[0] >> 4) & 0xF;
|
|
if (ip_ver != 4) return 0;
|
|
uint16_t ihl = (buf[0] & 15) * 4;
|
|
if (ihl < 20 || len < ihl + 8u) return 0;
|
|
uint8_t* transport = buf + ihl;
|
|
uint8_t proto = buf[9];
|
|
if (proto == IPPROTO_UDP) {
|
|
if (len < 28) return 0;
|
|
uint32_t src_ip, dst_ip; uint16_t src_port, dst_port;
|
|
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
|
|
memcpy(&src_port, transport, 2); memcpy(&dst_port, transport + 2, 2);
|
|
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, transport + 8, len - ihl - 8);
|
|
return 1;
|
|
}
|
|
if (proto == IPPROTO_ICMP) {
|
|
if (len < 28) return 0;
|
|
uint8_t icmp_type = transport[0];
|
|
if (icmp_type != 8) return 0;
|
|
uint32_t src_ip, dst_ip;
|
|
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
|
|
uint16_t icmp_id, icmp_seq;
|
|
memcpy(&icmp_id, transport + 4, 2); memcpy(&icmp_seq, transport + 6, 2);
|
|
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, src_ip, icmp_id, icmp_seq, transport + 8, len - ihl - 8);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Помощник: передача данных из очереди to_lwip в lwIP TCP
|
|
// ====================================================================
|
|
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc) {
|
|
if (!pc->to_lwip || !pc->pcb || pc->rem_closed) return;
|
|
int sent = 0;
|
|
while (pc->to_lwip->head) {
|
|
uint16_t space = tcp_sndbuf(pc->pcb);
|
|
if (!space) break;
|
|
struct ll_entry* e = queue_data_get(pc->to_lwip);
|
|
uint16_t len = e->len < space ? e->len : space;
|
|
err_t ret = tcp_write(pc->pcb, e->dgram, len, TCP_WRITE_FLAG_COPY);
|
|
if (ret != LERR_OK) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "proxy: lwIP write blocked sid=%08x ret=%d space=%u", pc->stream_id, ret, space);
|
|
queue_data_put_first(pc->to_lwip, e); break;
|
|
}
|
|
sent = 1;
|
|
e->len -= len;
|
|
if (e->len) { memmove(e->dgram, e->dgram + len, e->len); queue_data_put_first(pc->to_lwip, e); }
|
|
else { queue_dgram_free(e); queue_entry_free(e); }
|
|
// Буфер lwIP тоже ограничен snd_buf; to_lwip ограничен окном proxy.
|
|
proxy_flow_consume(&pc->flow, len);
|
|
}
|
|
queue_resume_callback(pc->to_lwip);
|
|
if (sent) tcp_output(pc->pcb);
|
|
if (pc->fin_deferred && !pc->to_lwip->head) {
|
|
err_t ret = tcp_shutdown(pc->pcb, 0, 1);
|
|
if (ret == LERR_OK) {
|
|
pc->fin_deferred = 0;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "proxy: lwIP FIN queued sid=%08x", pc->stream_id);
|
|
} else DEBUG_WARN(DEBUG_CATEGORY_PROXY, "proxy: lwIP FIN retry sid=%08x ret=%d", pc->stream_id, ret);
|
|
}
|
|
}
|
|
|
|
// ====================================================================
|
|
// lwIP TCP коллбэки
|
|
// ====================================================================
|
|
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
|
|
struct tcp_proxy_client *p = (struct tcp_proxy_client *)arg;
|
|
if (err != LERR_OK || !newpcb) return LERR_ABRT;
|
|
|
|
struct tcp_proxy_client_conn *pc = u_calloc(1, sizeof(struct tcp_proxy_client_conn));
|
|
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: accept alloc failed"); return LERR_MEM; }
|
|
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY ACCEPT pcb=%p sid=%08x", (void*)newpcb, pc->stream_id);
|
|
proxy_flow_init(&pc->flow, p->inst, p->ua, p->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, pc->stream_id, tcp_proxy_client_flow_wake, pc);
|
|
|
|
int i;
|
|
for (i = 0; i < p->mapping_count; i++) {
|
|
if (p->mappings[i].local_port == newpcb->local_port) {
|
|
struct in_addr ra; ra.s_addr = inet_addr(p->mappings[i].remote_ip);
|
|
memcpy(pc->dest_ip, &ra.s_addr, 4);
|
|
pc->dest_port = htons(p->mappings[i].remote_port);
|
|
break;
|
|
}
|
|
}
|
|
if (i == p->mapping_count) {
|
|
memcpy(pc->dest_ip, &newpcb->local_ip, 4);
|
|
pc->dest_port = htons(newpcb->local_port);
|
|
}
|
|
|
|
tcp_arg(newpcb, pc);
|
|
tcp_recv(newpcb, tcp_proxy_client_recv_cb);
|
|
tcp_sent(newpcb, tcp_proxy_client_sent_cb);
|
|
tcp_err(newpcb, tcp_proxy_client_err_cb);
|
|
tcp_poll(newpcb, tcp_proxy_client_poll_cb, 2);
|
|
tcp_nagle_disable(newpcb);
|
|
|
|
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
|
|
pc->tx_queue = queue_new(p->ua, 0, 0, 0, "tx_queue");
|
|
if (!pc->tx_queue || !pc->to_lwip) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: tx_queue alloc failed sid=%08x", pc->stream_id);
|
|
queue_free(pc->to_lwip); queue_free(pc->tx_queue); pc->to_lwip = NULL;
|
|
tcp_arg(newpcb, NULL); tcp_abort(newpcb); u_free(pc); return LERR_ABRT;
|
|
}
|
|
queue_set_callback(pc->tx_queue, tcp_proxy_client_tx_queue_drain_cb, pc);
|
|
|
|
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); tcp_arg(newpcb, NULL); tcp_abort(newpcb); queue_free(pc->to_lwip); queue_free(pc->tx_queue); u_free(pc); return LERR_ABRT; }
|
|
|
|
pc->next = p->conns; p->conns = pc; p->conn_count++;
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
|
|
pc->stream_id, newpcb->local_port, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), p->conn_count, newpcb->snd_wnd, newpcb->mss);
|
|
return LERR_OK;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Backpressure: буфер lwIP→ETCP (tx_queue) с пачковым восстановлением окна.
|
|
// Зеркало tcp_proxy_server.c read_queue_drain_cb + pause_resume_cb + retry_timer_cb.
|
|
// ====================================================================
|
|
|
|
// После отправки последних данных (очередь пуста): релеить FIN/CLOSE в exit.
|
|
// Возвращает 1, если соединение завершено и pc освобождён (дальше pc/q использовать нельзя).
|
|
static int tcp_proxy_client_fin_flush(struct tcp_proxy_client_conn* pc) {
|
|
if (pc->fin_local && !pc->tx_queue->head && !pc->flow.fin_sent) {
|
|
pc->flow.fin_pending = 1;
|
|
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) {
|
|
tcp_proxy_client_conn_free(pc);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void tcp_proxy_client_flow_wake(void* arg) {
|
|
struct tcp_proxy_client_conn* pc = arg;
|
|
if (pc->rem_closed || pc->error) return;
|
|
queue_resume_callback(pc->tx_queue);
|
|
tcp_proxy_client_feed_from_transport(pc);
|
|
tcp_proxy_client_fin_flush(pc);
|
|
}
|
|
|
|
// Дрейн tx_queue → ETCP. tcp_recved вызывается только после успешной отправки:
|
|
// окно закрывается плавно (штатная backpressure), а при возобновлении открывается
|
|
// пачкой (за 2 пакета wnd_inflation ≥ TCP_WND_UPDATE_THRESHOLD → немедленный ACK).
|
|
static void tcp_proxy_client_tx_queue_drain_cb(struct ll_queue* q, void* arg) {
|
|
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
|
|
if (!pc || !pc->proxy || pc->rem_closed) return;
|
|
|
|
struct ll_entry* e = queue_data_get(q);
|
|
if (!e) { queue_resume_callback(q); return; }
|
|
|
|
int ret = tcp_proxy_client_send_data(pc, e->dgram, e->len, 0);
|
|
if (ret == 0) {
|
|
if (pc->pcb) tcp_recved(pc->pcb, e->len);
|
|
queue_dgram_free(e); queue_entry_free(e);
|
|
if (queue_entry_count(q) == 0 && tcp_proxy_client_fin_flush(pc)) return;
|
|
queue_resume_callback(q);
|
|
} else {
|
|
queue_data_put_first(q, e);
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY BP stall sid=%08x q=%d rcv_wnd=%u",
|
|
pc->stream_id, queue_entry_count(q), pc->pcb ? pc->pcb->rcv_wnd : 0);
|
|
if (ret != -2) etcp_router_on_send_ready(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
|
ETCP_RT_ID_TCP_PROXY_SERVER, &pc->tx_waiter,
|
|
tcp_proxy_client_pause_resume_cb, pc);
|
|
if (!pc->tx_retry_timer)
|
|
pc->tx_retry_timer = uasync_set_timeout(pc->proxy->ua, 5000, pc, tcp_proxy_client_retry_timer_cb, "tpc_retry");
|
|
}
|
|
}
|
|
|
|
// send_q освободился — возобновить дрейн tx_queue
|
|
static void tcp_proxy_client_pause_resume_cb(struct ll_queue* q, void* arg) {
|
|
(void)q;
|
|
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
|
|
if (!pc || !pc->proxy || pc->rem_closed || !pc->tx_queue) return;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY BP resume sid=%08x q=%d",
|
|
pc->stream_id, queue_entry_count(pc->tx_queue));
|
|
queue_resume_callback(pc->tx_queue);
|
|
}
|
|
|
|
// Safety-net: форсированный ретрай, если waiter не сработал
|
|
static void tcp_proxy_client_retry_timer_cb(void* arg) {
|
|
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
|
|
if (!pc || !pc->proxy) return;
|
|
pc->tx_retry_timer = NULL;
|
|
if (pc->rem_closed || !pc->tx_queue) return;
|
|
struct ll_entry* e = queue_data_get(pc->tx_queue);
|
|
if (!e) { queue_resume_callback(pc->tx_queue); return; }
|
|
int ret = tcp_proxy_client_send_data(pc, e->dgram, e->len, 1);
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY BP retry(force) sid=%08x len=%u ret=%d",
|
|
pc->stream_id, e->len, ret);
|
|
if (ret == 0) {
|
|
if (pc->pcb) tcp_recved(pc->pcb, e->len);
|
|
queue_dgram_free(e); queue_entry_free(e);
|
|
if (queue_entry_count(pc->tx_queue) == 0 && tcp_proxy_client_fin_flush(pc)) return;
|
|
queue_resume_callback(pc->tx_queue);
|
|
} else {
|
|
queue_data_put_first(pc->tx_queue, e);
|
|
pc->tx_retry_timer = uasync_set_timeout(pc->proxy->ua, 5000, pc, tcp_proxy_client_retry_timer_cb, "tpc_retry");
|
|
}
|
|
}
|
|
|
|
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
|
|
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
|
if (!pc || !pc->proxy || pc->rem_closed) { if (p) pbuf_free(p); return LERR_OK; }
|
|
|
|
if (p == NULL || err != LERR_OK) {
|
|
pc->fin_local = 1;
|
|
if (p == NULL && err == ERR_OK) {
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY FIN sid=%08x to_exit=%u from_exit=%u bp#=%u pcb_state=%u sndbuf=%u cwnd=%u",
|
|
pc->stream_id, pc->bytes_to_exit, pc->bytes_from_exit, pc->bp_count,
|
|
pcb->state, pcb->snd_buf, pcb->cwnd);
|
|
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
|
|
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
|
|
if (pc->tx_queue && queue_entry_count(pc->tx_queue) == 0) {
|
|
if (tcp_proxy_client_fin_flush(pc)) return LERR_OK;
|
|
}
|
|
} else {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY ERROR recv sid=%08x pcb_state=%u err=%d",
|
|
pc->stream_id, pcb->state, err);
|
|
pc->error = 1;
|
|
tcp_proxy_client_send_error(pc);
|
|
}
|
|
return LERR_OK;
|
|
}
|
|
|
|
uint16_t len = p->tot_len;
|
|
if (pc->error || pc->rem_closed) {
|
|
tcp_recved(pcb, len); pbuf_free(p); return LERR_OK;
|
|
}
|
|
|
|
struct ll_entry* head = NULL;
|
|
struct ll_entry** tail = &head;
|
|
for (uint32_t off = 0; off < len;) {
|
|
uint16_t chunk = len - off > TCP_PROXY_CHUNK ? TCP_PROXY_CHUNK : len - off;
|
|
struct ll_entry* e = queue_entry_new_from_pool(pc->proxy->entry_pool);
|
|
if (e) { e->dgram = u_malloc(chunk); e->len = chunk; }
|
|
if (!e || !e->dgram) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: recv allocation failed sid=%08x len=%u", pc->stream_id, len);
|
|
if (e) queue_entry_free(e);
|
|
while (head) { struct ll_entry* next = head->next; queue_dgram_free(head); queue_entry_free(head); head = next; }
|
|
return LERR_MEM;
|
|
}
|
|
pbuf_copy_partial(p, e->dgram, chunk, off);
|
|
e->next = NULL; *tail = e; tail = &e->next; off += chunk;
|
|
}
|
|
while (head) { struct ll_entry* next = head->next; head->next = NULL; queue_data_put(pc->tx_queue, head); head = next; }
|
|
pbuf_free(p);
|
|
return LERR_OK;
|
|
}
|
|
|
|
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
|
|
(void)len;
|
|
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
|
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
|
|
tcp_proxy_client_feed_from_transport(pc);
|
|
tcp_proxy_client_fin_flush(pc);
|
|
return LERR_OK;
|
|
}
|
|
|
|
static void tcp_proxy_client_err_cb(void *arg, err_t err) {
|
|
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY ERR_CB pc=%p pcb=%p sid=%08x err=%d rem_closed=%d",
|
|
(void*)pc, (void*)(pc ? pc->pcb : NULL), pc ? pc->stream_id : 0, err, pc ? pc->rem_closed : 0);
|
|
if (!pc || !pc->proxy || pc->rem_closed) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY tcp_proxy_client_err_cb: pc=%p proxy=%p rem_closed=%d err=%d", (void*)pc, pc ? (void*)pc->proxy : NULL, pc ? pc->rem_closed : 0, err); return; }
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: error %d sid=%08x fin_local=%d rem_closed=%d",
|
|
err, pc->stream_id, pc->fin_local, pc->rem_closed);
|
|
pc->pcb = NULL; // pcb уже освобождён (или вот-вот) стеком lwip — не разыменовывать
|
|
if (err != LERR_CLSD) {
|
|
pc->error = 1;
|
|
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY send_error failed sid=%08x", pc->stream_id);
|
|
}
|
|
tcp_proxy_client_conn_free(pc);
|
|
}
|
|
|
|
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
|
|
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
|
|
if (!pc || !pc->proxy || pc->rem_closed) return LERR_OK;
|
|
|
|
if (pc->error) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY CLEANUP error sid=%08x", pc->stream_id);
|
|
tcp_proxy_client_abort_pcb(pc, "POLL_ABORT");
|
|
tcp_proxy_client_conn_free(pc);
|
|
return LERR_ABRT;
|
|
}
|
|
|
|
tcp_proxy_client_feed_from_transport(pc);
|
|
if (tcp_proxy_client_fin_flush(pc)) return LERR_OK;
|
|
if (pc->close_pending) {
|
|
if (pc->error) tcp_proxy_client_send_error(pc);
|
|
else tcp_proxy_client_send_close(pc);
|
|
}
|
|
return LERR_OK;
|
|
}
|
|
|
|
// ====================================================================
|
|
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
|
|
// ====================================================================
|
|
static void tcp_proxy_client_ensure_outbound_listen(struct tcp_proxy_client* p, uint16_t dport_net) {
|
|
uint16_t dport_host = ntohs(dport_net);
|
|
struct tcp_pcb* lp = p->lwip->listen_pcbs;
|
|
while (lp) { if (lp->local_port == dport_host) return; lp = lp->next; }
|
|
struct tcp_pcb* lpcb = tcp_new(p->lwip);
|
|
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: tcp_new failed for dynamic listen port %u", dport_host); return; }
|
|
tcp_bind(lpcb, INADDR_ANY, dport_net);
|
|
struct tcp_pcb* listen_pcb = tcp_listen(lpcb);
|
|
if (listen_pcb) {
|
|
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client: dynamic listen on port %u", dport_host);
|
|
}
|
|
}
|
|
|
|
// ====================================================================
|
|
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
|
|
// ====================================================================
|
|
static void tcp_proxy_client_tun_input(struct ll_queue* q, void* arg) {
|
|
struct tcp_proxy_client* p = (struct tcp_proxy_client*)arg;
|
|
struct ll_entry* entry = queue_data_get(q);
|
|
if (!entry) return;
|
|
if (entry->dgram && entry->len > 1) {
|
|
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
|
|
uint16_t ihl = len ? (ip[0] & 15) * 4 : 0;
|
|
uint16_t total = len >= 4 ? ((uint16_t)ip[2] << 8) | ip[3] : 0;
|
|
if (len < 20 || (ip[0] >> 4) != 4 || ihl < 20 || total < ihl || total > len) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "proxy TUN: invalid IPv4 packet len=%zu ihl=%u total=%u", len, ihl, total);
|
|
} else if ((ip[6] & 0x3f) || ip[7]) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "proxy TUN: fragmented IPv4 packet unsupported flags=%02x%02x", ip[6], ip[7]);
|
|
} else {
|
|
if (!tcp_proxy_client_handle_non_tcp(p, ip, total)) {
|
|
uint8_t proto = ip[9];
|
|
if (proto == IPPROTO_TCP) {
|
|
uint16_t ip_hdr_len = (ip[0] & 0x0F) * 4;
|
|
uint16_t ip_total = ((uint16_t)ip[2] << 8) | ip[3];
|
|
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len + 20 && len >= ip_total) {
|
|
uint16_t dport_net; memcpy(&dport_net, ip + ip_hdr_len + 2, 2);
|
|
tcp_proxy_client_ensure_outbound_listen(p, dport_net);
|
|
uint32_t src_ip, dst_ip;
|
|
memcpy(&src_ip, ip + 12, 4); memcpy(&dst_ip, ip + 16, 4);
|
|
uint16_t tcp_len = ip_total - ip_hdr_len;
|
|
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
|
|
if (pb) { pbuf_take(pb, ip + ip_hdr_len, tcp_len); lwip_tcp_input(p->lwip, pb, src_ip, dst_ip); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
queue_dgram_free(entry); queue_entry_free(entry); queue_resume_callback(q);
|
|
}
|
|
|
|
// ====================================================================
|
|
// Очистка tcp_proxy_client_conn
|
|
// ====================================================================
|
|
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
|
|
if (!pc) return;
|
|
struct tcp_proxy_client *p = pc->proxy;
|
|
proxy_flow_destroy(&pc->flow);
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY FREE pcb=%p sid=%08x", (void*)pc->pcb, pc->stream_id);
|
|
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY FREE sid=%08x total_conns=%d to_lwip_q=%u",
|
|
pc->stream_id, p->conn_count, pending);
|
|
if (pc->close_pending && p && p->inst) {
|
|
pc->close_pending = 0;
|
|
uint8_t subcmd = pc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
|
tcp_proxy_client_send_msg(p->inst, TOPO_GROUP_UTUN, p->via_node_id, subcmd, pc->stream_id, NULL, 0, 1);
|
|
}
|
|
struct tcp_proxy_client_conn **prev = &p->conns;
|
|
while (*prev) { if (*prev == pc) { *prev = pc->next; p->conn_count--; break; } prev = &(*prev)->next; }
|
|
if (pc->to_lwip) {
|
|
struct ll_entry *e;
|
|
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
|
|
queue_free(pc->to_lwip); pc->to_lwip = NULL;
|
|
}
|
|
if (pc->tx_queue) {
|
|
struct ll_entry *e;
|
|
while ((e = queue_data_get(pc->tx_queue))) { queue_dgram_free(e); queue_entry_free(e); }
|
|
queue_free(pc->tx_queue); pc->tx_queue = NULL;
|
|
}
|
|
if (pc->tx_retry_timer) { uasync_cancel_timeout(p->ua, pc->tx_retry_timer); pc->tx_retry_timer = NULL; }
|
|
if (p && p->inst) etcp_router_cancel_send_ready(p->inst, TOPO_GROUP_UTUN, p->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &pc->tx_waiter);
|
|
u_free(pc);
|
|
}
|
|
|
|
// Отвязать pcb от conn: сбросить коллбэки и pc->pcb. lwIP дальше сам освободит pcb
|
|
// (в т.ч. молча по истечении TIME_WAIT). Контракт: после вызова pc->pcb == NULL.
|
|
static void tcp_proxy_client_conn_detach(struct tcp_proxy_client_conn *pc) {
|
|
if (!pc || !pc->pcb) return;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY DETACH 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);
|
|
pc->pcb = NULL;
|
|
}
|
|
|
|
// Проверка, что pc->pcb ещё не освобождён пулом (защита от висячего указателя:
|
|
// таймер TIME_WAIT мог освободить pcb без err_cb). Возвращает 0 и сбрасывает
|
|
// pc->pcb, если pcb освобождён; иначе 1 (pcb валиден).
|
|
static int tcp_proxy_client_pcb_guard(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;
|
|
return 0;
|
|
}
|
|
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_guard(pc, why)) return;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY %s pcb=%p state=%s sid=%08x",
|
|
why, (void*)pcb, tcp_debug_state_str(pcb->state), pc->stream_id);
|
|
tcp_proxy_client_conn_detach(pc);
|
|
tcp_abort(pcb);
|
|
}
|
|
|
|
// ====================================================================
|
|
// Обработчики входящих сообщений (сторона клиента)
|
|
// ====================================================================
|
|
static struct tcp_proxy_client_conn* tcp_proxy_client_find_conn(struct tcp_proxy_client* p, uint32_t stream_id) {
|
|
struct tcp_proxy_client_conn* pc;
|
|
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
|
|
return NULL;
|
|
}
|
|
|
|
static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
|
|
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
|
if (!pc || pc->rem_closed || pc->error) {
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY DATA sid=%08x — no conn/closed, dropping", stream_id);
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
size_t data_len = entry->len - TCP_PROXY_RECV_HDR_SIZE;
|
|
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
|
|
if (data_len > 0) {
|
|
pc->bytes_from_exit += (uint32_t)data_len;
|
|
struct ll_entry* e = tcp_proxy_client_entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_RECV_HDR_SIZE, (uint16_t)data_len);
|
|
if (!e || proxy_flow_receive(&pc->flow, data_len) < 0) {
|
|
if (e) { queue_dgram_free(e); queue_entry_free(e); }
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: receive failed sid=%08x", stream_id);
|
|
tcp_proxy_client_send_error(pc);
|
|
tcp_proxy_client_abort_pcb(pc, "DATA_ABORT");
|
|
tcp_proxy_client_conn_free(pc);
|
|
} else {
|
|
queue_data_put(pc->to_lwip, e);
|
|
tcp_proxy_client_feed_from_transport(pc);
|
|
}
|
|
}
|
|
queue_dgram_free(entry); queue_entry_free(entry);
|
|
}
|
|
|
|
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);
|
|
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=%p", stream_id, pc->fin_local, (void*)pc->pcb);
|
|
pc->rem_closed = 1;
|
|
if (pc->pcb && tcp_proxy_client_pcb_guard(pc, "CLOSE")) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY CLOSE 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_close(pc->pcb);
|
|
pc->pcb = NULL;
|
|
}
|
|
tcp_proxy_client_conn_free(pc);
|
|
}
|
|
|
|
static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t 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; }
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
|
|
tcp_proxy_client_abort_pcb(pc, "ERROR");
|
|
pc->rem_closed = 1;
|
|
tcp_proxy_client_conn_free(pc);
|
|
}
|
|
|
|
static void tcp_proxy_client_handle_fin(struct tcp_proxy_client* p, uint32_t stream_id) {
|
|
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
|
|
if (!pc || !pc->pcb) return;
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY FIN FROM exit sid=%08x pcb_state=%u to_lwip=%d — shutdown write (send FIN to local)",
|
|
stream_id, pc->pcb->state, pc->to_lwip ? queue_entry_count(pc->to_lwip) : -1);
|
|
if (pc->flow.fin_received) return;
|
|
pc->fin_remote = 1; pc->flow.fin_received = 1; pc->fin_deferred = 1;
|
|
tcp_proxy_client_feed_from_transport(pc);
|
|
tcp_proxy_client_fin_flush(pc);
|
|
}
|
|
|
|
// ====================================================================
|
|
// ETCP коллбэк клиентской стороны (принимает DATA/CLOSE/ERROR/FIN от сервера)
|
|
// ====================================================================
|
|
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
|
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
|
|
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
|
|
|
|
if (!entry || !entry->dgram) {
|
|
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
|
return;
|
|
}
|
|
if (entry->len < ROUTER_SVC_HDR_SIZE) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
|
uint64_t peer, group;
|
|
memcpy(&peer, entry->dgram + ROUTER_SVC_SRC_OFF, 8);
|
|
memcpy(&group, entry->dgram + ROUTER_SVC_GROUP_OFF, 8);
|
|
if (!proxy || peer != proxy->via_node_id || group != TOPO_GROUP_UTUN) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "proxy client: unexpected peer=%016llx group=%016llx",
|
|
(unsigned long long)peer, (unsigned long long)group);
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
// CLOSE_ALL / restart-уведомление: [svc_id][src][dst] без payload
|
|
if (entry->len == ROUTER_SVC_HDR_SIZE && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY_CLIENT) {
|
|
uint64_t peer_id;
|
|
memcpy(&peer_id, entry->dgram + ROUTER_SVC_SRC_OFF, 8);
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "PROXY CLOSE_ALL from %016llx — clearing client conns for peer",
|
|
(unsigned long long)peer_id);
|
|
if (proxy) {
|
|
struct tcp_proxy_client_conn *pc, *next;
|
|
for (pc = proxy->conns; pc; pc = next) {
|
|
next = pc->next;
|
|
tcp_proxy_client_abort_pcb(pc, "CLOSE_ALL");
|
|
tcp_proxy_client_conn_free(pc);
|
|
}
|
|
proxy->conns = NULL; proxy->conn_count = 0;
|
|
socks_proxy_conn_free_all(&proxy->socks_conns, &proxy->socks_conn_count);
|
|
socks_proxy_conn_free_all(&proxy->http_conns, &proxy->http_conn_count);
|
|
}
|
|
queue_dgram_free(entry); queue_entry_free(entry);
|
|
return;
|
|
}
|
|
if (entry->len < TCP_PROXY_RECV_HDR_SIZE) {
|
|
queue_dgram_free(entry); queue_entry_free(entry);
|
|
return;
|
|
}
|
|
uint8_t subcmd = entry->dgram[ROUTER_SVC_PAYLOAD_OFF];
|
|
uint32_t stream_id; memcpy(&stream_id, entry->dgram + ROUTER_SVC_PAYLOAD_OFF + 1, 4);
|
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "PROXY CLIENT RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
|
|
|
|
if (proxy) {
|
|
size_t data_len = entry->len > TCP_PROXY_RECV_HDR_SIZE ? entry->len - TCP_PROXY_RECV_HDR_SIZE : 0;
|
|
// Пробуем SOCKS/HTTP first (у них приоритет — могут быть без TUN)
|
|
if (proxy->socks_enabled && socks_proxy_handle_etcp(&proxy->socks_conns, &proxy->socks_conn_count,
|
|
stream_id, subcmd, entry->dgram + TCP_PROXY_RECV_HDR_SIZE, data_len)) {
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
if (proxy->http_proxy_enabled && socks_proxy_handle_etcp(&proxy->http_conns, &proxy->http_conn_count,
|
|
stream_id, subcmd, entry->dgram + TCP_PROXY_RECV_HDR_SIZE, data_len)) {
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(proxy, stream_id);
|
|
if (pc && subcmd == TCP_PROXY_SUBCMD_CONNECTED) {
|
|
pc->flow.ready = 1;
|
|
tcp_proxy_client_flow_wake(pc);
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
if (pc && subcmd == TCP_PROXY_SUBCMD_WINDOW) {
|
|
if (proxy_flow_window(&pc->flow, entry->dgram + TCP_PROXY_RECV_HDR_SIZE, data_len) < 0)
|
|
tcp_proxy_client_handle_error(proxy, stream_id);
|
|
else tcp_proxy_client_flow_wake(pc);
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
// Существующие lwIP conns
|
|
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
|
|
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
|
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
|
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_client_handle_fin(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
|
}
|
|
if (subcmd == TCP_PROXY_SUBCMD_DATA || subcmd == TCP_PROXY_SUBCMD_CLOSE || subcmd == TCP_PROXY_SUBCMD_FIN ||
|
|
subcmd == TCP_PROXY_SUBCMD_ERROR || subcmd == TCP_PROXY_SUBCMD_WINDOW) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "PROXY subcmd=%02x sid=%08x — client conn not found, silent drop", subcmd, stream_id);
|
|
queue_dgram_free(entry); queue_entry_free(entry); return;
|
|
}
|
|
DEBUG_WARN(DEBUG_CATEGORY_PROXY, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
|
queue_dgram_free(entry); queue_entry_free(entry);
|
|
}
|
|
|
|
// ====================================================================
|
|
// Публичное API
|
|
// ====================================================================
|
|
struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
|
|
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
|
|
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
|
|
uint64_t via_node_id,
|
|
int socks_enabled, const char* socks_addr,
|
|
int http_proxy_enabled, const char* http_proxy_addr)
|
|
{
|
|
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "ua is NULL"); return NULL; }
|
|
if (inst && (inst->tcp_proxy_client || inst->router_bindings.callbacks[ETCP_RT_ID_TCP_PROXY_CLIENT])) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: duplicate instance binding"); return NULL;
|
|
}
|
|
if ((socks_enabled && (!socks_addr || !socks_addr[0])) ||
|
|
(http_proxy_enabled && (!http_proxy_addr || !http_proxy_addr[0])) || mapping_count < 0 || (mapping_count && !mappings)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: missing listener address or invalid mappings"); return NULL;
|
|
}
|
|
|
|
int need_tun = tun_name && tun_name[0] && tun_ip && tun_ip[0];
|
|
if ((tun_name && tun_name[0]) != (tun_ip && tun_ip[0])) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: TUN requires both name and IP"); return NULL;
|
|
}
|
|
if (!need_tun && !socks_enabled && !http_proxy_enabled) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "no proxy mode enabled"); return NULL;
|
|
}
|
|
|
|
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
|
|
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "u_calloc failed"); return NULL; }
|
|
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
|
|
p->via_node_id = via_node_id;
|
|
p->mappings = mappings; p->mapping_count = mapping_count;
|
|
p->socks_enabled = socks_enabled;
|
|
p->http_proxy_enabled = http_proxy_enabled;
|
|
|
|
p->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
|
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "memory_pool_init failed"); u_free(p); return NULL; }
|
|
|
|
if (need_tun) {
|
|
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
|
|
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
|
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
|
|
|
|
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
|
|
if (!p->lwip) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "lwip_tcp_init failed"); tun_close(p->tun); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
|
|
|
|
if (lwip_tcp_set_mtu(p->lwip, p->tun->mtu) != LERR_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy MTU setup failed: mtu=%d", p->tun->mtu);
|
|
tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
|
|
if (mapping_count > 0) {
|
|
int j; for (j = 0; j < mapping_count; j++) {
|
|
uint16_t port_net = htons(mappings[j].local_port);
|
|
struct tcp_pcb *lpcb = tcp_new(p->lwip);
|
|
if (!lpcb) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: mapping PCB allocation failed port=%u", mappings[j].local_port);
|
|
tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
if (tcp_bind(lpcb, INADDR_ANY, port_net) != LERR_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: mapping bind failed port=%u", mappings[j].local_port);
|
|
tcp_abort(lpcb); tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
|
|
if (!listen_pcb) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: mapping listen failed port=%u", mappings[j].local_port);
|
|
tcp_abort(lpcb); tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (socks_enabled && socks_addr && socks_addr[0]) {
|
|
p->socks_listen = socks_proxy_init_listen(ua, socks_addr, &p->socks_conns, &p->socks_conn_count,
|
|
&p->next_stream_id, inst, via_node_id, 0);
|
|
if (!p->socks_listen) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "tcp_proxy_client: SOCKS init failed on %s", socks_addr);
|
|
tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
}
|
|
|
|
if (http_proxy_enabled && http_proxy_addr && http_proxy_addr[0]) {
|
|
p->http_listen = socks_proxy_init_listen(ua, http_proxy_addr, &p->http_conns, &p->http_conn_count,
|
|
&p->next_stream_id, inst, via_node_id, 1);
|
|
if (!p->http_listen) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "tcp_proxy_client: HTTP proxy init failed on %s", http_proxy_addr);
|
|
tcp_proxy_client_destroy(p); return NULL;
|
|
}
|
|
}
|
|
|
|
if (inst) {
|
|
if (etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY_CLIENT, tcp_proxy_client_router_recv_cb) != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy client: etcp_router_bind failed");
|
|
tcp_proxy_client_destroy(p); return NULL;
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_RT_ID_TCP_PROXY_CLIENT);
|
|
p->router_bound = 1;
|
|
}
|
|
}
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client created: tun=%s socks=%s(http=%s) mappings=%d via_node=%016llx",
|
|
need_tun ? "yes" : "no", socks_enabled ? "yes" : "no", http_proxy_enabled ? "yes" : "no",
|
|
mapping_count, (unsigned long long)via_node_id);
|
|
return p;
|
|
}
|
|
|
|
void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
|
|
if (!p) return;
|
|
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "TCP proxy client destroying: lwip=%d socks=%d http=%d",
|
|
p->conn_count, p->socks_conn_count, p->http_conn_count);
|
|
if (p->router_bound) { etcp_router_unbind(p->inst, ETCP_RT_ID_TCP_PROXY_CLIENT); p->router_bound = 0; }
|
|
|
|
if (p->socks_listen) socks_proxy_close_listen(p->ua, p->socks_listen, NULL);
|
|
socks_proxy_conn_free_all(&p->socks_conns, &p->socks_conn_count);
|
|
|
|
if (p->http_listen) socks_proxy_close_listen(p->ua, p->http_listen, NULL);
|
|
socks_proxy_conn_free_all(&p->http_conns, &p->http_conn_count);
|
|
|
|
struct tcp_proxy_client_conn* pc = p->conns;
|
|
while (pc) {
|
|
struct tcp_proxy_client_conn* next = pc->next;
|
|
proxy_flow_destroy(&pc->flow);
|
|
if (pc->pcb && !memory_pool_is_freed(p->lwip->pcb_pool, pc->pcb) && pc->pcb->state != CLOSED) {
|
|
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;
|
|
if (pc->to_lwip) {
|
|
struct ll_entry *e;
|
|
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
|
|
queue_free(pc->to_lwip); pc->to_lwip = NULL;
|
|
}
|
|
if (pc->tx_queue) {
|
|
struct ll_entry *e;
|
|
while ((e = queue_data_get(pc->tx_queue))) { queue_dgram_free(e); queue_entry_free(e); }
|
|
queue_free(pc->tx_queue); pc->tx_queue = NULL;
|
|
}
|
|
if (pc->tx_retry_timer) { uasync_cancel_timeout(p->ua, pc->tx_retry_timer); pc->tx_retry_timer = NULL; }
|
|
if (p->inst) etcp_router_cancel_send_ready(p->inst, TOPO_GROUP_UTUN, p->via_node_id, ETCP_RT_ID_TCP_PROXY_SERVER, &pc->tx_waiter);
|
|
u_free(pc); pc = next;
|
|
}
|
|
p->conns = NULL;
|
|
|
|
if (p->lwip) { lwip_tcp_destroy(p->lwip); p->lwip = NULL; }
|
|
|
|
if (p->tun) tun_close(p->tun);
|
|
if (p->entry_pool) memory_pool_destroy(p->entry_pool);
|
|
u_free(p);
|
|
}
|