mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-10 20:37:15 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
217
transport/call/wbstream/joiner.go
Normal file
217
transport/call/wbstream/joiner.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package wbstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/transport/call/common"
|
||||
"github.com/sagernet/sing-box/transport/call/tunnel"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
const (
|
||||
reconnectInitialDelay = time.Second
|
||||
reconnectMaxDelay = 16 * time.Second
|
||||
)
|
||||
|
||||
type WBStreamJoiner struct {
|
||||
logger logger.ContextLogger
|
||||
OnConnected func(tunnel.DataTunnel)
|
||||
dialer N.Dialer
|
||||
dnsRouter adapter.DNSRouter
|
||||
PCConfig common.PeerConnectionConfigurer
|
||||
|
||||
mu sync.Mutex
|
||||
session *Session
|
||||
closed bool
|
||||
stopCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func NewWBStreamJoiner(logger logger.ContextLogger, dialer N.Dialer, dnsRouter adapter.DNSRouter, pcConfig common.PeerConnectionConfigurer) *WBStreamJoiner {
|
||||
return &WBStreamJoiner{
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
dnsRouter: dnsRouter,
|
||||
PCConfig: pcConfig,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) RunWithParams(jsonParams string) {
|
||||
var params struct {
|
||||
RoomID string `json:"roomId"`
|
||||
DisplayName string `json:"displayName"`
|
||||
TunnelMode string `json:"tunnelMode"`
|
||||
VP8FPS int `json:"vp8Fps"`
|
||||
VP8Batch int `json:"vp8Batch"`
|
||||
DualTrack bool `json:"dualTrack"`
|
||||
Reliable *bool `json:"reliable"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(jsonParams), ¶ms); err != nil {
|
||||
j.logger.Error(fmt.Sprintf("wbstream-joiner: failed to parse params: %v", err))
|
||||
return
|
||||
}
|
||||
if params.RoomID == "" {
|
||||
j.logger.Error("wbstream-joiner: missing roomId")
|
||||
return
|
||||
}
|
||||
if params.DisplayName == "" {
|
||||
params.DisplayName = "Joiner"
|
||||
}
|
||||
reliable := params.Reliable != nil && *params.Reliable
|
||||
httpClient := j.makeHTTPClient()
|
||||
j.logger.Info(fmt.Sprintf("wbstream-joiner: room=%s name=%s vp8Fps=%d vp8Batch=%d dualTrack=%v", params.RoomID, params.DisplayName, params.VP8FPS, params.VP8Batch, params.DualTrack))
|
||||
obf, err := tunnel.NewTunnelObfuscator(tunnel.DeriveSecretFromJoinLink(params.RoomID))
|
||||
if err != nil {
|
||||
j.logger.Error(fmt.Sprintf("wbstream-joiner: obfuscator init failed: %v", err))
|
||||
return
|
||||
}
|
||||
j.logger.Debug(fmt.Sprintf("wbstream-joiner: obf key-source=%q localEpoch=0x%08x", params.RoomID, obf.LocalEpoch()))
|
||||
var settingEngine *webrtc.SettingEngine
|
||||
if j.PCConfig != nil {
|
||||
se := webrtc.SettingEngine{}
|
||||
j.PCConfig.ConfigureSettingEngine(&se)
|
||||
settingEngine = &se
|
||||
}
|
||||
var attempt atomic.Int32
|
||||
j.logger.Info("wbstream-joiner: connecting")
|
||||
if err := j.runOnce(httpClient, params.RoomID, params.DisplayName, params.TunnelMode, obf, settingEngine, params.VP8FPS, params.VP8Batch, params.DualTrack, reliable, &attempt); err != nil {
|
||||
j.logger.Error(fmt.Sprintf("wbstream-joiner: %v", err))
|
||||
return
|
||||
}
|
||||
for {
|
||||
if j.isClosed() {
|
||||
j.logger.Info("wbstream-joiner: stopped")
|
||||
return
|
||||
}
|
||||
j.logger.Info("wbstream-joiner: tunnel lost")
|
||||
if !j.waitBeforeRetry(int(attempt.Load())) {
|
||||
return
|
||||
}
|
||||
attempt.Add(1)
|
||||
if j.isClosed() {
|
||||
return
|
||||
}
|
||||
j.logger.Info(fmt.Sprintf("wbstream-joiner: reconnect attempt #%d", attempt.Load()))
|
||||
if err := j.runOnce(httpClient, params.RoomID, params.DisplayName, params.TunnelMode, obf, settingEngine, params.VP8FPS, params.VP8Batch, params.DualTrack, reliable, &attempt); err != nil {
|
||||
j.logger.Warn(fmt.Sprintf("wbstream-joiner: %v, will retry", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) MarkConfigAcked() {
|
||||
j.mu.Lock()
|
||||
sess := j.session
|
||||
j.mu.Unlock()
|
||||
if sess != nil {
|
||||
sess.MarkConfigAcked()
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) Close() {
|
||||
j.stopOnce.Do(func() { close(j.stopCh) })
|
||||
j.mu.Lock()
|
||||
j.closed = true
|
||||
sess := j.session
|
||||
j.session = nil
|
||||
j.mu.Unlock()
|
||||
if sess != nil {
|
||||
sess.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) runOnce(httpClient *http.Client, roomID, displayName, tunnelMode string, obf *tunnel.TunnelObfuscator, settingEngine *webrtc.SettingEngine, vp8FPS, vp8Batch int, dualTrack, reliable bool, attempt *atomic.Int32) error {
|
||||
_, roomToken, _, serverURL, authErr := AuthAndGetToken(httpClient, roomID, displayName)
|
||||
if authErr != nil {
|
||||
return fmt.Errorf("auth: %w", authErr)
|
||||
}
|
||||
j.logger.Debug(fmt.Sprintf("wbstream-joiner: server=%s", serverURL))
|
||||
sess := NewSession(SessionConfig{
|
||||
RoomToken: roomToken,
|
||||
ServerURL: serverURL,
|
||||
DisplayName: displayName,
|
||||
TunnelMode: tunnelMode,
|
||||
Obfuscator: obf,
|
||||
Logger: j.logger,
|
||||
SettingEngine: settingEngine,
|
||||
Dialer: j.dialer,
|
||||
DNSRouter: j.dnsRouter,
|
||||
VP8FPS: vp8FPS,
|
||||
VP8Batch: vp8Batch,
|
||||
ScreenShare: dualTrack,
|
||||
IsJoiner: true,
|
||||
Reliable: reliable,
|
||||
})
|
||||
sess.OnConnected = func(tun tunnel.DataTunnel) {
|
||||
attempt.Store(0)
|
||||
j.logger.Info("wbstream-joiner: === TUNNEL CONNECTED ===")
|
||||
if j.OnConnected != nil {
|
||||
j.OnConnected(tun)
|
||||
}
|
||||
}
|
||||
j.mu.Lock()
|
||||
if j.closed {
|
||||
j.mu.Unlock()
|
||||
sess.Close()
|
||||
return nil
|
||||
}
|
||||
j.session = sess
|
||||
j.mu.Unlock()
|
||||
if err := sess.Start(); err != nil {
|
||||
j.clearSession(sess)
|
||||
return fmt.Errorf("session: %w", err)
|
||||
}
|
||||
<-sess.Done()
|
||||
sess.Close()
|
||||
j.clearSession(sess)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) waitBeforeRetry(attempt int) bool {
|
||||
delay := common.BackoffWithJitter(attempt, reconnectInitialDelay, reconnectMaxDelay)
|
||||
j.logger.Debug(fmt.Sprintf("wbstream-joiner: waiting %s before reconnect", delay))
|
||||
timer := time.NewTimer(delay)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
return !j.isClosed()
|
||||
case <-j.stopCh:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) clearSession(sess *Session) {
|
||||
j.mu.Lock()
|
||||
if j.session == sess {
|
||||
j.session = nil
|
||||
}
|
||||
j.mu.Unlock()
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) isClosed() bool {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
return j.closed
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) makeDialContext() func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return j.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
}
|
||||
|
||||
func (j *WBStreamJoiner) makeHTTPClient() *http.Client {
|
||||
transport := &http.Transport{DialContext: j.makeDialContext()}
|
||||
return &http.Client{Timeout: 60 * time.Second, Transport: transport}
|
||||
}
|
||||
Reference in New Issue
Block a user