manager.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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. // Preserve existing members if not specified in update payload
  147. if existing, ok := m.groups[g.ID]; ok {
  148. if g.Members == nil && existing.Members != nil {
  149. g.Members = existing.Members
  150. }
  151. }
  152. // Ensure member IDs and initial statuses
  153. for _, mem := range g.Members {
  154. if mem.ID == "" {
  155. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  156. }
  157. if mem.Weight <= 0 {
  158. mem.Weight = 50
  159. }
  160. if mem.Priority <= 0 {
  161. mem.Priority = 1
  162. }
  163. if mem.Status == "" {
  164. mem.Status = StatusHealthy
  165. }
  166. }
  167. m.groups[g.ID] = g
  168. if err := m.save(); err != nil {
  169. return err
  170. }
  171. return m.syncLocked()
  172. }
  173. func (m *Manager) syncLocked() error {
  174. err := m.engine.Sync(m.listLocked())
  175. _ = m.save()
  176. return err
  177. }
  178. // SetState enables or disables an SD-WAN group.
  179. func (m *Manager) SetState(idOrName string, disabled bool) error {
  180. m.mu.Lock()
  181. defer m.mu.Unlock()
  182. g := m.findLocked(idOrName)
  183. if g == nil {
  184. return fmt.Errorf("sd-wan group %q not found", idOrName)
  185. }
  186. g.Disabled = disabled
  187. if err := m.save(); err != nil {
  188. return err
  189. }
  190. return m.syncLocked()
  191. }
  192. // Delete removes an SD-WAN group.
  193. func (m *Manager) Delete(idOrName string) error {
  194. m.mu.Lock()
  195. defer m.mu.Unlock()
  196. g := m.findLocked(idOrName)
  197. if g == nil {
  198. return fmt.Errorf("sd-wan group %q not found", idOrName)
  199. }
  200. delete(m.groups, g.ID)
  201. if err := m.save(); err != nil {
  202. return err
  203. }
  204. return m.syncLocked()
  205. }
  206. // AddMember adds an interface member to a group.
  207. func (m *Manager) AddMember(groupIDOrName string, mem *SDWANMember) error {
  208. m.mu.Lock()
  209. defer m.mu.Unlock()
  210. g := m.findLocked(groupIDOrName)
  211. if g == nil {
  212. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  213. }
  214. if mem.Interface == "" {
  215. return fmt.Errorf("member interface is required")
  216. }
  217. // Check duplicates
  218. for _, existing := range g.Members {
  219. if existing.Interface == mem.Interface {
  220. return fmt.Errorf("interface %s already a member of %s", mem.Interface, g.Name)
  221. }
  222. }
  223. if mem.ID == "" {
  224. mem.ID = fmt.Sprintf("mem-%d-%s", time.Now().UnixNano(), mem.Interface)
  225. }
  226. if mem.Weight <= 0 {
  227. mem.Weight = 50
  228. }
  229. if mem.Priority <= 0 {
  230. mem.Priority = 1
  231. }
  232. if mem.TargetProbe == "" {
  233. mem.TargetProbe = "1.1.1.1"
  234. }
  235. mem.Status = StatusHealthy
  236. g.Members = append(g.Members, mem)
  237. if err := m.save(); err != nil {
  238. return err
  239. }
  240. return m.syncLocked()
  241. }
  242. // UpdateMember modifies a member's configuration.
  243. func (m *Manager) UpdateMember(groupIDOrName string, mem *SDWANMember) error {
  244. m.mu.Lock()
  245. defer m.mu.Unlock()
  246. g := m.findLocked(groupIDOrName)
  247. if g == nil {
  248. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  249. }
  250. idx := -1
  251. for i, existing := range g.Members {
  252. if (mem.ID != "" && existing.ID == mem.ID) || (mem.Interface != "" && existing.Interface == mem.Interface) {
  253. idx = i
  254. break
  255. }
  256. }
  257. if idx == -1 {
  258. return fmt.Errorf("member %s not found in group %s", mem.Interface, g.Name)
  259. }
  260. // Preserve existing ID & Interface if not specified
  261. if mem.ID == "" {
  262. mem.ID = g.Members[idx].ID
  263. }
  264. if mem.Interface == "" {
  265. mem.Interface = g.Members[idx].Interface
  266. }
  267. if mem.Weight <= 0 {
  268. mem.Weight = g.Members[idx].Weight
  269. }
  270. if mem.Priority <= 0 {
  271. mem.Priority = g.Members[idx].Priority
  272. }
  273. if mem.TargetProbe == "" {
  274. mem.TargetProbe = g.Members[idx].TargetProbe
  275. }
  276. if mem.MaxLatency <= 0 {
  277. mem.MaxLatency = g.Members[idx].MaxLatency
  278. }
  279. if mem.MaxLoss <= 0 {
  280. mem.MaxLoss = g.Members[idx].MaxLoss
  281. }
  282. // Preserve live telemetry
  283. mem.Status = g.Members[idx].Status
  284. if mem.Status == "" {
  285. mem.Status = StatusHealthy
  286. }
  287. mem.LiveLatency = g.Members[idx].LiveLatency
  288. mem.PacketLoss = g.Members[idx].PacketLoss
  289. mem.Jitter = g.Members[idx].Jitter
  290. mem.ConsecutiveFails = g.Members[idx].ConsecutiveFails
  291. g.Members[idx] = mem
  292. if err := m.save(); err != nil {
  293. return err
  294. }
  295. return m.syncLocked()
  296. }
  297. // DeleteMember removes a member from a group.
  298. func (m *Manager) DeleteMember(groupIDOrName, memberIDOrIface string) error {
  299. m.mu.Lock()
  300. defer m.mu.Unlock()
  301. g := m.findLocked(groupIDOrName)
  302. if g == nil {
  303. return fmt.Errorf("sd-wan group %q not found", groupIDOrName)
  304. }
  305. idx := -1
  306. for i, mem := range g.Members {
  307. if mem.ID == memberIDOrIface || mem.Interface == memberIDOrIface {
  308. idx = i
  309. break
  310. }
  311. }
  312. if idx == -1 {
  313. return fmt.Errorf("member %s not found in group %s", memberIDOrIface, g.Name)
  314. }
  315. g.Members = append(g.Members[:idx], g.Members[idx+1:]...)
  316. if err := m.save(); err != nil {
  317. return err
  318. }
  319. return m.syncLocked()
  320. }
  321. // ResolveGroupInterfaces resolves an SD-WAN group to its member interfaces (used by Firewall).
  322. func (m *Manager) ResolveGroupInterfaces(groupName string) []string {
  323. m.mu.RLock()
  324. defer m.mu.RUnlock()
  325. var res []string
  326. for _, g := range m.groups {
  327. if strings.EqualFold(g.Name, groupName) && !g.Disabled {
  328. for _, mem := range g.Members {
  329. if !mem.Disabled && mem.Interface != "" {
  330. res = append(res, mem.Interface)
  331. }
  332. }
  333. break
  334. }
  335. }
  336. sort.Strings(res)
  337. return res
  338. }
  339. // ResolveAllSDWANGroups returns a mapping of all group names to their member interfaces.
  340. func (m *Manager) ResolveAllSDWANGroups() map[string][]string {
  341. m.mu.RLock()
  342. defer m.mu.RUnlock()
  343. res := make(map[string][]string)
  344. for _, g := range m.groups {
  345. if !g.Disabled {
  346. var members []string
  347. for _, mem := range g.Members {
  348. if !mem.Disabled && mem.Interface != "" {
  349. members = append(members, mem.Interface)
  350. }
  351. }
  352. sort.Strings(members)
  353. res[g.Name] = members
  354. }
  355. }
  356. return res
  357. }
  358. // GetSummary returns high-level telemetry across all SD-WAN groups.
  359. func (m *Manager) GetSummary() TelemetrySummary {
  360. return m.GetTelemetrySummary()
  361. }
  362. // GetTelemetrySummary returns high-level telemetry across all SD-WAN groups.
  363. func (m *Manager) GetTelemetrySummary() TelemetrySummary {
  364. m.mu.RLock()
  365. defer m.mu.RUnlock()
  366. sum := TelemetrySummary{
  367. TotalGroups: len(m.groups),
  368. }
  369. for _, g := range m.groups {
  370. if !g.Disabled {
  371. sum.ActiveGroups++
  372. }
  373. for _, mem := range g.Members {
  374. sum.TotalMembers++
  375. switch mem.Status {
  376. case StatusHealthy:
  377. sum.HealthyMembers++
  378. case StatusDegraded:
  379. sum.DegradedMembers++
  380. case StatusDown:
  381. sum.DownMembers++
  382. }
  383. }
  384. }
  385. return sum
  386. }
  387. // SyncAll reconciles kernel routing and iptables for all groups.
  388. func (m *Manager) SyncAll() {
  389. m.mu.Lock()
  390. defer m.mu.Unlock()
  391. _ = m.engine.Sync(m.listLocked())
  392. }
  393. func (m *Manager) findLocked(idOrName string) *SDWANGroup {
  394. if g, ok := m.groups[idOrName]; ok {
  395. return g
  396. }
  397. for _, g := range m.groups {
  398. if strings.EqualFold(g.Name, idOrName) {
  399. return g
  400. }
  401. }
  402. return nil
  403. }
  404. // RenameInterface updates SD-WAN member references when an interface is renamed.
  405. func (m *Manager) RenameInterface(oldName, newName string) {
  406. m.mu.Lock()
  407. defer m.mu.Unlock()
  408. modified := false
  409. for _, g := range m.groups {
  410. for _, mem := range g.Members {
  411. if mem.Interface == oldName {
  412. mem.Interface = newName
  413. modified = true
  414. }
  415. }
  416. }
  417. if modified {
  418. _ = m.save()
  419. _ = m.engine.Sync(m.listLocked())
  420. }
  421. }