Есть технологии, которые вспыхивают ярко, несколько лет находятся на пике популярности, а затем постепенно исчезают, уступая место чему-то новому. А есть SOCKS5.

Он появился в 1996 году, когда массового HTTPS ещё практически не существовало, контейнеры оставались делом будущего, а облачные сервисы скорее напоминали научную фантастику. За это время вокруг него успело измениться почти всё. Появились NAT и IPv6, повсеместный TLS, HTTP/2, HTTP/3, QUIC, контейнеры, облачные платформы и Zero Trust. Сам SOCKS5 при этом почти не изменился и, что удивительно, продолжает прекрасно выполнять свою работу.

Его до сих пор используют OpenSSH для динамического проброса портов, корпоративные прокси, системы автоматизации, инструменты тестирования, VPN-клиенты и просто пользователи, которым нужно направить трафик через удалённый сервер.

Наверное, именно поэтому у меня всегда было к нему особое отношение. В SOCKS5 нет ничего лишнего. Это простой посредник, который помогает приложению установить соединение с нужным адресом через удалённый сервер. Он не пытается заменить VPN или стать универсальным сетевым протоколом. SOCKS5 честно выполняет одну задачу и делает это уже почти тридцать лет.

Год назад я рассказывал на Хабре о ProxiFyre, небольшом инструменте для Windows, который позволяет прозрачно направлять трафик выбранных приложений через SOCKS5-прокси. Многие программы вообще не умеют работать через SOCKS5 или требуют ручной настройки. ProxiFyre берёт эту работу на себя: приложение продолжает работать, как обычно, а решение о том, какие соединения следует отправить через прокси, принимается на уровне операционной системы.

За прошедший год проект заметно вырос. Появились поддержка IPv6, исключения для процессов, обход локальной сети, значительно переработанная работа с UDP и множество внутренних изменений, большинство из которых пользователи, скорее всего, никогда даже не заметят.

По мере развития ProxiFyre меня всё больше занимала другая сторона SOCKS5. Сам протокол уже почти тридцать лет остаётся одним из самых распространённых способов проксирования трафика. Его поддерживает множество клиентов и серверов, он пережил несколько поколений сетевых технологий и до сих пор остаётся стандартом де-факто.

Почему же пароль к SOCKS5-серверу до сих пор путешествует по сети открытым текстом?

Довольно быстро выяснилось, что это лишь часть истории. Гораздо интереснее оказался другой вопрос: почему за все эти годы мы так и не попытались решить эту проблему внутри самого SOCKS5, предпочитая снова и снова защищать его снаружи с помощью SSH, VPN, stunnel и других внешних туннелей?

Чтобы ответить на этот вопрос, придётся ненадолго вернуться почти на тридцать лет назад, ко времени появления самого SOCKS5.

Вернёмся в 1996 год

Чтобы понять, почему пароль в SOCKS5 до сих пор передаётся открытым текстом, нужно помнить одну важную вещь. В середине девяностых интернет выглядел совсем иначе.

Большинство SOCKS-серверов работало внутри корпоративных сетей, университетов и других организаций, где сама сеть считалась доверенной. Защитить требовалось прежде всего внутренние ресурсы, к которым сервер открывал доступ, а не соединение между клиентом и SOCKS5-прокси.

HTTPS тогда только начинал появляться. TLS ещё назывался SSL 3.0, браузеры лишь учились работать с защищёнными соединениями, а VPN ассоциировался скорее с дорогими корпоративными решениями, чем с небольшим сервером в интернете.

Поэтому авторы SOCKS5 решали совсем другие задачи. Им был нужен простой универсальный протокол, способный передавать TCP- и UDP-соединения, поддерживать разные способы аутентификации и не навязывать собственный механизм защиты трафика. Предполагалось, что при необходимости защищённый канал будет создан на другом уровне.

Именно поэтому RFC 1928 ничего не говорит о шифровании. Для аутентификации предусмотрен отдельный механизм согласования методов, а сами методы вынесены в независимые спецификации. Самой известной из них стал RFC 1929, описывающий аутентификацию по имени пользователя и паролю.

На первый взгляд всё выглядит вполне разумно. Клиент устанавливает TCP-соединение с SOCKS5-сервером, стороны выбирают способ аутентификации, после чего клиент отправляет имя пользователя и пароль. Но RFC 1929 прямо предупреждает об одном важном ограничении: пароль передаётся в открытом виде, поэтому такой метод не защищает от пассивного прослушивания сети.

