Files
tomswall/internal/config/providers.go
T
unkinben 8d9a76c751 Add comprehensive nftables compiler with shorewall feature parity
Rewrites the compiler from ~440 to ~1700 lines covering all major shorewall
firewall features: loopback, conntrack fast-path, anti-spoof, DHCP, intra-zone,
blacklist/whitelist, conntrack notrack, tunnels (13 types), rules with sections,
DNAT/redirect, SNAT/masquerade, static NAT, policies with zone exclusions,
MSS clamping, rate limiting, connection limiting, negated addresses, ICMP type
matching, TCP RST reject, user/UID matching, mark match/set, NFQUEUE, NONAT,
and policy-level rate/conn limiting.

Adds full config types for all shorewall subsystems (mangle, accounting, maclist,
netmap, providers, tunnels, conntrack, blrules, proxyarp/ndp, routes, tc, secmarks),
shorewall migration tooling, expanded CLI commands, expression-level diff engine,
and 49 unit tests.
2026-07-01 23:56:44 +10:00

118 lines
3.9 KiB
Go

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
}