Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements

This commit is contained in:
Shtorm
2026-08-06 14:19:34 +03:00
parent 051e928b01
commit d6b9f693c4
89 changed files with 15671 additions and 132 deletions

View File

@@ -0,0 +1,185 @@
package wbstream
import (
"context"
"fmt"
"net/http"
"time"
"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"
N "github.com/sagernet/sing/common/network"
)
func ConnectCreator(ctx context.Context, cookieStr, roomID, mode string, readBuf int, dialer N.Dialer, logger logger.ContextLogger) (*tunnel.RelayBridge, string, error) {
deviceID := common.CookieValue(cookieStr, "__wb_device_id")
if deviceID == "" {
return nil, "", fmt.Errorf("wbstream: cookies missing __wb_device_id")
}
cookieHeader := common.FilterCookies(cookieStr, WBStreamCookieAllowlist)
httpClient := common.HttpClient(dialer)
bearer, err := RefreshAccessToken(httpClient, cookieHeader, deviceID)
if err != nil {
return nil, "", fmt.Errorf("wbstream: slide-v3 refresh: %w", err)
}
requestedRoom := ParseRoomID(roomID)
resolvedRoomID, roomToken, accessToken, serverURL, err := AuthAsLoggedIn(httpClient, cookieHeader, bearer, requestedRoom, "Creator")
if err != nil {
return nil, "", fmt.Errorf("wbstream: auth: %w", err)
}
if readBuf <= 0 {
readBuf = 32768
}
if mode == "" {
mode = TunnelModeDC
}
obf, err := tunnel.NewTunnelObfuscator(tunnel.DeriveSecretFromJoinLink(resolvedRoomID))
if err != nil {
return nil, "", fmt.Errorf("wbstream: obfuscator init: %w", err)
}
joinSession := func(token, access, server string) (*Session, <-chan tunnel.DataTunnel) {
tunCh := make(chan tunnel.DataTunnel, 1)
sess := NewSession(SessionConfig{
RoomToken: token,
ServerURL: server,
DisplayName: "Creator",
TunnelMode: mode,
Obfuscator: obf,
Logger: logger,
Dialer: dialer,
RoomID: resolvedRoomID,
AccessToken: access,
ReadBuf: readBuf,
})
sess.OnConnected = func(tun tunnel.DataTunnel) {
select {
case tunCh <- tun:
default:
}
}
return sess, tunCh
}
sess, tunCh := joinSession(roomToken, accessToken, serverURL)
if err := sess.Start(); err != nil {
return nil, "", fmt.Errorf("wbstream: session start: %w", err)
}
var firstTun tunnel.DataTunnel
select {
case firstTun = <-tunCh:
case <-ctx.Done():
sess.Close()
return nil, "", ctx.Err()
case <-time.After(60 * time.Second):
sess.Close()
return nil, "", fmt.Errorf("wbstream: creator tunnel timed out")
}
relay := tunnel.NewRelayBridge(firstTun, "creator", bridgeReadBufFor(firstTun, readBuf), dialer, logger)
go creatorReconnectLoop(ctx, relay, sess, joinSession, httpClient, cookieHeader, deviceID, resolvedRoomID, readBuf, logger)
return relay, APIBase + "/room/" + resolvedRoomID, nil
}
func ConnectJoiner(ctx context.Context, roomID, displayName, mode string, readBuf int, dialer N.Dialer, dnsRouter adapter.DNSRouter, logger logger.ContextLogger) (tunnel.DataTunnel, error) {
roomID = ParseRoomID(roomID)
if displayName == "" {
displayName = "Joiner"
}
if mode == "" {
mode = TunnelModeDC
}
joiner := NewWBStreamJoiner(logger, dialer, dnsRouter, nil)
tunCh := make(chan tunnel.DataTunnel, 1)
joiner.OnConnected = func(tun tunnel.DataTunnel) {
select {
case tunCh <- tun:
default:
}
}
params := fmt.Sprintf(`{"roomId":%q,"displayName":%q,"tunnelMode":%q}`, roomID, displayName, mode)
go joiner.RunWithParams(params)
select {
case tun := <-tunCh:
return tun, nil
case <-ctx.Done():
joiner.Close()
return nil, ctx.Err()
}
}
func creatorReconnectLoop(
ctx context.Context,
relay *tunnel.RelayBridge,
sess *Session,
joinSession func(token, access, server string) (*Session, <-chan tunnel.DataTunnel),
httpClient *http.Client,
cookieHeader, deviceID, roomID string,
readBuf int,
logger logger.ContextLogger,
) {
current := sess
for {
select {
case <-current.Done():
case <-ctx.Done():
return
}
current.Close()
if relay.IsClosed() {
return
}
logger.Debug("wbstream: creator session ended, rejoining")
var newTun tunnel.DataTunnel
for {
select {
case <-ctx.Done():
return
case <-time.After(3 * time.Second):
}
if relay.IsClosed() {
return
}
bearer, err := RefreshAccessToken(httpClient, cookieHeader, deviceID)
if err != nil {
logger.Warn(fmt.Sprintf("wbstream: rejoin token refresh failed: %v, retrying", err))
continue
}
_, roomToken, accessToken, serverURL, err := AuthAsLoggedIn(httpClient, cookieHeader, bearer, roomID, "Creator")
if err != nil {
logger.Warn(fmt.Sprintf("wbstream: rejoin auth failed: %v, retrying", err))
continue
}
newSess, tunCh := joinSession(roomToken, accessToken, serverURL)
if err := newSess.Start(); err != nil {
logger.Warn(fmt.Sprintf("wbstream: rejoin session start failed: %v, retrying", err))
continue
}
select {
case newTun = <-tunCh:
case <-ctx.Done():
newSess.Close()
return
case <-time.After(60 * time.Second):
logger.Warn("wbstream: rejoin tunnel timed out, retrying")
newSess.Close()
continue
}
current = newSess
break
}
relay.SwapTunnel(newTun)
logger.Info(fmt.Sprintf("wbstream: creator tunnel reconnected, buf=%d", bridgeReadBufFor(newTun, readBuf)))
}
}
func bridgeReadBufFor(tun tunnel.DataTunnel, readBuf int) int {
switch tun.(type) {
case *tunnel.DCTunnel, *tunnel.MultiTrackKCPTunnel:
return readBuf
}
return common.VP8BufSize
}