manager.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. // Manager coordinates SD-WAN group configurations, kernel routing, and SLA health monitoring.
  16. type Manager struct {
  17. mu sync.RWMutex
  18. storePath string
  19. groups map[string]*SDWANGroup
  20. engine *KernelEngine
  21. monitor *HealthMonitor
  22. }
  23. // NewManager creates an SD-WAN manager with persistent storage.
  24. func NewManager(customStore ...string) *Manager {
  25. store := DefaultStorePath
  26. if len(customStore) > 0 && customStore[0] != "" {
  27. store = customStore[0]
  28. }
  29. mgr := &Manager{
  30. storePath: store,
  31. groups: make(map[string]*SDWANGroup),
  32. engine: NewKernelEngine(),
  33. }
  34. mgr.monitor = NewHealthMonitor(func() {
  35. mgr.mu.RLock()
  36. groups := mgr.listLocked()
  37. mgr.mu.RUnlock()
  38. _ = mgr.engine.Sync(groups)
  39. })
  40. mgr.load()
  41. mgr.SyncAll()
  42. mgr.monitor.Start(func() []*SDWANGroup {
  43. return mgr.List()
  44. })
  45. GlobalManager = mgr
  46. return mgr
  47. }
  48. func (m *Manager) Stop() {
  49. if m.monitor != nil {
  50. m.monitor.Stop()
  51. }
  52. }
  53. func (m *Manager) load() {
  54. m.mu.Lock()
  55. defer m.mu.Unlock()
  56. data, err := os.ReadFile(m.storePath)
  57. if err != nil {
  58. return
  59. }
  60. var list []*SDWANGroup
  61. if err := json.Unmarshal(data, &list); err == nil {
  62. for _, g := range list {
  63. m.groups[g.ID] = g
  64. }
  65. }
  66. }
  67. func (m *Manager) save() error {
  68. _ = os.MkdirAll(filepath.Dir(m.storePath), 0755)
  69. list := make([]*SDWANGroup, 0, len(m.groups))
  70. for _, g := range m.groups {
  71. list = append(list, g)
  72. }
  73. data, err := json.MarshalIndent(list, "", " ")
  74. if err != nil {
  75. return err
  76. }
  77. return os.WriteFile(m.storePath, data, 0644)
  78. }
  79. func (m *Manager) listLocked() []*SDWANGroup {
  80. list := make([]*SDWANGroup, 0, len(m.groups))
  81. for _, g := range m.groups {
  82. list = append(list, g)
  83. }
  84. sort.Slice(list, func(i, j int) bool {
  85. return list[i].Name < list[j].Name
  86. })
  87. return list
  88. }
  89. // List returns a sorted copy of all configured SD-WAN groups.
  90. func (m *Manager) List() []*SDWANGroup {
  91. m.mu.RLock()
  92. defer m.mu.RUnlock()
  93. return m.listLocked()
  94. }
  95. // Get returns an SD-WAN group by ID or group name.
  96. func (m *Manager) Get(idOrName string) (*SDWANGroup, bool) {
  97. m.mu.RLock()
  98. defer m.mu.RUnlock()
  99. if g, ok := m.groups[idOrName]; ok {
  100. return g, true
  101. }
  102. for _, g := range m.groups {
  103. if strings.EqualFold(g.Name, idOrName) {
  104. return g, true
  105. }
  106. }
  107. return nil, false
  108. }
  109. // Save validates, stores, and synchronizes an SD-WAN group.
  110. func (m *Manager) Save(g *SDWANGroup) error {
  111. m.mu.Lock()
  112. defer m.mu.Unlock()
  113. if g.Name == "" {
  114. g.Name = fmt.Sprintf("sdwan%d", len(m.groups))
  115. }
  116. if g.ID == "" {
  117. g.ID = fmt.Sprintf("sdwan-%d", time.Now().UnixNano())
  118. }
  119. if g.Mode == "" {
  120. g.Mode = ModeWeighted
  121. }
  122. if g.ProbeInterval <= 0 {
  123. g.ProbeInterval = 3
  124. }
  125. // Validate name collisions
  126. for _, existing := range m.groups {
  127. if existing.ID != g.ID && strings.EqualFold(existing.Name, g.Name) {
  128. return fmt.Errorf("sd-wan group with name %q already exists", g.Name)
  129. }
  130. }
  131. // Ensure member IDs and initial statuses
  132. for _, mem := range g.Members {
  133. if mem.ID == "" {
  134. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  135. }
  136. if mem.Weight <= 0 {
  137. mem.Weight = 50
  138. }
  139. if mem.Priority <= 0 {
  140. mem.Priority = 1
  141. }
  142. if mem.Status == "" {
  143. mem.Status = StatusHealthy
  144. }
  145. }
  146. m.groups[g.ID] = g
  147. if err := m.save(); err != nil {
  148. return err
  149. }
  150. return m.engine.Sync(m.listLocked())
  151. }
  152. // SetState enables or disables an SD-WAN group.
  153. func (m *Manager) SetState(idOrName string, disabled bool) error {
  154. m.mu.Lock()
  155. defer m.mu.Unlock()
  156. g := m.findLocked(idOrName)
  157. if g == nil {
  158. return fmt.Errorf("sd-wan group %q not found", idOrName)
  159. }
  160. g.Disabled = disabled
  161. if err := m.save(); err != nil {
  162. return err
  163. }
  164. return m.engine.Sync(m.listLocked())
  165. }
  166. // Delete removes an SD-WAN group.
  167. func (m *Manager) Delete(idOrName string) error {
  168. m.mu.Lock()
  169. defer m.mu.Unlock()
  170. g := m.findLocked(idOrName)
  171. if g == nil {
  172. return fmt.Errorf("sd-wan group %q not found", idOrName)
  173. }
  174. delete(m.groups, g.ID)
  175. if err := m.save(); err != nil {
  176. return err
  177. }
  178. return m.engine.Sync(m.listLocked())
  179. }
  180. // AddMember adds an interface member to a group.
  181. func (m *Manager) AddMember(groupIDOrName string, mem *SDWANMember) error {
  182. m.mu.Lock()
  183. defer m.mu.Unlock()
  184. g := m.findLocked(groupIDOrName)
  185. if g == nil {
  186. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  187. }
  188. if mem.Interface == "" {
  189. return fmt.Errorf("member interface is required")
  190. }
  191. // Check duplicates
  192. for _, existing := range g.Members {
  193. if existing.Interface == mem.Interface {
  194. return fmt.Errorf("interface %s already a member of %s", mem.Interface, g.Name)
  195. }
  196. }
  197. if mem.ID == "" {
  198. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  199. }
  200. if mem.Weight <= 0 {
  201. mem.Weight = 50
  202. }
  203. if mem.Priority <= 0 {
  204. mem.Priority = 1
  205. }
  206. if mem.TargetProbe == "" {
  207. mem.TargetProbe = "1.1.1.1"
  208. }
  209. mem.Status = StatusHealthy
  210. g.Members = append(g.Members, mem)
  211. if err := m.save(); err != nil {
  212. return err
  213. }
  214. return m.engine.Sync(m.listLocked())
  215. }
  216. // UpdateMember modifies a member's configuration.
  217. func (m *Manager) UpdateMember(groupIDOrName string, mem *SDWANMember) error {
  218. m.mu.Lock()
  219. defer m.mu.Unlock()
  220. g := m.findLocked(groupIDOrName)
  221. if g == nil {
  222. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  223. }
  224. idx := -1
  225. for i, existing := range g.Members {
  226. if existing.ID == mem.ID || existing.Interface == mem.Interface {
  227. idx = i
  228. break
  229. }
  230. }
  231. if idx == -1 {
  232. return fmt.Errorf("member %s not found in group %s", mem.Interface, g.Name)
  233. }
  234. // Preserve live telemetry
  235. mem.Status = g.Members[idx].Status
  236. mem.LiveLatency = g.Members[idx].LiveLatency
  237. mem.PacketLoss = g.Members[idx].PacketLoss
  238. mem.Jitter = g.Members[idx].Jitter
  239. mem.ConsecutiveFails = g.Members[idx].ConsecutiveFails
  240. g.Members[idx] = mem
  241. if err := m.save(); err != nil {
  242. return err
  243. }
  244. return m.engine.Sync(m.listLocked())
  245. }
  246. // DeleteMember removes a member from a group.
  247. func (m *Manager) DeleteMember(groupIDOrName, memberIDOrIface string) error {
  248. m.mu.Lock()
  249. defer m.mu.Unlock()
  250. g := m.findLocked(groupIDOrName)
  251. if g == nil {
  252. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  253. }
  254. idx := -1
  255. for i, mem := range g.Members {
  256. if mem.ID == memberIDOrIface || mem.Interface == memberIDOrIface {
  257. idx = i
  258. break
  259. }
  260. }
  261. if idx == -1 {
  262. return fmt.Errorf("member %s not found in group %s", memberIDOrIface, g.Name)
  263. }
  264. g.Members = append(g.Members[:idx], g.Members[idx+1:]...)
  265. if err := m.save(); err != nil {
  266. return err
  267. }
  268. return m.engine.Sync(m.listLocked())
  269. }
  270. // ResolveGroupInterfaces resolves an SD-WAN group to its member interfaces (used by Firewall).
  271. func (m *Manager) ResolveGroupInterfaces(groupName string) []string {
  272. m.mu.RLock()
  273. defer m.mu.RUnlock()
  274. var res []string
  275. for _, g := range m.groups {
  276. if strings.EqualFold(g.Name, groupName) && !g.Disabled {
  277. for _, mem := range g.Members {
  278. if !mem.Disabled && mem.Interface != "" {
  279. res = append(res, mem.Interface)
  280. }
  281. }
  282. break
  283. }
  284. }
  285. sort.Strings(res)
  286. return res
  287. }
  288. // ResolveAllSDWANGroups returns a mapping of all group names to their member interfaces.
  289. func (m *Manager) ResolveAllSDWANGroups() map[string][]string {
  290. m.mu.RLock()
  291. defer m.mu.RUnlock()
  292. res := make(map[string][]string)
  293. for _, g := range m.groups {
  294. if !g.Disabled {
  295. var members []string
  296. for _, mem := range g.Members {
  297. if !mem.Disabled && mem.Interface != "" {
  298. members = append(members, mem.Interface)
  299. }
  300. }
  301. sort.Strings(members)
  302. res[g.Name] = members
  303. }
  304. }
  305. return res
  306. }
  307. // GetSummary returns high-level telemetry across all SD-WAN groups.
  308. func (m *Manager) GetSummary() TelemetrySummary {
  309. return m.GetTelemetrySummary()
  310. }
  311. // GetTelemetrySummary returns high-level telemetry across all SD-WAN groups.
  312. func (m *Manager) GetTelemetrySummary() TelemetrySummary {
  313. m.mu.RLock()
  314. defer m.mu.RUnlock()
  315. sum := TelemetrySummary{
  316. TotalGroups: len(m.groups),
  317. }
  318. for _, g := range m.groups {
  319. if !g.Disabled {
  320. sum.ActiveGroups++
  321. }
  322. for _, mem := range g.Members {
  323. sum.TotalMembers++
  324. switch mem.Status {
  325. case StatusHealthy:
  326. sum.HealthyMembers++
  327. case StatusDegraded:
  328. sum.DegradedMembers++
  329. case StatusDown:
  330. sum.DownMembers++
  331. }
  332. }
  333. }
  334. return sum
  335. }
  336. // SyncAll reconciles kernel routing and iptables for all groups.
  337. func (m *Manager) SyncAll() {
  338. m.mu.Lock()
  339. defer m.mu.Unlock()
  340. _ = m.engine.Sync(m.listLocked())
  341. }
  342. func (m *Manager) findLocked(idOrName string) *SDWANGroup {
  343. if g, ok := m.groups[idOrName]; ok {
  344. return g
  345. }
  346. for _, g := range m.groups {
  347. if strings.EqualFold(g.Name, idOrName) {
  348. return g
  349. }
  350. }
  351. return nil
  352. }