195 lines
4.7 KiB
Go
195 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
type Route struct {
|
|
Subnet string
|
|
Gateway string
|
|
}
|
|
|
|
func ParseRoutes() []Route {
|
|
out, err := exec.Command("ip", "route", "list").Output()
|
|
if err != nil {
|
|
log.Printf("ip route list failed: %v", err)
|
|
return nil
|
|
}
|
|
return parseRouteOutput(string(out))
|
|
}
|
|
|
|
func parseRouteOutput(output string) []Route {
|
|
var routes []Route
|
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
subnet := fields[0]
|
|
if subnet == "default" {
|
|
subnet = "0.0.0.0/0"
|
|
} else if !strings.Contains(subnet, "/") {
|
|
subnet = subnet + "/32"
|
|
}
|
|
|
|
gw := ""
|
|
for i := 1; i < len(fields)-1; i++ {
|
|
if fields[i] == "via" {
|
|
gw = fields[i+1]
|
|
break
|
|
}
|
|
}
|
|
if gw != "" {
|
|
routes = append(routes, Route{Subnet: subnet, Gateway: gw})
|
|
}
|
|
}
|
|
return routes
|
|
}
|
|
|
|
func ipToGatewayName(gateways []Gateway, ip string) (string, bool) {
|
|
for _, gw := range gateways {
|
|
if gw.Address == ip {
|
|
return gw.Name, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func subnetsContains(subnets []string, subnet string) bool {
|
|
for _, s := range subnets {
|
|
if s == subnet {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func routeListHas(routes []Route, subnet, gw string) bool {
|
|
for _, r := range routes {
|
|
if r.Subnet == subnet && r.Gateway == gw {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func DoRouteUpdate(cfg *Config, gatewayStates map[string]bool, subnets []string) {
|
|
if cfg.Hotswap {
|
|
hotswapUpdate(cfg, gatewayStates, subnets)
|
|
} else {
|
|
failoverUpdate(cfg, gatewayStates, subnets)
|
|
}
|
|
|
|
finalRoutes := ParseRoutes()
|
|
log.Printf("Routes updated, total: %d", len(finalRoutes))
|
|
}
|
|
|
|
func hotswapUpdate(cfg *Config, gatewayStates map[string]bool, subnets []string) {
|
|
routes := ParseRoutes()
|
|
log.Printf("Current system routes: %d", len(routes))
|
|
|
|
for _, r := range routes {
|
|
name, known := ipToGatewayName(cfg.Gateway, r.Gateway)
|
|
if !known {
|
|
continue
|
|
}
|
|
if !gatewayStates[name] {
|
|
log.Printf("Gateway %q is down, deleting route %s via %s", name, r.Subnet, r.Gateway)
|
|
delRoute(r.Subnet, r.Gateway, cfg.Protected)
|
|
continue
|
|
}
|
|
if !subnetsContains(subnets, r.Subnet) {
|
|
log.Printf("Subnet %s not in subnets list, deleting route via %s", r.Subnet, r.Gateway)
|
|
delRoute(r.Subnet, r.Gateway, cfg.Protected)
|
|
}
|
|
}
|
|
|
|
for _, subnet := range subnets {
|
|
for i, gw := range cfg.Gateway {
|
|
if !gatewayStates[gw.Name] {
|
|
continue
|
|
}
|
|
routes = ParseRoutes()
|
|
if !routeListHas(routes, subnet, gw.Address) {
|
|
metric := cfg.Metric + i
|
|
log.Printf("Adding route %s via %s (gateway %q, metric %d)", subnet, gw.Address, gw.Name, metric)
|
|
addRoute(subnet, gw.Address, metric)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func failoverUpdate(cfg *Config, gatewayStates map[string]bool, subnets []string) {
|
|
routes := ParseRoutes()
|
|
log.Printf("Current system routes: %d", len(routes))
|
|
|
|
var primaryGW *Gateway
|
|
for _, gw := range cfg.Gateway {
|
|
if gatewayStates[gw.Name] {
|
|
primaryGW = &gw
|
|
log.Printf("Selected primary gateway: %q (%s)", gw.Name, gw.Address)
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, r := range routes {
|
|
name, known := ipToGatewayName(cfg.Gateway, r.Gateway)
|
|
if !known {
|
|
continue
|
|
}
|
|
if primaryGW != nil && r.Gateway == primaryGW.Address {
|
|
if subnetsContains(subnets, r.Subnet) {
|
|
continue
|
|
}
|
|
log.Printf("Subnet %s not in subnets, deleting primary route via %s", r.Subnet, r.Gateway)
|
|
delRoute(r.Subnet, r.Gateway, cfg.Protected)
|
|
continue
|
|
}
|
|
log.Printf("Deleting route %s via %s (gateway %q)", r.Subnet, r.Gateway, name)
|
|
delRoute(r.Subnet, r.Gateway, cfg.Protected)
|
|
}
|
|
|
|
if primaryGW == nil {
|
|
log.Println("No active gateway available")
|
|
return
|
|
}
|
|
|
|
for _, subnet := range subnets {
|
|
routes = ParseRoutes()
|
|
if !routeListHas(routes, subnet, primaryGW.Address) {
|
|
log.Printf("Adding route %s via %s (metric %d)", subnet, primaryGW.Address, cfg.Metric)
|
|
addRoute(subnet, primaryGW.Address, cfg.Metric)
|
|
}
|
|
}
|
|
}
|
|
|
|
func delRoute(subnet, gw string, protected []string) {
|
|
if subnetsContains(protected, subnet) {
|
|
log.Printf("Route %s is protected, skipping delete", subnet)
|
|
return
|
|
}
|
|
cmd := exec.Command("ip", "route", "del", subnet, "via", gw)
|
|
if err := cmd.Run(); err != nil {
|
|
log.Printf("Failed to delete route %s via %s: %v", subnet, gw, err)
|
|
} else {
|
|
log.Printf("Deleted route %s via %s", subnet, gw)
|
|
}
|
|
}
|
|
|
|
func addRoute(subnet, gw string, metric int) {
|
|
cmd := exec.Command("ip", "route", "add", subnet, "via", gw, "metric", fmt.Sprintf("%d", metric))
|
|
if err := cmd.Run(); err != nil {
|
|
log.Printf("Failed to add route %s via %s: %v", subnet, gw, err)
|
|
} else {
|
|
log.Printf("Added route %s via %s", subnet, gw)
|
|
}
|
|
}
|