VK реджектит standalone CreatePermission refresh с 400 независимо от здоровья стрима, поэтому permWatch ложно рециклил здоровые аллокации каждые ~60s. Permission держится живым через ChannelBind refresh (RFC 8656 §11), стандартный для VK путь, потому отдельный CreatePermission refresh глушим (PermissionRefreshInterval 24h). Реальный блэкхол ловим по провалу ChannelBind refresh (data-path), сброс на успешном байнде.
VK отдаёт несколько relay-адресов, но стрим использовал ровно один
(streamID%n). Если этот IP душит DPI/RST — allocate падает без альтернативы.
Credentials несёт список адресов (primary-first по streamID), DialTURN
пробует кандидатов по очереди и берёт первый успешный allocate. Балансировка
relay-IP по стримам сохранена, остальные адреса — фоллбэк. HostOverride (-turn)
схлопывает список до одного адреса.
Трафик-watchdog (tx растёт, rx заморожен) ложно рециклил здоровые
sender-only стримы: обратный путь WireGuard привязан к endpoint, а не к
стриму, поэтому rx==0 на отправляющем стриме — норма, не блэкхол.
permWatchFactory считает проваленные циклы refresh permission в логе pion
(scope turnc) и закрывает Stream.PermDead после порога. RefreshInterval
урезан 120s→30s. oneTURN рециклит allocation по PermDead.
При провале CreatePermission refresh pion роняет permission без ошибки,
relay молча дропает пакеты, стрим висит и ест свою долю WG-трафика.
Watchdog: tx растёт, rx заморожен 20с, отменяем turnctx и делаем свежий
allocate через TURNLoop.
ТСПУ читает SNI из ClientHello и инжектит delayed RST в TLS до VK
(login.vk.ru и др.). Дробим первый Write через SplitFirstWriteConn,
SNI уезжает во второй сегмент. Вписано в tls-client через
WithProxyDialerFactory без proxyUrl: прямой транспорт, резолвер сохранён.
Cert генерируется заново на каждый Dial вместо переиспользования через
sync.Once: каждая TURN-сессия и реконнект получают свой fingerprint,
чтобы N параллельных стримов с одного IP не коррелировались как ботный
трафик.
Стрим 1 поднимается первым и сигналит okchan; стримы 2..N ждут сигнал
(или ctx/safety-timeout 20s) и переиспользуют тёплый кэш credentials
вместо N одновременных запросов к VK — снимает thundering herd на
старте (частая причина rate-limit/captcha).
WrapInPlace/UnwrapInPlace работают прямо в wire-буфере: ingress читает
payload сразу за RTP-заголовком, egress отдаёт subslice расшифровки.
Убирает 2 memcpy (~1400B) и 2 буфера на пакет в obf-режиме, плюс
atomic-запись адреса пайпа теперь один раз. WrapInto/Unwrap делегируют,
wire-формат не изменён.
VK TURN реджектит standalone CreatePermission refresh с 400 Bad Request
каждые 120s, засоряя логи. Данные идут через ChannelData; channel-bind
refresh (RFC 8656 §9) держит permission живым, потому отдельный refresh
избыточен. Выставляем PermissionRefreshInterval в 24h.
Клиент всегда отправляет 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 теперь
всегда в первой записи туннеля — старые клиенты/серверы несовместимы.
- 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 по всем исходникам
Переименовать переменные-указатели флагов под имена флагов 1:1 (vklink→link,
host→turn, obfProfileRaw→obfProfile, убрать суффиксы *Flag/*Raw/*Hex). Урезать
многословные описания флагов; убрать аннотации (default), раз пакет flag
печатает их сам.
Сейчас нужен только VK-провайдер. internal/provider/static и связанные флаги
-static-user/-static-pass/-static-addr удалены, валидация и тесты обновлены.
Интерфейс provider.Provider остаётся — слот для будущих реализаций сохранён в
нейтральных формулировках doc-комментариев.
Переносит 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-пакеты.
Документация обновлена.
Вытаскивает источник 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
обновлены.
- bondserver: errors.Is для проверки smux.ErrGoAway (обёрнутые ошибки)
- bondserver: убрать лишнюю аллокацию в copyBackendToBond, зеркалит bondclient
- cmd/server: прерывать Accept-цикл при отмене ctx вместо warn-спама
- cmd/server: понизить лог выхода после таймаута с Error до Warn
Минимальное покрытие для заготовки auth и хелперов dtlsdial:
- NopAuthenticator.Authenticate возвращает (Anonymous, nil)
- GenerateSelfSignedCert выдаёт рабочий сертификат с приватным ключом
- HandshakeSem блокирует захват при заполнении и уважает отмену ctx
Также документирует на Authenticator, что интерфейс stream-ориентирован; auth
UDP-режима получит отдельный token-based интерфейс.
Применить gofmt и goimports к файлам, добавленным в недавних разбиениях
(udprelay/loop.go, udprelay/run.go, vkauth/token_oksession.go). Группы импортов
теперь по порядку stdlib / external / internal.
Защищает от регрессии, где ключ map Registry откатывается с {Tenant, ConnID} к
голому ConnID. Один и тот же ConnID под двумя TenantID должен резолвиться в
разные serverConn.
Унификация 0b3d8a1 пропустила in-package место вызова: Dialer.Dial всё ещё звал
selfsign.GenerateSelfSigned напрямую вместо нового хелпера
GenerateSelfSignedCert. Маршрутизируем через хелпер, чтобы любая будущая правка
генерации сертификата применялась единообразно.
fatalErr писался watcher-горутиной вне WaitGroup и читался после wg.Wait();
загрузке не хватало happens-after упорядочивания с записью. Заменено на
atomic.Pointer и канал watcherDone, который Run дренажит после отмены, так что
загрузка всегда видит полную запись.
Заменяет 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 роутинг.
Вводит internal/auth с TenantID, sentinel Anonymous, интерфейсом Authenticator
и NopAuthenticator (no-op, всегда возвращает Anonymous). doc.go описывает
планируемый multi-tenant поток (SQLite/HMAC/deeplink). Реальной реализации
пока нет.
cmd/client — однопроцессный CLI; package-level Log+SetLogger остаётся
каноническим API. captcha.Solve по-прежнему принимает явный логгер для DI.
Также осовременен backoffSteps через min().
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; поведение не изменено.
Добавляет dtlsdial.GenerateSelfSignedCert() как единственное место создания
self-signed сертификата. cmd/server теперь использует его вместо прямого импорта
selfsign; Dialer.Dial уже использовал ленивый путь certOnce, который внутри
зовёт ту же функцию.
writeToNextLane теперь зовёт c.cancel() сразу, когда removeLane возвращает
left==0, вместо ожидания таймаута через 10ms sleep-loop. Убирает spin, когда
все lane пропали, а readLane уже отменил контекст.
Все N стримов теперь стартуют конкурентно. okchan оставлен только как лог-сигнал
первого успешного handshake; стримы 2..N больше не ждут завершения
DTLS-handshake стрима 1.
Открыть все кандидатные стримы сначала, затем писать Hello с len(opened), чтобы
bondserver.waitForInitialLanes не зависал на lane, которые никогда не
открывались (закрыты/ошибка). Также документирует, почему клиентский write-retry
не зацикливается как серверный: набор lane клиента фиксирован на время жизни
Handle.
Конкурентные вызыватели HandleAuthError могли видеть устаревшие счётчики ошибок,
очищенные до того, как стрим стал виден в connected-счётчике. Переупорядочено
так, что снимок stream-alive идёт первым.
runCaptchaServerAndWait теперь делает select на ctx.Done вместе с keyCh и
закрывает listener в defer. Без этого, когда родительский manualCtx истекал
(3m), внутренняя горутина блокировалась на keyCh навсегда, оставляя
127.0.0.1:8765 занятым до выхода процесса. SolveViaProxy/SolveViaHTTP принимают
ctx и протягивают его; manualCaptchaSolver в cmd/client/main.go форвардит ctx
вместо того, чтобы его терять.