Add per-device L2/misc long-tail: tunnels/stopped_rules/proxy_arp/proxy_ndp/arp_rules/maclist
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

Storage + CRUD (migration 0007, model, id-keyed store, REST handlers) + compiler
rendering, each owned by a device. proxy_arp/proxy_ndp share the ProxyEntry
shape via table-parameterized store helpers.
This commit is contained in:
benvin
2026-07-26 15:16:13 +10:00
committed by Ben Vincent
parent 22e0d07227
commit d9d192757b
7 changed files with 828 additions and 0 deletions
+31
View File
@@ -39,6 +39,12 @@ type Input struct {
Providers []model.Provider
Routes []model.Route
RoutingRules []model.RoutingRule
Tunnels []model.Tunnel
StoppedRules []model.StoppedRule
ProxyARP []model.ProxyEntry
ProxyNDP []model.ProxyEntry
ArpRules []model.ArpRule
Maclist []model.MaclistEntry
}
// RenderedConfig is the per-device output served to the agent.
@@ -60,6 +66,12 @@ type RenderedConfig struct {
Providers []RenderedProvider `yaml:"providers,omitempty" json:"providers,omitempty"`
Routes []RenderedRoute `yaml:"routes,omitempty" json:"routes,omitempty"`
RoutingRules []RenderedRoutingRule `yaml:"routing_rules,omitempty" json:"routing_rules,omitempty"`
Tunnels []RenderedTunnel `yaml:"tunnels,omitempty" json:"tunnels,omitempty"`
StoppedRules []RenderedStoppedRule `yaml:"stopped_rules,omitempty" json:"stopped_rules,omitempty"`
ProxyARP []RenderedProxy `yaml:"proxy_arp,omitempty" json:"proxy_arp,omitempty"`
ProxyNDP []RenderedProxy `yaml:"proxy_ndp,omitempty" json:"proxy_ndp,omitempty"`
ArpRules []RenderedArpRule `yaml:"arp_rules,omitempty" json:"arp_rules,omitempty"`
Maclist []RenderedMaclist `yaml:"maclist,omitempty" json:"maclist,omitempty"`
}
// RenderedSNAT is a resolved SNAT/masquerade rule: source addresses masqueraded
@@ -207,6 +219,7 @@ func Render(in Input) (*RenderedConfig, error) {
// Per-device long-tail sections owned by this device.
renderPerDevice(in, out)
renderPerDeviceL2(in, out)
return out, nil
}
@@ -449,5 +462,23 @@ func Compile(ctx context.Context, s *store.Store, device string) (*RenderedConfi
if in.RoutingRules, err = s.ListRoutingRules(ctx); err != nil {
return nil, err
}
if in.Tunnels, err = s.ListTunnels(ctx); err != nil {
return nil, err
}
if in.StoppedRules, err = s.ListStoppedRules(ctx); err != nil {
return nil, err
}
if in.ProxyARP, err = s.ListProxyARP(ctx); err != nil {
return nil, err
}
if in.ProxyNDP, err = s.ListProxyNDP(ctx); err != nil {
return nil, err
}
if in.ArpRules, err = s.ListArpRules(ctx); err != nil {
return nil, err
}
if in.Maclist, err = s.ListMaclist(ctx); err != nil {
return nil, err
}
return Render(in)
}
+111
View File
@@ -0,0 +1,111 @@
package compiler
import "git.unkin.net/unkin/tomswallapi/internal/model"
// Per-device L2/misc long-tail sections rendered into a device's config.
type RenderedTunnel struct {
Type string `yaml:"type" json:"type"`
Zone string `yaml:"zone" json:"zone"`
Gateways []string `yaml:"gateways,omitempty" json:"gateways,omitempty"`
GatewayZones []string `yaml:"gateway_zones,omitempty" json:"gateway_zones,omitempty"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
type RenderedStoppedRule struct {
Action string `yaml:"action" json:"action"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
Proto string `yaml:"proto,omitempty" json:"proto,omitempty"`
DPort []string `yaml:"dport,omitempty" json:"dport,omitempty"`
SPort []string `yaml:"sport,omitempty" json:"sport,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
type RenderedProxy struct {
Address string `yaml:"address" json:"address"`
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
External string `yaml:"external" json:"external"`
HaveRoute bool `yaml:"haveroute,omitempty" json:"haveroute,omitempty"`
Persistent bool `yaml:"persistent,omitempty" json:"persistent,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
type RenderedArpRule struct {
Action string `yaml:"action" json:"action"`
ActionAddress string `yaml:"action_address,omitempty" json:"action_address,omitempty"`
ActionMAC string `yaml:"action_mac,omitempty" json:"action_mac,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
Opcode int `yaml:"opcode,omitempty" json:"opcode,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
type RenderedMaclist struct {
Action string `yaml:"action" json:"action"`
Interface string `yaml:"interface" json:"interface"`
MAC string `yaml:"mac,omitempty" json:"mac,omitempty"`
Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"`
Log string `yaml:"log,omitempty" json:"log,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
func renderPerDeviceL2(in Input, out *RenderedConfig) {
dev := in.Device.Name
for _, t := range in.Tunnels {
if t.Device != dev {
continue
}
out.Tunnels = append(out.Tunnels, RenderedTunnel{
Type: t.Type, Zone: t.Zone, Gateways: t.Gateways,
GatewayZones: t.GatewayZones, Port: t.Port, Comment: t.Comment,
})
}
for _, r := range in.StoppedRules {
if r.Device != dev {
continue
}
out.StoppedRules = append(out.StoppedRules, RenderedStoppedRule{
Action: r.Action, Source: r.Source, Dest: r.Dest, Proto: r.Proto,
DPort: r.DPort, SPort: r.SPort, Comment: r.Comment,
})
}
for _, p := range in.ProxyARP {
if p.Device != dev {
continue
}
out.ProxyARP = append(out.ProxyARP, renderProxy(p))
}
for _, p := range in.ProxyNDP {
if p.Device != dev {
continue
}
out.ProxyNDP = append(out.ProxyNDP, renderProxy(p))
}
for _, a := range in.ArpRules {
if a.Device != dev {
continue
}
out.ArpRules = append(out.ArpRules, RenderedArpRule{
Action: a.Action, ActionAddress: a.ActionAddress, ActionMAC: a.ActionMAC,
Source: a.Source, Dest: a.Dest, Opcode: a.Opcode, Comment: a.Comment,
})
}
for _, m := range in.Maclist {
if m.Device != dev {
continue
}
out.Maclist = append(out.Maclist, RenderedMaclist{
Action: m.Action, Interface: m.Interface, MAC: m.MAC,
Addresses: m.Addresses, Log: m.Log, Comment: m.Comment,
})
}
}
func renderProxy(p model.ProxyEntry) RenderedProxy {
return RenderedProxy{
Address: p.Address, Interface: p.Interface, External: p.External,
HaveRoute: p.HaveRoute, Persistent: p.Persistent, Comment: p.Comment,
}
}