mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-07 06:15:15 +03:00
279 lines
5.6 KiB
Go
279 lines
5.6 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/transport/call/common"
|
|
"github.com/sagernet/sing/common/logger"
|
|
)
|
|
|
|
const (
|
|
screenWriterFPS = 24
|
|
screenWriterBatch = 30
|
|
screenWriterMaxBytes = 60000
|
|
screenWriterQueue = 256
|
|
screenKeepalivePadMax = 48
|
|
)
|
|
|
|
type ScreenWriter struct {
|
|
obf *TunnelObfuscator
|
|
logger logger.ContextLogger
|
|
label string
|
|
|
|
sendMu sync.Mutex
|
|
send func([]byte) error
|
|
|
|
stopCh chan struct{}
|
|
sendQueue chan []byte
|
|
cfgChan chan struct{}
|
|
stopOnce sync.Once
|
|
running atomic.Bool
|
|
|
|
cfgMu sync.Mutex
|
|
fps int
|
|
batch int
|
|
sent atomic.Uint64
|
|
}
|
|
|
|
func NewScreenWriter(obf *TunnelObfuscator, label string, logger logger.ContextLogger) *ScreenWriter {
|
|
return &ScreenWriter{
|
|
obf: obf,
|
|
logger: logger,
|
|
label: label,
|
|
stopCh: make(chan struct{}),
|
|
sendQueue: make(chan []byte, screenWriterQueue),
|
|
cfgChan: make(chan struct{}, 1),
|
|
fps: screenWriterFPS,
|
|
batch: screenWriterBatch,
|
|
}
|
|
}
|
|
|
|
func (w *ScreenWriter) SetSend(fn func([]byte) error) {
|
|
w.sendMu.Lock()
|
|
w.send = fn
|
|
w.sendMu.Unlock()
|
|
}
|
|
|
|
func (w *ScreenWriter) SendData(data []byte) {
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
select {
|
|
case w.sendQueue <- data:
|
|
case <-w.stopCh:
|
|
}
|
|
}
|
|
|
|
func (w *ScreenWriter) Reconfigure(fps, batch int) {
|
|
if fps <= 0 && batch <= 0 {
|
|
return
|
|
}
|
|
w.cfgMu.Lock()
|
|
changed := false
|
|
if fps > 0 && w.fps != fps {
|
|
w.fps = fps
|
|
changed = true
|
|
}
|
|
if batch > 0 && w.batch != batch {
|
|
w.batch = batch
|
|
changed = true
|
|
}
|
|
w.cfgMu.Unlock()
|
|
if changed {
|
|
select {
|
|
case w.cfgChan <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *ScreenWriter) Start() {
|
|
if !w.running.CompareAndSwap(false, true) {
|
|
return
|
|
}
|
|
go w.writerLoop()
|
|
}
|
|
|
|
func (w *ScreenWriter) Stop() {
|
|
if !w.running.CompareAndSwap(true, false) {
|
|
return
|
|
}
|
|
w.stopOnce.Do(func() { close(w.stopCh) })
|
|
}
|
|
|
|
func (w *ScreenWriter) interval() time.Duration {
|
|
w.cfgMu.Lock()
|
|
fps, batch := w.fps, w.batch
|
|
w.cfgMu.Unlock()
|
|
frame := time.Second / time.Duration(fps)
|
|
sample := frame
|
|
if batch > 1 {
|
|
sample = frame / time.Duration(batch)
|
|
}
|
|
if sample <= 0 {
|
|
sample = time.Millisecond
|
|
}
|
|
return sample
|
|
}
|
|
|
|
func (w *ScreenWriter) nextKeepalive(sample time.Duration) (ticks, padLen int) {
|
|
ticks = int(common.DurationInRange(keepaliveIdleMin, keepaliveIdleMax) / sample)
|
|
if ticks < 1 {
|
|
ticks = 1
|
|
}
|
|
return ticks, common.IntInRange(0, screenKeepalivePadMax)
|
|
}
|
|
|
|
func (w *ScreenWriter) emit(msg []byte) {
|
|
if msg == nil || len(msg) > screenWriterMaxBytes {
|
|
return
|
|
}
|
|
w.sendMu.Lock()
|
|
send := w.send
|
|
w.sendMu.Unlock()
|
|
if send == nil {
|
|
return
|
|
}
|
|
if err := send(msg); err != nil {
|
|
return
|
|
}
|
|
n := w.sent.Add(1)
|
|
if n <= 5 || n%500 == 0 {
|
|
w.logger.Debug(fmt.Sprintf("[%s] sent frame #%d size=%d", w.label, n, len(msg)))
|
|
}
|
|
}
|
|
|
|
func (w *ScreenWriter) writerLoop() {
|
|
for {
|
|
sample := w.interval()
|
|
keepaliveEvery, keepalivePad := w.nextKeepalive(sample)
|
|
ticker := time.NewTicker(sample)
|
|
idle := 0
|
|
reconfigure := false
|
|
for !reconfigure {
|
|
select {
|
|
case <-w.stopCh:
|
|
ticker.Stop()
|
|
return
|
|
case <-w.cfgChan:
|
|
reconfigure = true
|
|
case <-ticker.C:
|
|
select {
|
|
case data := <-w.sendQueue:
|
|
w.emit(w.obf.EncodeData(data))
|
|
idle = 0
|
|
default:
|
|
idle++
|
|
if idle < keepaliveEvery {
|
|
continue
|
|
}
|
|
idle = 0
|
|
w.emit(w.obf.EncodeKeepalive(keepalivePad))
|
|
keepaliveEvery, keepalivePad = w.nextKeepalive(sample)
|
|
}
|
|
}
|
|
}
|
|
ticker.Stop()
|
|
}
|
|
}
|
|
|
|
type SymmetricScreenTunnel struct {
|
|
cam *VP8DataTunnel
|
|
screen *ScreenWriter
|
|
obf *TunnelObfuscator
|
|
logger logger.ContextLogger
|
|
screenReady func() bool
|
|
|
|
onDataMu sync.Mutex
|
|
onData func([]byte)
|
|
recv atomic.Uint64
|
|
trackCount atomic.Int32
|
|
}
|
|
|
|
func NewSymmetricScreenTunnel(cam *VP8DataTunnel, screen *ScreenWriter, obf *TunnelObfuscator, screenReady func() bool, logger logger.ContextLogger) *SymmetricScreenTunnel {
|
|
return &SymmetricScreenTunnel{cam: cam, screen: screen, obf: obf, screenReady: screenReady, logger: logger}
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) SetTrackCount(n int) {
|
|
if n < 1 {
|
|
n = 1
|
|
}
|
|
if n > 2 {
|
|
n = 2
|
|
}
|
|
old := s.trackCount.Swap(int32(n))
|
|
if int(old) != n {
|
|
s.logger.Debug(fmt.Sprintf("screen tunnel track count %d -> %d", old, n))
|
|
}
|
|
if n >= 2 {
|
|
s.screen.Start()
|
|
}
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) SendData(data []byte) {
|
|
var connID uint32
|
|
if len(data) >= 8 {
|
|
connID = binary.BigEndian.Uint32(data[4:8])
|
|
}
|
|
if connID == ControlConnID {
|
|
s.cam.SendData(data)
|
|
return
|
|
}
|
|
tc := uint32(s.trackCount.Load())
|
|
if tc < 1 {
|
|
tc = 1
|
|
}
|
|
if connID%tc == 1 && s.screenUp() {
|
|
s.screen.SendData(data)
|
|
return
|
|
}
|
|
s.cam.SendData(data)
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) SetOnData(fn func([]byte)) {
|
|
s.onDataMu.Lock()
|
|
s.onData = fn
|
|
s.onDataMu.Unlock()
|
|
s.cam.SetOnData(fn)
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) SetOnClose(fn func()) { s.cam.SetOnClose(fn) }
|
|
|
|
func (s *SymmetricScreenTunnel) Reconfigure(fps, batch int) {
|
|
s.cam.Reconfigure(fps, batch)
|
|
s.screen.Reconfigure(fps, batch)
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) Stop() {
|
|
s.screen.Stop()
|
|
s.cam.Stop()
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) HandleScreenFrame(frame []byte) {
|
|
res := s.obf.Decode(frame)
|
|
n := s.recv.Add(1)
|
|
if n <= 10 || n%500 == 0 {
|
|
s.logger.Debug(fmt.Sprintf("screen recv frame #%d in=%d hasFrame=%v keepalive=%v payload=%d", n, len(frame), res.HasFrame, res.Keepalive, len(res.Payload)))
|
|
}
|
|
if !res.HasFrame || res.SelfEcho || res.Keepalive || len(res.Payload) == 0 {
|
|
return
|
|
}
|
|
s.onDataMu.Lock()
|
|
handler := s.onData
|
|
s.onDataMu.Unlock()
|
|
if handler != nil {
|
|
handler(res.Payload)
|
|
}
|
|
}
|
|
|
|
func (s *SymmetricScreenTunnel) screenUp() bool {
|
|
if s.screenReady == nil {
|
|
return true
|
|
}
|
|
return s.screenReady()
|
|
}
|