Авторы спецификации прекрасно понимали последствия этого решения. Получилась довольно любопытная ситуация. С одной стороны, появился простой способ аутентификации, который со временем стал поддерживаться почти каждым SOCKS5-клиентом и сервером. С другой стороны, сам стандарт предупреждал, что использовать его через недоверенную сеть небезопасно.

Можно было ожидать, что за следующие десятилетия эта проблема исчезнет и появятся более современные способы безопасной аутентификации. Они действительно появились, но массовыми так и не стали.

Почему GSSAPI не стал ответом

Разработчики SOCKS5 понимали, что передача пароля открытым текстом не может быть универсальным решением. Поэтому протокол с самого начала позволял согласовывать разные способы аутентификации, а username/password был только одним из доступных вариантов.

Наиболее серьёзной защищённой альтернативой стал GSSAPI, описанный в RFC 1961 и обычно используемый вместе с Kerberos. Вместо передачи пароля серверу клиент подтверждает свою личность с помощью уже существующей инфраструктуры безопасности. Такой механизм позволяет выполнить взаимную аутентификацию клиента и сервера, а при необходимости также обеспечить целостность или конфиденциальность последующего обмена.

Для корпоративной сети конца девяностых такой подход выглядел вполне естественно. Если организация уже использовала Kerberos, SOCKS5 становился ещё одним сервисом в общей системе аутентификации. Пользователям не требовались отдельные учётные записи для прокси, а администраторам не приходилось хранить ещё один набор паролей.

Со временем типичный сценарий использования SOCKS5 заметно изменился. Сегодня сервером часто служит небольшой VPS, домашняя машина или облачная виртуальная машина, где Kerberos-инфраструктуры обычно нет. Разворачивать KDC, создавать service principal и keytab, настраивать DNS и клиентские системы только ради одного прокси-сервера выглядит чрезмерно сложным. Для корпоративного домена всё это может быть привычной частью инфраструктуры, но для персонального сервера или небольшого проекта требуется слишком много дополнительных компонентов.

Не менее важной оказалась поддержка на стороне клиентов. В большинстве программ SOCKS5-аутентификация по-прежнему сводится к двум привычным полям: имени пользователя и паролю. Иногда доступно подключение без аутентификации, тогда как GSSAPI встречается значительно реже и обычно требует дополнительной настройки операционной системы или самого приложения.

В результате SOCKS5 уже давно располагает механизмом защищённой аутентификации, но большинство пользователей продолжает применять метод из RFC 1929. Именно тот метод, при котором пароль передаётся открытым текстом. Причина заключается не в отсутствии более безопасной технологии, а в том, что доступная альтернатива была рассчитана прежде всего на корпоративную инфраструктуру и плохо соответствовала более простым современным сценариям.

Для обычного SOCKS5-сервера требовался подход, который сохранял бы совместимость привычной аутентификации по имени пользователя и паролю, но защищал её без развёртывания отдельной Kerberos-инфраструктуры.

Мы всё время защищали SOCKS5 снаружи

Если посмотреть на проблему шире, становится понятно, что дело давно не ограничивается одной лишь аутентификацией. Пароль передаётся открытым текстом, но после успешного подключения ситуация принципиально не меняется. Команда CONNECT, адрес назначения и весь последующий TCP-трафик между клиентом и SOCKS5-сервером тоже идут без дополнительной защиты.

Когда приложение использует собственный TLS, например при работе по HTTPS, содержимое его соединения уже зашифровано. Для SOCKS5 это по-прежнему обычный поток байтов, который нужно передать дальше. Защита приложения не распространяется на сам SOCKS5-сеанс, поэтому авторизация, команды протокола и незашифрованные данные остаются доступными для наблюдения на участке между клиентом и прокси-сервером.

За прошедшие годы появилось множество способов защитить этот участок. Можно построить SSH-туннель, поднять VPN, использовать stunnel или другой TLS-прокси. Все эти решения давно известны, хорошо изучены и прекрасно работают. Их объединяет один подход: SOCKS5 остаётся без изменений и помещается внутрь другого, уже защищённого транспорта.

Схема получается примерно такой:

  Новые уровни защиты появлялись вокруг SOCKS5, а сам протокол почти не менялся.
Новые уровни защиты появлялись вокруг SOCKS5, а сам протокол почти не менялся.

Сам SOCKS5 остаётся тем же протоколом, каким был почти тридцать лет назад. Меняется только транспорт между клиентом и сервером. Мы добавляем вокруг SOCKS5 новые уровни защиты, но сохраняем прежний способ взаимодействия клиента с сервером. Со временем такой подход стал восприниматься как естественный.

Именно здесь возник простой вопрос: почему TLS всегда существует рядом с SOCKS5, а не внутри него?

Первой мыслью был stunnel

Самый очевидный вариант существует уже много лет. Достаточно запустить stunnel на клиенте и сервере, а обычное SOCKS5-соединение пропустить через созданный TLS-канал. На клиентской стороне ProxiFyre подключается к локальному порту stunnel. Зашифрованное соединение уходит на удалённый сервер, где второй экземпляр stunnel передаёт его обычному SOCKS5-сервису. Сам SOCKS5 при этом остаётся без изменений.

Такое решение хорошо известно, предсказуемо и действительно работает. Более того, для уже развёрнутого SOCKS5-сервера это часто самый быстрый способ добавить TLS. Достаточно выпустить сертификат, настроить пересылку в обе стороны и запустить дополнительную службу на клиенте и сервере.

Однако для ProxiFyre эта схема выглядела избыточной. Вместо прямого подключения к прокси появлялся локальный посредник, отдельная конфигурация и ещё один процесс, за состоянием которого нужно следить. При использовании нескольких SOCKS5-серверов для каждого из них требуется собственный локальный listener. В конфигурации ProxiFyre при этом указываются уже не реальные адреса прокси, а набор локальных портов, связанных с отдельными TLS-туннелями.

Часть информации также оказывается разделена между двумя приложениями. ProxiFyre знает, какое правило выбрано и к какому SOCKS5-серверу требуется подключиться, но видит только локальный адрес 127.0.0.1. Проверка сертификата, имя удалённого узла, параметры TLS и диагностика защищённого соединения переходят в конфигурацию stunnel. В результате одна логическая операция управляется сразу в двух местах.

Для универсального TLS-прокси такая архитектура вполне естественна. Но в данном случае внешний компонент выполняет только одну задачу: защищает TCP-соединение, которое ProxiFyre и так устанавливает самостоятельно. Дополнительная служба увеличивает сложность конфигурации и сопровождения, почти ничего не добавляя к самой модели работы.

Постепенно ответ стал очевиден. Если ProxiFyre уже управляет соединением с SOCKS5-сервером и располагает всеми необходимыми параметрами, он может самостоятельно выполнить TLS handshake, проверить сертификат и только после этого начать обычный SOCKS5-обмен.

SOCKS5 внутри TLS

С технической точки зрения идея оказалась довольно простой. После установки TCP-соединения с прокси ProxiFyre запускает TLS handshake через системный стек Windows SChannel. Когда сервер подтвердил свою личность и защищённый канал готов, внутри него начинается обычный SOCKS5-обмен. Приветствие клиента, выбор метода аутентификации, имя пользователя, пароль и команда CONNECT передаются уже как зашифрованные прикладные данные. Сам протокол SOCKS5 при этом сохраняется в исходном виде и не требует новых команд или расширений.

Последовательность соединения теперь выглядит так:

TCP-соединение с прокси
          ↓
      TLS handshake
          ↓
   Проверка сертификата
          ↓
    SOCKS5 greeting
          ↓
     Аутентификация
          ↓
        CONNECT
          ↓
  Передача TCP-трафика

Для реализации был выбран SChannel, поскольку ProxiFyre работает только на Windows и уже тесно связан с возможностями операционной системы. Такой подход позволяет обойтись без отдельной поставки OpenSSL или другой криптографической библиотеки. TLS, шифрование и аутентификация сервера выполняются системным компонентом Windows, а ProxiFyre остаётся сосредоточен на маршрутизации соединений. Текущая реализация использует TLS 1.2, которого достаточно для совместимости с поддерживающими такой транспорт SOCKS5-серверами.

В конфигурации для включения защищённого транспорта достаточно добавить несколько параметров:

{
  "appNames": ["chrome"],
  "socks5ProxyEndpoint": "proxy.example.com:443",
  "username": "alice",
  "password": "your-password",
  "socks5Transport": "TLS",
  "tlsServerName": "proxy.example.com",
  "supportedProtocols": ["TCP", "UDP"],
  "supportedAddressFamilies": ["IPv4", "IPv6"]
}

Параметр socks5Transport выбирает TLS вместо обычного TCP, а tlsServerName задаёт имя, которое используется при проверке сертификата. Если оно совпадает с именем в socks5ProxyEndpoint, ProxiFyre может определить его автоматически. Для сертификатов, выпущенных общедоступным удостоверяющим центром, выполняется стандартная проверка цепочки доверия и имени сервера. Для частного или самоподписанного сертификата можно указать его SHA-256-отпечаток через tlsPinnedSha256. Возможность полностью отключить проверку тоже предусмотрена, но она предназначена главным образом для временной диагностики.

Старые конфигурации продолжают работать без изменений, поскольку обычный SOCKS5 поверх TCP остаётся режимом по умолчанию. При включении TLS защищаются пароль, команды протокола, адрес назначения и весь TCP-трафик между ProxiFyre и прокси-сервером. Для приложения ничего не меняется: оно по-прежнему устанавливает обычное соединение, а ProxiFyre прозрачно помещает его внутрь SOCKS5 и TLS.

С TCP на этом задачу можно было считать решённой. Но у SOCKS5 есть ещё одна важная возможность, UDP ASSOCIATE, и с ней всё оказалось немного сложнее.

С UDP всё немного сложнее

В SOCKS5 работа с UDP устроена иначе, чем передача TCP-соединений. Клиент сначала открывает обычное TCP-соединение с прокси и отправляет команду UDP ASSOCIATE. Сервер создаёт UDP-релей и сообщает его адрес и порт. После этого управляющее TCP-соединение остаётся открытым, а сами датаграммы передаются отдельным UDP-потоком. Когда TCP-соединение закрывается, связанная с ним UDP-ассоциация также прекращает существование.

При использовании нового TLS-транспорта ProxiFyre защищает именно управляющий канал. Внутри TLS передаются SOCKS5 greeting, имя пользователя, пароль, команда UDP ASSOCIATE и ответ сервера с параметрами релея. Но сами UDP-датаграммы продолжают идти отдельно, в стандартном формате SOCKS5. Иными словами, TLS защищает создание и управление UDP-ассоциацией, но не превращает UDP-трафик в часть TCP-потока.

На первый взгляд может показаться логичным передавать датаграммы через уже установленный TLS-канал. Однако в таком случае это был бы уже другой протокол. UDP оказался бы инкапсулирован в TCP со всеми характерными для него повторными передачами, упорядочиванием и задержкой следующих данных при потере одного сегмента. Альтернативой мог бы стать отдельный защищённый транспорт на базе DTLS или QUIC, но он потребовал бы новых расширений и собственной поддержки на обеих сторонах. Совместимость с обычными SOCKS5-серверами при этом была бы потеряна.

Поэтому в ProxiFyre была выбрана более консервативная модель. TCP-трафик полностью проходит внутри TLS, а для UDP ASSOCIATE защищается управляющее соединение и авторизация. Сами датаграммы сохраняют стандартный формат SOCKS5. Для QUIC и других протоколов со встроенным шифрованием полезная нагрузка уже защищена самим приложением, хотя SOCKS5-заголовок и характеристики UDP-потока остаются видимыми на участке между клиентом и прокси. Если приложение отправляет открытые данные по UDP, новый TLS-транспорт сам по себе их не зашифрует.

Это ограничение важно понимать, но оно не меняет основной результат. Пароль, команды SOCKS5 и весь TCP-трафик теперь можно защитить без дополнительной службы и отдельной TLS-обёртки. Управляющий канал UDP также больше не передаётся открытым текстом. Клиентская часть была готова, но дальше возникла другая, неожиданно практическая проблема: требовался SOCKS5-сервер, способный принять такое соединение.

Клиент готов, а сервер?

После реализации TLS в ProxiFyre обнаружилось вполне ожидаемое ограничение. Обычный SOCKS5-сервер предполагает, что сразу после установки TCP-соединения начинается согласование протокола. В новом режиме этому предшествует TLS handshake, поэтому сервер должен уметь принять защищённое соединение и только затем перейти к обычному SOCKS5-обмену.

Конечно, существующий SOCKS5-сервер можно поставить за stunnel или другим TLS-терминатором. Внешний процесс примет защищённое соединение, расшифрует его и передаст на локальный SOCKS5-порт. Технически задача будет решена, но мы вернёмся к той же архитектуре, от которой только что пытались уйти. TLS снова окажется в отдельной службе, а сертификаты, параметры соединения и диагностика будут распределены между несколькими компонентами.

Нужен был сервер, который воспринимает TLS как часть собственной архитектуры. Он должен принимать защищённое соединение, выполнять TLS handshake и затем запускать обычный SOCKS5-сеанс. При этом хотелось сохранить поддержку CONNECT и UDP ASSOCIATE, аутентификацию по имени пользователя и паролю, правила доступа и нормальную работу с IPv4 и IPv6.

Первой мыслью, конечно, был Dante. Это зрелый и хорошо известный SOCKS-сервер с гибкими правилами доступа, поддержкой TCP, UDP и нескольких методов аутентификации. Я много лет использовал его в разных проектах, поэтому формат конфигурации и общая модель управления доступом были хорошо знакомы. Однако встроенного TLS listener в Dante нет. При использовании имени пользователя и пароля его документация прямо предупреждает, что пароль и последующий трафик передаются без шифрования, а для защиты канала требуется внешний транспорт.

Dante прекрасно решает классическую задачу SOCKS5-сервера, но сохраняет тот самый подход, с которого началась эта история. Для TLS снова требуется отдельный слой. Поэтому пришлось подробнее подумать, каким должен быть сервер, если защищённый транспорт считать частью его основной архитектуры, а не внешним дополнением.

Так появился Alighieri

В какой-то момент стало понятно, что проще создать сервер, в котором TLS изначально является частью архитектуры, чем продолжать собирать нужную схему из нескольких независимых компонентов. Так появился Alighieri, новый SOCKS5-сервер для Windows и Linux. Название, конечно, отсылает к Данте, а модель конфигурации во многом опирается на накопленный опыт работы с Dante. При этом Alighieri написан с нуля на Rust и использует Tokio для асинхронной обработки соединений.

Задача заключалась не в том, чтобы воспроизвести все возможности Dante. Alighieri сосредоточен на современном SOCKS5 и поддерживает две наиболее востребованные команды: CONNECT для TCP и UDP ASSOCIATE для UDP. Правила доступа используют знакомую модель client и socks, применяются последовательно и по умолчанию запрещают всё, что не было разрешено явно. В правилах можно учитывать адрес клиента, назначение, порт, протокол, команду и способ аутентификации.

Обычная аутентификация по имени пользователя и паролю сохранилась ради совместимости с существующими клиентами, но сервер хранит пароли в виде Argon2id-хешей. Это, конечно, не защищает открытый SOCKS5-сеанс само по себе, поэтому TLS listener стал одной из базовых возможностей сервера. Alighieri сначала устанавливает защищённое соединение и только после этого принимает greeting, авторизацию и команды SOCKS5. Именно такая последовательность нужна новому режиму ProxiFyre.

Для публичного сервера можно указать готовые сертификат и ключ либо поручить их получение самому Alighieri. Встроенный ACME-клиент использует проверку TLS-ALPN-01, получает сертификат Let’s Encrypt через тот же порт 443 и затем автоматически обновляет его. Отдельный веб-сервер на порту 80 и доступ к API DNS-провайдера для этого не требуются. Сервер одновременно принимает обычные клиентские подключения SOCKS5 внутри TLS и отвечает на проверку центра сертификации.

Постепенно вокруг основной функции появились возможности, необходимые для нормальной эксплуатации сервера: системная служба для Windows, установка через systemd в Linux, текстовые и JSON-журналы, метрики Prometheus, ограничения скорости и количества подключений, DNS-политики и горячая перезагрузка конфигурации. При этом Alighieri остаётся самостоятельным SOCKS5-сервером. Его можно использовать вместе с ProxiFyre, с другим клиентом, поддерживающим SOCKS5 внутри TLS, или как обычный SOCKS5-прокси без защищённого listener.

Так из довольно небольшой задачи, защиты пароля между клиентом и прокси, постепенно вырос отдельный кроссплатформенный сервер. Но здесь неизбежно возникает вопрос: насколько Alighieri действительно отличается от Dante и в каких сценариях новый сервер имеет практический смысл?

Dante и Alighieri: разные задачи

Сравнение с Dante здесь неизбежно, хотя ставить между проектами знак равенства было бы неправильно. Dante развивается много лет, хорошо изучен и поддерживает гораздо более широкий набор сценариев. Помимо SOCKS5, в нём есть SOCKS4, команда BIND, GSSAPI, PAM, различные способы идентификации пользователей и клиентская библиотека для перенаправления приложений. Для классической Unix-инфраструктуры это зрелый и проверенный инструмент.

Alighieri создавался с другим приоритетом. Основное внимание уделялось SOCKS5, поддержке Windows и Linux, встроенному TLS и современным средствам эксплуатации. Поэтому в нём нет SOCKS4, BIND, PAM, GSSAPI и аналога libsocks. Эти возможности можно было бы постепенно добавлять, но они заметно усложнили бы проект и отвлекли от исходной задачи.

Главное различие проявляется в том, как оба сервера относятся к защищённому транспорту. В Dante TLS остаётся внешним слоем. Сервер принимает обычный SOCKS5-сеанс, а защиту соединения при необходимости обеспечивает отдельный туннель. В Alighieri TLS является частью listener. Сервер сам выполняет handshake, проверяет состояние защищённого канала и только после этого начинает обработку SOCKS5. Поэтому имя пользователя, пароль, команды протокола и TCP-трафик проходят внутри одного защищённого соединения.

Второе заметное различие связано с платформами. Dante ориентирован прежде всего на Unix-подобные системы. Alighieri использует одну кодовую базу для Linux и Windows, может работать как systemd-служба, Windows Service или обычное консольное приложение. Для смешанной инфраструктуры это позволяет использовать одинаковую конфигурационную модель и одинаковый набор правил на обеих платформах.

Есть и различие в эксплуатационном подходе. В Alighieri сразу появились JSON-журналы, метрики Prometheus, горячая перезагрузка конфигурации, ограничения скорости и количества соединений, управление диапазоном UDP-портов и автоматическое получение TLS-сертификатов. Каждая из этих функций встречается и в других серверных решениях, но обычно требует внешних компонентов или отдельной настройки. Здесь они собраны вокруг одной конкретной задачи: запустить SOCKS5-сервер и безопасно открыть его в интернете.

Поэтому Alighieri вряд ли стоит воспринимать как прямую замену Dante для любой существующей установки. Если инфраструктура уже использует Kerberos, PAM, SOCKS4, BIND или клиентскую библиотеку Dante, переход на новый сервер не даст очевидных преимуществ. Но для нового развёртывания, где нужны SOCKS5, Windows или Linux, CONNECT, UDP ASSOCIATE и встроенный TLS, Alighieri может оказаться более прямым решением.

Получается, что Dante и Alighieri продолжают одну и ту же идею, но смотрят на неё с разных сторон. Dante сохраняет широкий набор возможностей классического SOCKS-сервера. Alighieri сосредоточен на более узкой задаче и пытается встроить в неё те функции, которые сегодня обычно приходится собирать вокруг SOCKS5 отдельно.

Остаётся посмотреть, как такая связка выглядит на практике и что требуется для запуска ProxiFyre и Alighieri без дополнительных TLS-туннелей.

Как это выглядит на практике

Для публичного сервера достаточно одного listener на порту 443. Alighieri принимает на нём подключения SOCKS5 внутри TLS, а при использовании ACME этот же порт обслуживает проверку TLS-ALPN-01. Сервер самостоятельно получает сертификат Let’s Encrypt, сохраняет его в локальном кеше и обновляет по мере необходимости. Для выпуска сертификата домен должен указывать на сервер, а входящий TCP-порт 443 должен быть доступен из интернета.

Минимальная конфигурация может выглядеть так:

internal: 0.0.0.0:443
external: 0.0.0.0

socksmethod: username
userlist: /etc/alighieri/users

tls.acme.domains: proxy.example.com
tls.acme.email: admin@example.com
tls.acme.cache: /var/lib/alighieri/acme

logoutput: stdout
logformat: text

dns.deny: private linklocal loopback reserved

client pass "clients" { }

socks block "deny-loopback-v4" {
    to: 127.0.0.0/8
}

socks block "deny-loopback-v6" {
    to: ::1/128
}

socks pass "internet" {
    protocol: tcp udp
    command: connect udpassociate
}

Здесь socksmethod: username включает привычную аутентификацию по имени пользователя и паролю, а файл userlist хранит учётные записи с Argon2id-хешами. Правила client и socks применяются последовательно, поэтому конфигурация сначала блокирует доступ к loopback-адресам, а затем разрешает остальные TCP- и UDP-соединения. Дополнительная политика dns.deny также отсекает назначения, которые после разрешения имени попадают в частные, link-local, loopback или зарезервированные диапазоны.

Пользователь создаётся отдельной командой, после чего конфигурацию можно проверить без запуска сервера:

sudo alighieri user add alice --userlist /etc/alighieri/users
sudo alighieri --check /etc/alighieri/alighieri.conf

Команда user add запрашивает пароль интерактивно и записывает в файл подготовленный хеш. Режим --check запускает настоящий парсер конфигурации, но не открывает сетевые порты и не изменяет состояние системы. Для постоянной работы Alighieri можно установить как systemd-службу в Linux или как нативную Windows Service.

На стороне ProxiFyre соответствующее правило выглядит так:

{
  "logLevel": "Info",
  "bypassLan": true,

  "proxies": [
    {
      "appNames": [""],

      "socks5ProxyEndpoint": "proxy.example.com:443",
      "username": "alice",
      "password": "your-password",

      "socks5Transport": "TLS",
      "tlsServerName": "proxy.example.com",

      "supportedProtocols": [
        "TCP",
        "UDP"
      ],

      "supportedAddressFamilies": [
        "IPv4",
        "IPv6"
      ]
    }
  ],

  "excludes": [
    "wireguard.exe",
    "openvpn.exe"
  ]
}

Пустая строка в appNames означает правило для всех процессов, которые не совпали с более ранними настройками и не перечислены в excludes. Параметр socks5Transport включает TLS, а tlsServerName задаёт имя для SNI и проверки сертификата. Для публичного сертификата этого достаточно. Привязка tlsPinnedSha256 нужна главным образом для частных или самоподписанных сертификатов, когда вместо стандартной проверки цепочки требуется доверять конкретному сертификату.

После запуска приложение продолжает устанавливать обычные TCP и UDP-соединения. ProxiFyre определяет процесс, применяет нужное правило, подключается к Alighieri по TLS и уже внутри защищённого канала выполняет SOCKS5-аутентификацию и команду CONNECT либо UDP ASSOCIATE. Для пользователя и самого приложения эта дополнительная последовательность остаётся полностью прозрачной.

В результате вся клиент-серверная схема сводится к двум компонентам. ProxiFyre отвечает за прозрачное перенаправление приложений в Windows, а Alighieri принимает защищённый SOCKS5-сеанс и применяет серверные правила доступа. Внешний stunnel, отдельные локальные listener и дублирование TLS-настроек больше не требуются.

Два независимых проекта

ProxiFyre и Alighieri изначально решают разные задачи. ProxiFyre работает на клиентской стороне в Windows, определяет приложения и прозрачно направляет их соединения через SOCKS5. Alighieri работает на сервере, принимает подключения, проверяет пользователей, применяет правила доступа и передаёт трафик к конечному назначению.

Ни один из проектов не зависит от другого. ProxiFyre по-прежнему можно использовать с обычными SOCKS5-серверами, а Alighieri остаётся самостоятельным сервером для любых совместимых клиентов. Поддержка SOCKS5 поверх TLS просто добавила им общий защищённый режим работы.

Вместе они образуют удобную связку, но каждый проект сохраняет собственное назначение и остаётся полезным отдельно.

Что ещё изменилось за это время

TLS стал главной темой этой статьи, но со времени предыдущей публикации ProxiFyre изменился и в других областях. Возможность применять правило ко всем приложениям потребовала более точного управления исключениями. В конфигурации появился раздел excludes, который позволяет оставлять отдельные процессы на прямом соединении даже при использовании глобального правила appNames: [""]. Одновременно было добавлено кеширование результатов сопоставления процессов с правилами, а конфигурации с несколькими прокси получили более предсказуемую обработку.

Следующим практическим дополнением стал параметр bypassLan. При глобальном проксировании через удалённый сервер легко случайно отправить туда обращения к маршрутизатору, NAS, сетевому принтеру или другим локальным ресурсам. Теперь такой трафик можно оставить в локальной сети одним параметром, без составления отдельных правил для каждого адресного диапазона. Позже эта логика была распространена и на локальные диапазоны IPv6.

Отдельной большой задачей стала поддержка IPv6-назначений. Каждое правило теперь может явно указывать доступные семейства адресов через supportedAddressFamilies. Если конкретный прокси умеет передавать только IPv4, попытка использовать через него IPv6 блокируется, а не выпускается напрямую. Такой подход особенно важен для браузеров и других приложений с dual-stack, поскольку исключает ситуацию, когда IPv4 идёт через прокси, а IPv6 незаметно обходит его.

