Commit Graph

80 Commits

Author SHA1 Message Date
samosvalishe 2efe415344 chore: captcha version bump 2026-06-11 19:27:36 +03:00
samosvalishe f24c5416de fix(dnsdial): динамический фоллбэк на DoH при отказе UDP/53 после пробы 2026-06-11 19:27:30 +03:00
samosvalishe 2534bbe96a fix(turndial): детект блэкхола по ChannelBind, не CreatePermission refresh
VK реджектит standalone CreatePermission refresh с 400 независимо от здоровья стрима, поэтому permWatch ложно рециклил здоровые аллокации каждые ~60s. Permission держится живым через ChannelBind refresh (RFC 8656 §11), стандартный для VK путь, потому отдельный CreatePermission refresh глушим (PermissionRefreshInterval 24h). Реальный блэкхол ловим по провалу ChannelBind refresh (data-path), сброс на успешном байнде.
2026-06-08 08:34:56 +03:00
samosvalishe 2845874f86 chore: captcha version bump 2026-06-08 00:09:17 +03:00
samosvalishe 40000fde5d feat(turn): фоллбэк по нескольким relay-адресам при allocate
VK отдаёт несколько relay-адресов, но стрим использовал ровно один
(streamID%n). Если этот IP душит DPI/RST — allocate падает без альтернативы.

Credentials несёт список адресов (primary-first по streamID), DialTURN
пробует кандидатов по очереди и берёт первый успешный allocate. Балансировка
relay-IP по стримам сохранена, остальные адреса — фоллбэк. HostOverride (-turn)
схлопывает список до одного адреса.
2026-06-08 00:09:17 +03:00
samosvalishe 106a48533c fix(udprelay): детект блэкхола permission по логу pion вместо трафик-эвристики
Трафик-watchdog (tx растёт, rx заморожен) ложно рециклил здоровые
sender-only стримы: обратный путь WireGuard привязан к endpoint, а не к
стриму, поэтому rx==0 на отправляющем стриме — норма, не блэкхол.

permWatchFactory считает проваленные циклы refresh permission в логе pion
(scope turnc) и закрывает Stream.PermDead после порога. RefreshInterval
урезан 120s→30s. oneTURN рециклит allocation по PermDead.
2026-06-08 00:09:17 +03:00
samosvalishe 1ff8dd9cba fix(captcha): обновить captchaScriptVersion до 1.1.1348 2026-06-04 12:33:31 +03:00
samosvalishe a337bb0a63 fix(udprelay): рециклить стрим при блэкхоле TURN permission
При провале CreatePermission refresh pion роняет permission без ошибки,
relay молча дропает пакеты, стрим висит и ест свою долю WG-трафика.
Watchdog: tx растёт, rx заморожен 20с, отменяем turnctx и делаем свежий
allocate через TURNLoop.
2026-06-04 12:31:43 +03:00
samosvalishe 56da9fecdc fix(vkauth): фрагментировать ClientHello на control plane
ТСПУ читает SNI из ClientHello и инжектит delayed RST в TLS до VK
(login.vk.ru и др.). Дробим первый Write через SplitFirstWriteConn,
SNI уезжает во второй сегмент. Вписано в tls-client через
WithProxyDialerFactory без proxyUrl: прямой транспорт, резолвер сохранён.
2026-06-04 12:31:38 +03:00
samosvalishe 5fc2a09fbb fix(captcha): обновить captchaScriptVersion до 1.1.1346 2026-06-01 18:14:54 +03:00
samosvalishe a0ea846f25 fix(turndial): не переопределять PermissionRefreshInterval (дефолт pion) 2026-06-01 18:14:54 +03:00
samosvalishe 43180f0362 fix(dtlsdial): уникальный self-signed cert на каждый handshake
Cert генерируется заново на каждый Dial вместо переиспользования через
sync.Once: каждая TURN-сессия и реконнект получают свой fingerprint,
чтобы N параллельных стримов с одного IP не коррелировались как ботный
трафик.
2026-06-01 18:14:41 +03:00
samosvalishe a97ad6ca33 fix(udprelay): барьер старта стримов для прогрева кэша creds
Стрим 1 поднимается первым и сигналит okchan; стримы 2..N ждут сигнал
(или ctx/safety-timeout 20s) и переиспользуют тёплый кэш credentials
вместо N одновременных запросов к VK — снимает thundering herd на
старте (частая причина rate-limit/captcha).
2026-06-01 18:14:00 +03:00
samosvalishe 89c390cef5 perf(udprelay): in-place wrap/unwrap obf без копий payload
WrapInPlace/UnwrapInPlace работают прямо в wire-буфере: ingress читает
payload сразу за RTP-заголовком, egress отдаёт subslice расшифровки.
Убирает 2 memcpy (~1400B) и 2 буфера на пакет в obf-режиме, плюс
atomic-запись адреса пайпа теперь один раз. WrapInto/Unwrap делегируют,
wire-формат не изменён.
2026-06-01 18:13:50 +03:00
samosvalishe 98e1d7cc8e fix(turndial): подавить периодический CreatePermission refresh
VK TURN реджектит standalone CreatePermission refresh с 400 Bad Request
каждые 120s, засоряя логи. Данные идут через ChannelData; channel-bind
refresh (RFC 8656 §9) держит permission живым, потому отдельный refresh
избыточен. Выставляем PermissionRefreshInterval в 24h.
2026-05-26 00:20:30 +03:00
samosvalishe f1b047e9e5 feat: Client ID шлётся всегда, симметричный wire авторизации
Клиент всегда отправляет Client ID первой DTLS app-record, сервер
всегда читает. Флаг -auth и URI-параметр auth удалены — рассинхрон
client/server невозможен. -clients-file на сервере включает только
проверку ID по allowlist clients.json.

