Compare commits
No commits in common. "master" and "v2.0.5" have entirely different histories.
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
| Windows | Linux | macOS |
|
| Windows | Linux | macOS |
|
||||||
|---------|-------|-------|
|
|---------|-------|-------|
|
||||||
| [⬇️ .exe](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy.exe) (9 MB) | [⬇️ amd64](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_linux_amd64) (8.9 MB) | [⬇️ Intel](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_darwin_amd64) / [⬇️ ARM](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_darwin_arm64) |
|
| [⬇️ .exe](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_windows_amd64.exe) (9 MB) | [⬇️ amd64](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_linux_amd64) (8.9 MB) | [⬇️ Intel](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_darwin_amd64) / [⬇️ ARM](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.5/TgWsProxy_darwin_arm64) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
## 🚀 TG WS Proxy Go v2.0.2
|
||||||
|
|
||||||
|
> **Go-переосмысление** [Flowseal/tg-ws-proxy](https://github.com/Flowseal/tg-ws-proxy)
|
||||||
|
> Локальный SOCKS5-прокси для Telegram Desktop на Go
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✨ Что нового в v2.0.2
|
||||||
|
|
||||||
|
| Функция | Статус |
|
||||||
|
|---------|--------|
|
||||||
|
| 🔗 **tg://socks ссылки** | ✅ Работают на Windows |
|
||||||
|
| 📲 **Авто-конфигурация Telegram** | ✅ Открывает настройки прокси |
|
||||||
|
| 🔄 **Автообновление** | ✅ Скачивает новую версию |
|
||||||
|
| 🌐 **IPv6 поддержка** | ✅ Через NAT64 |
|
||||||
|
| 🔐 **SOCKS5 аутентификация** | ✅ --auth username:password |
|
||||||
|
| 🛑 **Авто-закрытие дубликатов** | ✅ Завершает старые экземпляры |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔧 Исправления v2.0.2
|
||||||
|
|
||||||
|
- ✅ **Исправлено:** При запуске второго экземпляра первый автоматически закрывается
|
||||||
|
- ✅ **Улучшено:** Стабильность работы на Windows
|
||||||
|
- ✅ **Добавлено:** Проверка и завершение дублирующихся процессов
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📥 Скачать
|
||||||
|
|
||||||
|
| Платформа | Файл | Размер | Ссылка |
|
||||||
|
|-----------|------|--------|--------|
|
||||||
|
| **Windows x64** | TgWsProxy.exe | 6.6 MB | [⬇️ Скачать](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.2/TgWsProxy_windows_amd64.exe) |
|
||||||
|
| **Linux x64** | TgWsProxy | 6.5 MB | [⬇️ Скачать](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.2/TgWsProxy_linux_amd64) |
|
||||||
|
| **macOS Intel** | TgWsProxy | 6.6 MB | [⬇️ Скачать](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.2/TgWsProxy_darwin_amd64) |
|
||||||
|
| **macOS Apple Silicon** | TgWsProxy | 5.8 MB | [⬇️ Скачать](https://github.com/y0sy4/tg-ws-proxy-go/releases/download/v2.0.2/TgWsProxy_darwin_arm64) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🚀 Быстрый старт
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
1. Скачай `TgWsProxy.exe`
|
||||||
|
2. Запусти
|
||||||
|
3. Telegram автоматически откроет настройки прокси
|
||||||
|
4. Подтверди добавление
|
||||||
|
|
||||||
|
**Linux/macOS:**
|
||||||
|
```bash
|
||||||
|
chmod +x TgWsProxy_*
|
||||||
|
./TgWsProxy_linux_amd64 # или ./TgWsProxy_darwin_amd64
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔧 Командная строка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./TgWsProxy [опции]
|
||||||
|
|
||||||
|
--port int Порт SOCKS5 (default 1080)
|
||||||
|
--host string Хост SOCKS5 (default "127.0.0.1")
|
||||||
|
--dc-ip string DC:IP через запятую
|
||||||
|
--auth string SOCKS5 аутентификация (username:password)
|
||||||
|
-v Подробное логирование
|
||||||
|
--version Показать версию
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📊 Сравнение с Python
|
||||||
|
|
||||||
|
| Метрика | Python | Go |
|
||||||
|
|---------|--------|-----|
|
||||||
|
| Размер | ~50 MB | **~6 MB** ⚡ |
|
||||||
|
| Зависимости | pip | **stdlib** ⚡ |
|
||||||
|
| Запуск | ~500 ms | **~50 ms** ⚡ |
|
||||||
|
| Память | ~50 MB | **~10 MB** ⚡ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔗 Ссылки
|
||||||
|
|
||||||
|
- 📦 **Релизы:** https://github.com/y0sy4/tg-ws-proxy-go/releases
|
||||||
|
- 📖 **Документация:** https://github.com/y0sy4/tg-ws-proxy-go#readme
|
||||||
|
- ❓ **FAQ:** https://github.com/y0sy4/tg-ws-proxy-go/blob/master/FAQ.md
|
||||||
|
- 🐛 **Баги:** https://github.com/y0sy4/tg-ws-proxy-go/issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with ❤️ using Go 1.21** | **License:** MIT
|
||||||
|
|
@ -116,24 +116,15 @@ func main() {
|
||||||
if *auth != "" {
|
if *auth != "" {
|
||||||
cfg.Auth = *auth
|
cfg.Auth = *auth
|
||||||
}
|
}
|
||||||
if *upstreamProxy != "" {
|
|
||||||
cfg.UpstreamProxy = *upstreamProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup logging - log to stdout if verbose, otherwise to file
|
// Setup logging - default to file if not specified
|
||||||
var logger *log.Logger
|
|
||||||
logPath := *logFile
|
logPath := *logFile
|
||||||
if cfg.Verbose && logPath == "" {
|
if logPath == "" {
|
||||||
// Verbose mode: log to stdout
|
// Use default log file in app config directory
|
||||||
logger = setupLogging("", cfg.LogMaxMB, cfg.Verbose)
|
appDir := getAppDir()
|
||||||
} else {
|
logPath = filepath.Join(appDir, "proxy.log")
|
||||||
// File mode: log to file (default to app dir if not specified)
|
|
||||||
if logPath == "" {
|
|
||||||
appDir := getAppDir()
|
|
||||||
logPath = filepath.Join(appDir, "proxy.log")
|
|
||||||
}
|
|
||||||
logger = setupLogging(logPath, cfg.LogMaxMB, cfg.Verbose)
|
|
||||||
}
|
}
|
||||||
|
logger := setupLogging(logPath, cfg.LogMaxMB, cfg.Verbose)
|
||||||
|
|
||||||
// Log advanced features usage and start HTTP proxy
|
// Log advanced features usage and start HTTP proxy
|
||||||
if *httpPort != 0 {
|
if *httpPort != 0 {
|
||||||
|
|
@ -155,7 +146,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start server
|
// Create and start server
|
||||||
server, err := proxy.NewServer(cfg, logger, cfg.UpstreamProxy)
|
server, err := proxy.NewServer(cfg, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create server: %v", err)
|
log.Fatalf("Failed to create server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -257,12 +248,8 @@ func getAppDir() string {
|
||||||
|
|
||||||
func setupLogging(logFile string, logMaxMB float64, verbose bool) *log.Logger {
|
func setupLogging(logFile string, logMaxMB float64, verbose bool) *log.Logger {
|
||||||
flags := log.LstdFlags | log.Lshortfile
|
flags := log.LstdFlags | log.Lshortfile
|
||||||
|
if verbose {
|
||||||
// If verbose and no log file specified, log to stdout
|
flags |= log.Lshortfile
|
||||||
if verbose && logFile == "" {
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
log.SetFlags(flags)
|
|
||||||
return log.New(os.Stdout, "", flags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
|
|
@ -273,8 +260,6 @@ func setupLogging(logFile string, logMaxMB float64, verbose bool) *log.Logger {
|
||||||
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: failed to open log file %s: %v, using stdout", logFile, err)
|
log.Printf("Warning: failed to open log file %s: %v, using stdout", logFile, err)
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
log.SetFlags(flags)
|
|
||||||
return log.New(os.Stdout, "", flags)
|
return log.New(os.Stdout, "", flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -1,5 +1,3 @@
|
||||||
module github.com/Flowseal/tg-ws-proxy
|
module github.com/Flowseal/tg-ws-proxy
|
||||||
|
|
||||||
go 1.25.0
|
go 1.21
|
||||||
|
|
||||||
require golang.org/x/net v0.52.0 // indirect
|
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1,2 +0,0 @@
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
|
||||||
|
|
@ -13,16 +13,15 @@ import (
|
||||||
|
|
||||||
// Config holds the proxy configuration.
|
// Config holds the proxy configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
DCIP []string `json:"dc_ip"`
|
DCIP []string `json:"dc_ip"`
|
||||||
Verbose bool `json:"verbose"`
|
Verbose bool `json:"verbose"`
|
||||||
AutoStart bool `json:"autostart"`
|
AutoStart bool `json:"autostart"`
|
||||||
LogMaxMB float64 `json:"log_max_mb"`
|
LogMaxMB float64 `json:"log_max_mb"`
|
||||||
BufKB int `json:"buf_kb"`
|
BufKB int `json:"buf_kb"`
|
||||||
PoolSize int `json:"pool_size"`
|
PoolSize int `json:"pool_size"`
|
||||||
Auth string `json:"auth"` // username:password
|
Auth string `json:"auth"` // username:password
|
||||||
UpstreamProxy string `json:"upstream_proxy"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default configuration.
|
// DefaultConfig returns the default configuration.
|
||||||
|
|
|
||||||
|
|
@ -10,45 +10,16 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPProxy represents an HTTP proxy server.
|
// HTTPProxy represents an HTTP proxy server.
|
||||||
type HTTPProxy struct {
|
type HTTPProxy struct {
|
||||||
port int
|
port int
|
||||||
verbose bool
|
verbose bool
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
upstreamProxy *url.URL
|
upstreamProxy *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialWithUpstream creates a connection, optionally routing through an upstream proxy.
|
|
||||||
func (h *HTTPProxy) dialWithUpstream(network, addr string) (net.Conn, error) {
|
|
||||||
if h.upstreamProxy == nil {
|
|
||||||
return net.Dial(network, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch h.upstreamProxy.Scheme {
|
|
||||||
case "socks5", "socks":
|
|
||||||
// Use proxy package for SOCKS5
|
|
||||||
proxyDialer, err := proxy.FromURL(h.upstreamProxy, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create SOCKS5 dialer: %w", err)
|
|
||||||
}
|
|
||||||
return proxyDialer.Dial(network, addr)
|
|
||||||
|
|
||||||
case "http", "https":
|
|
||||||
// Use http.Transport with Proxy for HTTP CONNECT
|
|
||||||
transport := &http.Transport{
|
|
||||||
Proxy: http.ProxyURL(h.upstreamProxy),
|
|
||||||
}
|
|
||||||
return transport.Dial(network, addr)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported upstream proxy scheme: %s", h.upstreamProxy.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPProxy creates a new HTTP proxy server.
|
// NewHTTPProxy creates a new HTTP proxy server.
|
||||||
func NewHTTPProxy(port int, verbose bool, logger *log.Logger, upstreamProxyURL string) (*HTTPProxy, error) {
|
func NewHTTPProxy(port int, verbose bool, logger *log.Logger, upstreamProxyURL string) (*HTTPProxy, error) {
|
||||||
var upstreamProxy *url.URL
|
var upstreamProxy *url.URL
|
||||||
|
|
@ -116,18 +87,18 @@ func (h *HTTPProxy) handleConnect(conn net.Conn, req *http.Request) {
|
||||||
if !strings.Contains(host, ":") {
|
if !strings.Contains(host, ":") {
|
||||||
host = host + ":80"
|
host = host + ":80"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to target (with upstream proxy if configured)
|
// Connect to target
|
||||||
target, err := h.dialWithUpstream("tcp", host)
|
target, err := net.Dial("tcp", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
|
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer target.Close()
|
defer target.Close()
|
||||||
|
|
||||||
// Send success response
|
// Send success response
|
||||||
conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
|
conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
|
||||||
|
|
||||||
// Bridge connections
|
// Bridge connections
|
||||||
go io.Copy(target, conn)
|
go io.Copy(target, conn)
|
||||||
io.Copy(conn, target)
|
io.Copy(conn, target)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -22,7 +20,6 @@ import (
|
||||||
"github.com/Flowseal/tg-ws-proxy/internal/pool"
|
"github.com/Flowseal/tg-ws-proxy/internal/pool"
|
||||||
"github.com/Flowseal/tg-ws-proxy/internal/socks5"
|
"github.com/Flowseal/tg-ws-proxy/internal/socks5"
|
||||||
"github.com/Flowseal/tg-ws-proxy/internal/websocket"
|
"github.com/Flowseal/tg-ws-proxy/internal/websocket"
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -149,80 +146,37 @@ func (s *Stats) Summary() string {
|
||||||
|
|
||||||
// Server represents the TG WS Proxy server.
|
// Server represents the TG WS Proxy server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
dcOpt map[int]string
|
dcOpt map[int]string
|
||||||
wsPool *pool.WSPool
|
wsPool *pool.WSPool
|
||||||
stats *Stats
|
stats *Stats
|
||||||
wsBlacklist map[pool.DCKey]bool
|
wsBlacklist map[pool.DCKey]bool
|
||||||
dcFailUntil map[pool.DCKey]time.Time
|
dcFailUntil map[pool.DCKey]time.Time
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
upstreamProxy string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new proxy server.
|
// NewServer creates a new proxy server.
|
||||||
func NewServer(cfg *config.Config, logger *log.Logger, upstreamProxy string) (*Server, error) {
|
func NewServer(cfg *config.Config, logger *log.Logger) (*Server, error) {
|
||||||
dcOpt, err := config.ParseDCIPList(cfg.DCIP)
|
dcOpt, err := config.ParseDCIPList(cfg.DCIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
dcOpt: dcOpt,
|
dcOpt: dcOpt,
|
||||||
wsPool: pool.NewWSPool(cfg.PoolSize, defaultPoolMaxAge),
|
wsPool: pool.NewWSPool(cfg.PoolSize, defaultPoolMaxAge),
|
||||||
stats: &Stats{},
|
stats: &Stats{},
|
||||||
wsBlacklist: make(map[pool.DCKey]bool),
|
wsBlacklist: make(map[pool.DCKey]bool),
|
||||||
dcFailUntil: make(map[pool.DCKey]time.Time),
|
dcFailUntil: make(map[pool.DCKey]time.Time),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
upstreamProxy: upstreamProxy,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialWithUpstream creates a connection, optionally routing through an upstream proxy.
|
|
||||||
func (s *Server) dialWithUpstream(network, addr string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
if s.upstreamProxy == "" {
|
|
||||||
return net.DialTimeout(network, addr, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse upstream proxy URL
|
|
||||||
u, err := url.Parse(s.upstreamProxy)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse upstream proxy: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch u.Scheme {
|
|
||||||
case "socks5", "socks":
|
|
||||||
var auth *proxy.Auth
|
|
||||||
if u.User != nil {
|
|
||||||
password, _ := u.User.Password()
|
|
||||||
auth = &proxy.Auth{
|
|
||||||
User: u.User.Username(),
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialer, err := proxy.SOCKS5(network, u.Host, auth, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create SOCKS5 dialer: %w", err)
|
|
||||||
}
|
|
||||||
return dialer.Dial(network, addr)
|
|
||||||
|
|
||||||
case "http", "https":
|
|
||||||
// Use http.Transport with Proxy for HTTP CONNECT
|
|
||||||
transport := &http.Transport{
|
|
||||||
Proxy: http.ProxyURL(u),
|
|
||||||
TLSHandshakeTimeout: timeout,
|
|
||||||
}
|
|
||||||
return transport.Dial(network, addr)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported upstream proxy scheme: %s", u.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the proxy server.
|
// Start starts the proxy server.
|
||||||
func (s *Server) Start(ctx context.Context) error {
|
func (s *Server) Start(ctx context.Context) error {
|
||||||
addr := net.JoinHostPort(s.config.Host, fmt.Sprintf("%d", s.config.Port))
|
addr := net.JoinHostPort(s.config.Host, fmt.Sprintf("%d", s.config.Port))
|
||||||
|
|
@ -453,9 +407,7 @@ func (s *Server) getWebSocket(dcKey pool.DCKey, targetIP string, domains []strin
|
||||||
s.logInfo("[%s] DC%d%s (%s:%d) -> %s via %s", label, dc, mediaTag, dst, port, url, targetIP)
|
s.logInfo("[%s] DC%d%s (%s:%d) -> %s via %s", label, dc, mediaTag, dst, port, url, targetIP)
|
||||||
|
|
||||||
// Connect using targetIP, but use domain for TLS handshake
|
// Connect using targetIP, but use domain for TLS handshake
|
||||||
ws, wsErr = websocket.ConnectWithDialer(targetIP, domain, "/apiws", wsTimeout, func(network, addr string) (net.Conn, error) {
|
ws, wsErr = websocket.Connect(targetIP, domain, "/apiws", wsTimeout)
|
||||||
return s.dialWithUpstream(network, addr, wsTimeout)
|
|
||||||
})
|
|
||||||
if wsErr == nil {
|
if wsErr == nil {
|
||||||
allRedirects = false
|
allRedirects = false
|
||||||
break
|
break
|
||||||
|
|
@ -498,7 +450,7 @@ func (s *Server) getWebSocket(dcKey pool.DCKey, targetIP string, domains []strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePassthrough(conn net.Conn, dst string, port uint16, label string) {
|
func (s *Server) handlePassthrough(conn net.Conn, dst string, port uint16, label string) {
|
||||||
remoteConn, err := s.dialWithUpstream("tcp", net.JoinHostPort(dst, fmt.Sprintf("%d", port)), 10*time.Second)
|
remoteConn, err := net.DialTimeout("tcp", net.JoinHostPort(dst, fmt.Sprintf("%d", port)), 10*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logWarning("[%s] passthrough failed to %s: %v", label, dst, err)
|
s.logWarning("[%s] passthrough failed to %s: %v", label, dst, err)
|
||||||
conn.Write(socks5.Reply(socks5.ReplyFail))
|
conn.Write(socks5.Reply(socks5.ReplyFail))
|
||||||
|
|
@ -513,7 +465,7 @@ func (s *Server) handlePassthrough(conn net.Conn, dst string, port uint16, label
|
||||||
// handleIPv6Connection handles IPv6 connections via dual-stack or IPv4-mapped addresses.
|
// handleIPv6Connection handles IPv6 connections via dual-stack or IPv4-mapped addresses.
|
||||||
func (s *Server) handleIPv6Connection(conn net.Conn, ipv6Addr string, port uint16, label string) {
|
func (s *Server) handleIPv6Connection(conn net.Conn, ipv6Addr string, port uint16, label string) {
|
||||||
// Try direct IPv6 first
|
// Try direct IPv6 first
|
||||||
remoteConn, err := s.dialWithUpstream("tcp6", net.JoinHostPort(ipv6Addr, fmt.Sprintf("%d", port)), 10*time.Second)
|
remoteConn, err := net.DialTimeout("tcp6", net.JoinHostPort(ipv6Addr, fmt.Sprintf("%d", port)), 10*time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.logInfo("[%s] IPv6 direct connection successful", label)
|
s.logInfo("[%s] IPv6 direct connection successful", label)
|
||||||
defer remoteConn.Close()
|
defer remoteConn.Close()
|
||||||
|
|
@ -573,7 +525,7 @@ func extractIPv4FromNAT64(ipv6, prefix string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleTCPFallback(conn net.Conn, dst string, port uint16, init []byte, label string, dc int, isMedia bool) {
|
func (s *Server) handleTCPFallback(conn net.Conn, dst string, port uint16, init []byte, label string, dc int, isMedia bool) {
|
||||||
remoteConn, err := s.dialWithUpstream("tcp", net.JoinHostPort(dst, fmt.Sprintf("%d", port)), 10*time.Second)
|
remoteConn, err := net.DialTimeout("tcp", net.JoinHostPort(dst, fmt.Sprintf("%d", port)), 10*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logWarning("[%s] TCP fallback to %s:%d failed: %v", label, dst, port, err)
|
s.logWarning("[%s] TCP fallback to %s:%d failed: %v", label, dst, port, err)
|
||||||
return
|
return
|
||||||
|
|
@ -720,9 +672,7 @@ func (s *Server) warmupPool() {
|
||||||
go func(dcKey pool.DCKey, targetIP string, domains []string) {
|
go func(dcKey pool.DCKey, targetIP string, domains []string) {
|
||||||
for s.wsPool.NeedRefill(dcKey) {
|
for s.wsPool.NeedRefill(dcKey) {
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
ws, err := websocket.ConnectWithDialer(targetIP, domain, "/apiws", wsConnectTimeout, func(network, addr string) (net.Conn, error) {
|
ws, err := websocket.Connect(targetIP, domain, "/apiws", wsConnectTimeout)
|
||||||
return s.dialWithUpstream(network, addr, wsConnectTimeout)
|
|
||||||
})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.wsPool.Put(dcKey, ws)
|
s.wsPool.Put(dcKey, ws)
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,6 @@ type WebSocket struct {
|
||||||
|
|
||||||
// Connect establishes a WebSocket connection to the given domain via IP.
|
// Connect establishes a WebSocket connection to the given domain via IP.
|
||||||
func Connect(ip, domain, path string, timeout time.Duration) (*WebSocket, error) {
|
func Connect(ip, domain, path string, timeout time.Duration) (*WebSocket, error) {
|
||||||
return ConnectWithDialer(ip, domain, path, timeout, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectWithDialer establishes a WebSocket connection using a custom dialer.
|
|
||||||
// If dialer is nil, it uses direct connection.
|
|
||||||
func ConnectWithDialer(ip, domain, path string, timeout time.Duration, dialFunc func(network, addr string) (net.Conn, error)) (*WebSocket, error) {
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = "/apiws"
|
path = "/apiws"
|
||||||
}
|
}
|
||||||
|
|
@ -62,55 +56,18 @@ func ConnectWithDialer(ip, domain, path string, timeout time.Duration, dialFunc
|
||||||
wsKey := base64.StdEncoding.EncodeToString(keyBytes)
|
wsKey := base64.StdEncoding.EncodeToString(keyBytes)
|
||||||
|
|
||||||
// Dial TLS connection
|
// Dial TLS connection
|
||||||
var rawConn net.Conn
|
dialer := &net.Dialer{Timeout: timeout}
|
||||||
var err error
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: domain,
|
||||||
if dialFunc != nil {
|
InsecureSkipVerify: true,
|
||||||
// Use custom dialer
|
|
||||||
rawConn, err = dialFunc("tcp", net.JoinHostPort(ip, "443"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dial: %w", err)
|
|
||||||
}
|
|
||||||
// Wrap with TLS
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: domain,
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
rawConn = tls.Client(rawConn, tlsConfig)
|
|
||||||
// Set handshake timeout
|
|
||||||
if err := rawConn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
|
||||||
rawConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Direct connection
|
|
||||||
dialer := &net.Dialer{Timeout: timeout}
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: domain,
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
rawConn, err = tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(ip, "443"), tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("tls dial: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
rawConn, err := tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(ip, "443"), tlsConfig)
|
||||||
// Clear deadline after handshake
|
if err != nil {
|
||||||
if err := rawConn.SetDeadline(time.Time{}); err != nil {
|
return nil, fmt.Errorf("tls dial: %w", err)
|
||||||
rawConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set TCP_NODELAY and buffer sizes
|
// Set TCP_NODELAY and buffer sizes
|
||||||
if tcpConn, ok := rawConn.(*tls.Conn); ok {
|
if tcpConn, ok := rawConn.NetConn().(*net.TCPConn); ok {
|
||||||
if netConn := tcpConn.NetConn(); netConn != nil {
|
|
||||||
if tcpNetConn, ok := netConn.(*net.TCPConn); ok {
|
|
||||||
tcpNetConn.SetNoDelay(true)
|
|
||||||
tcpNetConn.SetReadBuffer(256 * 1024)
|
|
||||||
tcpNetConn.SetWriteBuffer(256 * 1024)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
|
||||||
tcpConn.SetNoDelay(true)
|
tcpConn.SetNoDelay(true)
|
||||||
tcpConn.SetReadBuffer(256 * 1024)
|
tcpConn.SetReadBuffer(256 * 1024)
|
||||||
tcpConn.SetWriteBuffer(256 * 1024)
|
tcpConn.SetWriteBuffer(256 * 1024)
|
||||||
|
|
@ -158,7 +115,7 @@ func ConnectWithDialer(ip, domain, path string, timeout time.Duration, dialFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
return &WebSocket{
|
return &WebSocket{
|
||||||
conn: rawConn.(*tls.Conn),
|
conn: rawConn,
|
||||||
reader: reader,
|
reader: reader,
|
||||||
writer: bufio.NewWriter(rawConn),
|
writer: bufio.NewWriter(rawConn),
|
||||||
maskKey: make([]byte, 4),
|
maskKey: make([]byte, 4),
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func Start(host string, port int, dcIP string, verbose bool) string {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
ctx, cancel = context.WithCancel(context.Background())
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
server, err = proxy.NewServer(cfg, logger, "")
|
server, err = proxy.NewServer(cfg, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return fmt.Sprintf("Failed to create server: %v", err)
|
return fmt.Sprintf("Failed to create server: %v", err)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue