feat(pr8): Merge pull request #8 from qtozdec/master

This commit is contained in:
zarazaex
2026-04-11 20:23:22 +03:00
committed by GitHub
16 changed files with 3370 additions and 563 deletions

18
.dockerignore Normal file
View File

@@ -0,0 +1,18 @@
.git
.gitignore
Dockerfile
docker-compose*.yml
code/
doc/
asset/
*.log
*.tmp
tmp/
olcrtc
build/
dist/
coverage.out

54
Dockerfile Normal file
View File

@@ -0,0 +1,54 @@
# syntax=docker/dockerfile:1.7
ARG GO_VERSION=1.25
ARG ALPINE_VERSION=3.22
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build
WORKDIR /src
RUN apk add --no-cache ca-certificates git
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETOS=linux
ARG TARGETARCH=amd64
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags="-s -w" -o /out/olcrtc ./cmd/olcrtc
FROM alpine:${ALPINE_VERSION} AS runtime
RUN apk add --no-cache ca-certificates tzdata && \
addgroup -S olcrtc && \
mkdir -p /usr/share/olcrtc /var/lib/olcrtc && \
adduser -S -D -h /var/lib/olcrtc -s /sbin/nologin -G olcrtc olcrtc && \
chown -R olcrtc:olcrtc /var/lib/olcrtc
COPY --from=build /out/olcrtc /usr/local/bin/olcrtc
COPY script/docker/olcrtc-entrypoint.sh /usr/local/bin/olcrtc-entrypoint
COPY script/docker/olcrtc-healthcheck.sh /usr/local/bin/olcrtc-healthcheck
RUN chmod 0755 /usr/local/bin/olcrtc /usr/local/bin/olcrtc-entrypoint /usr/local/bin/olcrtc-healthcheck
USER olcrtc:olcrtc
WORKDIR /var/lib/olcrtc
ENV OLCRTC_MODE=srv \
OLCRTC_PROVIDER=telemost \
OLCRTC_DATA_DIR=/usr/share/olcrtc \
OLCRTC_DNS=1.1.1.1:53 \
OLCRTC_KEY_FILE=/var/lib/olcrtc/key.hex
VOLUME ["/var/lib/olcrtc"]
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD ["/usr/local/bin/olcrtc-healthcheck"]
ENTRYPOINT ["/usr/local/bin/olcrtc-entrypoint"]

View File

