inbound

package
v1.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package inbound represents a single configured "tunnel into a Telemost meeting" on the goloom-wg-server. Each Inbound owns:

  • one Telemost session (its own peer in the call)
  • one Sender for outbound VP8 frames + receivers for incoming
  • one wgrelay.WGCreator that bridges the tunnel to a local WG endpoint

Multiple Inbounds run independently in goroutines; the Manager (manager.go) supervises them so a failure in one doesn't take down the others.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type History

type History struct {
	// contains filtered or unexported fields
}

History stores per-inbound counter samples in a fixed ring buffer. One global instance lives on the Manager; on every stat tick each runner contributes its current TX/RX byte totals.

func NewHistory

func NewHistory(capPerInbound int) *History

func (*History) Drop

func (h *History) Drop(id string)

Drop forgets a removed inbound's history.

func (*History) Record

func (h *History) Record(id string, tx, rx uint64)

Record appends one sample for the given inbound. If the ring is at capacity the oldest entry is evicted.

func (*History) Snapshot

func (h *History) Snapshot(id string) []HistorySample

Snapshot returns a copy of the ring for the given inbound, or nil if nothing has been recorded yet.

type HistorySample

type HistorySample struct {
	At      int64  `json:"t"`  // unix seconds
	TxBytes uint64 `json:"tx"` // total since boot
	RxBytes uint64 `json:"rx"`
}

HistorySample is one bucket in the per-inbound throughput history. The admin panel renders these as sparklines, so we keep a small fixed-size ring (~5 minutes at 1s resolution) — enough to spot bursts without needing a real time-series store.

type LiveKitSpec

type LiveKitSpec struct {
	// RoomURL — public room link, e.g. https://stream.wb.ru/room/<id>.
	RoomURL string `yaml:"room_url" json:"room_url"`

	// AccessToken — guest user's long-lived JWT extracted from
	// localStorage.wb_auth_auth_slice.accessToken in the webview.
	// Persisted because re-issuing it requires another webview pass
	// through Cloudflare.
	AccessToken string `yaml:"access_token" json:"-"`

	// Cookies — joined Cookie header containing _wbafp / x_wbaas_token
	// / _wbauid. These are what Cloudflare actually checks; expiry is
	// about 14 days for x_wbaas_token, 1 year for _wbauid.
	Cookies string `yaml:"cookies" json:"-"`

	// CookiesExpireAt — earliest expiry among the captured cookies.
	// Admin UI surfaces this so operators can re-auth before runs go
	// dark. RFC3339-formatted in JSON.
	CookiesExpireAt time.Time `yaml:"cookies_expire_at,omitempty" json:"cookies_expire_at,omitempty"`
}

LiveKitSpec stores the long-lived credentials needed to mint short-lived LiveKit roomTokens at Connect time. Captured by the admin webview-auth flow; cookies expire roughly every 14 days at which point the operator must re-auth in the admin UI.

Mirror of github.com/Pinnss/goloom-server/internal/sfu.LiveKitWBStreamConnect with persistence-friendly tags.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager supervises a set of Runners, retrying failed ones with backoff and exposing CRUD operations the admin panel calls into.

func NewManager

func NewManager(lg *log.Logger) *Manager

func (*Manager) Add

func (m *Manager) Add(ctx context.Context, spec Spec) error

Add registers a new inbound and starts it (if Enabled). Returns an error if the ID is already in use.

The ctx parameter is intentionally ignored for goroutine lifetime — supervisors inherit from m.rootCtx (set by Start()) so an inbound created via the admin panel doesn't die when the HTTP response is flushed. ctx is reserved for future synchronous work that should be caller-cancellable.

func (*Manager) Get

func (m *Manager) Get(id string) (Spec, bool)

Get returns the runner spec for inspection. Panel routes use this to render config/QR/etc.

func (*Manager) History

func (m *Manager) History() *History

History exposes the throughput history store for the admin panel.

func (*Manager) List

func (m *Manager) List() []Spec

List returns specs sorted by tag for stable rendering.

func (*Manager) Remove

func (m *Manager) Remove(id string) error

Remove stops and unregisters the inbound. Idempotent.

func (*Manager) SetCaptchaBroker

func (m *Manager) SetCaptchaBroker(b vkauth.AdminCaptchaBroker)

