Add tomswall agent (control-plane pull mode)
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.
This commit is contained in:
+13
-13
@@ -32,15 +32,15 @@ type Config struct {
|
||||
ProxyNDP []ProxyNDP `yaml:"proxyndp,omitempty"`
|
||||
Routes []StaticRoute `yaml:"routes,omitempty"`
|
||||
ArpRules []ArpRule `yaml:"arprules,omitempty"`
|
||||
Accounting []AccountingRule `yaml:"accounting,omitempty"`
|
||||
Mangle []MangleRule `yaml:"mangle,omitempty"`
|
||||
Maclist []MaclistEntry `yaml:"maclist,omitempty"`
|
||||
TCDevices []TCDevice `yaml:"tcdevices,omitempty"`
|
||||
TCClasses []TCClass `yaml:"tcclasses,omitempty"`
|
||||
TCFilters []TCFilter `yaml:"tcfilters,omitempty"`
|
||||
TCInterfaces []TCInterface `yaml:"tcinterfaces,omitempty"`
|
||||
TCPriorities []TCPriority `yaml:"tcpriority,omitempty"`
|
||||
Secmarks []SecmarkRule `yaml:"secmarks,omitempty"`
|
||||
Accounting []AccountingRule `yaml:"accounting,omitempty"`
|
||||
Mangle []MangleRule `yaml:"mangle,omitempty"`
|
||||
Maclist []MaclistEntry `yaml:"maclist,omitempty"`
|
||||
TCDevices []TCDevice `yaml:"tcdevices,omitempty"`
|
||||
TCClasses []TCClass `yaml:"tcclasses,omitempty"`
|
||||
TCFilters []TCFilter `yaml:"tcfilters,omitempty"`
|
||||
TCInterfaces []TCInterface `yaml:"tcinterfaces,omitempty"`
|
||||
TCPriorities []TCPriority `yaml:"tcpriority,omitempty"`
|
||||
Secmarks []SecmarkRule `yaml:"secmarks,omitempty"`
|
||||
}
|
||||
|
||||
type AddressFamily string
|
||||
@@ -52,10 +52,10 @@ const (
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
AddressFamily AddressFamily `yaml:"address_family,omitempty"`
|
||||
IPForwarding bool `yaml:"ip_forwarding"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
TableName string `yaml:"table_name"`
|
||||
AddressFamily AddressFamily `yaml:"address_family,omitempty"`
|
||||
IPForwarding bool `yaml:"ip_forwarding"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
TableName string `yaml:"table_name"`
|
||||
|
||||
// When true, auto-generate CONTINUE policies for sub-zones to their parent zones.
|
||||
ImplicitContinue bool `yaml:"implicit_continue,omitempty"`
|
||||
|
||||
@@ -992,8 +992,8 @@ func TestValidateSNAT(t *testing.T) {
|
||||
wantErr: "persistent requires an address",
|
||||
},
|
||||
{
|
||||
name: "empty snat list is valid",
|
||||
snat: nil,
|
||||
name: "empty snat list is valid",
|
||||
snat: nil,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1006,4 +1006,3 @@ func TestValidateSNAT(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ func TestValidateRoutingRules(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing providers",
|
||||
name: "missing providers",
|
||||
setup: baseConfig,
|
||||
rules: []RoutingRule{
|
||||
{Source: "10.0.0.0/8", Provider: "isp1", Priority: 1000},
|
||||
@@ -1174,9 +1174,9 @@ func TestResolveNesting(t *testing.T) {
|
||||
t.Run("simple parent-child hierarchy", func(t *testing.T) {
|
||||
cfg := Config{
|
||||
Zones: map[string]Zone{
|
||||
"fw": {Type: ZoneFirewall},
|
||||
"net": {Type: ZoneIP},
|
||||
"dmz": {Type: ZoneIP, Parents: []string{"net"}},
|
||||
"fw": {Type: ZoneFirewall},
|
||||
"net": {Type: ZoneIP},
|
||||
"dmz": {Type: ZoneIP, Parents: []string{"net"}},
|
||||
},
|
||||
}
|
||||
order, err := cfg.ResolveNesting()
|
||||
@@ -1259,9 +1259,9 @@ func TestIsSubZone(t *testing.T) {
|
||||
}{
|
||||
{"dmz", "net", true},
|
||||
{"web", "dmz", true},
|
||||
{"web", "net", true}, // transitive
|
||||
{"net", "dmz", false}, // reverse
|
||||
{"net", "net", false}, // self
|
||||
{"web", "net", true}, // transitive
|
||||
{"net", "dmz", false}, // reverse
|
||||
{"net", "net", false}, // self
|
||||
{"nosuch", "net", false}, // non-existent
|
||||
}
|
||||
|
||||
@@ -1305,10 +1305,10 @@ func TestValidateName(t *testing.T) {
|
||||
|
||||
func TestSubstituteVars(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
vars map[string]string
|
||||
want string
|
||||
name string
|
||||
input string
|
||||
vars map[string]string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "braced substitution",
|
||||
@@ -1441,4 +1441,3 @@ func TestValidateSettings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ type Interface struct {
|
||||
|
||||
type InterfaceOptions struct {
|
||||
// Rule generation options
|
||||
DHCP bool `yaml:"dhcp,omitempty"`
|
||||
DHCP bool `yaml:"dhcp,omitempty"`
|
||||
TCPFlags *bool `yaml:"tcpflags,omitempty"`
|
||||
NoSmurfs bool `yaml:"nosmurfs,omitempty"`
|
||||
NoSmurfs bool `yaml:"nosmurfs,omitempty"`
|
||||
RouteBack *bool `yaml:"routeback,omitempty"`
|
||||
Bridge bool `yaml:"bridge,omitempty"`
|
||||
DestOnly bool `yaml:"destonly,omitempty"`
|
||||
Unmanaged bool `yaml:"unmanaged,omitempty"`
|
||||
Upnp bool `yaml:"upnp,omitempty"`
|
||||
Bridge bool `yaml:"bridge,omitempty"`
|
||||
DestOnly bool `yaml:"destonly,omitempty"`
|
||||
Unmanaged bool `yaml:"unmanaged,omitempty"`
|
||||
Upnp bool `yaml:"upnp,omitempty"`
|
||||
|
||||
// Startup behavior
|
||||
Optional bool `yaml:"optional,omitempty"`
|
||||
|
||||
@@ -91,13 +91,13 @@ type Rule struct {
|
||||
}
|
||||
|
||||
type TimeSpec struct {
|
||||
Start string `yaml:"start,omitempty"`
|
||||
Stop string `yaml:"stop,omitempty"`
|
||||
Weekdays []string `yaml:"weekdays,omitempty"`
|
||||
Monthdays []int `yaml:"monthdays,omitempty"`
|
||||
DateStart string `yaml:"date_start,omitempty"`
|
||||
DateStop string `yaml:"date_stop,omitempty"`
|
||||
UTC bool `yaml:"utc,omitempty"`
|
||||
Start string `yaml:"start,omitempty"`
|
||||
Stop string `yaml:"stop,omitempty"`
|
||||
Weekdays []string `yaml:"weekdays,omitempty"`
|
||||
Monthdays []int `yaml:"monthdays,omitempty"`
|
||||
DateStart string `yaml:"date_start,omitempty"`
|
||||
DateStop string `yaml:"date_stop,omitempty"`
|
||||
UTC bool `yaml:"utc,omitempty"`
|
||||
}
|
||||
|
||||
// PortSpec supports single ports, ranges, and lists.
|
||||
|
||||
@@ -5,8 +5,8 @@ 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
|
||||
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"`
|
||||
}
|
||||
@@ -20,10 +20,10 @@ type TCDeviceOptions struct {
|
||||
|
||||
// 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
|
||||
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"`
|
||||
|
||||
+11
-11
@@ -5,19 +5,19 @@ import "fmt"
|
||||
type TunnelType string
|
||||
|
||||
const (
|
||||
TunnelIPSec TunnelType = "ipsec"
|
||||
TunnelIPSecNAT TunnelType = "ipsecnat"
|
||||
TunnelIPIP TunnelType = "ipip"
|
||||
TunnelGRE TunnelType = "gre"
|
||||
TunnelL2TP TunnelType = "l2tp"
|
||||
TunnelPPTPClient TunnelType = "pptpclient"
|
||||
TunnelPPTPServer TunnelType = "pptpserver"
|
||||
TunnelOpenVPN TunnelType = "openvpn"
|
||||
TunnelIPSec TunnelType = "ipsec"
|
||||
TunnelIPSecNAT TunnelType = "ipsecnat"
|
||||
TunnelIPIP TunnelType = "ipip"
|
||||
TunnelGRE TunnelType = "gre"
|
||||
TunnelL2TP TunnelType = "l2tp"
|
||||
TunnelPPTPClient TunnelType = "pptpclient"
|
||||
TunnelPPTPServer TunnelType = "pptpserver"
|
||||
TunnelOpenVPN TunnelType = "openvpn"
|
||||
TunnelOpenVPNClient TunnelType = "openvpnclient"
|
||||
TunnelOpenVPNServer TunnelType = "openvpnserver"
|
||||
TunnelTinc TunnelType = "tinc"
|
||||
Tunnel6to4 TunnelType = "6to4"
|
||||
TunnelGeneric TunnelType = "generic"
|
||||
TunnelTinc TunnelType = "tinc"
|
||||
Tunnel6to4 TunnelType = "6to4"
|
||||
TunnelGeneric TunnelType = "generic"
|
||||
)
|
||||
|
||||
// Tunnel defines VPN tunnel rules that allow encapsulated traffic to pass
|
||||
|
||||
Reference in New Issue
Block a user