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), сброс на успешном байнде.
This commit is contained in:
samosvalishe
2026-06-08 08:34:45 +03:00
parent 2845874f86
commit 2534bbe96a
3 changed files with 11 additions and 13 deletions
+2 -2
View File
@@ -271,13 +271,13 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr,
const maxPayload = 1600
// PermDead закрывается при блэкхоле permission (см. turndial/permwatch.go) —
// PermDead закрывается при блэкхоле data-path (см. turndial/permwatch.go) —
// отменяем turnctx, TURNLoop делает свежий allocate.
wg.Go(func() {
select {
case <-turnctx.Done():
case <-stream.PermDead:
deps.log().Warnf("[STREAM %d] TURN permission refresh умер — рецикл allocation", streamID)
deps.log().Warnf("[STREAM %d] TURN channel-bind умер — рецикл allocation", streamID)
turncancel()
}
})
+3 -7
View File
@@ -7,15 +7,11 @@ import (
"github.com/pion/logging"
)
// VK relay при дропе permission молча роняет пакеты к peer без ошибки записи.
// pion на каждый проваленный цикл refresh пишет permFailMarker (Warnf, scope
// "turnc"). Считаем провалы подряд — точный per-stream детект блэкхола вместо
// трафик-эвристики (обратный путь WG привязан к endpoint, не к стриму).
const (
permFailMarker = "Failed to refresh permissions"
permOKMarker = "Refresh permissions successful"
permFailMarker = "Failed to bind channel"
permOKMarker = "Channel binding successful"
turncScope = "turnc"
// ~60s при PermissionRefreshInterval=30s, внутри 5-мин lifetime permission.
// ChannelBind refresh идёт ~раз в 5 мин (binding lifetime); 2 провала подряд.
permFailThreshold = 2
)
+6 -4
View File
@@ -37,8 +37,8 @@ type Stream struct {
Relay net.PacketConn
// ServerUDPAddr — резолвнутый UDP-адрес TURN-сервера (host:port).
ServerUDPAddr *net.UDPAddr
// PermDead закрывается при стойком провале refresh permission (relay
// блэкхолит) — вызывающий рециклит allocation. См. permwatch.go.
// PermDead закрывается при стойком провале ChannelBind refresh (relay
// блэкхолит data-path) - вызывающий рециклит allocation. См. permwatch.go.
PermDead <-chan struct{}
close func() error
}
@@ -108,7 +108,9 @@ func Open(ctx context.Context, cfg Config, peer *net.UDPAddr, user, pass, rawAdd
addrFamily = turn.RequestedAddressFamilyIPv6
}
// RefreshInterval 120s→30s: быстрее ловим блэкхол, внутри 5-мин lifetime.
// Standalone CreatePermission refresh VK реджектит 400 — глушим (24h).
// Permission держится живым через ChannelBind refresh (RFC 8656 §11);
// блэкхол ловим по провалу этого байнда (см. permwatch.go).
permDead := make(chan struct{})
var permOnce sync.Once
loggerFactory := &permWatchFactory{
@@ -124,7 +126,7 @@ func Open(ctx context.Context, cfg Config, peer *net.UDPAddr, user, pass, rawAdd
Username: user,
Password: pass,
RequestedAddressFamily: addrFamily,
PermissionRefreshInterval: 30 * time.Second,
PermissionRefreshInterval: 24 * time.Hour,
LoggerFactory: loggerFactory,
})
if err != nil {