mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
refactor(provider): убрать static-провайдер, оставить абстракцию
Сейчас нужен только VK-провайдер. internal/provider/static и связанные флаги -static-user/-static-pass/-static-addr удалены, валидация и тесты обновлены. Интерфейс provider.Provider остаётся — слот для будущих реализаций сохранён в нейтральных формулировках doc-комментариев.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
+1
-4
@@ -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) |
|
||||
|
||||
+1
-2
@@ -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`.
|
||||
|
||||
|
||||
+2
-15
@@ -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).
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user