SetCaptchaBroker installs the broker used by VK Calls inbounds running in captcha_mode=admin-webview. Pass nil to disable admin-webview solving (auto/none modes still work).

Безопасно вызывать после mgr.Add — broker пропагируется во все уже существующие runners. Раньше этот сеттер только писал в поле Manager'а, и runners созданные до его вызова навсегда оставались с nil broker'ом — приходилось тоглить инбаунды через админку чтобы «подхватить».

func (*Manager) SetEnabled

func (m *Manager) SetEnabled(ctx context.Context, id string, enabled bool) error

SetEnabled toggles the inbound's running state. If disabling a running inbound, stops it; if enabling a stopped one, starts it.

func (*Manager) SetOnChange

func (m *Manager) SetOnChange(fn func())

SetOnChange registers a callback fired after every Add/Remove/Toggle. Useful for persisting state to disk.

func (*Manager) SetVKProfileStore

func (m *Manager) SetVKProfileStore(s *vkauth.ProfileStore)

SetVKProfileStore installs the browser-FP pool used by VK Calls inbounds for auto-replay (S1c). Когда задан — captcha решается автоматически из пула, при провале — фоллбэк на interactive solver. Pass nil to disable auto-replay. Пропагируется в существующие runners (см. [SetCaptchaBroker] про rationale).

func (*Manager) Start

func (m *Manager) Start(ctx context.Context)

Start binds the manager to a long-lived context. All inbound runners spawned afterwards inherit from this context. Call once at process startup, before adding any inbounds.

func (*Manager) Statuses

func (m *Manager) Statuses() []Status

Statuses returns live statuses sorted by tag.

func (*Manager) StopAll

func (m *Manager) StopAll()

StopAll cancels every running inbound and waits for them to exit. Called at server shutdown.

type Runner

type Runner struct {
	Spec   Spec
	Logger *log.Logger
	// contains filtered or unexported fields
}

Runner owns the lifecycle of a single inbound: SFU session, wgrelay bridge, and the live-status surface used by the admin panel. Run blocks until the context is cancelled or an unrecoverable error occurs; the Manager retries with backoff between calls.

Multi-transport: Runner is transport-agnostic; the actual SFU stack (Telemost vs LiveKit/WB-Stream) is hidden behind sfu.Transport, picked from Spec.Transport at Run time.

func NewRunner

func NewRunner(spec Spec, lg *log.Logger) *Runner

func (*Runner) Run

func (r *Runner) Run(ctx context.Context) error

Run executes one full attempt at standing up the inbound. Dispatch is by transport family:

  • SFU family (telemost / vk-calls / livekit-wb-stream / "") → [Runner.runSFU]: dial out via sfu.Transport, bridge through wgrelay to WGEndpoint
  • Relay family (vk-turn) → [Runner.runRelay]: bind a listener via relay.Relay, forward to WGEndpoint, supervise until ctx cancelled

The two share Phase/Error/Status book-keeping but are otherwise completely independent — the SFU code path stays untouched.

func (*Runner) SetCaptchaBroker

func (r *Runner) SetCaptchaBroker(b vkauth.AdminCaptchaBroker)

SetCaptchaBroker swaps in the admin captcha broker used when VKCalls.CaptchaMode=="admin-webview". nil clears it.

func (*Runner) SetVKProfileStore

func (r *Runner) SetVKProfileStore(s *vkauth.ProfileStore)

SetVKProfileStore enables auto-replay для VK captcha (см. S1c). Pass nil чтобы отключить — runner вернётся к interactive-only.

func (*Runner) Status

func (r *Runner) Status() Status

Status returns the current state for the admin panel.

type Spec

