mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-26 07:08:11 +00:00
Merge branch 'openlibrecommunity:master' into docker-local-20260523
This commit is contained in:
@@ -63,6 +63,7 @@ type failoverConfig struct {
|
|||||||
func main() {
|
func main() {
|
||||||
if err := run(); err != nil {
|
if err := run(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
flushStderrFilter()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,12 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stderrFilterOnce sync.Once //nolint:gochecknoglobals // process-wide stderr fd filter
|
var (
|
||||||
|
stderrFilterOnce sync.Once //nolint:gochecknoglobals // process-wide stderr fd filter
|
||||||
|
stderrPipeWriter *os.File //nolint:gochecknoglobals // process-wide stderr fd filter
|
||||||
|
stderrFilterDone chan struct{} //nolint:gochecknoglobals // process-wide stderr fd filter
|
||||||
|
stderrFilterActive bool //nolint:gochecknoglobals // process-wide stderr fd filter
|
||||||
|
)
|
||||||
|
|
||||||
func installStderrFilter() {
|
func installStderrFilter() {
|
||||||
stderrFilterOnce.Do(func() {
|
stderrFilterOnce.Do(func() {
|
||||||
@@ -30,13 +35,29 @@ func installStderrFilter() {
|
|||||||
_ = unix.Close(origFD)
|
_ = unix.Close(origFD)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = writer.Close()
|
stderrPipeWriter = writer
|
||||||
|
stderrFilterDone = make(chan struct{})
|
||||||
|
stderrFilterActive = true
|
||||||
os.Stderr = os.NewFile(uintptr(unix.Stderr), "/dev/stderr")
|
os.Stderr = os.NewFile(uintptr(unix.Stderr), "/dev/stderr")
|
||||||
orig := os.NewFile(uintptr(origFD), "/dev/stderr-original")
|
orig := os.NewFile(uintptr(origFD), "/dev/stderr-original")
|
||||||
go copyFilteredStderr(reader, orig)
|
go func() {
|
||||||
|
defer close(stderrFilterDone)
|
||||||
|
copyFilteredStderr(reader, orig)
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushStderrFilter closes the pipe write ends so the filter goroutine
|
||||||
|
// sees EOF and drains any buffered output before the process exits.
|
||||||
|
func flushStderrFilter() {
|
||||||
|
if !stderrFilterActive {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = stderrPipeWriter.Close()
|
||||||
|
_ = unix.Close(unix.Stderr)
|
||||||
|
<-stderrFilterDone
|
||||||
|
}
|
||||||
|
|
||||||
func copyFilteredStderr(reader *os.File, out io.Writer) {
|
func copyFilteredStderr(reader *os.File, out io.Writer) {
|
||||||
defer func() { _ = reader.Close() }()
|
defer func() { _ = reader.Close() }()
|
||||||
br := bufio.NewReader(reader)
|
br := bufio.NewReader(reader)
|
||||||
|
|||||||
@@ -3,3 +3,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
func installStderrFilter() {}
|
func installStderrFilter() {}
|
||||||
|
|
||||||
|
func flushStderrFilter() {}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ olcrtc /etc/olcrtc/client.yaml
|
|||||||
| `socks.host` / `socks.port` | локальный SOCKS5 listener в `mode: cnc` |
|
| `socks.host` / `socks.port` | локальный SOCKS5 listener в `mode: cnc` |
|
||||||
| `socks.user` / `socks.pass` | необязательная auth для входящих SOCKS5-подключений |
|
| `socks.user` / `socks.pass` | необязательная auth для входящих SOCKS5-подключений |
|
||||||
| `socks.proxy_addr` / `socks.proxy_port` | исходящий SOCKS5-прокси на серверной стороне |
|
| `socks.proxy_addr` / `socks.proxy_port` | исходящий SOCKS5-прокси на серверной стороне |
|
||||||
|
| `socks.proxy_user` / `socks.proxy_pass` | необязательная auth для upstream-прокси (RFC 1929) |
|
||||||
| `engine.name` / `engine.url` / `engine.token` | прямой engine-режим, только при `auth.provider: none` |
|
| `engine.name` / `engine.url` / `engine.token` | прямой engine-режим, только при `auth.provider: none` |
|
||||||
| `video.*` | настройки `videochannel` |
|
| `video.*` | настройки `videochannel` |
|
||||||
| `vp8.*` | настройки `vp8channel` |
|
| `vp8.*` | настройки `vp8channel` |
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
data: data
|
data: data
|
||||||
debug: false
|
debug: false
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
sei:
|
sei:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
video:
|
video:
|
||||||
width: 1920
|
width: 1920
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
vp8:
|
vp8:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
data: data
|
data: data
|
||||||
debug: false
|
debug: false
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
sei:
|
sei:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
video:
|
video:
|
||||||
width: 1920
|
width: 1920
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
vp8:
|
vp8:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
data: data
|
data: data
|
||||||
debug: false
|
debug: false
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
sei:
|
sei:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
video:
|
video:
|
||||||
width: 1920
|
width: 1920
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ liveness:
|
|||||||
socks:
|
socks:
|
||||||
proxy_addr: "" # например "127.0.0.1"
|
proxy_addr: "" # например "127.0.0.1"
|
||||||
proxy_port: 0 # например 1080
|
proxy_port: 0 # например 1080
|
||||||
|
proxy_user: "" # необязательная auth для upstream-прокси (RFC 1929)
|
||||||
|
proxy_pass: ""
|
||||||
|
|
||||||
vp8:
|
vp8:
|
||||||
fps: 60
|
fps: 60
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ transport. Используй одинаковые traffic-настройки н
|
|||||||
|-----------|----------|
|
|-----------|----------|
|
||||||
| `socks.proxy_addr` | Адрес SOCKS5-прокси для исходящего трафика сервера |
|
| `socks.proxy_addr` | Адрес SOCKS5-прокси для исходящего трафика сервера |
|
||||||
| `socks.proxy_port` | Порт этого прокси |
|
| `socks.proxy_port` | Порт этого прокси |
|
||||||
|
| `socks.proxy_user` | Логин для аутентификации на upstream-прокси (необязательно) |
|
||||||
|
| `socks.proxy_pass` | Пароль для аутентификации на upstream-прокси (необязательно) |
|
||||||
|
|
||||||
|
Если `socks.proxy_user` пуст - сервер ходит к прокси без аутентификации (метод `0x00`).
|
||||||
|
Если задан - используется username/password auth по RFC 1929 (`proxy_pass` опционален, может быть пустым).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/xtaci/kcp-go/v5 v5.6.72
|
github.com/xtaci/kcp-go/v5 v5.6.72
|
||||||
github.com/xtaci/smux v1.5.57
|
github.com/xtaci/smux v1.5.57
|
||||||
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0
|
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0
|
||||||
github.com/zarazaex69/j v0.0.0-20260518222913-cb593e3bc582
|
github.com/zarazaex69/j v0.0.0-20260523204249-4015c3c4de75
|
||||||
golang.org/x/crypto v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b
|
golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b
|
||||||
golang.org/x/sys v0.43.0
|
golang.org/x/sys v0.43.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -235,8 +235,8 @@ github.com/xtaci/smux v1.5.57/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX8
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0 h1:dMjHX/YPV3ZD/KJKFjQdlMBwj2/rZIuOVKOvGv26m9k=
|
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0 h1:dMjHX/YPV3ZD/KJKFjQdlMBwj2/rZIuOVKOvGv26m9k=
|
||||||
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0/go.mod h1:7vALI2tjaLTOGiDKV7V2JkVU9bA1YADBDQA6uvpp1ac=
|
github.com/zarazaex69/gr v0.0.0-20260430043628-45b595f4fef0/go.mod h1:7vALI2tjaLTOGiDKV7V2JkVU9bA1YADBDQA6uvpp1ac=
|
||||||
github.com/zarazaex69/j v0.0.0-20260518222913-cb593e3bc582 h1:5ZvS/7kBTqTMKMjMO3S/4neE4YHRoYKbQdx/4y8Kobc=
|
github.com/zarazaex69/j v0.0.0-20260523204249-4015c3c4de75 h1:VoS7CB/151kq8zrsI2keb/SuqIquF7LVtVnDRYLijC8=
|
||||||
github.com/zarazaex69/j v0.0.0-20260518222913-cb593e3bc582/go.mod h1:7/ypJTenOIPx23fpo5uF7l4u+rxZqg9cFbTL/N77Ktc=
|
github.com/zarazaex69/j v0.0.0-20260523204249-4015c3c4de75/go.mod h1:7/ypJTenOIPx23fpo5uF7l4u+rxZqg9cFbTL/N77Ktc=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ type Config struct {
|
|||||||
DNSServer string
|
DNSServer string
|
||||||
SOCKSProxyAddr string
|
SOCKSProxyAddr string
|
||||||
SOCKSProxyPort int
|
SOCKSProxyPort int
|
||||||
|
SOCKSProxyUser string
|
||||||
|
SOCKSProxyPass string
|
||||||
Video VideoConfig
|
Video VideoConfig
|
||||||
VP8 VP8Config
|
VP8 VP8Config
|
||||||
SEI SEIConfig
|
SEI SEIConfig
|
||||||
@@ -652,6 +654,8 @@ func runOnce(
|
|||||||
DNSServer: cfg.DNSServer,
|
DNSServer: cfg.DNSServer,
|
||||||
SOCKSProxyAddr: cfg.SOCKSProxyAddr,
|
SOCKSProxyAddr: cfg.SOCKSProxyAddr,
|
||||||
SOCKSProxyPort: cfg.SOCKSProxyPort,
|
SOCKSProxyPort: cfg.SOCKSProxyPort,
|
||||||
|
SOCKSProxyUser: cfg.SOCKSProxyUser,
|
||||||
|
SOCKSProxyPass: cfg.SOCKSProxyPass,
|
||||||
TransportOptions: opts,
|
TransportOptions: opts,
|
||||||
Engine: cfg.Engine,
|
Engine: cfg.Engine,
|
||||||
URL: cfg.URL,
|
URL: cfg.URL,
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ type SOCKS struct {
|
|||||||
Pass string `yaml:"pass"`
|
Pass string `yaml:"pass"`
|
||||||
ProxyAddr string `yaml:"proxy_addr"`
|
ProxyAddr string `yaml:"proxy_addr"`
|
||||||
ProxyPort int `yaml:"proxy_port"`
|
ProxyPort int `yaml:"proxy_port"`
|
||||||
|
ProxyUser string `yaml:"proxy_user"`
|
||||||
|
ProxyPass string `yaml:"proxy_pass"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine selects a direct SFU connection when Auth.Provider is "none".
|
// Engine selects a direct SFU connection when Auth.Provider is "none".
|
||||||
@@ -262,6 +264,8 @@ func Apply(dst session.Config, f File) session.Config {
|
|||||||
dst.DNSServer = pickString(dst.DNSServer, f.Net.DNS)
|
dst.DNSServer = pickString(dst.DNSServer, f.Net.DNS)
|
||||||
dst.SOCKSProxyAddr = pickString(dst.SOCKSProxyAddr, f.SOCKS.ProxyAddr)
|
dst.SOCKSProxyAddr = pickString(dst.SOCKSProxyAddr, f.SOCKS.ProxyAddr)
|
||||||
dst.SOCKSProxyPort = pickInt(dst.SOCKSProxyPort, f.SOCKS.ProxyPort)
|
dst.SOCKSProxyPort = pickInt(dst.SOCKSProxyPort, f.SOCKS.ProxyPort)
|
||||||
|
dst.SOCKSProxyUser = pickString(dst.SOCKSProxyUser, f.SOCKS.ProxyUser)
|
||||||
|
dst.SOCKSProxyPass = pickString(dst.SOCKSProxyPass, f.SOCKS.ProxyPass)
|
||||||
dst.Video.Width = pickInt(dst.Video.Width, f.Video.Width)
|
dst.Video.Width = pickInt(dst.Video.Width, f.Video.Width)
|
||||||
dst.Video.Height = pickInt(dst.Video.Height, f.Video.Height)
|
dst.Video.Height = pickInt(dst.Video.Height, f.Video.Height)
|
||||||
dst.Video.FPS = pickInt(dst.Video.FPS, f.Video.FPS)
|
dst.Video.FPS = pickInt(dst.Video.FPS, f.Video.FPS)
|
||||||
@@ -307,6 +311,8 @@ func ApplyProfile(base session.Config, p Profile) session.Config {
|
|||||||
dst.DNSServer = overlayString(dst.DNSServer, p.Net.DNS)
|
dst.DNSServer = overlayString(dst.DNSServer, p.Net.DNS)
|
||||||
dst.SOCKSProxyAddr = overlayString(dst.SOCKSProxyAddr, p.SOCKS.ProxyAddr)
|
dst.SOCKSProxyAddr = overlayString(dst.SOCKSProxyAddr, p.SOCKS.ProxyAddr)
|
||||||
dst.SOCKSProxyPort = overlayInt(dst.SOCKSProxyPort, p.SOCKS.ProxyPort)
|
dst.SOCKSProxyPort = overlayInt(dst.SOCKSProxyPort, p.SOCKS.ProxyPort)
|
||||||
|
dst.SOCKSProxyUser = overlayString(dst.SOCKSProxyUser, p.SOCKS.ProxyUser)
|
||||||
|
dst.SOCKSProxyPass = overlayString(dst.SOCKSProxyPass, p.SOCKS.ProxyPass)
|
||||||
dst.Video.Width = overlayInt(dst.Video.Width, p.Video.Width)
|
dst.Video.Width = overlayInt(dst.Video.Width, p.Video.Width)
|
||||||
dst.Video.Height = overlayInt(dst.Video.Height, p.Video.Height)
|
dst.Video.Height = overlayInt(dst.Video.Height, p.Video.Height)
|
||||||
dst.Video.FPS = overlayInt(dst.Video.FPS, p.Video.FPS)
|
dst.Video.FPS = overlayInt(dst.Video.FPS, p.Video.FPS)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -37,6 +38,11 @@ import (
|
|||||||
// -olcrtc.local-soak-duration=12h \
|
// -olcrtc.local-soak-duration=12h \
|
||||||
// -timeout=13h
|
// -timeout=13h
|
||||||
//
|
//
|
||||||
|
// To pump every built-in transport sequentially in a single run pass
|
||||||
|
// `-olcrtc.local-soak-transport=all` (or a comma-separated subset like
|
||||||
|
// `datachannel,vp8channel`). Each transport gets its own subtest and its
|
||||||
|
// own full -olcrtc.local-soak-duration window.
|
||||||
|
//
|
||||||
// The test is gated by -olcrtc.local-soak so it never runs in regular CI.
|
// The test is gated by -olcrtc.local-soak so it never runs in regular CI.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -53,7 +59,8 @@ var (
|
|||||||
localSoakTransport = flag.String( //nolint:gochecknoglobals // package-level state intentional
|
localSoakTransport = flag.String( //nolint:gochecknoglobals // package-level state intentional
|
||||||
"olcrtc.local-soak-transport",
|
"olcrtc.local-soak-transport",
|
||||||
transportData,
|
transportData,
|
||||||
"transport to pump through: datachannel|videochannel|seichannel|vp8channel",
|
"transport(s) to pump through: datachannel|videochannel|seichannel|vp8channel, "+
|
||||||
|
"or 'all', or a comma-separated subset (e.g. datachannel,vp8channel)",
|
||||||
)
|
)
|
||||||
localSoakChunk = flag.Int( //nolint:gochecknoglobals // package-level state intentional
|
localSoakChunk = flag.Int( //nolint:gochecknoglobals // package-level state intentional
|
||||||
"olcrtc.local-soak-chunk",
|
"olcrtc.local-soak-chunk",
|
||||||
@@ -72,7 +79,12 @@ var (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
var errLocalSoakPayloadMismatch = errors.New("local soak payload mismatch")
|
var (
|
||||||
|
errLocalSoakPayloadMismatch = errors.New("local soak payload mismatch")
|
||||||
|
errLocalSoakTransportEmpty = errors.New("empty transport value")
|
||||||
|
errLocalSoakTransportNone = errors.New("no transports listed")
|
||||||
|
errLocalSoakTransportUnknown = errors.New("unknown transport")
|
||||||
|
)
|
||||||
|
|
||||||
// TestLocalThroughputSoak pumps a deterministic byte pattern through a
|
// TestLocalThroughputSoak pumps a deterministic byte pattern through a
|
||||||
// locally-built tunnel for -olcrtc.local-soak-duration and reports
|
// locally-built tunnel for -olcrtc.local-soak-duration and reports
|
||||||
@@ -90,15 +102,34 @@ func TestLocalThroughputSoak(t *testing.T) {
|
|||||||
t.Fatalf("invalid -olcrtc.local-soak-chunk=%d", *localSoakChunk)
|
t.Fatalf("invalid -olcrtc.local-soak-chunk=%d", *localSoakChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transports, err := resolveLocalSoakTransports(*localSoakTransport)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid -olcrtc.local-soak-transport=%q: %v", *localSoakTransport, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, transportName := range transports {
|
||||||
|
t.Run(transportName, func(t *testing.T) {
|
||||||
|
runLocalSoakOnce(t, transportName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runLocalSoakOnce builds a fresh tunnel for transportName and pumps it
|
||||||
|
// for one full -olcrtc.local-soak-duration window. Each subtest gets its
|
||||||
|
// own carrier, SOCKS port and goroutines via t.Cleanup, so transports
|
||||||
|
// don't share state and a leak in one of them won't poison the next.
|
||||||
|
func runLocalSoakOnce(t *testing.T, transportName string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
// Connection setup itself can be slow (first WebRTC handshake on
|
// Connection setup itself can be slow (first WebRTC handshake on
|
||||||
// some transports), so don't fold it into the duration budget.
|
// some transports), so don't fold it into the duration budget.
|
||||||
const setupBudget = 30 * time.Second
|
const setupBudget = 30 * time.Second
|
||||||
|
|
||||||
t.Logf("[soak] transport=%s duration=%s chunk=%d verify=%t progress=%s",
|
t.Logf("[soak] transport=%s duration=%s chunk=%d verify=%t progress=%s",
|
||||||
*localSoakTransport, *localSoakDuration, *localSoakChunk,
|
transportName, *localSoakDuration, *localSoakChunk,
|
||||||
*localSoakVerify, *localSoakProgress)
|
*localSoakVerify, *localSoakProgress)
|
||||||
|
|
||||||
rt := startLocalSoakTunnel(t, *localSoakTransport)
|
rt := startLocalSoakTunnel(t, transportName)
|
||||||
echoAddr := startEchoServer(t)
|
echoAddr := startEchoServer(t)
|
||||||
|
|
||||||
conn, err := connectViaSOCKSWithin(rt.socksAddr, echoAddr, setupBudget)
|
conn, err := connectViaSOCKSWithin(rt.socksAddr, echoAddr, setupBudget)
|
||||||
@@ -120,7 +151,7 @@ func TestLocalThroughputSoak(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("[soak] DONE transport=%s elapsed=%s sent=%s recv=%s send=%s/s recv=%s/s",
|
t.Logf("[soak] DONE transport=%s elapsed=%s sent=%s recv=%s send=%s/s recv=%s/s",
|
||||||
*localSoakTransport,
|
transportName,
|
||||||
stats.elapsed.Round(time.Second),
|
stats.elapsed.Round(time.Second),
|
||||||
humanBytes(stats.sent),
|
humanBytes(stats.sent),
|
||||||
humanBytes(stats.recv),
|
humanBytes(stats.recv),
|
||||||
@@ -129,6 +160,44 @@ func TestLocalThroughputSoak(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveLocalSoakTransports turns the -olcrtc.local-soak-transport flag
|
||||||
|
// value into an ordered, deduplicated list of built-in transport names.
|
||||||
|
// Accepts "all" as a shorthand for builtInTransportNames(), or a
|
||||||
|
// comma-separated subset (with whitespace tolerated around items).
|
||||||
|
func resolveLocalSoakTransports(value string) ([]string, error) {
|
||||||
|
trimmed := strings.TrimSpace(value)
|
||||||
|
if trimmed == "" {
|
||||||
|
return nil, errLocalSoakTransportEmpty
|
||||||
|
}
|
||||||
|
if strings.EqualFold(trimmed, "all") {
|
||||||
|
return builtInTransportNames(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
known := make(map[string]struct{}, 4)
|
||||||
|
for _, name := range builtInTransportNames() {
|
||||||
|
known[name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
items := splitTestList(trimmed)
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil, errLocalSoakTransportNone
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(items))
|
||||||
|
out := make([]string, 0, len(items))
|
||||||
|
for _, name := range items {
|
||||||
|
if _, ok := known[name]; !ok {
|
||||||
|
return nil, fmt.Errorf("%w: %q", errLocalSoakTransportUnknown, name)
|
||||||
|
}
|
||||||
|
if _, dup := seen[name]; dup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[name] = struct{}{}
|
||||||
|
out = append(out, name)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// startLocalSoakTunnel mirrors startTunnel but lets the caller pick the
|
// startLocalSoakTunnel mirrors startTunnel but lets the caller pick the
|
||||||
// transport (the original is hard-coded to datachannel).
|
// transport (the original is hard-coded to datachannel).
|
||||||
func startLocalSoakTunnel(t *testing.T, transportName string) *tunnelRuntime {
|
func startLocalSoakTunnel(t *testing.T, transportName string) *tunnelRuntime {
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ type Server struct {
|
|||||||
resolver *net.Resolver
|
resolver *net.Resolver
|
||||||
socksProxyAddr string
|
socksProxyAddr string
|
||||||
socksProxyPort int
|
socksProxyPort int
|
||||||
|
socksProxyUser string
|
||||||
|
socksProxyPass string
|
||||||
liveness control.Config
|
liveness control.Config
|
||||||
health *runtime.HealthTracker
|
health *runtime.HealthTracker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
@@ -110,6 +112,8 @@ type Config struct {
|
|||||||
DNSServer string
|
DNSServer string
|
||||||
SOCKSProxyAddr string
|
SOCKSProxyAddr string
|
||||||
SOCKSProxyPort int
|
SOCKSProxyPort int
|
||||||
|
SOCKSProxyUser string
|
||||||
|
SOCKSProxyPass string
|
||||||
TransportOptions transport.Options
|
TransportOptions transport.Options
|
||||||
Engine string
|
Engine string
|
||||||
URL string
|
URL string
|
||||||
@@ -166,6 +170,8 @@ func Run(ctx context.Context, cfg Config) error {
|
|||||||
dnsServer: cfg.DNSServer,
|
dnsServer: cfg.DNSServer,
|
||||||
socksProxyAddr: cfg.SOCKSProxyAddr,
|
socksProxyAddr: cfg.SOCKSProxyAddr,
|
||||||
socksProxyPort: cfg.SOCKSProxyPort,
|
socksProxyPort: cfg.SOCKSProxyPort,
|
||||||
|
socksProxyUser: cfg.SOCKSProxyUser,
|
||||||
|
socksProxyPass: cfg.SOCKSProxyPass,
|
||||||
liveness: cfg.Liveness,
|
liveness: cfg.Liveness,
|
||||||
health: runtime.NewHealthTracker(cfg.OnHealth),
|
health: runtime.NewHealthTracker(cfg.OnHealth),
|
||||||
peerSessions: make(map[string]*peerSession),
|
peerSessions: make(map[string]*peerSession),
|
||||||
@@ -914,16 +920,8 @@ func (s *Server) dial(req ConnectRequest) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) socks5Connect(conn net.Conn, targetAddr string, targetPort int) error {
|
func (s *Server) socks5Connect(conn net.Conn, targetAddr string, targetPort int) error {
|
||||||
if _, err := conn.Write([]byte{5, 1, 0}); err != nil {
|
if err := s.socks5Authenticate(conn); err != nil {
|
||||||
return fmt.Errorf("failed to write socks5 auth: %w", err)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
resp := make([]byte, 2)
|
|
||||||
if _, err := io.ReadFull(conn, resp); err != nil {
|
|
||||||
return fmt.Errorf("failed to read socks5 auth resp: %w", err)
|
|
||||||
}
|
|
||||||
if resp[0] != 5 || resp[1] != 0 {
|
|
||||||
return ErrSocks5AuthFailed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addrLen := len(targetAddr)
|
addrLen := len(targetAddr)
|
||||||
@@ -941,7 +939,7 @@ func (s *Server) socks5Connect(conn net.Conn, targetAddr string, targetPort int)
|
|||||||
return fmt.Errorf("failed to write socks5 connect req: %w", err)
|
return fmt.Errorf("failed to write socks5 connect req: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = make([]byte, 10)
|
resp := make([]byte, 10)
|
||||||
if _, err := io.ReadFull(conn, resp); err != nil {
|
if _, err := io.ReadFull(conn, resp); err != nil {
|
||||||
return fmt.Errorf("failed to read socks5 connect resp: %w", err)
|
return fmt.Errorf("failed to read socks5 connect resp: %w", err)
|
||||||
}
|
}
|
||||||
@@ -951,3 +949,63 @@ func (s *Server) socks5Connect(conn net.Conn, targetAddr string, targetPort int)
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) socks5Authenticate(conn net.Conn) error {
|
||||||
|
if s.socksProxyUser != "" {
|
||||||
|
// Offer username/password auth (RFC 1929) only.
|
||||||
|
if _, err := conn.Write([]byte{5, 1, 2}); err != nil {
|
||||||
|
return fmt.Errorf("failed to write socks5 auth: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No authentication.
|
||||||
|
if _, err := conn.Write([]byte{5, 1, 0}); err != nil {
|
||||||
|
return fmt.Errorf("failed to write socks5 auth: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(conn, resp); err != nil {
|
||||||
|
return fmt.Errorf("failed to read socks5 auth resp: %w", err)
|
||||||
|
}
|
||||||
|
if resp[0] != 5 {
|
||||||
|
return ErrSocks5AuthFailed
|
||||||
|
}
|
||||||
|
switch resp[1] {
|
||||||
|
case 0: // no auth accepted
|
||||||
|
if s.socksProxyUser != "" {
|
||||||
|
return ErrSocks5AuthFailed
|
||||||
|
}
|
||||||
|
case 2: // username/password
|
||||||
|
return s.socks5SendCredentials(conn)
|
||||||
|
default:
|
||||||
|
return ErrSocks5AuthFailed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) socks5SendCredentials(conn net.Conn) error {
|
||||||
|
user := s.socksProxyUser
|
||||||
|
pass := s.socksProxyPass
|
||||||
|
if len(user) > 255 {
|
||||||
|
user = user[:255]
|
||||||
|
}
|
||||||
|
if len(pass) > 255 {
|
||||||
|
pass = pass[:255]
|
||||||
|
}
|
||||||
|
authMsg := make([]byte, 0, 3+len(user)+len(pass))
|
||||||
|
authMsg = append(authMsg, 1, byte(len(user))) //nolint:gosec // G115: len clamped to ≤255 above
|
||||||
|
authMsg = append(authMsg, []byte(user)...)
|
||||||
|
authMsg = append(authMsg, byte(len(pass))) //nolint:gosec // G115: len clamped to ≤255 above
|
||||||
|
authMsg = append(authMsg, []byte(pass)...)
|
||||||
|
if _, err := conn.Write(authMsg); err != nil {
|
||||||
|
return fmt.Errorf("failed to write socks5 credentials: %w", err)
|
||||||
|
}
|
||||||
|
authResp := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(conn, authResp); err != nil {
|
||||||
|
return fmt.Errorf("failed to read socks5 credentials resp: %w", err)
|
||||||
|
}
|
||||||
|
if authResp[1] != 0 {
|
||||||
|
return ErrSocks5AuthFailed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ type Config struct {
|
|||||||
DNSServer string // resolver used for target dials, e.g. "8.8.8.8:53"
|
DNSServer string // resolver used for target dials, e.g. "8.8.8.8:53"
|
||||||
SOCKSProxyAddr string // optional outbound SOCKS5 proxy host
|
SOCKSProxyAddr string // optional outbound SOCKS5 proxy host
|
||||||
SOCKSProxyPort int // optional outbound SOCKS5 proxy port
|
SOCKSProxyPort int // optional outbound SOCKS5 proxy port
|
||||||
|
SOCKSProxyUser string // optional username for SOCKS5 proxy auth (RFC 1929)
|
||||||
|
SOCKSProxyPass string // optional password for SOCKS5 proxy auth (RFC 1929)
|
||||||
|
|
||||||
// --- transport tuning ---
|
// --- transport tuning ---
|
||||||
// TransportOptions carries transport-specific tuning. Use the Options
|
// TransportOptions carries transport-specific tuning. Use the Options
|
||||||
@@ -128,6 +130,8 @@ func (s *Server) Run(ctx context.Context) error {
|
|||||||
DNSServer: s.cfg.DNSServer,
|
DNSServer: s.cfg.DNSServer,
|
||||||
SOCKSProxyAddr: s.cfg.SOCKSProxyAddr,
|
SOCKSProxyAddr: s.cfg.SOCKSProxyAddr,
|
||||||
SOCKSProxyPort: s.cfg.SOCKSProxyPort,
|
SOCKSProxyPort: s.cfg.SOCKSProxyPort,
|
||||||
|
SOCKSProxyUser: s.cfg.SOCKSProxyUser,
|
||||||
|
SOCKSProxyPass: s.cfg.SOCKSProxyPass,
|
||||||
TransportOptions: s.cfg.TransportOptions,
|
TransportOptions: s.cfg.TransportOptions,
|
||||||
AuthHook: s.cfg.AuthHook,
|
AuthHook: s.cfg.AuthHook,
|
||||||
OnSessionOpen: s.cfg.OnSessionOpen,
|
OnSessionOpen: s.cfg.OnSessionOpen,
|
||||||
|
|||||||
Reference in New Issue
Block a user