docs: update docs and remove wbstream room creation support

This commit is contained in:
zarazaex69
2026-05-21 18:51:38 +03:00
parent c3fb28cf83
commit 0ec244e0dc
11 changed files with 60 additions and 173 deletions

View File

@@ -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,7 +8,7 @@
## Зачем это нужно
В сценариях, где прямой доступ к произвольному VPS нестабилен или заблокирован, полезно переносить трафик через сервисы, которые уже доступны у пользователя. Для внешнего наблюдателя соединение выглядит как обычный WebRTC-звонок с выбранным сервисом, а полезная нагрузка внутри дополнительно шифруется общим ключом `crypto.key`.
В сценариях, где прямой доступ к произвольному VPS / IP заблокирован, приходится переносить трафик через сервисы, которые уже доступны у пользователя. Для внешнего наблюдателя соединение выглядит как обычный WebRTC-звонок по разрешенному IP сервиса, а полезная нагрузка внутри дополнительно шифруется общим ключом `crypto.key`.
Базовая схема:
@@ -23,7 +23,7 @@
## Как это работает
Клиентский режим `cnc` поднимает локальный SOCKS5. Браузер, `curl`, sing-box или другое приложение подключается к нему как к обычному proxy.
Клиентский режим `cnc` поднимает локальный SOCKS5. Браузер, curl, sing-box, olcbox или другое приложение подключается к нему как к обычному proxy.
Серверный режим `srv` подключается к той же комнате/сессии, принимает зашифрованный smux stream и от своего имени открывает TCP-соединения к целевым адресам.
@@ -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`.

View File

@@ -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 комнаты.

View File

@@ -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`.

View File

@@ -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 комнаты.
---

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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("{}")))

View File

@@ -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)
}
}
}

View File

@@ -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{})
}

View File

@@ -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

View File

@@ -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 {