Files
MasterHttpRelayVPN/internal/config/config.go
T
Amin.MasterkinG 2a0baa80b0 Server side.
2026-04-20 19:24:38 +03:30

250 lines
6.9 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
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
MaxQueueBytesPerSOCKS int
SessionIdleTimeoutMS int
SOCKSIdleTimeoutMS int
ReadBodyLimitBytes int
}
func Load(path string) (Config, error) {
cfg := Config{
SOCKSHost: "127.0.0.1",
SOCKSPort: 1080,
ServerHost: "127.0.0.1",
ServerPort: 28080,
LogLevel: "INFO",
MaxChunkSize: 16 * 1024,
MaxPacketsPerBatch: 32,
MaxBatchBytes: 256 * 1024,
WorkerCount: 4,
HTTPRequestTimeoutMS: 15000,
WorkerPollIntervalMS: 200,
MaxQueueBytesPerSOCKS: 1024 * 1024,
SessionIdleTimeoutMS: 5 * 60 * 1000,
SOCKSIdleTimeoutMS: 2 * 60 * 1000,
ReadBodyLimitBytes: 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 "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 "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 "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
}
}
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.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.ReadBodyLimitBytes < c.MaxChunkSize {
return fmt.Errorf("READ_BODY_LIMIT_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, `"`)
}