Initial v1.1.8 Commits

This commit is contained in:
amurcanov
2026-05-23 22:18:08 +03:00
commit ac86caaf8b
76 changed files with 15693 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
package main
import (
"bufio"
"context"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
// CaptchaResultChan — канал для получения токена капчи из внешнего решателя (WebView)
var CaptchaResultChan = make(chan string, 1)
var captchaModeValue atomic.Value
func init() {
captchaModeValue.Store("auto")
}
func normalizeCaptchaMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case "auto", "rjs", "wv":
return strings.ToLower(strings.TrimSpace(mode))
default:
return "auto"
}
}
func setCaptchaMode(mode string) string {
normalized := normalizeCaptchaMode(mode)
captchaModeValue.Store(normalized)
return normalized
}
func getCaptchaMode() string {
mode, _ := captchaModeValue.Load().(string)
if mode == "" {
return "auto"
}
return mode
}
// drainCaptchaResult удаляет устаревший результат капчи из канала
func drainCaptchaResult() {
select {
case <-CaptchaResultChan:
default:
}
}
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
setupGlobalResolver()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Сигналы
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
go func() {
select {
case s := <-sig:
log.Printf("[КЛИЕНТ] Сигнал %v, завершаю...", s)
cancel()
case <-ctx.Done():
return
}
select {
case s := <-sig:
log.Printf("[КЛИЕНТ] Повторный %v, принудительный выход", s)
os.Exit(1)
case <-ctx.Done():
}
}()
var pauseFlag int32
// STDIN для PAUSE/RESUME/STOP и CAPTCHA_RESULT
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if !strings.Contains(line, "error:tunnel stopped") {
log.Printf("[STDIN] %s", line)
}
switch {
case line == "PAUSE":
atomic.StoreInt32(&pauseFlag, 1)
case line == "RESUME":
atomic.StoreInt32(&pauseFlag, 0)
case line == "STOP":
cancel()
return
case strings.HasPrefix(line, "CAPTCHA_RESULT|"):
result := strings.TrimPrefix(line, "CAPTCHA_RESULT|")
drainCaptchaResult()
CaptchaResultChan <- result
log.Printf("[КАПЧА] Результат от Kotlin записан в канал")
}
}
}()
host := flag.String("turn", "", "переопределить IP TURN")
port := flag.String("port", "", "переопределить порт TURN")
listen := flag.String("listen", "127.0.0.1:9000", "локальный адрес")
vkHash := flag.String("vk", "", "хеши VK-звонков (через запятую)")
peerAddr := flag.String("peer", "", "адрес:порт VPS сервера")
numW := flag.Int("n", 24, "количество воркеров (кратно 12)")
deviceID := flag.String("device-id", "unknown", "уникальный ID устройства")
connPassword := flag.String("password", "", "пароль подключения")
captchaMode := flag.String("captcha-mode", "auto", "режим обхода капчи (auto/wv/rjs)")
flag.Parse()
activeCaptchaMode := setCaptchaMode(*captchaMode)
if *peerAddr == "" || *vkHash == "" {
log.Fatal("[КЛИЕНТ] Нужны -peer и -vk")
}
peer, err := net.ResolveUDPAddr("udp", *peerAddr)
if err != nil {
log.Fatalf("[КЛИЕНТ] Ошибка разбора пира: %v", err)
}
hashes := ParseHashes(*vkHash)
if len(hashes) == 0 {
log.Fatal("[КЛИЕНТ] Нет хешей VK")
}
if *connPassword == "" {
log.Fatal("[КЛИЕНТ] Нужен -password: WRAP ключ теперь выводится из пароля подключения")
}
// WRAP key
wrapKey, err := deriveWrapKey(*connPassword)
if err != nil {
log.Fatalf("[КЛИЕНТ] WRAP key derive: %v", err)
}
// Лимит воркеров
maxWorkers := 108
if *numW > maxWorkers {
*numW = maxWorkers
}
if *numW < workersPerGroup {
*numW = workersPerGroup
}
*numW = (*numW / workersPerGroup) * workersPerGroup
tp := &TurnParams{
Host: *host,
Port: *port,
Hashes: hashes,
WrapKey: wrapKey,
}
// Слушаем локально
localConn, err := net.ListenPacket("udp", *listen)
if err != nil {
log.Fatalf("[КЛИЕНТ] Ошибка слушателя %s: %v", *listen, err)
}
if uc, ok := localConn.(*net.UDPConn); ok {
_ = uc.SetReadBuffer(socketBufSize)
_ = uc.SetWriteBuffer(socketBufSize)
}
stopLocalConn := context.AfterFunc(ctx, func() { _ = localConn.Close() })
defer stopLocalConn()
_, localPort, _ := net.SplitHostPort(*listen)
if localPort == "" {
localPort = "9000"
}
numGroups := *numW / workersPerGroup
wrapStatus := "OFF"
if len(wrapKey) == wrapKeyLen {
wrapStatus = "ON (password HKDF + RTP AEAD)"
}
captchaStatus := "AUTO: Go v2 x2 -> WBV Auto x2 -> Go v2 x1 -> Manual WBV"
switch activeCaptchaMode {
case "wv":
captchaStatus = "WBV selected in Android"
case "rjs":
captchaStatus = "RJS Go v2 with WBV Auto fallback"
}
log.Println("[КЛИЕНТ] ═══════════════════════════════════════")
log.Printf("[КЛИЕНТ] VK Creds: 2 stable app_id с циклическим fallback")
log.Printf("[КЛИЕНТ] TLS: Chrome 146 fingerprint")
log.Printf("[КЛИЕНТ] Воркеров: %d (групп: %d, по %d)", *numW, numGroups, workersPerGroup)
log.Printf("[КЛИЕНТ] Хешей: %d", len(hashes))
log.Printf("[КЛИЕНТ] Слушаю: %s | Пир: %s", *listen, *peerAddr)
log.Printf("[КЛИЕНТ] Протокол: UDP")
log.Printf("[КЛИЕНТ] WRAP: %s", wrapStatus)
log.Printf("[WRAP] Ключ выведен из пароля, режим RTP AEAD активен")
log.Printf("[КЛИЕНТ] Device ID: %s", *deviceID)
log.Printf("[КЛИЕНТ] Captcha: %s", captchaStatus)
log.Println("[КЛИЕНТ] ═══════════════════════════════════════")
stats := NewStats()
shutdownCh := make(chan struct{})
go func() {
<-ctx.Done()
close(shutdownCh)
}()
go stats.RunLoop(shutdownCh)
disp := NewDispatcher(ctx, localConn, stats)
defer disp.Shutdown()
configCh := make(chan string, 1)
configDone := make(chan struct{})
go func() {
defer close(configDone)
select {
case rawConf, ok := <-configCh:
if !ok || rawConf == "" {
return
}
finalConf := rawConf
if !strings.Contains(finalConf, "MTU =") {
lines := strings.Split(finalConf, "\n")
var newLines []string
for _, line := range lines {
newLines = append(newLines, line)
if strings.TrimSpace(line) == "[Interface]" {
newLines = append(newLines, "MTU = 1280")
}
}
finalConf = strings.Join(newLines, "\n")
}
fmt.Println()
fmt.Println("╔══════════════ WireGuard Конфиг ══════════════╗")
for _, line := range strings.Split(finalConf, "\n") {
fmt.Printf("║ %-44s ║\n", line)
}
fmt.Println("╚══════════════════════════════════════════════╝")
if err := os.WriteFile("wg-turn.conf", []byte(finalConf+"\n"), 0600); err != nil {
log.Printf("[КОНФИГ] Ошибка сохранения: %v", err)
} else {
log.Println("[КОНФИГ] Сохранён в wg-turn.conf")
}
case <-ctx.Done():
}
}()
var wg sync.WaitGroup
workerIDCounter := 1
var prevWaitReady <-chan struct{}
for g := 0; g < numGroups; g++ {
isFirst := (g == 0)
var myWaitReady <-chan struct{}
var mySignalReady chan<- struct{}
if g > 0 {
myWaitReady = prevWaitReady
}
if g < numGroups-1 {
ch := make(chan struct{})
mySignalReady = ch
prevWaitReady = ch
}
ids := make([]int, workersPerGroup)
for i := range ids {
ids[i] = workerIDCounter
workerIDCounter++
}
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{}) {
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)
}
wg.Wait()
close(configCh)
<-configDone
log.Println("[КЛИЕНТ] Все воркеры завершены")
}