Update v1.2.0
This commit is contained in:
@@ -374,15 +374,13 @@ func applySliderSwapsV2(gridSize int, swaps []int) ([]int, error) {
|
||||
}
|
||||
|
||||
func sliderTileRect(bounds image.Rectangle, gridSize int, index int) image.Rectangle {
|
||||
w := bounds.Dx() / gridSize
|
||||
h := bounds.Dy() / gridSize
|
||||
col := index % gridSize
|
||||
row := index / gridSize
|
||||
return image.Rect(
|
||||
bounds.Min.X+col*w,
|
||||
bounds.Min.Y+row*h,
|
||||
bounds.Min.X+(col+1)*w,
|
||||
bounds.Min.Y+(row+1)*h,
|
||||
bounds.Min.X+(col*bounds.Dx())/gridSize,
|
||||
bounds.Min.Y+(row*bounds.Dy())/gridSize,
|
||||
bounds.Min.X+((col+1)*bounds.Dx())/gridSize,
|
||||
bounds.Min.Y+((row+1)*bounds.Dy())/gridSize,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+27
-1
@@ -9,6 +9,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var pktPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, 2048)
|
||||
},
|
||||
}
|
||||
|
||||
func getPktBuf(size int) []byte {
|
||||
b := pktPool.Get().([]byte)
|
||||
if cap(b) < size {
|
||||
b = make([]byte, size)
|
||||
}
|
||||
return b[:size]
|
||||
}
|
||||
|
||||
func putPktBuf(b []byte) {
|
||||
if cap(b) < 2048 {
|
||||
return
|
||||
}
|
||||
pktPool.Put(b[:cap(b)])
|
||||
}
|
||||
|
||||
const (
|
||||
returnChBuf = 384
|
||||
|
||||
@@ -125,13 +146,14 @@ func (d *Dispatcher) readLoop() {
|
||||
d.clientAddr.Store(&addr)
|
||||
atomic.AddInt64(&d.stats.TotalBytesUp, int64(n))
|
||||
|
||||
pkt := make([]byte, n)
|
||||
pkt := getPktBuf(n)
|
||||
copy(pkt, buf[:n])
|
||||
|
||||
d.mu.Lock()
|
||||
nw := len(d.workers)
|
||||
if nw == 0 {
|
||||
d.mu.Unlock()
|
||||
putPktBuf(pkt)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -169,6 +191,7 @@ func (d *Dispatcher) readLoop() {
|
||||
// Все workers перегружены — сдвигаем указатель, пакет дропается
|
||||
d.rrIndex = (idx + 1) % nw
|
||||
d.rrCount = 0
|
||||
putPktBuf(pkt)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
}
|
||||
@@ -184,15 +207,18 @@ func (d *Dispatcher) writeLoop() {
|
||||
case pkt := <-d.ReturnCh:
|
||||
addrPtr := d.clientAddr.Load()
|
||||
if addrPtr == nil {
|
||||
putPktBuf(pkt)
|
||||
continue
|
||||
}
|
||||
addr := *addrPtr
|
||||
if _, err := d.localConn.WriteTo(pkt, addr); err != nil {
|
||||
if d.ctx.Err() != nil {
|
||||
putPktBuf(pkt)
|
||||
return
|
||||
}
|
||||
}
|
||||
atomic.AddInt64(&d.stats.TotalBytesDown, int64(len(pkt)))
|
||||
putPktBuf(pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var groupAuthMutex sync.Mutex
|
||||
|
||||
const (
|
||||
workersPerGroup = 9
|
||||
@@ -32,7 +30,6 @@ func WorkerGroup(
|
||||
getConfig bool,
|
||||
configCh chan<- string,
|
||||
workerIDs []int,
|
||||
cycleDuration time.Duration,
|
||||
pauseFlag *int32,
|
||||
deviceID, password string,
|
||||
stats *Stats,
|
||||
@@ -296,5 +293,4 @@ type Credentials struct {
|
||||
CacheStreamID int
|
||||
}
|
||||
|
||||
// Unused import suppressor
|
||||
var _ = fmt.Sprintf
|
||||
|
||||
|
||||
+3
-5
@@ -13,7 +13,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CaptchaResultChan — канал для получения токена капчи из внешнего решателя (WebView)
|
||||
@@ -284,18 +283,17 @@ func main() {
|
||||
}
|
||||
|
||||
gID := g + 1
|
||||
cycle := time.Duration(defaultCycleSecs) * time.Second
|
||||
var cc chan<- string
|
||||
if isFirst {
|
||||
cc = configCh
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(groupID int, cycleDir time.Duration, isFirstGroup bool, configChan chan<- string, workerIds []int, startHashIndex int, waitR <-chan struct{}, sigR chan<- struct{}) {
|
||||
go func(groupID int, isFirstGroup bool, configChan chan<- string, workerIds []int, startHashIndex int, waitR <-chan struct{}, sigR chan<- struct{}) {
|
||||
defer wg.Done()
|
||||
WorkerGroup(ctx, groupID, startHashIndex, tp, peer, disp, localPort,
|
||||
isFirstGroup, configChan, workerIds, cycleDir, &pauseFlag, *deviceID, *connPassword, stats, waitR, sigR)
|
||||
}(gID, cycle, isFirst, cc, ids, g, myWaitReady, mySignalReady)
|
||||
isFirstGroup, configChan, workerIds, &pauseFlag, *deviceID, *connPassword, stats, waitR, sigR)
|
||||
}(gID, isFirst, cc, ids, g, myWaitReady, mySignalReady)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
+35
-13
@@ -12,6 +12,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
@@ -21,6 +22,24 @@ import (
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
var aeadCache sync.Map
|
||||
|
||||
func getAEAD(key []byte) (cipher.AEAD, error) {
|
||||
if len(key) != wrapKeyLen {
|
||||
return nil, fmt.Errorf("obfs: key must be %d bytes", wrapKeyLen)
|
||||
}
|
||||
keyStr := string(key)
|
||||
if val, ok := aeadCache.Load(keyStr); ok {
|
||||
return val.(cipher.AEAD), nil
|
||||
}
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aeadCache.Store(keyStr, aead)
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
// ─── Configuration ───
|
||||
|
||||
// ObfsConfig holds per-session obfuscation parameters.
|
||||
@@ -43,20 +62,22 @@ func NewObfsConfig() *ObfsConfig {
|
||||
|
||||
// ─── Per-direction state (sequence + timestamp counters) ───
|
||||
|
||||
// ObfsState tracks monotonically increasing RTP sequence number and timestamp.
|
||||
// ObfsState tracks monotonically increasing RTP sequence number and timestamp using a 48-bit packet counter.
|
||||
type ObfsState struct {
|
||||
mu sync.Mutex
|
||||
seq uint16
|
||||
ts uint32
|
||||
mu sync.Mutex
|
||||
initSeq uint16
|
||||
initTs uint32
|
||||
count uint64
|
||||
}
|
||||
|
||||
// NewObfsState creates a state with random initial seq/ts.
|
||||
// NewObfsState creates a state with random initial seq/ts and count=0.
|
||||
func NewObfsState() *ObfsState {
|
||||
var buf [6]byte
|
||||
rand.Read(buf[:])
|
||||
return &ObfsState{
|
||||
seq: binary.BigEndian.Uint16(buf[0:2]),
|
||||
ts: binary.BigEndian.Uint32(buf[2:6]),
|
||||
initSeq: binary.BigEndian.Uint16(buf[0:2]),
|
||||
initTs: binary.BigEndian.Uint32(buf[2:6]),
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +110,13 @@ func obfsWrapPacket(key, payload []byte, cfg *ObfsConfig, state *ObfsState) ([]b
|
||||
}
|
||||
|
||||
state.mu.Lock()
|
||||
seq := state.seq
|
||||
ts := state.ts
|
||||
state.seq++
|
||||
state.ts += 960 // 20ms frame @ 48kHz (OPUS standard)
|
||||
c := state.count
|
||||
state.count++
|
||||
state.mu.Unlock()
|
||||
|
||||
seq := state.initSeq + uint16(c)
|
||||
ts := state.initTs + uint32(c)*960 + uint32(c>>16)
|
||||
|
||||
// Build nonce from RTP fields
|
||||
nonce := obfsBuildNonce(cfg.SSRC, seq, ts)
|
||||
|
||||
@@ -118,7 +140,7 @@ func obfsWrapPacket(key, payload []byte, cfg *ObfsConfig, state *ObfsState) ([]b
|
||||
binary.BigEndian.PutUint32(out[4:8], ts)
|
||||
binary.BigEndian.PutUint32(out[8:12], cfg.SSRC)
|
||||
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
aead, err := getAEAD(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("obfs: cipher init: %w", err)
|
||||
}
|
||||
@@ -178,7 +200,7 @@ func obfsUnwrapPacket(key, wire, dst []byte) (int, error) {
|
||||
|
||||
// Build nonce and decrypt
|
||||
nonce := obfsBuildNonce(ssrc, seq, ts)
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
aead, err := getAEAD(key)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("obfs: cipher init: %w", err)
|
||||
}
|
||||
|
||||
@@ -35,13 +35,7 @@ func LoadProfileFromDisk() (*SavedProfile, error) {
|
||||
return &sp, nil
|
||||
}
|
||||
|
||||
func SaveProfileToDisk(sp SavedProfile) error {
|
||||
data, err := json.MarshalIndent(sp, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(profileFile, data, 0644)
|
||||
}
|
||||
|
||||
|
||||
// profileList contains paired User-Agent and Client Hints strings.
|
||||
var profileList = []Profile{
|
||||
|
||||
@@ -371,7 +371,9 @@ func RunSession(
|
||||
return
|
||||
}
|
||||
_ = dtlsConn.SetWriteDeadline(time.Now().Add(sessionReadTimeout))
|
||||
if _, writeErr := dtlsConn.Write(pkt); writeErr != nil {
|
||||
_, writeErr := dtlsConn.Write(pkt)
|
||||
putPktBuf(pkt)
|
||||
if writeErr != nil {
|
||||
log.Printf("[ВОРКЕР #%d] Ошибка Writer: %v", sessionID, writeErr)
|
||||
return
|
||||
}
|
||||
@@ -403,11 +405,12 @@ func RunSession(
|
||||
continue
|
||||
}
|
||||
|
||||
pkt := make([]byte, n)
|
||||
pkt := getPktBuf(n)
|
||||
copy(pkt, b[:n])
|
||||
select {
|
||||
case d.ReturnCh <- pkt:
|
||||
case <-sessCtx.Done():
|
||||
putPktBuf(pkt)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
|
||||
type Stats struct {
|
||||
ActiveConnections int32
|
||||
Reconnects int64
|
||||
TotalBytesUp int64
|
||||
TotalBytesDown int64
|
||||
CredsErrors int64
|
||||
}
|
||||
|
||||
func NewStats() *Stats {
|
||||
|
||||
Reference in New Issue
Block a user