simple-route-manager/routes.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)
}
}