manager.go 67 KB

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