stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
This commit is contained in:
1 parent
a3783b5f55
commit
38c9f229bc
1272 files changed
+968388
-1
No files matched your search
@@ -0,0 +1,477 @@
|
||||
## TCP Proxy Protocol — архитектура (v2, без CONNECTED)
|
||||
|
||||
### 1. Протокольный формат (общий для client и server)
|
||||
|
||||
```
|
||||
┌──────┬──────┬───────────────────────┬──────────────────────┐
|
||||
│svc_id│subcmd│ stream_id (4) │ data ... │
|
||||
│ 1B │ 1B │ │ │
|
||||
└──────┴──────┴───────────────────────┴──────────────────────┘
|
||||
HDR = 6 байт
|
||||
|
||||
subcmd:
|
||||
0x01 CONNECT client→server : dest_ip(4)+dest_port(2)
|
||||
0x03 DATA ↔ bidirectional: payload
|
||||
0x04 CLOSE ↔ bidirectional: (нет данных)
|
||||
0x05 ERROR ↔ bidirectional: (нет данных)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Диаграмма состояний `tcp_proxy_client_conn`
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ ALLOC + │ accept_cb: u_calloc, stream_id++
|
||||
│ CONNECT │ send CONNECT → exit
|
||||
│ отправлен │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ send успешен? │
|
||||
└───────────┬───────────┘
|
||||
Y │ N
|
||||
┌────────┘ └──────┐
|
||||
▼ ▼
|
||||
┌────────────┐ ┌──────────────┐
|
||||
│ ACTIVE │ │ FREE │
|
||||
│ traffic │ └──────────────┘
|
||||
└────┬──┬────┘
|
||||
│ │
|
||||
│ └─────────────────────────────────┐
|
||||
│ │
|
||||
▼ lwIP FIN (p=NULL) ▼ ERROR от lwIP/exit
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ TUN_CLOSED │ │ ERROR │
|
||||
│ tun_closed=1 │ │ error=1 │
|
||||
│ ↓ send_close │ │ send ERROR→ │
|
||||
│ после drain │ └──────┬───────┘
|
||||
│ tx_buf+lwIP │ │
|
||||
└──────┬───────┘ poll_cb: tcp_abort+free
|
||||
│
|
||||
│ recv CLOSE от exit
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ TUN+REM CLOSED │
|
||||
│ tun=1 rem=1 │
|
||||
│ ждём flush буферов│
|
||||
└────────┬──────────┘
|
||||
│ poll_cb: pending=0, unsent=NULL, unacked=NULL
|
||||
│ (или sndbuf==0)
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ CLEANUP │
|
||||
│ free(pc) │
|
||||
└──────────────┘
|
||||
|
||||
|
||||
ДОП. СОСТОЯНИЯ:
|
||||
┌─────────────────┐ send_data в ETCP вернул -1 (normalizer полон)
|
||||
│ TX_BACKPRESSURE│ не tcp_recved() → окно lwIP=0 → recv_cb остановлен
|
||||
│ tx_buf != NULL │ etcp_router_waiter_register() → ждём освобождения
|
||||
│ tx_waiter pend │ waiter_cb: retry send_data → tcp_recved → окно открыто
|
||||
└─────────────────┘
|
||||
|
||||
┌─────────────────┐ send_close/send_error вернул -1
|
||||
│ CLOSE_PENDING │ → retry в poll_cb
|
||||
│ close_pending=1│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Диаграмма состояний `tcp_proxy_server_conn`
|
||||
|
||||
```
|
||||
┌───────────────────┐
|
||||
│ CREATING │ recv CONNECT
|
||||
│ socket() создан │ → неблокирующий connect()
|
||||
│ sock = неблок. │
|
||||
└────────┬──────────┘
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ connect() результат? │
|
||||
└────┬──────────┬──────────┘
|
||||
EINPROGRESS│ │ошибка сразу
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────────┐
|
||||
│CONNECTING│ │ ERROR │ send ERROR→client
|
||||
│ждём write│ │ FREE │
|
||||
└────┬─────┘ └──────────────┘
|
||||
│ write_cb: SO_ERROR==0
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ CONNECTED │ connected=1
|
||||
│ flush pend │ → flush pending_buf в сокет
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
client→DATA │ │ ERROR (sock/pipe)
|
||||
(sock_send) │ ▼
|
||||
или pending_buf │ ┌──────────────┐
|
||||
│ │ │ ERROR │ send ERROR→client
|
||||
│ │ │ FREE │
|
||||
│ sock EOF (n=0) │ │
|
||||
│ │ └──────────────┘
|
||||
│ ▼
|
||||
│ ┌──────────────────┐
|
||||
│ │ SOCK_CLOSED │ sock_closed=1
|
||||
│ │ out_buf/pause? │ если очереди пусты → send_close
|
||||
│ │ ждём drain │ иначе ждём retry_cb/waiter_cb
|
||||
│ └────────┬─────────┘
|
||||
│ │ очереди опустошены → send_close
|
||||
│ │ если cli_closed → FREE
|
||||
│ │
|
||||
client→CLOSE │
|
||||
│ │
|
||||
▼ │
|
||||
┌──────────────┐ │
|
||||
│ CLI_CLOSED │ │ cli_closed=1
|
||||
│ out_buf pend? │◄────────┘ если out_buf пуст → shutdown(SHUT_WR)
|
||||
│ ждём flush │ иначе ждём sock_retry_cb → shutdown
|
||||
└──────┬───────┘
|
||||
│
|
||||
└──────────────┐
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ cli && sock │ оба закрыты + очереди пусты
|
||||
│ очереди пусты │
|
||||
└────────┬─────────┘
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ FREE │ conn_free(rc)
|
||||
└──────────────┘
|
||||
|
||||
|
||||
ДОП. СОСТОЯНИЯ:
|
||||
┌─────────────────┐ send_msg в ETCP вернул -1
|
||||
│ PAUSED │ буферизуем в pause_buf, убираем read_id (пауза чтения)
|
||||
│ pause_buf>0 │ etcp_router_waiter_register() → ждём освобождения
|
||||
│ pause_waiter │ waiter_cb: retry send_msg → resume read_id
|
||||
└─────────────────┘
|
||||
|
||||
┌─────────────────┐ CLOSE/ERROR не доставлен через ETCP
|
||||
│ CLOSE_PENDING │ close_timer с backoff 50..5000 tb → ретрай
|
||||
│ close_pending=1│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. DATA FLOW
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
APP → lwIP → recv_cb → ETCP → handle → socket → DEST
|
||||
APP ← lwIP ← tcp_write ← ETCP ← read_cb ← socket ← DEST
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
|
||||
CLIENT (узел A) SERVER (узел B)
|
||||
|
||||
APP (через TUN) DEST (реальный)
|
||||
│ │
|
||||
▼ TCP SYN/ACK (lwIP) ▼ TCP SYN/ACK (OS)
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ lwIP │ │ socket │
|
||||
│ TCP │ │ (неблок) │
|
||||
└────┬─────┘ └────┬─────┘
|
||||
│ recv_cb │ read_cb
|
||||
│ │
|
||||
┌────▼────────────┐ ┌────▼───────────────┐
|
||||
│ tcp_proxy │ ETCP │ tcp_proxy │
|
||||
│ _client │◄═══════════════════════►│ _server │
|
||||
│ │ │ │
|
||||
│ ┌────────────┐ │ │ ┌──────────────┐ │
|
||||
│ │ recv_cb │ │──CONNECT+DATA──► │ │handle_connect│ │
|
||||
│ │(lwIP→ETCP) │ │ │ │handle_data │ │
|
||||
│ │ │ │ backpressure: │ └──────┬───────┘ │
|
||||
│ │ send fail: │ │ waiter на normalizer │ │ sock_send │
|
||||
│ │ tx_buf+ │ │ │ ┌──────▼───────┐ │
|
||||
│ │ waiter │ │ │ │ out_buf │ │
|
||||
│ │ окно lwIP=0│ │ │ │ pending_buf │ │
|
||||
│ └────────────┘ │ │ └─────────────┘ │
|
||||
│ │ │ │ send() │
|
||||
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
|
||||
│ │handle_data │ │◄────DATA──────── │ │ read_cb │ │
|
||||
│ │handle_close│ │ │ │(sock→ETCP) │ │
|
||||
│ └─────┬──────┘ │ │ │ │ │
|
||||
│ │ │ │ │ send fail: │ │
|
||||
│ ┌─────▼──────┐ │ │ │ pause_buf+ │ │
|
||||
│ │ to_lwip │ │ │ │ waiter+ │ │
|
||||
│ │ (очередь) │ │ │ │ пауза read_id│ │
|
||||
│ └─────┬──────┘ │ │ └─────────────┘ │
|
||||
│ │tcp_write│ │ │
|
||||
│ ▼ │ └───────────────────┘
|
||||
│ lwIP→TUN→APP │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. BACKPRESSURE (lwIP→ETCP и socket→ETCP)
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Используется встроенный механизм ll_queue threshold waiter:
|
||||
etcp_router_waiter_register(inst, peer_node_id, &waiter)
|
||||
→ внутри: queue_waiter_wait(normalizer->input_queue, &waiter)
|
||||
etcp_router_waiter_cancel(inst, peer_node_id, &waiter)
|
||||
|
||||
CLIENT (lwIP → ETCP):
|
||||
recv_cb(buf, len):
|
||||
ret = send_data(buf, len)
|
||||
if ret == 0: tcp_recved(len)
|
||||
if ret < 0:
|
||||
tx_buf = copy(buf), tx_len = len
|
||||
НЕ tcp_recved() → окно lwIP = 0, recv_cb больше не вызывается
|
||||
etcp_router_waiter_register(&tx_waiter)
|
||||
|
||||
tx_waiter callback:
|
||||
ret = send_data(tx_buf, tx_len)
|
||||
if ret == 0:
|
||||
tcp_recved(tx_len) → окно открыто, lwIP возобновляет recv_cb
|
||||
free(tx_buf)
|
||||
if tun_closed → send_close()
|
||||
// если ret < 0 → waiter остаётся, normalizer вызовет снова
|
||||
|
||||
SERVER (socket → ETCP):
|
||||
read_cb(buf, n):
|
||||
ret = send_msg(DATA, buf, n)
|
||||
if ret == 0: ok
|
||||
if ret < 0:
|
||||
pause_buf = copy(buf), pause_len = n
|
||||
uasync_remove_socket_t(sock) → пауза чтения с сокета
|
||||
etcp_router_waiter_register(&pause_waiter)
|
||||
|
||||
pause_waiter callback:
|
||||
ret = send_msg(DATA, pause_buf, pause_len)
|
||||
if ret == 0:
|
||||
free(pause_buf)
|
||||
uasync_add_socket_t(sock) → возобновляем чтение
|
||||
if sock_closed → send_close()
|
||||
// если ret < 0 → waiter остаётся
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Последовательность: HANDSHAKE (упрощённый, без CONNECTED)
|
||||
|
||||
```
|
||||
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
|
||||
─────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
tcp_proxy_client_accept_cb() CONNECT
|
||||
├─ u_calloc(conn) ────────────────────────────→ tcp_proxy_server_handle_connect()
|
||||
├─ stream_id++ ├─ socket(AF_INET,SOCK_STREAM)
|
||||
├─ send_connect() ├─ socket_set_nonblocking()
|
||||
│ [dest_ip(4)+dest_port(2)] ├─ uasync_add_socket_t(read/write/error)
|
||||
├─ conn→conns list ├─ connect(неблок.) → EINPROGRESS
|
||||
│ └─ rc→conns list
|
||||
▼
|
||||
ACTIVE — данные идут СРАЗУ (без ожидания CONNECTED)
|
||||
│
|
||||
│ DATA ...connect завершён...
|
||||
├──────────────────────────────────────────────────→ tcp_proxy_server_sock_write_cb()
|
||||
│ ├─ connected=1
|
||||
│ DATA └─ flush pending_buf в сокет
|
||||
├──────────────────────────────────────────────────→
|
||||
│
|
||||
│ DATA ←─ tcp_proxy_server_sock_read_cb()
|
||||
│◄────────────────────────────────────────────── (socket recv → ETCP)
|
||||
│
|
||||
└──→ трафик идёт в обе стороны
|
||||
|
||||
При ошибке connect:
|
||||
...connect fail... → tcp_proxy_server_send_error() → client получает ERROR, error=1, cleanup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Последовательность: CLOSE (drain очередей → close в конце)
|
||||
|
||||
```
|
||||
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
|
||||
─────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
══ Вариант А: lwIP закрывает первым ══
|
||||
|
||||
tcp_proxy_client_recv_cb(p=NULL)
|
||||
├─ tun_closed=1
|
||||
│ если tx_buf пуст → send_close CLOSE
|
||||
│ если tx_buf pend → waiter досылает ──────────→ tcp_proxy_server_handle_close()
|
||||
│ потом send_close ├─ cli_closed=1
|
||||
└─ │ если out_buf пуст → shutdown(SHUT_WR)
|
||||
│ иначе ждём sock_retry_cb
|
||||
│
|
||||
...out_buf дослан...
|
||||
sock_retry_cb:
|
||||
shutdown(SHUT_WR)
|
||||
│
|
||||
...сокет дочитывает остаток...
|
||||
│
|
||||
tcp_proxy_server_sock_read_cb(n=0)
|
||||
├─ sock_closed=1
|
||||
│ если pause_buf пуст → send_close
|
||||
│ иначе ждём waiter_cb
|
||||
│
|
||||
CLOSE │
|
||||
tcp_proxy_client_handle_close() ◄────────────────────────┘
|
||||
└─ rem_closed=1
|
||||
│
|
||||
poll_cb: tun=1, rem=1, to_lwip пуст, unsent=NULL, unacked=NULL
|
||||
├─ tcp_close(pcb)
|
||||
└─ conn_free()
|
||||
|
||||
|
||||
══ Вариант Б: серверный сокет закрывается первым ══
|
||||
|
||||
tcp_proxy_server_sock_read_cb(n=0)
|
||||
├─ sock_closed=1
|
||||
│ если out_buf+pause_buf пусты → send_close
|
||||
│ иначе ждём retry/waiter
|
||||
CLOSE │
|
||||
tcp_proxy_client_handle_close() ◄────────────────────────┘
|
||||
└─ rem_closed=1
|
||||
│
|
||||
▼
|
||||
...приложение закрывает TCP...
|
||||
│
|
||||
tcp_proxy_client_recv_cb(p=NULL)
|
||||
├─ tun_closed=1
|
||||
│ если tx_buf пуст → send_close
|
||||
└─
|
||||
|
||||
poll_cb: оба закрыты, буферы пусты → cleanup
|
||||
|
||||
|
||||
══ Вариант В: Ошибка / аномалия ══
|
||||
|
||||
ERROR
|
||||
─────────────────────── (или ←) ──────────
|
||||
error=1
|
||||
│
|
||||
▼
|
||||
poll_cb: tcp_abort(pcb) → немедленная очистка
|
||||
или: rc→error=1 → conn_free
|
||||
|
||||
|
||||
══ Вариант Г: conn не найден / closed / error ══
|
||||
|
||||
Любой пришедший пакет (DATA/CLOSE/ERROR) для conn в закрытом состоянии:
|
||||
→ tcp_proxy_*_send_msg(ERROR, stream_id)
|
||||
→ удалённая сторона получает ERROR и немедленно закрывается
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. Диспетчеризация в `tcp_proxy_client_etcp_recv_cb()`
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ tcp_proxy_client_etcp_recv_cb() │
|
||||
│ (единая точка входа для обоих направлений) │
|
||||
├────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ subcmd == CONNECT? │
|
||||
│ └── tcp_proxy_server_handle_connect() ←── всегда сервер │
|
||||
│ │
|
||||
│ stream_id в server.conns? (если server.enabled) │
|
||||
│ ├── DATA → tcp_proxy_server_handle_data() │
|
||||
│ ├── CLOSE → tcp_proxy_server_handle_close() │
|
||||
│ └── ERROR → rc->error=1; conn_free() │
|
||||
│ │
|
||||
│ stream_id в client.conns? (если client proxy != NULL) │
|
||||
│ ├── DATA → tcp_proxy_client_handle_data() │
|
||||
│ ├── CLOSE → tcp_proxy_client_handle_close() │
|
||||
│ └── ERROR → tcp_proxy_client_handle_error() │
|
||||
│ │
|
||||
│ Приоритет: │
|
||||
│ 1. CONNECT всегда интерпретируется сервером │
|
||||
│ 2. Если server.enabled И stream_id найден в server.conns → server │
|
||||
│ 3. Иначе если client proxy != NULL → client │
|
||||
│ 4. Иначе drop │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. INIT
|
||||
|
||||
```
|
||||
CLIENT SERVER
|
||||
─────────────────────────────────────────────────────────
|
||||
|
||||
utun_instance.c
|
||||
│
|
||||
├─ tcp_proxy_client_create() tcp_proxy_server_init()
|
||||
│ ├─ tun_init_nat() (TUN iface) ├─ memset(ctx,0)
|
||||
│ ├─ lwip_tcp_init() (TCP стек) ├─ ctx->enabled = config ...
|
||||
│ ├─ etcp_router_bind( ├─ etcp_router_bind(
|
||||
│ │ ETCP_ID_TCP_PROXY, │ ETCP_ID_TCP_PROXY,
|
||||
│ │ tcp_proxy_client_etcp_recv_cb) │ tcp_proxy_client_etcp_recv_cb)
|
||||
│ ├─ udp_proxy_init() ├─ udp_proxy_init()
|
||||
│ └─ icmp_proxy_init() └─ icmp_proxy_init() (если client не активен)
|
||||
│
|
||||
└─ ОБА слушают ETCP_ID_TCP_PROXY через один обработчик
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. Поля структур
|
||||
|
||||
```
|
||||
tcp_proxy_client_conn:
|
||||
stream_id — уникальный ID потока
|
||||
tun_closed — lwIP/FIN (сторона TUN)
|
||||
rem_closed — CLOSE от exit
|
||||
error — немедленная очистка
|
||||
close_sent — CLOSE/ERROR отправлен
|
||||
close_pending — отправка не удалась, ретрай
|
||||
to_lwip — очередь DATA от exit → lwIP (feed_from_transport)
|
||||
pcb — lwIP tcp_pcb
|
||||
tx_buf — буфер при backpressure lwIP→ETCP (send_data fail)
|
||||
tx_len — длина tx_buf
|
||||
tx_waiter — ll_queue waiter на normalizer.input_queue
|
||||
|
||||
tcp_proxy_server_conn:
|
||||
stream_id — уникальный ID потока
|
||||
cli_closed — клиент прислал CLOSE
|
||||
sock_closed — сокет получил EOF
|
||||
error — ошибка
|
||||
connected — сокет подключился (connect завершён)
|
||||
close_pending — CLOSE/ERROR не доставлен, ретрай
|
||||
pending_buf — буфер данных от клиента до завершения connect
|
||||
out_buf — буфер отправки в сокет (неблокирующий send, EAGAIN retry)
|
||||
out_off/out_len — offset/размер out_buf
|
||||
out_timer — таймер повтора send в сокет
|
||||
out_backoff — backoff для retry send в сокет
|
||||
pause_buf — буфер при backpressure socket→ETCP (send_msg fail)
|
||||
pause_len — длина pause_buf
|
||||
pause_waiter — ll_queue waiter на normalizer.input_queue
|
||||
close_timer — таймер повтора CLOSE/ERROR
|
||||
close_backoff — backoff для retry CLOSE/ERROR
|
||||
sock — OS сокет к адресату
|
||||
read_id — handle uasync для событий сокета
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11. etcp_router waiter API
|
||||
|
||||
```
|
||||
// etcp_router.h
|
||||
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst,
|
||||
uint64_t peer_node_id, struct queue_waiter_handle* h);
|
||||
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst,
|
||||
uint64_t peer_node_id, struct queue_waiter_handle* h);
|
||||
|
||||
// Внутри:
|
||||
// conn = route_bgp_find_conn_for_node(peer_node_id)
|
||||
// queue_waiter_wait(conn->normalizer->input, h)
|
||||
// queue_waiter_cancel(conn->normalizer->input, h)
|
||||
//
|
||||
// normalizer.input_queue уже имеет threshold=0 (ждёт полного освобождения)
|
||||
// через queue_set_threshold(input_queue, 0, 0) в etcp.c
|
||||
```
|
||||
@@ -0,0 +1,367 @@
|
||||
// icmp_proxy.c — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
|
||||
#include "icmp_proxy.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "tun_if.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#else
|
||||
#define ICMP_MINLEN 8
|
||||
#define ICMP_ECHO 8
|
||||
#define ICMP_ECHOREPLY 0
|
||||
struct icmp {
|
||||
uint8_t icmp_type;
|
||||
uint8_t icmp_code;
|
||||
uint16_t icmp_cksum;
|
||||
uint16_t icmp_id;
|
||||
uint16_t icmp_seq;
|
||||
uint8_t icmp_data[1];
|
||||
};
|
||||
struct ip {
|
||||
uint8_t ip_hl:4, ip_v:4;
|
||||
uint8_t ip_tos;
|
||||
uint16_t ip_len;
|
||||
uint16_t ip_id;
|
||||
uint16_t ip_off;
|
||||
uint8_t ip_ttl;
|
||||
uint8_t ip_p;
|
||||
uint16_t ip_sum;
|
||||
struct in_addr ip_src, ip_dst;
|
||||
};
|
||||
#endif
|
||||
|
||||
#define ICMP_DEF_TTL 64
|
||||
#define ICMP_TIMEOUT_TB 50000 // 5s for echo reply
|
||||
|
||||
static struct icmp_proxy_ctx* g_icmp_ctx = NULL;
|
||||
|
||||
static void req_expire_timer_cb(void* arg);
|
||||
static void req_expire(struct icmp_proxy_ctx* ctx);
|
||||
|
||||
static struct icmp_request* req_find_by_id(struct icmp_request* head, uint16_t echo_id, uint16_t echo_seq) {
|
||||
struct icmp_request* r;
|
||||
for (r = head; r; r = r->next) if (r->echo_id == echo_id && r->echo_seq == echo_seq) return r;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Сборка и отправка raw ICMP echo запроса
|
||||
static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id,
|
||||
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return -1;
|
||||
size_t icmp_len = ICMP_MINLEN + payload_len;
|
||||
uint8_t* buf = u_malloc(icmp_len);
|
||||
if (!buf) return -1;
|
||||
|
||||
struct icmp* icmp_hdr = (struct icmp*)buf;
|
||||
memset(icmp_hdr, 0, icmp_len);
|
||||
icmp_hdr->icmp_type = ICMP_ECHO;
|
||||
icmp_hdr->icmp_code = 0;
|
||||
icmp_hdr->icmp_id = echo_id;
|
||||
icmp_hdr->icmp_seq = echo_seq;
|
||||
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
|
||||
icmp_hdr->icmp_cksum = 0;
|
||||
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
|
||||
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto dst=0x%08x id=0x%04x seq=%u len=%zu",
|
||||
dst_ip, echo_id, echo_seq, icmp_len);
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}};
|
||||
ssize_t n = sendto(g_icmp_ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr));
|
||||
u_free(buf);
|
||||
if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto failed: %s", strerror(errno)); return -1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto sent %zd bytes", n);
|
||||
|
||||
struct icmp_request* r = u_calloc(1, sizeof(struct icmp_request));
|
||||
if (!r) return 0;
|
||||
r->client_node_id = client_node_id; r->dst_ip = dst_ip; r->orig_src_ip = orig_src_ip;
|
||||
r->echo_id = echo_id; r->echo_seq = echo_seq;
|
||||
r->payload_len = payload_len > sizeof(r->payload) ? sizeof(r->payload) : payload_len;
|
||||
if (payload_len > 0) memcpy(r->payload, payload, r->payload_len);
|
||||
r->sent_tb = get_time_tb();
|
||||
r->next = g_icmp_ctx->pending; g_icmp_ctx->pending = r;
|
||||
if (!g_icmp_ctx->expire_timer)
|
||||
g_icmp_ctx->expire_timer = uasync_set_timeout(g_icmp_ctx->ua, g_icmp_ctx->request_timeout_tb, g_icmp_ctx, req_expire_timer_cb, "icmp_expire");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Чтение echo ответа из raw сокета, сопоставление по echo_id
|
||||
static void raw_read_cb(socket_t sock, void* arg) {
|
||||
(void)sock; (void)arg;
|
||||
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return;
|
||||
uint8_t buf[65536];
|
||||
struct sockaddr_in from; socklen_t flen = sizeof(from);
|
||||
ssize_t n = recvfrom(g_icmp_ctx->raw_sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
|
||||
if (n <= 0) { if (n < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: recvfrom error: %s", strerror(errno)); return; }
|
||||
if (n < (ssize_t)(sizeof(struct ip) + ICMP_MINLEN)) return;
|
||||
|
||||
struct ip* ip_hdr = (struct ip*)buf;
|
||||
if (ip_hdr->ip_p != IPPROTO_ICMP) return;
|
||||
size_t ip_hdr_len = ip_hdr->ip_hl * 4;
|
||||
if (n < (ssize_t)(ip_hdr_len + ICMP_MINLEN)) return;
|
||||
struct icmp* icmp_hdr = (struct icmp*)(buf + ip_hdr_len);
|
||||
if (icmp_hdr->icmp_type != ICMP_ECHOREPLY) return;
|
||||
|
||||
struct icmp_request* r = req_find_by_id(g_icmp_ctx->pending, icmp_hdr->icmp_id, icmp_hdr->icmp_seq);
|
||||
if (!r) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "icmp_proxy: unclaimed echo reply id=0x%04x seq=%u from=0x%08x",
|
||||
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr); return; }
|
||||
{ struct icmp_request** rp = &g_icmp_ctx->pending;
|
||||
while (*rp) { if (*rp == r) { *rp = r->next; break; } rp = &(*rp)->next; } }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: echo reply id=0x%04x seq=%u from=0x%08x",
|
||||
icmp_hdr->icmp_id, icmp_hdr->icmp_seq, from.sin_addr.s_addr);
|
||||
|
||||
size_t payload_len = n - ip_hdr_len - ICMP_MINLEN;
|
||||
if (payload_len > 1500) payload_len = 1500;
|
||||
uint8_t* payload = (payload_len > 0) ? (uint8_t*)(icmp_hdr->icmp_data) : NULL;
|
||||
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) return;
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return; }
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
|
||||
memcpy(e->dgram + 2, &r->client_node_id, 8);
|
||||
memcpy(e->dgram + 10, &r->dst_ip, 4);
|
||||
memcpy(e->dgram + 14, &r->orig_src_ip, 4);
|
||||
memcpy(e->dgram + 18, &icmp_hdr->icmp_id, 2);
|
||||
memcpy(e->dgram + 20, &icmp_hdr->icmp_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
int ret = etcp_route_send(g_icmp_ctx->inst, TOPO_GROUP_UTUN, r->client_node_id, e, 0);
|
||||
if (ret != 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: etcp_route_send reply failed: %d", ret);
|
||||
else DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply forwarded to client %016llx", (unsigned long long)r->client_node_id);
|
||||
u_free(r);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Exit узел: принять REQUEST, отправить echo через raw сокет
|
||||
// ====================================================================
|
||||
static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
|
||||
if (!inst || !g_icmp_ctx || entry->len < ICMP_PROXY_HDR_SIZE + 1) goto drop;
|
||||
|
||||
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8);
|
||||
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 10, 4);
|
||||
uint32_t orig_src_ip; memcpy(&orig_src_ip, entry->dgram + 14, 4);
|
||||
uint16_t echo_id; memcpy(&echo_id, entry->dgram + 18, 2);
|
||||
uint16_t echo_seq; memcpy(&echo_seq, entry->dgram + 20, 2);
|
||||
uint8_t* payload = entry->dgram + ICMP_PROXY_HDR_SIZE;
|
||||
size_t payload_len = entry->len - ICMP_PROXY_HDR_SIZE;
|
||||
|
||||
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
|
||||
exit_send_echo(inst, client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, payload_len);
|
||||
} else if (g_icmp_ctx->test_loopback) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: test loopback reply to 0x%08x", dst_ip);
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (e) {
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (e->dgram) {
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY;
|
||||
memcpy(e->dgram + 2, &client_node_id, 8);
|
||||
memcpy(e->dgram + 10, &dst_ip, 4);
|
||||
memcpy(e->dgram + 14, &orig_src_ip, 4);
|
||||
memcpy(e->dgram + 18, &echo_id, 2);
|
||||
memcpy(e->dgram + 20, &echo_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
etcp_route_send(inst, TOPO_GROUP_UTUN, client_node_id, e, 0);
|
||||
} else queue_entry_free(e);
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: no raw socket, dropping echo request to 0x%08x", dst_ip);
|
||||
}
|
||||
|
||||
drop:
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: принять REPLY, доставить echo ответ в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
uint32_t src_ip;
|
||||
uint32_t orig_src_ip;
|
||||
uint16_t echo_id, echo_seq;
|
||||
memcpy(&src_ip, entry->dgram + 10, 4);
|
||||
memcpy(&orig_src_ip, entry->dgram + 14, 4);
|
||||
memcpy(&echo_id, entry->dgram + 18, 2);
|
||||
memcpy(&echo_seq, entry->dgram + 20, 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: client got reply id=0x%04x seq=%u from=0x%08x dst=0x%08x",
|
||||
echo_id, echo_seq, src_ip, orig_src_ip);
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL);
|
||||
icmp_proxy_deliver_reply(inst, orig_src_ip, src_ip, echo_id, echo_seq,
|
||||
entry->dgram + ICMP_PROXY_HDR_SIZE, entry->len - ICMP_PROXY_HDR_SIZE);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Единый etcp_router коллбэк
|
||||
// ====================================================================
|
||||
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
if (subcmd == ICMP_PROXY_SUBCMD_REQUEST) { exit_handle_request(conn, entry); return; }
|
||||
if (subcmd == ICMP_PROXY_SUBCMD_REPLY) { client_handle_reply(conn, entry); return; }
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: отправить ICMP echo запрос в exit узел
|
||||
// ====================================================================
|
||||
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst) return -1;
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) return -1;
|
||||
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_RT_ID_ICMP_PROXY;
|
||||
e->dgram[1] = ICMP_PROXY_SUBCMD_REQUEST;
|
||||
memcpy(e->dgram + 2, &inst->node_id, 8);
|
||||
memcpy(e->dgram + 10, &dst_ip, 4);
|
||||
memcpy(e->dgram + 14, &orig_src_ip, 4);
|
||||
memcpy(e->dgram + 18, &echo_id, 2);
|
||||
memcpy(e->dgram + 20, &echo_seq, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = ICMP_PROXY_HDR_SIZE + payload_len;
|
||||
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: доставить ICMP echo ответ в TUN
|
||||
// ====================================================================
|
||||
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
|
||||
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
|
||||
return -1;
|
||||
}
|
||||
struct tun_if* tun = inst->tcp_proxy_client->tun;
|
||||
|
||||
size_t icmp_len = ICMP_MINLEN + payload_len;
|
||||
size_t pkt_len = 20 + icmp_len;
|
||||
uint8_t* pkt = u_malloc(pkt_len);
|
||||
if (!pkt) return -1;
|
||||
|
||||
memset(pkt, 0, 20);
|
||||
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_ICMP;
|
||||
uint16_t total_len = htons(20 + icmp_len);
|
||||
memcpy(pkt + 2, &total_len, 2);
|
||||
memcpy(pkt + 12, &src_ip, 4);
|
||||
memcpy(pkt + 16, &dst_ip, 4);
|
||||
|
||||
{ uint32_t ip_sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); ip_sum += w; }
|
||||
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16); ip_sum += (ip_sum >> 16);
|
||||
uint16_t cs = (uint16_t)(~ip_sum); memcpy(pkt + 10, &cs, 2); }
|
||||
|
||||
struct icmp* icmp_hdr = (struct icmp*)(pkt + 20);
|
||||
icmp_hdr->icmp_type = ICMP_ECHOREPLY; icmp_hdr->icmp_code = 0;
|
||||
icmp_hdr->icmp_id = echo_id; icmp_hdr->icmp_seq = echo_seq;
|
||||
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len);
|
||||
icmp_hdr->icmp_cksum = 0;
|
||||
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr;
|
||||
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i];
|
||||
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
icmp_hdr->icmp_cksum = ~(uint16_t)sum;
|
||||
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { u_free(pkt); return -1; }
|
||||
e->dgram = u_malloc(1 + pkt_len);
|
||||
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len;
|
||||
u_free(pkt);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: reply delivered to TUN id=0x%04x seq=%u", echo_id, echo_seq);
|
||||
queue_data_put(tun->input_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled) {
|
||||
(void)inst;
|
||||
if (g_icmp_ctx) g_icmp_ctx->test_loopback = enabled;
|
||||
}
|
||||
|
||||
static void req_expire_timer_cb(void* arg) {
|
||||
struct icmp_proxy_ctx* ctx = (struct icmp_proxy_ctx*)arg;
|
||||
req_expire(ctx);
|
||||
ctx->expire_timer = NULL;
|
||||
if (ctx->pending)
|
||||
ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire");
|
||||
}
|
||||
|
||||
static void req_expire(struct icmp_proxy_ctx* ctx) {
|
||||
uint64_t now = get_time_tb(); struct icmp_request** prev = &ctx->pending;
|
||||
while (*prev) {
|
||||
struct icmp_request* r = *prev;
|
||||
if (now - r->sent_tb > ctx->request_timeout_tb) { *prev = r->next; u_free(r); }
|
||||
else prev = &r->next;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичные init/destroy
|
||||
// ====================================================================
|
||||
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
if (!inst) return -1;
|
||||
struct icmp_proxy_ctx* ctx = u_calloc(1, sizeof(struct icmp_proxy_ctx));
|
||||
if (!ctx) return -1;
|
||||
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID;
|
||||
ctx->request_timeout_tb = ICMP_TIMEOUT_TB;
|
||||
ctx->is_exit = inst->tcp_proxy_server.enabled;
|
||||
ctx->test_loopback = 0;
|
||||
ctx->expire_timer = NULL;
|
||||
g_icmp_ctx = ctx;
|
||||
|
||||
if (ctx->is_exit) {
|
||||
ctx->raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
if (ctx->raw_sock == SOCKET_INVALID)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket(SOCK_RAW) failed: %s", strerror(errno));
|
||||
else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket created fd=%d", ctx->raw_sock);
|
||||
ctx->raw_read_id = uasync_add_socket_t(ua, ctx->raw_sock, raw_read_cb, NULL, NULL, NULL);
|
||||
if (!ctx->raw_read_id) { socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; }
|
||||
}
|
||||
}
|
||||
|
||||
etcp_router_bind(inst, ETCP_RT_ID_ICMP_PROXY, icmp_proxy_recv_cb);
|
||||
ctx->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !g_icmp_ctx) return;
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_ICMP_PROXY);
|
||||
if (g_icmp_ctx->expire_timer) { uasync_cancel_timeout(g_icmp_ctx->ua, g_icmp_ctx->expire_timer); g_icmp_ctx->expire_timer = NULL; }
|
||||
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) {
|
||||
if (g_icmp_ctx->raw_read_id) { uasync_remove_socket_t(g_icmp_ctx->ua, g_icmp_ctx->raw_sock); g_icmp_ctx->raw_read_id = NULL; }
|
||||
socket_close_wrapper(g_icmp_ctx->raw_sock);
|
||||
}
|
||||
struct icmp_request* r = g_icmp_ctx->pending;
|
||||
while (r) { struct icmp_request* n = r->next; u_free(r); r = n; }
|
||||
u_free(g_icmp_ctx); g_icmp_ctx = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy destroyed");
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// icmp_proxy.h — ICMP echo (ping) прокси: клиент ↔ exit через etcp_router
|
||||
#ifndef ICMP_PROXY_H
|
||||
#define ICMP_PROXY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct ll_entry;
|
||||
struct ETCP_CONN;
|
||||
|
||||
// Подкоманды
|
||||
#define ICMP_PROXY_SUBCMD_REQUEST 0x01 // client→exit: пингани dst_ip
|
||||
#define ICMP_PROXY_SUBCMD_REPLY 0x02 // exit→client: echo reply
|
||||
|
||||
// Заголовок сообщения (не включая байт svc_id)
|
||||
// svc_id(1) + subcmd(1) + sender_node_id(8) + dst_ip(4) + orig_src_ip(4) + icmp_id(2) + icmp_seq(2) + payload
|
||||
#define ICMP_PROXY_HDR_SIZE 22
|
||||
|
||||
// Активный ICMP echo запрос (сторона exit узла)
|
||||
struct icmp_request {
|
||||
struct icmp_request* next;
|
||||
uint64_t client_node_id;
|
||||
uint32_t dst_ip;
|
||||
uint32_t orig_src_ip; // IP исходного отправителя (чтобы вернуть reply правильному адресату)
|
||||
uint16_t echo_id;
|
||||
uint16_t echo_seq;
|
||||
uint8_t payload[1500];
|
||||
size_t payload_len;
|
||||
uint64_t sent_tb;
|
||||
socket_t sock; // raw сокет (только если per-request)
|
||||
};
|
||||
|
||||
struct icmp_proxy_ctx {
|
||||
int initialized;
|
||||
int is_exit;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct UASYNC* ua;
|
||||
socket_t raw_sock; // один SOCK_RAW для всего ICMP на exit
|
||||
void* raw_read_id;
|
||||
struct icmp_request* pending;
|
||||
uint64_t request_timeout_tb;
|
||||
int test_loopback; // 1 = виртуальный loopback без raw сокета (только тесты)
|
||||
void* expire_timer; // периодический таймер очистки истёкших запросов
|
||||
};
|
||||
|
||||
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua);
|
||||
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
// Единый etcp_router коллбэк
|
||||
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
// Сторона клиента: принять IP/ICMP echo request с TUN, отправить через etcp
|
||||
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
uint32_t dst_ip, uint32_t orig_src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len);
|
||||
|
||||
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq,
|
||||
const uint8_t* payload, size_t payload_len);
|
||||
|
||||
void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // ICMP_PROXY_H
|
||||
@@ -0,0 +1,77 @@
|
||||
# ICMP Proxy (icmp_proxy)
|
||||
|
||||
## 1. Назначение
|
||||
Проксирование ICMP Echo (ping) между клиентским и exit-узлом по протоколу ETCP. Клиент
|
||||
принимает ICMP Echo Request с TUN-интерфейса, упаковывает запрос и отправляет через
|
||||
`etcp_route_send` на exit. Exit-узел через нативный `SOCK_RAW`-сокет отправляет ICMP Echo
|
||||
во внешнюю сеть, сопоставляет ответы по `echo_id`/`echo_seq` и возвращает клиенту. Клиент
|
||||
восстанавливает IP/ICMP Echo Reply и доставляет в TUN.
|
||||
|
||||
Один экземпляр контекста на процесс (глобальный `g_icmp_ctx`). Режим определяется флагом
|
||||
`inst->tcp_proxy_server.enabled`. Для тестов предусмотрен loopback-режим без raw-сокета.
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
### Инициализация
|
||||
```c
|
||||
icmp_proxy_init(inst, ua); // exit создаёт SOCK_RAW, клиент — нет
|
||||
```
|
||||
При инициализации регистрируется коллбэк `icmp_proxy_recv_cb` в `etcp_router` для сервиса
|
||||
`ETCP_RT_ID_ICMP_PROXY`. На exit создаётся один `SOCK_RAW(IPPROTO_ICMP)` на весь модуль.
|
||||
|
||||
### Клиент: отправка ping-запроса
|
||||
```c
|
||||
icmp_proxy_send_to_exit(inst, exit_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, len);
|
||||
```
|
||||
Вызывается из обработчика TUN-трафика. Запрос упаковывается и отправляется через `etcp_route_send`.
|
||||
Ответ приходит асинхронно в `icmp_proxy_recv_cb` (REPLY), где вызывается `icmp_proxy_deliver_reply`
|
||||
для сборки IP/ICMP Echo Reply и записи в TUN.
|
||||
|
||||
### Тестовый loopback
|
||||
```c
|
||||
icmp_proxy_set_test_loopback(inst, 1); // exit отвечает эмуляцией без raw-сокета
|
||||
```
|
||||
В этом режиме exit не отправляет реальный ICMP Echo, а сразу возвращает REPLY клиенту — полезно
|
||||
для тестирования прохождения пакетов через ETCP без необходимости в SOCK_RAW и внешней сети.
|
||||
|
||||
### Очистка
|
||||
```c
|
||||
icmp_proxy_destroy(inst); // закрывает raw-сокет, удаляет pending-запросы, unbind
|
||||
```
|
||||
|
||||
### Нюансы
|
||||
- Запросы истекают через 5 секунд (`ICMP_TIMEOUT_TB`). Таймер `expire_timer` запускается
|
||||
при добавлении первого запроса и перезапускается после каждой очистки.
|
||||
- Неопознанные echo-ответы (не найденные в pending) логируются как `DEBUG_WARN` и игнорируются.
|
||||
- Контрольная сумма ICMP пересчитывается на exit (при отправке) и на клиенте (при сборке ответа).
|
||||
- Поле `orig_src_ip` в протоколе нужно для восстановления исходного dst_ip на клиенте.
|
||||
|
||||
## 3. API
|
||||
|
||||
### Структуры
|
||||
| Структура | Назначение |
|
||||
|-----------|-----------|
|
||||
| `struct icmp_request` | Состояние активного echo-запроса на exit: client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, время отправки. Связный список. |
|
||||
| `struct icmp_proxy_ctx` | Глобальный контекст: is_exit, inst, ua, raw_sock, raw_read_id, список pending, таймаут запросов, флаг test_loopback, таймер очистки. |
|
||||
|
||||
### Формат пакета
|
||||
| Смещение | Размер | Поле |
|
||||
|----------|--------|------|
|
||||
| 0 | 1 | `svc_id` = `ETCP_RT_ID_ICMP_PROXY` |
|
||||
| 1 | 1 | `subcmd` = REQUEST (0x01) / REPLY (0x02) |
|
||||
| 2 | 8 | `sender_node_id` |
|
||||
| 10 | 4 | `dst_ip` (IP цели для ping) |
|
||||
| 14 | 4 | `orig_src_ip` (IP исходного отправителя для обратной доставки) |
|
||||
| 18 | 2 | `echo_id` (icmp_id) |
|
||||
| 20 | 2 | `echo_seq` (icmp_seq) |
|
||||
| 22 | N | `payload` (данные ICMP echo) |
|
||||
|
||||
### Функции
|
||||
| Функция | Назначение |
|
||||
|---------|-----------|
|
||||
| `icmp_proxy_init(inst, ua)` | Выделяет контекст, определяет is_exit, на exit создаёт SOCK_RAW и регистрирует его в uasync. Регистрирует коллбэк в etcp_router. |
|
||||
| `icmp_proxy_destroy(inst)` | Закрывает raw-сокет, отменяет таймер, освобождает pending, отвязывает коллбэк, освобождает память. |
|
||||
| `icmp_proxy_recv_cb(conn, entry)` | Единый коллбэк etcp_router. По subcmd направляет REQUEST → `exit_handle_request`, REPLY → `client_handle_reply`. |
|
||||
| `icmp_proxy_send_to_exit(inst, ...)` | Клиент: упаковывает ICMP Echo Request и отправляет через etcp_route_send на exit. |
|
||||
| `icmp_proxy_deliver_reply(inst, ...)` | Клиент: собирает IP/ICMP Echo Reply-пакет и помещает в TUN input_queue. |
|
||||
| `icmp_proxy_set_test_loopback(inst, enabled)` | Включает/выключает тестовый loopback-режим на exit (эмуляция echo-ответа без raw-сокета). |
|
||||
@@ -0,0 +1,106 @@
|
||||
Как узнать, что TCP полностью завершился?
|
||||
|
||||
В raw API прямого callback'а вида:
|
||||
|
||||
on_connection_closed()
|
||||
|
||||
нет.
|
||||
|
||||
Обычно используют собственное состояние:
|
||||
|
||||
struct conn {
|
||||
bool closing;
|
||||
};
|
||||
|
||||
и после успешного:
|
||||
|
||||
tcp_close(pcb);
|
||||
conn->closing = true;
|
||||
|
||||
считают соединение закрытым для приложения.
|
||||
|
||||
Сам TCP завершится позже внутри lwIP.
|
||||
|
||||
А вызовется ли err callback?
|
||||
|
||||
Обычно нет.
|
||||
|
||||
tcp_err()
|
||||
|
||||
вызывается при ненормальном завершении:
|
||||
|
||||
RST
|
||||
abort
|
||||
потеря PCB
|
||||
сетевые ошибки
|
||||
|
||||
но не при штатном FIN/ACK закрытии.
|
||||
|
||||
Нужно ли освобождать память после tcp_close()?
|
||||
|
||||
Сам pcb освобождать нельзя:
|
||||
|
||||
tcp_close(pcb);
|
||||
|
||||
/* НЕЛЬЗЯ */
|
||||
mem_free(pcb);
|
||||
|
||||
lwIP сделает это самостоятельно.
|
||||
|
||||
Но ваши данные (arg) нужно освободить самостоятельно.
|
||||
|
||||
Обычно так:
|
||||
|
||||
struct my_conn *c = arg;
|
||||
|
||||
tcp_arg(pcb, NULL);
|
||||
|
||||
free(c);
|
||||
|
||||
tcp_close(pcb);
|
||||
|
||||
или
|
||||
|
||||
free(c);
|
||||
tcp_close(pcb);
|
||||
pcb = NULL;
|
||||
|
||||
если после этого код больше не обращается к c.
|
||||
|
||||
Безопасная практика
|
||||
|
||||
После успешного tcp_close():
|
||||
|
||||
if (tcp_close(pcb) == ERR_OK) {
|
||||
pcb = NULL;
|
||||
}
|
||||
|
||||
и считать объект недействительным.
|
||||
|
||||
Потому что lwIP может уничтожить его в любой момент по мере завершения TCP state machine.
|
||||
|
||||
Типичный шаблон
|
||||
static void close_conn(struct my_conn *c)
|
||||
{
|
||||
err_t err;
|
||||
|
||||
err = tcp_close(c->pcb);
|
||||
|
||||
if (err == ERR_OK) {
|
||||
c->pcb = NULL;
|
||||
free(c);
|
||||
} else {
|
||||
/* ERR_MEM -> попробуем позже */
|
||||
}
|
||||
}
|
||||
|
||||
Но если free(c) делается сразу, нужно быть уверенным, что никакие callback'и lwIP больше не используют arg. Часто перед закрытием сначала снимают callback'и:
|
||||
|
||||
tcp_arg(pcb, NULL);
|
||||
tcp_recv(pcb, NULL);
|
||||
tcp_sent(pcb, NULL);
|
||||
tcp_err(pcb, NULL);
|
||||
|
||||
а затем вызывают tcp_close().
|
||||
|
||||
Это особенно важно в сложных приложениях с собственными структурами соединений.
|
||||
@@ -0,0 +1,621 @@
|
||||
// socks_proxy.c — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#include "socks_proxy.h"
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
static void on_accept_cb(socket_t sock, void* arg);
|
||||
static void on_read_cb(struct ll_queue* q, void* arg);
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg);
|
||||
|
||||
static int 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);
|
||||
|
||||
struct listen_ctx {
|
||||
socket_t listen_sock;
|
||||
void* socket_id;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
int is_http;
|
||||
struct socks_proxy_conn** conns;
|
||||
int* conn_count;
|
||||
uint32_t* next_stream_id;
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// Отправка сообщений через ETCP
|
||||
// ====================================================================
|
||||
static int 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) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
|
||||
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_RT_ID_TCP_PROXY;
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = (uint16_t)(TCP_PROXY_HDR_SIZE + len);
|
||||
int ret = etcp_route_send(inst, group_id, dst, e, force);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send_msg subcmd=%02x sid=%08x len=%zu force=%d → ret=%d",
|
||||
subcmd, sid, len, force, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_connect(struct socks_proxy_conn* c) {
|
||||
uint8_t buf[6];
|
||||
memcpy(buf, c->dest_ip, 4); memcpy(buf + 4, &c->dest_port, 2);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCKS proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via_node=%016llx %s",
|
||||
c->stream_id, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3],
|
||||
ntohs(c->dest_port), (unsigned long long)c->via_node_id, c->is_http ? "http" : "socks");
|
||||
return send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_CONNECT, c->stream_id, buf, 6, 1);
|
||||
}
|
||||
|
||||
static int send_data(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
return send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_DATA, c->stream_id, data, len, 0);
|
||||
}
|
||||
|
||||
static void send_close(struct socks_proxy_conn* c) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send CLOSE sid=%08x", c->stream_id);
|
||||
if (send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_CLOSE, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_error(struct socks_proxy_conn* c) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send ERROR sid=%08x", c->stream_id);
|
||||
if (send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_ERROR, c->stream_id, NULL, 0, 1) < 0) c->close_pending = 1;
|
||||
else { c->close_pending = 0; c->close_sent = 1; }
|
||||
}
|
||||
|
||||
static void send_fin(struct socks_proxy_conn* c) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS send FIN sid=%08x", c->stream_id);
|
||||
send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, TCP_PROXY_SUBCMD_FIN, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись ответа в TCP клиенту через write_queue
|
||||
// ====================================================================
|
||||
static int write_to_client(struct socks_proxy_conn* c, const uint8_t* data, uint16_t len) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client alloc failed sid=%08x", c->stream_id);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
if (len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: write_to_client len=%u > pool_sz=%zu sid=%08x",
|
||||
len, c->tc->data_pool->object_size, c->stream_id);
|
||||
queue_entry_free(e); memory_pool_free(c->tc->data_pool, buf);
|
||||
return -1;
|
||||
}
|
||||
memcpy(buf, data, len);
|
||||
e->dgram = buf; e->len = len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
c->bytes_to_client += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// SOCKS5 handshake
|
||||
// ====================================================================
|
||||
static void process_socks_greeting(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 3) return;
|
||||
uint8_t ver = c->buf[0], nmethods = c->buf[1];
|
||||
if (ver != 5 || c->buf_len < (uint16_t)(2 + nmethods)) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: greeting ver=%d nmethods=%d", ver, nmethods);
|
||||
uint8_t reply[] = { 0x05, 0x00 };
|
||||
write_to_client(c, reply, 2);
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_REQUEST;
|
||||
}
|
||||
|
||||
static void process_socks_request(struct socks_proxy_conn* c) {
|
||||
if (c->buf_len < 10) return;
|
||||
uint8_t ver = c->buf[0], cmd = c->buf[1], atyp = c->buf[3];
|
||||
if (ver != 5) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad ver=%d", ver); goto error; }
|
||||
if (cmd != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported cmd=%d (only CONNECT supported)", cmd); goto error; }
|
||||
|
||||
uint16_t need;
|
||||
if (atyp == 1) need = 10; // IPv4: 4+2=6 more bytes
|
||||
else if (atyp == 3) { // domain: 1+len+2
|
||||
if (c->buf_len < 5) return;
|
||||
need = (uint16_t)(5 + c->buf[4] + 2);
|
||||
}
|
||||
else if (atyp == 4) need = 22; // IPv6: 16+2=18 more
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: unsupported atyp=%d", atyp); goto error; }
|
||||
|
||||
if (c->buf_len < need) return;
|
||||
|
||||
if (atyp == 1) {
|
||||
memcpy(c->dest_ip, c->buf + 4, 4);
|
||||
memcpy(&c->dest_port, c->buf + 8, 2);
|
||||
} else if (atyp == 4) {
|
||||
// Извлекаем первые 4 байта IPv6 в dest_ip (для упрощения: IPv6 mapped IPv4 или реальный IPv6)
|
||||
memcpy(c->dest_ip, c->buf + 12, 4);
|
||||
memcpy(&c->dest_port, c->buf + 20, 2);
|
||||
// TODO: proper IPv6 support
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: IPv6 unsupported, using last 4 bytes of addr");
|
||||
} else { // atyp == 3 (domain)
|
||||
uint8_t dlen = c->buf[4];
|
||||
char domain[256]; memcpy(domain, c->buf + 5, dlen); domain[dlen] = '\0';
|
||||
memcpy(&c->dest_port, c->buf + 5 + dlen, 2);
|
||||
// resolve domain
|
||||
struct hostent* he = gethostbyname(domain);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", domain);
|
||||
goto error;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: resolved %s → %d.%d.%d.%d:%d",
|
||||
domain, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], ntohs(c->dest_port));
|
||||
}
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = SOCKS_STATE_RELAY; // relay immediately, no waiting for first DATA
|
||||
uint8_t reply[] = { 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, reply, 10);
|
||||
if (send_connect(c) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed sid=%08x", c->stream_id);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t err_reply[] = { 0x05, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
write_to_client(c, err_reply, 10);
|
||||
tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// HTTP CONNECT / HTTP proxy handshake
|
||||
// ====================================================================
|
||||
static void process_http_request(struct socks_proxy_conn* c) {
|
||||
char* line_end = memmem(c->buf, c->buf_len, "\r\n", 2);
|
||||
if (!line_end) return;
|
||||
|
||||
uint16_t line_len = (uint16_t)((uint8_t*)line_end - c->buf);
|
||||
if (line_len < 8) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: http request too short"); goto error; }
|
||||
|
||||
char line[512]; if (line_len > sizeof(line) - 1) line_len = sizeof(line) - 1;
|
||||
memcpy(line, c->buf, line_len); line[line_len] = '\0';
|
||||
|
||||
// ===== CONNECT =====
|
||||
if (strncmp(line, "CONNECT ", 8) == 0) {
|
||||
char host_port[256];
|
||||
if (sscanf(line, "CONNECT %255s HTTP/", host_port) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http connect: %s", line);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char* colon = strrchr(host_port, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: no port in %s", host_port); goto error; }
|
||||
*colon = '\0'; int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); goto error; }
|
||||
|
||||
struct hostent* he = gethostbyname(host_port);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host_port);
|
||||
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP CONNECT %s → %d.%d.%d.%d:%d",
|
||||
host_port, c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = HTTP_STATE_RELAY;
|
||||
uint8_t resp[] = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp));
|
||||
if (send_connect(c) < 0) { tcp_conn_push_close(c->tc); }
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== Не-CONNECT: HTTP-прокси (GET, POST, PUT, HEAD, OPTIONS, ...) =====
|
||||
char* hdr_end = memmem(c->buf, c->buf_len, "\r\n\r\n", 4);
|
||||
if (!hdr_end) return;
|
||||
|
||||
uint16_t headers_len = (uint16_t)((uint8_t*)hdr_end - c->buf) + 4;
|
||||
|
||||
char method[16] = {0}, url[512] = {0};
|
||||
if (sscanf(line, "%15s %511s", method, url) < 2) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad http request line: %s", line);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strncmp(url, "http://", 7) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: not an absolute http URL: %s", url);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char host[256];
|
||||
int port = 80;
|
||||
const char* host_start = url + 7;
|
||||
const char* path_start = strchr(host_start, '/');
|
||||
const char* col = NULL;
|
||||
for (const char* p = host_start; (path_start ? p < path_start : *p); p++) {
|
||||
if (*p == ':') { col = p; break; }
|
||||
}
|
||||
|
||||
if (col && (!path_start || col < path_start)) {
|
||||
size_t hlen = (size_t)(col - host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
port = atoi(col + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d in %s", port, url); goto error; }
|
||||
} else if (path_start) {
|
||||
size_t hlen = (size_t)(path_start - host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
} else {
|
||||
size_t hlen = strlen(host_start);
|
||||
if (hlen >= sizeof(host)) goto error;
|
||||
memcpy(host, host_start, hlen); host[hlen] = '\0';
|
||||
}
|
||||
|
||||
if (host[0] == '\0') { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: empty host in %s", url); goto error; }
|
||||
if (!path_start || *path_start == '\0') path_start = "/";
|
||||
|
||||
struct hostent* he = gethostbyname(host);
|
||||
if (!he || he->h_addrtype != AF_INET) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: DNS failed for %s", host);
|
||||
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
|
||||
}
|
||||
memcpy(c->dest_ip, he->h_addr, 4); c->dest_port = htons((uint16_t)port);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: HTTP %s %s:%d%s → %d.%d.%d.%d:%d",
|
||||
method, host, port, path_start,
|
||||
c->dest_ip[0], c->dest_ip[1], c->dest_ip[2], c->dest_ip[3], port);
|
||||
|
||||
if (send_connect(c) < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: send_connect failed for %s", host);
|
||||
uint8_t resp[] = "HTTP/1.1 502 Bad Gateway\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc); return;
|
||||
}
|
||||
|
||||
const char* version = strstr(line, " HTTP/");
|
||||
char version_str[16] = " HTTP/1.1";
|
||||
if (version) {
|
||||
size_t vlen = strlen(version);
|
||||
if (vlen >= sizeof(version_str)) vlen = sizeof(version_str) - 1;
|
||||
memcpy(version_str, version, vlen);
|
||||
version_str[vlen] = '\0';
|
||||
}
|
||||
|
||||
uint16_t rest_off = line_len + 2;
|
||||
uint16_t rest_len = headers_len - rest_off;
|
||||
uint8_t hdr_buf[2048];
|
||||
int hdr_n = snprintf((char*)hdr_buf, sizeof(hdr_buf), "%s %s%s\r\n", method, path_start, version_str);
|
||||
if (hdr_n < 0 || (size_t)hdr_n + rest_len > sizeof(hdr_buf)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: header reconstruction overflow hdr_n=%d rest=%u", hdr_n, rest_len);
|
||||
goto error;
|
||||
}
|
||||
memcpy(hdr_buf + hdr_n, c->buf + rest_off, rest_len);
|
||||
uint16_t hdr_chunk = (uint16_t)hdr_n + rest_len;
|
||||
uint16_t extra = (headers_len < c->buf_len) ? (uint16_t)(c->buf_len - headers_len) : 0;
|
||||
uint16_t total = hdr_chunk + extra;
|
||||
|
||||
uint8_t* pkt = u_malloc(total);
|
||||
if (!pkt) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx pkt malloc=%u failed sid=%08x", total, c->stream_id); goto error; }
|
||||
memcpy(pkt, hdr_buf, hdr_chunk);
|
||||
if (extra) memcpy(pkt + hdr_chunk, c->buf + headers_len, extra);
|
||||
|
||||
int ret = send_data(c, pkt, total);
|
||||
if (ret == 0) {
|
||||
u_free(pkt);
|
||||
} else {
|
||||
c->tx_buf = pkt; c->tx_len = total;
|
||||
etcp_router_on_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
|
||||
c->buf_len = 0;
|
||||
c->state = HTTP_STATE_RELAY;
|
||||
return;
|
||||
|
||||
error: {
|
||||
uint8_t resp[] = "HTTP/1.1 400 Bad Request\r\n\r\n";
|
||||
write_to_client(c, resp, (uint16_t)strlen((char*)resp)); tcp_conn_push_close(c->tc);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io read callback — парсинг рукопожатия + релей данных
|
||||
// ====================================================================
|
||||
static void on_read_cb(struct ll_queue* q, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) { queue_resume_callback(q); return; }
|
||||
|
||||
if (c->rem_closed) {
|
||||
queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return;
|
||||
}
|
||||
|
||||
if (c->state == SOCKS_STATE_GREETING || c->state == SOCKS_STATE_REQUEST ||
|
||||
c->state == HTTP_STATE_REQUEST) {
|
||||
// накапливаем данные для рукопожатия
|
||||
uint16_t space = sizeof(c->buf) - c->buf_len;
|
||||
if (space < e->len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handshake buffer overflow sid=%08x", c->stream_id);
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
tcp_conn_push_close(c->tc); queue_resume_callback(q); return;
|
||||
}
|
||||
memcpy(c->buf + c->buf_len, e->dgram, e->len);
|
||||
c->buf_len += e->len;
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
|
||||
if (c->is_http) {
|
||||
process_http_request(c);
|
||||
} else {
|
||||
if (c->state == SOCKS_STATE_GREETING) process_socks_greeting(c);
|
||||
if (c->state == SOCKS_STATE_REQUEST) process_socks_request(c);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// RELAY = релей данных в ETCP
|
||||
if (c->tx_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: RELAY with pending tx_buf sid=%08x — drop new len=%u", c->stream_id, e->len);
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
return;
|
||||
}
|
||||
int ret = send_data(c, e->dgram, e->len);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS PROXY SEND sid=%08x len=%u ret=%d is_http=%d", c->stream_id, e->len, ret, c->is_http);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
c->tx_buf = u_malloc(e->len);
|
||||
if (c->tx_buf) { memcpy(c->tx_buf, e->dgram, e->len); c->tx_len = e->len; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tx_buf malloc=%u failed sid=%08x — drop", e->len, c->stream_id); }
|
||||
memory_pool_free(c->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
etcp_router_on_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY BP: sid=%08x tx_buf=%u waiter_reg", c->stream_id, c->tx_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void tx_waiter_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (!c->tx_buf || c->rem_closed || c->close_sent) return;
|
||||
int ret = send_data(c, c->tx_buf, c->tx_len);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCKS PROXY WAKE: sid=%08x tx_buf=%u ret=%d is_http=%d", c->stream_id, c->tx_len, ret, c->is_http);
|
||||
if (ret == 0) {
|
||||
u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0;
|
||||
queue_resume_callback(c->tc->read_queue);
|
||||
} else {
|
||||
etcp_router_on_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter, tx_waiter_cb, c);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// tcp_io: FIN / Error / Closed
|
||||
// ====================================================================
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
if (c->rem_closed || c->close_sent) return;
|
||||
if (!tc->fin_local && !tc->write_buf && !tc->write_queue->head) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN → relay FIN sid=%08x", c->stream_id);
|
||||
send_fin(c);
|
||||
if (c->fin_remote && !c->close_sent && !c->close_pending) send_close(c);
|
||||
} else {
|
||||
// данные ещё в write_queue, отложим FIN
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "socks_proxy: local FIN deferred (wq=%d wbuf=%s) sid=%08x",
|
||||
tc->write_queue->count, tc->write_buf ? "y" : "n", c->stream_id);
|
||||
tcp_conn_set_flushed(tc, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err;
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp error sid=%08x err=%d state=%d fm_r=%d fm_l=%d rem_cl=%d cs=%d cp=%d "
|
||||
"bytes_client=%u bytes_exit=%u wq=%d",
|
||||
c->stream_id, err, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->close_sent, c->close_pending,
|
||||
c->bytes_to_client, c->bytes_from_exit, c->tc->write_queue->count);
|
||||
if (!c->close_sent && !c->close_pending) send_error(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct socks_proxy_conn* c = (struct socks_proxy_conn*)arg;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp closed sid=%08x state=%d fm_r=%d fm_l=%d rem_cl=%d bytes_client=%u bytes_exit=%u",
|
||||
c->stream_id, c->state, tc->fin_remote, tc->fin_local, c->rem_closed, c->bytes_to_client, c->bytes_from_exit);
|
||||
if (!c->close_sent && !c->close_pending) send_close(c);
|
||||
socks_proxy_conn_free(c);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Accept callback для слушающего сокета
|
||||
// ====================================================================
|
||||
static void on_accept_cb(socket_t sock, void* arg) {
|
||||
struct listen_ctx* ctx = (struct listen_ctx*)arg;
|
||||
struct sockaddr_in addr; socklen_t alen = sizeof(addr);
|
||||
socket_t csock = accept(sock, (struct sockaddr*)&addr, &alen);
|
||||
if (csock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: accept failed errno=%d", errno); return; }
|
||||
socket_set_nonblocking(csock);
|
||||
|
||||
struct socks_proxy_conn* c = u_calloc(1, sizeof(struct socks_proxy_conn));
|
||||
if (!c) { socket_close_wrapper(csock); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return; }
|
||||
c->stream_id = ++(*ctx->next_stream_id);
|
||||
c->is_http = (uint8_t)ctx->is_http;
|
||||
c->ua = ctx->ua; c->inst = ctx->inst; c->via_node_id = ctx->via_node_id;
|
||||
c->state = ctx->is_http ? HTTP_STATE_REQUEST : SOCKS_STATE_GREETING;
|
||||
c->head = ctx->conns; c->count = ctx->conn_count;
|
||||
|
||||
c->tc = tcp_conn_create(ctx->ua, csock, 4096, 4096, 8, 0, 0, on_fin_cb, on_error_cb, c);
|
||||
if (!c->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: tcp_conn_create failed"); u_free(c); socket_close_wrapper(csock); return; }
|
||||
c->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(c->tc->read_queue, on_read_cb, c);
|
||||
queue_set_waiter_defer(c->tc->read_queue, 1);
|
||||
|
||||
c->next = *ctx->conns; *ctx->conns = c; (*ctx->conn_count)++;
|
||||
|
||||
char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: accepted %s conn from %s:%d sid=%08x total=%d",
|
||||
ctx->is_http ? "http" : "socks", ip, ntohs(addr.sin_port), c->stream_id, *ctx->conn_count);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичное API для tcp_proxy_client (etcp dispatch)
|
||||
// ====================================================================
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id) {
|
||||
struct socks_proxy_conn* c;
|
||||
for (c = head; c; c = c->next) if (c->stream_id == stream_id) return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len) {
|
||||
struct socks_proxy_conn* c = socks_proxy_find_conn(*head, stream_id);
|
||||
if (!c) return 0;
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "SOCKS DATA <- sid=%08x len=%zu", stream_id, data_len);
|
||||
c->bytes_from_exit += (uint32_t)data_len;
|
||||
if (data_len > 0) {
|
||||
if (data_len > c->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
|
||||
data_len, c->tc->data_pool->object_size, stream_id);
|
||||
return 1;
|
||||
}
|
||||
struct ll_entry* e = queue_entry_new_from_pool(c->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(c->tc->data_pool);
|
||||
if (e && buf) {
|
||||
memcpy(buf, data, data_len); e->dgram = buf; e->len = (uint16_t)data_len;
|
||||
queue_data_put(c->tc->write_queue, e);
|
||||
c->bytes_to_client += (uint32_t)data_len;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: handle_data alloc failed sid=%08x wq=%d bytes_in=%u bytes_out=%u",
|
||||
stream_id, c->tc->write_queue->count, c->bytes_from_exit, c->bytes_to_client);
|
||||
if (e) queue_entry_free(e); if (buf) memory_pool_free(c->tc->data_pool, buf);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: REM_CLOSED sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: ERROR from exit sid=%08x", stream_id);
|
||||
c->rem_closed = 1;
|
||||
etcp_router_cancel_send_ready(c->inst, TOPO_GROUP_UTUN, c->via_node_id, ETCP_RT_ID_TCP_PROXY, &c->tx_waiter);
|
||||
tcp_conn_push_close(c->tc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FIN from exit sid=%08x", stream_id);
|
||||
c->fin_remote = 1;
|
||||
tcp_conn_push_fin(c->tc);
|
||||
if (c->tc->fin_local && !c->close_sent && !c->close_pending) send_close(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c) {
|
||||
if (!c) return;
|
||||
if (c->freed) return;
|
||||
c->freed = 1;
|
||||
struct socks_proxy_conn** head = c->head; int* count = c->count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: FREE sid=%08x state=%d total=%d", c->stream_id, c->state, count ? *count : 0);
|
||||
if (c->close_pending && c->inst) {
|
||||
c->close_pending = 0;
|
||||
uint8_t subcmd = c->tc && c->tc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
send_msg(c->inst, TOPO_GROUP_UTUN, c->via_node_id, subcmd, c->stream_id, NULL, 0, 1);
|
||||
}
|
||||
if (head) { struct socks_proxy_conn** prev = head; while (*prev) { if (*prev == c) { *prev = c->next; if (count) (*count)--; break; } prev = &(*prev)->next; } }
|
||||
if (c->tx_buf) { u_free(c->tx_buf); c->tx_buf = NULL; c->tx_len = 0; }
|
||||
if (c->tc) { tcp_conn_destroy(c->tc); c->tc = NULL; }
|
||||
if (c->inst) etcp_router_waiter_cancel(c->inst, TOPO_GROUP_UTUN, c->via_node_id, &c->tx_waiter);
|
||||
u_free(c);
|
||||
}
|
||||
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count) {
|
||||
while (*head) { struct socks_proxy_conn* next = (*head)->next; socks_proxy_conn_free(*head); }
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Слушающий сокет
|
||||
// ====================================================================
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http) {
|
||||
char ip[64], *colon = strrchr(addr_str, ':');
|
||||
if (!colon) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad addr '%s' (need IP:PORT)", addr_str); return NULL; }
|
||||
size_t ip_len = (size_t)(colon - addr_str);
|
||||
if (ip_len > sizeof(ip) - 1) ip_len = sizeof(ip) - 1;
|
||||
memcpy(ip, addr_str, ip_len); ip[ip_len] = '\0';
|
||||
int port = atoi(colon + 1);
|
||||
if (port <= 0 || port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bad port %d", port); return NULL; }
|
||||
|
||||
struct listen_ctx* ctx = u_calloc(1, sizeof(struct listen_ctx));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: u_calloc failed"); return NULL; }
|
||||
ctx->ua = ua; ctx->inst = inst; ctx->via_node_id = via_node_id;
|
||||
ctx->is_http = is_http; ctx->conns = conns; ctx->conn_count = count;
|
||||
ctx->next_stream_id = next_stream_id;
|
||||
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: socket() failed errno=%d", errno); u_free(ctx); return NULL; }
|
||||
socket_set_nonblocking(sock);
|
||||
socket_set_reuseaddr(sock, 1);
|
||||
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port);
|
||||
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: inet_pton(%s) failed", ip); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: bind(%s:%d) failed errno=%d", ip, port, errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
if (listen(sock, 32) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: listen() failed errno=%d", errno); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
ctx->listen_sock = sock;
|
||||
ctx->socket_id = uasync_add_socket_t(ua, sock, on_accept_cb, NULL, NULL, ctx);
|
||||
if (!ctx->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socks_proxy: uasync_add_socket_t failed"); socket_close_wrapper(sock); u_free(ctx); return NULL; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: %s listening on %s:%d sock=%d",
|
||||
is_http ? "HTTP" : "SOCKS", ip, port, (int)sock);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out) {
|
||||
if (!ctx) return;
|
||||
if (ctx->socket_id) uasync_remove_socket_t(ua, ctx->listen_sock);
|
||||
socket_close_wrapper(ctx->listen_sock);
|
||||
if (sock_out) *sock_out = SOCKET_INVALID;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "socks_proxy: listener closed sock=%d", (int)ctx->listen_sock);
|
||||
u_free(ctx);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// socks_proxy.h — SOCKS5 / HTTP CONNECT proxy (client side)
|
||||
#ifndef SOCKS_PROXY_H
|
||||
#define SOCKS_PROXY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
struct ll_entry;
|
||||
struct ll_queue;
|
||||
struct tcp_conn;
|
||||
|
||||
enum {
|
||||
SOCKS_STATE_GREETING = 0, // ждём SOCKS5 greeting (ver + methods)
|
||||
SOCKS_STATE_REQUEST, // ждём SOCKS5 CONNECT request
|
||||
SOCKS_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
SOCKS_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
enum {
|
||||
HTTP_STATE_REQUEST = 0, // ждём "CONNECT host:port HTTP/1.1\r\n"
|
||||
HTTP_STATE_CONNECTING, // отправили ETCP CONNECT, ждём ответ от exit
|
||||
HTTP_STATE_RELAY // релей данных
|
||||
};
|
||||
|
||||
struct socks_proxy_conn {
|
||||
struct socks_proxy_conn* next;
|
||||
struct socks_proxy_conn** head; // указатель на голову списка (для удаления)
|
||||
int* count; // указатель на счётчик списка
|
||||
struct tcp_conn* tc;
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
uint64_t via_node_id;
|
||||
uint32_t stream_id;
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
uint8_t fin_remote;
|
||||
uint8_t rem_closed;
|
||||
uint8_t close_sent;
|
||||
uint8_t close_pending;
|
||||
uint8_t is_http;
|
||||
uint8_t state;
|
||||
uint8_t freed;
|
||||
uint8_t buf[1024];
|
||||
uint16_t buf_len;
|
||||
uint8_t* tx_buf; // буфер при backpressure (retry в tx_waiter_cb)
|
||||
uint16_t tx_len;
|
||||
uint32_t bytes_to_client; // байт записано клиенту (curl) через write_queue
|
||||
uint32_t bytes_from_exit; // байт получено от exit node через ETCP DATA
|
||||
struct queue_waiter_handle tx_waiter;
|
||||
};
|
||||
|
||||
struct listen_ctx;
|
||||
|
||||
// Создать слушающий сокет для SOCKS/HTTP proxy. addr_str = "IP:PORT".
|
||||
// listen_ctx должен быть освобождён через socks_proxy_close_listen.
|
||||
// Возвращает listen_ctx при успехе, NULL при ошибке.
|
||||
struct listen_ctx* socks_proxy_init_listen(struct UASYNC* ua, const char* addr_str,
|
||||
struct socks_proxy_conn** conns, int* count, uint32_t* next_stream_id,
|
||||
struct UTUN_INSTANCE* inst, uint64_t via_node_id, int is_http);
|
||||
|
||||
// Закрыть слушающий сокет и освободить listen_ctx.
|
||||
// sock_out получает значение сокета (для последующего close).
|
||||
void socks_proxy_close_listen(struct UASYNC* ua, struct listen_ctx* ctx, socket_t* sock_out);
|
||||
|
||||
// Найти соединение по stream_id
|
||||
struct socks_proxy_conn* socks_proxy_find_conn(struct socks_proxy_conn* head, uint32_t stream_id);
|
||||
|
||||
// Обработать входящее ETCP сообщение (DATA/CLOSE/ERROR/FIN)
|
||||
// Возвращает 1 если обработано, 0 если stream_id не найден
|
||||
int socks_proxy_handle_etcp(struct socks_proxy_conn** head, int* count,
|
||||
uint32_t stream_id, uint8_t subcmd,
|
||||
const uint8_t* data, size_t data_len);
|
||||
|
||||
// Освободить одно соединение (удаляет из списка)
|
||||
void socks_proxy_conn_free(struct socks_proxy_conn* c);
|
||||
|
||||
// Освободить все соединения в списке
|
||||
void socks_proxy_conn_free_all(struct socks_proxy_conn** head, int* count);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // SOCKS_PROXY_H
|
||||
@@ -0,0 +1,132 @@
|
||||
# socks_proxy
|
||||
|
||||
## 1. Назначение
|
||||
Модуль реализует клиентскую сторону SOCKS5 и HTTP CONNECT прокси. Принимает TCP-соединения от локальных приложений (curl, браузер), выполняет handshake (SOCKS5 или HTTP CONNECT), определяет целевой IP:port (с разрешением доменных имён через DNS) и мультиплексирует трафик через ETCP к exit node. Взаимодействует с `tcp_proxy_server` на стороне exit node.
|
||||
|
||||
Файлы:
|
||||
- `src/proxy/socks_proxy.c` — реализация
|
||||
- `src/proxy/socks_proxy.h` — интерфейс
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
### Инициализация
|
||||
```c
|
||||
struct socks_proxy_conn* conns = NULL; // связный список активных соединений
|
||||
int conn_count = 0; // счётчик соединений
|
||||
uint32_t next_stream_id = 0; // монотонный генератор stream_id
|
||||
struct listen_ctx* listen = socks_proxy_init_listen(
|
||||
ua, "127.0.0.1:1080", &conns, &conn_count, &next_stream_id,
|
||||
inst, via_node_id, is_http);
|
||||
```
|
||||
`is_http=0` — SOCKS5, `is_http=1` — HTTP CONNECT/прокси.
|
||||
|
||||
### Приём ETCP-данных от exit node
|
||||
Внешний dispatcher (`tcp_proxy_client`) вызывает `socks_proxy_handle_etcp()` при получении ETCP-пакетов с `svc_id=ETCP_RT_ID_TCP_PROXY`:
|
||||
```c
|
||||
int handled = socks_proxy_handle_etcp(&conns, &conn_count, stream_id, subcmd, data, data_len);
|
||||
// handled=1 — найден и обработан, 0 — stream_id не найден
|
||||
```
|
||||
Поддерживаемые subcmd: `DATA`, `CLOSE`, `ERROR`, `FIN`.
|
||||
|
||||
### Поиск соединения
|
||||
```c
|
||||
struct socks_proxy_conn* c = socks_proxy_find_conn(conns, stream_id);
|
||||
// обход связного списка, O(N)
|
||||
```
|
||||
|
||||
### Завершение
|
||||
```c
|
||||
socks_proxy_conn_free_all(&conns, &conn_count); // все соединения
|
||||
socks_proxy_close_listen(ua, listen, &sock_out); // слушатель
|
||||
```
|
||||
|
||||
### Ключевые нюансы
|
||||
- **Бэкпрессур (backpressure):** при неудаче `etcp_route_send()` данные сохраняются в `tx_buf/tx_len`, и регистрируется waiter через `etcp_router_on_send_ready()`. Когда ETCP готов к отправке, `tx_waiter_cb` повторяет попытку и возобновляет `read_queue`.
|
||||
- **Несколько соединений:** управляются через связный список `struct socks_proxy_conn*` + счётчик. Каждое соединение — отдельный TCP-сокет (`tcp_conn`) и независимый ETCP stream.
|
||||
- **stream_id** генерируется монотонно (`++next_stream_id`), передаётся в заголовке ETCP-пакетов для мультиплексирования.
|
||||
|
||||
## 3. API
|
||||
|
||||
### Структуры
|
||||
|
||||
**`struct socks_proxy_conn`** — одно прокси-соединение:
|
||||
- `tc` — TCP-соединение с клиентом (`tcp_conn`)
|
||||
- `stream_id` — уникальный идентификатор потока в ETCP
|
||||
- `dest_ip[4]`, `dest_port` — целевой адрес (куда хочет подключиться клиент)
|
||||
- `state` — текущее состояние (SOCKS_STATE_* или HTTP_STATE_*)
|
||||
- `is_http` — флаг: 0=SOCKS5, 1=HTTP
|
||||
- `rem_closed`, `fin_remote`, `close_sent`, `close_pending` — флаги состояния закрытия
|
||||
- `buf[1024]`, `buf_len` — буфер накопления данных для парсинга handshake
|
||||
- `tx_buf`, `tx_len`, `tx_waiter` — бэкпрессур: отложенные данные и waiter handle
|
||||
- `bytes_to_client`, `bytes_from_exit` — счётчики переданных байт (диагностика)
|
||||
- `head`, `count` — указатели на голову списка и счётчик (для самоудаления)
|
||||
|
||||
**`struct listen_ctx`** (непрозрачный) — контекст слушающего сокета:
|
||||
- `listen_sock`, `socket_id` — сокет и uasync handle
|
||||
- `conns`, `conn_count`, `next_stream_id` — общий список соединений
|
||||
|
||||
### Основные функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `socks_proxy_init_listen(ua, addr, conns, count, next_sid, inst, via_node, is_http)` | Создаёт слушающий TCP-сокет на `addr` (IP:PORT). Возвращает `listen_ctx*`. |
|
||||
| `socks_proxy_close_listen(ua, ctx, sock_out)` | Закрывает слушающий сокет, освобождает ctx. |
|
||||
| `socks_proxy_handle_etcp(head, count, stream_id, subcmd, data, len)` | Диспетчер ETCP-сообщений от exit node. Обрабатывает DATA (→write_queue клиенту), CLOSE/ERROR (→push_close), FIN (→push_fin). |
|
||||
| `socks_proxy_find_conn(head, stream_id)` | Поиск соединения по stream_id в связном списке. |
|
||||
| `socks_proxy_conn_free(c)` | Освобождает соединение: отправляет отложенный CLOSE/ERROR в ETCP, удаляет из списка, освобождает `tcp_conn`, buffer, waiter. |
|
||||
| `socks_proxy_conn_free_all(head, count)` | Освобождает все соединения в списке. |
|
||||
|
||||
### Внутренние функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `send_msg(inst, dst, subcmd, sid, data, len, force)` | Формирует ETCP-пакет: `[TCP_PROXY_HDR_SIZE=6] svc_id(1)+subcmd(1)+stream_id(4)` + payload, отправляет через `etcp_route_send`. |
|
||||
| `send_connect(c)` | Отправляет CONNECT (dest_ip[4]+dest_port[2]) на exit node. |
|
||||
| `send_data(c, data, len)` | Отправляет DATA (релей пользовательского трафика). |
|
||||
| `send_close(c)`, `send_error(c)`, `send_fin(c)` | Отправляют управляющие subcmd. |
|
||||
| `write_to_client(c, data, len)` | Запись данных в `tc->write_queue` (отправка клиенту через TCP). |
|
||||
| `process_socks_greeting(c)` | Парсинг SOCKS5 GREETING: ver=5, выбор метода 0x00 (no auth), ответ 05 00. |
|
||||
| `process_socks_request(c)` | Парсинг SOCKS5 CONNECT REQUEST (atyp=1/3/4, IPv4/IPv6/domain). DNS-резолвинг для доменов. Ответ 05 00 00 01 + dummy addr. |
|
||||
| `process_http_request(c)` | Парсинг HTTP: CONNECT host:port (туннель) или GET/POST с абсолютным URL (режим HTTP-прокси с переписыванием URL в относительный). |
|
||||
| `on_accept_cb(sock, arg)` | Callback accept: создаёт `socks_proxy_conn`, `tcp_conn_create`, вешает read callback. |
|
||||
| `on_read_cb(q, arg)` | Callback чтения из TCP: handshake-парсинг или релей данных через ETCP с бэкпрессуром. |
|
||||
| `tx_waiter_cb(q, arg)` | Callback бэкпрессура: повторная отправка отложенных данных при готовности ETCP. |
|
||||
| `on_fin_cb`, `on_error_cb`, `on_closed_cb` | Callback'и жизненного цикла `tcp_conn`: FIN→удалённый FIN, ошибка→send_error+free, закрыт→send_close+free. |
|
||||
|
||||
## 4. Протокол
|
||||
|
||||
### SOCKS5 flow (is_http=0)
|
||||
```
|
||||
Клиент → uTun: [ver=5][nmethods][methods...]
|
||||
uTun → Клиент: [05][00] (GREETING: no auth)
|
||||
Клиент → uTun: [05][01][00][atyp][addr...][port] (CONNECT REQUEST)
|
||||
uTun → Клиент: [05][00][00][01][0.0.0.0][0] (ответ ок)
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port]
|
||||
Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA
|
||||
```
|
||||
|
||||
### HTTP CONNECT flow (is_http=1)
|
||||
```
|
||||
Клиент → uTun: CONNECT host:port HTTP/1.1\r\n\r\n
|
||||
uTun → Клиент: HTTP/1.1 200 Connection Established\r\n\r\n
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port]
|
||||
Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA
|
||||
```
|
||||
|
||||
### HTTP-прокси (GET/POST, is_http=1)
|
||||
```
|
||||
Клиент → uTun: GET http://host:port/path HTTP/1.1\r\nHost: ...\r\n\r\n
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port] + TCP_PROXY DATA[GET /path HTTP/1.1\r\n...]
|
||||
Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA
|
||||
```
|
||||
URL переписывается в относительный (`/path`), остальные заголовки передаются как есть.
|
||||
|
||||
## 5. Зависимости
|
||||
- `tcp_proxy_server.h` — константы subcmd (`TCP_PROXY_SUBCMD_*`) и размер заголовка (`TCP_PROXY_HDR_SIZE=6`)
|
||||
- `etcp.h` / `etcp_api.h` — `etcp_route_send()`, `ETCP_RT_ID_TCP_PROXY`
|
||||
- `etcp_router.h` — `etcp_router_on_send_ready()`, `etcp_router_cancel_send_ready()`, `etcp_router_waiter_cancel()`
|
||||
- `tcp_io.h` / `tcp_conn` — TCP-соединения (`tcp_conn_create`, `tcp_conn_push_close`, `tcp_conn_push_fin`, write/read очереди)
|
||||
- `u_async.h` — event loop, `uasync_add_socket_t`
|
||||
- `ll_queue.h` — очереди (write_queue, read_queue), waiter механизм
|
||||
- `memory_pool.h` — аллокация из пулов (`entry_pool`, `data_pool`)
|
||||
- `utun_instance.h` — `struct UTUN_INSTANCE`
|
||||
@@ -0,0 +1,685 @@
|
||||
// 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 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 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);
|
||||
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, 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_SOCKET, "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_SOCKET, "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) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
|
||||
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_RT_ID_TCP_PROXY;
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
e->len = TCP_PROXY_HDR_SIZE + len;
|
||||
return etcp_route_send(inst, group_id, dst, e, 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_SOCKET, "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) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
|
||||
return tcp_proxy_client_send_msg(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len, 0);
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "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;
|
||||
}
|
||||
|
||||
static int tcp_proxy_client_send_fin(struct tcp_proxy_client_conn* pc) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY FIN RELAY sid=%08x", pc->stream_id);
|
||||
return tcp_proxy_client_send_msg(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id,
|
||||
TCP_PROXY_SUBCMD_FIN, pc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Вывод: 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 > 2000) return LERR_BUF;
|
||||
uint8_t buf[2002];
|
||||
if (proxy->tun) {
|
||||
pbuf_copy_partial(p, buf, len, 0);
|
||||
ssize_t wr = tun_platform_write(proxy->tun, buf, len);
|
||||
if (wr != (ssize_t)len) {
|
||||
uint16_t ip_total = ((uint16_t)buf[2] << 8) | buf[3];
|
||||
uint32_t sip, dip; memcpy(&sip, buf + 12, 4); memcpy(&dip, buf + 16, 4);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TUN_WRITE_ERR ret=%zd len=%u ip_total=%u", wr, len, ip_total);
|
||||
}
|
||||
}
|
||||
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;
|
||||
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, buf + 20, 2); memcpy(&dst_port, buf + 22, 2);
|
||||
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, buf + 28, len - 28);
|
||||
return 1;
|
||||
}
|
||||
if (proto == IPPROTO_ICMP) {
|
||||
if (len < 28) return 0;
|
||||
uint8_t icmp_type = buf[20];
|
||||
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, buf + 24, 2); memcpy(&icmp_seq, buf + 26, 2);
|
||||
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, src_ip, icmp_id, icmp_seq, buf + 28, len - 28);
|
||||
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) return;
|
||||
if (pc->rem_closed) return;
|
||||
int sent_any = 0;
|
||||
uint32_t q_pre = queue_entry_count(pc->to_lwip);
|
||||
while (1) {
|
||||
uint16_t space = tcp_sndbuf(pc->pcb);
|
||||
if (space < TCP_MSS / 2) break;
|
||||
struct ll_entry *e = queue_data_get(pc->to_lwip);
|
||||
if (!e) break;
|
||||
if (e->len <= space) {
|
||||
err_t ret = tcp_write(pc->pcb, e->dgram, e->len, TCP_WRITE_FLAG_COPY);
|
||||
if (ret == LERR_OK) { sent_any = 1; queue_dgram_free(e); queue_entry_free(e); }
|
||||
else { queue_data_put_first(pc->to_lwip, e); break; }
|
||||
} else {
|
||||
queue_data_put_first(pc->to_lwip, e);
|
||||
break;
|
||||
}
|
||||
queue_resume_callback(pc->to_lwip);
|
||||
}
|
||||
queue_resume_callback(pc->to_lwip);
|
||||
if (sent_any) {
|
||||
uint32_t unsent = 0; struct tcp_seg* s;
|
||||
for (s = pc->pcb->unsent; s; s = s->next) unsent++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY FEED exit->client sid=%08x fed=%u q=%u->%u snd_wnd=%u cwnd=%u unsent=%u",
|
||||
pc->stream_id, sent_any, q_pre, queue_entry_count(pc->to_lwip), pc->pcb->snd_wnd, pc->pcb->cwnd, unsent);
|
||||
tcp_output(pc->pcb);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// 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_SOCKET, "TCP proxy client: accept alloc failed"); return LERR_MEM; }
|
||||
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
|
||||
|
||||
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");
|
||||
|
||||
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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); u_free(pc); return LERR_MEM; }
|
||||
|
||||
pc->next = p->conns; p->conns = pc; p->conn_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "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: waiter callback при освобождении normalizer очереди
|
||||
// ====================================================================
|
||||
static void tcp_proxy_client_tx_waiter_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_buf) return;
|
||||
int ret = tcp_proxy_client_send_data(pc, pc->tx_buf, pc->tx_len);
|
||||
if (ret == 0) {
|
||||
if (pc->pcb) tcp_recved(pc->pcb, pc->tx_len);
|
||||
u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0;
|
||||
if (pc->fin_local && !pc->close_sent && !pc->close_pending) {
|
||||
if (pc->fin_remote || pc->rem_closed)
|
||||
tcp_proxy_client_send_close(pc);
|
||||
else
|
||||
tcp_proxy_client_send_fin(pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_SOCKET, "PROXY FIN sid=%08x pcb_state=%u sndbuf=%u cwnd=%u unsent=%p unacked=%p",
|
||||
pc->stream_id, pcb->state, pcb->snd_buf, pcb->cwnd,
|
||||
(void*)pcb->unsent, (void*)pcb->unacked);
|
||||
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
|
||||
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
|
||||
if (!pc->tx_buf) {
|
||||
tcp_proxy_client_send_fin(pc);
|
||||
if (pc->fin_remote && !pc->close_sent && !pc->close_pending)
|
||||
tcp_proxy_client_send_close(pc);
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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;
|
||||
}
|
||||
|
||||
if (pc->tx_buf) { pbuf_free(p); return LERR_OK; }
|
||||
|
||||
uint8_t *data = u_malloc(len);
|
||||
if (data) {
|
||||
pbuf_copy_partial(p, data, len, 0);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
|
||||
int ret = tcp_proxy_client_send_data(pc, data, len);
|
||||
if (ret == 0) { tcp_recved(pcb, len); u_free(data); }
|
||||
else {
|
||||
pc->tx_buf = data; pc->tx_len = len;
|
||||
etcp_router_waiter_register(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id, &pc->tx_waiter,
|
||||
tcp_proxy_client_tx_waiter_cb, pc);
|
||||
}
|
||||
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
|
||||
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);
|
||||
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;
|
||||
if (!pc || !pc->proxy || pc->rem_closed) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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_SOCKET, "TCP proxy client: error %d sid=%08x pcb_state=%u fin_local=%d rem_closed=%d",
|
||||
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->fin_local, pc->rem_closed);
|
||||
pc->pcb = NULL;// pcb уже уничтожен стеком lwip
|
||||
pc->error = 1;
|
||||
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
|
||||
}
|
||||
|
||||
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_SOCKET, "PROXY CLEANUP error sid=%08x", pc->stream_id);
|
||||
if (pc->pcb) {
|
||||
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);
|
||||
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_SOCKET, "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_SOCKET, "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;
|
||||
if (len > 20 && len <= 2000) {
|
||||
if (!tcp_proxy_client_handle_non_tcp(p, ip, len)) {
|
||||
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 && 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;
|
||||
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "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_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
if (pc->proxy && pc->proxy->inst) etcp_router_waiter_cancel(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
u_free(pc);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Обработчики входящих сообщений (сторона клиента)
|
||||
// ====================================================================
|
||||
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_SOCKET, "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_HDR_SIZE;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
|
||||
if (data_len > 0) {
|
||||
struct ll_entry* e = tcp_proxy_client_entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
|
||||
if (e) 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_SOCKET, "PROXY CLOSE sid=%08x — no conn, dropping", stream_id); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x fin_local=%d pcb_state=%u", stream_id, pc->fin_local, pc->pcb ? pc->pcb->state : 0);
|
||||
pc->rem_closed = 1;
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
etcp_router_waiter_cancel(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
if (pc->pcb) {
|
||||
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_SOCKET, "PROXY ERROR sid=%08x — no conn, dropping", stream_id); return; }
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x fin_local=%d", stream_id, pc->fin_local);
|
||||
if (pc->pcb) {
|
||||
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;
|
||||
}
|
||||
pc->rem_closed = 1;
|
||||
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
etcp_router_waiter_cancel(pc->proxy->inst, TOPO_GROUP_UTUN, pc->proxy->via_node_id, &pc->tx_waiter);
|
||||
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_SOCKET, "PROXY FIN FROM exit sid=%08x pcb_state=%u — shutdown write (send FIN to local)", stream_id, pc->pcb->state);
|
||||
pc->fin_remote = 1;
|
||||
if (pc->pcb->state != TIME_WAIT && pc->pcb->state != CLOSED) {
|
||||
tcp_shutdown(pc->pcb, 0, 1);
|
||||
tcp_sent(pc->pcb, NULL);
|
||||
tcp_poll(pc->pcb, NULL, 0);
|
||||
}
|
||||
if (pc->fin_local && !pc->close_sent && !pc->close_pending)
|
||||
tcp_proxy_client_send_close(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 || entry->len < TCP_PROXY_HDR_SIZE) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
|
||||
uint64_t peer_id;
|
||||
memcpy(&peer_id, entry->dgram + 1, 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "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_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);
|
||||
}
|
||||
}
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "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_HDR_SIZE ? entry->len - TCP_PROXY_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_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_HDR_SIZE, data_len)) {
|
||||
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) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "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_SOCKET, "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_SOCKET, "ua is NULL"); return NULL; }
|
||||
|
||||
int need_tun = tun_name && tun_name[0] && tun_ip && tun_ip[0];
|
||||
if (!need_tun && !socks_enabled && !http_proxy_enabled) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "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_SOCKET, "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_SOCKET, "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_TUN, "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_SOCKET, "lwip_tcp_init failed"); tun_close(p->tun); memory_pool_destroy(p->entry_pool); u_free(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) continue;
|
||||
tcp_bind(lpcb, INADDR_ANY, port_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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_SOCKET, "tcp_proxy_client: SOCKS init failed on %s", socks_addr); }
|
||||
}
|
||||
|
||||
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_SOCKET, "tcp_proxy_client: HTTP proxy init failed on %s", http_proxy_addr); }
|
||||
}
|
||||
|
||||
if (inst) {
|
||||
if (etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, tcp_proxy_client_router_recv_cb) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_RT_ID_TCP_PROXY);
|
||||
if (need_tun) { udp_proxy_init(inst, ua); icmp_proxy_init(inst, ua); }
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "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;
|
||||
int total = p->conn_count + p->socks_conn_count + p->http_conn_count;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: lwip=%d socks=%d http=%d",
|
||||
p->conn_count, p->socks_conn_count, p->http_conn_count);
|
||||
if (p->inst) etcp_router_unbind(p->inst, ETCP_RT_ID_TCP_PROXY);
|
||||
udp_proxy_destroy(p->inst);
|
||||
icmp_proxy_destroy(p->inst);
|
||||
|
||||
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;
|
||||
if (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_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
|
||||
if (p->inst) etcp_router_waiter_cancel(p->inst, TOPO_GROUP_UTUN, p->via_node_id, &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);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// tcp_proxy_client.h — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
|
||||
#ifndef TCP_PROXY_CLIENT_H
|
||||
#define TCP_PROXY_CLIENT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
struct tun_if;
|
||||
struct ll_queue;
|
||||
struct ll_entry;
|
||||
struct memory_pool;
|
||||
struct tcp_proxy_client_mapping_config;
|
||||
struct tcp_proxy_server;
|
||||
struct lwip_tcp_ctx;
|
||||
struct socks_proxy_conn;
|
||||
struct listen_ctx;
|
||||
|
||||
struct tcp_pcb;
|
||||
|
||||
struct tcp_proxy_client_conn {
|
||||
struct tcp_proxy_client_conn* next;
|
||||
struct tcp_proxy_client* proxy;
|
||||
struct tcp_pcb* pcb;
|
||||
|
||||
uint32_t stream_id;
|
||||
|
||||
struct ll_queue* to_lwip; // DATA от exit → lwIP (управление потоком)
|
||||
|
||||
uint8_t fin_local; // локальная сторона отправила FIN (lwIP)
|
||||
uint8_t fin_remote; // exit отправил FIN (назначение закрылось)
|
||||
uint8_t rem_closed; // exit отправил CLOSE
|
||||
uint8_t error; // ошибка, немедленная очистка
|
||||
uint8_t close_sent; // отправили CLOSE/ERROR в exit
|
||||
uint8_t close_pending; // CLOSE/ERROR не доставлен, ждём повтора
|
||||
|
||||
uint8_t* tx_buf; // буфер при backpressure (lwIP→ETCP send fail)
|
||||
uint16_t tx_len;
|
||||
struct queue_waiter_handle tx_waiter;
|
||||
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
};
|
||||
|
||||
struct tcp_proxy_client {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct UASYNC* ua;
|
||||
struct tun_if* tun;
|
||||
struct tcp_proxy_client_conn* conns;
|
||||
int conn_count;
|
||||
struct memory_pool* entry_pool;
|
||||
uint32_t next_stream_id;
|
||||
uint64_t via_node_id;
|
||||
struct lwip_tcp_ctx* lwip;
|
||||
|
||||
struct tcp_proxy_client_mapping_config* mappings; // указатель на конфиг
|
||||
int mapping_count;
|
||||
|
||||
// SOCKS proxy
|
||||
int socks_enabled;
|
||||
struct socks_proxy_conn* socks_conns;
|
||||
int socks_conn_count;
|
||||
struct listen_ctx* socks_listen;
|
||||
|
||||
// HTTP CONNECT proxy
|
||||
int http_proxy_enabled;
|
||||
struct socks_proxy_conn* http_conns;
|
||||
int http_conn_count;
|
||||
struct listen_ctx* http_listen;
|
||||
};
|
||||
|
||||
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);
|
||||
void tcp_proxy_client_destroy(struct tcp_proxy_client* p);
|
||||
void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // TCP_PROXY_CLIENT_H
|
||||
@@ -0,0 +1,204 @@
|
||||
# tcp_proxy_client
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
Клиентский оркестратор TCP прокси. Создаёт TUN-интерфейс, запускает встроенный lwIP TCP стек, перехватывает исходящие TCP-соединения со стороны клиента и туннелирует их через ETCP к удалённому exit-узлу (tcp_proxy_server). Поддерживает три режима перехвата трафика одновременно:
|
||||
|
||||
1. **Прозрачное проксирование через TUN** — весь TCP-трафик с TUN-интерфейса попадает в lwIP, который управляет реальными TCP-сессиями. Исходящие соединения прозрачно проксируются на exit-узел. UDP (через `udp_proxy`) и ICMP Echo (через `icmp_proxy`) также пробрасываются.
|
||||
2. **SOCKS5 proxy listener** — встроенный SOCKS5-сервер, принимающий локальные подключения и туннелирующий их через ETCP.
|
||||
3. **HTTP CONNECT proxy listener** — встроенный HTTP CONNECT-прокси (работает через тот же механизм SOCKS, что и SOCKS5).
|
||||
|
||||
Модуль полностью оркестрирует весь TCP-прокси-пайплайн на стороне клиента: захват трафика → lwIP TCP стек → ETCP-туннелирование → приём ответных данных → отдача в lwIP → запись IP-пакетов обратно в TUN.
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
### Типовой сценарий
|
||||
|
||||
```
|
||||
tcp_proxy_client_create(inst, ua, tun_name, tun_ip, mtu, test_mode,
|
||||
mappings, mapping_count, via_node_id,
|
||||
socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr)
|
||||
→ TUN init (tun_init_nat) — создание TUN-интерфейса с NAT
|
||||
→ lwIP init (lwip_tcp_init) — запуск встроенного TCP стека
|
||||
→ Настройка port mappings — прослушивание локальных портов в lwIP
|
||||
→ SOCKS5/HTTP listen — запуск локальных прокси-слушателей
|
||||
→ ETCP router bind — регистрация коллбэка tcp_proxy_client_router_recv_cb
|
||||
→ UDP/ICMP proxy init
|
||||
```
|
||||
|
||||
### Ключевые концепции
|
||||
|
||||
**TUN interface** через `tun_init_nat()`. IP-пакеты из output_queue TUN направляются в `tcp_proxy_client_tun_input()` — главную точку входа inbound-трафика.
|
||||
|
||||
**Встроенный lwIP TCP стек** (`lwip_tcp_init()`) с output-коллбэком `tcp_proxy_client_output_cb`, который пишет готовые IP-пакеты обратно в TUN (в сторону ОС клиента).
|
||||
|
||||
**Inbound TUN трафик** (`tcp_proxy_client_tun_input()`): парсинг IP-пакета → по протоколу:
|
||||
- TCP → вызов `lwip_tcp_input()` — пакет попадает в lwIP, где срабатывает `accept_cb` для новых соединений или `recv_cb` для существующих
|
||||
- UDP → `udp_proxy_send_to_exit()` — проброс UDP в exit
|
||||
- ICMP (type 8/Echo) → `icmp_proxy_send_to_exit()` — проброс ping в exit
|
||||
|
||||
**Connection accept** (`tcp_proxy_client_accept_cb`): при новом входящем TCP-соединении в lwIP:
|
||||
1. Выделяется `struct tcp_proxy_client_conn`
|
||||
2. Определяется destination: поиск по port mapping config `mappings[]` (local_port → remote_ip:remote_port), иначе `local_ip:local_port` самого pcb
|
||||
3. Назначается `stream_id` (инкремент `next_stream_id`)
|
||||
4. Регистрируются lwIP коллбэки: `recv`, `sent`, `err`, `poll`
|
||||
5. Создаётся очередь `to_lwip` для данных от exit
|
||||
6. Отправляется ETCP CONNECT с dest_ip+dest_port через `etcp_route_send(force=1)`
|
||||
|
||||
**Client-side lwIP** — чтение данных (`tcp_proxy_client_recv_cb`):
|
||||
- При получении данных от lwIP (клиент что-то отправляет): копируем pbuf → `tcp_proxy_client_send_data()` → ETCP DATA
|
||||
- При backpressure (очередь нормализатора переполнена): сохраняем в `tx_buf`, регистрируем waiter через `etcp_router_waiter_register()`. При освобождении очереди `tcp_proxy_client_tx_waiter_cb` досылает данные и вызывает `tcp_recved()` для window update
|
||||
- При p==NULL (FIN от lwIP): выставляется `fin_local`, отправляется ETCP FIN. Если обе стороны закрылись — ETCP CLOSE
|
||||
|
||||
**Exit-side data** — приём ответных данных (`tcp_proxy_client_handle_data()`):
|
||||
- Данные от exit помещаются в очередь `to_lwip`
|
||||
- `tcp_proxy_client_feed_from_transport()`: пока есть место в `tcp_sndbuf(pcb)`, читает из `to_lwip` и пишет через `tcp_write()` в lwIP. После записи вызывает `tcp_output()` для отправки
|
||||
- Trigger на feed: при получении DATA, при `sent_cb` (lwIP освободил send buffer)
|
||||
|
||||
**Full lifecycle:**
|
||||
1. CONNECT — установка соединения с exit
|
||||
2. DATA ↔ DATA — двунаправленный обмен данными
|
||||
3. FIN (от клиента или exit) — half-close: `tcp_shutdown(pcb, 0, 1)` (закрытие только send-стороны)
|
||||
4. CLOSE — полное закрытие после обоих FIN, очистка `tcp_proxy_client_conn_free()`
|
||||
5. ERROR — аварийное закрытие (ошибка lwIP или exit), немедленная очистка
|
||||
|
||||
**Per-port mapping конфигурация** (`tcp_proxy_client_mapping_config`):
|
||||
```c
|
||||
struct tcp_proxy_client_mapping_config {
|
||||
uint16_t local_port; // локальный порт для прослушивания
|
||||
char remote_ip[64]; // IP назначения на exit-узле
|
||||
uint16_t remote_port; // порт назначения на exit-узле
|
||||
};
|
||||
```
|
||||
При старте для каждого mapping создаётся listen pcb в lwIP. Также работает динамический listen для любых исходящих портов (`tcp_proxy_client_ensure_outbound_listen()`).
|
||||
|
||||
**SOCKS5/HTTP proxy listeners** — используют `socks_proxy_init_listen()` (общий код с SOCKS, флаг `is_http` различает режимы). SOCKS/HTTP обрабатываются приоритетно в `router_recv_cb` — им не нужен TUN/lwIP.
|
||||
|
||||
### Протокол обмена с exit-узлом
|
||||
|
||||
Формат сообщения: `svc_id(1) + subcmd(1) + stream_id(4) + [data]` (заголовок `TCP_PROXY_HDR_SIZE = 6` байт).
|
||||
|
||||
| Subcmd | Код | Направление | Описание |
|
||||
|--------|-----|-------------|----------|
|
||||
| CONNECT | 0x01 | client→exit | Установка TCP-соединения с destination |
|
||||
| DATA | 0x03 | ↔ | Данные TCP-потока |
|
||||
| CLOSE | 0x04 | ↔ | Полное закрытие потока |
|
||||
| ERROR | 0x05 | ↔ | Ошибка, немедленная очистка |
|
||||
| FIN | 0x06 | ↔ | Half-close (shutdown write) |
|
||||
|
||||
Специальное сообщение: `svc_id(1) + peer_id(8)` (9 байт) — CLOSE_ALL: очистка всех соединений к указанному peer.
|
||||
|
||||
### Механизмы надёжности
|
||||
|
||||
- **Backpressure в нормализатор**: `etcp_router_waiter_register()` — если `etcp_route_send()` не может отправить (очередь переполнена), данные буферизуются в `tx_buf` и досылаются при освобождении
|
||||
- **Повтор CLOSE/ERROR**: если отправка CLOSE/ERROR не удалась, выставляется `close_pending=1`. В `poll_cb` повторяется попытка
|
||||
- **Очередь to_lwip**: буферизует данные от exit до готовности lwIP send buffer, обеспечивая window-based flow control
|
||||
|
||||
## 3. API
|
||||
|
||||
### Структуры
|
||||
|
||||
#### `struct tcp_proxy_client_conn` — одно проксируемое TCP-соединение
|
||||
| Поле | Тип | Назначение |
|
||||
|------|-----|------------|
|
||||
| `next` | `*` | Связный список всех соединений |
|
||||
| `proxy` | `*tcp_proxy_client` | Обратный указатель на родительский прокси |
|
||||
| `pcb` | `*tcp_pcb` | lwIP protocol control block для этого соединения |
|
||||
| `stream_id` | `uint32_t` | Уникальный идентификатор потока в протоколе ETCP |
|
||||
| `to_lwip` | `*ll_queue` | Очередь данных от exit → lwIP (flow control) |
|
||||
| `fin_local` | `uint8_t` | Локальная сторона (lwIP) отправила FIN |
|
||||
| `fin_remote` | `uint8_t` | Удалённая сторона (exit) отправила FIN |
|
||||
| `rem_closed` | `uint8_t` | Exit прислал CLOSE — прекращаем обработку |
|
||||
| `error` | `uint8_t` | Ошибка на любом конце — немедленная очистка |
|
||||
| `close_sent` | `uint8_t` | CLOSE/ERROR отправлен на exit |
|
||||
| `close_pending` | `uint8_t` | CLOSE/ERROR не доставлен, ждём повтора в poll |
|
||||
| `tx_buf` | `*uint8_t` | Буфер при backpressure (lwIP→ETCP send fail) |
|
||||
| `tx_len` | `uint16_t` | Длина данных в tx_buf |
|
||||
| `tx_waiter` | `queue_waiter_handle` | Дескриптор waiter для backpressure |
|
||||
| `dest_ip` | `uint8_t[4]` | IP назначения на exit-узле |
|
||||
| `dest_port` | `uint16_t` | Порт назначения на exit-узле (network byte order) |
|
||||
|
||||
#### `struct tcp_proxy_client` — состояние прокси-клиента
|
||||
| Поле | Тип | Назначение |
|
||||
|------|-----|------------|
|
||||
| `inst` | `*UTUN_INSTANCE` | Родительский инстанс uTun |
|
||||
| `ua` | `*UASYNC` | Асинхронный event loop |
|
||||
| `tun` | `*tun_if` | TUN-интерфейс для захвата/инжекции трафика |
|
||||
| `conns` | `*tcp_proxy_client_conn` | Голова связного списка lwIP-соединений |
|
||||
| `conn_count` | `int` | Количество активных lwIP-соединений |
|
||||
| `entry_pool` | `*memory_pool` | Пул для `struct ll_entry` (данные to_lwip) |
|
||||
| `next_stream_id` | `uint32_t` | Счётчик stream_id (начинается с 1) |
|
||||
| `via_node_id` | `uint64_t` | Node ID exit-узла для маршрутизации ETCP |
|
||||
| `lwip` | `*lwip_tcp_ctx` | Контекст встроенного lwIP TCP стека |
|
||||
| `mappings` | `*mapping_config` | Указатель на массив port mapping конфигурации |
|
||||
| `mapping_count` | `int` | Количество mapping записей |
|
||||
| `socks_enabled` | `int` | Флаг включения SOCKS5 прокси |
|
||||
| `socks_conns` | `*socks_proxy_conn` | Список активных SOCKS5-соединений |
|
||||
| `socks_conn_count` | `int` | Количество SOCKS5-соединений |
|
||||
| `socks_listen` | `*listen_ctx` | Слушающий сокет SOCKS5 |
|
||||
| `http_proxy_enabled` | `int` | Флаг включения HTTP CONNECT прокси |
|
||||
| `http_conns` | `*socks_proxy_conn` | Список активных HTTP CONNECT соединений |
|
||||
| `http_conn_count` | `int` | Количество HTTP CONNECT соединений |
|
||||
| `http_listen` | `*listen_ctx` | Слушающий сокет HTTP CONNECT |
|
||||
|
||||
### Функции
|
||||
|
||||
#### `tcp_proxy_client_create()`
|
||||
Создаёт и инициализирует весь прокси-клиент. Параметры: инстанс uTun, uasync, имя/IP/MTU TUN-интерфейса, test_mode, массив port mapping, via_node_id, флаги/адреса SOCKS и HTTP прокси. Возвращает NULL при ошибке.
|
||||
|
||||
#### `tcp_proxy_client_destroy()`
|
||||
Полная очистка: отвязка от ETCP router, остановка UDP/ICMP прокси, закрытие SOCKS/HTTP listeners, освобождение всех соединений (lwIP abort + очистка очередей to_lwip + tx_buf + waiter cancel), уничтожение lwIP стека, закрытие TUN, освобождение memory pool.
|
||||
|
||||
#### `tcp_proxy_client_router_recv_cb()`
|
||||
Главный ETCP-коллбэк (зарегистрирован через `etcp_router_bind` на `ETCP_RT_ID_TCP_PROXY=0x04`). Обрабатывает входящие сообщения от exit-узла:
|
||||
- **DATA** → `tcp_proxy_client_handle_data()` — данные в очередь to_lwip и feed в lwIP
|
||||
- **CLOSE** → `tcp_proxy_client_handle_close()` — пометка rem_closed, закрытие lwIP pcb, освобождение соединения
|
||||
- **ERROR** → `tcp_proxy_client_handle_error()` — аварийное закрытие lwIP pcb, освобождение соединения
|
||||
- **FIN** → `tcp_proxy_client_handle_fin()` — half-close через tcp_shutdown(pcb, 0, 1)
|
||||
- **CLOSE_ALL** (9-байтовый пакет без conn) → очистка всех соединений к peer
|
||||
- Сначала пробует SOCKS/HTTP обработчики (приоритет — могут работать без TUN)
|
||||
|
||||
### Внутренние функции
|
||||
|
||||
#### lwIP коллбэки
|
||||
- **`tcp_proxy_client_output_cb()`** — output lwIP: запись IP-пакетов в TUN (в сторону ОС клиента)
|
||||
- **`tcp_proxy_client_accept_cb()`** — новый входящий TCP: создание `tcp_proxy_client_conn`, определение destination из mapping конфига, отправка ETCP CONNECT
|
||||
- **`tcp_proxy_client_recv_cb()`** — данные от клиента (lwIP→ETCP): копирование pbuf → ETCP DATA с backpressure
|
||||
- **`tcp_proxy_client_sent_cb()`** — lwIP освободил send buffer: вызов feed_from_transport для передачи накопленных данных
|
||||
- **`tcp_proxy_client_err_cb()`** — ошибка lwIP: pcb уже уничтожен стеком, отправка ETCP ERROR
|
||||
- **`tcp_proxy_client_poll_cb()`** — периодический poll (2 интервала): очистка при error, повтор close_pending
|
||||
|
||||
#### Отправка сообщений в ETCP
|
||||
- **`tcp_proxy_client_send_msg()`** — базовая функция: сборка пакета `svc_id+subcmd+stream_id+data`, отправка через `etcp_route_send()`
|
||||
- **`tcp_proxy_client_send_connect()`** — CONNECT с dest_ip+dest_port (6 байт данных), force=1
|
||||
- **`tcp_proxy_client_send_data()`** — DATA с payload, без force (backpressure через waiter)
|
||||
- **`tcp_proxy_client_send_close()`** — CLOSE, force=1, при ошибке выставляет close_pending
|
||||
- **`tcp_proxy_client_send_error()`** — ERROR, force=1, при ошибке выставляет close_pending
|
||||
- **`tcp_proxy_client_send_fin()`** — FIN, force=1
|
||||
|
||||
#### Обработка трафика
|
||||
- **`tcp_proxy_client_tun_input()`** — коллбэк очереди output_queue TUN. Парсит IP-пакеты: TCP → lwip_tcp_input (с динамическим listen на целевой порт), UDP/ICMP → соответствующие прокси
|
||||
- **`tcp_proxy_client_handle_non_tcp()`** — перенаправление UDP в `udp_proxy_send_to_exit()` и ICMP Echo в `icmp_proxy_send_to_exit()`
|
||||
- **`tcp_proxy_client_feed_from_transport()`** — чтение данных из очереди `to_lwip` и запись в lwIP через `tcp_write()` пока есть место в send buffer. После feed вызывает `tcp_output()`
|
||||
- **`tcp_proxy_client_tx_waiter_cb()`** — коллбэк backpressure waiter: досылка `tx_buf` при освобождении normalizer очереди, вызов `tcp_recved()` для window update
|
||||
- **`tcp_proxy_client_ensure_outbound_listen()`** — динамическое создание listen pcb в lwIP для заданного порта (если ещё не создан), нужно для прозрачного проксирования любых исходящих TCP-соединений
|
||||
|
||||
#### Управление памятью и соединениями
|
||||
- **`tcp_proxy_client_entry_from_data()`** — создание `ll_entry` из пула `entry_pool` с копированием данных
|
||||
- **`tcp_proxy_client_find_conn()`** — линейный поиск соединения по `stream_id`
|
||||
- **`tcp_proxy_client_conn_free()`** — полная очистка одного соединения: досылка pending CLOSE/ERROR, удаление из списка, очистка очереди to_lwip, освобождение tx_buf, cancel waiter, free памяти
|
||||
|
||||
### Зависимости
|
||||
- `tun_if.h` — TUN-интерфейс (инициализация, запись, закрытие)
|
||||
- `lwip_tcp/lwip_tcp.h` — встроенный lwIP TCP стек (инициализация, input, управление pcb)
|
||||
- `etcp.h` / `etcp_api.h` / `etcp_router.h` — ETCP-маршрутизация и отправка данных
|
||||
- `tcp_proxy_server.h` — общие константы протокола (TCP_PROXY_SUBCMD_*, TCP_PROXY_HDR_SIZE)
|
||||
- `socks_proxy.h` — SOCKS5/HTTP CONNECT прокси (init_listen, handle_etcp, conn_free_all)
|
||||
- `udp_proxy.h` / `icmp_proxy.h` — проброс UDP и ICMP
|
||||
- `config_parser.h` — `tcp_proxy_client_mapping_config` (local_port → remote_ip:remote_port)
|
||||
- `utun_instance.h` — `struct UTUN_INSTANCE` с полем `tcp_proxy_client`
|
||||
- `lib/u_async.h` — асинхронный event loop
|
||||
- `lib/ll_queue.h` — lock-free очереди (to_lwip, output_queue TUN)
|
||||
- `lib/memory_pool.h` — пул для `struct ll_entry`
|
||||
- `lib/mem.h` — обёртки malloc/free с учётом утечек
|
||||
- `lib/debug_config.h` — логирование
|
||||
@@ -0,0 +1,560 @@
|
||||
// tcp_proxy_server.c — TCP прокси-сервер (exit node)
|
||||
#include "tcp_proxy_server.h"
|
||||
#include "udp_proxy.h"
|
||||
#include "icmp_proxy.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "config_parser.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
static struct tcp_proxy_server* g_tcp_proxy_server_ctx = NULL;
|
||||
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg);
|
||||
static void on_flushed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg);
|
||||
static void read_queue_drain_cb(struct ll_queue* q, void* arg);
|
||||
static void pause_resume_cb(struct ll_queue* q, void* arg);
|
||||
static void close_retry_cb(void* arg);
|
||||
static void diag_timer_cb(void* arg);
|
||||
static void retry_timer_cb(void* arg);
|
||||
static int 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 void send_close(struct tcp_proxy_server_conn* rc);
|
||||
static void send_error(struct tcp_proxy_server_conn* rc);
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
|
||||
|
||||
static inline int write_pending(struct tcp_conn* tc) {
|
||||
return tc && (tc->write_buf || tc->write_queue->head);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Отправка сообщений через ETCP
|
||||
// ====================================================================
|
||||
|
||||
static int 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) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
|
||||
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: malloc(%zu) failed", TCP_PROXY_HDR_SIZE + len); queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_RT_ID_TCP_PROXY;
|
||||
e->dgram[1] = subcmd;
|
||||
memcpy(e->dgram + 2, &sid, 4);
|
||||
if (len > 0) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
|
||||
if (TCP_PROXY_HDR_SIZE + len > UINT16_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: msg too large len=%zu subcmd=%02x", len, subcmd);
|
||||
queue_dgram_free(e); queue_entry_free(e); return -1;
|
||||
}
|
||||
e->len = TCP_PROXY_HDR_SIZE + len;
|
||||
return etcp_route_send(inst, group_id, dst, e, force);
|
||||
}
|
||||
|
||||
static void close_retry_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (!rc) return;
|
||||
rc->close_timer = NULL;
|
||||
if (!rc->close_pending) return;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
if (send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_backoff = rc->close_backoff < 5000 ? rc->close_backoff * 2 : 5000;
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
return;
|
||||
}
|
||||
rc->close_pending = 0;
|
||||
}
|
||||
|
||||
static void send_close(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
if (send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_pending = 1;
|
||||
rc->close_backoff = 50;
|
||||
if (!rc->close_timer)
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_error(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
if (send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0, 1) < 0) {
|
||||
rc->close_pending = 1;
|
||||
rc->close_backoff = 50;
|
||||
if (!rc->close_timer)
|
||||
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, close_retry_cb, "tps_close_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void send_fin(struct tcp_proxy_server_conn* rc) {
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
if (!inst) return;
|
||||
send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_FIN, rc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Коллбэки tcp_io
|
||||
// ====================================================================
|
||||
|
||||
static void on_fin_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
int pend_w = write_pending(tc);
|
||||
int pend_r = (rc->tx_buf != NULL) || (tc->read_queue && tc->read_queue->head);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:DST_FIN fd=%d sid=%08x fin_l=%d pend_w=%d pend_r=%d "
|
||||
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
|
||||
"rq=%d(%zub) wq=%d(%zub) wbuf=%s err=%d tx_buf=%s",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local, pend_w, pend_r,
|
||||
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
|
||||
rc->drain_count, rc->data_count,
|
||||
tc->read_queue->count, queue_total_bytes(tc->read_queue),
|
||||
tc->write_queue->count, queue_total_bytes(tc->write_queue),
|
||||
tc->write_buf ? "y" : "n", tc->error, rc->tx_buf ? "y" : "n");
|
||||
if (tc->fin_local) {
|
||||
send_close(rc); tcp_conn_push_close(tc);
|
||||
} else if (pend_w) {
|
||||
tcp_conn_set_flushed(tc, on_flushed_cb);
|
||||
} else if (pend_r) {
|
||||
rc->dst_fin_deferred = 1;
|
||||
} else {
|
||||
send_fin(rc);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_flushed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (rc->cli_closed) return;
|
||||
if (tc->fin_local) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSHED fd=%d sid=%08x fin_local=%d — both FINs, closing",
|
||||
(int)tc->sock, rc->stream_id, tc->fin_local);
|
||||
send_close(rc); tcp_conn_push_close(tc); return;
|
||||
}
|
||||
send_fin(rc);
|
||||
}
|
||||
|
||||
static void on_closed_cb(struct tcp_conn* tc, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSED fd=%d sid=%08x — freeing",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
static void on_error_cb(struct tcp_conn* tc, int err, void* arg) {
|
||||
(void)err;
|
||||
if (tc->closed) return;
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:ERR fd=%d sid=%08x err=%d fin_r=%d fin_l=%d "
|
||||
"sent=%u recv=%u relayed=%u drain#=%u data#=%u "
|
||||
"rq=%d wq=%d wbuf=%s conn=%d",
|
||||
tc ? (int)tc->sock : -1, rc->stream_id, err,
|
||||
tc ? tc->fin_remote : 0, tc ? tc->fin_local : 0,
|
||||
rc->bytes_sent, rc->bytes_recv, rc->bytes_relayed,
|
||||
rc->drain_count, rc->data_count,
|
||||
tc ? tc->read_queue->count : 0, tc ? tc->write_queue->count : 0,
|
||||
tc && tc->write_buf ? "y" : "n", tc ? tc->connected : 0);
|
||||
send_error(rc);
|
||||
tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
static void diag_timer_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
rc->diag_timer = NULL;
|
||||
struct tcp_conn* tc = rc->tc;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
|
||||
int ent_f = tc->entry_pool->free_count;
|
||||
int dat_f = tc->data_pool->free_count;
|
||||
|
||||
int rcv_buf = 0, snd_buf = 0;
|
||||
socklen_t optlen = sizeof(int);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_buf, &optlen);
|
||||
getsockopt(tc->sock, SOL_SOCKET, SO_SNDBUF, (char*)&snd_buf, &optlen);
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET,
|
||||
"SOCK:DIAG fd=%d sid=%08x conn=%d fin=%d "
|
||||
"rq=%d(%zub) wq=%d(%zub) wbuf=%s "
|
||||
"ent_f=%d dat_f=%d tcp_rb=%zu tcp_sb=%zu",
|
||||
(int)tc->sock, rc->stream_id, tc->connected, tc->fin_remote,
|
||||
tc->read_queue->count, queue_total_bytes(tc->read_queue),
|
||||
tc->write_queue->count, queue_total_bytes(tc->write_queue),
|
||||
tc->write_buf ? "y" : "n",
|
||||
ent_f, dat_f,
|
||||
(size_t)rcv_buf, (size_t)snd_buf);
|
||||
|
||||
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Дрейн read_queue → ETCP (автозабор + backpressure)
|
||||
// ====================================================================
|
||||
|
||||
static void read_queue_drain_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
struct ll_entry* e;
|
||||
|
||||
if (rc->tx_buf) {
|
||||
int ret = send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 0);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF RETRY: sid=%08x len=%u ret=%d", rc->stream_id, rc->tx_len, ret);
|
||||
if (ret == 0) {
|
||||
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
|
||||
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
|
||||
if (rc->dst_fin_deferred && !q->head) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
etcp_router_on_send_ready(inst, TOPO_GROUP_UTUN, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
|
||||
&rc->pause_waiter, pause_resume_cb, rc);
|
||||
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS TXBUF BP AGAIN: sid=%08x waiter_reg", rc->stream_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
e = queue_data_get(q);
|
||||
if (!e) {
|
||||
if (rc->dst_fin_deferred) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
}
|
||||
queue_resume_callback(q); return;
|
||||
}
|
||||
if (rc->cli_closed) {
|
||||
do {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
e = queue_data_get(q);
|
||||
} while (e);
|
||||
queue_resume_callback(q);
|
||||
return;
|
||||
}
|
||||
int ret = send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, e->dgram, e->len, 0);
|
||||
rc->bytes_relayed += e->len; rc->drain_count++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DRAIN #%u sid=%08x len=%u → ret=%d rq=%d",
|
||||
rc->drain_count, rc->stream_id, e->len, ret, q->count);
|
||||
if (ret == 0) {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else {
|
||||
rc->tx_buf = u_malloc(e->len);
|
||||
if (rc->tx_buf) { memcpy(rc->tx_buf, e->dgram, e->len); rc->tx_len = e->len; }
|
||||
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TPS tx_buf malloc=%u failed sid=%08x — drop", e->len, rc->stream_id); }
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram); queue_entry_free(e);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:BACKPRESSURE fd=%d sid=%08x — buffered %u for retry",
|
||||
(int)rc->tc->sock, rc->stream_id, rc->tx_len);
|
||||
etcp_router_on_send_ready(inst, TOPO_GROUP_UTUN, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY,
|
||||
&rc->pause_waiter, pause_resume_cb, rc);
|
||||
if (!rc->retry_timer) rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
}
|
||||
}
|
||||
|
||||
static void pause_resume_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
if (!rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RESUME sid=%08x tx_buf=%s rq=%d",
|
||||
rc->stream_id, rc->tx_buf ? "y" : "n",
|
||||
rc->tc->read_queue ? rc->tc->read_queue->count : 0);
|
||||
queue_resume_callback(rc->tc->read_queue);
|
||||
}
|
||||
|
||||
static void retry_timer_cb(void* arg) {
|
||||
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
|
||||
rc->retry_timer = NULL;
|
||||
if (!rc->tx_buf || !rc->tc || rc->tc->sock == SOCKET_INVALID || rc->cli_closed) return;
|
||||
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
|
||||
int ret = send_msg(inst, TOPO_GROUP_UTUN, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->tx_buf, rc->tx_len, 1);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "TPS RETRY TIMER: sid=%08x len=%u force=1 ret=%d", rc->stream_id, rc->tx_len, ret);
|
||||
if (ret == 0) {
|
||||
u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0;
|
||||
if (rc->dst_fin_deferred && rc->tc->read_queue && !rc->tc->read_queue->head) {
|
||||
rc->dst_fin_deferred = 0;
|
||||
send_fin(rc);
|
||||
return;
|
||||
}
|
||||
queue_resume_callback(rc->tc->read_queue);
|
||||
} else {
|
||||
rc->retry_timer = uasync_set_timeout(rc->ua, 5000, rc, retry_timer_cb, "tps_retry");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Управление жизненным циклом коннекта
|
||||
// ====================================================================
|
||||
|
||||
static int conn_total(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc || !rc->ctx) return 0;
|
||||
int n = 0; struct tcp_proxy_server_conn* c;
|
||||
for (c = rc->ctx->conns; c; c = c->next) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
|
||||
if (!rc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE enter rc=%p freed=%d sid=%08x", (void*)rc, rc->freed, rc->stream_id);
|
||||
if (rc->freed) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FREE double rc=%p — IGNORED", (void*)rc);
|
||||
return;
|
||||
}
|
||||
rc->freed = 1;
|
||||
int total = conn_total(rc);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d fin=%d error=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, rc->stream_id, total, rc->cli_closed, rc->tc ? rc->tc->fin_remote : 0, rc->tc ? rc->tc->error : 0);
|
||||
if (rc->close_pending && rc->ctx && rc->ctx->inst) {
|
||||
rc->close_pending = 0;
|
||||
uint8_t subcmd = (rc->tc && rc->tc->error) ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
|
||||
send_msg(rc->ctx->inst, TOPO_GROUP_UTUN, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0, 1);
|
||||
}
|
||||
if (rc->ctx) {
|
||||
struct tcp_proxy_server_conn** prev = &rc->ctx->conns;
|
||||
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
|
||||
}
|
||||
if (rc->ctx && rc->ctx->inst) etcp_router_cancel_send_ready(rc->ctx->inst, TOPO_GROUP_UTUN, rc->peer_node_id, ETCP_RT_ID_TCP_PROXY, &rc->pause_waiter);
|
||||
if (rc->tx_buf) { u_free(rc->tx_buf); rc->tx_buf = NULL; rc->tx_len = 0; }
|
||||
if (rc->retry_timer) { uasync_cancel_timeout(rc->ua, rc->retry_timer); rc->retry_timer = NULL; }
|
||||
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
|
||||
if (rc->diag_timer) { uasync_cancel_timeout(rc->ua, rc->diag_timer); rc->diag_timer = NULL; }
|
||||
if (rc->tc) { tcp_conn_destroy(rc->tc); rc->tc = NULL; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FREE u_free rc=%p", (void*)rc);
|
||||
u_free(rc);
|
||||
}
|
||||
|
||||
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id) {
|
||||
struct tcp_proxy_server_conn* c;
|
||||
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Входные точки из ETCP-диспетчера
|
||||
// ====================================================================
|
||||
|
||||
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry, uint32_t stream_id, uint64_t src_node_id) {
|
||||
if (!inst || !inst->tcp_proxy_server.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
uint8_t* dest_ip = entry->dgram + TCP_PROXY_HDR_SIZE;
|
||||
uint16_t dest_port = 0; memcpy(&dest_port, dest_ip + 4, 2);
|
||||
|
||||
struct tcp_proxy_server_conn* rc = u_calloc(1, sizeof(struct tcp_proxy_server_conn));
|
||||
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: u_calloc failed sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
rc->ctx = ctx; rc->stream_id = stream_id; rc->peer_node_id = src_node_id;
|
||||
memcpy(rc->dest_ip, dest_ip, 4); rc->dest_port = dest_port;
|
||||
rc->ua = inst->ua;
|
||||
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
ctx->conn_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
|
||||
(int)sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
|
||||
socket_set_nonblocking(sock);
|
||||
|
||||
rc->tc = tcp_conn_create(inst->ua, sock, 1500, 8192, 4, 0, inst->config->global.tcp_recv_buf, on_fin_cb, on_error_cb, rc);
|
||||
if (!rc->tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: tcp_conn_create failed"); socket_close_wrapper(sock); ctx->conn_count--; u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
rc->tc->on_closed = on_closed_cb;
|
||||
queue_set_callback(rc->tc->read_queue, read_queue_drain_cb, rc);
|
||||
queue_set_waiter_defer(rc->tc->read_queue, 1);
|
||||
|
||||
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, dest_ip, 4); addr.sin_port = dest_port;
|
||||
int ret = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
if (ret < 0 && errno != EINPROGRESS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: connect() to %d.%d.%d.%d:%d failed: %s",
|
||||
dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], ntohs(dest_port), strerror(errno));
|
||||
send_msg(inst, TOPO_GROUP_UTUN, src_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0, 1);
|
||||
ctx->conn_count--; tcp_conn_destroy(rc->tc); u_free(rc);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
|
||||
rc->next = ctx->conns; ctx->conns = rc;
|
||||
rc->diag_timer = uasync_set_timeout(rc->ua, 10000, rc, diag_timer_cb, "tps_diag");
|
||||
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
|
||||
(void)conn;
|
||||
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc || !rc->tc || rc->tc->sock == SOCKET_INVALID || rc->tc->error || rc->cli_closed) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TPS handle_data: no/closed conn sid=%08x, dropping", stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
|
||||
rc->bytes_sent += (uint32_t)data_len; rc->data_count++;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "TPS DATA #%u sid=%08x len=%zu",
|
||||
rc->data_count, stream_id, data_len);
|
||||
if (data_len > 0) {
|
||||
if (data_len > rc->tc->data_pool->object_size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: data_len=%zu > pool_sz=%zu, dropping sid=%08x",
|
||||
data_len, rc->tc->data_pool->object_size, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
struct ll_entry* e = queue_entry_new_from_pool(rc->tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(rc->tc->data_pool);
|
||||
if (e && buf) {
|
||||
memcpy(buf, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
|
||||
e->dgram = buf; e->len = (uint16_t)data_len;
|
||||
queue_data_put(rc->tc->write_queue, e);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server: handle_data alloc failed sid=%08x", stream_id);
|
||||
if (e) queue_entry_free(e);
|
||||
if (buf) memory_pool_free(rc->tc->data_pool, buf);
|
||||
}
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
rc->cli_closed = 1;
|
||||
if (!rc->tc) { tcp_proxy_server_conn_free(rc); return; }
|
||||
tcp_conn_pause_read(rc->tc);
|
||||
queue_set_callback(rc->tc->read_queue, NULL, NULL);
|
||||
{
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_data_get(rc->tc->read_queue))) {
|
||||
memory_pool_free(rc->tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
}
|
||||
}
|
||||
if (rc->tc->connected) tcp_conn_push_close(rc->tc); else tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc) { DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server: ERROR sid=%08x — no conn", stream_id); return; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR_RECV fd=%d sid=%08x total=%d fin=%d write_pend=%d",
|
||||
rc->tc ? (int)rc->tc->sock : -1, stream_id, conn_total(rc),
|
||||
rc->tc ? rc->tc->fin_remote : 0, rc->tc ? write_pending(rc->tc) : 0);
|
||||
tcp_proxy_server_handle_close(inst, stream_id);
|
||||
}
|
||||
|
||||
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
|
||||
if (!rc || !rc->tc || !rc->tc->connected) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "SOCK:FIN_RECV fd=%d sid=%08x — pushing FIN to wq",
|
||||
(int)rc->tc->sock, stream_id);
|
||||
tcp_conn_push_fin(rc->tc);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// ETCP коллбэк серверной стороны (принимает CONNECT/DATA/CLOSE/ERROR/FIN от клиента)
|
||||
// ====================================================================
|
||||
void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
|
||||
|
||||
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
|
||||
if (entry && entry->len == 9 && !conn && entry->dgram[0] == ETCP_RT_ID_TCP_PROXY) {
|
||||
uint64_t peer_id;
|
||||
memcpy(&peer_id, entry->dgram + 1, 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE_ALL from %016llx — clearing server conns for peer",
|
||||
(unsigned long long)peer_id);
|
||||
if (inst && inst->tcp_proxy_server.enabled) {
|
||||
struct tcp_proxy_server_conn *rc, *next;
|
||||
for (rc = inst->tcp_proxy_server.conns; rc; rc = next) {
|
||||
next = rc->next;
|
||||
if (rc->peer_node_id == peer_id) tcp_proxy_server_conn_free(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, "PROXY SERVER RECV subcmd=%02x sid=%08x pkt_len=%u", subcmd, stream_id, entry->len);
|
||||
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
|
||||
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
|
||||
tcp_proxy_server_handle_connect(inst, entry, stream_id, src_node_id);
|
||||
return;
|
||||
}
|
||||
if (inst && inst->tcp_proxy_server.enabled) {
|
||||
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
|
||||
if (rc) {
|
||||
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_server_handle_data(inst, conn, entry, stream_id); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_server_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_server_handle_error(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_FIN) { tcp_proxy_server_handle_fin(inst, 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) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "PROXY subcmd=%02x sid=%08x — server conn not found, silent drop", subcmd, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry); return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Инициализация / деинициализация
|
||||
// ====================================================================
|
||||
|
||||
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return -1;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
if (!inst->config) return 0;
|
||||
ctx->enabled = inst->config->global.tcp_proxy_server_enabled;
|
||||
ctx->inst = inst;
|
||||
if (!ctx->enabled) return 0;
|
||||
g_tcp_proxy_server_ctx = ctx;
|
||||
etcp_router_bind(inst, ETCP_RT_ID_TCP_PROXY, tcp_proxy_server_recv_cb);
|
||||
if (!inst->config->global.tcp_proxy_client_enabled) {
|
||||
udp_proxy_init(inst, inst->ua);
|
||||
icmp_proxy_init(inst, inst->ua);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server initialized node=%016llx", (unsigned long long)inst->node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return;
|
||||
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroying: conn_count=%d", ctx->conn_count);
|
||||
struct tcp_proxy_server_conn* rc = ctx->conns;
|
||||
while (rc) { struct tcp_proxy_server_conn* next = rc->next; tcp_proxy_server_conn_free(rc); rc = next; }
|
||||
ctx->conns = NULL; ctx->enabled = 0;
|
||||
if (g_tcp_proxy_server_ctx == ctx) g_tcp_proxy_server_ctx = NULL;
|
||||
udp_proxy_destroy(inst);
|
||||
icmp_proxy_destroy(inst);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroyed");
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// tcp_proxy_server.h — TCP прокси-сервер (exit node)
|
||||
#ifndef TCP_PROXY_SERVER_H
|
||||
#define TCP_PROXY_SERVER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/tcp_io.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct ll_entry;
|
||||
struct ETCP_CONN;
|
||||
|
||||
#define TCP_PROXY_SUBCMD_CONNECT 0x01
|
||||
#define TCP_PROXY_SUBCMD_DATA 0x03
|
||||
#define TCP_PROXY_SUBCMD_CLOSE 0x04
|
||||
#define TCP_PROXY_SUBCMD_ERROR 0x05
|
||||
#define TCP_PROXY_SUBCMD_FIN 0x06
|
||||
|
||||
#define TCP_PROXY_HDR_SIZE 6 // svc_id(1)+subcmd(1)+stream_id(4)
|
||||
#define TCP_PROXY_CONNECT_HDR_SIZE 12 // HDR_SIZE + dest_ip(4)+dest_port(2)
|
||||
|
||||
struct tcp_proxy_server_conn {
|
||||
struct tcp_proxy_server_conn* next;
|
||||
struct tcp_proxy_server* ctx;
|
||||
struct tcp_conn* tc;
|
||||
uint32_t stream_id;
|
||||
uint64_t peer_node_id;
|
||||
uint8_t dest_ip[4];
|
||||
uint16_t dest_port;
|
||||
|
||||
uint8_t cli_closed; // client sent CLOSE
|
||||
uint8_t close_pending; // CLOSE/ERROR не доставлен, ретрай
|
||||
|
||||
void* close_timer; // таймер повтора CLOSE/ERROR
|
||||
int close_backoff; // backoff: 50..5000 tb (5ms..500ms)
|
||||
void* diag_timer; // 1-секундный таймер диагностики
|
||||
uint8_t dst_fin_deferred; // FIN от destination отложен (ждём drain read_queue + tx_buf)
|
||||
uint8_t freed; // 1 = уже освобождён, защита от double-free
|
||||
|
||||
struct queue_waiter_handle pause_waiter;
|
||||
void* retry_timer; // fallback timer (500ms) for force=1 retry on stall
|
||||
uint8_t* tx_buf; // буфер при backpressure (retry в pause_resume_cb)
|
||||
uint16_t tx_len;
|
||||
uint32_t bytes_sent; // байт записано в destination (реальный TCP)
|
||||
uint32_t bytes_recv; // байт прочитано от destination
|
||||
uint32_t bytes_relayed; // байт отправлено клиенту через ETCP
|
||||
uint32_t drain_count; // счётчик вызовов read_queue_drain_cb
|
||||
uint32_t data_count; // счётчик входящих DATA от клиента
|
||||
struct UASYNC* ua;
|
||||
};
|
||||
|
||||
struct tcp_proxy_server {
|
||||
int enabled;
|
||||
int conn_count;
|
||||
struct tcp_proxy_server_conn* conns;
|
||||
struct UTUN_INSTANCE* inst;
|
||||
};
|
||||
|
||||
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst);
|
||||
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id);
|
||||
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
|
||||
|
||||
void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
|
||||
uint32_t stream_id, uint64_t src_node_id);
|
||||
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint32_t stream_id);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
# tcp_proxy_server
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
TCP прокси-сервер на exit node. Принимает CONNECT-запросы от клиента через ETCP, создаёт OS TCP сокет к реальному адресату, релеит данные в обе стороны: клиент↔ETCP↔сервер↔OS TCP↔destination. Поддерживает backpressure (пауза чтения из OS сокета при заполнении ETCP очереди), graceful CLOSE, экспоненциальный ретрай управляющих сообщений, диагностические таймеры.
|
||||
|
||||
Работает в паре с `tcp_proxy_client` (на ingress node) и `lwip_tcp` (lwIP-стек для обработки TCP-сессий).
|
||||
|
||||
При старте сервера автоматически инициализируются `udp_proxy` и `icmp_proxy`, если клиентский TCP прокси не активен (чтобы exit node обслуживал все виды трафика).
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
### Включение
|
||||
В конфиге: `tcp_proxy_server_enabled = yes` — сервер регистрирует обработчик `ETCP_RT_ID_TCP_PROXY` через `etcp_router_bind()` и готов принимать команды от клиентов.
|
||||
|
||||
### Типовой сценарий
|
||||
1. **CONNECT (0x01):** клиент шлёт `dest_ip(4) + dest_port(2)`, сервер создаёт `tcp_proxy_server_conn`, открывает OS TCP сокет, делает `connect()` к destination.
|
||||
2. **DATA (0x03):** клиент шлёт данные → сервер пишет в OS write_queue через pool-аллокацию.
|
||||
3. **Чтение из destination:** данные с OS сокета попадают в `read_queue`, оттуда `read_queue_drain_cb` релеит их клиенту через ETCP.
|
||||
4. **CLOSE (0x04):** клиент закрыл соединение → сервер паузит чтение, сливает read_queue, шлёт shutdown(SHUT_WR) в OS сокет.
|
||||
5. **FIN (0x06):** клиент подтверждает получение FIN от destination → сервер push-ит FIN в OS сокет (завершение полного закрытия).
|
||||
6. **ERROR (0x05):** ошибка на клиенте → сервер форсированно закрывает соединение.
|
||||
7. **Очистка:** по `on_closed_cb` (OS сокет закрыт полностью) → `tcp_proxy_server_conn_free()` освобождает все ресурсы.
|
||||
|
||||
### Ключевые нюансы
|
||||
- **Backpressure:** если `etcp_route_send()` вернула ошибку — данные буферизуются в `tx_buf`, регистрируется `pause_waiter` (ожидание освобождения ETCP канала), запускается fallback `retry_timer` (500ms, force=1). При возобновлении — сначала делается повтор tx_buf, потом продолжается дрейн очереди.
|
||||
- **FIN handling:** FIN от destination не отправляется клиенту пока есть pending writes в OS сокет (ждём `on_flushed_cb`) или pending reads (tx_buf / read_queue не пусты — флаг `dst_fin_deferred`). При опустошении очередей — FIN отправляется немедленно.
|
||||
- **Graceful CLOSE:** при получении CLOSE от клиента чтение из OS сокета паузится через `tcp_conn_pause_read()`, read_queue сливается (данные отбрасываются), push-ится close в OS сокет.
|
||||
- **Ретрай CLOSE/ERROR:** при неудачной отправке `send_msg()` — экспоненциальный backoff (50→100→200→…→5000 tb = 5ms→500ms) через `close_timer` + `close_retry_cb`.
|
||||
- **Диагностический таймер (1s):** логирует TCP буферы ОС, размеры read/write очередей, free counts entry/data pool-ов.
|
||||
- **CLOSE_ALL:** специальный 9-байтовый пакет без валидного conn удаляет все соединения для заданного peer.
|
||||
|
||||
## 3. API
|
||||
|
||||
### Структуры
|
||||
|
||||
| Структура | Назначение |
|
||||
|-----------|-----------|
|
||||
| `tcp_proxy_server` | Корневой контекст: enabled, связный список conns, conn_count, ссылка на UTUN_INSTANCE |
|
||||
| `tcp_proxy_server_conn` | Состояние одного прокси-соединения: stream_id, peer_node_id, dest_ip:port, tcp_conn, буферы/счётчики/таймеры для backpressure и закрытия |
|
||||
|
||||
### Функции инициализации/завершения
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_proxy_server_init(inst)` | Регистрирует ETCP биндинг на `ETCP_RT_ID_TCP_PROXY`, авто-инициализирует UDP/ICMP прокси если tcp_proxy_client не активен |
|
||||
| `tcp_proxy_server_destroy(inst)` | Освобождает все соединения, уничтожает UDP/ICMP прокси |
|
||||
|
||||
### Обработчики команд (вызываются из `tcp_proxy_server_recv_cb`)
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_proxy_server_recv_cb(conn, entry)` | Главный ETCP-коллбэк: диспетчеризует subcmd → handle_connect/data/close/error/fin, обрабатывает CLOSE_ALL |
|
||||
| `tcp_proxy_server_handle_connect(inst,entry,sid,src)` | Создаёт OS TCP сокет, `connect()` к destination, аллоцирует `tcp_proxy_server_conn`, регистрирует diag_timer и коллбэки tcp_conn |
|
||||
| `tcp_proxy_server_handle_data(inst,conn,entry,sid)` | Копирует данные клиента в write_queue OS сокета через pool-аллокацию |
|
||||
| `tcp_proxy_server_handle_close(inst, sid)` | Паузит чтение, сливает read_queue, отправляет shutdown в OS сокет, освобождает соединение если не connected |
|
||||
| `tcp_proxy_server_handle_error(inst, sid)` | Делегирует в `handle_close` (обрабатывается идентично CLOSE) |
|
||||
| `tcp_proxy_server_handle_fin(inst, sid)` | Push-ит FIN в write_queue OS сокета (клиент подтвердил получение FIN от destination) |
|
||||
|
||||
### Управление соединениями
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_proxy_server_find_conn(ctx, sid)` | Линейный поиск соединения по stream_id в связном списке |
|
||||
| `tcp_proxy_server_conn_free(rc)` | Полное освобождение: защита от double-free, отмена таймеров, отправка pending close/error если надо, удаление из списка, `tcp_conn_destroy()`, u_free |
|
||||
|
||||
### Внутренние callback-и (статические)
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `send_msg(inst, dst, subcmd, sid, data, len, force)` | Формирует ETCP-пакет (svc_id + subcmd + sid + data) и отправляет через `etcp_route_send()` |
|
||||
| `send_close(rc)` | Отправляет CLOSE клиенту, при неудаче запускает экспоненциальный ретрай через `close_timer` |
|
||||
| `send_error(rc)` | Аналогично `send_close`, но шлёт ERROR |
|
||||
| `send_fin(rc)` | Отправляет FIN клиенту (без ретрая) |
|
||||
| `on_fin_cb(tc, arg)` | OS сокет получил FIN → машина состояний: если локальный FIN тоже → close; если write pending → ждать flush; если read pending → отложить FIN |
|
||||
| `on_flushed_cb(tc, arg)` | Write очередь OS сокета опустела → отправить FIN клиенту или закрыть если оба FIN получены |
|
||||
| `on_error_cb(tc, err, arg)` | Ошибка OS сокета → отправить ERROR клиенту, освободить соединение |
|
||||
| `on_closed_cb(tc, arg)` | OS сокет полностью закрыт → освободить соединение |
|
||||
| `read_queue_drain_cb(q, arg)` | Дрейн read_queue OS сокета → отправка данных клиенту через ETCP; backpressure: буферизация в tx_buf + pause_waiter + retry_timer |
|
||||
| `pause_resume_cb(q, arg)` | ETCP канал освободился → возобновить дрейн read_queue |
|
||||
| `retry_timer_cb(arg)` | Fallback-таймер 500ms: повтор tx_buf с force=1, при успехе возобновляет read_queue |
|
||||
| `close_retry_cb(arg)` | Экспоненциальный backoff для повторной отправки CLOSE/ERROR (50→5000 tb) |
|
||||
| `diag_timer_cb(arg)` | 1-секундный таймер: логирует TCP буферы ОС, размеры очередей, free-counts pool-ов |
|
||||
|
||||
### Константы
|
||||
|
||||
| Макрос | Значение | Описание |
|
||||
|--------|----------|----------|
|
||||
| `TCP_PROXY_HDR_SIZE` | 6 | svc_id(1) + subcmd(1) + stream_id(4) |
|
||||
| `TCP_PROXY_CONNECT_HDR_SIZE` | 12 | HDR_SIZE + dest_ip(4) + dest_port(2) |
|
||||
| `TCP_PROXY_SUBCMD_CONNECT` | 0x01 | Запрос на создание TCP соединения к destination |
|
||||
| `TCP_PROXY_SUBCMD_DATA` | 0x03 | Данные для отправки в destination |
|
||||
| `TCP_PROXY_SUBCMD_CLOSE` | 0x04 | Клиент закрывает прокси-соединение |
|
||||
| `TCP_PROXY_SUBCMD_ERROR` | 0x05 | Ошибка на стороне клиента |
|
||||
| `TCP_PROXY_SUBCMD_FIN` | 0x06 | Клиент подтвердил FIN от destination |
|
||||
|
||||
### Зависимости
|
||||
|
||||
| Модуль | Использование |
|
||||
|--------|--------------|
|
||||
| `tcp_io.h` | `tcp_conn` — абстракция OS TCP сокета с read/write очередями и memory pool-ами |
|
||||
| `ll_queue.h` | Очереди read_queue/write_queue OS сокета, pause_waiter для backpressure |
|
||||
| `etcp_router.h` | `etcp_router_bind()`, `etcp_route_send()`, `etcp_router_on_send_ready()`, `etcp_router_cancel_send_ready()` |
|
||||
| `u_async.h` | Таймеры (`uasync_set_timeout`, `uasync_cancel_timeout`) |
|
||||
| `udp_proxy.h` | Авто-инициализация при старте сервера |
|
||||
| `icmp_proxy.h` | Авто-инициализация при старте сервера |
|
||||
| `config_parser.h` | Флаг `tcp_proxy_server_enabled` |
|
||||
| `mem.h` | `u_malloc`/`u_free`/`u_calloc` для tx_buf и структур |
|
||||
@@ -0,0 +1,265 @@
|
||||
// udp_proxy.c — UDP датаграммный прокси: клиент ↔ exit через etcp_router
|
||||
#include "udp_proxy.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "tun_if.h"
|
||||
#include "../routing_layer/topo_node.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#define UDP_FLOW_TIMEOUT_TB 600000 // 60с
|
||||
|
||||
static struct udp_proxy_ctx* g_udp_ctx = NULL;
|
||||
|
||||
static void flow_expire_timer_cb(void* arg);
|
||||
static void flow_expire(struct udp_proxy_ctx* ctx);
|
||||
|
||||
static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port) {
|
||||
struct udp_flow* f;
|
||||
for (f = head; f; f = f->next)
|
||||
if (f->client_node_id == client_node_id && f->src_ip == src_ip &&
|
||||
f->src_port == src_port && f->dst_ip == dst_ip && f->dst_port == dst_port) return f;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void flow_read_cb(socket_t sock, void* arg) {
|
||||
(void)sock; struct udp_flow* f = (struct udp_flow*)arg;
|
||||
if (!f || f->sock == SOCKET_INVALID || !g_udp_ctx) return;
|
||||
uint8_t buf[1600];
|
||||
struct sockaddr_in from; socklen_t flen = sizeof(from);
|
||||
ssize_t n = recvfrom(f->sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen);
|
||||
if (n <= 0) return;
|
||||
|
||||
f->last_activity_tb = get_time_tb();
|
||||
|
||||
// Обернуть ответ: меняем src/dst для восстановления на стороне клиента
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) return;
|
||||
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + n);
|
||||
if (!e->dgram) { queue_entry_free(e); return; }
|
||||
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
|
||||
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
|
||||
memcpy(e->dgram + 2, &f->client_node_id, 8);
|
||||
// Ответ src = оригинальный dst_ip:dest_port
|
||||
memcpy(e->dgram + 10, &f->dst_ip, 4);
|
||||
memcpy(e->dgram + 14, &f->dst_port, 2);
|
||||
// Ответ dst = оригинальный src_ip:src_port
|
||||
memcpy(e->dgram + 16, &f->src_ip, 4);
|
||||
memcpy(e->dgram + 20, &f->src_port, 2);
|
||||
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n);
|
||||
e->len = UDP_PROXY_HDR_SIZE + n;
|
||||
|
||||
etcp_route_send(g_udp_ctx->inst, TOPO_GROUP_UTUN, f->client_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Exit узел: принять REQUEST, создать сокет, переслать
|
||||
// ====================================================================
|
||||
static void exit_handle_data(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL);
|
||||
if (!inst || !g_udp_ctx || entry->len < UDP_PROXY_HDR_SIZE + 1) goto drop;
|
||||
|
||||
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8);
|
||||
uint32_t src_ip; memcpy(&src_ip, entry->dgram + 10, 4);
|
||||
uint16_t src_port; memcpy(&src_port, entry->dgram + 14, 2);
|
||||
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 16, 4);
|
||||
uint16_t dst_port; memcpy(&dst_port, entry->dgram + 20, 2);
|
||||
uint8_t* payload = entry->dgram + UDP_PROXY_HDR_SIZE;
|
||||
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE;
|
||||
|
||||
struct udp_flow* f = flow_find(g_udp_ctx->flows, client_node_id, src_ip, src_port, dst_ip, dst_port);
|
||||
if (!f) {
|
||||
f = u_calloc(1, sizeof(struct udp_flow));
|
||||
if (!f) goto drop;
|
||||
f->client_node_id = client_node_id; f->src_ip = src_ip; f->src_port = src_port;
|
||||
f->dst_ip = dst_ip; f->dst_port = dst_port;
|
||||
f->ua = g_udp_ctx->ua; f->created_tb = get_time_tb(); f->last_activity_tb = f->created_tb;
|
||||
|
||||
f->sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (f->sock == SOCKET_INVALID) { u_free(f); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "udp_proxy: socket failed"); goto drop; }
|
||||
socket_set_nonblocking(f->sock);
|
||||
struct sockaddr_in bind_addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = INADDR_ANY}, .sin_port = 0};
|
||||
bind(f->sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr));
|
||||
f->read_id = uasync_add_socket_t(g_udp_ctx->ua, f->sock, flow_read_cb, NULL, NULL, f);
|
||||
if (!f->read_id) { socket_close_wrapper(f->sock); u_free(f); goto drop; }
|
||||
f->next = g_udp_ctx->flows; g_udp_ctx->flows = f; g_udp_ctx->flow_count++;
|
||||
if (!g_udp_ctx->expire_timer)
|
||||
g_udp_ctx->expire_timer = uasync_set_timeout(g_udp_ctx->ua, g_udp_ctx->flow_timeout_tb, g_udp_ctx, flow_expire_timer_cb, "udp_expire");
|
||||
}
|
||||
f->last_activity_tb = get_time_tb();
|
||||
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}, .sin_port = dst_port};
|
||||
sendto(f->sock, payload, payload_len, 0, (struct sockaddr*)&addr, sizeof(addr));
|
||||
|
||||
drop:
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: принять REPLY, доставить в TUN
|
||||
// ====================================================================
|
||||
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
// svc_id уже обработан диспетчером etcp_router
|
||||
uint32_t src_ip, dst_ip;
|
||||
uint16_t src_port, dst_port;
|
||||
uint8_t* payload = entry->dgram + 1 + 1 + 8 + 4 + 2 + 4 + 2; // svc_id + subcmd + node_id + src_ip:port + dst_ip:port
|
||||
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE;
|
||||
|
||||
memcpy(&dst_ip, entry->dgram + 1 + 1 + 8 + 4 + 2, 4); // src в сообщении → dst в ответе
|
||||
memcpy(&dst_port, entry->dgram + 1 + 1 + 8 + 4 + 2 + 4, 2);
|
||||
memcpy(&src_ip, entry->dgram + 1 + 1 + 8, 4); // dst в сообщении → src в ответе
|
||||
memcpy(&src_port, entry->dgram + 1 + 1 + 8 + 4, 2);
|
||||
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL);
|
||||
udp_proxy_deliver_reply(inst, src_ip, src_port, dst_ip, dst_port, payload, payload_len);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Единый etcp_router коллбэк
|
||||
// ====================================================================
|
||||
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 2) {
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
if (subcmd == UDP_PROXY_SUBCMD_DATA) {
|
||||
if (g_udp_ctx && g_udp_ctx->is_exit) exit_handle_data(conn, entry);
|
||||
else client_handle_reply(conn, entry);
|
||||
return;
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: отправить UDP датаграмму в exit узел
|
||||
// ====================================================================
|
||||
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst || !g_udp_ctx) return -1;
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) return -1;
|
||||
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + payload_len);
|
||||
if (!e->dgram) { queue_entry_free(e); return -1; }
|
||||
e->dgram[0] = ETCP_RT_ID_UDP_PROXY;
|
||||
e->dgram[1] = UDP_PROXY_SUBCMD_DATA;
|
||||
memcpy(e->dgram + 2, &inst->node_id, 8);
|
||||
memcpy(e->dgram + 10, &src_ip, 4);
|
||||
memcpy(e->dgram + 14, &src_port, 2);
|
||||
memcpy(e->dgram + 16, &dst_ip, 4);
|
||||
memcpy(e->dgram + 20, &dst_port, 2);
|
||||
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len);
|
||||
e->len = UDP_PROXY_HDR_SIZE + payload_len;
|
||||
return etcp_route_send(inst, TOPO_GROUP_UTUN, exit_node_id, e, 0);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Сторона клиента: доставить UDP ответ в TUN
|
||||
// ====================================================================
|
||||
int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) return -1;
|
||||
|
||||
// Собрать IP/UDP ответный пакет
|
||||
size_t ip_len = 20 + 8 + payload_len;
|
||||
uint8_t* pkt = u_malloc(ip_len);
|
||||
if (!pkt) return -1;
|
||||
|
||||
memset(pkt, 0, 20);
|
||||
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_UDP;
|
||||
memcpy(pkt + 12, &src_ip, 4); memcpy(pkt + 16, &dst_ip, 4);
|
||||
|
||||
uint16_t total_len = htons(20 + 8 + payload_len);
|
||||
memcpy(pkt + 2, &total_len, 2);
|
||||
|
||||
{ uint32_t ip_sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); ip_sum += w; }
|
||||
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16); ip_sum += (ip_sum >> 16);
|
||||
uint16_t cs = (uint16_t)(~ip_sum); memcpy(pkt + 10, &cs, 2); }
|
||||
|
||||
uint16_t udp_len = htons(8 + payload_len);
|
||||
memcpy(pkt + 24, &udp_len, 2);
|
||||
memcpy(pkt + 20, &src_port, 2);
|
||||
memcpy(pkt + 22, &dst_port, 2);
|
||||
if (payload_len > 0) memcpy(pkt + 28, payload, payload_len);
|
||||
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) { u_free(pkt); return -1; }
|
||||
e->dgram = u_malloc(1 + ip_len);
|
||||
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, ip_len); e->len = 1 + ip_len;
|
||||
u_free(pkt);
|
||||
queue_data_put(inst->tcp_proxy_client->tun->input_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void flow_expire_timer_cb(void* arg) {
|
||||
struct udp_proxy_ctx* ctx = (struct udp_proxy_ctx*)arg;
|
||||
flow_expire(ctx);
|
||||
ctx->expire_timer = NULL;
|
||||
if (ctx->flows)
|
||||
ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->flow_timeout_tb, ctx, flow_expire_timer_cb, "udp_expire");
|
||||
}
|
||||
|
||||
static void flow_expire(struct udp_proxy_ctx* ctx) {
|
||||
uint64_t now = get_time_tb(); struct udp_flow** prev = &ctx->flows;
|
||||
while (*prev) {
|
||||
struct udp_flow* f = *prev;
|
||||
if (now - f->last_activity_tb > ctx->flow_timeout_tb) {
|
||||
*prev = f->next; ctx->flow_count--;
|
||||
if (f->read_id) { uasync_remove_socket_t(f->ua, f->sock); f->read_id = NULL; }
|
||||
socket_close_wrapper(f->sock); u_free(f);
|
||||
} else prev = &f->next;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Публичные init/destroy
|
||||
// ====================================================================
|
||||
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
|
||||
if (!inst) return -1;
|
||||
struct udp_proxy_ctx* ctx = u_calloc(1, sizeof(struct udp_proxy_ctx));
|
||||
if (!ctx) return -1;
|
||||
ctx->inst = inst; ctx->ua = ua;
|
||||
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
|
||||
ctx->expire_timer = NULL;
|
||||
ctx->is_exit = inst->tcp_proxy_server.enabled;
|
||||
g_udp_ctx = ctx;
|
||||
|
||||
etcp_router_bind(inst, ETCP_RT_ID_UDP_PROXY, udp_proxy_recv_cb);
|
||||
ctx->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy initialized (exit=%d)", ctx->is_exit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void udp_proxy_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst || !g_udp_ctx) return;
|
||||
etcp_router_unbind(inst, ETCP_RT_ID_UDP_PROXY);
|
||||
if (g_udp_ctx->expire_timer) { uasync_cancel_timeout(g_udp_ctx->ua, g_udp_ctx->expire_timer); g_udp_ctx->expire_timer = NULL; }
|
||||
struct udp_flow* f = g_udp_ctx->flows;
|
||||
while (f) { struct udp_flow* n = f->next;
|
||||
if (f->read_id) { uasync_remove_socket_t(g_udp_ctx->ua, f->sock); f->read_id = NULL; }
|
||||
socket_close_wrapper(f->sock); u_free(f); f = n; }
|
||||
u_free(g_udp_ctx); g_udp_ctx = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy destroyed");
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// udp_proxy.h — UDP датаграммный прокси: клиент ↔ exit через etcp_router
|
||||
#ifndef UDP_PROXY_H
|
||||
#define UDP_PROXY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include "../lib/socket_compat.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct ll_entry;
|
||||
struct ETCP_CONN;
|
||||
|
||||
// Подкоманды
|
||||
#define UDP_PROXY_SUBCMD_DATA 0x01 // client→exit: пробрось датаграмму | exit→client: ответ
|
||||
|
||||
// Заголовок сообщения (не включая байт svc_id)
|
||||
// svc_id(1) + subcmd(1) + sender_node_id(8) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload
|
||||
#define UDP_PROXY_HDR_SIZE 22
|
||||
|
||||
// Один UDP поток (сторона exit узла: сопоставляет client_node_id + кортеж адресов с OS сокетом)
|
||||
struct udp_flow {
|
||||
struct udp_flow* next;
|
||||
uint64_t client_node_id;
|
||||
uint32_t src_ip;
|
||||
uint16_t src_port;
|
||||
uint32_t dst_ip;
|
||||
uint16_t dst_port;
|
||||
socket_t sock;
|
||||
void* read_id;
|
||||
struct UASYNC* ua;
|
||||
uint64_t created_tb;
|
||||
uint64_t last_activity_tb;
|
||||
};
|
||||
|
||||
struct udp_proxy_ctx {
|
||||
int initialized;
|
||||
int is_exit; // 1=exit узел (создаёт сокеты), 0=клиент (шлёт через etcp)
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct UASYNC* ua;
|
||||
struct udp_flow* flows;
|
||||
int flow_count;
|
||||
uint64_t flow_timeout_tb;
|
||||
void* expire_timer; // периодический таймер очистки истёкших потоков
|
||||
};
|
||||
|
||||
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua);
|
||||
void udp_proxy_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
// Exit узел: принимает REQUEST, создаёт сокет, шлёт данные
|
||||
// Сторона клиента: принимает REPLY, доставляет на TUN
|
||||
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
|
||||
// Сторона клиента: принять IP/UDP пакет с TUN, отправить через etcp_route_send
|
||||
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port,
|
||||
const uint8_t* payload, size_t payload_len);
|
||||
|
||||
// Сторона клиента: доставить ответ на TUN (вызывается из recv_cb)
|
||||
int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
|
||||
uint32_t src_ip, uint16_t src_port,
|
||||
uint32_t dst_ip, uint16_t dst_port,
|
||||
const uint8_t* payload, size_t payload_len);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // UDP_PROXY_H
|
||||
@@ -0,0 +1,73 @@
|
||||
# UDP Proxy (udp_proxy)
|
||||
|
||||
## 1. Назначение
|
||||
Проксирование UDP-датаграмм между клиентским и exit-узлом по протоколу ETCP. Клиентский узел
|
||||
принимает UDP-пакеты с TUN-интерфейса, инкапсулирует их и отправляет через `etcp_route_send`
|
||||
на exit-узел. Exit-узел создаёт нативные UDP-сокеты для каждого потока (5-tuple) и ретранслирует
|
||||
датаграммы во внешнюю сеть. Ответы проходят обратный путь — от exit к клиенту и затем в TUN.
|
||||
|
||||
Один экземпляр контекста на процесс (глобальный `g_udp_ctx`), режим определяется флагом
|
||||
`inst->tcp_proxy_server.enabled`: включён → `is_exit=1`, иначе `is_exit=0`.
|
||||
|
||||
## 2. Как пользоваться
|
||||
|
||||
### Инициализация
|
||||
```c
|
||||
udp_proxy_init(inst, ua); // exit или клиент — определяется автоматически
|
||||
```
|
||||
При инициализации регистрируется коллбэк `udp_proxy_recv_cb` в `etcp_router` для сервиса
|
||||
`ETCP_RT_ID_UDP_PROXY`.
|
||||
|
||||
### Клиент: отправка датаграммы в exit
|
||||
```c
|
||||
udp_proxy_send_to_exit(inst, exit_node_id, src_ip, src_port, dst_ip, dst_port, payload, len);
|
||||
```
|
||||
Вызывается из обработчика TUN-трафика. Пакет упаковывается в формат ETCP и отправляется
|
||||
через `etcp_route_send`. Ответ приходит асинхронно в `udp_proxy_recv_cb`, который вызывает
|
||||
`udp_proxy_deliver_reply` для записи IP/UDP-пакета обратно в TUN.
|
||||
|
||||
### Exit: приём датаграмм
|
||||
Exit автоматически принимает REQUEST-ы через `etcp_router`. Для каждого нового потока
|
||||
(уникальный `client_node_id + src_ip:port + dst_ip:port`) создаётся нативный UDP-сокет,
|
||||
который регистрируется в `uasync` на чтение. При получении ответа от внешнего хоста
|
||||
ответ упаковывается и отправляется обратно клиенту.
|
||||
|
||||
### Очистка
|
||||
```c
|
||||
udp_proxy_destroy(inst); // закрывает все flows, отменяет таймер, unbind из etcp_router
|
||||
```
|
||||
|
||||
### Нюансы
|
||||
- Потоки истекают через 60 секунд неактивности (`UDP_FLOW_TIMEOUT_TB`). Таймер `expire_timer`
|
||||
запускается при создании первого потока, перезапускается после каждой очистки.
|
||||
- UDP-сокеты на exit привязываются к `INADDR_ANY:0` (bind), трафик отправляется через `sendto`.
|
||||
- Ответы в TUN на клиенте доставляются через `inst->tcp_proxy_client->tun->input_queue`.
|
||||
|
||||
## 3. API
|
||||
|
||||
### Структуры
|
||||
| Структура | Назначение |
|
||||
|-----------|-----------|
|
||||
| `struct udp_flow` | Состояние одного UDP-потока на exit: 5-tuple, нативный сокет, uasync read_id, времена создания и последней активности. Связный список. |
|
||||
| `struct udp_proxy_ctx` | Глобальный контекст: флаг is_exit, inst, ua, список flows, таймаут истечения, таймер очистки. |
|
||||
|
||||
### Формат пакета
|
||||
| Смещение | Размер | Поле |
|
||||
|----------|--------|------|
|
||||
| 0 | 1 | `svc_id` = `ETCP_RT_ID_UDP_PROXY` |
|
||||
| 1 | 1 | `subcmd` = `UDP_PROXY_SUBCMD_DATA` (0x01) |
|
||||
| 2 | 8 | `sender_node_id` |
|
||||
| 10 | 4 | `src_ip` (клиент→exit: IP отправителя; exit→клиент: dst_ip) |
|
||||
| 14 | 2 | `src_port` |
|
||||
| 16 | 4 | `dst_ip` (клиент→exit: IP назначения; exit→клиент: src_ip) |
|
||||
| 20 | 2 | `dst_port` |
|
||||
| 22 | N | `payload` (UDP-датаграмма) |
|
||||
|
||||
### Функции
|
||||
| Функция | Назначение |
|
||||
|---------|-----------|
|
||||
| `udp_proxy_init(inst, ua)` | Выделяет контекст, определяет is_exit, регистрирует коллбэк в etcp_router. Возвращает 0 при успехе. |
|
||||
| `udp_proxy_destroy(inst)` | Закрывает все потоки, сокеты, отменяет таймеры, отвязывает коллбэк, освобождает память. |
|
||||
| `udp_proxy_recv_cb(conn, entry)` | Единый коллбэк etcp_router. По subcmd направляет в exit_handle_data (exit) или client_handle_reply (клиент). |
|
||||
| `udp_proxy_send_to_exit(inst, ...)` | Клиент: упаковывает UDP-датаграмму и отправляет через etcp_route_send на exit. |
|
||||
| `udp_proxy_deliver_reply(inst, ...)` | Клиент: собирает IP/UDP-пакет и помещает в TUN input_queue для доставки приложению. |
|
||||
Reference in new issue
Block a user