Files
MasterHttpRelayVPN/internal/config/config.go
T

463 lines
13 KiB
Go

// ==============================================================================
// MasterHttpRelayVPN
// Author: MasterkinG32
// Github: https://github.com/masterking32
// Year: 2026
// ==============================================================================
package config
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Config struct {
AESEncryptionKey string
RelayURL string
HTTPUserAgentsFile string
HTTPHeaderProfile string
HTTPRandomizeHeaders bool
HTTPPaddingHeader string
HTTPPaddingMinBytes int
HTTPPaddingMaxBytes int
HTTPReferer string
HTTPAcceptLanguage string
HTTPTimingJitterMS int
HTTPBatchRandomize bool
HTTPBatchPacketsJitter int
HTTPBatchBytesJitter int
ServerHost string
ServerPort int
SOCKSHost string
SOCKSPort int
SOCKSAuth bool
SOCKSUsername string
SOCKSPassword string
LogLevel string
MaxChunkSize int
MaxPacketsPerBatch int
MaxBatchBytes int
WorkerCount int
HTTPRequestTimeoutMS int
WorkerPollIntervalMS int
IdlePollIntervalMS int
MaxQueueBytesPerSOCKS int
AckTimeoutMS int
MaxRetryCount int
ReorderTimeoutMS int
MaxReorderBufferPackets int
SessionIdleTimeoutMS int
SOCKSIdleTimeoutMS int
ReadBodyLimitBytes int
MaxServerQueueBytes int
}
func Load(path string) (Config, error) {
cfg := Config{
SOCKSHost: "127.0.0.1",
SOCKSPort: 1080,
HTTPUserAgentsFile: "user-agents.txt",
HTTPHeaderProfile: "browser",
HTTPRandomizeHeaders: true,
HTTPPaddingHeader: "X-Padding",
HTTPPaddingMinBytes: 16,
HTTPPaddingMaxBytes: 48,
HTTPTimingJitterMS: 50,
HTTPBatchRandomize: true,
HTTPBatchPacketsJitter: 4,
HTTPBatchBytesJitter: 32768,
ServerHost: "127.0.0.1",
ServerPort: 28080,
LogLevel: "INFO",
MaxChunkSize: 16 * 1024,
MaxPacketsPerBatch: 32,
MaxBatchBytes: 256 * 1024,
WorkerCount: 4,
HTTPRequestTimeoutMS: 15000,
WorkerPollIntervalMS: 200,
IdlePollIntervalMS: 1000,
MaxQueueBytesPerSOCKS: 1024 * 1024,
AckTimeoutMS: 5000,
MaxRetryCount: 5,
ReorderTimeoutMS: 5000,
MaxReorderBufferPackets: 128,
SessionIdleTimeoutMS: 5 * 60 * 1000,
SOCKSIdleTimeoutMS: 2 * 60 * 1000,
ReadBodyLimitBytes: 2 * 1024 * 1024,
MaxServerQueueBytes: 2 * 1024 * 1024,
}
file, err := os.Open(path)
if err != nil {
return Config{}, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
return Config{}, fmt.Errorf("invalid config line: %q", line)
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
switch key {
case "AES_ENCRYPTION_KEY":
cfg.AESEncryptionKey = trimString(value)
case "RELAY_URL":
cfg.RelayURL = trimString(value)
case "HTTP_USER_AGENTS_FILE":
cfg.HTTPUserAgentsFile = trimString(value)
case "HTTP_HEADER_PROFILE":
cfg.HTTPHeaderProfile = trimString(value)
case "HTTP_RANDOMIZE_HEADERS":
randomize, err := strconv.ParseBool(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_RANDOMIZE_HEADERS: %w", err)
}
cfg.HTTPRandomizeHeaders = randomize
case "HTTP_PADDING_HEADER":
cfg.HTTPPaddingHeader = trimString(value)
case "HTTP_PADDING_MIN_BYTES":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_PADDING_MIN_BYTES: %w", err)
}
cfg.HTTPPaddingMinBytes = size
case "HTTP_PADDING_MAX_BYTES":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_PADDING_MAX_BYTES: %w", err)
}
cfg.HTTPPaddingMaxBytes = size
case "HTTP_REFERER":
cfg.HTTPReferer = trimString(value)
case "HTTP_ACCEPT_LANGUAGE":
cfg.HTTPAcceptLanguage = trimString(value)
case "HTTP_TIMING_JITTER_MS":
valueInt, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_TIMING_JITTER_MS: %w", err)
}
cfg.HTTPTimingJitterMS = valueInt
case "HTTP_BATCH_RANDOMIZE":
randomize, err := strconv.ParseBool(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_BATCH_RANDOMIZE: %w", err)
}
cfg.HTTPBatchRandomize = randomize
case "HTTP_BATCH_PACKETS_JITTER":
valueInt, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_BATCH_PACKETS_JITTER: %w", err)
}
cfg.HTTPBatchPacketsJitter = valueInt
case "HTTP_BATCH_BYTES_JITTER":
valueInt, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_BATCH_BYTES_JITTER: %w", err)
}
cfg.HTTPBatchBytesJitter = valueInt
case "SERVER_HOST":
cfg.ServerHost = trimString(value)
case "SERVER_PORT":
port, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse SERVER_PORT: %w", err)
}
cfg.ServerPort = port
case "SOCKS_HOST":
cfg.SOCKSHost = trimString(value)
case "SOCKS_PORT":
port, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse SOCKS_PORT: %w", err)
}
cfg.SOCKSPort = port
case "SOCKS_AUTH":
auth, err := strconv.ParseBool(value)
if err != nil {
return Config{}, fmt.Errorf("parse SOCKS_AUTH: %w", err)
}
cfg.SOCKSAuth = auth
case "SOCKS_USERNAME":
cfg.SOCKSUsername = trimString(value)
case "SOCKS_PASSWORD":
cfg.SOCKSPassword = trimString(value)
case "LOG_LEVEL":
cfg.LogLevel = trimString(value)
case "MAX_CHUNK_SIZE":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_CHUNK_SIZE: %w", err)
}
cfg.MaxChunkSize = size
case "MAX_PACKETS_PER_BATCH":
count, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_PACKETS_PER_BATCH: %w", err)
}
cfg.MaxPacketsPerBatch = count
case "MAX_BATCH_BYTES":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_BATCH_BYTES: %w", err)
}
cfg.MaxBatchBytes = size
case "WORKER_COUNT":
count, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse WORKER_COUNT: %w", err)
}
cfg.WorkerCount = count
case "HTTP_REQUEST_TIMEOUT_MS":
timeout, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse HTTP_REQUEST_TIMEOUT_MS: %w", err)
}
cfg.HTTPRequestTimeoutMS = timeout
case "WORKER_POLL_INTERVAL_MS":
interval, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse WORKER_POLL_INTERVAL_MS: %w", err)
}
cfg.WorkerPollIntervalMS = interval
case "IDLE_POLL_INTERVAL_MS":
interval, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse IDLE_POLL_INTERVAL_MS: %w", err)
}
cfg.IdlePollIntervalMS = interval
case "MAX_QUEUE_BYTES_PER_SOCKS":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_QUEUE_BYTES_PER_SOCKS: %w", err)
}
cfg.MaxQueueBytesPerSOCKS = size
case "ACK_TIMEOUT_MS":
timeout, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse ACK_TIMEOUT_MS: %w", err)
}
cfg.AckTimeoutMS = timeout
case "MAX_RETRY_COUNT":
count, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_RETRY_COUNT: %w", err)
}
cfg.MaxRetryCount = count
case "REORDER_TIMEOUT_MS":
timeout, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse REORDER_TIMEOUT_MS: %w", err)
}
cfg.ReorderTimeoutMS = timeout
case "MAX_REORDER_BUFFER_PACKETS":
count, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_REORDER_BUFFER_PACKETS: %w", err)
}
cfg.MaxReorderBufferPackets = count
case "SESSION_IDLE_TIMEOUT_MS":
timeout, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse SESSION_IDLE_TIMEOUT_MS: %w", err)
}
cfg.SessionIdleTimeoutMS = timeout
case "SOCKS_IDLE_TIMEOUT_MS":
timeout, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse SOCKS_IDLE_TIMEOUT_MS: %w", err)
}
cfg.SOCKSIdleTimeoutMS = timeout
case "READ_BODY_LIMIT_BYTES":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse READ_BODY_LIMIT_BYTES: %w", err)
}
cfg.ReadBodyLimitBytes = size
case "MAX_SERVER_QUEUE_BYTES":
size, err := strconv.Atoi(value)
if err != nil {
return Config{}, fmt.Errorf("parse MAX_SERVER_QUEUE_BYTES: %w", err)
}
cfg.MaxServerQueueBytes = size
}
}
if err := scanner.Err(); err != nil {
return Config{}, err
}
return cfg, nil
}
func (c Config) ValidateClient() error {
if err := c.validateShared(); err != nil {
return err
}
if c.SOCKSAuth && (c.SOCKSUsername == "" || c.SOCKSPassword == "") {
return fmt.Errorf("SOCKS auth enabled but username/password missing")
}
if c.SOCKSPort < 1 || c.SOCKSPort > 65535 {
return fmt.Errorf("invalid SOCKS_PORT: %d", c.SOCKSPort)
}
if strings.TrimSpace(c.RelayURL) == "" {
return fmt.Errorf("RELAY_URL is required")
}
if c.HTTPRequestTimeoutMS < 1 {
return fmt.Errorf("invalid HTTP_REQUEST_TIMEOUT_MS: %d", c.HTTPRequestTimeoutMS)
}
if c.WorkerPollIntervalMS < 1 {
return fmt.Errorf("invalid WORKER_POLL_INTERVAL_MS: %d", c.WorkerPollIntervalMS)
}
if c.IdlePollIntervalMS < c.WorkerPollIntervalMS {
return fmt.Errorf("IDLE_POLL_INTERVAL_MS must be >= WORKER_POLL_INTERVAL_MS")
}
if c.AckTimeoutMS < 1 {
return fmt.Errorf("invalid ACK_TIMEOUT_MS: %d", c.AckTimeoutMS)
}
if c.MaxRetryCount < 0 {
return fmt.Errorf("invalid MAX_RETRY_COUNT: %d", c.MaxRetryCount)
}
if c.ReorderTimeoutMS < 1 {
return fmt.Errorf("invalid REORDER_TIMEOUT_MS: %d", c.ReorderTimeoutMS)
}
if c.MaxReorderBufferPackets < 1 {
return fmt.Errorf("invalid MAX_REORDER_BUFFER_PACKETS: %d", c.MaxReorderBufferPackets)
}
if c.HTTPHeaderProfile != "browser" && c.HTTPHeaderProfile != "cdn" && c.HTTPHeaderProfile != "api" && c.HTTPHeaderProfile != "minimal" {
return fmt.Errorf("invalid HTTP_HEADER_PROFILE: %s", c.HTTPHeaderProfile)
}
if c.HTTPPaddingMinBytes < 0 {
return fmt.Errorf("invalid HTTP_PADDING_MIN_BYTES: %d", c.HTTPPaddingMinBytes)
}
if c.HTTPPaddingMaxBytes < c.HTTPPaddingMinBytes {
return fmt.Errorf("HTTP_PADDING_MAX_BYTES must be >= HTTP_PADDING_MIN_BYTES")
}
if c.HTTPTimingJitterMS < 0 {
return fmt.Errorf("invalid HTTP_TIMING_JITTER_MS: %d", c.HTTPTimingJitterMS)
}
if c.HTTPBatchPacketsJitter < 0 {
return fmt.Errorf("invalid HTTP_BATCH_PACKETS_JITTER: %d", c.HTTPBatchPacketsJitter)
}
if c.HTTPBatchBytesJitter < 0 {
return fmt.Errorf("invalid HTTP_BATCH_BYTES_JITTER: %d", c.HTTPBatchBytesJitter)
}
if c.MaxQueueBytesPerSOCKS < c.MaxChunkSize {
return fmt.Errorf("MAX_QUEUE_BYTES_PER_SOCKS must be >= MAX_CHUNK_SIZE")
}
return nil
}
func (c Config) ValidateServer() error {
if err := c.validateShared(); err != nil {
return err
}
if c.ServerPort < 1 || c.ServerPort > 65535 {
return fmt.Errorf("invalid SERVER_PORT: %d", c.ServerPort)
}
if c.SessionIdleTimeoutMS < 1 {
return fmt.Errorf("invalid SESSION_IDLE_TIMEOUT_MS: %d", c.SessionIdleTimeoutMS)
}
if c.SOCKSIdleTimeoutMS < 1 {
return fmt.Errorf("invalid SOCKS_IDLE_TIMEOUT_MS: %d", c.SOCKSIdleTimeoutMS)
}
if c.ReorderTimeoutMS < 1 {
return fmt.Errorf("invalid REORDER_TIMEOUT_MS: %d", c.ReorderTimeoutMS)
}
if c.MaxReorderBufferPackets < 1 {
return fmt.Errorf("invalid MAX_REORDER_BUFFER_PACKETS: %d", c.MaxReorderBufferPackets)
}
if c.ReadBodyLimitBytes < c.MaxChunkSize {
return fmt.Errorf("READ_BODY_LIMIT_BYTES must be >= MAX_CHUNK_SIZE")
}
if c.MaxServerQueueBytes < c.MaxChunkSize {
return fmt.Errorf("MAX_SERVER_QUEUE_BYTES must be >= MAX_CHUNK_SIZE")
}
return nil
}
func (c Config) validateShared() error {
if strings.TrimSpace(c.AESEncryptionKey) == "" {
return fmt.Errorf("AES_ENCRYPTION_KEY is required")
}
if c.MaxChunkSize < 1 {
return fmt.Errorf("invalid MAX_CHUNK_SIZE: %d", c.MaxChunkSize)
}
if c.MaxPacketsPerBatch < 1 {
return fmt.Errorf("invalid MAX_PACKETS_PER_BATCH: %d", c.MaxPacketsPerBatch)
}
if c.MaxBatchBytes < c.MaxChunkSize {
return fmt.Errorf("MAX_BATCH_BYTES must be >= MAX_CHUNK_SIZE")
}
if c.WorkerCount < 1 {
return fmt.Errorf("invalid WORKER_COUNT: %d", c.WorkerCount)
}
return nil
}
func trimString(value string) string {
return strings.Trim(value, `"`)
}