From 0ec244e0dcc7efb9560d3237520a8e237047523b Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Thu, 21 May 2026 18:51:38 +0300 Subject: [PATCH] docs: update docs and remove wbstream room creation support --- docs/about.md | 34 +++++++++--------- docs/configuration.md | 23 +++--------- docs/manual.md | 2 +- docs/settings.md | 30 +++------------- internal/app/session/session.go | 7 ++++ internal/app/session/session_test.go | 3 +- internal/auth/wbstream/api.go | 52 ++-------------------------- internal/auth/wbstream/api_test.go | 39 ++++++++------------- internal/auth/wbstream/wbstream.go | 28 +++------------ internal/e2e/tunnel_test.go | 10 ++---- pkg/olcrtc/olcrtc.go | 5 ++- 11 files changed, 60 insertions(+), 173 deletions(-) diff --git a/docs/about.md b/docs/about.md index 828f5ab..f9cddcc 100644 --- a/docs/about.md +++ b/docs/about.md @@ -1,6 +1,6 @@ # olcRTC - общее описание -`olcRTC` (OpenLibreCommunity RTC) - зашифрованный TCP-over-WebRTC туннель. Он маскирует трафик под обычное участие в WebRTC/SFU-сервисе: Jitsi Meet, Yandex Telemost или WB Stream. +`olcRTC` (OpenLibreCommunity RTC) - зашифрованный TCP-over-WebRTC туннель. Он маскирует трафик под обычное участие в WebRTC/SFU-сервисе: Jitsi Meet, Yandex Telemost или WbStream. Проект: [github.com/openlibrecommunity/olcrtc](https://github.com/openlibrecommunity/olcrtc) Лицензия: WTFPL @@ -8,22 +8,22 @@ ## Зачем это нужно -В сценариях, где прямой доступ к произвольному VPS нестабилен или заблокирован, полезно переносить трафик через сервисы, которые уже доступны у пользователя. Для внешнего наблюдателя соединение выглядит как обычный WebRTC-звонок с выбранным сервисом, а полезная нагрузка внутри дополнительно шифруется общим ключом `crypto.key`. +В сценариях, где прямой доступ к произвольному VPS / IP заблокирован, приходится переносить трафик через сервисы, которые уже доступны у пользователя. Для внешнего наблюдателя соединение выглядит как обычный WebRTC-звонок по разрешенному IP сервиса, а полезная нагрузка внутри дополнительно шифруется общим ключом `crypto.key`. Базовая схема: ```text приложение -> SOCKS5 127.0.0.1:8808 - -> olcrtc cnc - -> WebRTC/SFU сервис - -> olcrtc srv - -> интернет + -> olcrtc cnc + -> WebRTC/SFU сервис + -> olcrtc srv + -> интернет ``` ## Как это работает -Клиентский режим `cnc` поднимает локальный SOCKS5. Браузер, `curl`, sing-box или другое приложение подключается к нему как к обычному proxy. +Клиентский режим `cnc` поднимает локальный SOCKS5. Браузер, curl, sing-box, olcbox или другое приложение подключается к нему как к обычному proxy. Серверный режим `srv` подключается к той же комнате/сессии, принимает зашифрованный smux stream и от своего имени открывает TCP-соединения к целевым адресам. @@ -32,10 +32,10 @@ ```text SOCKS CONNECT -> smux stream - -> XChaCha20-Poly1305 - -> transport - -> engine - -> WebRTC/SFU + -> XChaCha20-Poly1305 + -> transport + -> engine + -> WebRTC/SFU ``` ## Режимы @@ -60,9 +60,9 @@ olcrtc client.yaml | Provider | Engine | Комментарий | |---|---|---| | `jitsi` | `jitsi` | URL комнаты Jitsi, без отдельной регистрации | -| `telemost` | `goolom` | credentials через Yandex Telemost API | -| `wbstream` | `livekit` | guest flow WB Stream, умеет создавать комнаты для `gen` | -| `none` | задаётся в `engine.name` | прямой engine-режим с `engine.url` и `engine.token` | +| `telemost` | `goolom` | credentials через Yandex Telemost API, с отдельной регистрацией | +| `wbstream` | `livekit` | credentials через WbBStream API, с отдельной регистрацией | +| `none` | задаётся в `engine.name` | прямой engine-режим с `engine.url` и `engine.token`, с отдельной регистрацией | Термин `carrier` ещё встречается во внутреннем API и логах как историческое имя для выбранного auth/provider пути. В YAML актуальное поле - `auth.provider`. @@ -72,9 +72,9 @@ olcrtc client.yaml | Engine | Пакет | Возможности | |---|---|---| -| `livekit` | `internal/engine/livekit` | data packets и video tracks через LiveKit SDK | +| `livekit` | `internal/engine/livekit` | data packets/video tracks/LiveKit SDK | | `goolom` | `internal/engine/goolom` | Telemost/Goolom signaling, publisher/subscriber PeerConnection | -| `jitsi` | `internal/engine/jitsi` | Jitsi MUC/Jingle/colibri-ws, datachannel-путь и best-effort video | +| `jitsi` | `internal/engine/jitsi` | Jitsi MUC/Jingle/colibri-ws, datachannel/best-effort video | `internal/engine/builtin` связывает `auth.provider` с нужным engine. Отдельного пакета `internal/carrier` в текущем проекте нет. @@ -86,7 +86,7 @@ olcrtc client.yaml |---|---|---| | `datachannel` | нативный byte/data path engine | самый простой и быстрый путь, стабильно с Jitsi | | `vp8channel` | KCP поверх VP8-like video frames | основной video-path для WB Stream и Telemost | -| `seichannel` | payload в H264 SEI NAL units, ACK/retry | fallback для WB Stream | +| `seichannel` | payload в H264 SEI NAL units, ACK/retry | fallback для WB Stream / Jitsi| | `videochannel` | QR/tile кадры через ffmpeg, ACK/retry | экспериментальный визуальный транспорт | Рекомендуемый старт: `jitsi + datachannel`. Альтернатива: `wbstream + vp8channel`. diff --git a/docs/configuration.md b/docs/configuration.md index c712649..98d96eb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -183,22 +183,7 @@ failover: ## mode: gen -`gen` создаёт Room ID заранее и печатает их в stdout. Сейчас это полезно прежде всего для `wbstream`, потому что его auth-провайдер реализует создание комнат. - -```yaml -mode: gen -auth: - provider: wbstream -crypto: - key: "REPLACE_ME_WITH_64_HEX_CHARS" -net: - transport: vp8channel - dns: "1.1.1.1:53" -gen: - amount: 3 -data: data -``` - -```bash -olcrtc gen.yaml -``` +`gen` оставлен для auth-провайдеров, которые реализуют создание комнат через API. +Текущие встроенные провайдеры (`jitsi`, `telemost`, `wbstream`) не создают комнаты +через `olcrtc`: для `telemost` и `wbstream` создай комнату на сайте сервиса и +вставь её в `room.id`; для `jitsi` укажи URL комнаты. diff --git a/docs/manual.md b/docs/manual.md index 027edf2..0f4818e 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -176,7 +176,7 @@ data: data ### wbstream + vp8channel (альтернатива) -Создай руму через сайт [wbstream](https://stream.wb.ru) или заранее сгенерируй ID через `mode: gen` с `auth.provider: wbstream`. +Создай руму через сайт [wbstream](https://stream.wb.ru) и вставь её ID в `room.id`. `wbstream + datachannel` **не работает** в обычном guest flow — WB Stream выдаёт токены с `canPublishData=false`, и DC не маршрутизирует данные. Для обычного использования выбирай `vp8channel`. diff --git a/docs/settings.md b/docs/settings.md index cfd4fac..3b820bb 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -96,33 +96,11 @@ transport. Используй одинаковые traffic-настройки н ## mode: gen -Генерирует Room ID заранее, не запуская сервер. Поддерживается для auth-провайдеров с автосозданием комнат: `wbstream`. Для `telemost` комнату нужно создавать вручную через сайт. +`gen` оставлен для auth-провайдеров, которые умеют создавать комнаты через API. +Сейчас встроенные провайдеры не поддерживают автосоздание комнат через `olcrtc`. -**Обязательные поля:** - -| YAML поле | Описание | -|-----------|----------| -| `auth.provider` | `wbstream` | -| `net.dns` | DNS-сервер | -| `gen.amount` | Количество комнат | - -```yaml -# gen.yaml -mode: gen -auth: - provider: wbstream -net: - dns: "1.1.1.1:53" -gen: - amount: 3 -``` - -```sh -./olcrtc gen.yaml -# room-id-1 -# room-id-2 -# room-id-3 -``` +Для `telemost` и `wbstream` создай комнату через сайт сервиса и вставь её ID в +`room.id`. Для `jitsi` укажи URL комнаты. --- diff --git a/internal/app/session/session.go b/internal/app/session/session.go index b66847e..6b2d8f4 100644 --- a/internal/app/session/session.go +++ b/internal/app/session/session.go @@ -757,6 +757,13 @@ func ValidateGen(cfg Config) error { if cfg.Amount < 1 { return ErrAmountRequired } + p, err := auth.Get(cfg.Auth) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnsupportedCarrier, cfg.Auth) + } + if _, ok := p.(auth.RoomCreator); !ok { + return fmt.Errorf("%w: %s does not support room generation", ErrUnsupportedCarrier, cfg.Auth) + } return nil } diff --git a/internal/app/session/session_test.go b/internal/app/session/session_test.go index ad92906..f03f9e8 100644 --- a/internal/app/session/session_test.go +++ b/internal/app/session/session_test.go @@ -576,8 +576,9 @@ func TestValidateGen(t *testing.T) { want error }{ { - name: "valid wbstream", + name: "wbstream room generation unsupported", cfg: Config{Auth: testAuthWBStream, DNSServer: "1.1.1.1:53", Amount: 3}, + want: ErrUnsupportedCarrier, }, { name: "missing auth", diff --git a/internal/auth/wbstream/api.go b/internal/auth/wbstream/api.go index 9e0a74a..238167e 100644 --- a/internal/auth/wbstream/api.go +++ b/internal/auth/wbstream/api.go @@ -1,7 +1,6 @@ // Package wbstream is the auth provider for the WB Stream service. It -// produces LiveKit credentials by registering a guest, optionally creating -// a room, joining it, and exchanging the guest access token for a room -// token. +// produces LiveKit credentials by registering a guest, joining an existing +// room, and exchanging the guest access token for a room token. package wbstream import ( @@ -21,7 +20,6 @@ var apiBase = "https://stream.wb.ru" //nolint:gochecknoglobals // package-level var ( errGuestRegister = errors.New("guest register failed") - errCreateRoom = errors.New("create room failed") errJoinRoom = errors.New("join room failed") errGetToken = errors.New("get token failed") ) @@ -40,15 +38,6 @@ type guestRegisterResponse struct { AccessToken string `json:"accessToken"` } -type createRoomRequest struct { - RoomType string `json:"roomType"` - RoomPrivacy string `json:"roomPrivacy"` -} - -type createRoomResponse struct { - RoomID string `json:"roomId"` -} - type tokenResponse struct { RoomToken string `json:"roomToken"` ServerURL string `json:"serverUrl"` @@ -93,43 +82,6 @@ func registerGuest(ctx context.Context, displayName string) (string, error) { return res.AccessToken, nil } -func createRoom(ctx context.Context, accessToken string) (string, error) { - u := apiBase + "/api-room/api/v2/room" - reqBody := createRoomRequest{ - RoomType: "ROOM_TYPE_ALL_ON_SCREEN", - RoomPrivacy: "ROOM_PRIVACY_FREE", - } - - body, err := json.Marshal(reqBody) - if err != nil { - return "", fmt.Errorf("marshal request body: %w", err) - } - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(body)) - if err != nil { - return "", fmt.Errorf("create request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+accessToken) - req.Header.Set("User-Agent", "Mozilla/5.0 (Linux x86_64)") - - client := protect.NewHTTPClient() - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("do request: %w", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - return "", fmt.Errorf("create room status: %w", protect.StatusError(errCreateRoom, resp, 4096)) - } - - var res createRoomResponse - if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { - return "", fmt.Errorf("decode response: %w", err) - } - return res.RoomID, nil -} - func joinRoom(ctx context.Context, accessToken, roomID string) error { u := fmt.Sprintf("%s/api-room/api/v1/room/%s/join", apiBase, roomID) req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader([]byte("{}"))) diff --git a/internal/auth/wbstream/api_test.go b/internal/auth/wbstream/api_test.go index 2b06f88..530bfb9 100644 --- a/internal/auth/wbstream/api_test.go +++ b/internal/auth/wbstream/api_test.go @@ -34,13 +34,6 @@ func TestWBStreamAPIHappyPath(t *testing.T) { mux.HandleFunc("POST /auth/api/v1/auth/user/guest-register", func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(guestRegisterResponse{AccessToken: testAccessToken}) //nolint:gosec }) - mux.HandleFunc("POST /api-room/api/v2/room", func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") != "Bearer "+testAccessToken { - t.Fatalf("room auth = %q", r.Header.Get("Authorization")) - } - w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(createRoomResponse{RoomID: testRoomID}) - }) mux.HandleFunc("POST /api-room/api/v1/room/"+testRoomID+"/join", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) @@ -62,18 +55,10 @@ func TestWBStreamAPIHappyPath(t *testing.T) { t.Fatalf("registerGuest() = %q", access) } - room, err := createRoom(context.Background(), access) - if err != nil { - t.Fatalf("createRoom() error = %v", err) - } - if room != testRoomID { - t.Fatalf("createRoom() = %q", room) - } - - if err := joinRoom(context.Background(), access, room); err != nil { + if err := joinRoom(context.Background(), access, testRoomID); err != nil { t.Fatalf("joinRoom() error = %v", err) } - tok, err := getToken(context.Background(), access, room, testPeerName) + tok, err := getToken(context.Background(), access, testRoomID, testPeerName) if err != nil { t.Fatalf("getToken() error = %v", err) } @@ -90,9 +75,6 @@ func TestWBStreamAPIErrors(t *testing.T) { if _, err := registerGuest(context.Background(), testPeerName); !errors.Is(err, errGuestRegister) { t.Fatalf("registerGuest() error = %v, want %v", err, errGuestRegister) } - if _, err := createRoom(context.Background(), testAccessToken); !errors.Is(err, errCreateRoom) { - t.Fatalf("createRoom() error = %v, want %v", err, errCreateRoom) - } if err := joinRoom(context.Background(), testAccessToken, testRoomID); !errors.Is(err, errJoinRoom) { t.Fatalf("joinRoom() error = %v, want %v", err, errJoinRoom) } @@ -106,9 +88,6 @@ func TestWBStreamIssue(t *testing.T) { mux.HandleFunc("POST /auth/api/v1/auth/user/guest-register", func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(guestRegisterResponse{AccessToken: testAccessToken}) //nolint:gosec }) - mux.HandleFunc("POST /api-room/api/v2/room", func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(createRoomResponse{RoomID: "created"}) - }) mux.HandleFunc("POST /api-room/api/v1/room/{id}/join", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) @@ -120,7 +99,7 @@ func TestWBStreamIssue(t *testing.T) { p := Provider{} creds, err := p.Issue(context.Background(), auth.Config{ - RoomURL: "any", + RoomURL: testRoomID, Name: testPeerName, }) if err != nil { @@ -129,7 +108,17 @@ func TestWBStreamIssue(t *testing.T) { if creds.Token != testToken { t.Fatalf("creds.Token = %q", creds.Token) } - if creds.Extra["roomID"] != "created" { + if creds.Extra["roomID"] != testRoomID { t.Fatalf("creds.Extra[roomID] = %q", creds.Extra["roomID"]) } } + +func TestWBStreamIssueRequiresRoom(t *testing.T) { + p := Provider{} + for _, roomURL := range []string{"", "any"} { + _, err := p.Issue(context.Background(), auth.Config{RoomURL: roomURL, Name: testPeerName}) + if !errors.Is(err, auth.ErrRoomIDRequired) { + t.Fatalf("Issue(RoomURL=%q) error = %v, want %v", roomURL, err, auth.ErrRoomIDRequired) + } + } +} diff --git a/internal/auth/wbstream/wbstream.go b/internal/auth/wbstream/wbstream.go index f27f89f..21d039e 100644 --- a/internal/auth/wbstream/wbstream.go +++ b/internal/auth/wbstream/wbstream.go @@ -17,23 +17,17 @@ func (Provider) Engine() string { return "livekit" } func (Provider) DefaultServiceURL() string { return "https://stream.wb.ru" } // Issue runs the WB Stream auth flow and returns LiveKit credentials. -// -// If cfg.RoomURL is empty or "any", a fresh room is created on the fly — -// keeping the behaviour the legacy wbstream provider had. func (Provider) Issue(ctx context.Context, cfg auth.Config) (auth.Credentials, error) { + if cfg.RoomURL == "" || cfg.RoomURL == "any" { + return auth.Credentials{}, auth.ErrRoomIDRequired + } + accessToken, err := registerGuest(ctx, cfg.Name) if err != nil { return auth.Credentials{}, fmt.Errorf("register guest: %w", err) } roomID := cfg.RoomURL - if roomID == "" || roomID == "any" { - roomID, err = createRoom(ctx, accessToken) - if err != nil { - return auth.Credentials{}, fmt.Errorf("create room: %w", err) - } - } - if err := joinRoom(ctx, accessToken, roomID); err != nil { return auth.Credentials{}, fmt.Errorf("join room: %w", err) } @@ -55,20 +49,6 @@ func (Provider) Issue(ctx context.Context, cfg auth.Config) (auth.Credentials, e }, nil } -// CreateRoom registers a temporary guest and creates a WB Stream room. -// Used by gen mode. -func (Provider) CreateRoom(ctx context.Context, cfg auth.Config) (string, error) { - accessToken, err := registerGuest(ctx, cfg.Name) - if err != nil { - return "", fmt.Errorf("register guest: %w", err) - } - roomID, err := createRoom(ctx, accessToken) - if err != nil { - return "", fmt.Errorf("create room: %w", err) - } - return roomID, nil -} - func init() { //nolint:gochecknoinits // auth registration is the canonical Go pattern for plugins auth.Register("wbstream", Provider{}) } diff --git a/internal/e2e/tunnel_test.go b/internal/e2e/tunnel_test.go index c5f9d6f..5800652 100644 --- a/internal/e2e/tunnel_test.go +++ b/internal/e2e/tunnel_test.go @@ -20,8 +20,6 @@ import ( "time" "github.com/openlibrecommunity/olcrtc/internal/app/session" - "github.com/openlibrecommunity/olcrtc/internal/auth" - authWBStream "github.com/openlibrecommunity/olcrtc/internal/auth/wbstream" "github.com/openlibrecommunity/olcrtc/internal/client" "github.com/openlibrecommunity/olcrtc/internal/engine" enginebuiltin "github.com/openlibrecommunity/olcrtc/internal/engine/builtin" @@ -526,11 +524,9 @@ func realRoomURL(ctx context.Context, t *testing.T, carrierName string) string { if *realE2EWBStreamRoom != "" { return *realE2EWBStreamRoom } - room, err := authWBStream.Provider{}.CreateRoom(ctx, auth.Config{Name: "olcrtc-e2e-room"}) - if err != nil { - t.Skipf("skip wbstream real e2e: create room failed: %v", err) - } - return room + _ = ctx + t.Skip("skip wbstream real e2e: set -olcrtc.real-wbstream-room to an existing room ID") + return "" case "jitsi": // Jitsi has no notion of "creating" a room — names are conjured // on first join. The default flag points at meet.small-dm.ru diff --git a/pkg/olcrtc/olcrtc.go b/pkg/olcrtc/olcrtc.go index eea88eb..16307ae 100644 --- a/pkg/olcrtc/olcrtc.go +++ b/pkg/olcrtc/olcrtc.go @@ -240,9 +240,8 @@ func (s *Session) SetShouldReconnect(fn func() bool) { } // CreateRoom creates a new room via the auth provider and returns the room ID. -// Only works when the session was created with Auth set to a provider that -// supports room creation (wbstream). Returns [ErrRoomCreationUnsupported] -// for providers that don't support it (e.g. telemost). +// Only works when Auth names a provider that supports room creation. Built-in +// providers currently return [ErrRoomCreationUnsupported]. func CreateRoom(ctx context.Context, authName string) (string, error) { p, err := auth.Get(authName) if err != nil {