|
|
@@ -100,6 +100,16 @@ func (m *Manager) initDefaults() {
|
|
|
OSPFAreaRanges: []OSPFAreaRange{},
|
|
|
OSPFInterfaces: []OSPFInterface{},
|
|
|
OSPFStaticNbrs: []OSPFStaticNeighbor{},
|
|
|
+ RIPInstances: []RIPInstance{},
|
|
|
+ RIPInterfaces: []RIPInterface{},
|
|
|
+ RIPNeighbors: []RIPNeighbor{},
|
|
|
+ BFDInstance: BFDInstance{Enabled: false},
|
|
|
+ BFDInterfaces: []BFDInterface{},
|
|
|
+ BFDNeighbors: []BFDNeighbor{},
|
|
|
+ BabelInstances: []BabelInstance{},
|
|
|
+ BabelInterfaces: []BabelInterface{},
|
|
|
+ RAdvInstances: []RAdvInstance{},
|
|
|
+ RAdvInterfaces: []RAdvInterface{},
|
|
|
RoutingFilters: []RoutingFilterRule{},
|
|
|
RoutingCommunityLists: []RoutingFilterCommunityList{},
|
|
|
RoutingASLists: []RoutingFilterASList{},
|
|
|
@@ -124,6 +134,33 @@ func (m *Manager) load() error {
|
|
|
if m.config.RoutingASLists == nil {
|
|
|
m.config.RoutingASLists = []RoutingFilterASList{}
|
|
|
}
|
|
|
+ if m.config.RIPInstances == nil {
|
|
|
+ m.config.RIPInstances = []RIPInstance{}
|
|
|
+ }
|
|
|
+ if m.config.RIPInterfaces == nil {
|
|
|
+ m.config.RIPInterfaces = []RIPInterface{}
|
|
|
+ }
|
|
|
+ if m.config.RIPNeighbors == nil {
|
|
|
+ m.config.RIPNeighbors = []RIPNeighbor{}
|
|
|
+ }
|
|
|
+ if m.config.BFDInterfaces == nil {
|
|
|
+ m.config.BFDInterfaces = []BFDInterface{}
|
|
|
+ }
|
|
|
+ if m.config.BFDNeighbors == nil {
|
|
|
+ m.config.BFDNeighbors = []BFDNeighbor{}
|
|
|
+ }
|
|
|
+ if m.config.BabelInstances == nil {
|
|
|
+ m.config.BabelInstances = []BabelInstance{}
|
|
|
+ }
|
|
|
+ if m.config.BabelInterfaces == nil {
|
|
|
+ m.config.BabelInterfaces = []BabelInterface{}
|
|
|
+ }
|
|
|
+ if m.config.RAdvInstances == nil {
|
|
|
+ m.config.RAdvInstances = []RAdvInstance{}
|
|
|
+ }
|
|
|
+ if m.config.RAdvInterfaces == nil {
|
|
|
+ m.config.RAdvInterfaces = []RAdvInterface{}
|
|
|
+ }
|
|
|
|
|
|
if len(m.config.OSPFInstances) == 0 {
|
|
|
rID := m.config.OSPFInstance.RouterID
|
|
|
@@ -283,6 +320,9 @@ func (m *Manager) generateBirdConfig() string {
|
|
|
sb.WriteString(" if source = RTS_RIP then {\n")
|
|
|
sb.WriteString(" krt_metric = 120;\n")
|
|
|
sb.WriteString(" }\n")
|
|
|
+ sb.WriteString(" if source = RTS_BABEL then {\n")
|
|
|
+ sb.WriteString(" krt_metric = 130;\n")
|
|
|
+ sb.WriteString(" }\n")
|
|
|
sb.WriteString(" if source ~ [ RTS_STATIC, RTS_INHERIT ] then {\n")
|
|
|
sb.WriteString(" krt_metric = 255;\n")
|
|
|
sb.WriteString(" }\n")
|
|
|
@@ -291,6 +331,14 @@ func (m *Manager) generateBirdConfig() string {
|
|
|
sb.WriteString(" };\n")
|
|
|
sb.WriteString(" learn;\n")
|
|
|
sb.WriteString("}\n\n")
|
|
|
+ sb.WriteString("protocol kernel {\n")
|
|
|
+ sb.WriteString(" metric 0;\n")
|
|
|
+ sb.WriteString(" ipv6 {\n")
|
|
|
+ sb.WriteString(" import none;\n")
|
|
|
+ sb.WriteString(" export all;\n")
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ sb.WriteString(" learn;\n")
|
|
|
+ sb.WriteString("}\n\n")
|
|
|
|
|
|
// Static BGP Networks announcements
|
|
|
var activeNets []string
|
|
|
@@ -533,6 +581,9 @@ func (m *Manager) generateBirdConfig() string {
|
|
|
}
|
|
|
|
|
|
sb.WriteString(" };\n")
|
|
|
+ if peer.UseBFD {
|
|
|
+ sb.WriteString(" bfd yes;\n")
|
|
|
+ }
|
|
|
sb.WriteString("}\n\n")
|
|
|
}
|
|
|
}
|
|
|
@@ -790,6 +841,9 @@ func (m *Manager) generateBirdConfig() string {
|
|
|
sb.WriteString(" authentication simple;\n")
|
|
|
sb.WriteString(fmt.Sprintf(" password \"%s\";\n", iface.AuthKey))
|
|
|
}
|
|
|
+ if iface.UseBFD {
|
|
|
+ sb.WriteString(" bfd yes;\n")
|
|
|
+ }
|
|
|
|
|
|
// Find static neighbors matching this interface template
|
|
|
var templateNbrs []OSPFStaticNeighbor
|
|
|
@@ -864,6 +918,304 @@ func (m *Manager) generateBirdConfig() string {
|
|
|
sb.WriteString("}\n\n")
|
|
|
}
|
|
|
|
|
|
+ // BFD Protocol (/routing bfd)
|
|
|
+ if m.config.BFDInstance.Enabled || len(m.config.BFDInterfaces) > 0 || len(m.config.BFDNeighbors) > 0 {
|
|
|
+ bfdDisabled := m.config.BFDInstance.Disabled
|
|
|
+ sb.WriteString("protocol bfd bfd_main {\n")
|
|
|
+ if bfdDisabled {
|
|
|
+ sb.WriteString(" disabled;\n")
|
|
|
+ }
|
|
|
+ for _, bif := range m.config.BFDInterfaces {
|
|
|
+ if bif.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ifName := strings.TrimSpace(bif.Interface)
|
|
|
+ if ifName == "" {
|
|
|
+ ifName = "*"
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" interface \"%s\" {\n", ifName))
|
|
|
+ if bif.MinRxInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" min rx interval %d ms;\n", bif.MinRxInterval))
|
|
|
+ }
|
|
|
+ if bif.MinTxInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" min tx interval %d ms;\n", bif.MinTxInterval))
|
|
|
+ }
|
|
|
+ if bif.IdleTxInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" idle tx interval %d ms;\n", bif.IdleTxInterval))
|
|
|
+ }
|
|
|
+ if bif.Multiplier > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" multiplier %d;\n", bif.Multiplier))
|
|
|
+ }
|
|
|
+ if bif.Passive {
|
|
|
+ sb.WriteString(" passive yes;\n")
|
|
|
+ }
|
|
|
+ if bif.Auth != "" && bif.Auth != "none" && bif.AuthKey != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" authentication %s;\n", bif.Auth))
|
|
|
+ sb.WriteString(fmt.Sprintf(" password \"%s\";\n", bif.AuthKey))
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+ for _, bn := range m.config.BFDNeighbors {
|
|
|
+ if bn.Disabled || strings.TrimSpace(bn.Address) == "" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if bn.Interface != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" neighbor %s dev \"%s\";\n", strings.TrimSpace(bn.Address), strings.TrimSpace(bn.Interface)))
|
|
|
+ } else if bn.Multihop {
|
|
|
+ sb.WriteString(fmt.Sprintf(" neighbor %s multihop;\n", strings.TrimSpace(bn.Address)))
|
|
|
+ } else {
|
|
|
+ sb.WriteString(fmt.Sprintf(" neighbor %s;\n", strings.TrimSpace(bn.Address)))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sb.WriteString("}\n\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ // RIP Protocols (/routing rip)
|
|
|
+ for idx, inst := range m.config.RIPInstances {
|
|
|
+ if inst.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ instProtoName := sanitizeIdentifier("rip", fmt.Sprintf("%s_%d", inst.Name, idx))
|
|
|
+
|
|
|
+ protoKeyword := "protocol rip"
|
|
|
+ channelKeyword := "ipv4"
|
|
|
+ if inst.Version == 1 {
|
|
|
+ protoKeyword = "protocol rip ng"
|
|
|
+ channelKeyword = "ipv6"
|
|
|
+ }
|
|
|
+
|
|
|
+ sb.WriteString(fmt.Sprintf("%s %s {\n", protoKeyword, instProtoName))
|
|
|
+ if inst.VRF != "" && inst.VRF != "main" && inst.VRF != "default" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" vrf %s;\n", inst.VRF))
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" %s {\n", channelKeyword))
|
|
|
+ if inst.RoutingTable != "" && inst.RoutingTable != "main" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" table %s;\n", inst.RoutingTable))
|
|
|
+ }
|
|
|
+ sb.WriteString(" import filter { krt_metric = 120; accept; };\n")
|
|
|
+
|
|
|
+ var ripExports []string
|
|
|
+ if inst.RedistributeStatic {
|
|
|
+ ripExports = append(ripExports, "RTS_STATIC", "RTS_INHERIT")
|
|
|
+ }
|
|
|
+ if inst.RedistributeConnected {
|
|
|
+ ripExports = append(ripExports, "RTS_DEVICE")
|
|
|
+ }
|
|
|
+ if inst.RedistributeOSPF {
|
|
|
+ ripExports = append(ripExports, "RTS_OSPF")
|
|
|
+ }
|
|
|
+ if inst.RedistributeBGP {
|
|
|
+ ripExports = append(ripExports, "RTS_BGP")
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(ripExports) > 0 || inst.RedistributeDefault {
|
|
|
+ sb.WriteString(" export filter {\n")
|
|
|
+ if inst.RedistributeDefault {
|
|
|
+ sb.WriteString(" if net = 0.0.0.0/0 || net = ::/0 then accept;\n")
|
|
|
+ }
|
|
|
+ if len(ripExports) > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" if source ~ [ %s ] then accept;\n", strings.Join(ripExports, ", ")))
|
|
|
+ }
|
|
|
+ sb.WriteString(" reject;\n")
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ } else {
|
|
|
+ sb.WriteString(" export none;\n")
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n\n")
|
|
|
+
|
|
|
+ // Interfaces for this RIP instance
|
|
|
+ for _, iface := range m.config.RIPInterfaces {
|
|
|
+ if iface.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if iface.Instance != "" && !strings.EqualFold(iface.Instance, inst.Name) && !strings.EqualFold(iface.Instance, "Default") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ifName := strings.TrimSpace(iface.Interface)
|
|
|
+ if ifName == "" {
|
|
|
+ ifName = "*"
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" interface \"%s\" {\n", ifName))
|
|
|
+ if iface.Metric > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" metric %d;\n", iface.Metric))
|
|
|
+ }
|
|
|
+ if iface.Mode == "broadcast" {
|
|
|
+ sb.WriteString(" mode broadcast;\n")
|
|
|
+ } else {
|
|
|
+ sb.WriteString(" mode multicast;\n")
|
|
|
+ }
|
|
|
+ if inst.Version == 2 || inst.Version == 0 {
|
|
|
+ sb.WriteString(" version 2;\n")
|
|
|
+ }
|
|
|
+ if inst.UpdateTime > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" update time %d;\n", inst.UpdateTime))
|
|
|
+ }
|
|
|
+ if inst.TimeoutTime > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" timeout time %d;\n", inst.TimeoutTime))
|
|
|
+ }
|
|
|
+ if iface.Passive {
|
|
|
+ sb.WriteString(" passive yes;\n")
|
|
|
+ }
|
|
|
+ if iface.UseBFD {
|
|
|
+ sb.WriteString(" bfd yes;\n")
|
|
|
+ }
|
|
|
+ if iface.Auth != "" && iface.Auth != "none" && iface.AuthKey != "" {
|
|
|
+ if iface.Auth == "md5" || iface.Auth == "cryptographic" {
|
|
|
+ sb.WriteString(" authentication cryptographic;\n")
|
|
|
+ } else {
|
|
|
+ sb.WriteString(" authentication plaintext;\n")
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" password \"%s\";\n", iface.AuthKey))
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ sb.WriteString("}\n\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Babel Protocols (/routing babel)
|
|
|
+ for idx, inst := range m.config.BabelInstances {
|
|
|
+ if inst.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ instProtoName := sanitizeIdentifier("babel", fmt.Sprintf("%s_%d", inst.Name, idx))
|
|
|
+ sb.WriteString(fmt.Sprintf("protocol babel %s {\n", instProtoName))
|
|
|
+ if inst.VRF != "" && inst.VRF != "main" && inst.VRF != "default" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" vrf %s;\n", inst.VRF))
|
|
|
+ }
|
|
|
+ if inst.RouterID != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" router id %s;\n", inst.RouterID))
|
|
|
+ }
|
|
|
+ sb.WriteString(" ipv4 {\n")
|
|
|
+ if inst.RoutingTable != "" && inst.RoutingTable != "main" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" table %s;\n", inst.RoutingTable))
|
|
|
+ }
|
|
|
+ sb.WriteString(" import filter { krt_metric = 130; accept; };\n")
|
|
|
+
|
|
|
+ var babelExports []string
|
|
|
+ if inst.RedistributeStatic {
|
|
|
+ babelExports = append(babelExports, "RTS_STATIC", "RTS_INHERIT")
|
|
|
+ }
|
|
|
+ if inst.RedistributeConnected {
|
|
|
+ babelExports = append(babelExports, "RTS_DEVICE")
|
|
|
+ }
|
|
|
+ if inst.RedistributeOSPF {
|
|
|
+ babelExports = append(babelExports, "RTS_OSPF")
|
|
|
+ }
|
|
|
+ if inst.RedistributeBGP {
|
|
|
+ babelExports = append(babelExports, "RTS_BGP")
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(babelExports) > 0 || inst.RedistributeDefault {
|
|
|
+ sb.WriteString(" export filter {\n")
|
|
|
+ if inst.RedistributeDefault {
|
|
|
+ sb.WriteString(" if net = 0.0.0.0/0 then accept;\n")
|
|
|
+ }
|
|
|
+ if len(babelExports) > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" if source ~ [ %s ] then accept;\n", strings.Join(babelExports, ", ")))
|
|
|
+ }
|
|
|
+ sb.WriteString(" reject;\n")
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ } else {
|
|
|
+ sb.WriteString(" export none;\n")
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n\n")
|
|
|
+
|
|
|
+ sb.WriteString(" ipv6 {\n")
|
|
|
+ sb.WriteString(" import all;\n")
|
|
|
+ sb.WriteString(" export all;\n")
|
|
|
+ sb.WriteString(" };\n\n")
|
|
|
+
|
|
|
+ for _, iface := range m.config.BabelInterfaces {
|
|
|
+ if iface.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if iface.Instance != "" && !strings.EqualFold(iface.Instance, inst.Name) && !strings.EqualFold(iface.Instance, "Default") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ifName := strings.TrimSpace(iface.Interface)
|
|
|
+ if ifName == "" {
|
|
|
+ ifName = "*"
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" interface \"%s\" {\n", ifName))
|
|
|
+ if iface.Type != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" type %s;\n", iface.Type))
|
|
|
+ } else {
|
|
|
+ sb.WriteString(" type wired;\n")
|
|
|
+ }
|
|
|
+ if iface.RxCost > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" rxcost %d;\n", iface.RxCost))
|
|
|
+ }
|
|
|
+ if iface.HelloInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" hello interval %d s;\n", iface.HelloInterval))
|
|
|
+ }
|
|
|
+ if iface.UpdateInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" update interval %d s;\n", iface.UpdateInterval))
|
|
|
+ }
|
|
|
+ if iface.Auth != "" && iface.Auth != "none" && iface.AuthKey != "" {
|
|
|
+ sb.WriteString(" authentication mac;\n")
|
|
|
+ sb.WriteString(fmt.Sprintf(" password \"%s\";\n", iface.AuthKey))
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+ sb.WriteString("}\n\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ // RAdv Protocols (/routing radv)
|
|
|
+ for idx, inst := range m.config.RAdvInstances {
|
|
|
+ if inst.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ instProtoName := sanitizeIdentifier("radv", fmt.Sprintf("%s_%d", inst.Name, idx))
|
|
|
+ sb.WriteString(fmt.Sprintf("protocol radv %s {\n", instProtoName))
|
|
|
+ for _, iface := range m.config.RAdvInterfaces {
|
|
|
+ if iface.Disabled {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if iface.Instance != "" && !strings.EqualFold(iface.Instance, inst.Name) && !strings.EqualFold(iface.Instance, "Default") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ifName := strings.TrimSpace(iface.Interface)
|
|
|
+ if ifName == "" {
|
|
|
+ ifName = "*"
|
|
|
+ }
|
|
|
+ sb.WriteString(fmt.Sprintf(" interface \"%s\" {\n", ifName))
|
|
|
+ if iface.MinRAInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" min ra interval %d;\n", iface.MinRAInterval))
|
|
|
+ }
|
|
|
+ if iface.MaxRAInterval > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" max ra interval %d;\n", iface.MaxRAInterval))
|
|
|
+ }
|
|
|
+ if iface.DefaultLifetime > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" default lifetime %d;\n", iface.DefaultLifetime))
|
|
|
+ }
|
|
|
+ if iface.HopLimit > 0 {
|
|
|
+ sb.WriteString(fmt.Sprintf(" hop limit %d;\n", iface.HopLimit))
|
|
|
+ }
|
|
|
+ for _, pfx := range iface.Prefixes {
|
|
|
+ pfx = strings.TrimSpace(pfx)
|
|
|
+ if pfx != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" prefix %s {\n", pfx))
|
|
|
+ sb.WriteString(" autonomous 1;\n")
|
|
|
+ sb.WriteString(" onlink 1;\n")
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if len(iface.RDNSS) > 0 {
|
|
|
+ sb.WriteString(" rdnss {\n")
|
|
|
+ for _, ns := range iface.RDNSS {
|
|
|
+ ns = strings.TrimSpace(ns)
|
|
|
+ if ns != "" {
|
|
|
+ sb.WriteString(fmt.Sprintf(" ns %s;\n", ns))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+ sb.WriteString(" };\n")
|
|
|
+ }
|
|
|
+ sb.WriteString("}\n\n")
|
|
|
+ }
|
|
|
+
|
|
|
return sb.String()
|
|
|
}
|
|
|
|
|
|
@@ -2417,46 +2769,1000 @@ func (m *Manager) SetASListState(id string, disabled bool) error {
|
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
|
-// Summary Telemetry
|
|
|
+// RIP Methods (/routing rip)
|
|
|
// -------------------------------------------------------------
|
|
|
|
|
|
-func (m *Manager) GetSummary() DynamicRoutingSummary {
|
|
|
+func (m *Manager) ListRIPInstances() []RIPInstance {
|
|
|
m.mu.RLock()
|
|
|
defer m.mu.RUnlock()
|
|
|
|
|
|
- summary := DynamicRoutingSummary{
|
|
|
- BGPEnabled: m.config.BGPInstance.Enabled,
|
|
|
- OSPFEnabled: m.config.OSPFInstance.Enabled,
|
|
|
- BGPPeerCount: len(m.config.BGPPeers),
|
|
|
- RoutingFilterCount: len(m.config.RoutingFilters),
|
|
|
- }
|
|
|
-
|
|
|
out, err := birdcExec("show status")
|
|
|
birdRunning := err == nil && strings.Contains(string(out), "Daemon is up and running")
|
|
|
|
|
|
- if birdRunning && summary.BGPEnabled {
|
|
|
- summary.BGPRunning = true
|
|
|
- peers := m.ListBGPPeers()
|
|
|
- est := 0
|
|
|
- for _, p := range peers {
|
|
|
- if strings.EqualFold(p.State, "Established") || strings.EqualFold(p.State, "Up") {
|
|
|
- est++
|
|
|
- }
|
|
|
+ res := make([]RIPInstance, len(m.config.RIPInstances))
|
|
|
+ copy(res, m.config.RIPInstances)
|
|
|
+ for i := range res {
|
|
|
+ res[i].Running = birdRunning && res[i].Enabled && !res[i].Disabled
|
|
|
+ }
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveRIPInstance(inst RIPInstance) (*RIPInstance, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if inst.ID == "" {
|
|
|
+ inst.ID = fmt.Sprintf("rip-inst-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if inst.Name == "" {
|
|
|
+ inst.Name = "default"
|
|
|
+ }
|
|
|
+ if inst.Version == 0 {
|
|
|
+ inst.Version = 2
|
|
|
+ }
|
|
|
+ if inst.UpdateTime == 0 {
|
|
|
+ inst.UpdateTime = 30
|
|
|
+ }
|
|
|
+ if inst.TimeoutTime == 0 {
|
|
|
+ inst.TimeoutTime = 180
|
|
|
+ }
|
|
|
+ if inst.GarbageTime == 0 {
|
|
|
+ inst.GarbageTime = 120
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.RIPInstances {
|
|
|
+ if existing.ID == inst.ID || strings.EqualFold(existing.Name, inst.Name) {
|
|
|
+ m.config.RIPInstances[i] = inst
|
|
|
+ found = true
|
|
|
+ break
|
|
|
}
|
|
|
- summary.BGPEstablishedCount = est
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.RIPInstances = append(m.config.RIPInstances, inst)
|
|
|
}
|
|
|
|
|
|
- if birdRunning && summary.OSPFEnabled {
|
|
|
- summary.OSPFRunning = true
|
|
|
- nbrs := m.GetOSPFNeighbors()
|
|
|
- summary.OSPFNeighborCount = len(nbrs)
|
|
|
- adj := 0
|
|
|
- for _, n := range nbrs {
|
|
|
- if strings.Contains(strings.ToLower(n.State), "full") {
|
|
|
- adj++
|
|
|
- }
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &inst, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteRIPInstance(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newInsts := make([]RIPInstance, 0, len(m.config.RIPInstances))
|
|
|
+ for _, inst := range m.config.RIPInstances {
|
|
|
+ if inst.ID != id && inst.Name != id {
|
|
|
+ newInsts = append(newInsts, inst)
|
|
|
}
|
|
|
- summary.OSPFAdjacentCount = adj
|
|
|
+ }
|
|
|
+ m.config.RIPInstances = newInsts
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetRIPInstanceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.RIPInstances {
|
|
|
+ if m.config.RIPInstances[i].ID == id || m.config.RIPInstances[i].Name == id {
|
|
|
+ m.config.RIPInstances[i].Disabled = disabled
|
|
|
+ m.config.RIPInstances[i].Enabled = !disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListRIPInterfaces() []RIPInterface {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]RIPInterface, len(m.config.RIPInterfaces))
|
|
|
+ copy(res, m.config.RIPInterfaces)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveRIPInterface(iface RIPInterface) (*RIPInterface, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if iface.ID == "" {
|
|
|
+ iface.ID = fmt.Sprintf("rip-iface-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if iface.Interface == "" {
|
|
|
+ iface.Interface = "*"
|
|
|
+ }
|
|
|
+ if iface.Metric == 0 {
|
|
|
+ iface.Metric = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.RIPInterfaces {
|
|
|
+ if existing.ID == iface.ID {
|
|
|
+ m.config.RIPInterfaces[i] = iface
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.RIPInterfaces = append(m.config.RIPInterfaces, iface)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &iface, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteRIPInterface(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newIfaces := make([]RIPInterface, 0, len(m.config.RIPInterfaces))
|
|
|
+ for _, iface := range m.config.RIPInterfaces {
|
|
|
+ if iface.ID != id && iface.Interface != id {
|
|
|
+ newIfaces = append(newIfaces, iface)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.RIPInterfaces = newIfaces
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetRIPInterfaceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.RIPInterfaces {
|
|
|
+ if m.config.RIPInterfaces[i].ID == id || m.config.RIPInterfaces[i].Interface == id {
|
|
|
+ m.config.RIPInterfaces[i].Disabled = disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListRIPNeighbors() []RIPNeighbor {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]RIPNeighbor, len(m.config.RIPNeighbors))
|
|
|
+ copy(res, m.config.RIPNeighbors)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveRIPNeighbor(nbr RIPNeighbor) (*RIPNeighbor, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if nbr.ID == "" {
|
|
|
+ nbr.ID = fmt.Sprintf("rip-nbr-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.RIPNeighbors {
|
|
|
+ if existing.ID == nbr.ID || existing.Address == nbr.Address {
|
|
|
+ m.config.RIPNeighbors[i] = nbr
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.RIPNeighbors = append(m.config.RIPNeighbors, nbr)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &nbr, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteRIPNeighbor(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newNbrs := make([]RIPNeighbor, 0, len(m.config.RIPNeighbors))
|
|
|
+ for _, nbr := range m.config.RIPNeighbors {
|
|
|
+ if nbr.ID != id && nbr.Address != id {
|
|
|
+ newNbrs = append(newNbrs, nbr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.RIPNeighbors = newNbrs
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) GetRIPNeighbors() []RIPNeighborLive {
|
|
|
+ var list []RIPNeighborLive
|
|
|
+ out, err := birdcExec("show rip neighbors")
|
|
|
+ if err != nil {
|
|
|
+ return list
|
|
|
+ }
|
|
|
+ lines := strings.Split(string(out), "\n")
|
|
|
+ for _, line := range lines {
|
|
|
+ trimmed := strings.TrimSpace(line)
|
|
|
+ if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "IP address") || strings.Contains(trimmed, "There is no RIP") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ fields := strings.Fields(trimmed)
|
|
|
+ if len(fields) >= 2 {
|
|
|
+ nbr := RIPNeighborLive{
|
|
|
+ Address: fields[0],
|
|
|
+ Interface: fields[1],
|
|
|
+ }
|
|
|
+ if len(fields) >= 3 {
|
|
|
+ nbr.LastSeen = fields[len(fields)-1]
|
|
|
+ }
|
|
|
+ list = append(list, nbr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) GetRIPRoutes() []RIPRoute {
|
|
|
+ var routes []RIPRoute
|
|
|
+ out, err := birdcExec("show route where source = RTS_RIP all")
|
|
|
+ if err != nil {
|
|
|
+ return routes
|
|
|
+ }
|
|
|
+ lines := strings.Split(string(out), "\n")
|
|
|
+ var cur *RIPRoute
|
|
|
+ for _, line := range lines {
|
|
|
+ trimmed := strings.TrimSpace(line)
|
|
|
+ if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "Table") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if !strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, " ") {
|
|
|
+ fields := strings.Fields(trimmed)
|
|
|
+ if len(fields) >= 2 {
|
|
|
+ routes = append(routes, RIPRoute{
|
|
|
+ Prefix: fields[0],
|
|
|
+ })
|
|
|
+ cur = &routes[len(routes)-1]
|
|
|
+ for i := 1; i < len(fields); i++ {
|
|
|
+ if fields[i] == "via" && i+1 < len(fields) {
|
|
|
+ cur.NextHop = fields[i+1]
|
|
|
+ }
|
|
|
+ if fields[i] == "dev" && i+1 < len(fields) {
|
|
|
+ cur.Interface = fields[i+1]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if cur != nil {
|
|
|
+ if strings.Contains(trimmed, "rip_metric:") {
|
|
|
+ parts := strings.Split(trimmed, ":")
|
|
|
+ if len(parts) >= 2 {
|
|
|
+ var mVal int
|
|
|
+ fmt.Sscanf(strings.TrimSpace(parts[1]), "%d", &mVal)
|
|
|
+ cur.Metric = mVal
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return routes
|
|
|
+}
|
|
|
+
|
|
|
+// -------------------------------------------------------------
|
|
|
+// BFD Methods (/routing bfd)
|
|
|
+// -------------------------------------------------------------
|
|
|
+
|
|
|
+func (m *Manager) GetBFDInstance() BFDInstance {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+
|
|
|
+ inst := m.config.BFDInstance
|
|
|
+ out, err := birdcExec("show status")
|
|
|
+ inst.Running = err == nil && strings.Contains(string(out), "Daemon is up and running") && inst.Enabled && !inst.Disabled
|
|
|
+ return inst
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveBFDInstance(inst BFDInstance) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ m.config.BFDInstance = inst
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListBFDInterfaces() []BFDInterface {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]BFDInterface, len(m.config.BFDInterfaces))
|
|
|
+ copy(res, m.config.BFDInterfaces)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveBFDInterface(iface BFDInterface) (*BFDInterface, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if iface.ID == "" {
|
|
|
+ iface.ID = fmt.Sprintf("bfd-iface-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if iface.Interface == "" {
|
|
|
+ iface.Interface = "*"
|
|
|
+ }
|
|
|
+ if iface.MinRxInterval == 0 {
|
|
|
+ iface.MinRxInterval = 100
|
|
|
+ }
|
|
|
+ if iface.MinTxInterval == 0 {
|
|
|
+ iface.MinTxInterval = 100
|
|
|
+ }
|
|
|
+ if iface.Multiplier == 0 {
|
|
|
+ iface.Multiplier = 3
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.BFDInterfaces {
|
|
|
+ if existing.ID == iface.ID {
|
|
|
+ m.config.BFDInterfaces[i] = iface
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.BFDInterfaces = append(m.config.BFDInterfaces, iface)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &iface, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteBFDInterface(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newIfaces := make([]BFDInterface, 0, len(m.config.BFDInterfaces))
|
|
|
+ for _, iface := range m.config.BFDInterfaces {
|
|
|
+ if iface.ID != id && iface.Interface != id {
|
|
|
+ newIfaces = append(newIfaces, iface)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.BFDInterfaces = newIfaces
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetBFDInterfaceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.BFDInterfaces {
|
|
|
+ if m.config.BFDInterfaces[i].ID == id || m.config.BFDInterfaces[i].Interface == id {
|
|
|
+ m.config.BFDInterfaces[i].Disabled = disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListBFDNeighbors() []BFDNeighbor {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]BFDNeighbor, len(m.config.BFDNeighbors))
|
|
|
+ copy(res, m.config.BFDNeighbors)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveBFDNeighbor(nbr BFDNeighbor) (*BFDNeighbor, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if nbr.ID == "" {
|
|
|
+ nbr.ID = fmt.Sprintf("bfd-nbr-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.BFDNeighbors {
|
|
|
+ if existing.ID == nbr.ID || existing.Address == nbr.Address {
|
|
|
+ m.config.BFDNeighbors[i] = nbr
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.BFDNeighbors = append(m.config.BFDNeighbors, nbr)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &nbr, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteBFDNeighbor(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newNbrs := make([]BFDNeighbor, 0, len(m.config.BFDNeighbors))
|
|
|
+ for _, nbr := range m.config.BFDNeighbors {
|
|
|
+ if nbr.ID != id && nbr.Address != id {
|
|
|
+ newNbrs = append(newNbrs, nbr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.BFDNeighbors = newNbrs
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) GetBFDSessions() []BFDSession {
|
|
|
+ var list []BFDSession
|
|
|
+ out, err := birdcExec("show bfd sessions")
|
|
|
+ if err != nil {
|
|
|
+ return list
|
|
|
+ }
|
|
|
+ lines := strings.Split(string(out), "\n")
|
|
|
+ for _, line := range lines {
|
|
|
+ trimmed := strings.TrimSpace(line)
|
|
|
+ if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "IP address") || strings.Contains(trimmed, "There is no BFD") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ fields := strings.Fields(trimmed)
|
|
|
+ if len(fields) >= 3 {
|
|
|
+ sess := BFDSession{
|
|
|
+ IP: fields[0],
|
|
|
+ Interface: fields[1],
|
|
|
+ State: fields[2],
|
|
|
+ }
|
|
|
+ if len(fields) >= 4 {
|
|
|
+ sess.Since = fields[3]
|
|
|
+ }
|
|
|
+ if len(fields) >= 5 {
|
|
|
+ sess.Interval = fields[4]
|
|
|
+ }
|
|
|
+ if len(fields) >= 6 {
|
|
|
+ sess.Timeout = fields[5]
|
|
|
+ }
|
|
|
+ list = append(list, sess)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list
|
|
|
+}
|
|
|
+
|
|
|
+// -------------------------------------------------------------
|
|
|
+// Babel Methods (/routing babel)
|
|
|
+// -------------------------------------------------------------
|
|
|
+
|
|
|
+func (m *Manager) ListBabelInstances() []BabelInstance {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+
|
|
|
+ out, err := birdcExec("show status")
|
|
|
+ birdRunning := err == nil && strings.Contains(string(out), "Daemon is up and running")
|
|
|
+
|
|
|
+ res := make([]BabelInstance, len(m.config.BabelInstances))
|
|
|
+ copy(res, m.config.BabelInstances)
|
|
|
+ for i := range res {
|
|
|
+ res[i].Running = birdRunning && res[i].Enabled && !res[i].Disabled
|
|
|
+ }
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveBabelInstance(inst BabelInstance) (*BabelInstance, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if inst.ID == "" {
|
|
|
+ inst.ID = fmt.Sprintf("babel-inst-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if inst.Name == "" {
|
|
|
+ inst.Name = "default"
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.BabelInstances {
|
|
|
+ if existing.ID == inst.ID || strings.EqualFold(existing.Name, inst.Name) {
|
|
|
+ m.config.BabelInstances[i] = inst
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.BabelInstances = append(m.config.BabelInstances, inst)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &inst, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteBabelInstance(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newInsts := make([]BabelInstance, 0, len(m.config.BabelInstances))
|
|
|
+ for _, inst := range m.config.BabelInstances {
|
|
|
+ if inst.ID != id && inst.Name != id {
|
|
|
+ newInsts = append(newInsts, inst)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.BabelInstances = newInsts
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetBabelInstanceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.BabelInstances {
|
|
|
+ if m.config.BabelInstances[i].ID == id || m.config.BabelInstances[i].Name == id {
|
|
|
+ m.config.BabelInstances[i].Disabled = disabled
|
|
|
+ m.config.BabelInstances[i].Enabled = !disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListBabelInterfaces() []BabelInterface {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]BabelInterface, len(m.config.BabelInterfaces))
|
|
|
+ copy(res, m.config.BabelInterfaces)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveBabelInterface(iface BabelInterface) (*BabelInterface, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if iface.ID == "" {
|
|
|
+ iface.ID = fmt.Sprintf("babel-iface-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if iface.Interface == "" {
|
|
|
+ iface.Interface = "*"
|
|
|
+ }
|
|
|
+ if iface.Type == "" {
|
|
|
+ iface.Type = "wired"
|
|
|
+ }
|
|
|
+ if iface.RxCost == 0 {
|
|
|
+ iface.RxCost = 10
|
|
|
+ }
|
|
|
+ if iface.HelloInterval == 0 {
|
|
|
+ iface.HelloInterval = 4
|
|
|
+ }
|
|
|
+ if iface.UpdateInterval == 0 {
|
|
|
+ iface.UpdateInterval = 16
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.BabelInterfaces {
|
|
|
+ if existing.ID == iface.ID {
|
|
|
+ m.config.BabelInterfaces[i] = iface
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.BabelInterfaces = append(m.config.BabelInterfaces, iface)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &iface, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteBabelInterface(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newIfaces := make([]BabelInterface, 0, len(m.config.BabelInterfaces))
|
|
|
+ for _, iface := range m.config.BabelInterfaces {
|
|
|
+ if iface.ID != id && iface.Interface != id {
|
|
|
+ newIfaces = append(newIfaces, iface)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.BabelInterfaces = newIfaces
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetBabelInterfaceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.BabelInterfaces {
|
|
|
+ if m.config.BabelInterfaces[i].ID == id || m.config.BabelInterfaces[i].Interface == id {
|
|
|
+ m.config.BabelInterfaces[i].Disabled = disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) GetBabelNeighbors() []BabelNeighbor {
|
|
|
+ var list []BabelNeighbor
|
|
|
+ out, err := birdcExec("show babel neighbors")
|
|
|
+ if err != nil {
|
|
|
+ return list
|
|
|
+ }
|
|
|
+ lines := strings.Split(string(out), "\n")
|
|
|
+ for _, line := range lines {
|
|
|
+ trimmed := strings.TrimSpace(line)
|
|
|
+ if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "IP address") || strings.Contains(trimmed, "There is no Babel") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ fields := strings.Fields(trimmed)
|
|
|
+ if len(fields) >= 2 {
|
|
|
+ nbr := BabelNeighbor{
|
|
|
+ IP: fields[0],
|
|
|
+ Interface: fields[1],
|
|
|
+ }
|
|
|
+ if len(fields) >= 3 {
|
|
|
+ fmt.Sscanf(fields[2], "%d", &nbr.Metric)
|
|
|
+ }
|
|
|
+ if len(fields) >= 4 {
|
|
|
+ fmt.Sscanf(fields[3], "%d", &nbr.Cost)
|
|
|
+ }
|
|
|
+ if len(fields) >= 5 {
|
|
|
+ nbr.Uptime = fields[4]
|
|
|
+ }
|
|
|
+ list = append(list, nbr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return list
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) GetBabelRoutes() []BabelRoute {
|
|
|
+ var routes []BabelRoute
|
|
|
+ out, err := birdcExec("show route where source = RTS_BABEL all")
|
|
|
+ if err != nil {
|
|
|
+ return routes
|
|
|
+ }
|
|
|
+ lines := strings.Split(string(out), "\n")
|
|
|
+ for _, line := range lines {
|
|
|
+ trimmed := strings.TrimSpace(line)
|
|
|
+ if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "Table") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if !strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, " ") {
|
|
|
+ fields := strings.Fields(trimmed)
|
|
|
+ if len(fields) >= 2 {
|
|
|
+ rt := BabelRoute{
|
|
|
+ Prefix: fields[0],
|
|
|
+ }
|
|
|
+ for i := 1; i < len(fields); i++ {
|
|
|
+ if fields[i] == "via" && i+1 < len(fields) {
|
|
|
+ rt.NextHop = fields[i+1]
|
|
|
+ }
|
|
|
+ if fields[i] == "dev" && i+1 < len(fields) {
|
|
|
+ rt.Interface = fields[i+1]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ routes = append(routes, rt)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return routes
|
|
|
+}
|
|
|
+
|
|
|
+// -------------------------------------------------------------
|
|
|
+// RAdv Methods (/routing radv)
|
|
|
+// -------------------------------------------------------------
|
|
|
+
|
|
|
+func (m *Manager) ListRAdvInstances() []RAdvInstance {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+
|
|
|
+ out, err := birdcExec("show status")
|
|
|
+ birdRunning := err == nil && strings.Contains(string(out), "Daemon is up and running")
|
|
|
+
|
|
|
+ res := make([]RAdvInstance, len(m.config.RAdvInstances))
|
|
|
+ copy(res, m.config.RAdvInstances)
|
|
|
+ for i := range res {
|
|
|
+ res[i].Running = birdRunning && res[i].Enabled && !res[i].Disabled
|
|
|
+ }
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveRAdvInstance(inst RAdvInstance) (*RAdvInstance, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if inst.ID == "" {
|
|
|
+ inst.ID = fmt.Sprintf("radv-inst-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if inst.Name == "" {
|
|
|
+ inst.Name = "default"
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.RAdvInstances {
|
|
|
+ if existing.ID == inst.ID || strings.EqualFold(existing.Name, inst.Name) {
|
|
|
+ m.config.RAdvInstances[i] = inst
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.RAdvInstances = append(m.config.RAdvInstances, inst)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &inst, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteRAdvInstance(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newInsts := make([]RAdvInstance, 0, len(m.config.RAdvInstances))
|
|
|
+ for _, inst := range m.config.RAdvInstances {
|
|
|
+ if inst.ID != id && inst.Name != id {
|
|
|
+ newInsts = append(newInsts, inst)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.RAdvInstances = newInsts
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetRAdvInstanceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.RAdvInstances {
|
|
|
+ if m.config.RAdvInstances[i].ID == id || m.config.RAdvInstances[i].Name == id {
|
|
|
+ m.config.RAdvInstances[i].Disabled = disabled
|
|
|
+ m.config.RAdvInstances[i].Enabled = !disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) ListRAdvInterfaces() []RAdvInterface {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+ res := make([]RAdvInterface, len(m.config.RAdvInterfaces))
|
|
|
+ copy(res, m.config.RAdvInterfaces)
|
|
|
+ return res
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SaveRAdvInterface(iface RAdvInterface) (*RAdvInterface, error) {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ if iface.ID == "" {
|
|
|
+ iface.ID = fmt.Sprintf("radv-iface-%d", time.Now().UnixNano())
|
|
|
+ }
|
|
|
+ if iface.Interface == "" {
|
|
|
+ iface.Interface = "*"
|
|
|
+ }
|
|
|
+ if iface.MinRAInterval == 0 {
|
|
|
+ iface.MinRAInterval = 200
|
|
|
+ }
|
|
|
+ if iface.MaxRAInterval == 0 {
|
|
|
+ iface.MaxRAInterval = 600
|
|
|
+ }
|
|
|
+ if iface.DefaultLifetime == 0 {
|
|
|
+ iface.DefaultLifetime = 1800
|
|
|
+ }
|
|
|
+
|
|
|
+ found := false
|
|
|
+ for i, existing := range m.config.RAdvInterfaces {
|
|
|
+ if existing.ID == iface.ID {
|
|
|
+ m.config.RAdvInterfaces[i] = iface
|
|
|
+ found = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !found {
|
|
|
+ m.config.RAdvInterfaces = append(m.config.RAdvInterfaces, iface)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := m.applyBirdConfig(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &iface, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) DeleteRAdvInterface(id string) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ newIfaces := make([]RAdvInterface, 0, len(m.config.RAdvInterfaces))
|
|
|
+ for _, iface := range m.config.RAdvInterfaces {
|
|
|
+ if iface.ID != id && iface.Interface != id {
|
|
|
+ newIfaces = append(newIfaces, iface)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m.config.RAdvInterfaces = newIfaces
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *Manager) SetRAdvInterfaceState(id string, disabled bool) error {
|
|
|
+ m.mu.Lock()
|
|
|
+ defer m.mu.Unlock()
|
|
|
+
|
|
|
+ for i := range m.config.RAdvInterfaces {
|
|
|
+ if m.config.RAdvInterfaces[i].ID == id || m.config.RAdvInterfaces[i].Interface == id {
|
|
|
+ m.config.RAdvInterfaces[i].Disabled = disabled
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := m.save(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return m.applyBirdConfig()
|
|
|
+}
|
|
|
+
|
|
|
+// -------------------------------------------------------------
|
|
|
+// Summary Telemetry
|
|
|
+// -------------------------------------------------------------
|
|
|
+
|
|
|
+func (m *Manager) GetSummary() DynamicRoutingSummary {
|
|
|
+ m.mu.RLock()
|
|
|
+ defer m.mu.RUnlock()
|
|
|
+
|
|
|
+ summary := DynamicRoutingSummary{
|
|
|
+ BGPEnabled: m.config.BGPInstance.Enabled,
|
|
|
+ OSPFEnabled: m.config.OSPFInstance.Enabled,
|
|
|
+ BGPPeerCount: len(m.config.BGPPeers),
|
|
|
+ RoutingFilterCount: len(m.config.RoutingFilters),
|
|
|
+ }
|
|
|
+
|
|
|
+ out, err := birdcExec("show status")
|
|
|
+ birdRunning := err == nil && strings.Contains(string(out), "Daemon is up and running")
|
|
|
+
|
|
|
+ if birdRunning && summary.BGPEnabled {
|
|
|
+ summary.BGPRunning = true
|
|
|
+ peers := m.ListBGPPeers()
|
|
|
+ est := 0
|
|
|
+ for _, p := range peers {
|
|
|
+ if strings.EqualFold(p.State, "Established") || strings.EqualFold(p.State, "Up") {
|
|
|
+ est++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ summary.BGPEstablishedCount = est
|
|
|
+ }
|
|
|
+
|
|
|
+ if birdRunning && summary.OSPFEnabled {
|
|
|
+ summary.OSPFRunning = true
|
|
|
+ nbrs := m.GetOSPFNeighbors()
|
|
|
+ summary.OSPFNeighborCount = len(nbrs)
|
|
|
+ adj := 0
|
|
|
+ for _, n := range nbrs {
|
|
|
+ if strings.Contains(strings.ToLower(n.State), "full") {
|
|
|
+ adj++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ summary.OSPFAdjacentCount = adj
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, inst := range m.config.RIPInstances {
|
|
|
+ if inst.Enabled && !inst.Disabled {
|
|
|
+ summary.RIPEnabled = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if summary.RIPEnabled && birdRunning {
|
|
|
+ summary.RIPRunning = true
|
|
|
+ nbrs := m.GetRIPNeighbors()
|
|
|
+ summary.RIPNeighborCount = len(nbrs)
|
|
|
+ }
|
|
|
+
|
|
|
+ if m.config.BFDInstance.Enabled && !m.config.BFDInstance.Disabled {
|
|
|
+ summary.BFDEnabled = true
|
|
|
+ }
|
|
|
+ if summary.BFDEnabled && birdRunning {
|
|
|
+ summary.BFDRunning = true
|
|
|
+ sessions := m.GetBFDSessions()
|
|
|
+ summary.BFDSessionCount = len(sessions)
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, inst := range m.config.BabelInstances {
|
|
|
+ if inst.Enabled && !inst.Disabled {
|
|
|
+ summary.BabelEnabled = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if summary.BabelEnabled && birdRunning {
|
|
|
+ summary.BabelRunning = true
|
|
|
+ nbrs := m.GetBabelNeighbors()
|
|
|
+ summary.BabelNeighborCount = len(nbrs)
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, inst := range m.config.RAdvInstances {
|
|
|
+ if inst.Enabled && !inst.Disabled {
|
|
|
+ summary.RAdvEnabled = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if summary.RAdvEnabled && birdRunning {
|
|
|
+ summary.RAdvRunning = true
|
|
|
}
|
|
|
|
|
|
return summary
|