mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 16:16:32 +00:00
846 lines
36 KiB
Go
846 lines
36 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
yaml "github.com/goccy/go-yaml"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var (
|
|
v = viper.GetViper()
|
|
)
|
|
|
|
func init() {
|
|
v.SetConfigName("gost")
|
|
v.AddConfigPath("/etc/gost/")
|
|
v.AddConfigPath("$HOME/.gost/")
|
|
v.AddConfigPath(".")
|
|
}
|
|
|
|
var (
|
|
global = &Config{}
|
|
globalMux sync.RWMutex
|
|
|
|
// configFile remembers the last file loaded via ReadFile so the API can
|
|
// save back to the same location (issue go-gost/x#123).
|
|
configFile atomic.Value // string
|
|
)
|
|
|
|
// ConfigFile returns the path of the last config file loaded via ReadFile,
|
|
// or an empty string when the config came from stdin, a URL, or inline JSON.
|
|
func ConfigFile() string {
|
|
if s, ok := configFile.Load().(string); ok {
|
|
return s
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func Global() *Config {
|
|
globalMux.RLock()
|
|
defer globalMux.RUnlock()
|
|
|
|
cfg := &Config{}
|
|
*cfg = *global
|
|
return cfg
|
|
}
|
|
|
|
func Set(c *Config) {
|
|
globalMux.Lock()
|
|
defer globalMux.Unlock()
|
|
|
|
global = c
|
|
}
|
|
|
|
func OnUpdate(f func(c *Config) error) error {
|
|
globalMux.Lock()
|
|
defer globalMux.Unlock()
|
|
|
|
return f(global)
|
|
}
|
|
|
|
type LogConfig struct {
|
|
Output string `yaml:",omitempty" json:"output,omitempty"`
|
|
Level string `yaml:",omitempty" json:"level,omitempty"`
|
|
Format string `yaml:",omitempty" json:"format,omitempty"`
|
|
Rotation *LogRotationConfig `yaml:",omitempty" json:"rotation,omitempty"`
|
|
}
|
|
|
|
type LogRotationConfig struct {
|
|
// MaxSize is the maximum size in megabytes of the log file before it gets
|
|
// rotated. It defaults to 100 megabytes.
|
|
MaxSize int `yaml:"maxSize,omitempty" json:"maxSize,omitempty"`
|
|
// MaxAge is the maximum number of days to retain old log files based on the
|
|
// timestamp encoded in their filename. Note that a day is defined as 24
|
|
// hours and may not exactly correspond to calendar days due to daylight
|
|
// savings, leap seconds, etc. The default is not to remove old log files
|
|
// based on age.
|
|
MaxAge int `yaml:"maxAge,omitempty" json:"maxAge,omitempty"`
|
|
// MaxBackups is the maximum number of old log files to retain. The default
|
|
// is to retain all old log files (though MaxAge may still cause them to get
|
|
// deleted.)
|
|
MaxBackups int `yaml:"maxBackups,omitempty" json:"maxBackups,omitempty"`
|
|
// LocalTime determines if the time used for formatting the timestamps in
|
|
// backup files is the computer's local time. The default is to use UTC
|
|
// time.
|
|
LocalTime bool `yaml:"localTime,omitempty" json:"localTime,omitempty"`
|
|
// Compress determines if the rotated log files should be compressed
|
|
// using gzip. The default is not to perform compression.
|
|
Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"`
|
|
}
|
|
|
|
type LoggerConfig struct {
|
|
Name string `json:"name"`
|
|
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
|
}
|
|
|
|
type ProfilingConfig struct {
|
|
Addr string `json:"addr"`
|
|
}
|
|
|
|
type APIConfig struct {
|
|
Addr string `json:"addr"`
|
|
PathPrefix string `yaml:"pathPrefix,omitempty" json:"pathPrefix,omitempty"`
|
|
AccessLog bool `yaml:"accesslog,omitempty" json:"accesslog,omitempty"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
|
}
|
|
|
|
type MetricsConfig struct {
|
|
Addr string `json:"addr"`
|
|
Path string `yaml:",omitempty" json:"path,omitempty"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
|
}
|
|
|
|
type TLSConfig struct {
|
|
CertFile string `yaml:"certFile,omitempty" json:"certFile,omitempty"`
|
|
KeyFile string `yaml:"keyFile,omitempty" json:"keyFile,omitempty"`
|
|
CAFile string `yaml:"caFile,omitempty" json:"caFile,omitempty"`
|
|
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
|
|
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
|
|
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
|
|
|
|
// for auto-generated default certificate.
|
|
Validity time.Duration `yaml:",omitempty" json:"validity,omitempty"`
|
|
CommonName string `yaml:"commonName,omitempty" json:"commonName,omitempty"`
|
|
Organization string `yaml:",omitempty" json:"organization,omitempty"`
|
|
|
|
RejectUnknownSNI bool `yaml:"rejectUnknownSNI,omitempty" json:"rejectUnknownSNI,omitempty"`
|
|
ServerNames []string `yaml:"serverNames,omitempty" json:"serverNames,omitempty"`
|
|
}
|
|
|
|
type TLSOptions struct {
|
|
MinVersion string `yaml:"minVersion,omitempty" json:"minVersion,omitempty"`
|
|
MaxVersion string `yaml:"maxVersion,omitempty" json:"maxVersion,omitempty"`
|
|
CipherSuites []string `yaml:"cipherSuites,omitempty" json:"cipherSuites,omitempty"`
|
|
ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"`
|
|
}
|
|
|
|
type PluginConfig struct {
|
|
Type string `json:"type"`
|
|
Addr string `json:"addr"`
|
|
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
|
Token string `yaml:",omitempty" json:"token,omitempty"`
|
|
}
|
|
|
|
type AutherConfig struct {
|
|
Name string `json:"name"`
|
|
Auths []*AuthConfig `yaml:",omitempty" json:"auths,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type AuthConfig struct {
|
|
Username string `yaml:",omitempty" json:"username,omitempty"`
|
|
Password string `yaml:",omitempty" json:"password,omitempty"`
|
|
File string `yaml:",omitempty" json:"file,omitempty"`
|
|
}
|
|
|
|
type SelectorConfig struct {
|
|
Strategy string `json:"strategy"`
|
|
MaxFails int `yaml:"maxFails" json:"maxFails"`
|
|
FailTimeout time.Duration `yaml:"failTimeout" json:"failTimeout"`
|
|
MaxLatency time.Duration `yaml:"maxLatency,omitempty" json:"maxLatency,omitempty"`
|
|
}
|
|
|
|
type AdmissionConfig struct {
|
|
Name string `json:"name"`
|
|
// Deprecated: use whitelist instead
|
|
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
|
|
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
|
|
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type BypassConfig struct {
|
|
Name string `json:"name"`
|
|
// Deprecated: use whitelist instead
|
|
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
|
|
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
|
|
Network string `yaml:",omitempty" json:"network,omitempty"`
|
|
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type FileLoader struct {
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
type RedisLoader struct {
|
|
Addr string `json:"addr"`
|
|
DB int `yaml:",omitempty" json:"db,omitempty"`
|
|
Username string `yaml:",omitempty" json:"username,omitempty"`
|
|
Password string `yaml:",omitempty" json:"password,omitempty"`
|
|
Key string `yaml:",omitempty" json:"key,omitempty"`
|
|
Type string `yaml:",omitempty" json:"type,omitempty"`
|
|
}
|
|
|
|
type HTTPLoader struct {
|
|
URL string `yaml:"url" json:"url"`
|
|
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
|
}
|
|
|
|
type NameserverConfig struct {
|
|
Addr string `json:"addr"`
|
|
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
|
Prefer string `yaml:",omitempty" json:"prefer,omitempty"`
|
|
ClientIP string `yaml:"clientIP,omitempty" json:"clientIP,omitempty"`
|
|
Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
|
|
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
|
|
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
|
Async bool `yaml:",omitempty" json:"async,omitempty"`
|
|
Only string `yaml:",omitempty" json:"only,omitempty"`
|
|
}
|
|
|
|
type ResolverConfig struct {
|
|
Name string `json:"name"`
|
|
Nameservers []*NameserverConfig `yaml:",omitempty" json:"nameservers,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type HostMappingConfig struct {
|
|
IP string `json:"ip"`
|
|
Hostname string `json:"hostname"`
|
|
Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
|
|
}
|
|
|
|
type HostsConfig struct {
|
|
Name string `json:"name"`
|
|
Mappings []*HostMappingConfig `yaml:",omitempty" json:"mappings,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type IngressRuleConfig struct {
|
|
Hostname string `json:"hostname"`
|
|
Endpoint string `json:"endpoint"`
|
|
}
|
|
|
|
type IngressConfig struct {
|
|
Name string `json:"name"`
|
|
Rules []*IngressRuleConfig `yaml:",omitempty" json:"rules,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type SDConfig struct {
|
|
Name string `json:"name"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type RouterRouteConfig struct {
|
|
// Deprecated: use dst instead
|
|
Net string `yaml:",omitempty" json:"net,omitempty"`
|
|
Dst string `yaml:",omitempty" json:"dst,omitempty"`
|
|
Gateway string `json:"gateway"`
|
|
}
|
|
|
|
type RouterConfig struct {
|
|
Name string `json:"name"`
|
|
Routes []*RouterRouteConfig `yaml:",omitempty" json:"routes,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type RecorderConfig struct {
|
|
Name string `json:"name"`
|
|
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
|
|
TCP *TCPRecorder `yaml:"tcp,omitempty" json:"tcp,omitempty"`
|
|
HTTP *HTTPRecorder `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Redis *RedisRecorder `yaml:",omitempty" json:"redis,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
// P2PConfig defines a named p2p tunnel provider, currently backed by an
|
|
// external gRPC plugin process.
|
|
type P2PConfig struct {
|
|
Name string `json:"name"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type FileRecorder struct {
|
|
Path string `json:"path"`
|
|
Sep string `yaml:",omitempty" json:"sep,omitempty"`
|
|
Rotation *LogRotationConfig `yaml:",omitempty" json:"rotation,omitempty"`
|
|
}
|
|
|
|
type TCPRecorder struct {
|
|
Addr string `json:"addr"`
|
|
Timeout time.Duration `json:"timeout"`
|
|
}
|
|
|
|
type HTTPRecorder struct {
|
|
URL string `yaml:"url" json:"url"`
|
|
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
|
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
|
}
|
|
|
|
type RedisRecorder struct {
|
|
Addr string `json:"addr"`
|
|
DB int `yaml:",omitempty" json:"db,omitempty"`
|
|
Username string `yaml:",omitempty" json:"username,omitempty"`
|
|
Password string `yaml:",omitempty" json:"password,omitempty"`
|
|
Key string `yaml:",omitempty" json:"key,omitempty"`
|
|
Type string `yaml:",omitempty" json:"type,omitempty"`
|
|
}
|
|
|
|
type RecorderObject struct {
|
|
Name string `json:"name"`
|
|
Record string `json:"record"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type RewriterConfig struct {
|
|
Name string `json:"name"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
// CacheConfig defines a named cache store. Memory and Redis backends are
|
|
// supported; other backends (file, plugin) are follow-ups.
|
|
type CacheConfig struct {
|
|
Name string `json:"name"`
|
|
Memory *MemoryCache `yaml:",omitempty" json:"memory,omitempty"`
|
|
Redis *RedisCache `yaml:",omitempty" json:"redis,omitempty"`
|
|
}
|
|
|
|
// RedisCache configures the Redis cache backend.
|
|
type RedisCache struct {
|
|
Addr string `json:"addr"`
|
|
DB int `yaml:",omitempty" json:"db,omitempty"`
|
|
Username string `yaml:",omitempty" json:"username,omitempty"`
|
|
Password string `yaml:",omitempty" json:"password,omitempty"`
|
|
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
|
|
Key string `yaml:",omitempty" json:"key,omitempty"` // key prefix, default "gost:cache:"
|
|
}
|
|
|
|
// MemoryCache configures the in-memory cache backend.
|
|
type MemoryCache struct {
|
|
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
|
|
MaxSize int `yaml:"maxSize,omitempty" json:"maxSize,omitempty"`
|
|
MaxBytes int64 `yaml:"maxBytes,omitempty" json:"maxBytes,omitempty"`
|
|
CleanupInterval time.Duration `yaml:"cleanupInterval,omitempty" json:"cleanupInterval,omitempty"`
|
|
Eviction string `yaml:",omitempty" json:"eviction,omitempty"` // "oldest" | "lru"
|
|
}
|
|
|
|
type LimiterConfig struct {
|
|
Name string `json:"name"`
|
|
Limits []string `yaml:",omitempty" json:"limits,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type ObserverConfig struct {
|
|
Name string `json:"name"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
}
|
|
|
|
type ListenerConfig struct {
|
|
Type string `json:"type"`
|
|
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
|
ChainGroup *ChainGroupConfig `yaml:"chainGroup,omitempty" json:"chainGroup,omitempty"`
|
|
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
|
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type HandlerConfig struct {
|
|
Type string `json:"type"`
|
|
Retries int `yaml:",omitempty" json:"retries,omitempty"`
|
|
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
|
ChainGroup *ChainGroupConfig `yaml:"chainGroup,omitempty" json:"chainGroup,omitempty"`
|
|
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
|
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
|
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type ForwarderConfig struct {
|
|
// Deprecated: use hop instead
|
|
Name string `yaml:",omitempty" json:"name,omitempty"`
|
|
// the referenced hop name
|
|
Hop string `yaml:",omitempty" json:"hop,omitempty"`
|
|
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
|
Nodes []*ForwardNodeConfig `json:"nodes"`
|
|
HopGroup *ForwardHopGroupConfig `yaml:"hopGroup,omitempty" json:"hopGroup,omitempty"`
|
|
}
|
|
|
|
type ForwardHopGroupConfig struct {
|
|
Hops []*ForwardHopConfig `yaml:",omitempty" json:"hops,omitempty"`
|
|
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
|
}
|
|
|
|
type ForwardHopConfig struct {
|
|
Hop string `yaml:",omitempty" json:"hop,omitempty"`
|
|
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
|
Probe *ProbeConfig `yaml:",omitempty" json:"probe,omitempty"`
|
|
}
|
|
|
|
type ForwardNodeConfig struct {
|
|
Name string `yaml:",omitempty" json:"name,omitempty"`
|
|
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
|
Network string `yaml:",omitempty" json:"network,omitempty"`
|
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
|
// Deprecated: use matcher instead
|
|
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
|
// Deprecated: use matcher instead
|
|
Host string `yaml:",omitempty" json:"host,omitempty"`
|
|
// Deprecated: use matcher instead
|
|
Path string `yaml:",omitempty" json:"path,omitempty"`
|
|
// Deprecated: use matcher instead
|
|
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
|
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
|
// Deprecated: use http.auth instead
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
|
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Probe *ProbeConfig `yaml:",omitempty" json:"probe,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type HTTPURLRewriteConfig struct {
|
|
Match string
|
|
Replacement string
|
|
}
|
|
|
|
type HTTPBodyRewriteConfig struct {
|
|
// filter by MIME types
|
|
Type string
|
|
Match string
|
|
Replacement string
|
|
// name of the rewriter plugin (via registry)
|
|
Rewriter string `yaml:",omitempty" json:"rewriter,omitempty"`
|
|
MaxChunkSize int `yaml:"maxChunkSize,omitempty" json:"maxChunkSize,omitempty"`
|
|
}
|
|
|
|
type HTTPHeaderRewriteConfig struct {
|
|
// Name is the header name regex, e.g. "(?i)^(location|set-cookie)$".
|
|
Name string
|
|
// Match is the header value regex.
|
|
Match string
|
|
// Replacement is the replacement string.
|
|
Replacement string
|
|
// Rewriter is the name of the rewriter plugin (via registry).
|
|
Rewriter string `yaml:",omitempty" json:"rewriter,omitempty"`
|
|
}
|
|
|
|
type NodeFilterConfig struct {
|
|
Host string `yaml:",omitempty" json:"host,omitempty"`
|
|
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
|
Path string `yaml:",omitempty" json:"path,omitempty"`
|
|
}
|
|
|
|
// NodeMatcherConfig defines a routing-rule matcher for a hop node.
|
|
//
|
|
// Priority controls election among multiple matching nodes:
|
|
// - 0 (default): auto-computed from the rule string length — longer rules
|
|
// (more specific) get higher priority.
|
|
// - negative: the node participates in matching but priority short-circuit
|
|
// is disabled; the selector (round-robin, random, hash, etc.) always applies.
|
|
// - positive: explicit priority; when a single node has strictly higher
|
|
// priority than all others, it wins directly, bypassing the selector.
|
|
type NodeMatcherConfig struct {
|
|
Rule string `yaml:",omitempty" json:"rule,omitempty"`
|
|
Priority int `yaml:",omitempty" json:"priority,omitempty"`
|
|
// BodySize is the max request body prefix (bytes) exposed to BodyRegexp
|
|
// and BodyJSON matchers. 0 (default) uses DefaultMatcherBodySize (1MB).
|
|
// Capped at MaxMatcherBodySize (10MB). Only takes effect under an HTTP
|
|
// sniffing handler whose sniffer reads the body prefix before node selection.
|
|
BodySize int `yaml:",omitempty" json:"bodySize,omitempty"`
|
|
}
|
|
|
|
// ProbeConfig holds the configuration for a node-level liveness probe.
|
|
type ProbeConfig struct {
|
|
Type string `json:"type"`
|
|
Addr string `json:"addr,omitempty"`
|
|
Interval time.Duration `json:"interval"`
|
|
Timeout time.Duration `json:"timeout"`
|
|
HTTPPath string `yaml:"httpPath,omitempty" json:"httpPath,omitempty"`
|
|
HTTPHost string `yaml:"httpHost,omitempty" json:"httpHost,omitempty"`
|
|
HTTPHeaders map[string]string `yaml:"httpHeaders,omitempty" json:"httpHeaders,omitempty"`
|
|
ExpectedStatus int `yaml:"expectedStatus,omitempty" json:"expectedStatus,omitempty"`
|
|
Command string `yaml:"command,omitempty" json:"command,omitempty"`
|
|
}
|
|
|
|
type HTTPNodeConfig struct {
|
|
// rewrite host header
|
|
Host string `yaml:",omitempty" json:"host,omitempty"`
|
|
// regex matched against the request path to derive Host; Host becomes a
|
|
// replacement template ($1, $2, ...) when set.
|
|
HostPattern string `yaml:"hostPattern,omitempty" json:"hostPattern,omitempty"`
|
|
// Deprecated: use requestHeader instead
|
|
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
|
// additional request header
|
|
RequestHeader map[string]string `yaml:"requestHeader,omitempty" json:"requestHeader,omitempty"`
|
|
// additional response header
|
|
ResponseHeader map[string]string `yaml:"responseHeader,omitempty" json:"responseHeader,omitempty"`
|
|
// Deprecated: use rewriteURL instead
|
|
Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"`
|
|
// rewrite URL
|
|
RewriteURL []HTTPURLRewriteConfig `yaml:"rewriteURL,omitempty" json:"rewriteURL,omitempty"`
|
|
// Deprecated: use rewriteResponseBody instead
|
|
RewriteBody []HTTPBodyRewriteConfig `yaml:"rewriteBody,omitempty" json:"rewriteBody,omitempty"`
|
|
// rewrite request body
|
|
RewriteRequestBody []HTTPBodyRewriteConfig `yaml:"rewriteRequestBody,omitempty" json:"rewriteRequestBody,omitempty"`
|
|
// rewrite response body
|
|
RewriteResponseBody []HTTPBodyRewriteConfig `yaml:"rewriteResponseBody,omitempty" json:"rewriteResponseBody,omitempty"`
|
|
|
|
// rewrite request header values
|
|
RewriteRequestHeader []HTTPHeaderRewriteConfig `yaml:"rewriteRequestHeader,omitempty" json:"rewriteRequestHeader,omitempty"`
|
|
// rewrite response header values
|
|
RewriteResponseHeader []HTTPHeaderRewriteConfig `yaml:"rewriteResponseHeader,omitempty" json:"rewriteResponseHeader,omitempty"`
|
|
|
|
// comma-separated response status codes marking the node failed, e.g. "429,5xx"
|
|
FailCodes string `yaml:"failCodes,omitempty" json:"failCodes,omitempty"`
|
|
|
|
// HTTP basic auth
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
}
|
|
|
|
type TLSNodeConfig struct {
|
|
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
|
|
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
|
|
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
|
|
}
|
|
|
|
type DialerConfig struct {
|
|
Type string `json:"type"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type ConnectorConfig struct {
|
|
Type string `json:"type"`
|
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type SockOptsConfig struct {
|
|
Mark int `yaml:",omitempty" json:"mark,omitempty"`
|
|
}
|
|
|
|
type ServiceConfig struct {
|
|
Name string `json:"name"`
|
|
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
|
// Deprecated: use metadata.interface instead
|
|
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
|
// Deprecated: use metadata.so_mark instead
|
|
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
|
Admission string `yaml:",omitempty" json:"admission,omitempty"`
|
|
Admissions []string `yaml:",omitempty" json:"admissions,omitempty"`
|
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
|
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
|
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
|
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
|
Quotas []string `yaml:",omitempty" json:"quotas,omitempty"`
|
|
CLimiter string `yaml:"climiter,omitempty" json:"climiter,omitempty"`
|
|
RLimiter string `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"`
|
|
Logger string `yaml:",omitempty" json:"logger,omitempty"`
|
|
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
|
|
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
|
Rewriter string `yaml:",omitempty" json:"rewriter,omitempty"`
|
|
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
|
Cache string `yaml:",omitempty" json:"cache,omitempty"`
|
|
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
|
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
|
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
// service status, read-only
|
|
Status *ServiceStatus `yaml:",omitempty" json:"status,omitempty"`
|
|
}
|
|
|
|
type ServiceStatus struct {
|
|
CreateTime int64 `yaml:"createTime" json:"createTime"`
|
|
State string `yaml:"state" json:"state"`
|
|
Events []ServiceEvent `yaml:",omitempty" json:"events,omitempty"`
|
|
Stats *ServiceStats `yaml:",omitempty" json:"stats,omitempty"`
|
|
StoppedByLimit bool `yaml:"stoppedByLimit,omitempty" json:"stoppedByLimit,omitempty"`
|
|
Quotas []ServiceQuota `yaml:",omitempty" json:"quotas,omitempty"`
|
|
}
|
|
|
|
type ServiceEvent struct {
|
|
Time int64 `yaml:"time" json:"time"`
|
|
Msg string `yaml:"msg" json:"msg"`
|
|
}
|
|
|
|
type ServiceStats struct {
|
|
TotalConns uint64 `yaml:"totalConns" json:"totalConns"`
|
|
CurrentConns uint64 `yaml:"currentConns" json:"currentConns"`
|
|
TotalErrs uint64 `yaml:"totalErrs" json:"totalErrs"`
|
|
InputBytes uint64 `yaml:"inputBytes" json:"inputBytes"`
|
|
OutputBytes uint64 `yaml:"outputBytes" json:"outputBytes"`
|
|
}
|
|
|
|
// QuotaConfig is a named cumulative traffic-volume limiter; the same name
|
|
// referenced by several services (ServiceConfig.Quotas) shares one counter.
|
|
type QuotaConfig struct {
|
|
Name string `json:"name"`
|
|
Limit string `yaml:",omitempty" json:"limit,omitempty"`
|
|
StartsAt string `yaml:"startsAt,omitempty" json:"startsAt,omitempty"`
|
|
ExpiresAt string `yaml:"expiresAt,omitempty" json:"expiresAt,omitempty"`
|
|
Direction string `yaml:",omitempty" json:"direction,omitempty"`
|
|
Flush string `yaml:",omitempty" json:"flush,omitempty"`
|
|
Store *QuotaStoreConfig `yaml:",omitempty" json:"store,omitempty"`
|
|
// read-only
|
|
Status *QuotaStatus `yaml:",omitempty" json:"status,omitempty"`
|
|
}
|
|
|
|
type QuotaStoreConfig struct {
|
|
Type string `json:"type"` // "file" (default) | "redis" (stub, not implemented)
|
|
File string `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *QuotaRedisConfig `yaml:",omitempty" json:"redis,omitempty"`
|
|
}
|
|
|
|
type QuotaRedisConfig struct {
|
|
Addr string `json:"addr"`
|
|
DB int `yaml:",omitempty" json:"db,omitempty"`
|
|
Username string `yaml:",omitempty" json:"username,omitempty"`
|
|
Password string `yaml:",omitempty" json:"password,omitempty"`
|
|
Key string `yaml:",omitempty" json:"key,omitempty"`
|
|
}
|
|
|
|
type QuotaStatus struct {
|
|
Used uint64 `yaml:"used" json:"used"`
|
|
Limit uint64 `yaml:"limit" json:"limit"`
|
|
StartsAt int64 `yaml:"startsAt,omitempty" json:"startsAt,omitempty"`
|
|
ExpiresAt int64 `yaml:"expiresAt,omitempty" json:"expiresAt,omitempty"`
|
|
Active bool `yaml:"active" json:"active"`
|
|
Expired bool `yaml:"expired,omitempty" json:"expired,omitempty"`
|
|
Blocked bool `yaml:"blocked" json:"blocked"`
|
|
Direction string `yaml:"direction,omitempty" json:"direction,omitempty"`
|
|
}
|
|
|
|
type ServiceQuota struct {
|
|
Name string `yaml:"name" json:"name"`
|
|
Used uint64 `yaml:"used" json:"used"`
|
|
Limit uint64 `yaml:"limit" json:"limit"`
|
|
StartsAt int64 `yaml:"startsAt,omitempty" json:"startsAt,omitempty"`
|
|
ExpiresAt int64 `yaml:"expiresAt,omitempty" json:"expiresAt,omitempty"`
|
|
Active bool `yaml:"active" json:"active"`
|
|
Expired bool `yaml:"expired,omitempty" json:"expired,omitempty"`
|
|
Blocked bool `yaml:"blocked" json:"blocked"`
|
|
Direction string `yaml:"direction,omitempty" json:"direction,omitempty"`
|
|
}
|
|
|
|
type ChainConfig struct {
|
|
Name string `json:"name"`
|
|
Hops []*HopConfig `json:"hops"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type ChainGroupEntry struct {
|
|
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
|
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
|
Probe *ProbeConfig `yaml:",omitempty" json:"probe,omitempty"`
|
|
}
|
|
|
|
// gostDecodeHook is a composite mapstructure decode hook that handles custom
|
|
// type conversions needed by GOST config fields. viper's internal unmarshalling
|
|
// never calls yaml.Unmarshaler / json.Unmarshaler on leaf types, so the hook is
|
|
// the only way to support these.
|
|
func gostDecodeHook() mapstructure.DecodeHookFunc {
|
|
return func(f, t reflect.Type, data any) (any, error) {
|
|
// Plain string → ChainGroupEntry: backward compat for "chains: [a, b]".
|
|
if t == reflect.TypeOf(ChainGroupEntry{}) {
|
|
if s, ok := data.(string); ok {
|
|
return ChainGroupEntry{Chain: s}, nil
|
|
}
|
|
return data, nil
|
|
}
|
|
// String → time.Duration: accept "10s", "1m", etc. in YAML/JSON.
|
|
if t == reflect.TypeOf(time.Duration(0)) {
|
|
if s, ok := data.(string); ok {
|
|
return time.ParseDuration(s)
|
|
}
|
|
return data, nil
|
|
}
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
type ChainGroupConfig struct {
|
|
Chains []*ChainGroupEntry `yaml:",omitempty" json:"chains,omitempty"`
|
|
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
|
}
|
|
|
|
type HopConfig struct {
|
|
Name string `json:"name"`
|
|
// Deprecated: use metadata.interface instead
|
|
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
|
// Deprecated: use metadata.so_mark instead
|
|
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
|
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
|
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
|
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
|
Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,omitempty"`
|
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type NodeConfig struct {
|
|
Name string `json:"name"`
|
|
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
|
Network string `yaml:",omitempty" json:"network,omitempty"`
|
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
|
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
|
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
|
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
|
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
|
// Deprecated: use metadata.interface instead
|
|
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
|
// Deprecated: use metadata.netns instead
|
|
Netns string `yaml:",omitempty" json:"netns,omitempty"`
|
|
// Deprecated: use metadata.so_mark instead
|
|
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
|
// Deprecated: use matcher instead
|
|
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
|
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
|
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
|
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Probe *ProbeConfig `yaml:",omitempty" json:"probe,omitempty"`
|
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
type Config struct {
|
|
Services []*ServiceConfig `json:"services"`
|
|
Chains []*ChainConfig `yaml:",omitempty" json:"chains,omitempty"`
|
|
Hops []*HopConfig `yaml:",omitempty" json:"hops,omitempty"`
|
|
Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,omitempty"`
|
|
Authers []*AutherConfig `yaml:",omitempty" json:"authers,omitempty"`
|
|
Admissions []*AdmissionConfig `yaml:",omitempty" json:"admissions,omitempty"`
|
|
Bypasses []*BypassConfig `yaml:",omitempty" json:"bypasses,omitempty"`
|
|
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
|
|
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
|
|
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
|
|
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
|
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
|
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
|
Rewriters []*RewriterConfig `yaml:",omitempty" json:"rewriters,omitempty"`
|
|
P2Ps []*P2PConfig `yaml:"p2ps,omitempty" json:"p2ps,omitempty"`
|
|
Caches []*CacheConfig `yaml:",omitempty" json:"caches,omitempty"`
|
|
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
|
Quotas []*QuotaConfig `yaml:",omitempty" json:"quotas,omitempty"`
|
|
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
|
RLimiters []*LimiterConfig `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"`
|
|
Observers []*ObserverConfig `yaml:",omitempty" json:"observers,omitempty"`
|
|
Loggers []*LoggerConfig `yaml:",omitempty" json:"loggers,omitempty"`
|
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
|
Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"`
|
|
API *APIConfig `yaml:",omitempty" json:"api,omitempty"`
|
|
Metrics *MetricsConfig `yaml:",omitempty" json:"metrics,omitempty"`
|
|
}
|
|
|
|
func (c *Config) Load() error {
|
|
if err := v.ReadInConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return v.Unmarshal(c, viper.DecodeHook(gostDecodeHook()))
|
|
}
|
|
|
|
func (c *Config) Read(r io.Reader, configType string) error {
|
|
v.SetConfigType(configType)
|
|
if err := v.ReadConfig(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
return v.Unmarshal(c, viper.DecodeHook(gostDecodeHook()))
|
|
}
|
|
|
|
func (c *Config) ReadFile(file string) error {
|
|
v.SetConfigFile(file)
|
|
v.SetConfigType(strings.TrimPrefix(filepath.Ext(file), ".")) // force format from extension
|
|
if err := v.ReadInConfig(); err != nil {
|
|
return err
|
|
}
|
|
configFile.Store(file)
|
|
return v.Unmarshal(c, viper.DecodeHook(gostDecodeHook()))
|
|
}
|
|
|
|
func (c *Config) Write(w io.Writer, format string) error {
|
|
switch format {
|
|
case "json":
|
|
enc := json.NewEncoder(w)
|
|
enc.SetIndent("", " ")
|
|
enc.Encode(c)
|
|
return nil
|
|
case "yaml":
|
|
fallthrough
|
|
default:
|
|
enc := yaml.NewEncoder(w, yaml.Indent(2))
|
|
defer enc.Close()
|
|
|
|
return enc.Encode(c)
|
|
}
|
|
}
|