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) } }