You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

13 KiB

socks_proxy

1. Назначение

Модуль реализует локальные SOCKS5 CONNECT, HTTP/1.0–1.1 forward proxy и HTTP CONNECT. Разрешает доменное имя назначения и передаёт TCP через ETCP к tcp_proxy_server на exit node. Обычный HTTP разбирается до границы каждого запроса и ответа; CONNECT и согласованный Upgrade переходят в сырой туннель.

Файлы:

  • src/proxy/socks_proxy.c — реализация
  • src/proxy/socks_proxy.h — интерфейс
  • src/proxy/http_proxy.c/h — потоковый HTTP codec, независимый от DNS, TCP и ETCP

2. Как пользоваться

Инициализация

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:

int handled = socks_proxy_handle_etcp(&conns, &conn_count, stream_id, subcmd, data, data_len);
// handled=1 — найден и обработан, 0 — stream_id не найден

Поддерживаемые subcmd: CONNECTED, DATA, WINDOW, CLOSE, ERROR, FIN.

Поиск соединения

struct socks_proxy_conn* c = socks_proxy_find_conn(conns, stream_id);
// обход связного списка, O(N)

Завершение

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* + счётчик. В HTTP keep-alive каждое сообщение получает новый upstream stream; клиентский TCP-сокет сохраняется. Pipelining обрабатывается последовательно, включая запросы к разным адресатам.
  • 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_*)
  • http — отдельное состояние HTTP-транзакции и таймер; NULL для SOCKS5
  • next_stream_id — заимствованный генератор идентификаторов последующих HTTP-транзакций
  • rem_closed, fin_remote, close_sent, close_pending — флаги состояния закрытия
  • buf[16384], buf_len — непрочитанный хвост входного блока; HTTP headers хранятся в codec
  • 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.
http_read(c), http_receive(c) Потоковый разбор клиентского запроса и upstream ответа через HTTP codec.
http_finish(c) Завершение/отмена upstream, ожидание доставки ответа клиенту и переход к следующему запросу.
http_fail(c), http_deadline(c) Ошибки и тайм-ауты: собственный HTTP error до final headers, закрытие оборванного сообщения после них.
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\nHost: host:port\r\n\r\n
uTun → exit (ETCP):    TCP_PROXY CONNECT[dest_ip+port]
exit → uTun (ETCP):    CONNECTED
uTun → Клиент:         HTTP/1.1 200 Connection Established\r\n\r\n
Клиент ↔ 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...]
exit → uTun:           HTTP headers/body через ETCP DATA, затем FIN
uTun → Клиент:         проверенный и реконструированный HTTP response
uTun → exit (ETCP):    CLOSE завершённого stream
Клиент → uTun:         следующий запрос → новый CONNECT с новым stream_id

URL преобразуется в origin-form без потери query; Host строится из absolute URL. Заголовки текущего соединения, включая поля из Connection, и Proxy-Authorization удаляются в обоих направлениях. Framing и Connection формируются заново, добавляется Via. Upstream обычного запроса получает Connection: close; frontend HTTP/1.1 сохраняет keep-alive для ответов с определённой длиной.

Codec поддерживает Content-Length, chunked с extensions/trailers, промежуточные ответы, HEAD/204/304, тела ответа до EOF и подтверждённый 101. Тело передаётся по мере поступления, без накопления целиком. HTTP/1.0 получает декодированное chunked тело и закрытие соединения. Неоднозначный framing (CL+TE, разные CL), obs-fold, неверные порты и управляющие байты отклоняются до отправки заголовков.

ETCP credit возвращается за исходные принятые байты после опустошения клиентской write queue и write_buf. Частичный header/chunk marker остаётся charged до проверки и доставки результата; удалённые trailers также учитываются. Переход к новому stream разрешён только после доставки полного ответа и отправки CLOSE/ERROR старого stream. Синхронная доставка через loopback защищена счётчиком busy и флагом sending.

Лимиты: headers 32 KiB, start line и chunk/trailer line 8 KiB, не более 512 полей. Назначения — IPv4 или DNS-имя с IPv4-результатом; IPv6 отклоняется с 502 из-за формата общего proxy CONNECT. Поддерживаемый transfer coding — chunked.

Тайм-ауты: request/response headers 30 s, DNS/connect 15 s, отсутствие прогресса передачи и idle keep-alive 60 s. Заголовки имеют общий deadline, который не продлевается каждым байтом. Зависшее закрытие принудительно освобождается через 60 s. CONNECT/Upgrade после установки туннеля используют обычный lifecycle TCP. Ошибки и смена транзакций логируются в категории proxy; credentials не выводятся.

ERROR на exit немедленно уничтожает сокет и отложенный upload; CLOSE сохраняет обычное завершение очереди.

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()
  • 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