type Spec struct {
	ID          string `yaml:"id" json:"id"`
	Tag         string `yaml:"tag" json:"tag"`
	Meeting     string `yaml:"meeting" json:"meeting"`
	DisplayName string `yaml:"display_name" json:"display_name"`

	// Transport selects the SFU/transport implementation to use.
	// Empty string is treated as "telemost" for backward compatibility
	// with pre-multi-transport configs. Valid values come from
	// [github.com/Pinnss/goloom-server/internal/sfu].Kind.
	Transport string `yaml:"transport,omitempty" json:"transport,omitempty"`

	// PoolSize, when >1, runs the SFU session inside an N-member pool —
	// N parallel Telemost room participants share one logical inbound,
	// each publishing on their own track. Yandex Telemost caps each
	// publisher at ~3 Mbps, so N pool members give an ~N×3 Mbps download
	// budget on the subscriber side. Only meaningful for SFU-family
	// transports that tunnel through the SFU's media plane (currently
	// only Telemost). 0 and 1 both fall back to legacy single-instance
	// behaviour. Client side must match (encoded in the connection
	// string as p=N).
	PoolSize int `yaml:"pool_size,omitempty" json:"pool_size,omitempty"`

	// LiveKit holds extra credentials for transport=livekit-wb-stream.
	// Populated by the admin webview-auth flow; ignored otherwise.
	LiveKit *LiveKitSpec `yaml:"livekit,omitempty" json:"livekit,omitempty"`

	// VKCalls holds extra knobs for transport=vk-calls. Ignored
	// otherwise.
	VKCalls *VKCallsSpec `yaml:"vk_calls,omitempty" json:"vk_calls,omitempty"`

	// VKTurn holds extra knobs for transport=vk-turn. Ignored
	// otherwise. This transport is a listener (not an SFU client) —
	// see [github.com/Pinnss/goloom-server/internal/relay/vkturn].
	VKTurn *VKTurnSpec `yaml:"vk_turn,omitempty" json:"vk_turn,omitempty"`

	// WGEndpoint is the local UDP address the relay forwards decrypted
	// tunnel frames to (typically 127.0.0.1:51820 for wg0, +1 for wg1, etc.).
	WGEndpoint string `yaml:"wg_endpoint" json:"wg_endpoint"`

	// WGInterface is the wireguard interface name this inbound is bound to.
	// Empty if the operator manages WG out-of-band; populated when the admin
	// panel provisioned the interface itself.
	WGInterface string `yaml:"wg_iface,omitempty" json:"wg_iface,omitempty"`

	// WGSubnet is the /24 the WG interface owns (e.g. "10.66.66.0/24").
	// Stored so the Manager can free it when the inbound is removed.
	WGSubnet string `yaml:"wg_subnet,omitempty" json:"wg_subnet,omitempty"`

	// ClientWGPrivateKey is the client-side WG private key, kept here so
	// the panel can re-render the client config / QR on demand without
	// asking the operator to upload it again. Server pubkey is derived.
	ClientWGPrivateKey string `yaml:"client_wg_private_key,omitempty" json:"-"`
	ClientWGPublicKey  string `yaml:"client_wg_public_key,omitempty" json:"client_wg_public_key,omitempty"`
	ServerWGPrivateKey string `yaml:"server_wg_private_key,omitempty" json:"-"`
	ServerWGPublicKey  string `yaml:"server_wg_public_key,omitempty" json:"server_wg_public_key,omitempty"`

	Enabled bool `yaml:"enabled" json:"enabled"`

	CreatedAt time.Time `yaml:"created_at" json:"created_at"`
}

Spec is the persistent description of an inbound — what gets serialised to the server's YAML / JSON state.

type Status

type Status struct {
	ID         string    `json:"id"`
	Tag        string    `json:"tag"`
	Enabled    bool      `json:"enabled"`
	Running    bool      `json:"running"`
	Phase      string    `json:"phase"` // "starting" | "waiting_peer" | "handshaking" | "relaying" | "stopped" | "error"
	LastError  string    `json:"last_error,omitempty"`
	Meeting    string    `json:"meeting"`
	WGEndpoint string    `json:"wg_endpoint"`
	WGIface    string    `json:"wg_iface,omitempty"`
	StartedAt  time.Time `json:"started_at,omitempty"`

	TxPackets uint64 `json:"tx_packets"`
	TxBytes   uint64 `json:"tx_bytes"`
	RxPackets uint64 `json:"rx_packets"`
	RxBytes   uint64 `json:"rx_bytes"`

	// Relay-specific counters — non-zero only when Spec.Transport
	// addresses the [github.com/Pinnss/goloom-server/internal/relay]
	// family (currently just vk-turn). SFU transports leave these at 0.
	RelayActive   uint64 `json:"relay_active,omitempty"`
	RelayAccepted uint64 `json:"relay_accepted,omitempty"`
	RelayListen   string `json:"relay_listen,omitempty"`
}

Status is the live snapshot the admin panel renders. Not persisted.

