Update v1.2.0

This commit is contained in:
amurcanov
2026-05-26 22:48:52 +03:00
parent 63ba2cf1d9
commit bc0c8f5fc9
33 changed files with 1689 additions and 546 deletions
+4 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+1 -7
View File
@@ -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{
+5 -2
View File
@@ -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
}
}
-2
View File
@@ -8,10 +8,8 @@ import (
type Stats struct {
ActiveConnections int32
Reconnects int64
TotalBytesUp int64
TotalBytesDown int64
CredsErrors int64
}
func NewStats() *Stats {