Files
alpine-router/handlers/policy_sync.go

68 lines
1.7 KiB
Go
Raw Normal View History

2026-04-15 11:38:26 +03:00
package handlers
import (
"sort"
"strings"
"time"
"nano-router/clients"
"nano-router/config"
)
// StartPolicySync starts a background goroutine that re-applies nftables rules
// whenever the ARP table changes for devices that have an explicit policy.
// This ensures that policy (VPN / disabled) follows a device even when its IP
// changes due to DHCP renewal or it connects on a different interface.
func StartPolicySync(interval time.Duration) {
go func() {
// Give the binary time to fully start before the first check.
time.Sleep(15 * time.Second)
var lastSig string
for {
sig := policyARPSignature()
if sig != lastSig {
lastSig = sig
applyBlockedFirewall()
}
time.Sleep(interval)
}
}()
}
// policyARPSignature returns a stable string that captures the current mapping
// of MAC→IPs only for devices that have an explicit (non-default) policy.
// If the string changes between ticks, the firewall needs to be re-applied.
func policyARPSignature() string {
cfg, err := config.Load()
if err != nil {
return ""
}
// Collect MACs with explicit policies.
policyMACs := make(map[string]string) // mac → policy
for _, kd := range cfg.KnownDevices {
if kd.MAC != "" && kd.Policy != "" {
policyMACs[kd.MAC] = kd.Policy
}
// Also treat legacy blocked=true as disabled policy.
if kd.MAC != "" && kd.Blocked && kd.Policy == "" {
policyMACs[kd.MAC] = "disabled"
}
}
if len(policyMACs) == 0 {
return ""
}
arpByMAC := clients.GetARPIPsByMAC()
var parts []string
for mac, policy := range policyMACs {
ips := arpByMAC[mac]
sort.Strings(ips)
parts = append(parts, policy+":"+mac+"="+strings.Join(ips, ","))
}
sort.Strings(parts)
return strings.Join(parts, "|")
}