diff --git a/cmd/client/main.go b/cmd/client/main.go index b672394..03889e6 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -16,7 +16,6 @@ import ( "github.com/samosvalishe/btp/internal/config" "github.com/samosvalishe/btp/internal/logx" "github.com/samosvalishe/btp/internal/provider" - "github.com/samosvalishe/btp/internal/provider/static" "github.com/samosvalishe/btp/internal/provider/vk" "github.com/samosvalishe/btp/internal/proxy/bondclient" "github.com/samosvalishe/btp/internal/proxy/tcpfwd" @@ -161,12 +160,6 @@ func buildProvider(cfg *config.Client, dialer net.Dialer, connected *atomic.Int3 Log: logger, Debug: cfg.Log.Debug, }, vk.DefaultManualSolver) - case config.ProviderStatic: - return static.New(static.Config{ - User: cfg.Static.User, - Pass: cfg.Static.Pass, - Addr: cfg.Static.Addr, - }) default: return nil, fmt.Errorf("unknown provider %q", cfg.Provider.Name) } diff --git a/docs/flags.md b/docs/flags.md index fcc3ead..727e87a 100644 --- a/docs/flags.md +++ b/docs/flags.md @@ -6,11 +6,8 @@ | --- | --- | --- | | `-listen` | `127.0.0.1:9000` | локальный адрес `ip:port`, куда подключается WireGuard или Xray клиент | | `-peer` | **обязательный** | адрес сервера на VPS, `host:port` | -| `-provider` | `vk` | источник TURN-creds: `vk` \| `static` (см. `docs/providers.md`) | +| `-provider` | `vk` | источник TURN-creds: `vk` (см. `docs/providers.md`) | | `-link` | **обязательный для `-provider vk`** | ссылка VK Calls `https://vk.com/call/join/...` | -| `-static-user` | **обязательный для `-provider static`** | TURN username | -| `-static-pass` | **обязательный для `-provider static`** | TURN password | -| `-static-addr` | **обязательный для `-provider static`** | TURN server `host:port` | | `-n` | `10` | параллельных TURN-потоков | | `-transport` | `tcp` | транспорт до TURN-реле: `tcp` (TCP/TLS) \| `udp` | | `-mode` | `udp` | режим туннеля: `udp` (UDP-релей для WireGuard) \| `tcp` (TCP-форвардер для Xray/sing-box) | diff --git a/docs/glossary.md b/docs/glossary.md index e955ee9..3720735 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -30,9 +30,8 @@ client ←→ VK TURN-relay Standard relay protocol (RFC 5766/8656). Источник реле выбирается флагом `-provider` (см. ниже). Мы используем реле как транзит к собственному серверу. Транспорт до TURN выбирается флагом `-transport` (UDP/TCP). ### Provider -Источник TURN-реквизитов (user/pass/server-addr). Интерфейс `internal/provider.Provider`. Текущие реализации: +Источник TURN-реквизитов (user/pass/server-addr). Интерфейс `internal/provider.Provider`. Реализации: - **`vk`** (default) — `internal/provider/vk`, обёртка над VK Calls API (требует `-link`). -- **`static`** — `internal/provider/static`, фиксированные `-static-user/-static-pass/-static-addr` (для coturn, metered, любого совместимого TURN). Pipeline (proxy/udprelay, proxy/tcpfwd) работает только через интерфейс — без VK-specific импортов. См. `docs/providers.md`. diff --git a/docs/providers.md b/docs/providers.md index e25b4bf..7903e28 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -15,20 +15,7 @@ VK Calls API. Перебирает встроенные `app_id/app_secret`, п - `-streams-per-cred` (default 10) — сколько TURN-стримов делят один кеш креденшалов. - `-manual-captcha` — пропустить auto-solver, сразу открыть браузер. -Пакет: `internal/provider/vk/`. Внутренние зависимости: `internal/client/{vkauth,captcha,captcha/manual,browserprofile,namegen,ish,dnsdial}`. - -### `static` - -Фиксированные creds для любого RFC 5766/8656-совместимого TURN: self-hosted coturn, metered.ca с long-term creds, корпоративный TURN. - -**Обязательные флаги:** -- `-static-user` — TURN username. -- `-static-pass` — TURN password. -- `-static-addr` — TURN server host:port. - -Без сетевого control-plane: никаких captcha, throttling, lockout. Если TURN вернёт 401, pipeline крутит retry с теми же creds — провайдер не умеет их ротировать. - -Пакет: `internal/provider/static/`. +Пакет: `internal/provider/vk/`. VK-специфичные зависимости (`vkauth`, `captcha`, `captcha/manual`, `browserprofile`, `namegen`) живут под `internal/provider/vk/internal/*` и недоступны извне `provider/vk`. Общие: `internal/client/{ish,dnsdial}`. ## Как добавить новый провайдер @@ -66,5 +53,5 @@ VK Calls API. Перебирает встроенные `app_id/app_secret`, п ## Известные ограничения текущей абстракции -1. **Per-process один провайдер** — нет fallback chain (VK → static). Можно добавить wrapper-провайдер позже. +1. **Per-process один провайдер** — нет fallback chain. Можно добавить wrapper-провайдер позже. 2. **`StreamsAlive` callback** — VK-провайдер использует для решения fatal-vs-throttle. Другие провайдеры могут игнорировать (поле `Config.StreamsAlive` nil-safe). diff --git a/internal/config/config.go b/internal/config/config.go index c0ce54e..51452a1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -67,22 +67,14 @@ type VKOpts struct { ManualCaptcha bool // -manual-captcha } -// StaticOpts — фиксированные TURN-реквизиты (только клиент, провайдер "static"). -type StaticOpts struct { - User string // -static-user - Pass string // -static-pass - Addr string // -static-addr -} - // ProviderOpts выбирает реализацию provider.Provider. type ProviderOpts struct { - Name string // -provider: vk (default) | static + Name string // -provider: vk (default) } // Известные имена провайдеров. const ( - ProviderVK = "vk" - ProviderStatic = "static" + ProviderVK = "vk" ) // DNSOpts — опции DNS-резолвинга (только клиент). @@ -109,7 +101,6 @@ type Client struct { Proxy ProxyOpts Provider ProviderOpts VK VKOpts - Static StaticOpts DNS DNSOpts Log LogOpts KCP KCPOpts @@ -134,11 +125,8 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) { host := fs.String("turn", "", "переопределить IP TURN-сервера (по умолчанию берётся из credentials провайдера)") port := fs.String("port", "", "переопределить порт TURN-сервера (по умолчанию берётся из credentials провайдера)") listen := fs.String("listen", "127.0.0.1:9000", "локальный адрес ip:port, куда подключается WireGuard или Xray клиент") - providerFlag := fs.String("provider", ProviderVK, "источник TURN-реквизитов: vk (default, VK Calls API) | static (фиксированные -static-* флаги)") + providerFlag := fs.String("provider", ProviderVK, "источник TURN-реквизитов: vk (default, VK Calls API)") vklink := fs.String("link", "", "ссылка VK Calls вида https://vk.com/call/join/... (обязательно для -provider vk)") - staticUser := fs.String("static-user", "", "TURN username (обязательно для -provider static)") - staticPass := fs.String("static-pass", "", "TURN password (обязательно для -provider static)") - staticAddr := fs.String("static-addr", "", "TURN server host:port (обязательно для -provider static)") peerAddr := fs.String("peer", "", "адрес сервера VK TURN Proxy на VPS, host:port (обязательно)") n := fs.Int("n", 10, "количество параллельных TURN-потоков (соединений к TURN-реле)") transportFlag := fs.String("transport", "tcp", "транспорт до TURN-реле: tcp (TCP/TLS, default) | udp") @@ -180,11 +168,6 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) { StreamsPerCred: *streamsPerCredFlag, ManualCaptcha: *manualCaptchaFlag, }, - Static: StaticOpts{ - User: *staticUser, - Pass: *staticPass, - Addr: *staticAddr, - }, DNS: DNSOpts{ Mode: *dnsFlag, }, @@ -240,12 +223,8 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) { link = link[:idx] } c.VK.Link = link - case ProviderStatic: - if c.Static.User == "" || c.Static.Pass == "" || c.Static.Addr == "" { - return nil, errors.New("-provider static requires -static-user, -static-pass, -static-addr") - } default: - return nil, fmt.Errorf("invalid -provider value %q: must be %s | %s", c.Provider.Name, ProviderVK, ProviderStatic) + return nil, fmt.Errorf("invalid -provider value %q: must be %s", c.Provider.Name, ProviderVK) } key, err := srtpmimicry.DecodeKey(c.Obf.Mode, *wrapKeyHex) if err != nil { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 8d18705..ffa9a6f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -129,34 +129,6 @@ func TestParseClient_NClampedToTen(t *testing.T) { } } -func TestParseClient_ProviderStaticOK(t *testing.T) { - args := []string{ - "-peer", "1.2.3.4:5000", - "-provider", "static", - "-static-user", "u", - "-static-pass", "p", - "-static-addr", "turn.example.com:3478", - } - c, err := ParseClient(args, io.Discard) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - if c.Provider.Name != ProviderStatic { - t.Errorf("Provider.Name: %q (expected %q)", c.Provider.Name, ProviderStatic) - } - if c.Static.User != "u" || c.Static.Pass != "p" || c.Static.Addr != "turn.example.com:3478" { - t.Errorf("Static: %+v", c.Static) - } -} - -func TestParseClient_ProviderStaticMissingFlags(t *testing.T) { - args := []string{"-peer", "1.2.3.4:5000", "-provider", "static"} - _, err := ParseClient(args, io.Discard) - if err == nil || !strings.Contains(err.Error(), "static-user") { - t.Errorf("expected static flags error, got %v", err) - } -} - func TestParseClient_ProviderUnknown(t *testing.T) { args := []string{"-peer", "1.2.3.4:5000", "-provider", "bogus"} _, err := ParseClient(args, io.Discard) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c2d926a..81dea39 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,6 +1,6 @@ // Package provider определяет абстракцию источника TURN-реквизитов. // -// Реализации (internal/provider/vk, internal/provider/static, ...) поставляют +// Реализации (internal/provider/vk, ...) поставляют // короткоживущие user/pass/addr для TURN-allocate. Pipeline (proxy/udprelay, // proxy/tcpfwd) работает только через этот интерфейс — никаких VK-specific // типов в общем коде. @@ -51,7 +51,7 @@ type Provider interface { // Используется только при возврате ошибки, обёрнутой ErrBackoffActive. BackoffUntilUnix() int64 - // Name — короткое имя провайдера для логов ("vk", "static", "metered"). + // Name — короткое имя провайдера для логов ("vk", ...). Name() string } diff --git a/internal/provider/static/static.go b/internal/provider/static/static.go deleted file mode 100644 index 326a806..0000000 --- a/internal/provider/static/static.go +++ /dev/null @@ -1,72 +0,0 @@ -// Package static — провайдер фиксированных TURN-реквизитов из CLI-флагов. -// -// Используется для подключения к любому совместимому TURN-серверу (coturn, -// metered, self-hosted) с долгоживущими credentials. Без сетевого control-plane: -// никаких captcha, throttling, lockout. Проверяет, что абстракция provider.Provider -// действительно достаточна для не-VK источников. -package static - -import ( - "context" - "fmt" - "strings" - - "github.com/samosvalishe/btp/internal/provider" -) - -// Config — параметры static-провайдера. -type Config struct { - User string // TURN username (обязателен) - Pass string // TURN password (обязателен) - Addr string // TURN server host:port (обязателен) -} - -// Provider реализует provider.Provider, всегда возвращает одни и те же реквизиты. -type Provider struct { - creds provider.Credentials -} - -// New создаёт static-провайдер. Валидирует обязательные поля. -func New(cfg Config) (*Provider, error) { - if cfg.User == "" { - return nil, fmt.Errorf("static: empty User") - } - if cfg.Pass == "" { - return nil, fmt.Errorf("static: empty Pass") - } - if cfg.Addr == "" { - return nil, fmt.Errorf("static: empty Addr") - } - if !strings.Contains(cfg.Addr, ":") { - return nil, fmt.Errorf("static: Addr must be host:port, got %q", cfg.Addr) - } - return &Provider{ - creds: provider.Credentials{ - User: cfg.User, - Pass: cfg.Pass, - ServerAddr: cfg.Addr, - }, - }, nil -} - -// GetCredentials возвращает фиксированные реквизиты. -func (p *Provider) GetCredentials(_ context.Context, _ int) (provider.Credentials, error) { - return p.creds, nil -} - -// IsAuthError — для static-провайдера auth-ошибки не приводят к ротации -// реквизитов (их просто негде взять). Возвращаем false всегда: pipeline сам -// сделает retry с тем же кешем. -func (*Provider) IsAuthError(error) bool { return false } - -// HandleAuthError ничего не делает (нет кеша для инвалидации). -func (*Provider) HandleAuthError(int) bool { return false } - -// ResetErrors ничего не делает. -func (*Provider) ResetErrors(int) {} - -// BackoffUntilUnix всегда 0 — backoff не реализован. -func (*Provider) BackoffUntilUnix() int64 { return 0 } - -// Name реализует provider.Provider. -func (*Provider) Name() string { return "static" } diff --git a/internal/provider/static/static_test.go b/internal/provider/static/static_test.go deleted file mode 100644 index 53f0e10..0000000 --- a/internal/provider/static/static_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package static - -import ( - "context" - "errors" - "strings" - "testing" - - "github.com/samosvalishe/btp/internal/provider" -) - -func TestNew_Validation(t *testing.T) { - t.Parallel() - cases := []struct { - name string - cfg Config - want string - }{ - {"empty user", Config{Pass: "p", Addr: "h:1"}, "User"}, - {"empty pass", Config{User: "u", Addr: "h:1"}, "Pass"}, - {"empty addr", Config{User: "u", Pass: "p"}, "Addr"}, - {"addr without port", Config{User: "u", Pass: "p", Addr: "host"}, "host:port"}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - _, err := New(tc.cfg) - if err == nil || !strings.Contains(err.Error(), tc.want) { - t.Errorf("got %v, want substring %q", err, tc.want) - } - }) - } -} - -func TestProvider_GetCredentials(t *testing.T) { - t.Parallel() - p, err := New(Config{User: "alice", Pass: "secret", Addr: "turn.example.com:3478"}) - if err != nil { - t.Fatal(err) - } - got, err := p.GetCredentials(context.Background(), 0) - if err != nil { - t.Fatal(err) - } - want := provider.Credentials{User: "alice", Pass: "secret", ServerAddr: "turn.example.com:3478"} - if got != want { - t.Errorf("got %+v, want %+v", got, want) - } -} - -func TestProvider_InterfaceContract(t *testing.T) { - t.Parallel() - p, _ := New(Config{User: "u", Pass: "p", Addr: "h:1"}) - if p.IsAuthError(errors.New("anything")) { - t.Error("IsAuthError should always return false") - } - if p.HandleAuthError(0) { - t.Error("HandleAuthError should always return false") - } - p.ResetErrors(0) - if p.BackoffUntilUnix() != 0 { - t.Error("BackoffUntilUnix should always return 0") - } - if p.Name() != "static" { - t.Errorf("Name=%q, want static", p.Name()) - } - var _ provider.Provider = p -}