Files
utun2/src/proxy/proxy_protocol.h
T

176 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Общий TCP proxy протокол и inline-машина окна; все вызовы выполняются в uasync владельца.
* init задаёт окно, но ready устанавливает владелец после CONNECTED (на exit — flush).
* Перед destroy владельца обязателен proxy_flow_destroy: retry хранит указатель на flow.
* Отправка: svc(1), cmd(1), sid(4), payload; приём: router header(26), cmd(1), sid(4), payload.
* sid и WINDOW uint32_t копируются в host byte order; IPv4/порт CONNECT — network byte order.
* CONNECTED подтверждает реальный connect exit → destination. DATA ограничен окном потока.
* FIN завершает одно направление; владелец ставит fin_pending только после всех своих DATA.
* CLOSE/ERROR и CONNECT отправляет владелец напрямую: общий retry их не сохраняет. */
#ifndef PROXY_PROTOCOL_H
#define PROXY_PROTOCOL_H
#include <stdint.h>
#include <string.h>
#include "../routing_layer/etcp_router.h"
#include "../routing_layer/topo_node.h"
#include "../../lib/u_async.h"
#include "../../lib/ll_queue.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#define TCP_PROXY_SUBCMD_CONNECT 0x01
#define TCP_PROXY_SUBCMD_CONNECTED 0x02
#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_SUBCMD_WINDOW 0x07
#define TCP_PROXY_HDR_SIZE 6
#define TCP_PROXY_CONNECT_HDR_SIZE 12
#define TCP_PROXY_RECV_HDR_SIZE (ROUTER_SVC_PAYLOAD_OFF + 5) // 31 байт с метаданными router, не wire header
#define TCP_PROXY_CHUNK 4096
#define TCP_PROXY_WINDOW 65536u
struct proxy_flow {
struct UTUN_INSTANCE* inst; // Заимствованный instance для отправки в TOPO_GROUP_UTUN, mode=0.
struct UASYNC* ua; // Заимствованный event loop; живёт дольше flow и retry.
uint64_t peer; // Конечный узел противоположной стороны потока.
uint32_t sid; // ID потока; на exit уникален только вместе с peer.
uint8_t svc; // Сервис получателя: TCP_PROXY_SERVER или TCP_PROXY_CLIENT.
uint8_t ready; // Разрешены DATA и FIN; начальное значение 0.
uint8_t connected_pending; // Exit должен поставить CONNECTED в router перед остальными control.
uint8_t fin_pending; // Владелец уже передал все DATA и просит отправить FIN.
uint8_t fin_sent; // FIN принят router; это не ACK конечного TCP.
uint8_t fin_received; // Владелец принял FIN; последующие DATA отклоняются.
uint32_t tx_credit; // Сколько DATA ещё разрешено отправить, изначально TCP_PROXY_WINDOW.
uint32_t rx_credit; // Сколько DATA разрешено принять до следующего WINDOW.
uint32_t consumed; // Потреблённые байты, кредит которых ещё не передан router.
void* retry; // Отменяемый таймер control, 5000 tb = 500 мс.
void (*wake)(void* arg); // Возобновление очередей владельца после timer flush; может освободить владельца.
void* arg; // Заимствованный аргумент wake.
};
// Копирует заимствованный data (len<=4096; при len>0 не NULL), отправляет в UTUN с mode=0.
// 0 = принято router, <0 = ошибка; force обходит лимит send_q, не гарантирует доставку.
// Созданный entry целиком передаётся router, который освобождает его при любом результате.
static inline int proxy_send(struct UTUN_INSTANCE* inst, uint64_t peer, uint8_t svc, uint8_t cmd,
uint32_t sid, const uint8_t* data, size_t len, int force) {
if (len > TCP_PROXY_CHUNK) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: oversized message sid=%08x cmd=%u len=%zu", sid, cmd, len);
return -1;
}
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: entry allocation failed sid=%08x", sid); return -1; }
e->dgram = (uint8_t*)u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: payload allocation failed sid=%08x", sid);
queue_entry_free(e); return -1;
}
e->dgram[0] = svc; e->dgram[1] = cmd;
memcpy(e->dgram + 2, &sid, 4);
if (len) memcpy(e->dgram + TCP_PROXY_HDR_SIZE, data, len);
e->len = TCP_PROXY_HDR_SIZE + len;
return etcp_route_send(inst, TOPO_GROUP_UTUN, peer, e, force, 0);
}
// Отправляет pending CONNECTED → WINDOW → FIN с force=1; неудачу повторяет через 500 мс.
// Владелец сам дренирует DATA перед fin_pending; flush не проверяет его очереди.
static inline void proxy_flow_flush(struct proxy_flow* f);
// Повторяем только недоставленные control-сообщения; DATA остаётся у владельца потока.
static inline void proxy_flow_retry(void* arg) {
struct proxy_flow* f = (struct proxy_flow*)arg;
f->retry = NULL;
proxy_flow_flush(f);
if (f->wake) f->wake(f->arg);
}
// Обнуляет неактивный f, задаёт peer/svc/sid и оба окна по 64 KiB; указатели заимствуются.
// Перед повторным init нужно отменить прежний retry через destroy; ready остаётся 0.
static inline void proxy_flow_init(struct proxy_flow* f, struct UTUN_INSTANCE* inst, struct UASYNC* ua,
uint64_t peer, uint8_t svc, uint32_t sid, void (*wake)(void*), void* arg) {
memset(f, 0, sizeof(*f));
f->inst = inst; f->ua = ua; f->peer = peer; f->svc = svc; f->sid = sid;
f->tx_credit = TCP_PROXY_WINDOW; f->rx_credit = TCP_PROXY_WINDOW;
f->wake = wake; f->arg = arg;
}
// Отменяет только control retry; память flow, очереди и router waiter освобождает владелец.
static inline void proxy_flow_destroy(struct proxy_flow* f) {
if (f->retry) { uasync_cancel_timeout(f->ua, f->retry); f->retry = NULL; }
}
// Сначала CONNECTED, затем WINDOW и FIN. Force обходит лишь очередь router, не окно потока.
static inline void proxy_flow_flush(struct proxy_flow* f) {
if (f->connected_pending) {
if (proxy_send(f->inst, f->peer, f->svc, TCP_PROXY_SUBCMD_CONNECTED, f->sid, NULL, 0, 1) < 0) goto retry;
f->connected_pending = 0; f->ready = 1;
}
if (f->consumed) {
uint32_t credit = f->consumed;
// Резервируем до синхронной loopback-доставки.
f->consumed = 0; f->rx_credit += credit;
if (proxy_send(f->inst, f->peer, f->svc, TCP_PROXY_SUBCMD_WINDOW, f->sid, (uint8_t*)&credit, 4, 1) < 0) {
f->consumed += credit; f->rx_credit -= credit; goto retry;
}
}
if (f->fin_pending && f->ready) {
if (proxy_send(f->inst, f->peer, f->svc, TCP_PROXY_SUBCMD_FIN, f->sid, NULL, 0, 1) < 0) goto retry;
f->fin_pending = 0; f->fin_sent = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "proxy: FIN queued peer=%016llx sid=%08x", (unsigned long long)f->peer, f->sid);
}
return;
retry:
DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "proxy: control retry sid=%08x connected=%u credit=%u fin=%u",
f->sid, f->connected_pending, f->consumed, f->fin_pending);
if (!f->retry) f->retry = uasync_set_timeout(f->ua, 5000, f, proxy_flow_retry, "proxy_control");
}
// Копирует DATA; 0 = принято router, -2 = ждём окно/CONNECTED, -1 = неверный DATA/ошибка router или выделения.
// При ошибке восстанавливает tx_credit; исходный буфер остаётся у владельца для повтора.
static inline int proxy_flow_send(struct proxy_flow* f, const uint8_t* data, size_t len, int force) {
if (!f->ready || len > f->tx_credit) return -2;
if (!len || len > TCP_PROXY_CHUNK || f->fin_sent) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: invalid DATA sid=%08x len=%zu fin=%u", f->sid, len, f->fin_sent);
return -1;
}
f->tx_credit -= len;
int ret = proxy_send(f->inst, f->peer, f->svc, TCP_PROXY_SUBCMD_DATA, f->sid, data, len, force);
if (ret < 0) f->tx_credit += len;
return ret;
}
// Проверяет DATA len=1..4096, доступное rx_credit и отсутствие FIN; 0 списывает кредит, -1 = ошибка.
// Проверку ready и доставку байтов локальному TCP выполняет вызывающая сторона.
static inline int proxy_flow_receive(struct proxy_flow* f, size_t len) {
if (!len || len > TCP_PROXY_CHUNK || len > f->rx_credit || f->fin_received) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: invalid receive sid=%08x len=%zu credit=%u fin=%u",
f->sid, len, f->rx_credit, f->fin_received);
return -1;
}
f->rx_credit -= len;
return 0;
}
// WINDOW: заимствованные 4 байта host-order credit; 0 добавляет кредит, -1 = неверный размер/кредит.
// Не вызывает wake: владелец после успеха сам возобновляет отправку.
static inline int proxy_flow_window(struct proxy_flow* f, const uint8_t* data, size_t len) {
uint32_t credit = 0;
if (len == 4) memcpy(&credit, data, 4);
if (!credit || credit > TCP_PROXY_WINDOW - f->tx_credit) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "proxy: invalid WINDOW sid=%08x len=%zu credit=%u tx=%u", f->sid, len, credit, f->tx_credit);
return -1;
}
f->tx_credit += credit;
DEBUG_TRACE(DEBUG_CATEGORY_PROXY, "proxy: WINDOW sid=%08x add=%u available=%u", f->sid, credit, f->tx_credit);
return 0;
}
// Возвращает кредит через flush после передачи len байт локальному TCP (OS send/lwIP tcp_write).
// Владелец гарантирует, что байты ранее приняты и не учтены повторно; проверки здесь нет.
static inline void proxy_flow_consume(struct proxy_flow* f, uint32_t len) {
f->consumed += len;
proxy_flow_flush(f);
}
#endif