manager.go 9.1 KB

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