mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-26 07:08:11 +00:00
docs: update docs and remove wbstream room creation support
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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 комнаты.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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 комнаты.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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("{}")))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user