package config import "fmt" // Provider defines an additional routing table for multi-ISP or policy routing. type Provider struct { // Provider name. Must be a valid name; "local", "main", "default", "unspec" are reserved. Name string `yaml:"name"` // Routing table number (1-252). Must be unique per provider. Number int `yaml:"number"` // FWMARK value for directing packets to this provider via mangle rules. Mark int `yaml:"mark,omitempty"` // Existing routing table to duplicate (e.g. "main" or another provider name). Duplicate string `yaml:"duplicate,omitempty"` // Network interface to the provider. Must be defined in interfaces. // Format: interface or interface:address (when multiple providers share an interface). Interface string `yaml:"interface"` // Gateway address. Supports: IP address, "detect", "none", or omit for PPP. Gateway string `yaml:"gateway,omitempty"` Options ProviderOptions `yaml:"options,omitempty"` // Interfaces to copy routes from when duplicating. Use "none" to only copy // routes through the provider's own interface. Copy []string `yaml:"copy,omitempty"` } type ProviderOptions struct { // Track inbound connections so responses route back out this interface. Track bool `yaml:"track,omitempty"` // Load-balance outbound traffic across providers with balance set. // Set to 1 for equal weight, or higher for more weight. Balance int `yaml:"balance,omitempty"` // Alternative load balancing via probability (0 < p <= 1). Load float64 `yaml:"load,omitempty"` // Do not create per-address routing rules for this interface. Loose bool `yaml:"loose,omitempty"` // Add a default route through this provider to the main routing table. // Set to 1 for equal weight, or higher for more weight. Fallback int `yaml:"fallback,omitempty"` // Mark this as the primary provider (equivalent to balance=1). Primary bool `yaml:"primary,omitempty"` // Source address for traffic routed through this provider. Src string `yaml:"src,omitempty"` // MTU override when forwarding through this provider. MTU int `yaml:"mtu,omitempty"` // TPROXY provider for transparent proxying. When set, mark/duplicate/gateway // should be empty and interface should be "lo". TProxy bool `yaml:"tproxy,omitempty"` // Allow the firewall to start even if this provider's interface is not up. Optional bool `yaml:"optional,omitempty"` // Provider survives disable — routing table keeps its default route. Persistent bool `yaml:"persistent,omitempty"` } var reservedProviderNames = map[string]bool{ "local": true, "main": true, "default": true, "unspec": true, } func (c *Config) validateProviders() error { seenNumbers := make(map[int]string) seenNames := make(map[string]bool) for i, p := range c.Providers { if p.Name == "" { return fmt.Errorf("provider[%d]: name required", i) } if err := ValidateName(p.Name, "provider"); err != nil { return fmt.Errorf("provider[%d]: %w", i, err) } if reservedProviderNames[p.Name] { return fmt.Errorf("provider[%d]: %q is a reserved name", i, p.Name) } if seenNames[p.Name] { return fmt.Errorf("provider[%d]: duplicate name %q", i, p.Name) } seenNames[p.Name] = true if p.Number < 1 || p.Number > 252 { return fmt.Errorf("provider[%d] %q: number must be between 1 and 252", i, p.Name) } if existing, ok := seenNumbers[p.Number]; ok { return fmt.Errorf("provider[%d] %q: number %d already used by %q", i, p.Name, p.Number, existing) } seenNumbers[p.Number] = p.Name if p.Interface == "" { return fmt.Errorf("provider[%d] %q: interface required", i, p.Name) } if p.Options.TProxy { if p.Mark != 0 || p.Duplicate != "" || p.Gateway != "" { return fmt.Errorf("provider[%d] %q: tproxy provider must have empty mark, duplicate, and gateway", i, p.Name) } } if p.Options.Load != 0 && (p.Options.Load <= 0 || p.Options.Load > 1) { return fmt.Errorf("provider[%d] %q: load probability must be between 0 (exclusive) and 1 (inclusive)", i, p.Name) } } return nil }