Files
utun2/src/proxy/proxy_protocol.h
T

158 lines
6.8 KiB
C

// Общий TCP proxy протокол. DATA ограничен окном каждого потока; WINDOW возвращает
// кредит после потребления данных локальным TCP. FIN следует за всеми DATA своего
// направления. CONNECTED подтверждает реальный connect exit → destination.
#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)
#define TCP_PROXY_CHUNK 4096
#define TCP_PROXY_WINDOW 65536u
struct proxy_flow {
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
uint64_t peer;
uint32_t sid;
uint8_t svc;
uint8_t ready;
uint8_t connected_pending;
uint8_t fin_pending;
uint8_t fin_sent;
uint8_t fin_received;
uint32_t tx_credit;
uint32_t rx_credit;
uint32_t consumed;
void* retry;
void (*wake)(void* arg);
void* arg;
};
// Сообщение целиком передаётся маршрутизатору, который освобождает entry при любом результате.
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);
}
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);
}
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;
}
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");
}
// 0 = отправлено, -2 = ждём окно/CONNECTED, -1 = router backpressure/ошибка выделения.
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;
}
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;
}
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;
}
// Вызывать только после передачи байтов локальному TCP, не после получения ETCP DATA.
static inline void proxy_flow_consume(struct proxy_flow* f, uint32_t len) {
f->consumed += len;
proxy_flow_flush(f);
}
#endif