@@ -1,8 +1,11 @@
// Package main provides the olcrtc CLI entrypoint.
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
@@ -16,68 +19,51 @@ import (
"github.com/openlibrecommunity/olcrtc/internal/server"
)
type config struct {
mode string
roomID string
provider string
socksPort int
socksHost string
keyHex string
debug bool
dataDir string
duo bool
dnsServer string
socksProxyAddr string
socksProxyPort int
}
var (
errUnsupportedProvider = errors.New("only telemost provider supported")
errRoomIDRequired = errors.New("room ID required")
errModeRequired = errors.New("specify -mode srv or -mode cnc")
)
func main() {
var (
mode string
roomID string
provider string
socksPort int
keyHex string
debug bool
dataDir string
duo bool
dnsServer string
socksProxyAddr string
socksProxyPort int
)
if err := run(); err != nil {
log.Print(err)
os.Exit(1)
}
}
flag.StringVar(&mode, "mode", "", "Mode: srv or cnc")
flag.StringVar(&roomID, "id", "", "Telemost room ID")
flag.StringVar(&provider, "provider", "telemost", "Provider (telemost only)")
flag.IntVar(&socksPort, "socks-port", 1080, "SOCKS5 port (client only)")
flag.StringVar(&keyHex, "key", "", "Shared encryption key (hex)")
flag.BoolVar(&debug, "debug", false, "Enable verbose logging")
flag.StringVar(&dataDir, "data", "data", "Path to data directory")
flag.BoolVar(&duo, "duo", false, "Use dual channels for 2x throughput")
flag.StringVar(&dnsServer, "dns", "1.1.1.1:53", "DNS server (default: Cloudflare 1.1.1.1)")
flag.StringVar(&socksProxyAddr, "socks-proxy", "", "SOCKS5 proxy address (server only)")
flag.IntVar(&socksProxyPort, "socks-proxy-port", 1080, "SOCKS5 proxy port (server only)")
flag.Parse()
func run() error {
cfg := parseFlags()
configureLogging(cfg.debug)
if debug {
log.SetFlags(log.Ltime | log.Lshortfile)
logger.SetVerbose(true)
} else {
log.SetFlags(log.Ltime)
if err := validateConfig(cfg); err != nil {
return err
}
if provider != "telemost" {
log.Fatal("Only telemost provider supported")
dataDir, err := resolveDataDir(cfg.dataDir)
if err != nil {
return err
}
if roomID == "" {
log.Fatal("Room ID required")
if err := loadNames(dataDir); err != nil {
return err
}
if mode != "srv" && mode != "cnc" {
log.Fatal("Specify -mode srv or -mode cnc")
}
if !filepath.IsAbs(dataDir) {
exePath, err := os.Executable()
if err == nil {
exeDir := filepath.Dir(exePath)
dataDir = filepath.Join(exeDir, dataDir)
}
}
namesPath := filepath.Join(dataDir, "names")
surnamesPath := filepath.Join(dataDir, "surnames")
names.LoadNameFiles(namesPath, surnamesPath)
roomURL := "https://telemost.yandex.ru/j/" + roomID
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -85,36 +71,126 @@ func main() {
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
errCh := make(chan error, 1)
go func() {
switch mode {
case "srv":
errCh <- server.Run(ctx, roomURL, keyHex, duo, dnsServer, socksProxyAddr, socksProxyPort)
case "cnc":
errCh <- client.Run(ctx, roomURL, keyHex, socksPort, duo)
}
}()
go runMode(ctx, cfg, errCh)
select {
case <-sigCh:
log.Println("Shutting down gracefully...")
cancel()
done := make(chan struct{})
go func() {
<-errCh
close(done)
}()
select {
case <-done:
log.Println("Shutdown complete")
case <-time.After(5 * time.Second):
log.Println("Shutdown timeout, forcing exit")
}
return waitForShutdown(errCh)
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
return err
}
}
func parseFlags() config {
cfg := config{}
flag.StringVar(&cfg.mode, "mode", "", "Mode: srv or cnc")
flag.StringVar(&cfg.roomID, "id", "", "Telemost room ID")
flag.StringVar(&cfg.provider, "provider", "telemost", "Provider (telemost only)")
flag.IntVar(&cfg.socksPort, "socks-port", 1080, "SOCKS5 port (client only)")
flag.StringVar(&cfg.socksHost, "socks-host", "127.0.0.1", "SOCKS5 listen host (client only)")
flag.StringVar(&cfg.keyHex, "key", "", "Shared encryption key (hex)")
flag.BoolVar(&cfg.debug, "debug", false, "Enable verbose logging")
flag.StringVar(&cfg.dataDir, "data", "data", "Path to data directory")
flag.BoolVar(&cfg.duo, "duo", false, "Use dual channels for 2x throughput")
flag.StringVar(&cfg.dnsServer, "dns", "1.1.1.1:53", "DNS server (default: Cloudflare 1.1.1.1)")
flag.StringVar(&cfg.socksProxyAddr, "socks-proxy", "", "SOCKS5 proxy address (server only)")
flag.IntVar(&cfg.socksProxyPort, "socks-proxy-port", 1080, "SOCKS5 proxy port (server only)")
flag.Parse()
return cfg
}
func configureLogging(debug bool) {
if debug {
log.SetFlags(log.Ltime | log.Lshortfile)
logger.SetVerbose(true)
return
}
log.SetFlags(log.Ltime)
}
func validateConfig(cfg config) error {
switch {
case cfg.provider != "telemost":
return errUnsupportedProvider
case cfg.roomID == "":
return errRoomIDRequired
case cfg.mode != "srv" && cfg.mode != "cnc":
return errModeRequired
default:
return nil
}
}
func resolveDataDir(dataDir string) (string, error) {
if filepath.IsAbs(dataDir) {
return dataDir, nil
}
exePath, err := os.Executable()
if err != nil {
return "", fmt.Errorf("resolve executable path: %w", err)
}
return filepath.Join(filepath.Dir(exePath), dataDir), nil
}
func loadNames(dataDir string) error {
namesPath := filepath.Join(dataDir, "names")
surnamesPath := filepath.Join(dataDir, "surnames")
if err := names.LoadNameFiles(namesPath, surnamesPath); err != nil {
return fmt.Errorf("load embedded names override: %w", err)
}
return nil
}
func runMode(ctx context.Context, cfg config, errCh chan<- error) {
roomURL := "https://telemost.yandex.ru/j/" + cfg.roomID
switch cfg.mode {
case "srv":
errCh <- server.Run(
ctx,
roomURL,
cfg.keyHex,
cfg.duo,
cfg.dnsServer,
cfg.socksProxyAddr,
cfg.socksProxyPort,
)
case "cnc":
errCh <- client.Run(
ctx,
roomURL,
cfg.keyHex,
cfg.socksPort,
cfg.duo,
cfg.socksHost,
"",
"",
)
}
}
func waitForShutdown(errCh <-chan error) error {
done := make(chan error, 1)
go func() {
done <- <-errCh
}()
select {
case err := <-done:
if err == nil {
log.Println("Shutdown complete")
}
return err
case <-time.After(5 * time.Second):
log.Println("Shutdown timeout, forcing exit")
return nil
}
}

19
docker-compose.server.yml Normal file
View File

@@ -0,0 +1,19 @@
services:
olcrtc-server:
build:
context: .
image: olcrtc/server:local
container_name: olcrtc-server
restart: unless-stopped
environment:
OLCRTC_ROOM_ID: "${OLCRTC_ROOM_ID:?set OLCRTC_ROOM_ID}"
OLCRTC_KEY: "${OLCRTC_KEY:-}"
OLCRTC_DNS: "${OLCRTC_DNS:-1.1.1.1:53}"
OLCRTC_DUO: "${OLCRTC_DUO:-false}"
OLCRTC_DEBUG: "${OLCRTC_DEBUG:-false}"
volumes:
- olcrtc-state:/var/lib/olcrtc
init: true
volumes:
olcrtc-state:

View File

@@ -1,3 +1,4 @@
// Package client implements the local SOCKS5 client side of the olcrtc tunnel.
package client
import (
@@ -6,22 +7,31 @@ import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/pion/webrtc/v4"
"github.com/openlibrecommunity/olcrtc/internal/crypto"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/mux"
"github.com/openlibrecommunity/olcrtc/internal/names"
"github.com/openlibrecommunity/olcrtc/internal/telemost"
"github.com/pion/webrtc/v4"
)
var (
errInvalidKeyLength = errors.New("key must be 32 bytes")
errInvalidKeyStringLength = errors.New("key string length must be 32")
errNoConnectedPeers = errors.New("no connected peers available")
)
// Client manages the client-side mux and SOCKS5 listener.
type Client struct {
peers []*telemost.Peer
cipher *crypto.Cipher
@@ -31,134 +41,226 @@ type Client struct {
wg sync.WaitGroup
}
func Run(ctx context.Context, roomURL, keyHex string, socksPort int, duo bool) error {
var key []byte
var err error
const defaultSOCKSListenHost = "127.0.0.1"
if keyHex == "" {
key = make([]byte, 32)
if _, err := rand.Read(key); err != nil {
return err
}
log.Printf("Generated key: %x", key)
} else {
key, err = hex.DecodeString(keyHex)
if err != nil {
return err
}
if len(key) != 32 {
return fmt.Errorf("key must be 32 bytes, got %d", len(key))
}
}
// Run starts the client and listens for SOCKS5 traffic.
func Run(
ctx context.Context,
roomURL,
keyHex string,
socksPort int,
duo bool,
socksHost,
socksUser,
socksPass string,
) error {
return RunWithReady(ctx, roomURL, keyHex, socksPort, duo, socksHost, socksUser, socksPass, nil)
}
keyStr := string(key)
if len(keyStr) != 32 {
return fmt.Errorf("key string length must be 32, got %d", len(keyStr))
}
// RunWithReady starts the client and invokes onReady once the local SOCKS5 listener is accepting connections.
func RunWithReady(
ctx context.Context,
roomURL,
keyHex string,
socksPort int,
duo bool,
socksHost,
socksUser,
socksPass string,
onReady func(),
) error {
runCtx, cancel := context.WithCancel(ctx)
defer cancel()
cipher, err := crypto.NewCipher(keyStr)
key, err := decodeKey(keyHex)
if err != nil {
return err
}
clientID := uint32(time.Now().UnixNano() & 0xFFFFFFFF)
keyStr := string(key)
if len(keyStr) != 32 {
return fmt.Errorf("%w: got %d", errInvalidKeyStringLength, len(keyStr))
}
cipher, err := crypto.NewCipher(keyStr)
if err != nil {
return fmt.Errorf("create cipher: %w", err)
}
c := &Client{
cipher: cipher,
clientID: clientID,
peers: make([]*telemost.Peer, 0),
clientID: uint32(time.Now().UnixNano() & 0xFFFFFFFF),
peers: make([]*telemost.Peer, 0, peerCount(duo)),
}
peerCount := 1
if duo {
peerCount = 2
log.Println("Duo mode: using 2 parallel channels")
}
c.mux = mux.New(c.clientID, c.sendFrame)
c.mux = mux.New(c.clientID, func(frame []byte) error {
for {
canSend := true
for _, peer := range c.peers {
if !peer.CanSend() {
canSend = false
break
}
}
if canSend {
break
}
time.Sleep(10 * time.Millisecond)
}
encrypted, err := c.cipher.Encrypt(frame)
if err != nil {
for peerID := range peerCount(duo) {
if err := c.addPeer(runCtx, roomURL, peerID, cancel); err != nil {
return err
}
idx := c.peerIdx.Add(1) % uint32(len(c.peers))
return c.peers[idx].Send(encrypted)
})
for i := 0; i < peerCount; i++ {
peer, err := telemost.NewPeer(roomURL, names.Generate(), c.onData)
if err != nil {
return err
}
c.peers = append(c.peers, peer)
peer.SetReconnectCallback(func(dc *webrtc.DataChannel) {
log.Printf("Client peer %d reconnected - resetting multiplexer state", i)
c.mux.UpdateSendFunc(func(frame []byte) error {
encrypted, err := c.cipher.Encrypt(frame)
if err != nil {
return err
}
idx := c.peerIdx.Add(1) % uint32(len(c.peers))
return c.peers[idx].Send(encrypted)
})
c.mux.Reset()
log.Println("Client multiplexer reset complete")
})
log.Printf("Connecting peer %d to Telemost...", i)
if err := peer.Connect(ctx); err != nil {
return err
}
log.Printf("Peer %d connected", i)
c.wg.Add(1)
go func() {
defer c.wg.Done()
peer.WatchConnection(ctx)
}()
}
time.Sleep(100 * time.Millisecond)
resetFrame := make([]byte, 12)
binary.BigEndian.PutUint32(resetFrame[0:4], c.clientID)
binary.BigEndian.PutUint16(resetFrame[4:6], 0xFFFF)
binary.BigEndian.PutUint16(resetFrame[6:8], 0xFFFF)
binary.BigEndian.PutUint32(resetFrame[8:12], 0)
encrypted, _ := cipher.Encrypt(resetFrame)
for _, peer := range c.peers {
peer.Send(encrypted)
}
log.Printf("Sent reset signal to server (clientID=%d)", c.clientID)
c.sendResetSignal()
err = c.runSOCKS5(runCtx, socksHost, socksPort, socksUser, socksPass, onReady)
err = c.runSOCKS5(ctx, socksPort)
log.Println("Waiting for client goroutines...")
c.wg.Wait()
log.Println("Client goroutines finished")
return err
}
func peerCount(duo bool) int {
if duo {
log.Println("Duo mode: using 2 parallel channels")
return 2
}
return 1
}
func decodeKey(keyHex string) ([]byte, error) {
if keyHex == "" {
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("generate random key: %w", err)
}
log.Printf("Generated key: %x", key)
return key, nil
}
key, err := hex.DecodeString(keyHex)
if err != nil {
return nil, fmt.Errorf("decode hex key: %w", err)
}
if len(key) != 32 {
return nil, fmt.Errorf("%w: got %d", errInvalidKeyLength, len(key))
}
return key, nil
}
func (c *Client) sendFrame(frame []byte) error {
waitUntilPeersCanSend(c.peers)
encrypted, err := c.cipher.Encrypt(frame)
if err != nil {
return fmt.Errorf("encrypt outgoing frame: %w", err)
}
peer, err := c.nextPeer()
if err != nil {
return err
}
if err := peer.Send(encrypted); err != nil {
return fmt.Errorf("send frame via peer: %w", err)
}
return nil
}
func waitUntilPeersCanSend(peers []*telemost.Peer) {
for {
canSend := true
for _, peer := range peers {
if !peer.CanSend() {
canSend = false
break
}
}
if canSend {
return
}
time.Sleep(10 * time.Millisecond)
}
}
func (c *Client) nextPeer() (*telemost.Peer, error) {
switch len(c.peers) {
case 0:
return nil, errNoConnectedPeers
case 1:
return c.peers[0], nil
default:
return c.peers[int(c.peerIdx.Add(1)%2)], nil
}
}
func (c *Client) addPeer(
runCtx context.Context,
roomURL string,
peerID int,
cancel context.CancelFunc,
) error {
peer, err := telemost.NewPeer(roomURL, names.Generate(), c.onData)
if err != nil {
return fmt.Errorf("create peer %d: %w", peerID, err)
}
peer.SetEndedCallback(func(reason string) {
log.Printf("Client peer %d reported conference end: %s", peerID, reason)
cancel()
})
peer.SetReconnectCallback(func(dc *webrtc.DataChannel) {
c.onReconnect(peerID, dc)
})
c.peers = append(c.peers, peer)
log.Printf("Connecting peer %d to Telemost...", peerID)
if err := peer.Connect(runCtx); err != nil {
return fmt.Errorf("connect peer %d: %w", peerID, err)
}
log.Printf("Peer %d connected", peerID)
c.wg.Add(1)
go func() {
defer c.wg.Done()
peer.WatchConnection(runCtx)
}()
return nil
}
func (c *Client) onReconnect(peerID int, dc *webrtc.DataChannel) {
if dc == nil {
log.Printf("Client peer %d channel closed - resetting multiplexer state", peerID)
} else {
log.Printf("Client peer %d reconnected - resetting multiplexer state", peerID)
}
c.mux.UpdateSendFunc(c.sendFrame)
c.mux.Reset()
log.Println("Client multiplexer reset complete")
}
func (c *Client) sendResetSignal() {
resetFrame := mux.BuildControlFrame(c.clientID, mux.ControlResetClient)
encrypted, err := c.cipher.Encrypt(resetFrame)
if err != nil {
log.Printf("Failed to encrypt reset signal: %v", err)
return
}
for _, peer := range c.peers {
if err := peer.Send(encrypted); err != nil {
log.Printf("Failed to send reset signal to server: %v", err)
}
}
log.Printf("Sent reset signal to server (clientID=%d)", c.clientID)
}
func (c *Client) onData(data []byte) {
plaintext, err := c.cipher.Decrypt(data)
if err != nil {
@@ -169,18 +271,36 @@ func (c *Client) onData(data []byte) {
c.mux.HandleFrame(plaintext)
}
func (c *Client) runSOCKS5(ctx context.Context, port int) error {
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return err
func (c *Client) runSOCKS5(
ctx context.Context,
host string,
port int,
username,
password string,
onReady func(),
) error {
if host == "" {
host = defaultSOCKSListenHost
}
log.Printf("SOCKS5 proxy listening on 0.0.0.0:%d", port)
listenAddr := net.JoinHostPort(host, strconv.Itoa(port))
var lc net.ListenConfig
listener, err := lc.Listen(ctx, "tcp", listenAddr)
if err != nil {
return fmt.Errorf("listen on %s: %w", listenAddr, err)
}
log.Printf("SOCKS5 proxy listening on %s (auth=%v)", listenAddr, username != "")
if onReady != nil {
onReady()
}
go func() {
<-ctx.Done()
log.Println("Closing SOCKS5 listener...")
listener.Close()
if err := listener.Close(); err != nil {
logger.Debug("SOCKS5 listener close error: %v", err)
}
}()
for {
@@ -189,11 +309,7 @@ func (c *Client) runSOCKS5(ctx context.Context, port int) error {
select {
case <-ctx.Done():
log.Println("SOCKS5 listener closed")
for _, peer := range c.peers {
peer.Close()
}
c.closePeers()
return nil
default:
log.Printf("Accept error: %v", err)
@@ -201,20 +317,28 @@ func (c *Client) runSOCKS5(ctx context.Context, port int) error {
}
}
go c.handleSOCKS5(conn)
go c.handleSOCKS5(conn, username, password)
}
}
func (c *Client) handleSOCKS5(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 256)
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return
func (c *Client) closePeers() {
for _, peer := range c.peers {
if err := peer.Close(); err != nil {
logger.Debug("Peer close error: %v", err)
}
}
}
if buf[0] != 5 {
//nolint:cyclop // SOCKS5 parsing is inherently stateful and mirrors the protocol handshake.
func (c *Client) handleSOCKS5(conn net.Conn, username, password string) {
defer func() {
if err := conn.Close(); err != nil {
logger.Debug("SOCKS5 connection close error: %v", err)
}
}()
buf := make([]byte, 513)
if !readSOCKSVersionAndMethods(conn, buf) {
return
}
@@ -223,58 +347,160 @@ func (c *Client) handleSOCKS5(conn net.Conn) {
return
}
conn.Write([]byte{5, 0})
requireAuth := username != ""
wantMethod := byte(0x00)
if requireAuth {
wantMethod = 0x02
}
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
if !supportsMethod(buf[:nmethods], wantMethod) {
writeResponse(conn, replyUnsupportedSOCKSMethod())
return
}
writeResponse(conn, []byte{5, wantMethod})
if requireAuth && !authenticateSOCKSUser(conn, buf, username, password) {
return
}
if buf[1] != 1 {
conn.Write([]byte{5, 7, 0, 1, 0, 0, 0, 0, 0, 0})
addr, port, ok := readConnectTarget(conn, buf)
if !ok {
return
}
var addr string
atyp := buf[3]
switch atyp {
case 1:
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case 3:
if _, err := io.ReadFull(conn, buf[:1]); err != nil {
return
}
length := buf[0]
if _, err := io.ReadFull(conn, buf[:length]); err != nil {
return
}
addr = string(buf[:length])
default:
conn.Write([]byte{5, 8, 0, 1, 0, 0, 0, 0, 0, 0})
return
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return
}
port := binary.BigEndian.Uint16(buf[:2])
sid := c.mux.OpenStream()
logger.Verbose("SOCKS5 opened stream sid=%d for %s:%d", sid, addr, port)
log.Printf("[CLIENT] sid=%d SOCKS5_START %s:%d", sid, addr, port)
req := map[string]interface{}{
"cmd": "connect",
"addr": addr,
"port": port,
if !c.sendConnectRequest(sid, addr, port) {
return
}
reqData, _ := json.Marshal(req)
c.mux.SendData(sid, reqData)
if !c.waitConnectResponse(conn, sid) {
return
}
c.mux.ReadStream(sid)
writeResponse(conn, replySuccess())
c.proxyStream(conn, sid)
}
func readSOCKSVersionAndMethods(conn net.Conn, buf []byte) bool {
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return false
}
return buf[0] == 5
}
func supportsMethod(methods []byte, wantMethod byte) bool {
for _, method := range methods {
if method == wantMethod {
return true
}
}
return false
}
func authenticateSOCKSUser(conn net.Conn, buf []byte, username, password string) bool {
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return false
}
if buf[0] != 0x01 {
return false
}
ulen := int(buf[1])
if _, err := io.ReadFull(conn, buf[:ulen+1]); err != nil {
return false
}
gotUser := string(buf[:ulen])
plen := int(buf[ulen])
if _, err := io.ReadFull(conn, buf[:plen]); err != nil {
return false
}
gotPass := string(buf[:plen])
if gotUser != username || gotPass != password {
writeResponse(conn, replyAuthFailed())
return false
}
writeResponse(conn, replyAuthOK())
return true
}
func readConnectTarget(conn net.Conn, buf []byte) (string, uint16, bool) {
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return "", 0, false
}
if buf[1] != 1 {
writeResponse(conn, replyCommandNotSupported())
return "", 0, false
}
addr, ok := readTargetAddress(conn, buf, buf[3])
if !ok {
return "", 0, false
}
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
return "", 0, false
}
return addr, binary.BigEndian.Uint16(buf[:2]), true
}
func readTargetAddress(conn net.Conn, buf []byte, atyp byte) (string, bool) {
switch atyp {
case 1:
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
return "", false
}
return fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]), true
case 3:
if _, err := io.ReadFull(conn, buf[:1]); err != nil {
return "", false
}
length := buf[0]
if _, err := io.ReadFull(conn, buf[:length]); err != nil {
return "", false
}
return string(buf[:length]), true
default:
writeResponse(conn, replyAddressNotSupported())
return "", false
}
}
func (c *Client) sendConnectRequest(sid uint16, addr string, port uint16) bool {
reqData, err := json.Marshal(struct {
Cmd string `json:"cmd"`
Addr string `json:"addr"`
Port uint16 `json:"port"`
}{
Cmd: "connect",
Addr: addr,
Port: port,
})
if err != nil {
logger.Debug("Connect request marshal error: %v", err)
return false
}
if err := c.mux.SendData(sid, reqData); err != nil {
logger.Debug("Connect request send error: %v", err)
return false
}
return true
}
func (c *Client) waitConnectResponse(conn net.Conn, sid uint16) bool {
dataReady := c.mux.WaitForData(sid)
timeout := time.NewTimer(10 * time.Second)
defer timeout.Stop()
@@ -283,18 +509,19 @@ func (c *Client) handleSOCKS5(conn net.Conn) {
case <-dataReady:
stream := c.mux.GetStream(sid)
if stream == nil || len(stream.RecvBuf()) == 0 {
conn.Write([]byte{5, 4, 0, 1, 0, 0, 0, 0, 0, 0})
return
writeResponse(conn, replyHostUnreachable())
return false
}
case <-timeout.C:
conn.Write([]byte{5, 4, 0, 1, 0, 0, 0, 0, 0, 0})
return
writeResponse(conn, replyHostUnreachable())
return false
}
c.mux.ReadStream(sid)
conn.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
return true
}
//nolint:cyclop // The stream pump handles two coordinated goroutines and shutdown races in one place.
func (c *Client) proxyStream(conn net.Conn, sid uint16) {
done := make(chan struct{})
streamClosed := make(chan struct{})
@@ -304,7 +531,9 @@ func (c *Client) handleSOCKS5(conn net.Conn) {
for {
n, err := conn.Read(buf)
if err != nil {
c.mux.CloseStream(sid)
if err := c.mux.CloseStream(sid); err != nil {
logger.Debug("Close stream error: %v", err)
}
return
}
if err := c.mux.SendData(sid, buf[:n]); err != nil {
@@ -326,14 +555,8 @@ func (c *Client) handleSOCKS5(conn net.Conn) {
return
case <-ticker.C:
data := c.mux.ReadStream(sid)
if len(data) > 0 {
for len(data) > 0 {
n, err := conn.Write(data)
if err != nil {
return
}
data = data[n:]
}
if len(data) > 0 && !writeStreamData(conn, data) {
return
}
if c.mux.StreamClosed(sid) {
@@ -348,3 +571,49 @@ func (c *Client) handleSOCKS5(conn net.Conn) {
case <-streamClosed:
}
}
func writeStreamData(conn net.Conn, data []byte) bool {
for len(data) > 0 {
n, err := conn.Write(data)
if err != nil {
return false
}
data = data[n:]
}
return true
}
func writeResponse(conn net.Conn, response []byte) {
if _, err := conn.Write(response); err != nil {
logger.Debug("SOCKS5 response write error: %v", err)
}
}
func replyUnsupportedSOCKSMethod() []byte {
return []byte{5, 0xFF}
}
func replyAuthFailed() []byte {
return []byte{0x01, 0x01}
}
func replyAuthOK() []byte {
return []byte{0x01, 0x00}
}
func replyCommandNotSupported() []byte {
return []byte{5, 7, 0, 1, 0, 0, 0, 0, 0, 0}
}
func replyAddressNotSupported() []byte {
return []byte{5, 8, 0, 1, 0, 0, 0, 0, 0, 0}
}
func replyHostUnreachable() []byte {
return []byte{5, 4, 0, 1, 0, 0, 0, 0, 0, 0}
}
func replySuccess() []byte {
return []byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}
}

View File

@@ -6,19 +6,33 @@ package mux
import (
"encoding/binary"
"errors"
"sync"
"time"
"github.com/openlibrecommunity/olcrtc/internal/logger"
)
const (
ControlStreamID uint16 = 0xFFFF
ControlLength uint16 = 0xFFFF
ControlResetClient uint32 = 1
)
type ControlFrame struct {
ClientID uint32
Type uint32
}
type Stream struct {
ID uint16
ClientID uint32
recvBuf []byte
closed bool
mu sync.Mutex
nextSeq uint32
outOfOrder map[uint32][]byte
ID uint16
ClientID uint32
recvBuf []byte
closed bool
mu sync.Mutex
nextSeq uint32
outOfOrder map[uint32][]byte
}
func (s *Stream) RecvBuf() []byte {
@@ -48,7 +62,7 @@ func New(clientID uint32, onSend func([]byte) error) *Multiplexer {
clientID: clientID,
onSend: onSend,
maxStreams: 10000,
maxBufferSize: 16 * 1024 * 1024,
maxBufferSize: 32 * 1024 * 1024,
dataReady: make(map[uint16]chan struct{}),
sendSeq: make(map[uint16]uint32),
}
@@ -64,7 +78,7 @@ func (m *Multiplexer) OpenStream() uint16 {
if m.nextID == 0 {
m.nextID = 1
}
if _, exists := m.streams[sid]; !exists {
m.streams[sid] = &Stream{
ID: sid,
@@ -86,9 +100,10 @@ func (m *Multiplexer) SendData(sid uint16, data []byte) error {
return nil
}
const chunkSize = 7168
// Keep encrypted DataChannel messages below Telemost's observed 8 KiB cap.
const chunkSize = 7000
totalChunks := (len(data) + chunkSize - 1) / chunkSize
if totalChunks > 10 {
logger.Debug("SendData: sid=%d, size=%d bytes, chunks=%d", sid, len(data), totalChunks)
}
@@ -100,12 +115,12 @@ func (m *Multiplexer) SendData(sid uint16, data []byte) error {
}
chunk := data[i:end]
m.sendSeqMu.Lock()
seq := m.sendSeq[sid]
m.sendSeq[sid]++
m.sendSeqMu.Unlock()
frame := make([]byte, 12+len(chunk))
binary.BigEndian.PutUint32(frame[0:4], m.clientID)
binary.BigEndian.PutUint16(frame[4:6], sid)
@@ -128,7 +143,7 @@ func (m *Multiplexer) CloseStream(sid uint16) error {
if stream, exists := m.streams[sid]; exists {
stream.closed = true
}
m.sendSeqMu.Lock()
delete(m.sendSeq, sid)
m.sendSeqMu.Unlock()
@@ -142,24 +157,47 @@ func (m *Multiplexer) CloseStream(sid uint16) error {
return m.onSend(frame)
}
func (m *Multiplexer) HandleFrame(frame []byte) {
func (m *Multiplexer) SendClientReset() error {
if m.clientID == 0 {
return errors.New("client reset requires a non-zero client id")
}
return m.onSend(BuildControlFrame(m.clientID, ControlResetClient))
}
func BuildControlFrame(clientID uint32, controlType uint32) []byte {
frame := make([]byte, 12)
binary.BigEndian.PutUint32(frame[0:4], clientID)
binary.BigEndian.PutUint16(frame[4:6], ControlStreamID)
binary.BigEndian.PutUint16(frame[6:8], ControlLength)
binary.BigEndian.PutUint32(frame[8:12], controlType)
return frame
}
func ParseControlFrame(frame []byte) (ControlFrame, bool) {
if len(frame) < 12 {
return ControlFrame{}, false
}
sid := binary.BigEndian.Uint16(frame[4:6])
length := binary.BigEndian.Uint16(frame[6:8])
if sid != ControlStreamID || length != ControlLength {
return ControlFrame{}, false
}
return ControlFrame{
ClientID: binary.BigEndian.Uint32(frame[0:4]),
Type: binary.BigEndian.Uint32(frame[8:12]),
}, true
}
func (m *Multiplexer) HandleFrame(frame []byte) {
control, ok := ParseControlFrame(frame)
if ok {
m.handleControlFrame(control)
return
}
if len(frame) < 12 {
if len(frame) >= 8 {
clientID := binary.BigEndian.Uint32(frame[0:4])
sid := binary.BigEndian.Uint16(frame[4:6])
length := binary.BigEndian.Uint16(frame[6:8])
if sid == 0xFFFF && length == 0xFFFF {
m.mu.Lock()
for streamSid, stream := range m.streams {
if stream.ClientID == clientID {
stream.closed = true
delete(m.streams, streamSid)
}
}
m.mu.Unlock()
}
}
return
}
@@ -168,18 +206,6 @@ func (m *Multiplexer) HandleFrame(frame []byte) {
length := binary.BigEndian.Uint16(frame[6:8])
seq := binary.BigEndian.Uint32(frame[8:12])
if sid == 0xFFFF && length == 0xFFFF {
m.mu.Lock()
for streamSid, stream := range m.streams {
if stream.ClientID == clientID {
stream.closed = true
delete(m.streams, streamSid)
}
}
m.mu.Unlock()
return
}
if length == 0 {
m.mu.Lock()
if stream, exists := m.streams[sid]; exists && stream.ClientID == clientID {
@@ -197,7 +223,7 @@ func (m *Multiplexer) HandleFrame(frame []byte) {
m.mu.Lock()
defer m.mu.Unlock()
stream, exists := m.streams[sid]
if !exists {
if len(m.streams) >= m.maxStreams {
@@ -218,29 +244,41 @@ func (m *Multiplexer) HandleFrame(frame []byte) {
stream.nextSeq = 0
stream.outOfOrder = make(map[uint32][]byte)
}
if seq == stream.nextSeq {
if len(stream.recvBuf)+len(data) > m.maxBufferSize {
stream.closed = true
// Backpressure: if the stream buffer is full, release the mux lock and
// wait for the reader to drain it. Dropping/closing here would corrupt
// the TCP stream carried over the mux — large HTTP/2 downloads (X,
// Instagram, YouTube) that push data faster than conn.Write can accept
// would lose bytes and hang forever.
if s := m.waitForBufferSpace(sid, clientID, len(data)); s == nil {
return
} else {
stream = s
}
stream.recvBuf = append(stream.recvBuf, data...)
stream.nextSeq++
for {
if nextData, ok := stream.outOfOrder[stream.nextSeq]; ok {
if len(stream.recvBuf)+len(nextData) > m.maxBufferSize {
stream.closed = true
return
}
stream.recvBuf = append(stream.recvBuf, nextData...)
delete(stream.outOfOrder, stream.nextSeq)
stream.nextSeq++
} else {
nextData, ok := stream.outOfOrder[stream.nextSeq]
if !ok {
break
}
if s := m.waitForBufferSpace(sid, clientID, len(nextData)); s == nil {
return
} else {
stream = s
}
nextData, ok = stream.outOfOrder[stream.nextSeq]
if !ok {
break
}
stream.recvBuf = append(stream.recvBuf, nextData...)
delete(stream.outOfOrder, stream.nextSeq)
stream.nextSeq++
logger.Verbose("Applied out-of-order packet sid=%d seq=%d", sid, stream.nextSeq-1)
}
m.dataReadyMu.Lock()
if ch, ok := m.dataReady[sid]; ok {
select {
@@ -256,6 +294,46 @@ func (m *Multiplexer) HandleFrame(frame []byte) {
}
}
func (m *Multiplexer) handleControlFrame(control ControlFrame) {
switch control.Type {
case ControlResetClient:
m.ResetClient(control.ClientID)
default:
logger.Debug("Unknown mux control frame type=%d clientID=%d", control.Type, control.ClientID)
}
}
func (m *Multiplexer) ResetClient(clientID uint32) {
m.mu.Lock()
defer m.mu.Unlock()
for streamSid, stream := range m.streams {
if stream.ClientID == clientID {
stream.closed = true
delete(m.streams, streamSid)
}
}
}
// waitForBufferSpace releases m.mu and waits until the stream's recvBuf has
// room for `need` more bytes, then re-acquires the lock. Returns the (possibly
// re-fetched) stream, or nil if the stream disappeared / was reset / closed.
// Caller must hold m.mu (write-locked) on entry and will hold it on return.
func (m *Multiplexer) waitForBufferSpace(sid uint16, clientID uint32, need int) *Stream {
for {
stream, ok := m.streams[sid]
if !ok || stream.ClientID != clientID || stream.closed {
return nil
}
if len(stream.recvBuf)+need <= m.maxBufferSize {
return stream
}
m.mu.Unlock()
time.Sleep(5 * time.Millisecond)
m.mu.Lock()
}
}
func (m *Multiplexer) ReadStream(sid uint16) []byte {
m.mu.Lock()
defer m.mu.Unlock()
@@ -302,10 +380,10 @@ func (m *Multiplexer) Reset() {
for _, stream := range m.streams {
stream.closed = true
}
m.streams = make(map[uint16]*Stream)
m.nextID = 1
m.sendSeqMu.Lock()
m.sendSeq = make(map[uint16]uint32)
m.sendSeqMu.Unlock()
@@ -314,14 +392,14 @@ func (m *Multiplexer) Reset() {
func (m *Multiplexer) UpdateSendFunc(onSend func([]byte) error) {
m.mu.Lock()
defer m.mu.Unlock()
m.onSend = onSend
}
func (m *Multiplexer) WaitForData(sid uint16) <-chan struct{} {
m.dataReadyMu.Lock()
defer m.dataReadyMu.Unlock()
if _, ok := m.dataReady[sid]; !ok {
m.dataReady[sid] = make(chan struct{}, 1)
}
@@ -331,7 +409,7 @@ func (m *Multiplexer) WaitForData(sid uint16) <-chan struct{} {
func (m *Multiplexer) CleanupDataChannel(sid uint16) {
m.dataReadyMu.Lock()
defer m.dataReadyMu.Unlock()
if ch, ok := m.dataReady[sid]; ok {
close(ch)
delete(m.dataReady, sid)

735
internal/names/data/names Normal file
View File

@@ -0,0 +1,735 @@
Аарон
Аба
Аббас
Абд аль-Узза
Абдуллах
Абид
Аботур
Аввакум
Август
Авдей
Авель
Аверкий
Авигдор
Авирмэд
Авксентий
Авл
Авнер
Аврелий
Автандил
Автоном
Агапит
Агафангел
Агафодор
Агафон
Аги
Агриппа
Адам
Адар
Адиль
Адольф
Адонирам
Адриан
Азамат
Азарий
Азат
Азиз
Азим
Айварс
Айдар
Айрат
Акакий
Аквилий
Акиф
Акоп
Аксель
Алан
Аланус
Алек
Александр
Алексей
Алемдар
Алик
Алим
Алипий
Алишер
Алмат
Алоиз
Алон
Альберик
Альберт
Альбин
Альваро
Альвиан
Альвизе
Альфонс
Альфред
Амадис
Амвросий
Амедей
Амин
Амир
Амр
Амфилохий
Анания
Анас
Анастасий
Анатолий
Ангеляр
Андокид
Андрей
Андроник
Аннерс
Анри
Ансельм
Антипа
Антон
Антоний
Антонин
Антуан
Арам
Арефа
Арзуман
Аристарх
Аристон
Ариф
Аркадий
Арсений
Артём
Артур
Арфаксад
Асаф
Атанасий
Атом
Аттик
Афанасий
Афинагор
Афиней
Афиф
Африкан
Ахилл
Ахмад
Ахтям
Ашот
Бадар
Барни
Бартоломео
Басир
Бахтияр
Баян
Безсон
Бен
Беньямин
Берт
Бехруз
Билял
Богдан
Болеслав
Бонавентура
Борис
Борислав
Боян
Бронислав
Брячислав
Бурхан
Бутрос
Бямбасурэн
Вадим
Валентин
Валентино
Валерий
Валерьян
Вальдемар
Вангьял
Варлам
Варнава
Варфоломей
Василий
Вахтанг
Велвел
Венансио
Венедикт
Вениамин
Венцеслав
Вигго
Викентий
Виктор
Викторин
Вильгельм
Винцас
Виссарион
Виталий
Витаутас
Вито
Владимир
Владислав
Владлен
Влас
Воислав
Володарь
Вольфганг
Вописк
Всеволод
Всеслав
Вук
Вукол
Вышеслав
Вячеслав
Габриеле
Гавриил
Гай
Галактион
Галымжан
Гамлет
Гаспар
Гафур
Гвидо
Гейдар
Геласий
Гелий
Гельмут
Геннадий
Генри
Генрих
Георге
Георгий
Гераклид
Герасим
Герберт
Герман
Германн
Геронтий
Герхард
Гийом
Гильем
Гинкмар
Глеб
Гней
Гоар
Горацио
Гордей
Градислав
Григорий
Гримоальд
Гуго
Гурий
Густав
Гьялцен
Давид
Дамдинсурэн
Дамир
Даниил
Дарий
Демид
Демьян
Денеш
Денис
Децим
Джаббар
Джамиль
Джан
Джанер
Джанфранко
Джафар
Джейкоб
Джихангир
Джованни
Джон
Джохар
Джулиано
Джулиус
Дино
Диодор
Дитер
Дитмар
Дитрих
Дмитрий
Доминик
Дональд
Донат
Дорофей
Досифей
Евгений
Евграф
Евдоким
Еврит
Евсей
Евстафий
Евтихан
Евтихий
Егор
Елеазар
Елисей
Емельян
Епифаний
Ербол
Ерванд
Еремей
Ермак
Ермолай
Ерофей
Ефим
Ефрем
Жан
Ждан
Жером
Жоан
Захар
Захария
Збигнев
Зденек
Зейналабдин
Зенон
Зеэв
Зигмунд
Зинон
Зия
Золтан
Зосима
Иакинф
Иан
Ибрагим
Ибрахим
Иван
Игнатий
Игорь
Иероним
Иерофей
Израиль
Икрима
Иларий
Илия
Илларион
Илмари
Ильфат
Илья
Имран
Иннокентий
Иоаким
Иоанн
Иоанникий
Иоахим
Иов
Иоганн
Иоганнес
Ионафан
Иосафат
Ираклий
Иржи
Иринарх
Ириней
Иродион
Иса
Исаак
Исаакий
Исаия
Исидор
Ислам
Исмаил
Истислав
Истома
Истукарий
Иштван
Йюрген
Кадваллон
Кадир
Казимир
Каликст
Калин
Каллистрат
Кальман
Канат
Карен
Карлос
Карп
Картерий
Кассиан
Кассий
Касторий
Касьян
Катберт
Квинт
Кехлер
Киллиан
Ким
Кир
Кириак
Кирилл
Клаас
Клавдиан
Клеоник
Климент
Кондрат
Конон
Конрад
Константин
Корнелиус
Корнилий
Коррадо
Косьма
Кратет
Кратипп
Крис
Криспин
Кристиан
Кронид
Кузьма
Куприян
Курбан
Курт
Кутлуг-Буга
Кэлин
Лаврентий
Лавс
Ладислав
Лазарь
Лайл
Лампрехт
Ландульф
Лев
Леви
Ленни
Леонид
Леонтий
Леонхард
Лиам
Линкей
Логгин
Лоренц
Лоренцо
Луи
Луитпольд
Лука
Лукас
Лукий
Лукьян
Луций
Людовик
Люцифер
Макар
Максим
Максимиан
Максимилиан
Малик
Малх
Мамбет
Маний
Мануил
Мануэль
Мариан
Мариус
Марк
Маркел
Мартын
Марчелло
Матвей
Матео
Матиас
Матфей
Матфий
Махмуд
Меир
Мелентий
Мелитон
Менахем-Мендель
Месроп
Мефодий
Мечислав
Мика
Микеланджело
Микулаш
Милорад
Мина
Мирко
Мирон
Мирослав
Митрофан
Михаил
Михей
Младан
Модест
Моисей
Мордехай
Мстислав
Мурад
Мухаммед
Мэдисон
Мэлор
Мэлс
Назар
Наиль
Насиф
Натан
Натаниэль
Наум
Нафанаил
Нацагдорж
Нестор
Никандр
Никанор
Никита
Никифор
Никодим
Николай
Нил
Нильс
Ноа
Ной
Норд
Нуржан
Нурлан
Овадья
Оге
Одинец
Октав
Октавиан
Октавий
Октавио
Олаф
Оле
Олег
Оливер
Ольгерд
Онисим
Орест
Осип
Оскар
Осман
Отто
Оттон
Очирбат
Пабло
Павел
Павлин
Павсикакий
Паисий
Палладий
Панкратий
Пантелеймон
Папа
Паруйр
Парфений
Патрик
Пафнутий
Пахомий
Педро
Пётр
Пимен
Пинхас
Пипин
Питирим
Пол
Полидор
Полиевкт
Поликарп
Поликрат
Порфирий
Потап
Предраг
Премысл
Приск
Прокл
Прокопий
Прокул
Протасий
Прохор
Публий
Рагнар
Рагуил
Радмир
Радослав
Разумник
Раймонд
Рамадан
Рамазан
Рахман
Рашад
Рейнхард
Ренат
Реститут
Ричард
Роберт
Родерик
Родион
Рожер
Розарио
Роман
Ромен
Рон
Ронан
Ростислав
Рудольф
Руслан
Руф
Руфин
Рушан
Сабит
Савва
Савватий
Савелий
Савин
Саддам
Садик
Саид
Салават
Салих
Саллюстий
Салман
Самуил
Сармат
Святослав
Севастьян
Северин
Секст
Секунд
Семён
Септимий
Серапион
Сергей
Серж
Сигеберт
Сильвестр
Симеон
Симон
Созон
Соломон
Сонам
Софрон
Спиридон
Срджан
Станислав
Степан
Стефано
Стивен
Таврион
Тавус
Тадеуш
Тарас
Тарасий
Тейс
Тендзин
Теофил
Терентий
Терри
Тиберий
Тигран
Тимофей
Тимур
Тихомир
Тихон
Томас
Томоми
Торос
Тофик
Трифон
Трофим
Тудхалия
Тутмос
Тьерри
Тьяго
Уве
Уильям
Улдис
Ульрих
Ульф
Умар
Урызмаг
Усама
Усман
Фавст
Фаддей
Файзулла
Фарид
Фахраддин
Федериго
Федосей
Федот
Фейсал
Феликс
Феоктист
Феофан
Феофил
Феофилакт
Фердинанд
Ференц
Фёдор
Фидель
Филарет
Филат
Филип
Филипп
Философ
Филострат
Фирс
Фока
Фома
Фотий
Франц
Франческо
Фредерик
Фридрих
Фродо
Фрол
Фульк
Хайме
Ханс
Харальд
Харитон
Харри
Харрисон
Хасан
Хетаг
Хильдерик
Хирам
Хлодвиг
Хокон
Хорив
Хоселито
Хосрой
Хрисанф
Христофор
Хуан
Цэрэндорж
Чеслав
Шалом
Шамиль
Шамсуддин
Шапур
Шарль
Шейх-Хайдар
Шон
Эберхард
Эдмунд
Эдна
Эдуард
Элбэгдорж
Элджернон
Элиас
Эллиот
Эмиль
Энрик
Энрико
Энтони
Эразм
Эраст
Эрик
Эрнст
Эсекьель
Эстебан
Этьен
Ювеналий
Юлиан
Юлий
Юлиус
Юрий
Юстас
Юстин
Яков
Якуб
Якун
Ян
Яни
Януарий
Яромир
Ярополк
Ярослав

View File

@@ -0,0 +1,735 @@
Аарон
Аба
Аббас
Абд аль-Узза
Абдуллах
Абид
Аботур
Аввакум
Август
Авдей
Авель
Аверкий
Авигдор
Авирмэд
Авксентий
Авл
Авнер
Аврелий
Автандил
Автоном
Агапит
Агафангел
Агафодор
Агафон
Аги
Агриппа
Адам
Адар
Адиль
Адольф
Адонирам
Адриан
Азамат
Азарий
Азат
Азиз
Азим
Айварс
Айдар
Айрат
Акакий
Аквилий
Акиф
Акоп
Аксель
Алан
Аланус
Алек
Александр
Алексей
Алемдар
Алик
Алим
Алипий
Алишер
Алмат
Алоиз
Алон
Альберик
Альберт
Альбин
Альваро
Альвиан
Альвизе
Альфонс
Альфред
Амадис
Амвросий
Амедей
Амин
Амир
Амр
Амфилохий
Анания
Анас
Анастасий
Анатолий
Ангеляр
Андокид
Андрей
Андроник
Аннерс
Анри
Ансельм
Антипа
Антон
Антоний
Антонин
Антуан
Арам
Арефа
Арзуман
Аристарх
Аристон
Ариф
Аркадий
Арсений
Артём
Артур
Арфаксад
Асаф
Атанасий
Атом
Аттик
Афанасий
Афинагор
Афиней
Афиф
Африкан
Ахилл
Ахмад
Ахтям
Ашот
Бадар
Барни
Бартоломео
Басир
Бахтияр
Баян
Безсон
Бен
Беньямин
Берт
Бехруз
Билял
Богдан
Болеслав
Бонавентура
Борис
Борислав
Боян
Бронислав
Брячислав
Бурхан
Бутрос
Бямбасурэн
Вадим
Валентин
Валентино
Валерий
Валерьян
Вальдемар
Вангьял
Варлам
Варнава
Варфоломей
Василий
Вахтанг
Велвел
Венансио
Венедикт
Вениамин
Венцеслав
Вигго
Викентий
Виктор
Викторин
Вильгельм
Винцас
Виссарион
Виталий
Витаутас
Вито
Владимир
Владислав
Владлен
Влас
Воислав
Володарь
Вольфганг
Вописк
Всеволод
Всеслав
Вук
Вукол
Вышеслав
Вячеслав
Габриеле
Гавриил
Гай
Галактион
Галымжан
Гамлет
Гаспар
Гафур
Гвидо
Гейдар
Геласий
Гелий
Гельмут
Геннадий
Генри
Генрих
Георге
Георгий
Гераклид
Герасим
Герберт
Герман
Германн
Геронтий
Герхард
Гийом
Гильем
Гинкмар
Глеб
Гней
Гоар
Горацио
Гордей
Градислав
Григорий
Гримоальд
Гуго
Гурий
Густав
Гьялцен
Давид
Дамдинсурэн
Дамир
Даниил
Дарий
Демид
Демьян
Денеш
Денис
Децим
Джаббар
Джамиль
Джан
Джанер
Джанфранко
Джафар
Джейкоб
Джихангир
Джованни
Джон
Джохар
Джулиано
Джулиус
Дино
Диодор
Дитер
Дитмар
Дитрих
Дмитрий
Доминик
Дональд
Донат
Дорофей
Досифей
Евгений
Евграф
Евдоким
Еврит
Евсей
Евстафий
Евтихан
Евтихий
Егор
Елеазар
Елисей
Емельян
Епифаний
Ербол
Ерванд
Еремей
Ермак
Ермолай
Ерофей
Ефим
Ефрем
Жан
Ждан
Жером
Жоан
Захар
Захария
Збигнев
Зденек
Зейналабдин
Зенон
Зеэв
Зигмунд
Зинон
Зия
Золтан
Зосима
Иакинф
Иан
Ибрагим
Ибрахим
Иван
Игнатий
Игорь
Иероним
Иерофей
Израиль
Икрима
Иларий
Илия
Илларион
Илмари
Ильфат
Илья
Имран
Иннокентий
Иоаким
Иоанн
Иоанникий
Иоахим
Иов
Иоганн
Иоганнес
Ионафан
Иосафат
Ираклий
Иржи
Иринарх
Ириней
Иродион
Иса
Исаак
Исаакий
Исаия
Исидор
Ислам
Исмаил
Истислав
Истома
Истукарий
Иштван
Йюрген
Кадваллон
Кадир
Казимир
Каликст
Калин
Каллистрат
Кальман
Канат
Карен
Карлос
Карп
Картерий
Кассиан
Кассий
Касторий
Касьян
Катберт
Квинт
Кехлер
Киллиан
Ким
Кир
Кириак
Кирилл
Клаас
Клавдиан
Клеоник
Климент
Кондрат
Конон
Конрад
Константин
Корнелиус
Корнилий
Коррадо
Косьма
Кратет
Кратипп
Крис
Криспин
Кристиан
Кронид
Кузьма
Куприян
Курбан
Курт
Кутлуг-Буга
Кэлин
Лаврентий
Лавс
Ладислав
Лазарь
Лайл
Лампрехт
Ландульф
Лев
Леви
Ленни
Леонид
Леонтий
Леонхард
Лиам
Линкей
Логгин
Лоренц
Лоренцо
Луи
Луитпольд
Лука
Лукас
Лукий
Лукьян
Луций
Людовик
Люцифер
Макар
Максим
Максимиан
Максимилиан
Малик
Малх
Мамбет
Маний
Мануил
Мануэль
Мариан
Мариус
Марк
Маркел
Мартын
Марчелло
Матвей
Матео
Матиас
Матфей
Матфий
Махмуд
Меир
Мелентий
Мелитон
Менахем-Мендель
Месроп
Мефодий
Мечислав
Мика
Микеланджело
Микулаш
Милорад
Мина
Мирко
Мирон
Мирослав
Митрофан
Михаил
Михей
Младан
Модест
Моисей
Мордехай
Мстислав
Мурад
Мухаммед
Мэдисон
Мэлор
Мэлс
Назар
Наиль
Насиф
Натан
Натаниэль
Наум
Нафанаил
Нацагдорж
Нестор
Никандр
Никанор
Никита
Никифор
Никодим
Николай
Нил
Нильс
Ноа
Ной
Норд
Нуржан
Нурлан
Овадья
Оге
Одинец
Октав
Октавиан
Октавий
Октавио
Олаф
Оле
Олег
Оливер
Ольгерд
Онисим
Орест
Осип
Оскар
Осман
Отто
Оттон
Очирбат
Пабло
Павел
Павлин
Павсикакий
Паисий
Палладий
Панкратий
Пантелеймон
Папа
Паруйр
Парфений
Патрик
Пафнутий
Пахомий
Педро
Пётр
Пимен
Пинхас
Пипин
Питирим
Пол
Полидор
Полиевкт
Поликарп
Поликрат
Порфирий
Потап
Предраг
Премысл
Приск
Прокл
Прокопий
Прокул
Протасий
Прохор
Публий
Рагнар
Рагуил
Радмир
Радослав
Разумник
Раймонд
Рамадан
Рамазан
Рахман
Рашад
Рейнхард
Ренат
Реститут
Ричард
Роберт
Родерик
Родион
Рожер
Розарио
Роман
Ромен
Рон
Ронан
Ростислав
Рудольф
Руслан
Руф
Руфин
Рушан
Сабит
Савва
Савватий
Савелий
Савин
Саддам
Садик
Саид
Салават
Салих
Саллюстий
Салман
Самуил
Сармат
Святослав
Севастьян
Северин
Секст
Секунд
Семён
Септимий
Серапион
Сергей
Серж
Сигеберт
Сильвестр
Симеон
Симон
Созон
Соломон
Сонам
Софрон
Спиридон
Срджан
Станислав
Степан
Стефано
Стивен
Таврион
Тавус
Тадеуш
Тарас
Тарасий
Тейс
Тендзин
Теофил
Терентий
Терри
Тиберий
Тигран
Тимофей
Тимур
Тихомир
Тихон
Томас
Томоми
Торос
Тофик
Трифон
Трофим
Тудхалия
Тутмос
Тьерри
Тьяго
Уве
Уильям
Улдис
Ульрих
Ульф
Умар
Урызмаг
Усама
Усман
Фавст
Фаддей
Файзулла
Фарид
Фахраддин
Федериго
Федосей
Федот
Фейсал
Феликс
Феоктист
Феофан
Феофил
Феофилакт
Фердинанд
Ференц
Фёдор
Фидель
Филарет
Филат
Филип
Филипп
Философ
Филострат
Фирс
Фока
Фома
Фотий
Франц
Франческо
Фредерик
Фридрих
Фродо
Фрол
Фульк
Хайме
Ханс
Харальд
Харитон
Харри
Харрисон
Хасан
Хетаг
Хильдерик
Хирам
Хлодвиг
Хокон
Хорив
Хоселито
Хосрой
Хрисанф
Христофор
Хуан
Цэрэндорж
Чеслав
Шалом
Шамиль
Шамсуддин
Шапур
Шарль
Шейх-Хайдар
Шон
Эберхард
Эдмунд
Эдна
Эдуард
Элбэгдорж
Элджернон
Элиас
Эллиот
Эмиль
Энрик
Энрико
Энтони
Эразм
Эраст
Эрик
Эрнст
Эсекьель
Эстебан
Этьен
Ювеналий
Юлиан
Юлий
Юлиус
Юрий
Юстас
Юстин
Яков
Якуб
Якун
Ян
Яни
Януарий
Яромир
Ярополк
Ярослав

View File

@@ -1,35 +1,49 @@
// Package names generates display names for Telemost peers.
package names
import (
"bufio"
"math/rand/v2"
"crypto/rand"
_ "embed"
"fmt"
"math/big"
"os"
"strings"
)
//go:embed data/names
var embeddedNames string
//go:embed data/surnames
var embeddedSurnames string
//nolint:gochecknoglobals // Package-level state keeps the loaded name dictionaries cached for the process lifetime.
var (
firstNames []string
lastNames []string
firstNames = parseEmbedded(embeddedNames)
lastNames = parseEmbedded(embeddedSurnames)
)
var defaultFirstNames = []string{
"Александр", "Дмитрий", "Максим", "Сергей", "Андрей", "Алексей", "Артём", "Илья", "Кирилл", "Михаил",
"Никита", "Матвей", "Роман", "Егор", "Арсений", "Иван", "Денис", "Евгений", "Даниил", "Тимофей",
"Владислав", "Игорь", "Владимир", "Павел", "Руслан", "Марк", "Константин", "Николай", "Олег", "Виктор",
}
func parseEmbedded(raw string) []string {
var names []string
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line != "" {
names = append(names, line)
}
}
var defaultLastNames = []string{
"Иванов", "Смирнов", "Кузнецов", "Попов", "Васильев", "Петров", "Соколов", "Михайлов", "Новиков", "Фёдоров",
"Морозов", "Волков", "Алексеев", "Лебедев", "Семёнов", "Егоров", "Павлов", "Козлов", "Степанов", "Николаев",
"Орлов", "Андреев", "Макаров", "Никитин", "Захаров", "Зайцев", "Соловьёв", "Борисов", "Яковлев", "Григорьев",
return names
}
func loadNames(path string) ([]string, error) {
//nolint:gosec // Paths come from local CLI/runtime configuration; loading override files is intentional here.
file, err := os.Open(path)
if err != nil {
return nil, err
return nil, fmt.Errorf("open names file %q: %w", path, err)
}
defer file.Close()
defer func() {
_ = file.Close()
}()
var names []string
scanner := bufio.NewScanner(file)
@@ -40,27 +54,44 @@ func loadNames(path string) ([]string, error) {
}
}
return names, scanner.Err()
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scan names file %q: %w", path, err)
}
return names, nil
}
// LoadNameFiles overrides embedded name dictionaries from local files when they are present.
func LoadNameFiles(firstPath, lastPath string) error {
firstNames = defaultFirstNames
lastNames = defaultLastNames
if names, err := loadNames(firstPath); err == nil {
if names, err := loadNames(firstPath); err == nil && len(names) > 0 {
firstNames = names
}
if names, err := loadNames(lastPath); err == nil {
if names, err := loadNames(lastPath); err == nil && len(names) > 0 {
lastNames = names
}
return nil
}
// Generate returns a random display name assembled from the currently loaded dictionaries.
func Generate() string {
first := firstNames[rand.IntN(len(firstNames))]
last := lastNames[rand.IntN(len(lastNames))]
if len(firstNames) == 0 || len(lastNames) == 0 {
return "anonymous user"
}
return first + " " + last
return firstNames[randomIndex(len(firstNames))] + " " + lastNames[randomIndex(len(lastNames))]
}
func randomIndex(limit int) int {
if limit <= 1 {
return 0
}
n, err := rand.Int(rand.Reader, big.NewInt(int64(limit)))
if err != nil {
return 0
}
return int(n.Int64())
}

View File

@@ -0,0 +1,66 @@
package protect
import (
"context"
"net"
"net/http"
"syscall"
"time"
)
// Protector is called with a socket file descriptor before connect.
// On Android, this calls VpnService.protect(fd) to bypass VPN routing.
var Protector func(fd int) bool
func controlFunc(network, address string, c syscall.RawConn) error {
if Protector == nil {
return nil
}
var err error
c.Control(func(fd uintptr) {
if !Protector(int(fd)) {
err = &net.OpError{Op: "protect", Net: network, Err: net.ErrClosed}
}
})
return err
}
// NewDialer returns a net.Dialer that calls Protector on each new socket.
func NewDialer() *net.Dialer {
return &net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
Control: controlFunc,
}
}
// NewHTTPClient returns an http.Client using protected sockets.
func NewHTTPClient() *http.Client {
dialer := NewDialer()
transport := &http.Transport{
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
}
return &http.Client{Transport: transport}
}
// DialContext dials using a protected socket.
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return NewDialer().DialContext(ctx, network, address)
}
// proxyDialer implements golang.org/x/net/proxy.Dialer for pion ICE.
type proxyDialer struct{}
func (d *proxyDialer) Dial(network, addr string) (net.Conn, error) {
return NewDialer().Dial(network, addr)
}
// NewProxyDialer returns a proxy.Dialer that protects ICE sockets.
func NewProxyDialer() *proxyDialer {
return &proxyDialer{}
}

View File

@@ -3,7 +3,6 @@ package server
import (
"context"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
@@ -14,12 +13,12 @@ import (
"sync/atomic"
"time"
"github.com/pion/webrtc/v4"
"github.com/openlibrecommunity/olcrtc/internal/crypto"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/mux"
"github.com/openlibrecommunity/olcrtc/internal/names"
"github.com/openlibrecommunity/olcrtc/internal/telemost"
"github.com/pion/webrtc/v4"
)
type Server struct {
@@ -28,6 +27,8 @@ type Server struct {
mux *mux.Multiplexer
connections map[uint16]net.Conn
connMu sync.RWMutex
streamPumps map[uint16]net.Conn
pumpMu sync.Mutex
peerIdx atomic.Uint32
wg sync.WaitGroup
dnsServer string
@@ -44,6 +45,8 @@ type ConnectRequest struct {
}
func Run(ctx context.Context, roomURL, keyHex string, duo bool, dnsServer, socksProxyAddr string, socksProxyPort int) error {
runCtx, cancel := context.WithCancel(ctx)
defer cancel()
var key []byte
var err error
@@ -76,6 +79,7 @@ func Run(ctx context.Context, roomURL, keyHex string, duo bool, dnsServer, socks
s := &Server{
cipher: cipher,
connections: make(map[uint16]net.Conn),
streamPumps: make(map[uint16]net.Conn),
peers: make([]*telemost.Peer, 0),
dnsServer: dnsServer,
socksProxyAddr: socksProxyAddr,
@@ -124,14 +128,23 @@ func Run(ctx context.Context, roomURL, keyHex string, duo bool, dnsServer, socks
})
for i := 0; i < peerCount; i++ {
peerID := i
peer, err := telemost.NewPeer(roomURL, names.Generate(), s.onData)
if err != nil {
return err
}
peer.SetEndedCallback(func(reason string) {
log.Printf("Server peer %d reported conference end: %s", peerID, reason)
cancel()
})
s.peers = append(s.peers, peer)
peer.SetReconnectCallback(func(dc *webrtc.DataChannel) {
log.Printf("Server peer %d reconnected - resetting multiplexer state", i)
if dc == nil {
log.Printf("Server peer %d channel closed - resetting multiplexer state", peerID)
} else {
log.Printf("Server peer %d reconnected - resetting multiplexer state", peerID)
}
s.connMu.Lock()
for sid, conn := range s.connections {
@@ -158,20 +171,20 @@ func Run(ctx context.Context, roomURL, keyHex string, duo bool, dnsServer, socks
log.Println("Server multiplexer reset complete")
})
log.Printf("Connecting peer %d to Telemost...", i)
if err := peer.Connect(ctx); err != nil {
log.Printf("Connecting peer %d to Telemost...", peerID)
if err := peer.Connect(runCtx); err != nil {
return err
}
log.Printf("Peer %d connected", i)
log.Printf("Peer %d connected", peerID)
s.wg.Add(1)
go func() {
defer s.wg.Done()
peer.WatchConnection(ctx)
peer.WatchConnection(runCtx)
}()
}
err = s.run(ctx)
err = s.run(runCtx)
log.Println("Waiting for server goroutines...")
s.wg.Wait()
@@ -220,49 +233,29 @@ func (s *Server) onData(data []byte) {
return
}
if len(plaintext) >= 12 {
clientID := binary.BigEndian.Uint32(plaintext[0:4])
sid := binary.BigEndian.Uint16(plaintext[4:6])
length := binary.BigEndian.Uint16(plaintext[6:8])
if sid == 0xFFFF && length == 0xFFFF {
log.Printf("Received reset signal from client (clientID=%d) - cleaning up", clientID)
s.connMu.Lock()
for streamSid, conn := range s.connections {
stream := s.mux.GetStream(streamSid)
if stream != nil && stream.ClientID == clientID {
if conn != nil {
conn.Close()
}
delete(s.connections, streamSid)
}
}
s.connMu.Unlock()
}
} else if len(plaintext) >= 8 {
clientID := binary.BigEndian.Uint32(plaintext[0:4])
sid := binary.BigEndian.Uint16(plaintext[4:6])
length := binary.BigEndian.Uint16(plaintext[6:8])
if sid == 0xFFFF && length == 0xFFFF {
log.Printf("Received reset signal from client (clientID=%d) - cleaning up", clientID)
s.connMu.Lock()
for streamSid, conn := range s.connections {
stream := s.mux.GetStream(streamSid)
if stream != nil && stream.ClientID == clientID {
if conn != nil {
conn.Close()
}
delete(s.connections, streamSid)
}
}
s.connMu.Unlock()
}
if control, ok := mux.ParseControlFrame(plaintext); ok && control.Type == mux.ControlResetClient {
log.Printf("Received reset signal from client (clientID=%d) - cleaning up", control.ClientID)
s.closeClientConnections(control.ClientID)
}
s.mux.HandleFrame(plaintext)
}
func (s *Server) closeClientConnections(clientID uint32) {
s.connMu.Lock()
defer s.connMu.Unlock()
for streamSid, conn := range s.connections {
stream := s.mux.GetStream(streamSid)
if stream != nil && stream.ClientID == clientID {
if conn != nil {
conn.Close()
}
delete(s.connections, streamSid)
}
}
}
func (s *Server) run(ctx context.Context) error {
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
@@ -290,54 +283,81 @@ func (s *Server) run(ctx context.Context) error {
case <-ticker.C:
}
sids := s.mux.GetStreams()
for _, sid := range sids {
go func(sid uint16) {
data := s.mux.ReadStream(sid)
if len(data) > 0 {
s.connMu.RLock()
conn, exists := s.connections[sid]
s.connMu.RUnlock()
if s.mux.StreamClosed(sid) {
s.closeStreamConnection(sid)
continue
}
if exists && conn != nil {
if _, err := conn.Write(data); err != nil {
s.mux.CloseStream(sid)
conn.Close()
s.connMu.Lock()
delete(s.connections, sid)
s.connMu.Unlock()
}
} else {
var req ConnectRequest
if err := json.Unmarshal(data, &req); err == nil && req.Cmd == "connect" {
log.Printf("[SERVER] sid=%d RECEIVED_CONNECT_REQUEST %s:%d", sid, req.Addr, req.Port)
s.connMu.Lock()
if oldConn, exists := s.connections[sid]; exists && oldConn != nil {
oldConn.Close()
}
s.connMu.Unlock()
go s.handleConnect(sid, req)
}
}
}
if s.hasConnection(sid) {
continue
}
if s.mux.StreamClosed(sid) {
s.connMu.Lock()
conn, exists := s.connections[sid]
if exists && conn != nil {
conn.Close()
delete(s.connections, sid)
}
s.connMu.Unlock()
}
}(sid)
data := s.mux.ReadStream(sid)
if len(data) == 0 {
continue
}
var req ConnectRequest
if err := json.Unmarshal(data, &req); err == nil && req.Cmd == "connect" {
log.Printf("[SERVER] sid=%d RECEIVED_CONNECT_REQUEST %s:%d", sid, req.Addr, req.Port)
s.closeStreamConnection(sid)
go s.handleConnect(ctx, sid, req)
}
}
}
}
func (s *Server) handleConnect(sid uint16, req ConnectRequest) {
func (s *Server) hasConnection(sid uint16) bool {
s.connMu.RLock()
defer s.connMu.RUnlock()
conn := s.connections[sid]
return conn != nil
}
func (s *Server) closeStreamConnection(sid uint16) {
s.connMu.Lock()
conn := s.connections[sid]
if conn != nil {
conn.Close()
delete(s.connections, sid)
}
s.connMu.Unlock()
}
func (s *Server) closeStreamConnectionIfCurrent(sid uint16, expected net.Conn) {
s.connMu.Lock()
conn := s.connections[sid]
if conn == expected {
conn.Close()
delete(s.connections, sid)
}
s.connMu.Unlock()
}
func (s *Server) markStreamPump(sid uint16, conn net.Conn) bool {
s.pumpMu.Lock()
defer s.pumpMu.Unlock()
if current := s.streamPumps[sid]; current == conn {
return false
} else if current != nil {
current.Close()
}
s.streamPumps[sid] = conn
return true
}
func (s *Server) unmarkStreamPump(sid uint16, conn net.Conn) {
s.pumpMu.Lock()
if s.streamPumps[sid] == conn {
delete(s.streamPumps, sid)
}
s.pumpMu.Unlock()
}
func (s *Server) handleConnect(ctx context.Context, sid uint16, req ConnectRequest) {
startTime := time.Now()
addr := fmt.Sprintf("%s:%d", req.Addr, req.Port)
logger.Verbose("Handling connect request sid=%d to %s", sid, addr)
@@ -379,7 +399,6 @@ func (s *Server) handleConnect(sid uint16, req ConnectRequest) {
}
logger.Verbose("SOCKS5 proxy dial took %v for sid=%d", time.Since(dialStart), sid)
}
dialElapsed := time.Since(dialStart)
if err != nil {
@@ -388,6 +407,7 @@ func (s *Server) handleConnect(sid uint16, req ConnectRequest) {
return
}
logger.Verbose("TCP dial took %v for sid=%d", dialElapsed, sid)
s.connMu.Lock()
s.connections[sid] = conn
s.connMu.Unlock()
@@ -395,6 +415,7 @@ func (s *Server) handleConnect(sid uint16, req ConnectRequest) {
log.Printf("[SERVER] sid=%d CONNECT_SUCCESS dial_time=%v", sid, dialElapsed)
s.mux.SendData(sid, []byte{0x00})
s.startStreamPump(ctx, sid, conn)
go func() {
defer func() {
@@ -434,6 +455,41 @@ func (s *Server) handleConnect(sid uint16, req ConnectRequest) {
}()
}
func (s *Server) startStreamPump(ctx context.Context, sid uint16, conn net.Conn) {
if !s.markStreamPump(sid, conn) {
return
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
defer s.unmarkStreamPump(sid, conn)
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
data := s.mux.ReadStream(sid)
if len(data) > 0 {
if _, err := conn.Write(data); err != nil {
s.mux.CloseStream(sid)
s.closeStreamConnectionIfCurrent(sid, conn)
return
}
}
if s.mux.StreamClosed(sid) {
s.closeStreamConnectionIfCurrent(sid, conn)
return
}
}
}
}()
}
func (s *Server) canSendData() bool {
for _, peer := range s.peers {
if !peer.CanSend() {

View File

@@ -8,6 +8,7 @@ import (
"net/url"
"github.com/google/uuid"
"github.com/openlibrecommunity/olcrtc/internal/protect"
)
const apiBase = "https://cloud-api.yandex.ru/telemost_front/v2/telemost"
@@ -44,7 +45,8 @@ func GetConnectionInfo(roomURL, displayName string) (*ConnectionInfo, error) {
req.Header.Set("Origin", "https://telemost.yandex.ru")
req.Header.Set("Referer", "https://telemost.yandex.ru/")
resp, err := http.DefaultClient.Do(req)
client := protect.NewHTTPClient()
resp, err := client.Do(req)
if err != nil {
return nil, err
}

File diff suppressed because it is too large Load Diff

220
mobile/mobile.go Normal file
View File

@@ -0,0 +1,220 @@
// Package mobile provides a gomobile-compatible API for olcRTC.
// Build with: gomobile bind -target=android ./mobile
package mobile
import (
"context"
"errors"
"log"
"sync"
"time"
"github.com/openlibrecommunity/olcrtc/internal/client"
"github.com/openlibrecommunity/olcrtc/internal/logger"
"github.com/openlibrecommunity/olcrtc/internal/protect"
)
// SocketProtector protects sockets from VPN routing on Android.
// Implement this interface in Kotlin/Java and pass to SetProtector.
type SocketProtector interface {
Protect(fd int) bool
}
// LogWriter receives log messages from olcRTC.
type LogWriter interface {
WriteLog(msg string)
}
var (
errAlreadyRunning = errors.New("olcRTC already running")
errRoomIDRequired = errors.New("roomID is required")
errKeyHexRequired = errors.New("keyHex is required")
errNotRunning = errors.New("olcRTC is not running")
errStoppedBeforeReady = errors.New("olcRTC stopped before becoming ready")
errStartTimedOut = errors.New("olcRTC start timed out")
)
//nolint:gochecknoglobals // Mobile bindings expose a singleton runtime controlled by the embedding app.
var (
mu sync.Mutex
cancel context.CancelFunc
done chan struct{}
ready chan struct{}
errRun error
)
// SetProtector sets the Android VPN socket protector.
// Must be called before Start.
func SetProtector(p SocketProtector) {
if p == nil {
protect.Protector = nil
return
}
protect.Protector = func(fd int) bool {
return p.Protect(fd)
}
}
// SetLogWriter sets a custom log writer for olcRTC output.
func SetLogWriter(w LogWriter) {
if w != nil {
log.SetOutput(&logBridge{w: w})
}
}
// SetDebug enables or disables verbose logging.
func SetDebug(enabled bool) {
logger.SetVerbose(enabled)
if enabled {
log.SetFlags(log.Ltime | log.Lshortfile)
return
}
log.SetFlags(log.Ltime)
}
// Start launches the olcRTC client in background.
// roomID: Telemost room ID (e.g. "xxx-xxx-xxx")
// keyHex: 64-char hex encryption key
// socksPort: local SOCKS5 proxy port (e.g. 10808)
// duo: use dual channels for higher throughput
// socksUser/socksPass: SOCKS5 credentials (empty = no auth).
func Start(roomID, keyHex string, socksPort int, duo bool, socksUser, socksPass string) error {
mu.Lock()
defer mu.Unlock()
switch {
case cancel != nil:
return errAlreadyRunning
case roomID == "":
return errRoomIDRequired
case keyHex == "":
return errKeyHexRequired
}
roomURL := "https://telemost.yandex.ru/j/" + roomID
ctx, cancelFunc := context.WithCancel(context.Background())
cancel = cancelFunc
done = make(chan struct{})
ready = make(chan struct{})
localReady := ready
errRun = nil
var readyOnce sync.Once
go func() {
defer cancelFunc()
err := client.RunWithReady(
ctx,
roomURL,
keyHex,
socksPort,
duo,
"",
socksUser,
socksPass,
func() {
readyOnce.Do(func() {
close(localReady)
})
},
)
mu.Lock()
cancel = nil
errRun = err
mu.Unlock()
close(done)
}()
return nil
}
// WaitReady blocks until the Telemost peers are connected and the local SOCKS5 listener is ready.
//
//nolint:cyclop // The control flow is intentionally linear so mobile callers can observe each startup state clearly.
func WaitReady(timeoutMillis int) error {
mu.Lock()
r := ready
d := done
runErr := errRun
running := cancel != nil
mu.Unlock()
if r == nil {
if runErr != nil {
return runErr
}
return errNotRunning
}
select {
case <-r:
return nil
default:
}
if !running {
if runErr != nil {
return runErr
}
return errStoppedBeforeReady
}
timer := time.NewTimer(time.Duration(timeoutMillis) * time.Millisecond)
defer timer.Stop()
select {
case <-r:
return nil
case <-d:
mu.Lock()
runErr = errRun
mu.Unlock()
if runErr != nil {
return runErr
}
return errStoppedBeforeReady
case <-timer.C:
return errStartTimedOut
}
}
// Stop gracefully stops the olcRTC client.
func Stop() {
mu.Lock()
cancelFunc := cancel
doneCh := done
mu.Unlock()
if cancelFunc == nil {
return
}
cancelFunc()
if doneCh != nil {
<-doneCh
}
}
// IsRunning returns true if the olcRTC client is active.
func IsRunning() bool {
mu.Lock()
defer mu.Unlock()
return cancel != nil
}
// logBridge adapts LogWriter to io.Writer for log package.
type logBridge struct {
w LogWriter
}
func (b *logBridge) Write(p []byte) (int, error) {
b.w.WriteLog(string(p))
return len(p), nil
}

View File

@@ -0,0 +1,79 @@
#!/bin/sh
set -eu
die() {
echo "olcrtc-entrypoint: $*" >&2
exit 1
}
bool_flag() {
case "${1:-}" in
1|true|TRUE|yes|YES|on|ON) return 0 ;;
*) return 1 ;;
esac
}
make_key() {
if command -v od >/dev/null 2>&1; then
od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
else
hexdump -n 32 -e '32/1 "%02x"' /dev/urandom
fi
}
if [ "${1:-}" = "olcrtc" ]; then
shift
fi
if [ "$#" -gt 0 ]; then
exec /usr/local/bin/olcrtc "$@"
fi
mode="${OLCRTC_MODE:-srv}"
room_id="${OLCRTC_ROOM_ID:-${ROOM_ID:-}}"
provider="${OLCRTC_PROVIDER:-telemost}"
data_dir="${OLCRTC_DATA_DIR:-/usr/share/olcrtc}"
dns_server="${OLCRTC_DNS:-1.1.1.1:53}"
key="${OLCRTC_KEY:-${KEY:-}}"
key_file="${OLCRTC_KEY_FILE:-/var/lib/olcrtc/key.hex}"
[ "$mode" = "srv" ] || die "server image defaults to OLCRTC_MODE=srv; got '$mode'"
[ -n "$room_id" ] || die "set OLCRTC_ROOM_ID to the Telemost room id"
if [ -z "$key" ]; then
if [ -s "$key_file" ]; then
key="$(tr -d '[:space:]' < "$key_file")"
else
key="$(make_key)"
umask 077
printf '%s\n' "$key" > "$key_file"
echo "olcrtc-entrypoint: generated encryption key and saved it to $key_file" >&2
echo "olcrtc-entrypoint: OLCRTC_KEY=$key" >&2
fi
fi
case "$key" in
*[!0-9a-fA-F]*)
die "OLCRTC_KEY must be a 64-character hex string"
;;
esac
[ "${#key}" -eq 64 ] || die "OLCRTC_KEY must be 64 hex characters"
set -- /usr/local/bin/olcrtc \
-mode "$mode" \
-provider "$provider" \
-id "$room_id" \
-key "$key" \
-data "$data_dir" \
-dns "$dns_server"
if bool_flag "${OLCRTC_DUO:-}"; then
set -- "$@" -duo
fi
if bool_flag "${OLCRTC_DEBUG:-}"; then
set -- "$@" -debug
fi
exec "$@"

View File

@@ -0,0 +1,8 @@
#!/bin/sh
set -eu
exe="$(readlink /proc/1/exe 2>/dev/null || true)"
case "$exe" in
*/olcrtc) exit 0 ;;
*) exit 1 ;;
esac