Значительно изменилась и обработка UDP. Установка UDP ASSOCIATE больше не должна задерживать приём первых датаграмм, а создание ассоциаций выполняется параллельными рабочими задачами. Были добавлены очереди для пакетов, поступивших до завершения согласования, корректная обработка динамических адресов UDP-релея и восстановление после временных ошибок сокета. В последнем релизе ProxiFyre также научился обнаруживать ассоциации, закрытые прокси-сервером по тайм-ауту, и автоматически создавать их заново. Это особенно заметно при работе QUIC и HTTP/3, где UDP давно перестал быть редким дополнением к TCP.

Большая часть остальных изменений относится к стабильности и почти незаметна снаружи. Перерабатывалось управление временем жизни асинхронных операций, исправлялись гонки при завершении работы, усиливалась проверка границ SOCKS5-пакетов, улучшались тайм-ауты, очистка ресурсов и диагностика ошибок конфигурации. Даже запуск стал быстрее за счёт более эффективного определения MAC-адреса шлюза. Именно эта незаметная работа подготовила транспортную часть к добавлению TLS без превращения новой функции в ещё один хрупкий слой поверх старого кода.

В итоге обновление оказалось значительно шире одной новой настройки. ProxiFyre получил более гибкие правила, нормальную работу с локальной сетью, IPv6 и более устойчивый UDP, а SOCKS5 поверх TLS стал логическим продолжением этой эволюции, а не изолированной функцией, добавленной ради очередного релиза.

Вместо заключения

Вся эта история началась с довольно простого наблюдения. Наиболее распространённый способ аутентификации в SOCKS5 по-прежнему отправляет имя пользователя и пароль открытым текстом. Более защищённые механизмы существуют, но обычно требуют корпоративной инфраструктуры и редко поддерживаются обычными клиентами. Внешние туннели решают задачу надёжно, однако добавляют отдельные службы, конфигурации и ещё один уровень сопровождения.

В ProxiFyre хотелось сохранить привычный SOCKS5 и защитить его без дополнительного посредника. Поэтому TLS стал частью самого подключения к прокси. Сначала устанавливается защищённый канал, проверяется сертификат сервера, а затем внутри него выполняются обычная аутентификация, команда CONNECT и передача TCP-трафика. Для приложений и самого протокола практически ничего не изменилось. SOCKS5 продолжает выполнять ту же работу, только теперь наиболее чувствительная часть обмена больше не путешествует по сети открытым текстом.

Alighieri решает другую задачу. Это самостоятельный SOCKS5-сервер для Windows и Linux, который можно использовать как кроссплатформенную альтернативу Dante в сценариях, где нужны CONNECT, UDP ASSOCIATE, правила доступа и встроенный TLS. ProxiFyre отвечает за прозрачное перенаправление приложений на клиентской Windows-машине, а Alighieri принимает соединения и применяет серверные политики. Поддержка SOCKS5 поверх TLS стала общей точкой совместимости между двумя независимыми проектами.

Забавно, что всё началось с желания защитить пароль SOCKS5, а закончилось новым транспортом, отдельным сервером и более внимательным взглядом на протокол, который почти не менялся три десятилетия. При этом сам SOCKS5 остался прежним. Простым, понятным и предсказуемым.

Наверное, в этом и заключается его главная сила. Он по-прежнему честно делает свою работу. Просто спустя почти тридцать лет ему наконец-то захотелось немного помочь.

Комментарии (2)


  1. ivanokunev
    23.07.2026 10:33

    Связка интересная, как и сам разбор, но возможно ограниченность и прямолинейность играют в данном случае против проекта - проксифиер отличная подмена proxychains, но в случае с alighieri, лично я, лучше бы положился на связку gost-релееров, которая позволяет вести практически любой транспорт, имея те же вводные, а смена дайлера/форвардера/обслуживание пользователей/днс/метрики обходятся дешево за счет единого комбайна - представляется в итоге инструментом, а не узким решением.


    1. SerpentFly Автор
      23.07.2026 10:33

      Благодарю! GOST действительно гораздо универсальнее. Alighieri начинался как ответ на запрос добавить в WireSock Secure Connect лёгкий SOCKS5-сервер, который всегда был бы привязан к туннелю. Но вовремя остановиться не получилось, и в итоге вырос самостоятельный кроссплатформенный сервер со встроенным TLS и очень неплохой производительностью.

      Прямого сравнения с GOST я пока не проводил, но даже на слабых VPS скорость получается хорошей. Здесь скорее выбор между универсальным комбайном и специализированным SOCKS5-сервером.