type VKCallsSpec

type VKCallsSpec struct {
	// MeetingURL — full https://vk.com/call/join/<id> link or just
	// the bare <id> short string.
	MeetingURL string `yaml:"meeting_url,omitempty" json:"meeting_url,omitempty"`

	// Role — "receiver" (default; server joins first, waits) or
	// "caller" (server joins second, drives the offer). Should
	// almost always be "receiver" for an inbound.
	Role string `yaml:"role,omitempty" json:"role,omitempty"`

	// CaptchaMode picks the runtime captcha solver:
	//
	//   - "auto"          (default) — open the operator's default
	//                                 browser via a local reverse-
	//                                 proxy. Needs a desktop session
	//                                 on the server box; fine for
	//                                 dev/laptop deploys.
	//   - "none"          — no solver; the inbound fails fast on a
	//                       captcha challenge. Useful when you
	//                       expect the call link to bypass captcha
	//                       (e.g. cached IP-bound exemptions).
	//   - "admin-webview" — admin panel proxies the captcha to a
	//                       connected admin browser via the
	//                       CaptchaBroker (default for headless VPS).
	//
	// Empty defaults to "auto".
	CaptchaMode string `yaml:"captcha_mode,omitempty" json:"captcha_mode,omitempty"`

	// Codec выбирает video transport stack — см. VKCallsConnect.Codec.
	// Допустимые значения: "h264" / "" (default, RS I_PCM grid) или
	// "vp8" (Telemost-стек). Эксперимент S5: VP8 даёт целевую
	// throughput ~30+ Mbit/s vs ~600 Kbit/s на H.264.
	Codec string `yaml:"codec,omitempty" json:"codec,omitempty"`
}

VKCallsSpec persists the VK-Calls-specific knobs for one inbound.

Mirror of github.com/Pinnss/goloom-server/internal/sfu.VKCallsConnect minus the CaptchaSolver — the solver is wired at runtime by the runner (different deploy modes need different solvers; see the "captcha_mode" field).

MeetingURL parallels Spec.Meeting (same URL slot, just kept here too for symmetry with LiveKitSpec.RoomURL). When both Spec.Meeting and Spec.VKCalls.MeetingURL are set, MeetingURL wins.

type VKTurnSpec added in v1.0.2

type VKTurnSpec struct {
	// ListenAddr — public UDP endpoint the relay binds, e.g.
	// "0.0.0.0:56001". Must be unique across vk-turn inbounds on the
	// same host (otherwise the second Start fails with EADDRINUSE).
	ListenAddr string `yaml:"listen_addr" json:"listen_addr"`

	// VKLink — VK call URL the client side references when registering
	// VK TURN credentials via captcha (https://vk.com/call/join/<id>).
	// Not used by the listener itself — surfaced in the admin's client
	// connection-link generator only.
	VKLink string `yaml:"vk_link,omitempty" json:"vk_link,omitempty"`

	// UseWrap toggles the ChaCha20-XOR obfuscation layer (symmetric to
	// the client's `-wrap` flag). When true, WrapKeyHex must be 64 hex
	// characters (32 bytes).
	UseWrap bool `yaml:"use_wrap,omitempty" json:"use_wrap,omitempty"`

	// WrapKeyHex — 64-char hex-encoded 32-byte shared key for WRAP.
	// Auto-rolled at create-time by the admin form when UseWrap is on.
	WrapKeyHex string `yaml:"wrap_key_hex,omitempty" json:"-"`

	// PresharedKey — 32-byte base64 WG preshared key that the
	// auto-provisioned WG interface uses for this inbound's peer.
	// anton48 / Moroka8 client validates that the connection-link
	// includes a non-empty presharedKey, so vk-turn inbounds always
	// generate one. Server side bakes it into the peer section of
	// /etc/wireguard/<iface>.conf via wgprovision.
	PresharedKey string `yaml:"preshared_key,omitempty" json:"-"`
}

VKTurnSpec persists the VK TURN relay-specific knobs for one inbound.

Unlike SFU transports, this is purely server-side: the relay listens on ListenAddr and forwards decrypted UDP payload to Spec.WGEndpoint. VKLink is not consumed by the server — it's stored only so the admin panel can render an end-user connection link/QR for third-party clients (anton48/Moroka8) that still need a VK call URL to anchor their TURN credential request.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL