Files

142 lines
3.4 KiB
Go
Raw Permalink Normal View History

2026-04-13 12:40:49 +03:00
package handlers
import (
"log"
2026-04-15 11:38:26 +03:00
"nano-router/clients"
"nano-router/config"
"nano-router/firewall"
"nano-router/nat"
"nano-router/network"
2026-04-13 12:40:49 +03:00
)
2026-04-15 11:38:26 +03:00
// resolveClientPolicy returns the effective routing policy for a device.
// Explicit per-device Policy takes priority; then legacy Blocked flag; then default.
func resolveClientPolicy(kd config.KnownDevice, defaultPolicy string) string {
if kd.Policy != "" {
return kd.Policy
}
if kd.Blocked {
return "disabled"
}
if defaultPolicy != "" {
return defaultPolicy
}
return "direct"
}
2026-04-13 12:40:49 +03:00
// applyAllRules rebuilds the complete nftables ruleset from the current config:
2026-04-15 11:38:26 +03:00
// NAT masquerade + tproxy for VPN clients + disabled client drops +
// user firewall rules + VLAN isolation.
2026-04-13 12:40:49 +03:00
func applyAllRules(cfg *config.AppConfig) error {
if !nat.IsInstalled() {
return nil
}
2026-04-15 11:38:26 +03:00
defaultPolicy := cfg.ClientPolicy.Default
if defaultPolicy == "" {
defaultPolicy = "direct"
}
// Classify each known device into disabled or vpn buckets.
// For devices connected on multiple interfaces (same MAC, different IPs)
// we also include all live ARP IPs so every interface gets the same policy.
arpByMAC := clients.GetARPIPsByMAC()
seenIP := make(map[string]bool)
var disabledIPs, vpnIPs []string
addIP := func(ip, policy string) {
if ip == "" || seenIP[ip] {
return
}
seenIP[ip] = true
switch policy {
case "disabled":
disabledIPs = append(disabledIPs, ip)
case "vpn":
vpnIPs = append(vpnIPs, ip)
}
}
2026-04-13 12:40:49 +03:00
for _, kd := range cfg.KnownDevices {
2026-04-15 11:38:26 +03:00
policy := resolveClientPolicy(kd, defaultPolicy)
// Primary stored IP.
ip := kd.IP
if kd.StaticIP != "" {
ip = kd.StaticIP
}
addIP(ip, policy)
// All other IPs this MAC currently has in the ARP table.
for _, arpIP := range arpByMAC[kd.MAC] {
addIP(arpIP, policy)
2026-04-13 12:40:49 +03:00
}
}
// Build the LAN interface set for isolation:
// all NAT interfaces + all VLAN interfaces (active + pending).
seen := map[string]bool{}
var lanIfaces []string
addLAN := func(name string) {
if name != "" && !seen[name] {
lanIfaces = append(lanIfaces, name)
seen[name] = true
}
}
for _, name := range cfg.NAT.Interfaces {
addLAN(name)
}
names, _ := network.GetInterfaces()
for _, name := range names {
if network.IsVLAN(name) {
addLAN(name)
2026-04-15 11:38:26 +03:00
addLAN(network.VLANParent(name))
2026-04-13 12:40:49 +03:00
}
}
for name := range network.GetAllPending() {
if network.IsVLAN(name) {
addLAN(name)
addLAN(network.VLANParent(name))
}
}
// Convert config.FirewallRule → firewall.Rule.
fwRules := make([]firewall.Rule, len(cfg.Firewall.Rules))
for i, r := range cfg.Firewall.Rules {
fwRules[i] = firewall.Rule{
ID: r.ID,
Enabled: r.Enabled,
Action: r.Action,
Protocol: r.Protocol,
SrcAddr: r.SrcAddr,
SrcPort: r.SrcPort,
DstAddr: r.DstAddr,
DstPort: r.DstPort,
InIface: r.InIface,
OutIface: r.OutIface,
Comment: r.Comment,
}
}
return firewall.ApplyAll(
firewall.NATConfig{Interfaces: cfg.NAT.Interfaces},
firewall.Config{Rules: fwRules, VLANIsolation: cfg.Firewall.VLANIsolation},
lanIfaces,
2026-04-15 11:38:26 +03:00
firewall.ClientPolicies{
DisabledIPs: disabledIPs,
VPNIPs: vpnIPs,
},
2026-04-13 12:40:49 +03:00
)
}
2026-04-15 11:38:26 +03:00
// applyBlockedFirewall is the async helper called after client or policy updates.
2026-04-13 12:40:49 +03:00
func applyBlockedFirewall() {
cfg, err := config.Load()
if err != nil {
log.Printf("Warning: load config for firewall: %v", err)
return
}
if err := applyAllRules(cfg); err != nil {
log.Printf("Warning: apply firewall rules: %v", err)
}
}