e0f54ef320
Add `tomswall agent`: it pulls this device's compiled config from tomswallapi, differentially applies it, and reports the applied generation. It caches the last known-good config and, when the control plane is unreachable, keeps applying that cache — it never fails closed. - internal/agent: rendered-config types, HTTP client (fetch + status report), on-disk cache, on-device DNS resolver for dns sets (honors the device's configured resolver, fail-safe on lookup failure), and the pull-apply-report loop behind a mockable Applier. - Translate the interface-agnostic, address-matched rendered model into native tomswall config using the "all:<cidr>" any-interface source/dest form, reusing the existing differential engine. Named-set members are inlined as concrete addresses (native nft set references are a tracked follow-up). - cmd/tomswall: wire the `agent` subcommand (flags + TOMSWALL_* env, --once). - Unit tests: translation, cache, and the don't-fail-closed fallback loop. - Add DESIGN.md documenting the control-plane architecture.
129 lines
4.1 KiB
Go
129 lines
4.1 KiB
Go
package config
|
|
|
|
import "fmt"
|
|
|
|
// TCDevice defines a traffic-shaped interface with bandwidth limits.
|
|
type TCDevice struct {
|
|
Interface string `yaml:"interface"`
|
|
InBandwidth string `yaml:"in_bandwidth,omitempty"` // ingress rate limit
|
|
OutBandwidth string `yaml:"out_bandwidth"` // egress max
|
|
Options TCDeviceOptions `yaml:"options,omitempty"`
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
type TCDeviceOptions struct {
|
|
Classify bool `yaml:"classify,omitempty"`
|
|
HTB bool `yaml:"htb,omitempty"`
|
|
HFSC bool `yaml:"hfsc,omitempty"`
|
|
Linklayer string `yaml:"linklayer,omitempty"` // ethernet, atm, adsl
|
|
}
|
|
|
|
// TCClass defines an HTB/HFSC traffic class with rate guarantees.
|
|
type TCClass struct {
|
|
Interface string `yaml:"interface"` // format: iface:class or iface:parent:class
|
|
Mark int `yaml:"mark,omitempty"` // 1-255 fw mark
|
|
Rate string `yaml:"rate"` // minimum guaranteed bandwidth
|
|
Ceil string `yaml:"ceil,omitempty"` // max bandwidth
|
|
Priority int `yaml:"priority,omitempty"` // scheduling order
|
|
Options TCClassOptions `yaml:"options,omitempty"`
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
type TCClassOptions struct {
|
|
Default bool `yaml:"default,omitempty"` // default class for unclassified traffic
|
|
TCPAck bool `yaml:"tcp_ack,omitempty"`
|
|
Pfifo bool `yaml:"pfifo,omitempty"`
|
|
}
|
|
|
|
// TCFilter classifies packets into traffic classes.
|
|
type TCFilter struct {
|
|
Class string `yaml:"class"` // interface:class
|
|
Source string `yaml:"source,omitempty"`
|
|
Dest string `yaml:"dest,omitempty"`
|
|
Proto string `yaml:"proto,omitempty"`
|
|
DPort PortSpec `yaml:"dport,omitempty"`
|
|
SPort PortSpec `yaml:"sport,omitempty"`
|
|
TOS string `yaml:"tos,omitempty"`
|
|
Length int `yaml:"length,omitempty"`
|
|
Priority int `yaml:"priority,omitempty"` // filter eval order
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
// TCInterface defines simple traffic shaping (3-band priority queueing).
|
|
type TCInterface struct {
|
|
Interface string `yaml:"interface"`
|
|
Type string `yaml:"type,omitempty"` // external, internal
|
|
InBandwidth string `yaml:"in_bandwidth,omitempty"`
|
|
OutBandwidth string `yaml:"out_bandwidth,omitempty"`
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
// TCPriority assigns packets to priority bands (1=high, 2=medium, 3=low).
|
|
type TCPriority struct {
|
|
Band int `yaml:"band"` // 1, 2, or 3
|
|
Proto string `yaml:"proto,omitempty"`
|
|
DPort PortSpec `yaml:"dport,omitempty"`
|
|
SPort PortSpec `yaml:"sport,omitempty"`
|
|
Address string `yaml:"address,omitempty"`
|
|
Interface string `yaml:"interface,omitempty"`
|
|
Helper string `yaml:"helper,omitempty"`
|
|
Comment string `yaml:"comment,omitempty"`
|
|
}
|
|
|
|
func (c *Config) validateTCDevices() error {
|
|
for i, dev := range c.TCDevices {
|
|
if dev.Interface == "" {
|
|
return fmt.Errorf("tcdevices[%d]: interface required", i)
|
|
}
|
|
if dev.OutBandwidth == "" {
|
|
return fmt.Errorf("tcdevices[%d]: out_bandwidth required", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateTCClasses() error {
|
|
for i, cls := range c.TCClasses {
|
|
if cls.Interface == "" {
|
|
return fmt.Errorf("tcclasses[%d]: interface required", i)
|
|
}
|
|
if cls.Rate == "" {
|
|
return fmt.Errorf("tcclasses[%d]: rate required", i)
|
|
}
|
|
if cls.Mark != 0 && (cls.Mark < 1 || cls.Mark > 255) {
|
|
return fmt.Errorf("tcclasses[%d]: mark must be 1-255", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateTCFilters() error {
|
|
for i, f := range c.TCFilters {
|
|
if f.Class == "" {
|
|
return fmt.Errorf("tcfilters[%d]: class required", i)
|
|
}
|
|
if f.Source == "" && f.Dest == "" {
|
|
return fmt.Errorf("tcfilters[%d]: source or dest required", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateTCInterfaces() error {
|
|
for i, iface := range c.TCInterfaces {
|
|
if iface.Interface == "" {
|
|
return fmt.Errorf("tcinterfaces[%d]: interface required", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateTCPriority() error {
|
|
for i, p := range c.TCPriorities {
|
|
if p.Band < 1 || p.Band > 3 {
|
|
return fmt.Errorf("tcpriority[%d]: band must be 1, 2, or 3", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|