// ============================================================================== // 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, `"`) }