- cmd/vk-turn-client/manual_captcha.go (875 строк, был в package main) →
internal/client/captcha/manual/manual.go как отдельный пакет. Экспортирует
SolveViaProxy и SolveViaHTTP (переименованы из solveCaptchaViaProxy/
solveCaptchaViaHTTP). Остальное package-private.
- Глобал isDebug, который ручной flow читал из main, заменён на manual.Debug
(package var). cmd/vk-turn-client/main.go ставит manualcaptcha.Debug =
cfg.Debug рядом с существующим isDebug = cfg.Debug.
- Тестовый файл перенесён с кодом; пакет переключён с main на manual.
TestRewriteProxyRedirectLocation по-прежнему зовёт package-private rewriter
изнутри пакета.
- План предлагал дробить manual.go дальше (server.go/browser.go/proxy.go/
rewrite_html.go). Пропущено: лишний churn без выгоды, раз файл живёт в
своём пакете. Можно сделать позже.
- server/main.go → cmd/vk-turn-server/main.go. Несёт остаточные правки сервера
(drop параметра useBond, убрано поле startup-лога).
- client/internal/{browserprofile,captcha,dnsdial,namegen,vkauth} →
internal/client/* (единая иерархия internal/; параллельное дерево
client/internal/ убрано). Все Go import-пути обновлены.
- .github/workflows/{ci,release}.yml, Dockerfile, README.md — пути сборки
переключены на ./cmd/vk-turn-client и ./cmd/vk-turn-server.
- README: убран устаревший пример "server -vless-bond" (флаг удалён ранее);
добавлены рецепты сборки .so для Android arm64 + Linux arm64/amd64 под
ручную упаковку для мобилок.
- internal/proxy/udp/udp.go, internal/config/config.go: обновлены устаревшие
ссылки client/internal/* в doc-комментариях.
- notes/GLOSSARY.md (новый, ~20 терминов): фиксирует DTLS-как-обфускацию,
wrap=AEAD-не-XOR, четыре значения "client", три значения "stream", пять
ортогональных переключателей "mode", заметки по сборке libvkturn.so, iSH,
хрупкость init globalBondRegistry и удаления флагов.
cmd/vk-turn-client/ намеренно оставлен вне этого коммита; сворачивается вместе
с извлечением ручной captcha в следующем коммите, т.к. переименование и правки
импортов manualcaptcha.* попадают в один файл.
Wire-форматы не тронуты. Поведение не изменено кроме уже закоммиченных
удалений CLI.
Чистка CLI (breaking):
- Клиент: убран -no-dtls (был помечен DO NOT USE; VK content-filter дропает
payload без DTLS-handshake, так что прямой путь в проде не работал). Поле
config.Client.Direct, guard диспатчера в main и TestParseClient_WrapDirectConflict
удалены.
- Сервер: убран -vless-bond. Сервер уже автоопределяет bond по magic-префиксу
на первом smux-стриме (bond-autodetect=true), флаг был избыточен. Поле
config.Server.VLESSBond, параметр useBond у handleVLESSConnection и
ветка-предупреждение "non-bond stream while bond enabled" удалены.
- Клиентский -vless-bond остаётся — он по-прежнему управляет страйпингом на
стороне клиента.
Wire-форматы не тронуты. Оба удаления флагов — breaking CLI-изменения.
ParseClient/ParseServer возвращают валидированные структуры; main становится
связкой. Валидация без сайд-эффектов (без DNS/сети); main держит ResolveUDPAddr,
установки dnsdial.* и вывод gen-wrap-key. Сервер теперь переиспользует
wrap.GenKeyHex/wrap.DecodeKey вместо своего hex+rand пути.
client/main.go: 319 → 272. server/main.go: 424 → 402.
19 тестов покрывают дефолты, санитизацию vk-link, отсутствующие флаги,
валидацию -dns, конфликт wrap+no-dtls, декод wrap-key, положительность
streams-per-cred, clamp n, split dns-servers и short-circuit gen-wrap-key.
Флаги CLI, дефолты и поведение не изменены. Две строки ошибок
("Need peer address!" / "Need vk-link!") переведены в нижний регистр под
revive; не часть wire-контракта.
Добавляет server_test.go с 6 юнит-тестами для registry bond-сервера: reorder
data-фреймов не по порядку в backend, drain pending-map при приходе пропущенного
seq, round-robin запись по lane, lane churn (сбойный lane выкинут, выживший
продолжает), all-lanes-fail возвращает исходную ошибку, dedup + cleanup registry.
Вводит небольшой интерфейс laneStream (io.Reader+Writer+SetDeadline) на
serverLane, чтобы тесты инжектили in-memory фейки без запуска реальной
smux-сессии. *smux.Stream его удовлетворяет, так что HandleStreamAfterMagic и
on-wire поведение не изменены.
Переносит runVLESSMode/maintainVLESSSession/createSmuxSession/pipe и sessionPool
в internal/proxy/vless. Клиентская половина bond (handleBondedTCP, copyTCPToBond,
copyBondToTCP, bondClientLane) остаётся в client/main.go до этапа 5.1 —
инжектится в vless через Deps.BondHandler.
sessionPool/pooledSession экспортированы как SessionPool/PooledSession, чтобы
половина bond работала со счётчиками lane (ID/Sess/Opened/Active/Closed/
ToSession/FromSession).
Также дедуплицирует iSH (linux/386) listener-shim в новый пакет internal/ish;
client/manual_captcha.go и internal/proxy/vless импортируют его через
ish.WrapListener.
client/main.go: 895 → 552 (-343). Wire-формат и CLI не изменены.
Переносит oneDtlsConnection/oneTurnConnection и их retry-циклы из client/main.go
в internal/proxy/udp, за интерфейс AuthHandler, чтобы пакет оставался вне
client/internal/. UDPPacket/packetPool становятся udp.Packet/udp.Pool. Глобал
globalAppCancel убран в пользу Deps.AppCancel. turnParams пока остаётся в
client/main.go, т.к. vless ещё его использует; переедет с этапом 4.2.
client/main.go: 1195 → 895. Wire-формат и CLI не изменены.
Переносит общий пайплайн TURN dial+allocate в internal/turnpipe и DTLS-настройку
клиента в internal/dtlsdial. И oneTurnConnection, и createSmuxSession теперь
используют одни строительные блоки, убирая ~140 строк дублированной настройки.
Глобал handshakeSem убран, заменён двумя сконфигурированными dtlsdial.Dialer:
udpDtlsDialer (timeout 20s, sem cap=3) и vlessDtlsDialer (timeout 30s, без
gate). Поведение идентично прежним путям.
client/main.go: 1380 → 1195. Импорты netadapt, pion/dtls, selfsign,
pion/logging и pion/turn вынесены из client/main.go в новые пакеты.
Этап 2 плана рефакторинга. Выносит слой VK credentials/captcha/throttle/
lockout из client/main.go в отдельный пакет с инжектируемым хуком token-chain
для тестов.
Интерфейс:
- vkauth.Client{GetCredentials, HandleAuthError, ResetErrors, LockoutUntilUnix}
- vkauth.IsAuthError (package-level)
- vkauth.DefaultAutoSolve / AutoSolveFunc / ManualSolveFunc
Wire-формат и флаги CLI не изменены. client/main.go: 1967 → 1380.
- internal/stats: throughputStats/countingConn/форматтеры, общие для клиента
и сервера
- internal/netadapt: DirectNet, ConnectedUDPConn, SplitFirstWriteConn
- internal/bond: wire-кодек (Frame/Hello, Read/Write, CloseWrite)
Убирает дублирование между client/main.go и server/main.go. Wire-формат и
поведение CLI не изменены; round-trip кодека покрыт тестами.
writeBondFrame пишет синхронно через w.Write, поэтому слайс буфера чтения
безопасно передавать напрямую. Убирает одну аллокацию+копию на TCP-чанк на
горячем пути upload.
- wrap_test.go: проверять ошибки newWrapConn/wrapInto во всех тестах
- server/wrap.go: nolint на type assertion bufPool (pool New всегда
возвращает *[]byte)
- captcha/slider.go: обосновать пустой импорт image/jpeg
- namegen_test.go: применить закон Де Моргана к условию цикла
Фейковый DTLS wire-формат (0202b9b) был fingerprintable по содержимому:
фиксированный content_type=0x17, нет handshake-записей, размеры пакетов
кластеризовались в 6 фиксированных корзин padding. Throughput на shaped
TURN-путях совпадал с wrap-off.
Новый wire-формат неотличим от случайного после первого байта:
[1B header | (prefix_len-1)B random | 12B nonce | AEAD ct | 16B tag]
где prefix_len = 1 + (header & 0x07). Байт 0 распределён равномерно по
0..255, длина префикса закодирована в младших 3 битах.
Nonce = 4B sessionID || 8B counter (BE). MSB sessionID кодирует направление
(0 клиент, 1 сервер), чтобы клиент и сервер не коллизировали под одним
ключом. counter стартует со случайного uint64 против reuse nonce между
перезапусками процесса. AAD = nonce; случайный префикс — неаутентифицированный
шум.
Padding по фиксированным корзинам убран полностью. Усиление в 2-4x мелких
ACK/keepalive-пакетов было вероятной причиной регрессии throughput.
Overhead: 29..36 B на пакет (против 31..47 в DTLS-мимикрии, 12 в pre-AEAD
XOR). Целостность обеспечивает Poly1305.
- Перенос client/namegen.go в client/internal/namegen с экспортируемым
Generate()
- Вынос данных имён в data/*.txt, загрузка через //go:embed
- Расширение пулов: мужские 729→760, женские 31→99, фамилии 60→286
(вкл. мем-имена)
- Переписан feminizeSurname через []rune (убрана хрупкая байтовая
арифметика на len-4)
- Переход с math/rand на math/rand/v2
- Юнит-тесты: parseLines, feminizeSurname, целостность пулов, форма и
покрытие по полу у Generate
Удаляет флаг -yandex-link, getYandexCreds и ветку Telemost из основного
потока клиента. Убрана зависимость gorilla/websocket.
Вычищен мёртвый код, всплывший после удаления:
- browserprofile.ApplyHTTP / Random / profileList
- dnsdial DohResolver.LookupIPAddr / queryIPs / parseAnswer / dohCache
и их тесты (единственный production-вызыватель — путь forwarder)
- устаревшая строка -captcha-solver в README
Публичные DoH-эндпоинты Yandex оставлены (RU-дружественный fallback, не
связан с Telemost).
Вытаскивает auto-captcha пайплайн из client/main и даёт ему узкий публичный
интерфейс, чтобы main.go больше не держал ~1.2k строк обработки VK-челленджей.
Новые пакеты под client/internal/:
- browserprofile: типы Profile / Saved плюс Load / Save / Random и инжекторы
заголовков ApplyHTTP / ApplyFhttp (перенесены из profiles.go + main.go).
manual_captcha.go тоже переключён на него.
- captcha: package-level entrypoint Solve(ctx, *Error, ...), плюс
Error / ParseError / IsCaptcha (переименованы из VkCaptchaError /
ParseVkCaptchaError / IsCaptchaError). Сессия, scoring слайдера, синтез
курсора и PoW теперь неэкспортируемая деталь реализации.
solveVkCaptcha в main.go сжимается до обёртки над captcha.Solve с загрузкой
SavedProfile.
Удаляет slider_captcha.go / slider_captcha_test.go и fallback
captchaSolveModeSliderPOC. POC дублировал v2-солвер с захардкоженным
debug_info, scoring только по RGB и единственной попыткой — строго хуже
v2-пути, который уже делает ретраи внутри.
- Удалён глобал autoCaptchaSliderPOC, параметр useSliderPOC у solveVkCaptcha,
captchaSolveModeSliderPOC и его ветки в dispatch-цикле.
- Последовательность ретраев сведена к Auto → Manual.
- Общие image-хелперы (sliderTileRect, pixelDiff, absDiff) перенесены в
captcha_v2_slider.go.
- Удалены теперь неиспользуемые generateBrowserFp, fetchCaptchaBootstrap,
solvePoW.
- Удалён legacy v1 auto-captcha путь: callCaptchaNotRobot, глобал
captchaSolverVersion, флаг -captcha-solver. v2-солвер — единственный
auto-путь; slider POC и ручной fallback остаются.
- Удалены закомментированные блоки generateFakeCursor/generateCheckboxCursor
и generateSliderCursor/buildSliderCursor.
- Убраны неиспользуемые параметры candidateIndex/candidateCount у
requestSliderCheck.
- interface{} заменён на any во всех .go файлах.
- Паттерн sync.WaitGroup Add+Done+go func заменён на wg.Go (Go 1.25).
- Несколько циклов for i := 0; i < N; i++ переведены на for range N.
- rewriteProxyRequest теперь вырезает X-Requested-With, X-Android-Package,
X-Android-Cert, X-Client-Data, X-Discord-Locale, X-Discord-Timezone,
Save-Data, Purpose, Sec-Purpose перед форвардом captcha-запросов
- maleFirstNames расширен с 39 до 735 записей из исторического корпуса имён
Заменяет bschaatsbergen/dnsdialer (только UDP) на внутренний пакет dnsdial.
Добавляет поддержку DNS-over-HTTPS и режим auto (проба UDP/53 → залипающий
fallback на DoH при сбое).
Новый флаг: -dns udp|doh|auto (по умолчанию: auto)
- Атомарный счётчик nonce вместо per-packet rand.Read (syscall getrandom);
wire-формат не изменён, сервер декодирует без правок
- wrapInto: TX-путь без аллокаций с заранее выделенным буфером;
wrapPacket оставлен для тестов и control-путей
- Кеш указателя при приёме WG исключает аллокацию addr.String() на пакет
(быстрый путь — сравнение указателей, медленный — строк)
- Атомики статистики throughput спрятаны за isDebug — снижает трафик
cache-line по N стримам в production-сборках
- splitFirstWriteConn разбивает первую TCP-запись на байте 6 + задержка
20ms, ломая STUN magic cookie между сегментами против поверхностного DPI
- VK_TURN_KCP_FEC=data:parity включает Reed-Solomon FEC поверх KCP;
обе стороны должны совпадать; по умолчанию 0:0 (выключено)
- Исправлен WaitGroup: wg.Add(1) → wg.Add(2) для двух горутин в
oneDtlsConnection; раньше wg.Wait() возвращался слишком рано