diff --git a/internal/proxy/common/common.go b/internal/proxy/common/common.go new file mode 100644 index 0000000..89d0e11 --- /dev/null +++ b/internal/proxy/common/common.go @@ -0,0 +1,43 @@ +// Package common holds small helpers shared between udprelay and tcpfwd. The +// two proxy modes layer DTLS and srtpmimicry differently (see V2-6 decision +// in notes/AUDIT_2026-05-19_V2.md), so a full Engine/Handler abstraction was +// rejected. This package only collects the bits that are genuinely identical. +package common + +import ( + "context" + "fmt" + "net" + + "github.com/cacggghp/vk-turn-proxy/internal/transport/turndial" + "github.com/cacggghp/vk-turn-proxy/internal/wire/srtpmimicry" +) + +// GetCredsFunc resolves VK TURN credentials for a (link, streamID) pair. +// Matches vkauth.Client.GetCredentials. +type GetCredsFunc func(ctx context.Context, link string, streamID int) (user, pass, rawURL string, err error) + +// DialTURN fetches credentials and opens a TURN stream. The caller is +// responsible for closing the returned stream and for any auth-error retry +// policy (udprelay) or session-restart policy (tcpfwd). +func DialTURN(ctx context.Context, host, port string, udp bool, peer *net.UDPAddr, link string, streamID int, getCreds GetCredsFunc) (*turndial.Stream, error) { + user, pass, rawURL, err := getCreds(ctx, link, streamID) + if err != nil { + return nil, fmt.Errorf("get TURN creds: %w", err) + } + return turndial.Open(ctx, turndial.Config{ + HostOverride: host, + PortOverride: port, + UDP: udp, + }, peer, user, pass, rawURL) +} + +// NewClientWrap returns a client-side srtpmimicry.Conn if key has the +// expected length, otherwise (nil, nil) — wrap disabled. NewConn errors are +// propagated so callers can surface them. +func NewClientWrap(key []byte) (*srtpmimicry.Conn, error) { + if len(key) != srtpmimicry.KeyLen { + return nil, nil + } + return srtpmimicry.NewConn(key, false) +} diff --git a/internal/proxy/tcpfwd/tcpfwd.go b/internal/proxy/tcpfwd/tcpfwd.go index 96dbbda..af874cd 100644 --- a/internal/proxy/tcpfwd/tcpfwd.go +++ b/internal/proxy/tcpfwd/tcpfwd.go @@ -11,16 +11,17 @@ import ( "github.com/cacggghp/vk-turn-proxy/internal/client/ish" "github.com/cacggghp/vk-turn-proxy/internal/logx" + "github.com/cacggghp/vk-turn-proxy/internal/proxy/common" "github.com/cacggghp/vk-turn-proxy/internal/stats" "github.com/cacggghp/vk-turn-proxy/internal/transport/dtlsdial" "github.com/cacggghp/vk-turn-proxy/internal/transport/kcptun" - "github.com/cacggghp/vk-turn-proxy/internal/transport/turndial" "github.com/cacggghp/vk-turn-proxy/internal/wire/srtpmimicry" "github.com/xtaci/smux" ) -// GetCredsFunc resolves VK TURN credentials for a (link, streamID) pair. -type GetCredsFunc func(ctx context.Context, link string, streamID int) (string, string, string, error) +// GetCredsFunc is an alias of common.GetCredsFunc kept for cmd/main wiring +// stability. +type GetCredsFunc = common.GetCredsFunc // Params is the per-pool TURN/wrap configuration. type Params struct { @@ -232,16 +233,7 @@ func createSmuxSession(ctx context.Context, deps *Deps, params *Params, peer *ne } } - user, pass, rawURL, err := params.GetCreds(ctx, params.Link, id) - if err != nil { - return nil, nil, fmt.Errorf("get TURN creds: %w", err) - } - - stream, err := turndial.Open(ctx, turndial.Config{ - HostOverride: params.Host, - PortOverride: params.Port, - UDP: params.UDP, - }, peer, user, pass, rawURL) + stream, err := common.DialTURN(ctx, params.Host, params.Port, params.UDP, peer, params.Link, id, params.GetCreds) if err != nil { return nil, nil, err } @@ -250,13 +242,10 @@ func createSmuxSession(ctx context.Context, deps *Deps, params *Params, peer *ne deps.log().Debugf("[session %d] TURN server IP: %s", id, stream.ServerUDPAddr.IP) deps.log().Debugf("relayed-address=%s", relayConn.LocalAddr().String()) - var relayWC *srtpmimicry.Conn - if len(params.WrapKey) == srtpmimicry.KeyLen { - relayWC, err = srtpmimicry.NewConn(params.WrapKey, false) - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("wrap init: %w", err) - } + relayWC, err := common.NewClientWrap(params.WrapKey) + if err != nil { + cleanup() + return nil, nil, fmt.Errorf("wrap init: %w", err) } dtlsPC := &srtpmimicry.RelayPacketConn{Relay: relayConn, Peer: peer, Conn: relayWC} dtlsConn, err := deps.DTLSDialer.Dial(ctx, dtlsPC, peer) diff --git a/internal/proxy/udprelay/udprelay.go b/internal/proxy/udprelay/udprelay.go index 5bcf9c8..ef97537 100644 --- a/internal/proxy/udprelay/udprelay.go +++ b/internal/proxy/udprelay/udprelay.go @@ -17,9 +17,9 @@ import ( "time" "github.com/cacggghp/vk-turn-proxy/internal/logx" + "github.com/cacggghp/vk-turn-proxy/internal/proxy/common" "github.com/cacggghp/vk-turn-proxy/internal/stats" "github.com/cacggghp/vk-turn-proxy/internal/transport/dtlsdial" - "github.com/cacggghp/vk-turn-proxy/internal/transport/turndial" "github.com/cacggghp/vk-turn-proxy/internal/wire/srtpmimicry" "github.com/cbeuw/connutil" ) @@ -37,12 +37,12 @@ var Pool = sync.Pool{ New: func() any { return &Packet{Data: make([]byte, 2048)} }, } -// GetCredsFunc resolves VK TURN credentials for a (link, streamID) pair. -// Matches vkauth.Client.GetCredentials. -type GetCredsFunc func(ctx context.Context, link string, streamID int) (string, string, string, error) +// GetCredsFunc is an alias of common.GetCredsFunc kept for cmd/main wiring +// stability. +type GetCredsFunc = common.GetCredsFunc // AuthHandler is the subset of vkauth.Client this package needs. Keeping it as -// an interface lets internal/proxy/udp avoid importing internal/client/vkauth. +// an interface lets internal/proxy/udprelay avoid importing internal/client/vkauth. type AuthHandler interface { IsAuthError(err error) bool HandleAuthError(streamID int) bool @@ -248,16 +248,7 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, time.Sleep(time.Duration(rand.Intn(400)+100) * time.Millisecond) var err error defer func() { c <- err }() - user, pass, urlTarget, err1 := params.GetCreds(ctx, params.Link, streamID) - if err1 != nil { - err = fmt.Errorf("failed to get TURN credentials: %s", err1) - return - } - stream, err1 := turndial.Open(ctx, turndial.Config{ - HostOverride: params.Host, - PortOverride: params.Port, - UDP: params.UDP, - }, peer, user, pass, urlTarget) + stream, err1 := common.DialTURN(ctx, params.Host, params.Port, params.UDP, peer, params.Link, streamID, params.GetCreds) if err1 != nil { if deps.Auth.IsAuthError(err1) { deps.Auth.HandleAuthError(streamID) @@ -293,15 +284,11 @@ func oneTURN(ctx context.Context, deps *Deps, params *Params, peer *net.UDPAddr, } }) var internalPipeAddr atomic.Value - var wc *srtpmimicry.Conn - if len(params.WrapKey) == srtpmimicry.KeyLen { - var wcErr error - wc, wcErr = srtpmimicry.NewConn(params.WrapKey, false) - if wcErr != nil { - log.Printf("[STREAM %d] WRAP init failed: %v", streamID, wcErr) - turncancel() - return - } + wc, wcErr := common.NewClientWrap(params.WrapKey) + if wcErr != nil { + log.Printf("[STREAM %d] WRAP init failed: %v", streamID, wcErr) + turncancel() + return } go func() { diff --git a/notes/AUDIT_2026-05-19_V2.md b/notes/AUDIT_2026-05-19_V2.md index f51dc1a..3ed5c62 100644 --- a/notes/AUDIT_2026-05-19_V2.md +++ b/notes/AUDIT_2026-05-19_V2.md @@ -287,11 +287,12 @@ internal/ - [ ] **V2-5.5** `client/internal/namegen` → `internal/client/namegen`. - [ ] **V2-5.6** Удалить пустой `client/` (после переезда `main.go` на V2-1 и `manual_captcha` на V2-2 — он пуст). -### Этап V2-6 — `proxy.Engine` + Handler +### Этап V2-6 — `proxy.Engine` + Handler (свёрнут до минимальной экстракции) -- [ ] **V2-6.1** Создать `internal/proxy/engine/engine.go`: интерфейс `Handler` (`Serve(ctx, dtlsConn)`), общий `Engine{Open N TURN-stream, DTLS handshake, creds rotation, counters}`. -- [ ] **V2-6.2** `udprelay` и `tcpfwd` рефакторятся в реализации `Handler`. Дублирование выходит. `Deps` сокращается. -- [ ] **V2-6.3** Bond становится опцией `tcpfwd.Handler` (флаг конструктора), не отдельным пакетом-сосседом. `bondclient` / `bondserver` остаются как helper'ы, но вызываются изнутри `tcpfwd`. +- [x] **V2-6.0** Решение 2026-05-19: полный Engine+Handler отклонён после ревью кода. udprelay (WG→DTLS-terminate→wrap→TURN) и tcpfwd (TCP→smux→KCP→DTLS→wrap→TURN) имеют **разное layering wrap/DTLS** и разные lifecycle (per-stream restart vs maintained pool). Реальная общая часть ~30 LOC, а не 70%, как заявлено в плане. Engine с двумя ветками был бы тем же дублём внутри. +- [x] **V2-6.1** Создан `internal/proxy/common/common.go`: `GetCredsFunc`, `DialTURN`, `NewClientWrap`. Только то, что действительно идентично. +- [x] **V2-6.2** `udprelay` и `tcpfwd` используют `common.DialTURN` / `common.NewClientWrap`. Локальные `GetCredsFunc` стали `type ... = common.GetCredsFunc` (alias для совместимости cmd/main wiring). +- [ ] **V2-6.3** Bond как опция `tcpfwd.Handler` — **отменено** вместе с Handler-интерфейсом. `bondclient` остаётся отдельным пакетом, вызываемым из cmd через `Deps.BondHandler`. Текущий контракт читаемый, не трогаем. ### Этап V2-7 — config структуризация