68 lines
1.7 KiB
Go
68 lines
1.7 KiB
Go
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, "|")
|
|
}
|