Также:
- clients.json и client_config.json пишутся с правами 0o600
- исправлены все находки golangci-lint (errcheck, gosec, govet shadow)

Примечание: удалён флаг -auth и URI-параметр auth; Client ID теперь
всегда в первой записи туннеля — старые клиенты/серверы несовместимы.
2026-05-25 00:33:26 +03:00
samosvalishe e061345089 feat: авторизация по client-id, freeturn:// URI и подписки
- internal/clientsdb: БД клиентов с hot-reload + length-prefixed ID wire
- internal/uri: parse/encode share-ссылок freeturn://
- internal/sub: загрузка и парсинг подписок (sub.md)
- client: флаги -auth, -client-id, -sub; позиционный URI; ID через crypto/rand
- server: подкоманда clients (add/remove/list), чтение ID под -clients-file
- запись/чтение ID учитывают DTLS record-orientation (буфер на запись целиком)
- docs: uri.md, sub.md; правки flags/deploy/quickstart
- снят UTF-8 BOM по всем исходникам
2026-05-24 01:14:38 +03:00
samosvalishe 41d877dc64 chore: free turn proxy 2026-05-24 00:01:57 +03:00
samosvalishe bf59e5c8b4 refactor(config): единообразные имена переменных флагов, ужать help-текст
Переименовать переменные-указатели флагов под имена флагов 1:1 (vklink→link,
host→turn, obfProfileRaw→obfProfile, убрать суффиксы *Flag/*Raw/*Hex). Урезать
многословные описания флагов; убрать аннотации (default), раз пакет flag
печатает их сам.
2026-05-23 20:52:30 +03:00
samosvalishe 4bedd00662 refactor(wire): переименовать srtpmimicry → rtpopus, заменить bool -obf на -obf-profile
Готовим зонтик `internal/wire/` под будущие профили мимикрии (rtph264, vp8
и т.д.). Сейчас интерфейс/registry не вводим — родится при появлении второго
профиля; пока только перепаковываем единственный профиль в собственный namespace.

Пакет:
- `internal/wire/srtpmimicry` → `internal/wire/rtpopus`
- error-префикс `"wrap: ..."` → `"rtpopus: ..."`
- wire-формат не тронут (заморожен — совместимость с задеплоенными пирами)

CLI (breaking):
- `-obf` (bool) удалён
- добавлен `-obf-profile none|rtpopus` (default `none`)
- `ObfOpts.Mode bool` → `Profile ObfProfile` + `Enabled()` helper
- консты `ObfProfileNone`/`ObfProfileRTPOpus`
- validation invalid-value error для `-obf-profile`

Docker (breaking):
- env `OBF_MODE=true` → `OBF_PROFILE=rtpopus`

Callsites:
- `common.NewClientWrap` → `NewClientObf`
- vars `wc`/`relayWC` → `obfConn`, `wrapListener` → `obfListener`
- log `obf=%t` → `obf-profile=%s`

Доки + тесты подтянуты под новые имена.
2026-05-23 15:24:00 +03:00
samosvalishe cf47f374b3 refactor(provider): убрать static-провайдер, оставить абстракцию
Сейчас нужен только VK-провайдер. internal/provider/static и связанные флаги
-static-user/-static-pass/-static-addr удалены, валидация и тесты обновлены.
Интерфейс provider.Provider остаётся — слот для будущих реализаций сохранён в
нейтральных формулировках doc-комментариев.
2026-05-23 14:54:46 +03:00
samosvalishe 2b85d937ea refactor(provider/vk): перенести vkauth/captcha/browserprofile/namegen под provider/vk/internal
Переносит VK-specific пакеты под internal/provider/vk/internal/* — теперь
недоступны для импорта извне provider/vk (Go internal/ rule). ish остался в
internal/client/ish — не VK-specific.

vk.SetCaptchaLoggers удалён: логгеры captcha/manual подключаются внутри vk.New,
debug-флаг передаётся через новое поле Config.Debug. cmd/client/main.go больше
не импортирует captcha-пакеты.

Документация обновлена.
2026-05-23 14:36:58 +03:00
samosvalishe 12c1ad6283 feat: абстракция провайдера (vk + static)
Вытаскивает источник TURN-creds из захардкоженной привязки к VK в интерфейс
internal/provider.Provider. Pipeline (proxy/udprelay, proxy/tcpfwd) работает
только через интерфейс — без VK-specific импортов и sentinel'ов.

Реализации:
- internal/provider/vk: фасад над client/vkauth (требует -link)
- internal/provider/static: фиксированные creds для self-hosted coturn /
  metered / любого RFC 5766/8656 TURN (флаги -static-user/-static-pass/
  -static-addr)

Изменения интерфейса:
- common.GetCredsFunc дропнула link (provider держит сам)
- udprelay AuthHandler: LockoutUntilUnix → BackoffUntilUnix
- vkauth sentinels обёрнуты в provider.ErrBackoffActive / ErrFatalNoStreams
  через errors.Join (backward-compat сохранён)

Документация: docs/providers.md (новый), docs/glossary.md и docs/flags.md
обновлены.
2026-05-23 14:11:05 +03:00
samosvalishe dd8233208c fix(lint): устранить замечания golangci-lint + переход на dockers_v2
Проход lint (52 → 0):
- vkauth/token: переименовать затенённый err (govet shadow x3)
- udprelay/loop: явный _ = conn.Close() (errcheck x2)
- captcha/manual: ReadHeaderTimeout + html.EscapeString (G112, G705)
- proxy/tcpfwd: хелпер nonNegUint64 для байтовых счётчиков io.Copy (G115)
- proxy/bondclient, wire/bondframe: bounds-проверки на len->uint16/uint32
- wire/srtpmimicry: задокументирована усечка RTP seq mod 2^16
- dnsdial/doh: bounds-проверка + binary.BigEndian.PutUint16
- captcha/slider: clamp luma до 255 перед конверсией uint8
- vkauth/types: обоснованный nolint для публичных учёток VK SDK

Новый пакет internal/randx — Intn/IntN/Float64 на crypto/rand, drop-in замена
math/rand. Мигрированы все места antifingerprint-jitter (~22) в captcha, slider,
namegen, vkauth, udprelay.

GoReleaser:
- bump GORELEASER_VERSION v2.5.0 → v2.15.4 в Taskfile и обоих CI-workflow;
  текущий синтаксис .goreleaser.yaml (archives.ids, format_overrides.formats)
  требует v2.8+
- миграция dockers + docker_manifests → dockers_v2 (единый блок, multi-platform
  buildx); Dockerfile.goreleaser использует конвенцию ARG TARGETPLATFORM
2026-05-23 01:37:42 +03:00
samosvalishe 971b1ac8fc chore: правка форматирования 2026-05-23 00:33:45 +03:00
samosvalishe 8b2e028f99 fix: устранить замечания комплексной проверки
Критичное:
- go.mod: версия 1.25.5→1.25.0 (go mod tidy); Dockerfile: тег 1.25
- release.yml: go-version 1.25.5 → stable
- удалён temp-файл types.go.tmp.18500.1c9f4278a15a
- udprelay/loop.go: defer conn1/conn2.Close() при сбое Dial; лог write-error

Безопасность/код:
- browserprofile: пермиссии 0644→0600
- bondserver: once.Do перед go readLane (ordering fix)
- cmd/server: err= → err:= (shadow fix в AfterFunc)
- vkauth/client: vkDelayRandom ctx-aware (возвращает error)
- captcha/manual: whitelist хостов в /generic_proxy (SSRF mitigation)

Инфра:
- .dockerignore: исключить .git/.github/notes/dist из build context
- docker-compose.yml: env_file .env для OBF_KEY
- entrypoint.sh: позиционные аргументы вместо unquoted vars; валидация host:port

CI:
- ci.yml: исправлен путь go module cache (~/go/pkg/mod); добавлен -race
- .gitignore: добавлены /dist/, *.test, coverage.out, .env, *.tmp
- .golangci.yml: добавлены gosec и staticcheck
- routes-macos.sh: set -eu вместо set -u

Код:
- bondclient/bondserver: errors.Is(err, io.EOF) вместо err != io.EOF
- cmd/client: константа dtlsHandshakeConcurrency для HandshakeSem

Доки:
- quickstart.md: добавлен Intel Mac (darwin-amd64)
- build.md: версия Go, Windows/macOS cross-compile инструкции
- flags.md: уточнено описание -bond
2026-05-21 20:14:45 +03:00
samosvalishe 31e35efbf3 refactor: переименовать флаги и поля CLI/конфига
- wrap → obf: -wrap/-wrap-key/-gen-wrap-key → -obf/-obf-key/-gen-obf-key;
  ObfOpts.WrapMode/WrapKey/GenWrapKey → Mode/Key/GenKey
- vless → mode: -vless/-vless-bond → -mode tcp / -bond;
  clientProxyMode/serverProxyMode принимают string вместо bool
- -udp → -transport tcp|udp; TURNOpts.UDP → TransportUDP (везде)
- -vk-link → -link; -dns → -dns-mode; DNSModeUDP → DNSModePlain ("plain")
- KCP: убрать LoadProfileFromEnv/LoadFECFromEnv — хардкодить DefaultProfile/FEC{}
- Добавить валидацию -transport/-mode/-bond/-dns-mode в ParseClient/ParseServer
2026-05-21 19:19:40 +03:00
samosvalishe 6c3d3d341c chore(lint): расширить golangci-конфиг и устранить замечания
- Добавить nilerr, contextcheck, noctx, unconvert, unparam, gocritic, prealloc;
  убрать пресет std-error-handling и check-blank
- Заменить net.Dial/Listen/ListenPacket на context-aware варианты в tcpfwd,
  bondserver, tcpfwdserver, udprelay, udpserver
- Убрать параметр typ из writeToNextLane (всегда FrameData)
- Убрать лишний DohEndpoint из возвращаемых значений forwardRaw
- Анонимные receivers для методов без доступа к self (logx, vkauth, doh)
- Переименовать неиспользуемые параметры в _ (bondclient, bondserver, netconn,
  dtlsdial тесты)
- cmd/client: unlambda fix StreamsAlive → connectedStreams.Load
2026-05-21 16:49:59 +03:00
samosvalishe d1e80751c7 refactor: удалить пакет internal/auth
Multi-tenant авторизация была заглушкой (NopAuthenticator), которая никогда не
планировалась к реализации.

- удалён пакет internal/auth (TenantID, Authenticator, NopAuthenticator)
- bondserver.Registry: map[connKey] → map[uint64], убран параметр tenant
- tcpfwdserver: убран auth.Anonymous из вызова HandleStreamAfterMagic
- удалён тест TestRegistryTenantIsolation
2026-05-21 16:02:16 +03:00
samosvalishe 4ac1f3e25c chore: убрать случайно добавленный .tmp файл 2026-05-21 12:37:03 +03:00
samosvalishe e417dac087 style: перевести комментарии на русский, убрать очевидные 2026-05-21 12:31:50 +03:00
samosvalishe ada9b949a4 fix: правки после рефакторинга
- bondserver: errors.Is для проверки smux.ErrGoAway (обёрнутые ошибки)
- bondserver: убрать лишнюю аллокацию в copyBackendToBond, зеркалит bondclient
- cmd/server: прерывать Accept-цикл при отмене ctx вместо warn-спама
- cmd/server: понизить лог выхода после таймаута с Error до Warn
2026-05-21 09:09:19 +03:00
samosvalishe 3b73d720f1 test: покрыть NopAuthenticator, GenerateSelfSignedCert, HandshakeSem
Минимальное покрытие для заготовки auth и хелперов dtlsdial:
- NopAuthenticator.Authenticate возвращает (Anonymous, nil)
- GenerateSelfSignedCert выдаёт рабочий сертификат с приватным ключом
- HandshakeSem блокирует захват при заполнении и уважает отмену ctx

Также документирует на Authenticator, что интерфейс stream-ориентирован; auth
UDP-режима получит отдельный token-based интерфейс.
2026-05-21 02:02:23 +03:00
samosvalishe c4b6774241 style: gofmt/goimports новых файлов
Применить gofmt и goimports к файлам, добавленным в недавних разбиениях
(udprelay/loop.go, udprelay/run.go, vkauth/token_oksession.go). Группы импортов
теперь по порядку stdlib / external / internal.
2026-05-21 02:01:24 +03:00
samosvalishe 71e3bb1f01 test(bondserver): тест изоляции tenant
Защищает от регрессии, где ключ map Registry откатывается с {Tenant, ConnID} к
голому ConnID. Один и тот же ConnID под двумя TenantID должен резолвиться в
разные serverConn.
2026-05-21 01:33:20 +03:00
samosvalishe 21138fef26 fix(dtlsdial): Dial использует хелпер GenerateSelfSignedCert внутри
Унификация 0b3d8a1 пропустила in-package место вызова: Dialer.Dial всё ещё звал
selfsign.GenerateSelfSigned напрямую вместо нового хелпера
GenerateSelfSignedCert. Маршрутизируем через хелпер, чтобы любая будущая правка
генерации сертификата применялась единообразно.
2026-05-21 01:33:18 +03:00
samosvalishe fba9a5e26d fix(udprelay): синхронизировать watcher-горутину с возвратом Run
fatalErr писался watcher-горутиной вне WaitGroup и читался после wg.Wait();
загрузке не хватало happens-after упорядочивания с записью. Заменено на
atomic.Pointer и канал watcherDone, который Run дренажит после отмены, так что
загрузка всегда видит полную запись.
2026-05-21 01:33:12 +03:00
samosvalishe 0e2d42c912 refactor(vkauth): разбить token.go на файлы по шагам
token.go теперь тонкий оркестратор (~75 строк). Каждый шаг VK-токена в своём
файле:
- token_request.go: хелпер doRequest (POST + browser-заголовки + JSON)
- token_anon.go: fetchAnonToken (шаг 1, login.vk.ru)
- token_call.go: fetchCallToken + captcha-loop + solveCaptcha (шаг 2)
- token_oksession.go: fetchOkRuSession (шаг 3, calls.okcdn.ru)
- token_creds.go: fetchTurnCreds (шаг 4, vchat.joinConversationByLink)

Публичный API (Client, getTokenChain, tokenChainFn) не изменён. Поведение
идентично оригиналу.
2026-05-21 01:26:21 +03:00
samosvalishe d82258f844 refactor(udprelay): разбить на run/loop/listener
Разбивает монолитный udprelay.go на 530 строк на три файла:
- run.go: Run, Deps, ErrFatal, Params, AuthHandler, GetCredsFunc
- loop.go: DTLSLoop, TURNLoop, oneDTLS, oneTURN
- listener.go: runListener, Packet, packetPool, inboundQueueCap

Поведение не изменено; чисто структурный рефакторинг.
2026-05-21 01:25:59 +03:00
samosvalishe 52271d0367 refactor(bondserver): tenant-scoped ключ Registry
Заменяет map[uint64]*serverConn на map[connKey]*serverConn, где connKey =
{auth.TenantID, uint64}. Предотвращает коллизии ConnID между tenant в будущих
multi-tenant развёртываниях. Все текущие вызовы передают auth.Anonymous, поэтому
поведение не изменено.

HandleStreamAfterMagic получает параметр tenant auth.TenantID; tcpfwdserver
передаёт auth.Anonymous, сохраняя single-tenant семантику. serverConn получает
поле tenantID под будущий per-tenant роутинг.
2026-05-21 01:25:05 +03:00
samosvalishe 65aa55b211 feat(auth): добавить заготовку Authenticator
Вводит internal/auth с TenantID, sentinel Anonymous, интерфейсом Authenticator
и NopAuthenticator (no-op, всегда возвращает Anonymous). doc.go описывает
планируемый multi-tenant поток (SQLite/HMAC/deeplink). Реальной реализации
пока нет.
2026-05-21 01:24:58 +03:00
samosvalishe 6b8b82f530 refactor(client): убрать deprecated-теги с package-level логгеров
cmd/client — однопроцессный CLI; package-level Log+SetLogger остаётся
каноническим API. captcha.Solve по-прежнему принимает явный логгер для DI.
Также осовременен backoffSteps через min().
2026-05-21 01:20:54 +03:00
samosvalishe 5735b4a297 fix(captcha/dnsdial): начать DI-миграцию package-level логгеров
captcha.Solve теперь принимает явный параметр logx.Logger, и captchaSession
использует его внутри; package-level переменная Log и SetLogger сохранены, но
помечены deprecated.

auto_solver.go обновлён, чтобы передавать captcha.Log в Solve, сохраняя текущую
обвязку до полного DI-прохода.

manual и dnsdial: SetLogger и Log помечены deprecated с пояснением для будущей
struct-based DI; поведение не изменено.
2026-05-21 01:14:43 +03:00
samosvalishe ef549dc2b8 fix(dtlsdial): унифицировать генерацию self-signed сертификата
Добавляет dtlsdial.GenerateSelfSignedCert() как единственное место создания
self-signed сертификата. cmd/server теперь использует его вместо прямого импорта
selfsign; Dialer.Dial уже использовал ленивый путь certOnce, который внутри
зовёт ту же функцию.
2026-05-21 01:11:08 +03:00
samosvalishe f54f59bff9 fix(bondserver): отмена при spin из-за потери lane
writeToNextLane теперь зовёт c.cancel() сразу, когда removeLane возвращает
left==0, вместо ожидания таймаута через 10ms sleep-loop. Убирает spin, когда
все lane пропали, а readLane уже отменил контекст.
2026-05-21 01:09:54 +03:00
samosvalishe 9090d5894a fix(udprelay): параллельный старт стримов
Все N стримов теперь стартуют конкурентно. okchan оставлен только как лог-сигнал
первого успешного handshake; стримы 2..N больше не ждут завершения
DTLS-handshake стрима 1.
2026-05-21 01:09:49 +03:00
samosvalishe ac695603b7 fix(bondclient): слать Hello с реальным числом lane после фильтрации
Открыть все кандидатные стримы сначала, затем писать Hello с len(opened), чтобы
bondserver.waitForInitialLanes не зависал на lane, которые никогда не
открывались (закрыты/ошибка). Также документирует, почему клиентский write-retry
не зацикливается как серверный: набор lane клиента фиксирован на время жизни
Handle.
2026-05-21 00:43:48 +03:00
samosvalishe 6588544368 fix(udprelay): инкремент ConnectedStreams до ResetErrors
Конкурентные вызыватели HandleAuthError могли видеть устаревшие счётчики ошибок,
очищенные до того, как стрим стал виден в connected-счётчике. Переупорядочено
так, что снимок stream-alive идёт первым.
2026-05-21 00:43:42 +03:00
samosvalishe 7ffa21b8aa fix(captcha/manual): останавливать HTTP-сервер при отмене ctx
runCaptchaServerAndWait теперь делает select на ctx.Done вместе с keyCh и
закрывает listener в defer. Без этого, когда родительский manualCtx истекал
(3m), внутренняя горутина блокировалась на keyCh навсегда, оставляя
127.0.0.1:8765 занятым до выхода процесса. SolveViaProxy/SolveViaHTTP принимают
ctx и протягивают его; manualCaptchaSolver в cmd/client/main.go форвардит ctx
вместо того, чтобы его терять.
2026-05-21 00:43:36 +03:00
samosvalishe 242862397a chore: переименовать модуль в github.com/samosvalishe/btp; обновить зависимости
- путь модуля: github.com/cacggghp/vk-turn-proxy → github.com/samosvalishe/btp
- директории cmd: cmd/vk-turn-{client,server} → cmd/{client,server}
- зависимости: pion/turn v5.0.4, kcp-go v5.6.72, smux v1.5.57, x/crypto v0.51,
  klauspost/{compress,cpuid,reedsolomon}, x/{net,sys,text,tools,mod,time}
- README, workflows, GLOSSARY обновлены под новые пути
2026-05-20 12:39:26 +03:00