manager.go 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413
  1. package dynamicrouting
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "net"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "regexp"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. const (
  18. defaultConfigPath = "/etc/debtik/routing/dynamic.json"
  19. birdConfigPath = "/etc/bird/bird.conf"
  20. )
  21. type Manager struct {
  22. mu sync.RWMutex
  23. configPath string
  24. config DynamicConfig
  25. }
  26. func NewManager(configPath string) *Manager {
  27. if configPath == "" {
  28. configPath = defaultConfigPath
  29. }
  30. m := &Manager{
  31. configPath: configPath,
  32. }
  33. if err := m.load(); err != nil {
  34. log.Printf("[DynamicRouting] Config load warning: %v, initializing defaults", err)
  35. m.initDefaults()
  36. _ = m.save()
  37. }
  38. // Apply initial configuration to BIRD 2
  39. if err := m.applyBirdConfig(); err != nil {
  40. log.Printf("[DynamicRouting] Initial BIRD apply warning: %v", err)
  41. }
  42. return m
  43. }
  44. func (m *Manager) initDefaults() {
  45. m.config = DynamicConfig{
  46. BGPInstance: BGPInstance{
  47. Enabled: false,
  48. AS: 65001,
  49. RouterID: "192.168.131.56",
  50. HoldTime: 180,
  51. KeepaliveTime: 60,
  52. RedistributeConnected: false,
  53. RedistributeStatic: false,
  54. RedistributeOSPF: false,
  55. },
  56. BGPPeers: []BGPPeer{},
  57. BGPNetworks: []BGPNetwork{},
  58. OSPFInstances: []OSPFInstance{
  59. {
  60. ID: "ospf-inst-default",
  61. Name: "Default",
  62. Version: 2,
  63. VRF: "main",
  64. RouterID: "192.168.131.56",
  65. RoutingTable: "main",
  66. Enabled: true,
  67. },
  68. },
  69. OSPFInstance: OSPFInstance{
  70. Name: "Default",
  71. Version: 2,
  72. VRF: "main",
  73. Enabled: true,
  74. RouterID: "192.168.131.56",
  75. RedistributeConnected: false,
  76. RedistributeStatic: true,
  77. RedistributeBGP: false,
  78. DefaultMetric: 10,
  79. },
  80. OSPFAreas: []OSPFArea{
  81. {
  82. ID: "area-default",
  83. Name: "Default",
  84. Instance: "Default",
  85. AreaID: "0.0.0.0",
  86. AreaType: "default",
  87. },
  88. },
  89. OSPFAreaRanges: []OSPFAreaRange{},
  90. OSPFInterfaces: []OSPFInterface{},
  91. OSPFStaticNbrs: []OSPFStaticNeighbor{},
  92. RoutingFilters: []RoutingFilterRule{},
  93. RoutingCommunityLists: []RoutingFilterCommunityList{},
  94. RoutingASLists: []RoutingFilterASList{},
  95. }
  96. }
  97. func (m *Manager) load() error {
  98. m.mu.Lock()
  99. defer m.mu.Unlock()
  100. data, err := os.ReadFile(m.configPath)
  101. if err != nil {
  102. return err
  103. }
  104. if err := json.Unmarshal(data, &m.config); err != nil {
  105. return err
  106. }
  107. if m.config.RoutingCommunityLists == nil {
  108. m.config.RoutingCommunityLists = []RoutingFilterCommunityList{}
  109. }
  110. if m.config.RoutingASLists == nil {
  111. m.config.RoutingASLists = []RoutingFilterASList{}
  112. }
  113. if len(m.config.OSPFInstances) == 0 {
  114. rID := m.config.OSPFInstance.RouterID
  115. if rID == "" {
  116. rID = "192.168.131.56"
  117. }
  118. m.config.OSPFInstances = []OSPFInstance{
  119. {
  120. ID: "ospf-inst-default",
  121. Name: "Default",
  122. Version: 2,
  123. VRF: "main",
  124. RouterID: rID,
  125. RoutingTable: "main",
  126. Enabled: true,
  127. },
  128. }
  129. }
  130. if len(m.config.OSPFAreas) == 0 {
  131. m.config.OSPFAreas = []OSPFArea{
  132. {
  133. ID: "area-default",
  134. Name: "Default",
  135. Instance: "Default",
  136. AreaID: "0.0.0.0",
  137. AreaType: "default",
  138. },
  139. }
  140. }
  141. return nil
  142. }
  143. func (m *Manager) save() error {
  144. dir := filepath.Dir(m.configPath)
  145. if err := os.MkdirAll(dir, 0755); err != nil {
  146. return err
  147. }
  148. data, err := json.MarshalIndent(m.config, "", " ")
  149. if err != nil {
  150. return err
  151. }
  152. return os.WriteFile(m.configPath, data, 0644)
  153. }
  154. // sanitizeIdentifier creates a valid BIRD protocol/function identifier: [a-zA-Z][a-zA-Z0-9_]*
  155. func sanitizeIdentifier(prefix, raw string) string {
  156. reg := regexp.MustCompile("[^a-zA-Z0-9_]+")
  157. cleaned := reg.ReplaceAllString(raw, "_")
  158. cleaned = strings.Trim(cleaned, "_")
  159. if cleaned == "" {
  160. cleaned = "inst"
  161. }
  162. res := cleaned
  163. if prefix != "" {
  164. res = prefix + "_" + cleaned
  165. }
  166. if len(res) > 32 {
  167. res = res[:32]
  168. }
  169. return res
  170. }
  171. // formatInterfaceTargets formats interface names and network prefixes for BIRD 2 interface pattern
  172. func formatInterfaceTargets(ifaceNames, netPrefixes string) string {
  173. var targets []string
  174. if ifaceNames != "" {
  175. for _, raw := range strings.Split(ifaceNames, ",") {
  176. item := strings.TrimSpace(raw)
  177. if item != "" {
  178. if item == "*" {
  179. targets = append(targets, "\"*\"")
  180. } else if strings.HasPrefix(item, "\"") && strings.HasSuffix(item, "\"") {
  181. targets = append(targets, item)
  182. } else {
  183. targets = append(targets, fmt.Sprintf("%q", item))
  184. }
  185. }
  186. }
  187. }
  188. if netPrefixes != "" {
  189. for _, raw := range strings.Split(netPrefixes, ",") {
  190. item := strings.TrimSpace(raw)
  191. if item != "" {
  192. targets = append(targets, item)
  193. }
  194. }
  195. }
  196. if len(targets) == 0 {
  197. return "\"*\""
  198. }
  199. return strings.Join(targets, ", ")
  200. }
  201. // ipInSubnets returns true if ipStr is contained in any comma-separated subnet in networksStr
  202. func ipInSubnets(ipStr, networksStr string) bool {
  203. ip := net.ParseIP(strings.TrimSpace(ipStr))
  204. if ip == nil {
  205. return false
  206. }
  207. for _, raw := range strings.Split(networksStr, ",") {
  208. cidr := strings.TrimSpace(raw)
  209. if cidr == "" {
  210. continue
  211. }
  212. _, ipNet, err := net.ParseCIDR(cidr)
  213. if err == nil && ipNet.Contains(ip) {
  214. return true
  215. }
  216. }
  217. return false
  218. }
  219. // generateBirdConfig creates candidate bird.conf text from current configuration
  220. func (m *Manager) generateBirdConfig() string {
  221. var sb strings.Builder
  222. routerID := "192.168.131.56"
  223. if m.config.BGPInstance.RouterID != "" {
  224. routerID = m.config.BGPInstance.RouterID
  225. } else if m.config.OSPFInstance.RouterID != "" {
  226. routerID = m.config.OSPFInstance.RouterID
  227. }
  228. sb.WriteString("# /etc/bird/bird.conf\n")
  229. sb.WriteString("# Generated dynamically by DebTik (BIRD 2 Engine)\n\n")
  230. sb.WriteString("timeformat protocol iso long;\n")
  231. sb.WriteString("timeformat base iso long;\n")
  232. sb.WriteString("timeformat log iso long;\n")
  233. sb.WriteString("log syslog all;\n")
  234. sb.WriteString(fmt.Sprintf("router id %s;\n\n", routerID))
  235. // Base system protocols
  236. sb.WriteString("protocol device {\n")
  237. sb.WriteString(" scan time 10;\n")
  238. sb.WriteString("}\n\n")
  239. sb.WriteString("protocol direct {\n")
  240. sb.WriteString(" ipv4;\n")
  241. sb.WriteString("}\n\n")
  242. sb.WriteString("protocol kernel {\n")
  243. sb.WriteString(" metric 0;\n")
  244. sb.WriteString(" ipv4 {\n")
  245. sb.WriteString(" export filter {\n")
  246. sb.WriteString(" if source = RTS_DEVICE then reject;\n")
  247. sb.WriteString(" if source = RTS_BGP then {\n")
  248. sb.WriteString(" if bgp_path.first = bgp_path.last && bgp_path.len = 0 then\n")
  249. sb.WriteString(" krt_metric = 200;\n")
  250. sb.WriteString(" else\n")
  251. sb.WriteString(" krt_metric = 20;\n")
  252. sb.WriteString(" }\n")
  253. sb.WriteString(" if source = RTS_OSPF then {\n")
  254. sb.WriteString(" krt_metric = 110;\n")
  255. sb.WriteString(" }\n")
  256. sb.WriteString(" if source = RTS_RIP then {\n")
  257. sb.WriteString(" krt_metric = 120;\n")
  258. sb.WriteString(" }\n")
  259. sb.WriteString(" if source ~ [ RTS_STATIC, RTS_INHERIT ] then {\n")
  260. sb.WriteString(" krt_metric = 255;\n")
  261. sb.WriteString(" }\n")
  262. sb.WriteString(" accept;\n")
  263. sb.WriteString(" };\n")
  264. sb.WriteString(" };\n")
  265. sb.WriteString(" learn;\n")
  266. sb.WriteString("}\n\n")
  267. // Static BGP Networks announcements
  268. var activeNets []string
  269. if m.config.BGPInstance.Enabled {
  270. for _, net := range m.config.BGPNetworks {
  271. if !net.Disabled && strings.TrimSpace(net.Network) != "" {
  272. activeNets = append(activeNets, strings.TrimSpace(net.Network))
  273. }
  274. }
  275. }
  276. if len(activeNets) > 0 {
  277. sb.WriteString("protocol static bgp_static_nets {\n")
  278. sb.WriteString(" ipv4;\n")
  279. for _, n := range activeNets {
  280. sb.WriteString(fmt.Sprintf(" route %s blackhole;\n", n))
  281. }
  282. sb.WriteString("}\n\n")
  283. }
  284. // Community Lists (/routing filter community-list)
  285. for _, cl := range m.config.RoutingCommunityLists {
  286. if cl.Disabled || strings.TrimSpace(cl.Name) == "" || len(cl.Communities) == 0 {
  287. continue
  288. }
  289. var items []string
  290. for _, c := range cl.Communities {
  291. c = strings.TrimSpace(c)
  292. if strings.Contains(c, ":") {
  293. parts := strings.Split(c, ":")
  294. items = append(items, fmt.Sprintf("(%s, %s)", strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])))
  295. }
  296. }
  297. if len(items) > 0 {
  298. sb.WriteString(fmt.Sprintf("define comm_%s = [ %s ];\n", sanitizeIdentifier("", cl.Name), strings.Join(items, ", ")))
  299. }
  300. }
  301. // AS Lists (/routing filter num-list)
  302. for _, al := range m.config.RoutingASLists {
  303. if al.Disabled || strings.TrimSpace(al.Name) == "" || strings.TrimSpace(al.Range) == "" {
  304. continue
  305. }
  306. rng := strings.TrimSpace(al.Range)
  307. rng = regexp.MustCompile(`(\d+)\s*-\s*(\d+)`).ReplaceAllString(rng, "$1..$2")
  308. sb.WriteString(fmt.Sprintf("define aslist_%s = [ %s ];\n", sanitizeIdentifier("", al.Name), rng))
  309. }
  310. sb.WriteString("\n")
  311. // Routing Filters (/routing filter)
  312. chainRules := make(map[string][]RoutingFilterRule)
  313. for _, rule := range m.config.RoutingFilters {
  314. if rule.Disabled || strings.TrimSpace(rule.Chain) == "" {
  315. continue
  316. }
  317. chain := strings.TrimSpace(rule.Chain)
  318. chainRules[chain] = append(chainRules[chain], rule)
  319. }
  320. for chain, rules := range chainRules {
  321. sort.Slice(rules, func(i, j int) bool {
  322. return rules[i].RuleOrder < rules[j].RuleOrder
  323. })
  324. filterName := sanitizeIdentifier("filter", chain)
  325. fnName := fmt.Sprintf("fn_%s", filterName)
  326. // Procedural function for this chain
  327. sb.WriteString(fmt.Sprintf("function %s() {\n", fnName))
  328. for _, r := range rules {
  329. rendered := translateRos7RuleToBird(&r)
  330. if rendered != "" {
  331. sb.WriteString(fmt.Sprintf(" # rule %d\n", r.RuleOrder))
  332. lines := strings.Split(rendered, "\n")
  333. for _, l := range lines {
  334. if strings.TrimSpace(l) != "" {
  335. sb.WriteString(" " + l + "\n")
  336. }
  337. }
  338. }
  339. }
  340. sb.WriteString("}\n\n")
  341. // Standalone filter for import and general use (unmatched routes rejected by default)
  342. sb.WriteString(fmt.Sprintf("filter %s {\n", filterName))
  343. sb.WriteString(fmt.Sprintf(" %s();\n", fnName))
  344. sb.WriteString(" reject;\n")
  345. sb.WriteString("}\n\n")
  346. }
  347. // BGP Protocols (True Multi-Instance / Multi-AS support!)
  348. if m.config.BGPInstance.Enabled {
  349. for i, peer := range m.config.BGPPeers {
  350. if peer.RemoteAddress == "" || peer.RemoteAS == 0 {
  351. continue
  352. }
  353. // Local AS determination: peer.LocalAS if set, otherwise global AS
  354. localAS := m.config.BGPInstance.AS
  355. if peer.LocalAS > 0 {
  356. localAS = peer.LocalAS
  357. }
  358. protoName := sanitizeIdentifier("bgp", fmt.Sprintf("%s_%d", peer.ID, i))
  359. // Per-peer Output Networks (RouterOS v7 output.network)
  360. var peerNets []string
  361. for _, on := range peer.OutputNetworks {
  362. on = strings.TrimSpace(on)
  363. if on != "" {
  364. peerNets = append(peerNets, on)
  365. }
  366. }
  367. if peer.DefaultOriginate {
  368. peerNets = append(peerNets, "0.0.0.0/0")
  369. }
  370. peerNetsProto := ""
  371. if len(peerNets) > 0 {
  372. peerNetsProto = fmt.Sprintf("bgp_nets_%s", protoName)
  373. sb.WriteString(fmt.Sprintf("protocol static %s {\n", peerNetsProto))
  374. sb.WriteString(" ipv4;\n")
  375. for _, pn := range peerNets {
  376. sb.WriteString(fmt.Sprintf(" route %s blackhole;\n", pn))
  377. }
  378. sb.WriteString("}\n\n")
  379. }
  380. sb.WriteString(fmt.Sprintf("protocol bgp %s {\n", protoName))
  381. if peer.Name != "" {
  382. sb.WriteString(fmt.Sprintf(" description \"%s\";\n", peer.Name))
  383. }
  384. if peer.Disabled {
  385. sb.WriteString(" disabled;\n")
  386. }
  387. if peer.LocalAddress != "" {
  388. if peer.LocalPort > 0 {
  389. sb.WriteString(fmt.Sprintf(" local %s port %d as %d;\n", peer.LocalAddress, peer.LocalPort, localAS))
  390. } else {
  391. sb.WriteString(fmt.Sprintf(" local %s as %d;\n", peer.LocalAddress, localAS))
  392. }
  393. } else {
  394. if peer.LocalPort > 0 {
  395. sb.WriteString(fmt.Sprintf(" local port %d as %d;\n", peer.LocalPort, localAS))
  396. } else {
  397. sb.WriteString(fmt.Sprintf(" local as %d;\n", localAS))
  398. }
  399. }
  400. if peer.RemotePort > 0 {
  401. sb.WriteString(fmt.Sprintf(" neighbor %s port %d as %d;\n", peer.RemoteAddress, peer.RemotePort, peer.RemoteAS))
  402. } else {
  403. sb.WriteString(fmt.Sprintf(" neighbor %s as %d;\n", peer.RemoteAddress, peer.RemoteAS))
  404. }
  405. if peer.Multihop {
  406. sb.WriteString(" multihop;\n")
  407. }
  408. if peer.Passive || (!peer.Connect && peer.Listen) {
  409. sb.WriteString(" passive;\n")
  410. }
  411. if peer.Password != "" {
  412. sb.WriteString(fmt.Sprintf(" password \"%s\";\n", peer.Password))
  413. }
  414. holdTime := 180
  415. if peer.HoldTime > 0 {
  416. holdTime = peer.HoldTime
  417. } else if m.config.BGPInstance.HoldTime > 0 {
  418. holdTime = m.config.BGPInstance.HoldTime
  419. }
  420. sb.WriteString(fmt.Sprintf(" hold time %d;\n", holdTime))
  421. if peer.KeepaliveTime > 0 {
  422. sb.WriteString(fmt.Sprintf(" keepalive time %d;\n", peer.KeepaliveTime))
  423. }
  424. // IPv4 Channel
  425. sb.WriteString(" ipv4 {\n")
  426. if peer.NexthopSelf || peer.NexthopChoice == "force-self" {
  427. sb.WriteString(" next hop self;\n")
  428. }
  429. // Import Filter
  430. defaultAD := 20
  431. if peer.LocalRole == "ibgp" || (localAS > 0 && peer.RemoteAS > 0 && localAS == peer.RemoteAS) {
  432. defaultAD = 200
  433. }
  434. if peer.InFilter != "" && len(chainRules[peer.InFilter]) > 0 {
  435. sb.WriteString(fmt.Sprintf(" import filter %s;\n", sanitizeIdentifier("filter", peer.InFilter)))
  436. } else {
  437. sb.WriteString(fmt.Sprintf(" import filter { krt_metric = %d; accept; };\n", defaultAD))
  438. }
  439. // Export Filter
  440. if peer.OutFilter != "" && len(chainRules[peer.OutFilter]) > 0 {
  441. fnName := fmt.Sprintf("fn_%s", sanitizeIdentifier("filter", peer.OutFilter))
  442. sb.WriteString(" export filter {\n")
  443. sb.WriteString(fmt.Sprintf(" %s();\n", fnName))
  444. if peerNetsProto != "" {
  445. sb.WriteString(fmt.Sprintf(" if proto = \"%s\" then accept;\n", peerNetsProto))
  446. }
  447. if len(activeNets) > 0 {
  448. sb.WriteString(" if proto = \"bgp_static_nets\" then accept;\n")
  449. }
  450. if peer.RedistConnected || m.config.BGPInstance.RedistributeConnected {
  451. sb.WriteString(" if source = RTS_DEVICE then accept;\n")
  452. }
  453. if peer.RedistStatic || m.config.BGPInstance.RedistributeStatic {
  454. sb.WriteString(" if source ~ [ RTS_STATIC, RTS_INHERIT ] then accept;\n")
  455. }
  456. if peer.RedistOSPF || m.config.BGPInstance.RedistributeOSPF {
  457. sb.WriteString(" if source = RTS_OSPF then accept;\n")
  458. }
  459. sb.WriteString(" reject;\n")
  460. sb.WriteString(" };\n")
  461. } else {
  462. var exportSources []string
  463. if peerNetsProto != "" {
  464. exportSources = append(exportSources, fmt.Sprintf(`proto = "%s"`, peerNetsProto))
  465. }
  466. if len(activeNets) > 0 {
  467. exportSources = append(exportSources, `proto = "bgp_static_nets"`)
  468. }
  469. exportSources = append(exportSources, `source = RTS_BGP`)
  470. if peer.RedistConnected || m.config.BGPInstance.RedistributeConnected {
  471. exportSources = append(exportSources, `source = RTS_DEVICE`)
  472. }
  473. if peer.RedistStatic || m.config.BGPInstance.RedistributeStatic {
  474. exportSources = append(exportSources, `source ~ [ RTS_STATIC, RTS_INHERIT ]`)
  475. }
  476. if peer.RedistOSPF || m.config.BGPInstance.RedistributeOSPF {
  477. exportSources = append(exportSources, `source = RTS_OSPF`)
  478. }
  479. if len(exportSources) > 0 {
  480. sb.WriteString(fmt.Sprintf(" export where (%s);\n", strings.Join(exportSources, ") || (")))
  481. } else {
  482. sb.WriteString(" export none;\n")
  483. }
  484. }
  485. sb.WriteString(" };\n")
  486. sb.WriteString("}\n\n")
  487. }
  488. }
  489. // OSPFv2 Protocols (RouterOS v7 Multi-Instance OSPF)
  490. ospfInstances := m.config.OSPFInstances
  491. if len(ospfInstances) == 0 {
  492. legacy := m.config.OSPFInstance
  493. instRouterID := legacy.RouterID
  494. if instRouterID == "" {
  495. instRouterID = routerID
  496. }
  497. ospfInstances = []OSPFInstance{
  498. {
  499. ID: "ospf-inst-default",
  500. Name: "Default",
  501. Version: 2,
  502. VRF: "main",
  503. RouterID: instRouterID,
  504. RoutingTable: "main",
  505. Enabled: legacy.Enabled,
  506. Disabled: legacy.Disabled,
  507. RedistributeConnected: legacy.RedistributeConnected,
  508. RedistributeStatic: legacy.RedistributeStatic,
  509. RedistributeBGP: legacy.RedistributeBGP,
  510. DefaultMetric: legacy.DefaultMetric,
  511. },
  512. }
  513. }
  514. for _, inst := range ospfInstances {
  515. if inst.Disabled {
  516. continue
  517. }
  518. instRouterID := inst.RouterID
  519. if instRouterID == "" {
  520. instRouterID = routerID
  521. }
  522. instProtoName := sanitizeIdentifier("ospf", inst.Name)
  523. sb.WriteString(fmt.Sprintf("protocol ospf v%d %s {\n", inst.Version, instProtoName))
  524. if inst.VRF != "" && inst.VRF != "main" && inst.VRF != "default" {
  525. sb.WriteString(fmt.Sprintf(" vrf %s;\n", inst.VRF))
  526. }
  527. sb.WriteString(fmt.Sprintf(" router id %s;\n", instRouterID))
  528. sb.WriteString(" ipv4 {\n")
  529. if inst.RoutingTable != "" && inst.RoutingTable != "main" {
  530. sb.WriteString(fmt.Sprintf(" table %s;\n", inst.RoutingTable))
  531. }
  532. sb.WriteString(" import filter { krt_metric = 110; accept; };\n")
  533. var ospfExports []string
  534. if inst.RedistributeStatic {
  535. ospfExports = append(ospfExports, "RTS_STATIC", "RTS_INHERIT")
  536. }
  537. if inst.RedistributeConnected {
  538. ospfExports = append(ospfExports, "RTS_DEVICE")
  539. }
  540. if inst.RedistributeBGP {
  541. ospfExports = append(ospfExports, "RTS_BGP")
  542. }
  543. if len(ospfExports) > 0 {
  544. if inst.DefaultMetric > 0 {
  545. sb.WriteString(fmt.Sprintf(" export filter {\n if source ~ [ %s ] then {\n ospf_metric1 = %d;\n accept;\n }\n reject;\n };\n", strings.Join(ospfExports, ", "), inst.DefaultMetric))
  546. } else {
  547. sb.WriteString(fmt.Sprintf(" export where source ~ [ %s ];\n", strings.Join(ospfExports, ", ")))
  548. }
  549. } else {
  550. sb.WriteString(" export none;\n")
  551. }
  552. sb.WriteString(" };\n\n")
  553. // Areas for this instance
  554. areas := m.config.OSPFAreas
  555. if len(areas) == 0 {
  556. areas = []OSPFArea{
  557. {
  558. Name: "Default",
  559. Instance: inst.Name,
  560. AreaID: "0.0.0.0",
  561. AreaType: "default",
  562. },
  563. }
  564. }
  565. for _, area := range areas {
  566. if area.Disabled {
  567. continue
  568. }
  569. if area.Instance != "" && !strings.EqualFold(area.Instance, inst.Name) && !strings.EqualFold(area.Instance, "Default") {
  570. continue
  571. }
  572. areaID := strings.TrimSpace(area.AreaID)
  573. if areaID == "" {
  574. areaID = "0.0.0.0"
  575. }
  576. sb.WriteString(fmt.Sprintf(" area %s {\n", areaID))
  577. if area.AreaType == "stub" {
  578. sb.WriteString(" stub;\n")
  579. } else if area.AreaType == "nssa" {
  580. sb.WriteString(" nssa;\n")
  581. }
  582. if area.DefaultCost > 0 {
  583. sb.WriteString(fmt.Sprintf(" default cost %d;\n", area.DefaultCost))
  584. }
  585. if area.NoSummaries {
  586. sb.WriteString(" summary no;\n")
  587. }
  588. // Area ranges (stubnet)
  589. for _, ar := range m.config.OSPFAreaRanges {
  590. if ar.Disabled {
  591. continue
  592. }
  593. if strings.EqualFold(ar.Area, area.Name) || strings.EqualFold(ar.Area, area.AreaID) || ar.Area == "" {
  594. prefix := strings.TrimSpace(ar.Prefix)
  595. if prefix != "" {
  596. var stubnetOpts []string
  597. if ar.Cost > 0 {
  598. stubnetOpts = append(stubnetOpts, fmt.Sprintf("cost %d;", ar.Cost))
  599. }
  600. if !ar.Advertise {
  601. stubnetOpts = append(stubnetOpts, "hidden;")
  602. }
  603. if len(stubnetOpts) > 0 {
  604. sb.WriteString(fmt.Sprintf(" stubnet %s { %s };\n", prefix, strings.Join(stubnetOpts, " ")))
  605. } else {
  606. sb.WriteString(fmt.Sprintf(" stubnet %s;\n", prefix))
  607. }
  608. }
  609. }
  610. }
  611. // Collect active static neighbors for this area
  612. var areaNbrs []OSPFStaticNeighbor
  613. for _, nbr := range m.config.OSPFStaticNbrs {
  614. if nbr.Disabled {
  615. continue
  616. }
  617. if strings.EqualFold(nbr.Area, area.Name) || strings.EqualFold(nbr.Area, area.AreaID) || (nbr.Area == "" && (strings.EqualFold(area.Name, "Default") || area.AreaID == "0.0.0.0")) {
  618. areaNbrs = append(areaNbrs, nbr)
  619. }
  620. }
  621. usedNbrIDs := make(map[string]bool)
  622. // Interface templates matching this area
  623. for _, iface := range m.config.OSPFInterfaces {
  624. if iface.Disabled {
  625. continue
  626. }
  627. if iface.Area != "" && !strings.EqualFold(iface.Area, area.Name) && !strings.EqualFold(iface.Area, area.AreaID) && iface.Area != "Default" {
  628. continue
  629. }
  630. ifaceName := strings.TrimSpace(iface.Interfaces)
  631. if ifaceName == "" {
  632. ifaceName = strings.TrimSpace(iface.Interface)
  633. }
  634. netName := strings.TrimSpace(iface.Networks)
  635. if netName == "" {
  636. netName = strings.TrimSpace(iface.Network)
  637. }
  638. target := formatInterfaceTargets(ifaceName, netName)
  639. sb.WriteString(fmt.Sprintf(" interface %s {\n", target))
  640. if iface.Cost > 0 {
  641. sb.WriteString(fmt.Sprintf(" cost %d;\n", iface.Cost))
  642. }
  643. if iface.Priority > 0 {
  644. sb.WriteString(fmt.Sprintf(" priority %d;\n", iface.Priority))
  645. }
  646. if iface.HelloInterval > 0 {
  647. sb.WriteString(fmt.Sprintf(" hello %d;\n", iface.HelloInterval))
  648. }
  649. if iface.DeadInterval > 0 {
  650. sb.WriteString(fmt.Sprintf(" dead %d;\n", iface.DeadInterval))
  651. }
  652. netType := strings.ToLower(strings.TrimSpace(iface.NetworkType))
  653. if iface.Passive {
  654. sb.WriteString(" stub;\n")
  655. } else {
  656. switch netType {
  657. case "ptp", "point-to-point":
  658. sb.WriteString(" type ptp;\n")
  659. case "nbma", "nonbroadcast":
  660. sb.WriteString(" type nonbroadcast;\n")
  661. case "ptmp", "point-to-multipoint":
  662. sb.WriteString(" type ptmp;\n")
  663. default:
  664. sb.WriteString(" type bcast;\n")
  665. }
  666. }
  667. auth := strings.ToLower(iface.Auth)
  668. if auth == "" {
  669. auth = strings.ToLower(iface.AuthType)
  670. }
  671. if (auth == "md5" || auth == "cryptographic") && iface.AuthKey != "" {
  672. sb.WriteString(" authentication cryptographic;\n")
  673. sb.WriteString(fmt.Sprintf(" password \"%s\";\n", iface.AuthKey))
  674. } else if auth == "simple" && iface.AuthKey != "" {
  675. sb.WriteString(" authentication simple;\n")
  676. sb.WriteString(fmt.Sprintf(" password \"%s\";\n", iface.AuthKey))
  677. }
  678. // Find static neighbors matching this interface template
  679. var templateNbrs []OSPFStaticNeighbor
  680. for _, nbr := range areaNbrs {
  681. if usedNbrIDs[nbr.ID] {
  682. continue
  683. }
  684. if netName != "" && ipInSubnets(nbr.Address, netName) {
  685. templateNbrs = append(templateNbrs, nbr)
  686. usedNbrIDs[nbr.ID] = true
  687. }
  688. }
  689. if len(templateNbrs) == 0 && (strings.Contains(netType, "nbma") || strings.Contains(netType, "nonbroadcast") || strings.Contains(netType, "ptmp")) {
  690. for _, nbr := range areaNbrs {
  691. if !usedNbrIDs[nbr.ID] {
  692. templateNbrs = append(templateNbrs, nbr)
  693. usedNbrIDs[nbr.ID] = true
  694. }
  695. }
  696. }
  697. if len(templateNbrs) > 0 {
  698. for _, nbr := range templateNbrs {
  699. if nbr.PollInterval > 0 {
  700. sb.WriteString(fmt.Sprintf(" poll %d;\n", nbr.PollInterval))
  701. break
  702. }
  703. }
  704. sb.WriteString(" neighbors {\n")
  705. for _, nbr := range templateNbrs {
  706. addr := strings.TrimSpace(nbr.Address)
  707. if addr != "" {
  708. sb.WriteString(fmt.Sprintf(" %s;\n", addr))
  709. }
  710. }
  711. sb.WriteString(" };\n")
  712. }
  713. sb.WriteString(" };\n")
  714. }
  715. // Any remaining static neighbors in this area not matched to a template
  716. var remainingNbrs []OSPFStaticNeighbor
  717. for _, nbr := range areaNbrs {
  718. if !usedNbrIDs[nbr.ID] {
  719. remainingNbrs = append(remainingNbrs, nbr)
  720. }
  721. }
  722. if len(remainingNbrs) > 0 {
  723. sb.WriteString(" interface \"*\" {\n")
  724. sb.WriteString(" type nonbroadcast;\n")
  725. for _, nbr := range remainingNbrs {
  726. if nbr.PollInterval > 0 {
  727. sb.WriteString(fmt.Sprintf(" poll %d;\n", nbr.PollInterval))
  728. break
  729. }
  730. }
  731. sb.WriteString(" neighbors {\n")
  732. for _, nbr := range remainingNbrs {
  733. addr := strings.TrimSpace(nbr.Address)
  734. if addr != "" {
  735. sb.WriteString(fmt.Sprintf(" %s;\n", addr))
  736. }
  737. }
  738. sb.WriteString(" };\n")
  739. sb.WriteString(" };\n")
  740. }
  741. sb.WriteString(" };\n")
  742. }
  743. sb.WriteString("}\n\n")
  744. }
  745. return sb.String()
  746. }
  747. // applyBirdConfig generates /etc/bird/bird.conf and live reloads BIRD daemon
  748. func (m *Manager) applyBirdConfig() error {
  749. cfgText := m.generateBirdConfig()
  750. // Ensure directory exists with proper permissions
  751. dir := filepath.Dir(birdConfigPath)
  752. if err := os.MkdirAll(dir, 0775); err == nil {
  753. _ = os.Chmod(dir, 0775)
  754. _ = exec.Command("chown", "root:bird", dir).Run()
  755. }
  756. // Write candidate configuration to temporary file
  757. tmpPath := birdConfigPath + ".new"
  758. if err := os.WriteFile(tmpPath, []byte(cfgText), 0664); err != nil {
  759. return fmt.Errorf("failed to write %s: %w", tmpPath, err)
  760. }
  761. _ = exec.Command("chown", "root:bird", tmpPath).Run()
  762. // Validate syntax with bird -p
  763. out, err := exec.Command("bird", "-p", "-c", tmpPath).CombinedOutput()
  764. if err != nil {
  765. _ = os.Remove(tmpPath)
  766. return fmt.Errorf("bird syntax check failed: %s: %w", string(out), err)
  767. }
  768. // Replace active config
  769. if err := os.Rename(tmpPath, birdConfigPath); err != nil {
  770. return fmt.Errorf("failed to replace %s: %w", birdConfigPath, err)
  771. }
  772. _ = os.Chmod(birdConfigPath, 0664)
  773. _ = exec.Command("chown", "root:bird", birdConfigPath).Run()
  774. // Ensure BIRD systemd service is active
  775. _ = exec.Command("systemctl", "start", "bird").Run()
  776. // Live reload without packet loss
  777. reloadOut, reloadErr := birdcExec("configure")
  778. if reloadErr != nil {
  779. return fmt.Errorf("BIRD reload failed: %s: %w", string(reloadOut), reloadErr)
  780. }
  781. // Synchronize kernel blackhole routes (distance 255) for all active advertised prefixes
  782. m.syncKernelBlackholeRoutes()
  783. return nil
  784. }
  785. func birdcExec(cmd string) ([]byte, error) {
  786. return exec.Command("birdc", cmd).CombinedOutput()
  787. }
  788. // syncKernelBlackholeRoutes ensures all BGP advertised networks (global BGP networks and peer OutputNetworks)
  789. // have corresponding blackhole routes with metric 255 installed in the Linux kernel routing table.
  790. // This guarantees that BGP can announce these prefixes reliably into the RIB/FIB, while ensuring any active
  791. // connected/local routes take precedence (metric 0 < metric 255) and prevents routing loops if subnets are incomplete.
  792. func (m *Manager) syncKernelBlackholeRoutes() {
  793. desired := make(map[string]bool)
  794. if m.config.BGPInstance.Enabled {
  795. for _, n := range m.config.BGPNetworks {
  796. cidr := strings.TrimSpace(n.Network)
  797. if !n.Disabled && cidr != "" {
  798. if _, _, err := net.ParseCIDR(cidr); err == nil {
  799. desired[cidr] = true
  800. }
  801. }
  802. }
  803. for _, p := range m.config.BGPPeers {
  804. if p.Disabled {
  805. continue
  806. }
  807. for _, on := range p.OutputNetworks {
  808. cidr := strings.TrimSpace(on)
  809. if cidr != "" {
  810. if _, _, err := net.ParseCIDR(cidr); err == nil {
  811. desired[cidr] = true
  812. }
  813. }
  814. }
  815. }
  816. }
  817. // Query existing kernel blackhole routes with metric 255
  818. out, err := exec.Command("ip", "route", "show", "type", "blackhole").Output()
  819. existing := make(map[string]bool)
  820. if err == nil {
  821. lines := strings.Split(string(out), "\n")
  822. for _, line := range lines {
  823. line = strings.TrimSpace(line)
  824. if line == "" {
  825. continue
  826. }
  827. parts := strings.Fields(line)
  828. if len(parts) >= 2 && parts[0] == "blackhole" {
  829. dst := parts[1]
  830. if strings.Contains(line, "metric 255") {
  831. existing[dst] = true
  832. }
  833. }
  834. }
  835. }
  836. // Install desired blackhole routes with metric 255 proto static
  837. for cidr := range desired {
  838. if !existing[cidr] {
  839. cmd := exec.Command("ip", "route", "replace", "blackhole", cidr, "metric", "255", "proto", "static")
  840. if out, err := cmd.CombinedOutput(); err != nil {
  841. log.Printf("[DynamicRouting] Warning: failed to install kernel blackhole route %s: %s (%v)", cidr, string(out), err)
  842. } else {
  843. log.Printf("[DynamicRouting] Installed kernel blackhole route %s with metric 255 (BGP advertised)", cidr)
  844. }
  845. }
  846. }
  847. // Cleanup any obsolete blackhole routes that were previously installed with metric 255
  848. // Check static_routes.json first to ensure we don't delete user's manual static routes
  849. userManualStaticBlackholes := make(map[string]bool)
  850. if data, err := os.ReadFile("/etc/debtik/routing/static_routes.json"); err == nil {
  851. var userRoutes []struct {
  852. DstAddress string `json:"dst_address"`
  853. Gateway string `json:"gateway"`
  854. Action string `json:"action"`
  855. Disabled bool `json:"disabled"`
  856. }
  857. if json.Unmarshal(data, &userRoutes) == nil {
  858. for _, ur := range userRoutes {
  859. if !ur.Disabled && (strings.EqualFold(ur.Gateway, "blackhole") || strings.EqualFold(ur.Action, "blackhole")) {
  860. userManualStaticBlackholes[strings.TrimSpace(ur.DstAddress)] = true
  861. }
  862. }
  863. }
  864. }
  865. for cidr := range existing {
  866. if !desired[cidr] && !userManualStaticBlackholes[cidr] {
  867. _ = exec.Command("ip", "route", "del", "blackhole", cidr, "metric", "255").Run()
  868. log.Printf("[DynamicRouting] Removed obsolete kernel blackhole route %s metric 255", cidr)
  869. }
  870. }
  871. }
  872. // -------------------------------------------------------------
  873. // BGP API & Operations (Multi-Instance BGP)
  874. // -------------------------------------------------------------
  875. func (m *Manager) GetBGPInstance() BGPInstance {
  876. m.mu.RLock()
  877. defer m.mu.RUnlock()
  878. inst := m.config.BGPInstance
  879. out, err := birdcExec("show status")
  880. if err == nil && strings.Contains(string(out), "Daemon is up and running") && inst.Enabled {
  881. inst.Running = true
  882. }
  883. return inst
  884. }
  885. func (m *Manager) UpdateBGPInstance(inst BGPInstance) error {
  886. m.mu.Lock()
  887. m.config.BGPInstance = inst
  888. _ = m.save()
  889. m.mu.Unlock()
  890. return m.applyBirdConfig()
  891. }
  892. func (m *Manager) ListBGPPeers() []BGPPeer {
  893. m.mu.RLock()
  894. defer m.mu.RUnlock()
  895. peers := make([]BGPPeer, len(m.config.BGPPeers))
  896. copy(peers, m.config.BGPPeers)
  897. // Fetch live peer telemetry from birdc
  898. out, err := birdcExec("show protocols all")
  899. if err == nil {
  900. type livePeerData struct {
  901. State string
  902. RemoteAddr string
  903. RemoteAS uint32
  904. LocalAS uint32
  905. PfxRcd int
  906. PfxSnt int
  907. Uptime string
  908. }
  909. liveMap := make(map[string]*livePeerData)
  910. lines := strings.Split(string(out), "\n")
  911. var cur *livePeerData
  912. now := time.Now()
  913. for _, line := range lines {
  914. trimmed := strings.TrimSpace(line)
  915. // A protocol header line in BIRD is unindented and has "BGP" in column 2
  916. if !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") {
  917. fields := strings.Fields(line)
  918. if len(fields) >= 2 && fields[1] == "BGP" {
  919. cur = &livePeerData{}
  920. var sinceStr string
  921. if len(fields) >= 6 && strings.Contains(fields[4], "-") && strings.Contains(fields[5], ":") {
  922. sinceStr = fields[4] + " " + fields[5]
  923. } else if len(fields) >= 5 {
  924. sinceStr = fields[4]
  925. }
  926. cur.Uptime = parseBirdSinceToUptime(sinceStr, now)
  927. } else {
  928. cur = nil
  929. }
  930. }
  931. if cur == nil {
  932. continue
  933. }
  934. if strings.HasPrefix(trimmed, "BGP state:") {
  935. cur.State = strings.TrimSpace(strings.TrimPrefix(trimmed, "BGP state:"))
  936. } else if strings.HasPrefix(trimmed, "Neighbor address:") {
  937. cur.RemoteAddr = strings.TrimSpace(strings.TrimPrefix(trimmed, "Neighbor address:"))
  938. liveMap[cur.RemoteAddr] = cur
  939. } else if strings.HasPrefix(trimmed, "Neighbor AS:") {
  940. v, _ := strconv.ParseUint(strings.TrimSpace(strings.TrimPrefix(trimmed, "Neighbor AS:")), 10, 32)
  941. cur.RemoteAS = uint32(v)
  942. } else if strings.HasPrefix(trimmed, "Local AS:") {
  943. v, _ := strconv.ParseUint(strings.TrimSpace(strings.TrimPrefix(trimmed, "Local AS:")), 10, 32)
  944. cur.LocalAS = uint32(v)
  945. } else if strings.HasPrefix(trimmed, "Routes:") {
  946. parts := strings.Split(trimmed, ",")
  947. for _, p := range parts {
  948. p = strings.TrimSpace(p)
  949. if strings.Contains(p, "imported") {
  950. f := strings.Fields(p)
  951. if len(f) >= 2 {
  952. cur.PfxRcd, _ = strconv.Atoi(f[len(f)-2])
  953. }
  954. } else if strings.Contains(p, "exported") {
  955. f := strings.Fields(p)
  956. if len(f) >= 2 {
  957. cur.PfxSnt, _ = strconv.Atoi(f[0])
  958. }
  959. }
  960. }
  961. }
  962. }
  963. for i := range peers {
  964. if live, ok := liveMap[peers[i].RemoteAddress]; ok {
  965. peers[i].State = live.State
  966. if live.State == "Established" {
  967. peers[i].Uptime = live.Uptime
  968. } else {
  969. peers[i].Uptime = ""
  970. }
  971. peers[i].PrefixReceived = live.PfxRcd
  972. peers[i].PrefixSent = live.PfxSnt
  973. if peers[i].LocalAS == 0 && live.LocalAS > 0 {
  974. peers[i].LocalAS = live.LocalAS
  975. }
  976. } else if peers[i].Disabled {
  977. peers[i].State = "Disabled"
  978. peers[i].Uptime = ""
  979. } else {
  980. peers[i].State = "Idle"
  981. peers[i].Uptime = ""
  982. }
  983. }
  984. }
  985. return peers
  986. }
  987. func parseBirdSinceToUptime(sinceStr string, now time.Time) string {
  988. sinceStr = strings.TrimSpace(sinceStr)
  989. if sinceStr == "" {
  990. return ""
  991. }
  992. // Case 1: "2006-01-02 15:04:05" (ISO long)
  993. if t, err := time.ParseInLocation("2006-01-02 15:04:05", sinceStr, time.Local); err == nil {
  994. return formatDuration(now.Sub(t))
  995. }
  996. // Case 2: "2006-01-02 15:04:05.999" (ISO long with milliseconds)
  997. if t, err := time.ParseInLocation("2006-01-02 15:04:05.999", sinceStr, time.Local); err == nil {
  998. return formatDuration(now.Sub(t))
  999. }
  1000. // Case 3: "2006-01-02" (date only)
  1001. if t, err := time.ParseInLocation("2006-01-02", sinceStr, time.Local); err == nil {
  1002. return formatDuration(now.Sub(t))
  1003. }
  1004. // Case 4: "15:04:05" or "15:04:05.123" (time only, happened today)
  1005. timeOnly := sinceStr
  1006. if idx := strings.IndexByte(timeOnly, '.'); idx != -1 {
  1007. timeOnly = timeOnly[:idx]
  1008. }
  1009. if t, err := time.ParseInLocation("15:04:05", timeOnly, time.Local); err == nil {
  1010. tToday := time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local)
  1011. if tToday.After(now) {
  1012. tToday = tToday.AddDate(0, 0, -1)
  1013. }
  1014. return formatDuration(now.Sub(tToday))
  1015. }
  1016. return ""
  1017. }
  1018. func formatDuration(d time.Duration) string {
  1019. if d < 0 {
  1020. d = 0
  1021. }
  1022. sec := int64(d.Seconds())
  1023. days := sec / 86400
  1024. hours := (sec % 86400) / 3600
  1025. mins := (sec % 3600) / 60
  1026. secs := sec % 60
  1027. if days > 0 {
  1028. return fmt.Sprintf("%dd %02dh %02dm %02ds", days, hours, mins, secs)
  1029. }
  1030. return fmt.Sprintf("%02dh %02dm %02ds", hours, mins, secs)
  1031. }
  1032. func (m *Manager) SaveBGPPeer(peer *BGPPeer) error {
  1033. m.mu.Lock()
  1034. defer m.mu.Unlock()
  1035. if peer.ID == "" {
  1036. peer.ID = fmt.Sprintf("bgp-peer-%d", time.Now().UnixNano())
  1037. }
  1038. found := false
  1039. for i, p := range m.config.BGPPeers {
  1040. if p.ID == peer.ID {
  1041. m.config.BGPPeers[i] = *peer
  1042. found = true
  1043. break
  1044. }
  1045. }
  1046. if !found {
  1047. m.config.BGPPeers = append(m.config.BGPPeers, *peer)
  1048. }
  1049. if err := m.save(); err != nil {
  1050. return err
  1051. }
  1052. return m.applyBirdConfig()
  1053. }
  1054. func (m *Manager) DeleteBGPPeer(id string) error {
  1055. m.mu.Lock()
  1056. defer m.mu.Unlock()
  1057. newPeers := make([]BGPPeer, 0, len(m.config.BGPPeers))
  1058. for _, p := range m.config.BGPPeers {
  1059. if p.ID != id {
  1060. newPeers = append(newPeers, p)
  1061. }
  1062. }
  1063. m.config.BGPPeers = newPeers
  1064. if err := m.save(); err != nil {
  1065. return err
  1066. }
  1067. return m.applyBirdConfig()
  1068. }
  1069. func (m *Manager) SetBGPPeerState(id string, disabled bool) error {
  1070. m.mu.Lock()
  1071. defer m.mu.Unlock()
  1072. for i := range m.config.BGPPeers {
  1073. if m.config.BGPPeers[i].ID == id {
  1074. m.config.BGPPeers[i].Disabled = disabled
  1075. break
  1076. }
  1077. }
  1078. if err := m.save(); err != nil {
  1079. return err
  1080. }
  1081. return m.applyBirdConfig()
  1082. }
  1083. func (m *Manager) ListBGPNetworks() []BGPNetwork {
  1084. m.mu.RLock()
  1085. defer m.mu.RUnlock()
  1086. return m.config.BGPNetworks
  1087. }
  1088. func (m *Manager) SaveBGPNetwork(net BGPNetwork) error {
  1089. m.mu.Lock()
  1090. defer m.mu.Unlock()
  1091. if net.ID == "" {
  1092. net.ID = fmt.Sprintf("bgp-net-%d", time.Now().UnixNano())
  1093. }
  1094. found := false
  1095. for i, n := range m.config.BGPNetworks {
  1096. if n.ID == net.ID {
  1097. m.config.BGPNetworks[i] = net
  1098. found = true
  1099. break
  1100. }
  1101. }
  1102. if !found {
  1103. m.config.BGPNetworks = append(m.config.BGPNetworks, net)
  1104. }
  1105. if err := m.save(); err != nil {
  1106. return err
  1107. }
  1108. return m.applyBirdConfig()
  1109. }
  1110. func (m *Manager) DeleteBGPNetwork(id string) error {
  1111. m.mu.Lock()
  1112. defer m.mu.Unlock()
  1113. newNets := make([]BGPNetwork, 0, len(m.config.BGPNetworks))
  1114. for _, n := range m.config.BGPNetworks {
  1115. if n.ID != id {
  1116. newNets = append(newNets, n)
  1117. }
  1118. }
  1119. m.config.BGPNetworks = newNets
  1120. if err := m.save(); err != nil {
  1121. return err
  1122. }
  1123. return m.applyBirdConfig()
  1124. }
  1125. func (m *Manager) GetBGPRoutes() []BGPRoute {
  1126. var routes []BGPRoute
  1127. out, err := birdcExec("show route where source = RTS_BGP all")
  1128. if err != nil {
  1129. return routes
  1130. }
  1131. lines := strings.Split(string(out), "\n")
  1132. var cur *BGPRoute
  1133. for _, line := range lines {
  1134. trimmed := strings.TrimSpace(line)
  1135. if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "Table") {
  1136. continue
  1137. }
  1138. if !strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, " ") {
  1139. fields := strings.Fields(trimmed)
  1140. if len(fields) >= 2 {
  1141. routes = append(routes, BGPRoute{
  1142. Prefix: fields[0],
  1143. Best: strings.Contains(line, "*"),
  1144. Origin: "IGP",
  1145. })
  1146. cur = &routes[len(routes)-1]
  1147. }
  1148. continue
  1149. }
  1150. if cur == nil {
  1151. continue
  1152. }
  1153. if strings.HasPrefix(trimmed, "via ") {
  1154. parts := strings.Fields(trimmed)
  1155. if len(parts) >= 2 {
  1156. cur.NextHop = parts[1]
  1157. }
  1158. } else if strings.HasPrefix(trimmed, "BGP.as_path:") {
  1159. cur.Path = strings.TrimSpace(strings.TrimPrefix(trimmed, "BGP.as_path:"))
  1160. } else if strings.HasPrefix(trimmed, "BGP.origin:") {
  1161. cur.Origin = strings.TrimSpace(strings.TrimPrefix(trimmed, "BGP.origin:"))
  1162. } else if strings.HasPrefix(trimmed, "BGP.next_hop:") {
  1163. cur.NextHop = strings.TrimSpace(strings.TrimPrefix(trimmed, "BGP.next_hop:"))
  1164. } else if strings.HasPrefix(trimmed, "BGP.med:") {
  1165. cur.Metric, _ = strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(trimmed, "BGP.med:")))
  1166. }
  1167. }
  1168. return routes
  1169. }
  1170. // -------------------------------------------------------------
  1171. // OSPF API & Operations (RouterOS v7 Style OSPF)
  1172. // -------------------------------------------------------------
  1173. func (m *Manager) ListOSPFInstances() []OSPFInstance {
  1174. m.mu.RLock()
  1175. defer m.mu.RUnlock()
  1176. out, err := birdcExec("show protocols")
  1177. protocols := map[string]bool{}
  1178. if err == nil {
  1179. for _, line := range strings.Split(string(out), "\n") {
  1180. fields := strings.Fields(line)
  1181. if len(fields) >= 4 && fields[1] == "OSPF" {
  1182. protocols[fields[0]] = fields[3] == "up"
  1183. }
  1184. }
  1185. }
  1186. res := make([]OSPFInstance, len(m.config.OSPFInstances))
  1187. for i, inst := range m.config.OSPFInstances {
  1188. instCopy := inst
  1189. instCopy.Running = false
  1190. if protocols[sanitizeIdentifier("ospf", inst.Name)] && !inst.Disabled {
  1191. instCopy.Running = true
  1192. }
  1193. res[i] = instCopy
  1194. }
  1195. return res
  1196. }
  1197. func (m *Manager) SaveOSPFInstance(inst *OSPFInstance) error {
  1198. m.mu.Lock()
  1199. defer m.mu.Unlock()
  1200. if inst.ID == "" {
  1201. inst.ID = fmt.Sprintf("ospf-inst-%d", time.Now().UnixNano())
  1202. }
  1203. if inst.Name == "" {
  1204. inst.Name = "Default"
  1205. }
  1206. if inst.Version <= 0 {
  1207. inst.Version = 2
  1208. }
  1209. if inst.VRF == "" {
  1210. inst.VRF = "main"
  1211. }
  1212. if inst.RoutingTable == "" {
  1213. inst.RoutingTable = "main"
  1214. }
  1215. found := false
  1216. for i, it := range m.config.OSPFInstances {
  1217. if it.ID == inst.ID {
  1218. m.config.OSPFInstances[i] = *inst
  1219. found = true
  1220. break
  1221. }
  1222. }
  1223. if !found {
  1224. m.config.OSPFInstances = append(m.config.OSPFInstances, *inst)
  1225. }
  1226. // Update legacy instance holder as well
  1227. m.config.OSPFInstance = *inst
  1228. if err := m.save(); err != nil {
  1229. return err
  1230. }
  1231. return m.applyBirdConfig()
  1232. }
  1233. func (m *Manager) DeleteOSPFInstance(id string) error {
  1234. m.mu.Lock()
  1235. defer m.mu.Unlock()
  1236. var newInst []OSPFInstance
  1237. for _, it := range m.config.OSPFInstances {
  1238. if it.ID != id {
  1239. newInst = append(newInst, it)
  1240. }
  1241. }
  1242. m.config.OSPFInstances = newInst
  1243. if err := m.save(); err != nil {
  1244. return err
  1245. }
  1246. return m.applyBirdConfig()
  1247. }
  1248. func (m *Manager) SetOSPFInstanceState(id string, disabled bool) error {
  1249. m.mu.Lock()
  1250. defer m.mu.Unlock()
  1251. for i := range m.config.OSPFInstances {
  1252. if m.config.OSPFInstances[i].ID == id {
  1253. m.config.OSPFInstances[i].Disabled = disabled
  1254. break
  1255. }
  1256. }
  1257. if err := m.save(); err != nil {
  1258. return err
  1259. }
  1260. return m.applyBirdConfig()
  1261. }
  1262. func (m *Manager) GetOSPFInstance() OSPFInstance {
  1263. m.mu.RLock()
  1264. defer m.mu.RUnlock()
  1265. if len(m.config.OSPFInstances) > 0 {
  1266. inst := m.config.OSPFInstances[0]
  1267. out, err := birdcExec("show protocols")
  1268. if err == nil && strings.Contains(string(out), "OSPF") && strings.Contains(string(out), "up") && !inst.Disabled {
  1269. inst.Running = true
  1270. }
  1271. return inst
  1272. }
  1273. inst := m.config.OSPFInstance
  1274. out, err := birdcExec("show protocols")
  1275. if err == nil && strings.Contains(string(out), "OSPF") && strings.Contains(string(out), "up") && inst.Enabled {
  1276. inst.Running = true
  1277. }
  1278. return inst
  1279. }
  1280. func (m *Manager) UpdateOSPFInstance(inst OSPFInstance) error {
  1281. return m.SaveOSPFInstance(&inst)
  1282. }
  1283. func (m *Manager) ListOSPFAreas() []OSPFArea {
  1284. m.mu.RLock()
  1285. defer m.mu.RUnlock()
  1286. return m.config.OSPFAreas
  1287. }
  1288. func (m *Manager) SaveOSPFArea(area OSPFArea) error {
  1289. m.mu.Lock()
  1290. defer m.mu.Unlock()
  1291. if area.ID == "" {
  1292. area.ID = fmt.Sprintf("area-%d", time.Now().UnixNano())
  1293. }
  1294. if area.Name == "" {
  1295. area.Name = "Default"
  1296. }
  1297. if area.Instance == "" {
  1298. area.Instance = "Default"
  1299. }
  1300. if area.AreaID == "" {
  1301. area.AreaID = "0.0.0.0"
  1302. }
  1303. if area.AreaType == "" {
  1304. area.AreaType = "default"
  1305. }
  1306. found := false
  1307. for i, a := range m.config.OSPFAreas {
  1308. if a.ID == area.ID {
  1309. m.config.OSPFAreas[i] = area
  1310. found = true
  1311. break
  1312. }
  1313. }
  1314. if !found {
  1315. m.config.OSPFAreas = append(m.config.OSPFAreas, area)
  1316. }
  1317. if err := m.save(); err != nil {
  1318. return err
  1319. }
  1320. return m.applyBirdConfig()
  1321. }
  1322. func (m *Manager) DeleteOSPFArea(id string) error {
  1323. m.mu.Lock()
  1324. defer m.mu.Unlock()
  1325. newAreas := make([]OSPFArea, 0, len(m.config.OSPFAreas))
  1326. for _, a := range m.config.OSPFAreas {
  1327. if a.ID != id {
  1328. newAreas = append(newAreas, a)
  1329. }
  1330. }
  1331. m.config.OSPFAreas = newAreas
  1332. if err := m.save(); err != nil {
  1333. return err
  1334. }
  1335. return m.applyBirdConfig()
  1336. }
  1337. func (m *Manager) SetOSPFAreaState(id string, disabled bool) error {
  1338. m.mu.Lock()
  1339. defer m.mu.Unlock()
  1340. for i := range m.config.OSPFAreas {
  1341. if m.config.OSPFAreas[i].ID == id {
  1342. m.config.OSPFAreas[i].Disabled = disabled
  1343. break
  1344. }
  1345. }
  1346. if err := m.save(); err != nil {
  1347. return err
  1348. }
  1349. return m.applyBirdConfig()
  1350. }
  1351. func (m *Manager) ListOSPFAreaRanges() []OSPFAreaRange {
  1352. m.mu.RLock()
  1353. defer m.mu.RUnlock()
  1354. return m.config.OSPFAreaRanges
  1355. }
  1356. func (m *Manager) SaveOSPFAreaRange(ar OSPFAreaRange) error {
  1357. m.mu.Lock()
  1358. defer m.mu.Unlock()
  1359. if ar.ID == "" {
  1360. ar.ID = fmt.Sprintf("range-%d", time.Now().UnixNano())
  1361. }
  1362. if ar.Area == "" {
  1363. ar.Area = "Default"
  1364. }
  1365. found := false
  1366. for i, item := range m.config.OSPFAreaRanges {
  1367. if item.ID == ar.ID {
  1368. m.config.OSPFAreaRanges[i] = ar
  1369. found = true
  1370. break
  1371. }
  1372. }
  1373. if !found {
  1374. m.config.OSPFAreaRanges = append(m.config.OSPFAreaRanges, ar)
  1375. }
  1376. if err := m.save(); err != nil {
  1377. return err
  1378. }
  1379. return m.applyBirdConfig()
  1380. }
  1381. func (m *Manager) DeleteOSPFAreaRange(id string) error {
  1382. m.mu.Lock()
  1383. defer m.mu.Unlock()
  1384. newRanges := make([]OSPFAreaRange, 0, len(m.config.OSPFAreaRanges))
  1385. for _, item := range m.config.OSPFAreaRanges {
  1386. if item.ID != id {
  1387. newRanges = append(newRanges, item)
  1388. }
  1389. }
  1390. m.config.OSPFAreaRanges = newRanges
  1391. if err := m.save(); err != nil {
  1392. return err
  1393. }
  1394. return m.applyBirdConfig()
  1395. }
  1396. func (m *Manager) ListOSPFInterfaces() []OSPFInterface {
  1397. m.mu.RLock()
  1398. defer m.mu.RUnlock()
  1399. return m.config.OSPFInterfaces
  1400. }
  1401. func (m *Manager) SaveOSPFInterface(iface OSPFInterface) error {
  1402. m.mu.Lock()
  1403. defer m.mu.Unlock()
  1404. if iface.ID == "" {
  1405. iface.ID = fmt.Sprintf("ospf-tmpl-%d", time.Now().UnixNano())
  1406. }
  1407. if iface.Area == "" {
  1408. iface.Area = "Default"
  1409. }
  1410. if iface.Cost <= 0 {
  1411. iface.Cost = 1
  1412. }
  1413. if iface.Priority <= 0 {
  1414. iface.Priority = 128
  1415. }
  1416. if iface.NetworkType == "" {
  1417. iface.NetworkType = "broadcast"
  1418. }
  1419. if iface.HelloInterval <= 0 {
  1420. iface.HelloInterval = 10
  1421. }
  1422. if iface.DeadInterval <= 0 {
  1423. iface.DeadInterval = 40
  1424. }
  1425. found := false
  1426. for i, item := range m.config.OSPFInterfaces {
  1427. if item.ID == iface.ID {
  1428. m.config.OSPFInterfaces[i] = iface
  1429. found = true
  1430. break
  1431. }
  1432. }
  1433. if !found {
  1434. m.config.OSPFInterfaces = append(m.config.OSPFInterfaces, iface)
  1435. }
  1436. if err := m.save(); err != nil {
  1437. return err
  1438. }
  1439. return m.applyBirdConfig()
  1440. }
  1441. func (m *Manager) DeleteOSPFInterface(id string) error {
  1442. m.mu.Lock()
  1443. defer m.mu.Unlock()
  1444. newInterfaces := make([]OSPFInterface, 0, len(m.config.OSPFInterfaces))
  1445. for _, item := range m.config.OSPFInterfaces {
  1446. if item.ID != id {
  1447. newInterfaces = append(newInterfaces, item)
  1448. }
  1449. }
  1450. m.config.OSPFInterfaces = newInterfaces
  1451. if err := m.save(); err != nil {
  1452. return err
  1453. }
  1454. return m.applyBirdConfig()
  1455. }
  1456. func (m *Manager) SetOSPFInterfaceState(id string, disabled bool) error {
  1457. m.mu.Lock()
  1458. defer m.mu.Unlock()
  1459. for i := range m.config.OSPFInterfaces {
  1460. if m.config.OSPFInterfaces[i].ID == id {
  1461. m.config.OSPFInterfaces[i].Disabled = disabled
  1462. break
  1463. }
  1464. }
  1465. if err := m.save(); err != nil {
  1466. return err
  1467. }
  1468. return m.applyBirdConfig()
  1469. }
  1470. func (m *Manager) GetOSPFInterfacesLive() []OSPFInterfaceLive {
  1471. var list []OSPFInterfaceLive
  1472. out, err := birdcExec("show ospf interface")
  1473. if err != nil {
  1474. return list
  1475. }
  1476. lines := strings.Split(string(out), "\n")
  1477. var cur *OSPFInterfaceLive
  1478. for _, line := range lines {
  1479. trimmed := strings.TrimSpace(line)
  1480. if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasSuffix(trimmed, ":") {
  1481. continue
  1482. }
  1483. if strings.HasPrefix(trimmed, "Interface ") {
  1484. if cur != nil {
  1485. list = append(list, *cur)
  1486. }
  1487. parts := strings.SplitN(strings.TrimPrefix(trimmed, "Interface "), " ", 2)
  1488. ifName := parts[0]
  1489. addr := ""
  1490. if len(parts) > 1 {
  1491. addr = strings.Trim(parts[1], "()")
  1492. }
  1493. cur = &OSPFInterfaceLive{
  1494. Flags: "D",
  1495. Interface: ifName,
  1496. Address: addr,
  1497. Area: "Default",
  1498. InstanceID: 0,
  1499. Cost: 1,
  1500. Priority: 128,
  1501. State: "waiting",
  1502. }
  1503. continue
  1504. }
  1505. if cur == nil {
  1506. continue
  1507. }
  1508. if strings.HasPrefix(trimmed, "Area: ") {
  1509. val := strings.TrimPrefix(trimmed, "Area: ")
  1510. areaParts := strings.Fields(val)
  1511. if len(areaParts) > 0 {
  1512. cur.Area = areaParts[0]
  1513. }
  1514. } else if strings.HasPrefix(trimmed, "State: ") {
  1515. cur.State = strings.ToLower(strings.TrimSpace(strings.TrimPrefix(trimmed, "State: ")))
  1516. } else if strings.HasPrefix(trimmed, "Priority: ") {
  1517. fmt.Sscanf(strings.TrimPrefix(trimmed, "Priority: "), "%d", &cur.Priority)
  1518. } else if strings.HasPrefix(trimmed, "Cost: ") {
  1519. fmt.Sscanf(strings.TrimPrefix(trimmed, "Cost: "), "%d", &cur.Cost)
  1520. }
  1521. }
  1522. if cur != nil {
  1523. list = append(list, *cur)
  1524. }
  1525. return list
  1526. }
  1527. func (m *Manager) ListOSPFStaticNeighbors() []OSPFStaticNeighbor {
  1528. m.mu.RLock()
  1529. defer m.mu.RUnlock()
  1530. return m.config.OSPFStaticNbrs
  1531. }
  1532. func (m *Manager) SaveOSPFStaticNeighbor(nbr OSPFStaticNeighbor) error {
  1533. m.mu.Lock()
  1534. defer m.mu.Unlock()
  1535. if nbr.ID == "" {
  1536. nbr.ID = fmt.Sprintf("snbr-%d", time.Now().UnixNano())
  1537. }
  1538. if nbr.Area == "" {
  1539. nbr.Area = "Default"
  1540. }
  1541. found := false
  1542. for i, item := range m.config.OSPFStaticNbrs {
  1543. if item.ID == nbr.ID {
  1544. m.config.OSPFStaticNbrs[i] = nbr
  1545. found = true
  1546. break
  1547. }
  1548. }
  1549. if !found {
  1550. m.config.OSPFStaticNbrs = append(m.config.OSPFStaticNbrs, nbr)
  1551. }
  1552. if err := m.save(); err != nil {
  1553. return err
  1554. }
  1555. return m.applyBirdConfig()
  1556. }
  1557. func (m *Manager) DeleteOSPFStaticNeighbor(id string) error {
  1558. m.mu.Lock()
  1559. defer m.mu.Unlock()
  1560. newNbrs := make([]OSPFStaticNeighbor, 0, len(m.config.OSPFStaticNbrs))
  1561. for _, item := range m.config.OSPFStaticNbrs {
  1562. if item.ID != id {
  1563. newNbrs = append(newNbrs, item)
  1564. }
  1565. }
  1566. m.config.OSPFStaticNbrs = newNbrs
  1567. if err := m.save(); err != nil {
  1568. return err
  1569. }
  1570. return m.applyBirdConfig()
  1571. }
  1572. func (m *Manager) SetOSPFStaticNeighborState(id string, disabled bool) error {
  1573. m.mu.Lock()
  1574. defer m.mu.Unlock()
  1575. for i := range m.config.OSPFStaticNbrs {
  1576. if m.config.OSPFStaticNbrs[i].ID == id {
  1577. m.config.OSPFStaticNbrs[i].Disabled = disabled
  1578. if err := m.save(); err != nil {
  1579. return err
  1580. }
  1581. return m.applyBirdConfig()
  1582. }
  1583. }
  1584. return fmt.Errorf("static neighbor %s not found", id)
  1585. }
  1586. func (m *Manager) GetOSPFNeighbors() []OSPFNeighbor {
  1587. var neighbors []OSPFNeighbor
  1588. out, err := birdcExec("show ospf neighbors")
  1589. if err != nil {
  1590. return neighbors
  1591. }
  1592. lines := strings.Split(string(out), "\n")
  1593. for _, line := range lines {
  1594. trimmed := strings.TrimSpace(line)
  1595. if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "Router ID") || strings.HasSuffix(trimmed, ":") {
  1596. continue
  1597. }
  1598. fields := strings.Fields(trimmed)
  1599. if len(fields) >= 5 {
  1600. rID := fields[0]
  1601. pri := 0
  1602. fmt.Sscanf(fields[1], "%d", &pri)
  1603. rawState := fields[2]
  1604. state := rawState
  1605. if idx := strings.Index(rawState, "/"); idx != -1 {
  1606. state = rawState[:idx]
  1607. }
  1608. dTime := fields[3]
  1609. iface := fields[4]
  1610. ip := rID
  1611. if len(fields) >= 6 {
  1612. ip = fields[5]
  1613. }
  1614. neighbors = append(neighbors, OSPFNeighbor{
  1615. Flags: "D",
  1616. Comment: "",
  1617. Instance: "Default",
  1618. Area: "Default",
  1619. Address: ip,
  1620. State: state,
  1621. StateChanges: 6,
  1622. NeighborID: rID,
  1623. Interface: iface,
  1624. DeadTimer: dTime,
  1625. Priority: pri,
  1626. })
  1627. }
  1628. }
  1629. return neighbors
  1630. }
  1631. func (m *Manager) GetOSPFLSA() []OSPFLSA {
  1632. var lsaList []OSPFLSA
  1633. out, err := birdcExec("show ospf lsadb")
  1634. if err != nil {
  1635. return lsaList
  1636. }
  1637. localRouterID := "192.168.131.56"
  1638. if len(m.config.OSPFInstances) > 0 && m.config.OSPFInstances[0].RouterID != "" {
  1639. localRouterID = m.config.OSPFInstances[0].RouterID
  1640. }
  1641. lines := strings.Split(string(out), "\n")
  1642. currentArea := "Default"
  1643. for _, line := range lines {
  1644. trimmed := strings.TrimSpace(line)
  1645. if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") {
  1646. continue
  1647. }
  1648. if strings.HasPrefix(trimmed, "Area ") {
  1649. currentArea = strings.TrimSpace(strings.TrimPrefix(trimmed, "Area "))
  1650. continue
  1651. } else if strings.EqualFold(trimmed, "Global") {
  1652. currentArea = "Default"
  1653. continue
  1654. }
  1655. if strings.HasPrefix(trimmed, "Type") || strings.HasPrefix(trimmed, "ospf_") {
  1656. continue
  1657. }
  1658. fields := strings.Fields(trimmed)
  1659. if len(fields) >= 5 {
  1660. rawType := fields[0]
  1661. typeName := "router"
  1662. switch rawType {
  1663. case "0001":
  1664. typeName = "router"
  1665. case "0002":
  1666. typeName = "network"
  1667. case "0003":
  1668. typeName = "summary-net"
  1669. case "0004":
  1670. typeName = "summary-asbr"
  1671. case "0005":
  1672. typeName = "as-external"
  1673. default:
  1674. typeName = rawType
  1675. }
  1676. lsID := fields[1]
  1677. router := fields[2]
  1678. seq := fields[3]
  1679. age := 0
  1680. fmt.Sscanf(fields[4], "%d", &age)
  1681. flags := "D"
  1682. if router == localRouterID {
  1683. flags = "SD"
  1684. }
  1685. lsaList = append(lsaList, OSPFLSA{
  1686. Flags: flags,
  1687. Instance: "Default",
  1688. Area: currentArea,
  1689. Type: typeName,
  1690. Originator: router,
  1691. ID: lsID,
  1692. Link: "",
  1693. LinkIndex: 0,
  1694. Sequence: seq,
  1695. Age: age,
  1696. })
  1697. }
  1698. }
  1699. return lsaList
  1700. }
  1701. func (m *Manager) GetOSPFRoutes() []OSPFRoute {
  1702. var routes []OSPFRoute
  1703. out, err := birdcExec("show route where source = RTS_OSPF all")
  1704. if err != nil {
  1705. return routes
  1706. }
  1707. lines := strings.Split(string(out), "\n")
  1708. var cur *OSPFRoute
  1709. for _, line := range lines {
  1710. trimmed := strings.TrimSpace(line)
  1711. if trimmed == "" || strings.HasPrefix(trimmed, "BIRD") || strings.HasPrefix(trimmed, "Table") {
  1712. continue
  1713. }
  1714. if !strings.HasPrefix(line, "\t") && !strings.HasPrefix(line, " ") {
  1715. fields := strings.Fields(trimmed)
  1716. if len(fields) >= 2 {
  1717. cost := 10
  1718. if idx := strings.Index(line, "/"); idx != -1 {
  1719. sub := line[idx+1:]
  1720. if end := strings.Index(sub, ")"); end != -1 {
  1721. cost, _ = strconv.Atoi(sub[:end])
  1722. }
  1723. }
  1724. routes = append(routes, OSPFRoute{
  1725. Prefix: fields[0],
  1726. Cost: cost,
  1727. RouteType: "Intra-Area",
  1728. })
  1729. cur = &routes[len(routes)-1]
  1730. }
  1731. continue
  1732. }
  1733. if cur == nil {
  1734. continue
  1735. }
  1736. if strings.HasPrefix(trimmed, "via ") {
  1737. parts := strings.Fields(trimmed)
  1738. if len(parts) >= 2 {
  1739. cur.NextHop = parts[1]
  1740. }
  1741. if len(parts) >= 4 && parts[2] == "on" {
  1742. cur.Interface = parts[3]
  1743. }
  1744. } else if strings.HasPrefix(trimmed, "Type:") {
  1745. cur.RouteType = strings.TrimSpace(strings.TrimPrefix(trimmed, "Type:"))
  1746. }
  1747. }
  1748. return routes
  1749. }
  1750. // -------------------------------------------------------------
  1751. // Routing Filter API & Operations (/routing filter)
  1752. // -------------------------------------------------------------
  1753. func (m *Manager) ListRoutingFilters() []RoutingFilterRule {
  1754. m.mu.RLock()
  1755. defer m.mu.RUnlock()
  1756. filters := make([]RoutingFilterRule, len(m.config.RoutingFilters))
  1757. copy(filters, m.config.RoutingFilters)
  1758. return filters
  1759. }
  1760. func (m *Manager) SaveRoutingFilter(rule *RoutingFilterRule) error {
  1761. m.mu.Lock()
  1762. if rule.ID == "" {
  1763. rule.ID = fmt.Sprintf("rf-%d", time.Now().UnixNano()/1e6)
  1764. }
  1765. if rule.RuleOrder <= 0 {
  1766. rule.RuleOrder = (len(m.config.RoutingFilters) + 1) * 10
  1767. }
  1768. found := false
  1769. for i, r := range m.config.RoutingFilters {
  1770. if r.ID == rule.ID {
  1771. m.config.RoutingFilters[i] = *rule
  1772. found = true
  1773. break
  1774. }
  1775. }
  1776. if !found {
  1777. m.config.RoutingFilters = append(m.config.RoutingFilters, *rule)
  1778. }
  1779. _ = m.save()
  1780. m.mu.Unlock()
  1781. return m.applyBirdConfig()
  1782. }
  1783. func (m *Manager) DeleteRoutingFilter(id string) error {
  1784. m.mu.Lock()
  1785. newRules := make([]RoutingFilterRule, 0, len(m.config.RoutingFilters))
  1786. for _, r := range m.config.RoutingFilters {
  1787. if r.ID != id {
  1788. newRules = append(newRules, r)
  1789. }
  1790. }
  1791. m.config.RoutingFilters = newRules
  1792. _ = m.save()
  1793. m.mu.Unlock()
  1794. return m.applyBirdConfig()
  1795. }
  1796. func (m *Manager) SetRoutingFilterState(id string, disabled bool) error {
  1797. m.mu.Lock()
  1798. for i := range m.config.RoutingFilters {
  1799. if m.config.RoutingFilters[i].ID == id {
  1800. m.config.RoutingFilters[i].Disabled = disabled
  1801. break
  1802. }
  1803. }
  1804. _ = m.save()
  1805. m.mu.Unlock()
  1806. return m.applyBirdConfig()
  1807. }
  1808. // translateRos7RuleToBird converts a RouterOS v7 rule script (or fallback fields) to valid BIRD 2 syntax
  1809. func translateRos7RuleToBird(r *RoutingFilterRule) string {
  1810. script := strings.TrimSpace(r.Rule)
  1811. if script != "" {
  1812. s := script
  1813. // 0. Convert double-equals (==) to single-equals (=) for BIRD 2, preserving !=, <=, >=, ~=
  1814. reDoubleEq := regexp.MustCompile(`([^!<>~=])\s*==\s*([^=])`)
  1815. for reDoubleEq.MatchString(s) {
  1816. s = reDoubleEq.ReplaceAllString(s, "$1 = $2")
  1817. }
  1818. // 1. Convert dst in A.B.C.D/M{L,H} -> net ~ [ A.B.C.D/M{L,H} ]
  1819. reDstRange := regexp.MustCompile(`dst\s+in\s+([0-9\.\/]+)\{([0-9]+),([0-9]+)\}`)
  1820. s = reDstRange.ReplaceAllString(s, "net ~ [ $1{$2,$3} ]")
  1821. // 2. Convert dst in A.B.C.D/M -> net ~ [ A.B.C.D/M+ ]
  1822. reDstIn := regexp.MustCompile(`dst\s+in\s+([0-9\.\/]+)`)
  1823. s = reDstIn.ReplaceAllString(s, "net ~ [ $1+ ]")
  1824. // 3. Convert dst = A.B.C.D/M or dst == A.B.C.D/M -> net = A.B.C.D/M
  1825. reDstEq := regexp.MustCompile(`\bdst\s*=\s*([0-9\.\/]+)`)
  1826. s = reDstEq.ReplaceAllString(s, "net = $1")
  1827. // 4. Convert dst != A.B.C.D/M -> net != A.B.C.D/M
  1828. reDstNeq := regexp.MustCompile(`\bdst\s*!=\s*([0-9\.\/]+)`)
  1829. s = reDstNeq.ReplaceAllString(s, "net != $1")
  1830. // 5. Convert dst-len -> net.len
  1831. s = regexp.MustCompile(`\bdst-len\b`).ReplaceAllString(s, "net.len")
  1832. // 6. Convert protocol matches
  1833. s = regexp.MustCompile(`protocol\s+bgp\b`).ReplaceAllString(s, "source = RTS_BGP")
  1834. s = regexp.MustCompile(`protocol\s+ospf\b`).ReplaceAllString(s, "source = RTS_OSPF")
  1835. s = regexp.MustCompile(`protocol\s+connected\b`).ReplaceAllString(s, "source = RTS_DEVICE")
  1836. s = regexp.MustCompile(`protocol\s+static\b`).ReplaceAllString(s, "source = RTS_STATIC")
  1837. s = regexp.MustCompile(`protocol\s+rip\b`).ReplaceAllString(s, "source = RTS_RIP")
  1838. // 7. Convert bgp-communities includes A:B -> (A, B) ~ bgp_community
  1839. reCommInc := regexp.MustCompile(`bgp-communities\s+includes\s+([0-9]+):([0-9]+)`)
  1840. s = reCommInc.ReplaceAllString(s, "($1, $2) ~ bgp_community")
  1841. // 8. Convert bgp-communities (in|equal-list) ListName -> bgp_community ~ comm_ListName
  1842. reCommList := regexp.MustCompile(`bgp-communities\s+(?:in|equal-list)\s+([A-Za-z0-9_]+)`)
  1843. s = reCommList.ReplaceAllString(s, "bgp_community ~ comm_$1")
  1844. // 9. Convert bgp-as-path in ListName -> bgp_path.last ~ aslist_ListName
  1845. reASList := regexp.MustCompile(`bgp-as-path\s+in\s+([A-Za-z0-9_]+)`)
  1846. s = reASList.ReplaceAllString(s, "bgp_path.last ~ aslist_$1")
  1847. // 10. Convert bgp-as-path "pattern" -> bgp_path ~ [= * pattern * =]
  1848. reASPattern := regexp.MustCompile(`bgp-as-path\s+"([^"]+)"`)
  1849. s = reASPattern.ReplaceAllStringFunc(s, func(match string) string {
  1850. sub := reASPattern.FindStringSubmatch(match)
  1851. if len(sub) == 2 {
  1852. pat := strings.Trim(sub[1], "^$")
  1853. return fmt.Sprintf("bgp_path ~ [= * %s * =]", pat)
  1854. }
  1855. return match
  1856. })
  1857. // 11. Convert actions:
  1858. reSetLP := regexp.MustCompile(`set\s+bgp-local-pref\s*=?\s*([0-9]+);?`)
  1859. s = reSetLP.ReplaceAllString(s, "bgp_local_pref = $1;")
  1860. reSetMed := regexp.MustCompile(`set\s+bgp-med\s*=?\s*([0-9]+);?`)
  1861. s = reSetMed.ReplaceAllString(s, "bgp_med = $1;")
  1862. reSetDist := regexp.MustCompile(`set\s+distance\s*=?\s*([0-9]+);?`)
  1863. s = reSetDist.ReplaceAllString(s, "preference = $1;")
  1864. reSetGW := regexp.MustCompile(`set\s+gw\s*=?\s*([0-9\.]+);?`)
  1865. s = reSetGW.ReplaceAllString(s, "gw = $1;")
  1866. reSetPrepend := regexp.MustCompile(`set\s+bgp-path-prepend\s*=?\s*([0-9]+);?`)
  1867. s = reSetPrepend.ReplaceAllStringFunc(s, func(match string) string {
  1868. sub := reSetPrepend.FindStringSubmatch(match)
  1869. if len(sub) == 2 {
  1870. n, _ := strconv.Atoi(sub[1])
  1871. if n > 0 && n <= 10 {
  1872. var b strings.Builder
  1873. for i := 0; i < n; i++ {
  1874. b.WriteString("bgp_path.prepend(bgp_path.first); ")
  1875. }
  1876. return b.String()
  1877. } else if n > 10 {
  1878. return fmt.Sprintf("bgp_path.prepend(%d);", n)
  1879. }
  1880. }
  1881. return match
  1882. })
  1883. reSetCommAdd := regexp.MustCompile(`set\s+bgp-communities\.add\s+([0-9]+):([0-9]+);?`)
  1884. s = reSetCommAdd.ReplaceAllString(s, "bgp_community.add(($1, $2));")
  1885. reSetCommDel := regexp.MustCompile(`set\s+bgp-communities\.delete\s+([0-9]+):([0-9]+);?`)
  1886. s = reSetCommDel.ReplaceAllString(s, "bgp_community.delete(($1, $2));")
  1887. // Ensure semicolons on accept and reject
  1888. s = regexp.MustCompile(`\baccept(?:\s*;)?`).ReplaceAllString(s, "accept;")
  1889. s = regexp.MustCompile(`\b(reject|discard)(?:\s*;)?`).ReplaceAllString(s, "reject;")
  1890. // Convert passthrough keyword to no-op
  1891. s = regexp.MustCompile(`\bpassthrough(?:\s*;)?`).ReplaceAllString(s, "")
  1892. // Ensure "then" before opening brace of if statement
  1893. reIfThen := regexp.MustCompile(`\)\s*\{`)
  1894. s = reIfThen.ReplaceAllString(s, ") then {")
  1895. s = strings.ReplaceAll(s, ") then then {", ") then {")
  1896. return s
  1897. }
  1898. // Fallback for rules configured with structured UI form
  1899. var conditions []string
  1900. if r.Prefix != "" {
  1901. pfx := strings.TrimSpace(r.Prefix)
  1902. pfxLen := strings.TrimSpace(r.PrefixLen)
  1903. if pfxLen != "" {
  1904. if strings.Contains(pfxLen, "-") {
  1905. parts := strings.Split(pfxLen, "-")
  1906. if len(parts) == 2 {
  1907. conditions = append(conditions, fmt.Sprintf("net ~ [ %s{%s,%s} ]", pfx, strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])))
  1908. }
  1909. } else {
  1910. conditions = append(conditions, fmt.Sprintf("net ~ [ %s{%s,32} ]", pfx, pfxLen))
  1911. }
  1912. } else {
  1913. conditions = append(conditions, fmt.Sprintf("net ~ [ %s+ ]", pfx))
  1914. }
  1915. }
  1916. if r.MatchProtocol != "" && r.MatchProtocol != "any" {
  1917. switch strings.ToLower(r.MatchProtocol) {
  1918. case "bgp":
  1919. conditions = append(conditions, "source = RTS_BGP")
  1920. case "ospf":
  1921. conditions = append(conditions, "source = RTS_OSPF")
  1922. case "connected":
  1923. conditions = append(conditions, "source = RTS_DEVICE")
  1924. case "static":
  1925. conditions = append(conditions, "source = RTS_STATIC")
  1926. }
  1927. }
  1928. if r.MatchASPath != "" {
  1929. conditions = append(conditions, fmt.Sprintf("bgp_path ~ [= * %s * =]", strings.Trim(strings.TrimSpace(r.MatchASPath), "^$")))
  1930. }
  1931. if r.MatchCommunity != "" {
  1932. comm := strings.TrimSpace(r.MatchCommunity)
  1933. if strings.Contains(comm, ":") {
  1934. parts := strings.Split(comm, ":")
  1935. conditions = append(conditions, fmt.Sprintf("(%s, %s) ~ bgp_community", parts[0], parts[1]))
  1936. }
  1937. }
  1938. condStr := "true"
  1939. if len(conditions) > 0 {
  1940. condStr = strings.Join(conditions, " && ")
  1941. }
  1942. var sb strings.Builder
  1943. sb.WriteString(fmt.Sprintf("if %s then {\n", condStr))
  1944. if r.SetLocalPref > 0 {
  1945. sb.WriteString(fmt.Sprintf(" bgp_local_pref = %d;\n", r.SetLocalPref))
  1946. }
  1947. if r.SetMetric > 0 {
  1948. sb.WriteString(fmt.Sprintf(" bgp_med = %d;\n", r.SetMetric))
  1949. }
  1950. if r.SetASPrepend > 0 {
  1951. for i := 0; i < r.SetASPrepend; i++ {
  1952. sb.WriteString(" bgp_path.prepend(bgp_path.first);\n")
  1953. }
  1954. }
  1955. if r.SetCommunity != "" {
  1956. comm := strings.TrimSpace(r.SetCommunity)
  1957. if strings.Contains(comm, ":") {
  1958. parts := strings.Split(comm, ":")
  1959. sb.WriteString(fmt.Sprintf(" bgp_community.add((%s, %s));\n", parts[0], parts[1]))
  1960. }
  1961. }
  1962. if r.SetNextHop != "" {
  1963. sb.WriteString(fmt.Sprintf(" bgp_next_hop = %s;\n", strings.TrimSpace(r.SetNextHop)))
  1964. }
  1965. if r.SetDistance > 0 {
  1966. sb.WriteString(fmt.Sprintf(" preference = %d;\n", r.SetDistance))
  1967. }
  1968. if r.Action == "discard" || r.Action == "reject" {
  1969. sb.WriteString(" reject;\n")
  1970. } else if r.Action == "passthrough" {
  1971. // Passthrough: do not emit terminating accept or reject
  1972. } else {
  1973. sb.WriteString(" accept;\n")
  1974. }
  1975. sb.WriteString("}")
  1976. return sb.String()
  1977. }
  1978. // -------------------------------------------------------------
  1979. // Community Lists (/routing filter community-list)
  1980. // -------------------------------------------------------------
  1981. func (m *Manager) ListCommunityLists() []RoutingFilterCommunityList {
  1982. m.mu.RLock()
  1983. defer m.mu.RUnlock()
  1984. res := make([]RoutingFilterCommunityList, len(m.config.RoutingCommunityLists))
  1985. copy(res, m.config.RoutingCommunityLists)
  1986. return res
  1987. }
  1988. func (m *Manager) SaveCommunityList(cl *RoutingFilterCommunityList) error {
  1989. m.mu.Lock()
  1990. if cl.ID == "" {
  1991. cl.ID = fmt.Sprintf("cl-%d", time.Now().UnixNano()/1e6)
  1992. }
  1993. found := false
  1994. for i, r := range m.config.RoutingCommunityLists {
  1995. if r.ID == cl.ID {
  1996. m.config.RoutingCommunityLists[i] = *cl
  1997. found = true
  1998. break
  1999. }
  2000. }
  2001. if !found {
  2002. m.config.RoutingCommunityLists = append(m.config.RoutingCommunityLists, *cl)
  2003. }
  2004. _ = m.save()
  2005. m.mu.Unlock()
  2006. return m.applyBirdConfig()
  2007. }
  2008. func (m *Manager) DeleteCommunityList(id string) error {
  2009. m.mu.Lock()
  2010. newLists := make([]RoutingFilterCommunityList, 0, len(m.config.RoutingCommunityLists))
  2011. for _, r := range m.config.RoutingCommunityLists {
  2012. if r.ID != id {
  2013. newLists = append(newLists, r)
  2014. }
  2015. }
  2016. m.config.RoutingCommunityLists = newLists
  2017. _ = m.save()
  2018. m.mu.Unlock()
  2019. return m.applyBirdConfig()
  2020. }
  2021. func (m *Manager) SetCommunityListState(id string, disabled bool) error {
  2022. m.mu.Lock()
  2023. for i := range m.config.RoutingCommunityLists {
  2024. if m.config.RoutingCommunityLists[i].ID == id {
  2025. m.config.RoutingCommunityLists[i].Disabled = disabled
  2026. break
  2027. }
  2028. }
  2029. _ = m.save()
  2030. m.mu.Unlock()
  2031. return m.applyBirdConfig()
  2032. }
  2033. // -------------------------------------------------------------
  2034. // AS Lists (/routing filter num-list)
  2035. // -------------------------------------------------------------
  2036. func (m *Manager) ListASLists() []RoutingFilterASList {
  2037. m.mu.RLock()
  2038. defer m.mu.RUnlock()
  2039. res := make([]RoutingFilterASList, len(m.config.RoutingASLists))
  2040. copy(res, m.config.RoutingASLists)
  2041. return res
  2042. }
  2043. func (m *Manager) SaveASList(al *RoutingFilterASList) error {
  2044. m.mu.Lock()
  2045. if al.ID == "" {
  2046. al.ID = fmt.Sprintf("al-%d", time.Now().UnixNano()/1e6)
  2047. }
  2048. found := false
  2049. for i, r := range m.config.RoutingASLists {
  2050. if r.ID == al.ID {
  2051. m.config.RoutingASLists[i] = *al
  2052. found = true
  2053. break
  2054. }
  2055. }
  2056. if !found {
  2057. m.config.RoutingASLists = append(m.config.RoutingASLists, *al)
  2058. }
  2059. _ = m.save()
  2060. m.mu.Unlock()
  2061. return m.applyBirdConfig()
  2062. }
  2063. func (m *Manager) DeleteASList(id string) error {
  2064. m.mu.Lock()
  2065. newLists := make([]RoutingFilterASList, 0, len(m.config.RoutingASLists))
  2066. for _, r := range m.config.RoutingASLists {
  2067. if r.ID != id {
  2068. newLists = append(newLists, r)
  2069. }
  2070. }
  2071. m.config.RoutingASLists = newLists
  2072. _ = m.save()
  2073. m.mu.Unlock()
  2074. return m.applyBirdConfig()
  2075. }
  2076. func (m *Manager) SetASListState(id string, disabled bool) error {
  2077. m.mu.Lock()
  2078. for i := range m.config.RoutingASLists {
  2079. if m.config.RoutingASLists[i].ID == id {
  2080. m.config.RoutingASLists[i].Disabled = disabled
  2081. break
  2082. }
  2083. }
  2084. _ = m.save()
  2085. m.mu.Unlock()
  2086. return m.applyBirdConfig()
  2087. }
  2088. // -------------------------------------------------------------
  2089. // Summary Telemetry
  2090. // -------------------------------------------------------------
  2091. func (m *Manager) GetSummary() DynamicRoutingSummary {
  2092. m.mu.RLock()
  2093. defer m.mu.RUnlock()
  2094. summary := DynamicRoutingSummary{
  2095. BGPEnabled: m.config.BGPInstance.Enabled,
  2096. OSPFEnabled: m.config.OSPFInstance.Enabled,
  2097. BGPPeerCount: len(m.config.BGPPeers),
  2098. RoutingFilterCount: len(m.config.RoutingFilters),
  2099. }
  2100. out, err := birdcExec("show status")
  2101. birdRunning := err == nil && strings.Contains(string(out), "Daemon is up and running")
  2102. if birdRunning && summary.BGPEnabled {
  2103. summary.BGPRunning = true
  2104. peers := m.ListBGPPeers()
  2105. est := 0
  2106. for _, p := range peers {
  2107. if strings.EqualFold(p.State, "Established") || strings.EqualFold(p.State, "Up") {
  2108. est++
  2109. }
  2110. }
  2111. summary.BGPEstablishedCount = est
  2112. }
  2113. if birdRunning && summary.OSPFEnabled {
  2114. summary.OSPFRunning = true
  2115. nbrs := m.GetOSPFNeighbors()
  2116. summary.OSPFNeighborCount = len(nbrs)
  2117. adj := 0
  2118. for _, n := range nbrs {
  2119. if strings.Contains(strings.ToLower(n.State), "full") {
  2120. adj++
  2121. }
  2122. }
  2123. summary.OSPFAdjacentCount = adj
  2124. }
  2125. return summary
  2126. }