Есть технологии, которые вспыхивают ярко, несколько лет находятся на пике популярности, а затем постепенно исчезают, уступая место чему-то новому. А есть 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 новые уровни защиты, но сохраняем прежний способ взаимодействия клиента с сервером. Со временем такой подход стал восприниматься как естественный.
Именно здесь возник простой вопрос: почему 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-обмену. Стандартный SOCKS5-сервер ожидает протокольное приветствие сразу после установки TCP-соединения, поэтому новый режим нельзя было использовать без соответствующей поддержки на серверной стороне.
При этом хотелось сохранить привычные возможности SOCKS5: команды CONNECT и UDP ASSOCIATE, аутентификацию по имени пользователя и паролю, правила доступа, IPv4 и IPv6. TLS должен был стать частью самого listener, а не отдельной службой перед сервером.
Первой мыслью, конечно, был Dante. Это зрелый и хорошо известный SOCKS-сервер с гибкими правилами, поддержкой TCP, UDP и нескольких методов аутентификации. Я много лет использовал его в разных проектах, поэтому формат конфигурации и общая модель управления доступом были хорошо знакомы. Однако встроенного TLS listener в Dante нет, а его документация прямо предупреждает, что при обычной аутентификации пароль и последующий трафик передаются без шифрования.
Dante прекрасно решает классическую задачу SOCKS-сервера. Для нового сценария требовалась немного другая архитектура, в которой защищённый транспорт изначально считается частью сервера.
Так появился Alighieri
Изначально всё начиналось с запроса добавить в WireSock Secure Connect лёгкий SOCKS5-сервер, который всегда был бы привязан к VPN-туннелю. Но вовремя остановиться не получилось, и постепенно из этой задачи вырос самостоятельный проект. Так появился Alighieri, кроссплатформенный SOCKS5-сервер для Windows и Linux. Название отсылает к Данте, а модель конфигурации во многом опирается на опыт работы с Dante. Сам сервер написан с нуля на Rust и использует Tokio для асинхронной обработки соединений.
Alighieri сосредоточен на SOCKS5 и поддерживает две основные команды: CONNECT для TCP и UDP ASSOCIATE для UDP. Правила доступа используют знакомую модель client и socks, применяются последовательно и по умолчанию запрещают всё, что не было разрешено явно. При проверке можно учитывать адрес клиента, назначение, порт, протокол, команду и способ аутентификации.
Аутентификация по имени пользователя и паролю сохранена ради совместимости с обычными SOCKS5-клиентами, а сами пароли хранятся на сервере в виде Argon2id-хешей. TLS listener защищает авторизацию, команды протокола и последующий TCP-трафик. Для нового режима ProxiFyre этого достаточно: сначала устанавливается защищённое соединение, а затем внутри него начинается стандартный SOCKS5-сеанс.
Для публичного сервера можно указать готовые сертификат и ключ либо поручить их получение самому Alighieri. Встроенный ACME-клиент использует TLS-ALPN-01, получает сертификат Let’s Encrypt через порт 443 и автоматически обновляет его. Отдельный веб-сервер на порту 80 и доступ к API DNS-провайдера для этого не требуются.
Постепенно появились и возможности, необходимые для повседневной эксплуатации: Windows Service, установка через systemd, текстовые и JSON-журналы, метрики Prometheus, ограничения скорости и числа подключений, DNS-политики и горячая перезагрузка конфигурации. Alighieri при этом остаётся самостоятельным сервером и может работать как с ProxiFyre, так и с другими совместимыми клиентами.
Dante и Alighieri: разные задачи
Сравнение с Dante неизбежно, хотя проекты рассчитаны на разные сценарии. Dante развивается много лет, хорошо изучен и поддерживает широкий набор возможностей. Помимо SOCKS5, в нём есть SOCKS4, команда BIND, GSSAPI, PAM, различные способы идентификации пользователей и клиентская библиотека для перенаправления приложений. Для классической Unix-инфраструктуры это зрелый и проверенный инструмент.
Alighieri создавался с более узким набором приоритетов: SOCKS5, Windows и Linux, встроенный TLS и современная эксплуатация. В нём нет SOCKS4, BIND, PAM, GSSAPI и аналога libsocks. Добавление этих возможностей заметно усложнило бы проект и увело его от исходной задачи.
Заметная разница связана и с платформами. Dante ориентирован прежде всего на Unix-подобные системы. Alighieri использует одну кодовую базу для Linux и Windows и может работать как systemd-служба, Windows Service или обычное консольное приложение. Для смешанной инфраструктуры это позволяет применять одинаковую конфигурацию и одинаковые правила на обеих платформах.
В Alighieri также сразу появились JSON-журналы, Prometheus-метрики, горячая перезагрузка, ограничения скорости и числа соединений, управление диапазоном UDP-портов и автоматическое получение сертификатов. Это не делает его универсальнее Dante, но упрощает новый сценарий, в котором требуется открыть SOCKS5-сервер в интернете с минимальным количеством внешних компонентов.
Поэтому Alighieri не стоит рассматривать как безусловную замену Dante. Если инфраструктура использует Kerberos, PAM, SOCKS4, BIND или клиентскую библиотеку Dante, зрелый сервер остаётся естественным выбором. Если нужны SOCKS5, Windows или Linux, CONNECT, UDP ASSOCIATE и встроенный TLS, Alighieri предлагает более специализированный вариант.
Как это выглядит на практике
Для публичного сервера достаточно одного 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. Для пользователя и приложения эта последовательность остаётся прозрачной.
Что ещё изменилось за это время
TLS стал главной темой статьи, но со времени предыдущей публикации ProxiFyre изменился и в других областях. В конфигурации появился раздел excludes, позволяющий оставлять отдельные процессы на прямом соединении даже при использовании глобального правила appNames: [""]. Одновременно было добавлено кеширование результатов сопоставления процессов с правилами, а конфигурации с несколькими прокси получили более предсказуемую обработку.
Следующим практическим дополнением стал параметр bypassLan. При глобальном проксировании через удалённый сервер легко случайно отправить туда обращения к маршрутизатору, NAS, сетевому принтеру или другим локальным ресурсам. Теперь такой трафик можно оставить в локальной сети одним параметром. Позже эта логика была распространена и на локальные диапазоны IPv6.
Отдельной большой задачей стала поддержка IPv6-назначений. Каждое правило может явно задавать разрешённые семейства адресов через supportedAddressFamilies. Если прокси поддерживает только IPv4, попытка использовать через него IPv6 блокируется, а не выпускается напрямую. Это исключает ситуацию, когда IPv4 идёт через прокси, а IPv6 незаметно обходит его.
Значительно изменилась и обработка UDP. Ассоциации теперь создаются параллельно, первые датаграммы ожидают завершения согласования в очереди, а закрытая сервером по тайм-ауту ассоциация автоматически создаётся заново. Улучшена работа с динамическими адресами UDP-релея и временными ошибками сокетов. Эти изменения особенно заметны при работе QUIC и HTTP/3.
Большая часть остальных изменений относится к стабильности и почти незаметна снаружи. Перерабатывалось управление временем жизни асинхронных операций, исправлялись гонки при завершении работы, усиливалась проверка границ SOCKS5-пакетов, улучшались тайм-ауты, очистка ресурсов и диагностика ошибок конфигурации.
Вместо заключения
Всё началось с желания защитить пароль SOCKS5 без дополнительного TLS-прокси. В результате ProxiFyre получил новый транспорт, а Alighieri стал самостоятельным кроссплатформенным сервером, способным принять такое соединение.
Сам SOCKS5 при этом остался прежним: простым, понятным и предсказуемым. Наверное, в этом и заключается его главная сила. Он по-прежнему честно делает свою работу. Просто спустя почти тридцать лет ему наконец-то захотелось немного помочь.
