manager.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. package sdwan
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "sort"
  8. "strings"
  9. "sync"
  10. "time"
  11. )
  12. const DefaultStorePath = "/etc/debtik/sdwan.json"
  13. // Global resolver registered for firewall integration
  14. var GlobalManager *Manager
  15. // ResolvedRouteNexthop represents a target egress interface/gateway for routing.
  16. type ResolvedRouteNexthop struct {
  17. Gateway string `json:"gateway"`
  18. Interface string `json:"interface"`
  19. Weight int `json:"weight"`
  20. }
  21. var (
  22. stateChangeCallbacks []func()
  23. stateChangeMu sync.Mutex
  24. )
  25. // RegisterStateChangeCallback registers a function to be invoked when SD-WAN link health or topology changes.
  26. func RegisterStateChangeCallback(cb func()) {
  27. if cb == nil {
  28. return
  29. }
  30. stateChangeMu.Lock()
  31. defer stateChangeMu.Unlock()
  32. stateChangeCallbacks = append(stateChangeCallbacks, cb)
  33. }
  34. // NotifyStateChange invokes all registered state change callbacks.
  35. func NotifyStateChange() {
  36. stateChangeMu.Lock()
  37. cbs := make([]func(), len(stateChangeCallbacks))
  38. copy(cbs, stateChangeCallbacks)
  39. stateChangeMu.Unlock()
  40. for _, cb := range cbs {
  41. go cb()
  42. }
  43. }
  44. // Manager coordinates SD-WAN group configurations, kernel routing, and SLA health monitoring.
  45. type Manager struct {
  46. mu sync.RWMutex
  47. storePath string
  48. groups map[string]*SDWANGroup
  49. engine *KernelEngine
  50. monitor *HealthMonitor
  51. }
  52. // NewManager creates an SD-WAN manager with persistent storage.
  53. func NewManager(customStore ...string) *Manager {
  54. store := DefaultStorePath
  55. if len(customStore) > 0 && customStore[0] != "" {
  56. store = customStore[0]
  57. }
  58. mgr := &Manager{
  59. storePath: store,
  60. groups: make(map[string]*SDWANGroup),
  61. engine: NewKernelEngine(),
  62. }
  63. mgr.monitor = NewHealthMonitor(func() {
  64. mgr.SyncAll()
  65. NotifyStateChange()
  66. })
  67. mgr.load()
  68. mgr.SyncAll()
  69. mgr.monitor.applyResults = mgr.applyProbeResults
  70. mgr.monitor.Start(func() []*SDWANGroup {
  71. return mgr.List()
  72. })
  73. GlobalManager = mgr
  74. return mgr
  75. }
  76. func (m *Manager) Stop() {
  77. if m.monitor != nil {
  78. m.monitor.Stop()
  79. }
  80. if m.engine != nil {
  81. m.engine.mu.Lock()
  82. for dev := range m.engine.virtualDevices {
  83. m.engine.removeVirtualDevice(dev)
  84. }
  85. m.engine.virtualDevices = make(map[string]bool)
  86. m.engine.cleanupMangle()
  87. m.engine.cleanupIPRules()
  88. m.engine.mu.Unlock()
  89. }
  90. }
  91. // Has checks if an SD-WAN group exists by ID or name.
  92. func (m *Manager) Has(idOrName string) bool {
  93. _, found := m.Get(idOrName)
  94. return found
  95. }
  96. func (m *Manager) load() {
  97. m.mu.Lock()
  98. defer m.mu.Unlock()
  99. data, err := os.ReadFile(m.storePath)
  100. if err != nil {
  101. return
  102. }
  103. var list []*SDWANGroup
  104. if err := json.Unmarshal(data, &list); err == nil {
  105. for _, g := range list {
  106. m.groups[g.ID] = g
  107. }
  108. }
  109. }
  110. func (m *Manager) save() error {
  111. _ = os.MkdirAll(filepath.Dir(m.storePath), 0755)
  112. list := make([]*SDWANGroup, 0, len(m.groups))
  113. for _, g := range m.groups {
  114. list = append(list, g)
  115. }
  116. data, err := json.MarshalIndent(list, "", " ")
  117. if err != nil {
  118. return err
  119. }
  120. return os.WriteFile(m.storePath, data, 0644)
  121. }
  122. func (m *Manager) listLocked() []*SDWANGroup {
  123. list := make([]*SDWANGroup, 0, len(m.groups))
  124. for _, g := range m.groups {
  125. list = append(list, g)
  126. }
  127. sort.Slice(list, func(i, j int) bool {
  128. return list[i].Name < list[j].Name
  129. })
  130. return list
  131. }
  132. func copyGroup(g *SDWANGroup) *SDWANGroup {
  133. if g == nil {
  134. return nil
  135. }
  136. copy := *g
  137. copy.Members = make([]*SDWANMember, len(g.Members))
  138. for i, member := range g.Members {
  139. if member != nil {
  140. v := *member
  141. copy.Members[i] = &v
  142. }
  143. }
  144. return &copy
  145. }
  146. // applyProbeResults merges telemetry only if the tested configuration is still current.
  147. func (m *Manager) applyProbeResults(groups []*SDWANGroup) bool {
  148. m.mu.Lock()
  149. defer m.mu.Unlock()
  150. changed := false
  151. for _, result := range groups {
  152. live := m.groups[result.ID]
  153. if live == nil || live.Disabled != result.Disabled {
  154. continue
  155. }
  156. for _, observed := range result.Members {
  157. if observed == nil {
  158. continue
  159. }
  160. for _, member := range live.Members {
  161. if member == nil || member.ID != observed.ID || member.Interface != observed.Interface || member.TargetProbe != observed.TargetProbe || member.Disabled != observed.Disabled || member.MaxLatency != observed.MaxLatency || member.MaxLoss != observed.MaxLoss {
  162. continue
  163. }
  164. if (member.Status == StatusDown) != (observed.Status == StatusDown) {
  165. changed = true
  166. }
  167. member.Status, member.LiveLatency, member.PacketLoss, member.Jitter = observed.Status, observed.LiveLatency, observed.PacketLoss, observed.Jitter
  168. member.LastChecked, member.ConsecutiveFails, member.ConsecutiveSuccesses = observed.LastChecked, observed.ConsecutiveFails, observed.ConsecutiveSuccesses
  169. break
  170. }
  171. }
  172. }
  173. return changed
  174. }
  175. // List returns independent snapshots of all configured SD-WAN groups.
  176. func (m *Manager) List() []*SDWANGroup {
  177. m.mu.RLock()
  178. defer m.mu.RUnlock()
  179. groups := m.listLocked()
  180. for i, group := range groups {
  181. groups[i] = copyGroup(group)
  182. }
  183. return groups
  184. }
  185. // Get returns an SD-WAN group by ID or group name.
  186. func (m *Manager) Get(idOrName string) (*SDWANGroup, bool) {
  187. m.mu.RLock()
  188. defer m.mu.RUnlock()
  189. if g, ok := m.groups[idOrName]; ok {
  190. return copyGroup(g), true
  191. }
  192. for _, g := range m.groups {
  193. if strings.EqualFold(g.Name, idOrName) {
  194. return copyGroup(g), true
  195. }
  196. }
  197. return nil, false
  198. }
  199. // Save validates, stores, and synchronizes an SD-WAN group.
  200. func (m *Manager) Save(g *SDWANGroup) error {
  201. m.mu.Lock()
  202. defer m.mu.Unlock()
  203. if g.Name == "" {
  204. g.Name = fmt.Sprintf("sdwan%d", len(m.groups))
  205. }
  206. if g.ID == "" {
  207. g.ID = fmt.Sprintf("sdwan-%d", time.Now().UnixNano())
  208. }
  209. if g.Mode == "" {
  210. g.Mode = ModeWeighted
  211. }
  212. if g.ProbeInterval <= 0 {
  213. g.ProbeInterval = 3
  214. }
  215. // Validate name collisions
  216. for _, existing := range m.groups {
  217. if existing.ID != g.ID && strings.EqualFold(existing.Name, g.Name) {
  218. return fmt.Errorf("sd-wan group with name %q already exists", g.Name)
  219. }
  220. }
  221. // Preserve live metrics while applying group settings; callers may hold stale snapshots.
  222. if existing, ok := m.groups[g.ID]; ok {
  223. if g.Members == nil && existing.Members != nil {
  224. g.Members = copyGroup(existing).Members
  225. } else {
  226. for _, member := range g.Members {
  227. if member == nil {
  228. return fmt.Errorf("sd-wan member must not be null")
  229. }
  230. for _, old := range existing.Members {
  231. if old != nil && ((member.ID != "" && member.ID == old.ID) || (member.ID == "" && member.Interface == old.Interface)) {
  232. member.Status, member.LiveLatency, member.PacketLoss, member.Jitter = old.Status, old.LiveLatency, old.PacketLoss, old.Jitter
  233. member.LastChecked, member.ConsecutiveFails, member.ConsecutiveSuccesses = old.LastChecked, old.ConsecutiveFails, old.ConsecutiveSuccesses
  234. member.TableID, member.MarkHex, member.MarkInt = old.TableID, old.MarkHex, old.MarkInt
  235. break
  236. }
  237. }
  238. }
  239. }
  240. }
  241. // Ensure member IDs and initial statuses
  242. for _, mem := range g.Members {
  243. if mem == nil {
  244. return fmt.Errorf("sd-wan member must not be null")
  245. }
  246. if mem.ID == "" {
  247. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  248. }
  249. if mem.Weight <= 0 {
  250. mem.Weight = 50
  251. }
  252. if mem.Priority <= 0 {
  253. mem.Priority = 1
  254. }
  255. if mem.Status == "" {
  256. mem.Status = StatusHealthy
  257. }
  258. }
  259. m.groups[g.ID] = copyGroup(g)
  260. if err := m.save(); err != nil {
  261. return err
  262. }
  263. return m.syncLocked()
  264. }
  265. func (m *Manager) syncLocked() error {
  266. err := m.engine.Sync(m.listLocked())
  267. _ = m.save()
  268. NotifyStateChange()
  269. return err
  270. }
  271. // SetState enables or disables an SD-WAN group.
  272. func (m *Manager) SetState(idOrName string, disabled bool) error {
  273. m.mu.Lock()
  274. defer m.mu.Unlock()
  275. g := m.findLocked(idOrName)
  276. if g == nil {
  277. return fmt.Errorf("sd-wan group %q not found", idOrName)
  278. }
  279. g.Disabled = disabled
  280. if err := m.save(); err != nil {
  281. return err
  282. }
  283. return m.syncLocked()
  284. }
  285. // Delete removes an SD-WAN group.
  286. func (m *Manager) Delete(idOrName string) error {
  287. m.mu.Lock()
  288. defer m.mu.Unlock()
  289. g := m.findLocked(idOrName)
  290. if g == nil {
  291. return fmt.Errorf("sd-wan group %q not found", idOrName)
  292. }
  293. delete(m.groups, g.ID)
  294. if err := m.save(); err != nil {
  295. return err
  296. }
  297. return m.syncLocked()
  298. }
  299. // AddMember adds an interface member to a group.
  300. func (m *Manager) AddMember(groupIDOrName string, mem *SDWANMember) error {
  301. m.mu.Lock()
  302. defer m.mu.Unlock()
  303. g := m.findLocked(groupIDOrName)
  304. if g == nil {
  305. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  306. }
  307. if mem.Interface == "" {
  308. return fmt.Errorf("member interface is required")
  309. }
  310. // Check duplicates
  311. for _, existing := range g.Members {
  312. if existing.Interface == mem.Interface {
  313. return fmt.Errorf("interface %s already a member of %s", mem.Interface, g.Name)
  314. }
  315. }
  316. if mem.ID == "" {
  317. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  318. }
  319. if mem.Weight <= 0 {
  320. mem.Weight = 50
  321. }
  322. if mem.Priority <= 0 {
  323. mem.Priority = 1
  324. }
  325. if mem.TargetProbe == "" {
  326. mem.TargetProbe = "1.1.1.1"
  327. }
  328. mem.Status = StatusHealthy
  329. memberCopy := *mem
  330. g.Members = append(g.Members, &memberCopy)
  331. if err := m.save(); err != nil {
  332. return err
  333. }
  334. return m.syncLocked()
  335. }
  336. // UpdateMember modifies a member's configuration.
  337. func (m *Manager) UpdateMember(groupIDOrName string, mem *SDWANMember) error {
  338. m.mu.Lock()
  339. defer m.mu.Unlock()
  340. g := m.findLocked(groupIDOrName)
  341. if g == nil {
  342. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  343. }
  344. idx := -1
  345. for i, existing := range g.Members {
  346. if (mem.ID != "" && existing.ID == mem.ID) || (mem.Interface != "" && existing.Interface == mem.Interface) {
  347. idx = i
  348. break
  349. }
  350. }
  351. if idx == -1 {
  352. return fmt.Errorf("member %s not found in group %s", mem.Interface, g.Name)
  353. }
  354. // Preserve existing ID & Interface if not specified
  355. if mem.ID == "" {
  356. mem.ID = g.Members[idx].ID
  357. }
  358. if mem.Interface == "" {
  359. mem.Interface = g.Members[idx].Interface
  360. }
  361. if mem.Weight <= 0 {
  362. mem.Weight = g.Members[idx].Weight
  363. }
  364. if mem.Priority <= 0 {
  365. mem.Priority = g.Members[idx].Priority
  366. }
  367. if mem.TargetProbe == "" {
  368. mem.TargetProbe = g.Members[idx].TargetProbe
  369. }
  370. if mem.MaxLatency <= 0 {
  371. mem.MaxLatency = g.Members[idx].MaxLatency
  372. }
  373. if mem.MaxLoss <= 0 {
  374. mem.MaxLoss = g.Members[idx].MaxLoss
  375. }
  376. // Preserve live telemetry
  377. mem.Status = g.Members[idx].Status
  378. if mem.Status == "" {
  379. mem.Status = StatusHealthy
  380. }
  381. mem.LiveLatency = g.Members[idx].LiveLatency
  382. mem.PacketLoss = g.Members[idx].PacketLoss
  383. mem.Jitter = g.Members[idx].Jitter
  384. mem.ConsecutiveFails = g.Members[idx].ConsecutiveFails
  385. mem.ConsecutiveSuccesses = g.Members[idx].ConsecutiveSuccesses
  386. mem.LastChecked = g.Members[idx].LastChecked
  387. mem.TableID, mem.MarkHex, mem.MarkInt = g.Members[idx].TableID, g.Members[idx].MarkHex, g.Members[idx].MarkInt
  388. memberCopy := *mem
  389. g.Members[idx] = &memberCopy
  390. if err := m.save(); err != nil {
  391. return err
  392. }
  393. return m.syncLocked()
  394. }
  395. // DeleteMember removes a member from a group.
  396. func (m *Manager) DeleteMember(groupIDOrName, memberIDOrIface string) error {
  397. m.mu.Lock()
  398. defer m.mu.Unlock()
  399. g := m.findLocked(groupIDOrName)
  400. if g == nil {
  401. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  402. }
  403. idx := -1
  404. for i, mem := range g.Members {
  405. if mem.ID == memberIDOrIface || mem.Interface == memberIDOrIface {
  406. idx = i
  407. break
  408. }
  409. }
  410. if idx == -1 {
  411. return fmt.Errorf("member %s not found in group %s", memberIDOrIface, g.Name)
  412. }
  413. g.Members = append(g.Members[:idx], g.Members[idx+1:]...)
  414. if err := m.save(); err != nil {
  415. return err
  416. }
  417. return m.syncLocked()
  418. }
  419. // ResolveGroupInterfaces resolves an SD-WAN group to its member interfaces (used by Firewall).
  420. func (m *Manager) ResolveGroupInterfaces(groupName string) []string {
  421. m.mu.RLock()
  422. defer m.mu.RUnlock()
  423. var res []string
  424. for _, g := range m.groups {
  425. if strings.EqualFold(g.Name, groupName) && !g.Disabled {
  426. for _, mem := range g.Members {
  427. if !mem.Disabled && mem.Interface != "" {
  428. res = append(res, mem.Interface)
  429. }
  430. }
  431. break
  432. }
  433. }
  434. sort.Strings(res)
  435. return res
  436. }
  437. // ResolveAllSDWANGroups returns a mapping of all group names to their member interfaces.
  438. func (m *Manager) ResolveAllSDWANGroups() map[string][]string {
  439. m.mu.RLock()
  440. defer m.mu.RUnlock()
  441. res := make(map[string][]string)
  442. for _, g := range m.groups {
  443. if !g.Disabled {
  444. var members []string
  445. for _, mem := range g.Members {
  446. if !mem.Disabled && mem.Interface != "" {
  447. members = append(members, mem.Interface)
  448. }
  449. }
  450. sort.Strings(members)
  451. res[g.Name] = members
  452. }
  453. }
  454. return res
  455. }
  456. // GetSummary returns high-level telemetry across all SD-WAN groups.
  457. func (m *Manager) GetSummary() TelemetrySummary {
  458. return m.GetTelemetrySummary()
  459. }
  460. // GetTelemetrySummary returns high-level telemetry across all SD-WAN groups.
  461. func (m *Manager) GetTelemetrySummary() TelemetrySummary {
  462. m.mu.RLock()
  463. defer m.mu.RUnlock()
  464. sum := TelemetrySummary{
  465. TotalGroups: len(m.groups),
  466. }
  467. for _, g := range m.groups {
  468. if !g.Disabled {
  469. sum.ActiveGroups++
  470. }
  471. for _, mem := range g.Members {
  472. sum.TotalMembers++
  473. switch mem.Status {
  474. case StatusHealthy:
  475. sum.HealthyMembers++
  476. case StatusDegraded:
  477. sum.DegradedMembers++
  478. case StatusDown:
  479. sum.DownMembers++
  480. }
  481. }
  482. }
  483. return sum
  484. }
  485. // SyncAll reconciles kernel routing and iptables for all groups.
  486. func (m *Manager) SyncAll() {
  487. m.mu.Lock()
  488. defer m.mu.Unlock()
  489. _ = m.engine.Sync(m.listLocked())
  490. }
  491. func (m *Manager) findLocked(idOrName string) *SDWANGroup {
  492. if g, ok := m.groups[idOrName]; ok {
  493. return g
  494. }
  495. for _, g := range m.groups {
  496. if strings.EqualFold(g.Name, idOrName) {
  497. return g
  498. }
  499. }
  500. return nil
  501. }
  502. // RenameInterface updates SD-WAN member references when an interface is renamed.
  503. func (m *Manager) RenameInterface(oldName, newName string) {
  504. m.mu.Lock()
  505. defer m.mu.Unlock()
  506. modified := false
  507. for _, g := range m.groups {
  508. for _, mem := range g.Members {
  509. if mem.Interface == oldName {
  510. mem.Interface = newName
  511. modified = true
  512. }
  513. }
  514. }
  515. if modified {
  516. _ = m.save()
  517. _ = m.engine.Sync(m.listLocked())
  518. NotifyStateChange()
  519. }
  520. }
  521. // ResolveGroupForRouting resolves an SD-WAN group name or ID to active egress nexthops for kernel routing.
  522. func (m *Manager) ResolveGroupForRouting(groupNameOrID string) ([]ResolvedRouteNexthop, bool) {
  523. m.mu.RLock()
  524. defer m.mu.RUnlock()
  525. g := m.findLocked(groupNameOrID)
  526. if g == nil || g.Disabled || len(g.Members) == 0 {
  527. return nil, false
  528. }
  529. var candidates []*SDWANMember
  530. for _, mem := range g.Members {
  531. if !mem.Disabled && mem.Interface != "" {
  532. candidates = append(candidates, mem)
  533. }
  534. }
  535. if len(candidates) == 0 {
  536. return nil, false
  537. }
  538. var online []*SDWANMember
  539. for _, mem := range candidates {
  540. if mem.Status != StatusDown {
  541. online = append(online, mem)
  542. }
  543. }
  544. // Fallback to all enabled candidates if all are down so traffic isn't completely blackholed
  545. if len(online) == 0 {
  546. online = candidates
  547. }
  548. switch g.Mode {
  549. case ModeFailover:
  550. // Sort by priority (1 is highest priority)
  551. sort.Slice(online, func(i, j int) bool {
  552. p1, p2 := online[i].Priority, online[j].Priority
  553. if p1 <= 0 {
  554. p1 = 100
  555. }
  556. if p2 <= 0 {
  557. p2 = 100
  558. }
  559. return p1 < p2
  560. })
  561. primary := online[0]
  562. gw := primary.Gateway
  563. if gw == "" {
  564. gw = m.engine.detectInterfaceGateway(primary.Interface)
  565. }
  566. return []ResolvedRouteNexthop{{
  567. Gateway: gw,
  568. Interface: primary.Interface,
  569. Weight: 1,
  570. }}, true
  571. case ModeLowestLatency:
  572. best := online[0]
  573. for _, mem := range online[1:] {
  574. if mem.LiveLatency > 0 && (best.LiveLatency <= 0 || mem.LiveLatency < best.LiveLatency) {
  575. best = mem
  576. }
  577. }
  578. gw := best.Gateway
  579. if gw == "" {
  580. gw = m.engine.detectInterfaceGateway(best.Interface)
  581. }
  582. return []ResolvedRouteNexthop{{
  583. Gateway: gw,
  584. Interface: best.Interface,
  585. Weight: 1,
  586. }}, true
  587. case ModeLowestLoss:
  588. best := online[0]
  589. for _, mem := range online[1:] {
  590. if mem.PacketLoss < best.PacketLoss {
  591. best = mem
  592. }
  593. }
  594. gw := best.Gateway
  595. if gw == "" {
  596. gw = m.engine.detectInterfaceGateway(best.Interface)
  597. }
  598. return []ResolvedRouteNexthop{{
  599. Gateway: gw,
  600. Interface: best.Interface,
  601. Weight: 1,
  602. }}, true
  603. case ModeWeighted, ModePCC:
  604. fallthrough
  605. default:
  606. var res []ResolvedRouteNexthop
  607. for _, mem := range online {
  608. gw := mem.Gateway
  609. if gw == "" {
  610. gw = m.engine.detectInterfaceGateway(mem.Interface)
  611. }
  612. w := mem.Weight
  613. if w <= 0 {
  614. w = 1
  615. }
  616. res = append(res, ResolvedRouteNexthop{
  617. Gateway: gw,
  618. Interface: mem.Interface,
  619. Weight: w,
  620. })
  621. }
  622. return res, true
  623. }
  624. }
  625. // ResolveGroupForRouting resolves an SD-WAN group name or ID via GlobalManager or file fallback.
  626. func ResolveGroupForRouting(groupNameOrID string) ([]ResolvedRouteNexthop, bool) {
  627. if GlobalManager != nil {
  628. return GlobalManager.ResolveGroupForRouting(groupNameOrID)
  629. }
  630. // During startup use the same mode and health selection as the live manager.
  631. return resolveGroupFromFile(DefaultStorePath, groupNameOrID)
  632. }
  633. func resolveGroupFromFile(path, groupNameOrID string) ([]ResolvedRouteNexthop, bool) {
  634. data, err := os.ReadFile(path)
  635. if err != nil {
  636. return nil, false
  637. }
  638. var groups []*SDWANGroup
  639. if err := json.Unmarshal(data, &groups); err != nil {
  640. return nil, false
  641. }
  642. mgr := &Manager{groups: make(map[string]*SDWANGroup), engine: NewKernelEngine()}
  643. for _, group := range groups {
  644. if group != nil {
  645. mgr.groups[group.ID] = group
  646. }
  647. }
  648. return mgr.ResolveGroupForRouting(groupNameOrID)
  649. }
  650. // IsSDWANGroup reports whether the given name corresponds to an active or configured SD-WAN group.
  651. func IsSDWANGroup(name string) bool {
  652. clean := strings.TrimSpace(name)
  653. if clean == "" {
  654. return false
  655. }
  656. if GlobalManager != nil {
  657. return GlobalManager.Has(clean)
  658. }
  659. data, err := os.ReadFile(DefaultStorePath)
  660. if err != nil {
  661. return false
  662. }
  663. var groups []*SDWANGroup
  664. if err := json.Unmarshal(data, &groups); err != nil {
  665. return false
  666. }
  667. for _, g := range groups {
  668. if strings.EqualFold(g.Name, clean) || g.ID == clean {
  669. return true
  670. }
  671. }
  672. return false
  673. }