|
|
@@ -18,7 +18,7 @@ import (
|
|
|
"github.com/vishvananda/netlink"
|
|
|
)
|
|
|
|
|
|
-const (
|
|
|
+var (
|
|
|
StaticRoutesStorePath = "/etc/debtik/routing/static_routes.json"
|
|
|
PolicyRulesStorePath = "/etc/debtik/routing/policy_rules.json"
|
|
|
CustomTablesStorePath = "/etc/debtik/routing/custom_tables.json"
|
|
|
@@ -585,7 +585,10 @@ func (m *RoutingManager) ListRoutes() []RouteItem {
|
|
|
return liveRoutes
|
|
|
}
|
|
|
|
|
|
-func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
+func (m *RoutingManager) SaveRoute(r *RouteItem) (err error) {
|
|
|
+ if r == nil {
|
|
|
+ return fmt.Errorf("route cannot be nil")
|
|
|
+ }
|
|
|
m.mu.Lock()
|
|
|
defer m.mu.Unlock()
|
|
|
before := append([]RouteItem(nil), m.staticRoutes...)
|
|
|
@@ -674,6 +677,25 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 4. Fallback match: if editing with OldDstAddress but gateway was changed and didn't match oldGw
|
|
|
+ if foundIndex == -1 && r.OldDstAddress != "" {
|
|
|
+ normOldDst := normalizeDst(r.OldDstAddress)
|
|
|
+ oldTable := r.OldRoutingTable
|
|
|
+ if oldTable == "" {
|
|
|
+ oldTable = "main"
|
|
|
+ }
|
|
|
+ for i, existing := range m.staticRoutes {
|
|
|
+ exTbl := existing.RoutingTable
|
|
|
+ if exTbl == "" {
|
|
|
+ exTbl = "main"
|
|
|
+ }
|
|
|
+ if normalizeDst(existing.DstAddress) == normOldDst && exTbl == oldTable {
|
|
|
+ foundIndex = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if r.CheckInterval <= 0 {
|
|
|
r.CheckInterval = 5
|
|
|
}
|
|
|
@@ -699,7 +721,9 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
|
|
|
if normalizeDst(oldExisting.DstAddress) != normalizeDst(r.DstAddress) ||
|
|
|
oldExisting.RoutingTable != r.RoutingTable ||
|
|
|
- oldExisting.Distance != r.Distance {
|
|
|
+ oldExisting.Distance != r.Distance ||
|
|
|
+ oldExisting.Gateway != r.Gateway ||
|
|
|
+ oldExisting.Interface != r.Interface {
|
|
|
oldCopy := oldExisting
|
|
|
oldRouteToClean = &oldCopy
|
|
|
}
|
|
|
@@ -710,7 +734,7 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
r.HealthLossPct = oldExisting.HealthLossPct
|
|
|
r.HealthLastCheck = oldExisting.HealthLastCheck
|
|
|
}
|
|
|
- m.staticRoutes[foundIndex] = r
|
|
|
+ m.staticRoutes[foundIndex] = *r
|
|
|
} else {
|
|
|
if r.ID == "" || strings.HasPrefix(r.ID, "kr-") {
|
|
|
r.ID = fmt.Sprintf("route-%d", time.Now().UnixNano())
|
|
|
@@ -724,7 +748,7 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- m.staticRoutes = append(m.staticRoutes, r)
|
|
|
+ m.staticRoutes = append(m.staticRoutes, *r)
|
|
|
}
|
|
|
|
|
|
if r.OldDstAddress != "" {
|
|
|
@@ -732,7 +756,11 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
if r.OldDistance != nil {
|
|
|
oldDist = *r.OldDistance
|
|
|
}
|
|
|
- if normalizeDst(r.OldDstAddress) != normalizeDst(r.DstAddress) || r.OldRoutingTable != r.RoutingTable || oldDist != r.Distance {
|
|
|
+ if normalizeDst(r.OldDstAddress) != normalizeDst(r.DstAddress) ||
|
|
|
+ r.OldRoutingTable != r.RoutingTable ||
|
|
|
+ oldDist != r.Distance ||
|
|
|
+ r.OldGateway != r.Gateway ||
|
|
|
+ r.OldInterface != r.Interface {
|
|
|
oldRouteToClean = &RouteItem{
|
|
|
DstAddress: r.OldDstAddress,
|
|
|
Gateway: r.OldGateway,
|
|
|
@@ -754,13 +782,26 @@ func (m *RoutingManager) SaveRoute(r RouteItem) (err error) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // In-memory deduplication pass on static routes
|
|
|
+ seenKeys := make(map[string]bool)
|
|
|
+ var deduped []RouteItem
|
|
|
+ for _, sr := range m.staticRoutes {
|
|
|
+ k := fmt.Sprintf("%s|%s|%s|%s|%d", normalizeDst(sr.DstAddress), sr.RoutingTable, sr.Gateway, sr.Interface, sr.Distance)
|
|
|
+ if !seenKeys[k] {
|
|
|
+ seenKeys[k] = true
|
|
|
+ deduped = append(deduped, sr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.staticRoutes = deduped
|
|
|
+
|
|
|
if saveErr := m.save(); saveErr != nil {
|
|
|
return saveErr
|
|
|
}
|
|
|
|
|
|
- // Clean up old route group if destination, routing table or distance changed
|
|
|
+ // Clean up old route group if destination, routing table, distance, gateway, or interface changed
|
|
|
if oldRouteToClean != nil {
|
|
|
_ = m.syncRouteGroupToKernel(oldRouteToClean.DstAddress, oldRouteToClean.RoutingTable, oldRouteToClean.Distance)
|
|
|
+ _ = m.deleteDirectKernelRoute(*oldRouteToClean)
|
|
|
}
|
|
|
|
|
|
// Apply updated/new route group to kernel
|
|
|
@@ -811,6 +852,7 @@ func (m *RoutingManager) DeleteRoute(id string) error {
|
|
|
updated = append(updated, r)
|
|
|
}
|
|
|
}
|
|
|
+ _ = m.deleteDirectKernelRoute(lr)
|
|
|
break
|
|
|
} else {
|
|
|
// Pure kernel route, not managed in static_routes.json
|
|
|
@@ -824,6 +866,7 @@ func (m *RoutingManager) DeleteRoute(id string) error {
|
|
|
m.staticRoutes = updated
|
|
|
_ = m.save()
|
|
|
_ = m.syncRouteGroupToKernel(target.DstAddress, target.RoutingTable, target.Distance)
|
|
|
+ _ = m.deleteDirectKernelRoute(*target)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -1108,14 +1151,21 @@ func (m *RoutingManager) removeRouteFromKernel(r RouteItem) error {
|
|
|
|
|
|
func (m *RoutingManager) deleteDirectKernelRoute(r RouteItem) error {
|
|
|
normDst := normalizeDst(r.DstAddress)
|
|
|
- args := []string{"route", "del", normDst}
|
|
|
gw := strings.TrimSpace(r.Gateway)
|
|
|
- if gw != "" && net.ParseIP(gw) != nil {
|
|
|
- args = append(args, "via", gw)
|
|
|
- }
|
|
|
- iface := strings.TrimSpace(r.Interface)
|
|
|
- if iface != "" {
|
|
|
- args = append(args, "dev", iface)
|
|
|
+ isBlackhole := strings.EqualFold(gw, "blackhole") || strings.EqualFold(r.Action, "blackhole")
|
|
|
+
|
|
|
+ var args []string
|
|
|
+ if isBlackhole {
|
|
|
+ args = []string{"route", "del", "blackhole", normDst}
|
|
|
+ } else {
|
|
|
+ args = []string{"route", "del", normDst}
|
|
|
+ if gw != "" && net.ParseIP(gw) != nil {
|
|
|
+ args = append(args, "via", gw)
|
|
|
+ }
|
|
|
+ iface := strings.TrimSpace(r.Interface)
|
|
|
+ if iface != "" {
|
|
|
+ args = append(args, "dev", iface)
|
|
|
+ }
|
|
|
}
|
|
|
if r.RoutingTable != "" && r.RoutingTable != "main" {
|
|
|
args = append(args, "table", r.RoutingTable)
|
|
|
@@ -1130,6 +1180,20 @@ func (m *RoutingManager) deleteDirectKernelRoute(r RouteItem) error {
|
|
|
log.Printf("[RouteManager] direct ip route del (%s): %s (%v), attempting fallback", strings.Join(args, " "), string(out), err)
|
|
|
}
|
|
|
|
|
|
+ if isBlackhole {
|
|
|
+ fbBhArgs := []string{"route", "del", "blackhole", normDst}
|
|
|
+ if r.RoutingTable != "" && r.RoutingTable != "main" {
|
|
|
+ fbBhArgs = append(fbBhArgs, "table", r.RoutingTable)
|
|
|
+ }
|
|
|
+ if r.Distance > 0 {
|
|
|
+ fbBhArgs = append(fbBhArgs, "metric", strconv.Itoa(r.Distance))
|
|
|
+ }
|
|
|
+ if err := exec.Command("ip", fbBhArgs...).Run(); err == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return exec.Command("ip", "route", "del", "blackhole", normDst).Run()
|
|
|
+ }
|
|
|
+
|
|
|
fbArgs := []string{"route", "del", normDst}
|
|
|
if r.RoutingTable != "" && r.RoutingTable != "main" {
|
|
|
fbArgs = append(fbArgs, "table", r.RoutingTable)
|