14.04.2026 Update

This commit is contained in:
2026-04-15 11:38:26 +03:00
parent 6aa0349f5d
commit f50d79fab3
45 changed files with 5645 additions and 751 deletions

View File

@@ -8,8 +8,8 @@ import (
"strconv"
"strings"
"alpine-router/config"
"alpine-router/traffic"
"nano-router/config"
"nano-router/traffic"
)
const LeasesFile = "/var/lib/misc/dnsmasq.leases"
@@ -28,6 +28,7 @@ type Client struct {
Known bool `json:"known"`
Blocked bool `json:"blocked"`
StaticIP string `json:"static_ip"`
Policy string `json:"policy"` // "disabled" | "direct" | "vpn" | "" (use default)
}
func GetAll() ([]Client, error) {
@@ -78,30 +79,31 @@ func GetAll() ([]Client, error) {
found := false
for ip, c := range byIP {
if kd.MAC != "" && c.MAC == kd.MAC {
c.Blocked = kd.Blocked
matchedMAC := kd.MAC != "" && c.MAC == kd.MAC
matchedIP := kd.IP != "" && ip == kd.IP && (kd.MAC == "" || c.MAC == kd.MAC)
if !matchedMAC && !matchedIP {
continue
}
// Policy, blocked state, and hostname apply to every IP this MAC
// has on the network (device connected to multiple interfaces/VLANs).
c.Blocked = kd.Blocked
c.Policy = kd.Policy
if kd.Hostname != "" {
c.Hostname = kd.Hostname
}
// Static IP binding (DHCP reservation) and IP override only apply
// to the canonical/primary entry for this device.
if matchedIP || ip == kd.StaticIP || (!found && matchedMAC) {
c.StaticIP = kd.StaticIP
if kd.Hostname != "" {
c.Hostname = kd.Hostname
}
if kd.StaticIP != "" {
c.IP = kd.StaticIP
}
found = true
break
}
if kd.IP != "" && ip == kd.IP && (kd.MAC == "" || c.MAC == kd.MAC) {
c.Blocked = kd.Blocked
c.StaticIP = kd.StaticIP
if kd.Hostname != "" {
c.Hostname = kd.Hostname
}
if kd.StaticIP != "" {
c.IP = kd.StaticIP
}
found = true
break
}
found = true
// No break — keep iterating so all IPs for this MAC are updated.
}
if !found && key != "" {
@@ -120,6 +122,7 @@ func GetAll() ([]Client, error) {
Known: true,
Blocked: kd.Blocked,
StaticIP: kd.StaticIP,
Policy: kd.Policy,
}
}
@@ -150,8 +153,22 @@ func GetAll() ([]Client, error) {
go syncKnownDevices(byIP)
// Exclude upstream gateways — they appear in the ARP table but are not
// LAN clients. Build the exclusion set from configured interface gateways.
gatewayIPs := make(map[string]bool)
if cfgErr == nil && cfg != nil {
for _, iface := range cfg.Interfaces {
if iface.Gateway != "" {
gatewayIPs[iface.Gateway] = true
}
}
}
result := make([]Client, 0, len(byIP))
for _, c := range byIP {
if gatewayIPs[c.IP] {
continue
}
result = append(result, *c)
}
@@ -177,6 +194,7 @@ func syncKnownDevices(byIP map[string]*Client) {
savedHostnames := make(map[string]string)
savedBlocked := make(map[string]bool)
savedStaticIPs := make(map[string]string)
savedPolicies := make(map[string]string)
for _, kd := range cfg.KnownDevices {
key := kd.MAC
if key == "" {
@@ -189,6 +207,9 @@ func syncKnownDevices(byIP map[string]*Client) {
if kd.StaticIP != "" {
savedStaticIPs[key] = kd.StaticIP
}
if kd.Policy != "" {
savedPolicies[key] = kd.Policy
}
}
var seen []config.KnownDevice
@@ -210,12 +231,32 @@ func syncKnownDevices(byIP map[string]*Client) {
if sip, ok := savedStaticIPs[key]; ok {
kd.StaticIP = sip
}
if pol, ok := savedPolicies[key]; ok {
kd.Policy = pol
}
seen = append(seen, kd)
}
}
_ = config.UpdateKnownDevices(seen)
}
// GetARPIPsByMAC returns a map of MAC address → all IPs currently seen in the
// ARP table for that MAC. Used by the firewall to apply per-device policies to
// every IP a device has (e.g. multi-interface or dual-stack devices).
func GetARPIPsByMAC() map[string][]string {
arp, err := parseARPTable()
if err != nil {
return nil
}
result := make(map[string][]string)
for ip, c := range arp {
if c.MAC != "" {
result[c.MAC] = append(result[c.MAC], ip)
}
}
return result
}
func parseDNSMasqLeases() (map[string]*Client, error) {
f, err := os.Open(LeasesFile)
if err != nil {