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— уникальный идентификатор потока в ETCPdest_ip[4],dest_port— целевой адрес (куда хочет подключиться клиент)state— текущее состояние (SOCKS_STATE_* или HTTP_STATE_*)http— отдельное состояние HTTP-транзакции и таймер; NULL для SOCKS5next_stream_id— заимствованный генератор идентификаторов последующих HTTP-транзакцийrem_closed,fin_remote,close_sent,close_pending— флаги состояния закрытияbuf[16384],buf_len— непрочитанный хвост входного блока; HTTP headers хранятся в codectx_buf,tx_len,tx_waiter— бэкпрессур: отложенные данные и waiter handlebytes_to_client,bytes_from_exit— счётчики переданных байт (диагностика)head,count— указатели на голову списка и счётчик (для самоудаления)
struct listen_ctx (непрозрачный) — контекст слушающего сокета:
listen_sock,socket_id— сокет и uasync handleconns,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_PROXYetcp_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_tll_queue.h— очереди (write_queue, read_queue), waiter механизмmemory_pool.h— аллокация из пулов (entry_pool,data_pool)utun_instance.h—struct UTUN_INSTANCE