|
@@ -45,6 +45,7 @@ func NewRoutingManager() *RoutingManager {
|
|
|
m.syncCustomTablesToSystem()
|
|
m.syncCustomTablesToSystem()
|
|
|
m.applyAll()
|
|
m.applyAll()
|
|
|
m.startHealthCheckWorker()
|
|
m.startHealthCheckWorker()
|
|
|
|
|
+ m.startRouteSyncWorker()
|
|
|
return m
|
|
return m
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -113,16 +114,10 @@ func (m *RoutingManager) syncCustomTablesToSystem() {
|
|
|
|
|
|
|
|
func (m *RoutingManager) cleanRogueDefaultRoutes() {
|
|
func (m *RoutingManager) cleanRogueDefaultRoutes() {
|
|
|
hasManagedDefault := false
|
|
hasManagedDefault := false
|
|
|
- managedGateways := make(map[string]bool)
|
|
|
|
|
for _, r := range m.staticRoutes {
|
|
for _, r := range m.staticRoutes {
|
|
|
if normalizeDst(r.DstAddress) == "default" && !r.Disabled {
|
|
if normalizeDst(r.DstAddress) == "default" && !r.Disabled {
|
|
|
hasManagedDefault = true
|
|
hasManagedDefault = true
|
|
|
- if r.Gateway != "" {
|
|
|
|
|
- managedGateways[r.Gateway] = true
|
|
|
|
|
- }
|
|
|
|
|
- if r.Interface != "" {
|
|
|
|
|
- managedGateways[r.Interface] = true
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ break
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -148,14 +143,101 @@ func (m *RoutingManager) cleanRogueDefaultRoutes() {
|
|
|
gw = r.Gw.String()
|
|
gw = r.Gw.String()
|
|
|
}
|
|
}
|
|
|
ifName := linkMap[r.LinkIndex]
|
|
ifName := linkMap[r.LinkIndex]
|
|
|
- if r.Priority == 0 && gw != "" && !managedGateways[gw] && !managedGateways[ifName] {
|
|
|
|
|
- log.Printf("[RouteManager] Removing rogue unmanaged default route: via %s dev %s", gw, ifName)
|
|
|
|
|
|
|
+ // If priority is 0 (unmetered route from dhclient or boot) and protocol != RTPROT_STATIC:
|
|
|
|
|
+ if r.Priority == 0 && int(r.Protocol) != 4 {
|
|
|
|
|
+ log.Printf("[RouteManager] Removing rogue unmanaged default route: via %s dev %s proto %d", gw, ifName, r.Protocol)
|
|
|
_ = netlink.RouteDel(&r)
|
|
_ = netlink.RouteDel(&r)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (m *RoutingManager) getTableIDByName(name string) int {
|
|
|
|
|
+ name = strings.TrimSpace(name)
|
|
|
|
|
+ if name == "" || name == "main" {
|
|
|
|
|
+ return 254
|
|
|
|
|
+ }
|
|
|
|
|
+ if name == "default" {
|
|
|
|
|
+ return 253
|
|
|
|
|
+ }
|
|
|
|
|
+ if name == "local" {
|
|
|
|
|
+ return 255
|
|
|
|
|
+ }
|
|
|
|
|
+ for id, n := range m.getTableMap() {
|
|
|
|
|
+ if strings.EqualFold(n, name) {
|
|
|
|
|
+ return id
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if id, err := strconv.Atoi(name); err == nil {
|
|
|
|
|
+ return id
|
|
|
|
|
+ }
|
|
|
|
|
+ return 254
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *RoutingManager) startRouteSyncWorker() {
|
|
|
|
|
+ ticker := time.NewTicker(3 * time.Second)
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ for {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case <-m.stopChan:
|
|
|
|
|
+ ticker.Stop()
|
|
|
|
|
+ return
|
|
|
|
|
+ case <-ticker.C:
|
|
|
|
|
+ m.syncAllRoutesWorker()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (m *RoutingManager) syncAllRoutesWorker() {
|
|
|
|
|
+ m.mu.Lock()
|
|
|
|
|
+ defer m.mu.Unlock()
|
|
|
|
|
+
|
|
|
|
|
+ m.cleanRogueDefaultRoutes()
|
|
|
|
|
+
|
|
|
|
|
+ liveRoutes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ type groupKey struct {
|
|
|
|
|
+ dst string
|
|
|
|
|
+ table int
|
|
|
|
|
+ dist int
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ kernelRouteGroups := make(map[groupKey]bool)
|
|
|
|
|
+ for _, lr := range liveRoutes {
|
|
|
|
|
+ dst := "default"
|
|
|
|
|
+ if lr.Dst != nil {
|
|
|
|
|
+ dst = lr.Dst.String()
|
|
|
|
|
+ }
|
|
|
|
|
+ tbl := lr.Table
|
|
|
|
|
+ if tbl == 0 {
|
|
|
|
|
+ tbl = 254
|
|
|
|
|
+ }
|
|
|
|
|
+ k := groupKey{dst: normalizeDst(dst), table: tbl, dist: lr.Priority}
|
|
|
|
|
+ kernelRouteGroups[k] = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ seen := make(map[groupKey]bool)
|
|
|
|
|
+ for _, r := range m.staticRoutes {
|
|
|
|
|
+ if r.Disabled || r.HealthStatus == "offline" {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ normDst := normalizeDst(r.DstAddress)
|
|
|
|
|
+ tableID := m.getTableIDByName(r.RoutingTable)
|
|
|
|
|
+ k := groupKey{dst: normDst, table: tableID, dist: r.Distance}
|
|
|
|
|
+ if !seen[k] {
|
|
|
|
|
+ seen[k] = true
|
|
|
|
|
+ if !kernelRouteGroups[k] {
|
|
|
|
|
+ log.Printf("[RouteSync] Missing kernel route %s table %s (id %d) metric %d -> Restoring", normDst, r.RoutingTable, tableID, r.Distance)
|
|
|
|
|
+ _ = m.syncRouteGroupToKernel(r.DstAddress, r.RoutingTable, r.Distance)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (m *RoutingManager) applyAll() {
|
|
func (m *RoutingManager) applyAll() {
|
|
|
m.cleanRogueDefaultRoutes()
|
|
m.cleanRogueDefaultRoutes()
|
|
|
|
|
|
|
@@ -190,6 +272,7 @@ func (m *RoutingManager) applyAll() {
|
|
|
func isDynamicProtocol(proto netlink.RouteProtocol) bool {
|
|
func isDynamicProtocol(proto netlink.RouteProtocol) bool {
|
|
|
switch int(proto) {
|
|
switch int(proto) {
|
|
|
case 11, // RTPROT_ZEBRA
|
|
case 11, // RTPROT_ZEBRA
|
|
|
|
|
+ 12, // RTPROT_BIRD
|
|
|
16, // RTPROT_DHCP
|
|
16, // RTPROT_DHCP
|
|
|
42, // RTPROT_BABEL
|
|
42, // RTPROT_BABEL
|
|
|
186, // RTPROT_BGP
|
|
186, // RTPROT_BGP
|
|
@@ -203,6 +286,57 @@ func isDynamicProtocol(proto netlink.RouteProtocol) bool {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func getRouteProtocolName(proto netlink.RouteProtocol, priority int, isDirect bool, isMulticast bool) string {
|
|
|
|
|
+ if isMulticast {
|
|
|
|
|
+ return "multicast"
|
|
|
|
|
+ }
|
|
|
|
|
+ switch int(proto) {
|
|
|
|
|
+ case 16:
|
|
|
|
|
+ return "dhcp"
|
|
|
|
|
+ case 4:
|
|
|
|
|
+ return "static"
|
|
|
|
|
+ case 186:
|
|
|
|
|
+ return "bgp"
|
|
|
|
|
+ case 188:
|
|
|
|
|
+ return "ospf"
|
|
|
|
|
+ case 189:
|
|
|
|
|
+ return "rip"
|
|
|
|
|
+ case 11:
|
|
|
|
|
+ return "zebra"
|
|
|
|
|
+ case 42:
|
|
|
|
|
+ return "babel"
|
|
|
|
|
+ case 12: // RTPROT_BIRD
|
|
|
|
|
+ if priority == 20 {
|
|
|
|
|
+ return "bgp"
|
|
|
|
|
+ } else if priority == 200 {
|
|
|
|
|
+ return "ibgp"
|
|
|
|
|
+ } else if priority == 110 {
|
|
|
|
|
+ return "ospf"
|
|
|
|
|
+ } else if priority == 120 {
|
|
|
|
|
+ return "rip"
|
|
|
|
|
+ }
|
|
|
|
|
+ return "bgp"
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ return "connected"
|
|
|
|
|
+ case 3:
|
|
|
|
|
+ return "boot"
|
|
|
|
|
+ default:
|
|
|
|
|
+ if isDirect {
|
|
|
|
|
+ return "connected"
|
|
|
|
|
+ }
|
|
|
|
|
+ if priority == 20 {
|
|
|
|
|
+ return "bgp"
|
|
|
|
|
+ } else if priority == 200 {
|
|
|
|
|
+ return "ibgp"
|
|
|
|
|
+ } else if priority == 110 {
|
|
|
|
|
+ return "ospf"
|
|
|
|
|
+ } else if priority == 120 {
|
|
|
|
|
+ return "rip"
|
|
|
|
|
+ }
|
|
|
|
|
+ return "static"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (m *RoutingManager) listLiveRoutesLocked() []RouteItem {
|
|
func (m *RoutingManager) listLiveRoutesLocked() []RouteItem {
|
|
|
tableNames := m.getTableMap()
|
|
tableNames := m.getTableMap()
|
|
|
links, _ := netlink.LinkList()
|
|
links, _ := netlink.LinkList()
|
|
@@ -231,10 +365,11 @@ func (m *RoutingManager) listLiveRoutesLocked() []RouteItem {
|
|
|
tableName = strconv.Itoa(r.Table)
|
|
tableName = strconv.Itoa(r.Table)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- isDirect := r.Scope == netlink.SCOPE_LINK || r.Protocol == netlink.RouteProtocol(2)
|
|
|
|
|
isDyn := isDynamicProtocol(r.Protocol)
|
|
isDyn := isDynamicProtocol(r.Protocol)
|
|
|
|
|
+ isDirect := (r.Scope == netlink.SCOPE_LINK || r.Protocol == netlink.RouteProtocol(2)) && !isDyn && r.Protocol != 4
|
|
|
isStatic := !isDirect && !isDyn
|
|
isStatic := !isDirect && !isDyn
|
|
|
isMulticast := r.Type == 5 || strings.HasPrefix(dst, "224.") || strings.HasPrefix(dst, "ff") || dst == "224.0.0.0/4"
|
|
isMulticast := r.Type == 5 || strings.HasPrefix(dst, "224.") || strings.HasPrefix(dst, "ff") || dst == "224.0.0.0/4"
|
|
|
|
|
+ protoName := getRouteProtocolName(r.Protocol, r.Priority, isDirect, isMulticast)
|
|
|
|
|
|
|
|
if len(r.MultiPath) > 0 {
|
|
if len(r.MultiPath) > 0 {
|
|
|
for _, nh := range r.MultiPath {
|
|
for _, nh := range r.MultiPath {
|
|
@@ -256,6 +391,7 @@ func (m *RoutingManager) listLiveRoutesLocked() []RouteItem {
|
|
|
Gateway: nhGw,
|
|
Gateway: nhGw,
|
|
|
Interface: nhIfName,
|
|
Interface: nhIfName,
|
|
|
Distance: r.Priority,
|
|
Distance: r.Priority,
|
|
|
|
|
+ Protocol: protoName,
|
|
|
RoutingTable: tableName,
|
|
RoutingTable: tableName,
|
|
|
TableID: r.Table,
|
|
TableID: r.Table,
|
|
|
Active: true,
|
|
Active: true,
|
|
@@ -286,6 +422,7 @@ func (m *RoutingManager) listLiveRoutesLocked() []RouteItem {
|
|
|
Gateway: gw,
|
|
Gateway: gw,
|
|
|
Interface: ifName,
|
|
Interface: ifName,
|
|
|
Distance: r.Priority,
|
|
Distance: r.Priority,
|
|
|
|
|
+ Protocol: protoName,
|
|
|
RoutingTable: tableName,
|
|
RoutingTable: tableName,
|
|
|
TableID: r.Table,
|
|
TableID: r.Table,
|
|
|
Active: true,
|
|
Active: true,
|
|
@@ -314,6 +451,7 @@ func (m *RoutingManager) ListRoutes() []RouteItem {
|
|
|
liveRoutes[i].Comment = sr.Comment
|
|
liveRoutes[i].Comment = sr.Comment
|
|
|
liveRoutes[i].Static = true
|
|
liveRoutes[i].Static = true
|
|
|
liveRoutes[i].Dynamic = false
|
|
liveRoutes[i].Dynamic = false
|
|
|
|
|
+ liveRoutes[i].Protocol = "static"
|
|
|
liveRoutes[i].ID = sr.ID
|
|
liveRoutes[i].ID = sr.ID
|
|
|
if sr.Distance > 0 && liveRoutes[i].Distance == 0 {
|
|
if sr.Distance > 0 && liveRoutes[i].Distance == 0 {
|
|
|
liveRoutes[i].Distance = sr.Distance
|
|
liveRoutes[i].Distance = sr.Distance
|
|
@@ -337,6 +475,7 @@ func (m *RoutingManager) ListRoutes() []RouteItem {
|
|
|
srCopy.Active = false
|
|
srCopy.Active = false
|
|
|
srCopy.Static = true
|
|
srCopy.Static = true
|
|
|
srCopy.Dynamic = false
|
|
srCopy.Dynamic = false
|
|
|
|
|
+ srCopy.Protocol = "static"
|
|
|
liveRoutes = append(liveRoutes, srCopy)
|
|
liveRoutes = append(liveRoutes, srCopy)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -627,12 +766,6 @@ func (m *RoutingManager) syncRouteGroupToKernel(dstAddress, routingTable string,
|
|
|
}
|
|
}
|
|
|
args = append(args, "proto", "static")
|
|
args = append(args, "proto", "static")
|
|
|
_ = exec.Command("ip", args...).Run()
|
|
_ = exec.Command("ip", args...).Run()
|
|
|
-
|
|
|
|
|
- fallbackArgs := []string{"route", "del", normDst}
|
|
|
|
|
- if tbl != "main" {
|
|
|
|
|
- fallbackArgs = append(fallbackArgs, "table", tbl)
|
|
|
|
|
- }
|
|
|
|
|
- _ = exec.Command("ip", fallbackArgs...).Run()
|
|
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -651,6 +784,9 @@ func (m *RoutingManager) syncRouteGroupToKernel(dstAddress, routingTable string,
|
|
|
if iface != "" && gw != iface && (gw == "" || net.ParseIP(gw) != nil) {
|
|
if iface != "" && gw != iface && (gw == "" || net.ParseIP(gw) != nil) {
|
|
|
args = append(args, "dev", iface)
|
|
args = append(args, "dev", iface)
|
|
|
}
|
|
}
|
|
|
|
|
+ if gw != "" && iface != "" && net.ParseIP(gw) != nil {
|
|
|
|
|
+ args = append(args, "onlink")
|
|
|
|
|
+ }
|
|
|
if distance > 0 {
|
|
if distance > 0 {
|
|
|
args = append(args, "metric", strconv.Itoa(distance))
|
|
args = append(args, "metric", strconv.Itoa(distance))
|
|
|
}
|
|
}
|
|
@@ -689,6 +825,9 @@ func (m *RoutingManager) syncRouteGroupToKernel(dstAddress, routingTable string,
|
|
|
if iface != "" {
|
|
if iface != "" {
|
|
|
args = append(args, "dev", iface)
|
|
args = append(args, "dev", iface)
|
|
|
}
|
|
}
|
|
|
|
|
+ if gw != "" && iface != "" && net.ParseIP(gw) != nil {
|
|
|
|
|
+ args = append(args, "onlink")
|
|
|
|
|
+ }
|
|
|
args = append(args, "weight", "1")
|
|
args = append(args, "weight", "1")
|
|
|
}
|
|
}
|
|
|
|
|
|