manager.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. package bridge
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "fmt"
  6. "net"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "time"
  14. "github.com/vishvananda/netlink"
  15. )
  16. const BridgeConfigFile = "/etc/proxmox-router/bridge.json"
  17. type BridgeConfigStore struct {
  18. Bridges []*BridgeInstance `json:"bridges"`
  19. Ports []*BridgePort `json:"ports"`
  20. VLANs []*BridgeVLAN `json:"vlans"`
  21. }
  22. type BridgeManager struct {
  23. mu sync.RWMutex
  24. storePath string
  25. bridges map[string]*BridgeInstance
  26. ports map[string]*BridgePort
  27. vlans map[string]*BridgeVLAN
  28. }
  29. func NewBridgeManager() *BridgeManager {
  30. bm := &BridgeManager{
  31. storePath: BridgeConfigFile,
  32. bridges: make(map[string]*BridgeInstance),
  33. ports: make(map[string]*BridgePort),
  34. vlans: make(map[string]*BridgeVLAN),
  35. }
  36. bm.load()
  37. bm.SyncAll()
  38. return bm
  39. }
  40. func (m *BridgeManager) load() {
  41. m.mu.Lock()
  42. defer m.mu.Unlock()
  43. data, err := os.ReadFile(m.storePath)
  44. if err != nil {
  45. return
  46. }
  47. var store BridgeConfigStore
  48. if err := json.Unmarshal(data, &store); err != nil {
  49. return
  50. }
  51. for _, b := range store.Bridges {
  52. m.bridges[b.ID] = b
  53. }
  54. for _, p := range store.Ports {
  55. m.ports[p.ID] = p
  56. }
  57. for _, v := range store.VLANs {
  58. m.vlans[v.ID] = v
  59. }
  60. }
  61. func (m *BridgeManager) save() error {
  62. _ = os.MkdirAll(filepath.Dir(m.storePath), 0755)
  63. store := BridgeConfigStore{
  64. Bridges: make([]*BridgeInstance, 0, len(m.bridges)),
  65. Ports: make([]*BridgePort, 0, len(m.ports)),
  66. VLANs: make([]*BridgeVLAN, 0, len(m.vlans)),
  67. }
  68. for _, b := range m.bridges {
  69. store.Bridges = append(store.Bridges, b)
  70. }
  71. for _, p := range m.ports {
  72. store.Ports = append(store.Ports, p)
  73. }
  74. for _, v := range m.vlans {
  75. store.VLANs = append(store.VLANs, v)
  76. }
  77. data, err := json.MarshalIndent(store, "", " ")
  78. if err != nil {
  79. return err
  80. }
  81. return os.WriteFile(m.storePath, data, 0644)
  82. }
  83. // ==================== Bridge Interfaces ====================
  84. func (m *BridgeManager) ListBridges() []*BridgeInstance {
  85. m.mu.Lock()
  86. defer m.mu.Unlock()
  87. list := make([]*BridgeInstance, 0, len(m.bridges))
  88. for _, b := range m.bridges {
  89. m.updateBridgeLiveStats(b)
  90. list = append(list, b)
  91. }
  92. return list
  93. }
  94. func (m *BridgeManager) GetBridge(idOrName string) (*BridgeInstance, bool) {
  95. m.mu.Lock()
  96. defer m.mu.Unlock()
  97. if b, ok := m.bridges[idOrName]; ok {
  98. m.updateBridgeLiveStats(b)
  99. return b, true
  100. }
  101. for _, b := range m.bridges {
  102. if b.Name == idOrName {
  103. m.updateBridgeLiveStats(b)
  104. return b, true
  105. }
  106. }
  107. return nil, false
  108. }
  109. func (m *BridgeManager) SaveBridge(b BridgeInstance) error {
  110. m.mu.Lock()
  111. defer m.mu.Unlock()
  112. if b.Name == "" {
  113. return fmt.Errorf("bridge name is required")
  114. }
  115. if b.MTU <= 0 {
  116. b.MTU = 1500
  117. }
  118. if b.PVID <= 0 {
  119. b.PVID = 1
  120. }
  121. if b.STP == "" {
  122. b.STP = "rstp"
  123. }
  124. if b.Priority <= 0 {
  125. b.Priority = 32768
  126. }
  127. if b.ID == "" {
  128. b.ID = fmt.Sprintf("br-%d", time.Now().UnixNano())
  129. }
  130. // Update existing or add new
  131. m.bridges[b.ID] = &b
  132. _ = m.save()
  133. // Apply in Linux kernel
  134. return m.syncBridge(&b)
  135. }
  136. func (m *BridgeManager) DeleteBridge(id string) error {
  137. m.mu.Lock()
  138. defer m.mu.Unlock()
  139. b, ok := m.bridges[id]
  140. if !ok {
  141. return fmt.Errorf("bridge not found")
  142. }
  143. // Detach any ports belonging to this bridge
  144. for _, p := range m.ports {
  145. if p.Bridge == b.Name {
  146. _ = exec.Command("ip", "link", "set", p.Interface, "nomaster").Run()
  147. delete(m.ports, p.ID)
  148. }
  149. }
  150. // Delete any VLAN entries for this bridge
  151. for vid, v := range m.vlans {
  152. if v.Bridge == b.Name {
  153. delete(m.vlans, vid)
  154. }
  155. }
  156. // Delete bridge link in kernel
  157. _ = exec.Command("ip", "link", "del", b.Name).Run()
  158. delete(m.bridges, id)
  159. return m.save()
  160. }
  161. func (m *BridgeManager) SetBridgeState(id string, disabled bool) error {
  162. m.mu.Lock()
  163. defer m.mu.Unlock()
  164. b, ok := m.bridges[id]
  165. if !ok {
  166. return fmt.Errorf("bridge not found")
  167. }
  168. b.Disabled = disabled
  169. _ = m.save()
  170. if disabled {
  171. _ = exec.Command("ip", "link", "set", b.Name, "down").Run()
  172. } else {
  173. _ = exec.Command("ip", "link", "set", b.Name, "up").Run()
  174. }
  175. return nil
  176. }
  177. func (m *BridgeManager) AddBridgeIP(idOrName string, ipCIDR string) error {
  178. m.mu.Lock()
  179. defer m.mu.Unlock()
  180. b, ok := m.bridges[idOrName]
  181. if !ok {
  182. for _, br := range m.bridges {
  183. if br.Name == idOrName {
  184. b = br
  185. ok = true
  186. break
  187. }
  188. }
  189. }
  190. if !ok {
  191. return fmt.Errorf("bridge not found")
  192. }
  193. ipCIDR = strings.TrimSpace(ipCIDR)
  194. if _, _, err := net.ParseCIDR(ipCIDR); err != nil {
  195. return fmt.Errorf("invalid IP/CIDR: %w", err)
  196. }
  197. // Check if already in list
  198. for _, ip := range b.IPAddresses {
  199. if ip == ipCIDR {
  200. return nil
  201. }
  202. }
  203. b.IPAddresses = append(b.IPAddresses, ipCIDR)
  204. _ = m.save()
  205. // Add to kernel
  206. _ = exec.Command("ip", "addr", "add", ipCIDR, "dev", b.Name).Run()
  207. return nil
  208. }
  209. func (m *BridgeManager) DeleteBridgeIP(idOrName string, ipCIDR string) error {
  210. m.mu.Lock()
  211. defer m.mu.Unlock()
  212. b, ok := m.bridges[idOrName]
  213. if !ok {
  214. for _, br := range m.bridges {
  215. if br.Name == idOrName {
  216. b = br
  217. ok = true
  218. break
  219. }
  220. }
  221. }
  222. if !ok {
  223. return fmt.Errorf("bridge not found")
  224. }
  225. ipCIDR = strings.TrimSpace(ipCIDR)
  226. var updated []string
  227. for _, ip := range b.IPAddresses {
  228. if ip != ipCIDR {
  229. updated = append(updated, ip)
  230. }
  231. }
  232. b.IPAddresses = updated
  233. _ = m.save()
  234. _ = exec.Command("ip", "addr", "del", ipCIDR, "dev", b.Name).Run()
  235. return nil
  236. }
  237. func (m *BridgeManager) syncBridge(b *BridgeInstance) error {
  238. link, err := netlink.LinkByName(b.Name)
  239. if err != nil {
  240. // Create bridge
  241. args := []string{"link", "add", b.Name, "type", "bridge"}
  242. if b.VlanFiltering {
  243. args = append(args, "vlan_filtering", "1")
  244. } else {
  245. args = append(args, "vlan_filtering", "0")
  246. }
  247. if b.PVID > 0 {
  248. args = append(args, "vlan_default_pvid", strconv.Itoa(b.PVID))
  249. }
  250. if out, err := exec.Command("ip", args...).CombinedOutput(); err != nil {
  251. return fmt.Errorf("failed to create bridge %s: %s: %w", b.Name, string(out), err)
  252. }
  253. link, _ = netlink.LinkByName(b.Name)
  254. }
  255. if link != nil {
  256. // Set MTU
  257. if b.MTU > 0 {
  258. _ = netlink.LinkSetMTU(link, b.MTU)
  259. }
  260. // Set Admin MAC if specified
  261. if b.MACAddress != "" {
  262. if hw, err := net.ParseMAC(b.MACAddress); err == nil {
  263. _ = netlink.LinkSetHardwareAddr(link, hw)
  264. }
  265. }
  266. // Set VLAN filtering on existing bridge
  267. vfVal := "0"
  268. if b.VlanFiltering {
  269. vfVal = "1"
  270. }
  271. pvidVal := strconv.Itoa(b.PVID)
  272. if b.PVID <= 0 {
  273. pvidVal = "1"
  274. }
  275. _ = exec.Command("ip", "link", "set", b.Name, "type", "bridge", "vlan_filtering", vfVal, "vlan_default_pvid", pvidVal).Run()
  276. // Set STP
  277. stpVal := "0"
  278. if b.STP == "rstp" || b.STP == "stp" {
  279. stpVal = "1"
  280. }
  281. _ = os.WriteFile(fmt.Sprintf("/sys/class/net/%s/bridge/stp_state", b.Name), []byte(stpVal), 0644)
  282. // Set link up or down
  283. if b.Disabled {
  284. _ = netlink.LinkSetDown(link)
  285. } else {
  286. _ = netlink.LinkSetUp(link)
  287. }
  288. // Sync IP addresses
  289. for _, ip := range b.IPAddresses {
  290. _ = exec.Command("ip", "addr", "add", ip, "dev", b.Name).Run()
  291. }
  292. }
  293. return nil
  294. }
  295. func (m *BridgeManager) updateBridgeLiveStats(b *BridgeInstance) {
  296. // Count ports belonging to this bridge
  297. portsCount := 0
  298. for _, p := range m.ports {
  299. if p.Bridge == b.Name && !p.Disabled {
  300. portsCount++
  301. }
  302. }
  303. b.PortsCount = portsCount
  304. link, err := netlink.LinkByName(b.Name)
  305. if err != nil {
  306. b.Running = false
  307. return
  308. }
  309. attrs := link.Attrs()
  310. b.Running = (attrs.Flags&net.FlagUp != 0) && (attrs.OperState == netlink.OperUp || attrs.OperState == netlink.OperUnknown)
  311. if b.MACAddress == "" {
  312. b.MACAddress = attrs.HardwareAddr.String()
  313. }
  314. if attrs.Statistics != nil {
  315. b.RxPackets = attrs.Statistics.RxPackets
  316. b.TxPackets = attrs.Statistics.TxPackets
  317. b.RxBytes = attrs.Statistics.RxBytes
  318. b.TxBytes = attrs.Statistics.TxBytes
  319. b.RxHuman = formatBytes(b.RxBytes)
  320. b.TxHuman = formatBytes(b.TxBytes)
  321. }
  322. // Read IP addresses from kernel
  323. if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
  324. var ips []string
  325. for _, a := range addrs {
  326. ips = append(ips, a.IPNet.String())
  327. }
  328. b.IPAddresses = ips
  329. }
  330. }
  331. // ==================== Bridge Ports ====================
  332. func (m *BridgeManager) ListPorts() []*BridgePort {
  333. m.mu.Lock()
  334. defer m.mu.Unlock()
  335. list := make([]*BridgePort, 0, len(m.ports))
  336. for _, p := range m.ports {
  337. m.updatePortLiveStatus(p)
  338. list = append(list, p)
  339. }
  340. return list
  341. }
  342. func (m *BridgeManager) SavePort(p BridgePort) error {
  343. m.mu.Lock()
  344. defer m.mu.Unlock()
  345. if p.Bridge == "" || p.Interface == "" {
  346. return fmt.Errorf("bridge and interface are required")
  347. }
  348. if p.PVID <= 0 {
  349. p.PVID = 1
  350. }
  351. if p.Priority <= 0 {
  352. p.Priority = 128
  353. }
  354. if p.PathCost <= 0 {
  355. p.PathCost = 10
  356. }
  357. if p.FrameTypes == "" {
  358. p.FrameTypes = "admit-all"
  359. }
  360. if p.ID == "" {
  361. p.ID = fmt.Sprintf("port-%d", time.Now().UnixNano())
  362. }
  363. m.ports[p.ID] = &p
  364. _ = m.save()
  365. return m.syncPort(&p)
  366. }
  367. func (m *BridgeManager) DeletePort(id string) error {
  368. m.mu.Lock()
  369. defer m.mu.Unlock()
  370. p, ok := m.ports[id]
  371. if !ok {
  372. return fmt.Errorf("port not found")
  373. }
  374. // Remove from bridge in kernel
  375. _ = exec.Command("ip", "link", "set", p.Interface, "nomaster").Run()
  376. delete(m.ports, id)
  377. return m.save()
  378. }
  379. func (m *BridgeManager) SetPortState(id string, disabled bool) error {
  380. m.mu.Lock()
  381. defer m.mu.Unlock()
  382. p, ok := m.ports[id]
  383. if !ok {
  384. return fmt.Errorf("port not found")
  385. }
  386. p.Disabled = disabled
  387. _ = m.save()
  388. if disabled {
  389. _ = exec.Command("ip", "link", "set", p.Interface, "nomaster").Run()
  390. } else {
  391. _ = m.syncPort(p)
  392. }
  393. return nil
  394. }
  395. func (m *BridgeManager) syncPort(p *BridgePort) error {
  396. if p.Disabled {
  397. _ = exec.Command("ip", "link", "set", p.Interface, "nomaster").Run()
  398. return nil
  399. }
  400. // Check if interface exists
  401. if _, err := netlink.LinkByName(p.Interface); err != nil {
  402. return fmt.Errorf("interface %s does not exist", p.Interface)
  403. }
  404. // Attach interface to bridge
  405. if out, err := exec.Command("ip", "link", "set", p.Interface, "master", p.Bridge).CombinedOutput(); err != nil {
  406. return fmt.Errorf("failed to attach %s to bridge %s: %s: %w", p.Interface, p.Bridge, string(out), err)
  407. }
  408. // Bring interface UP
  409. _ = exec.Command("ip", "link", "set", p.Interface, "up").Run()
  410. // Port isolation / Split Horizon
  411. isolated := "off"
  412. if p.Horizon > 0 {
  413. isolated = "on"
  414. }
  415. _ = exec.Command("bridge", "link", "set", "dev", p.Interface, "isolated", isolated).Run()
  416. // Learning
  417. learning := "on"
  418. if !p.Learn {
  419. learning = "off"
  420. }
  421. _ = exec.Command("bridge", "link", "set", "dev", p.Interface, "learning", learning).Run()
  422. // Ingress filtering
  423. ingress := "off"
  424. if p.IngressFiltering {
  425. ingress = "on"
  426. }
  427. _ = exec.Command("bridge", "link", "set", "dev", p.Interface, "ingress_filtering", ingress).Run()
  428. // Priority & Path Cost
  429. prioCostArgs := []string{"link", "set", "dev", p.Interface}
  430. if p.Priority > 0 {
  431. prioCostArgs = append(prioCostArgs, "priority", strconv.Itoa(p.Priority))
  432. }
  433. if p.PathCost > 0 {
  434. prioCostArgs = append(prioCostArgs, "cost", strconv.Itoa(p.PathCost))
  435. }
  436. if len(prioCostArgs) > 4 {
  437. _ = exec.Command("bridge", prioCostArgs...).Run()
  438. }
  439. // VLAN filtering configuration
  440. if p.PVID > 0 {
  441. _ = exec.Command("bridge", "vlan", "add", "dev", p.Interface, "vid", strconv.Itoa(p.PVID), "pvid", "untagged").Run()
  442. }
  443. return nil
  444. }
  445. func (m *BridgeManager) updatePortLiveStatus(p *BridgePort) {
  446. if p.Disabled {
  447. p.Status = "disabled"
  448. return
  449. }
  450. link, err := netlink.LinkByName(p.Interface)
  451. if err != nil {
  452. p.Status = "inactive"
  453. return
  454. }
  455. attrs := link.Attrs()
  456. if attrs.OperState == netlink.OperDown {
  457. p.Status = "disabled"
  458. } else if attrs.MasterIndex > 0 {
  459. p.Status = "forwarding"
  460. } else {
  461. p.Status = "unattached"
  462. }
  463. }
  464. // ==================== Bridge VLANs ====================
  465. func (m *BridgeManager) ListVLANs() []*BridgeVLAN {
  466. m.mu.Lock()
  467. defer m.mu.Unlock()
  468. list := make([]*BridgeVLAN, 0, len(m.vlans))
  469. for _, v := range m.vlans {
  470. v.CurrentTagged = v.Tagged
  471. v.CurrentUntagged = v.Untagged
  472. list = append(list, v)
  473. }
  474. return list
  475. }
  476. func (m *BridgeManager) SaveVLAN(v BridgeVLAN) error {
  477. m.mu.Lock()
  478. defer m.mu.Unlock()
  479. if v.Bridge == "" || v.VlanIDs == "" {
  480. return fmt.Errorf("bridge and vlan_ids are required")
  481. }
  482. if v.ID == "" {
  483. v.ID = fmt.Sprintf("bvlan-%d", time.Now().UnixNano())
  484. }
  485. m.vlans[v.ID] = &v
  486. _ = m.save()
  487. return m.syncVLAN(&v)
  488. }
  489. func (m *BridgeManager) DeleteVLAN(id string) error {
  490. m.mu.Lock()
  491. defer m.mu.Unlock()
  492. v, ok := m.vlans[id]
  493. if !ok {
  494. return fmt.Errorf("vlan entry not found")
  495. }
  496. // Remove VLANs from kernel
  497. vids := parseVLANRange(v.VlanIDs)
  498. for _, vid := range vids {
  499. vidStr := strconv.Itoa(vid)
  500. for _, iface := range append(v.Tagged, v.Untagged...) {
  501. _ = exec.Command("bridge", "vlan", "del", "dev", iface, "vid", vidStr).Run()
  502. }
  503. }
  504. delete(m.vlans, id)
  505. return m.save()
  506. }
  507. func (m *BridgeManager) SetVLANState(id string, disabled bool) error {
  508. m.mu.Lock()
  509. defer m.mu.Unlock()
  510. v, ok := m.vlans[id]
  511. if !ok {
  512. return fmt.Errorf("vlan entry not found")
  513. }
  514. v.Disabled = disabled
  515. _ = m.save()
  516. if disabled {
  517. vids := parseVLANRange(v.VlanIDs)
  518. for _, vid := range vids {
  519. vidStr := strconv.Itoa(vid)
  520. for _, iface := range append(v.Tagged, v.Untagged...) {
  521. _ = exec.Command("bridge", "vlan", "del", "dev", iface, "vid", vidStr).Run()
  522. }
  523. }
  524. } else {
  525. _ = m.syncVLAN(v)
  526. }
  527. return nil
  528. }
  529. func (m *BridgeManager) syncVLAN(v *BridgeVLAN) error {
  530. if v.Disabled {
  531. return nil
  532. }
  533. vids := parseVLANRange(v.VlanIDs)
  534. for _, vid := range vids {
  535. vidStr := strconv.Itoa(vid)
  536. for _, iface := range v.Tagged {
  537. _ = exec.Command("bridge", "vlan", "add", "dev", iface, "vid", vidStr).Run()
  538. }
  539. for _, iface := range v.Untagged {
  540. _ = exec.Command("bridge", "vlan", "add", "dev", iface, "vid", vidStr, "untagged").Run()
  541. }
  542. }
  543. return nil
  544. }
  545. // ==================== Bridge Hosts (FDB / MAC Table) ====================
  546. func (m *BridgeManager) ListHosts(bridgeFilter string) ([]BridgeHost, error) {
  547. args := []string{"fdb", "show"}
  548. if bridgeFilter != "" {
  549. args = append(args, "br", bridgeFilter)
  550. }
  551. out, err := exec.Command("bridge", args...).Output()
  552. if err != nil {
  553. return nil, fmt.Errorf("failed to run bridge fdb show: %w", err)
  554. }
  555. var hosts []BridgeHost
  556. scanner := bufio.NewScanner(strings.NewReader(string(out)))
  557. for scanner.Scan() {
  558. line := strings.TrimSpace(scanner.Text())
  559. if line == "" {
  560. continue
  561. }
  562. fields := strings.Fields(line)
  563. if len(fields) < 3 {
  564. continue
  565. }
  566. mac := fields[0]
  567. dev := ""
  568. vlan := 1
  569. master := ""
  570. age := 0.0
  571. isLocal := false
  572. isStatic := false
  573. for i := 1; i < len(fields); i++ {
  574. switch fields[i] {
  575. case "dev":
  576. if i+1 < len(fields) {
  577. dev = fields[i+1]
  578. i++
  579. }
  580. case "vlan":
  581. if i+1 < len(fields) {
  582. vlan, _ = strconv.Atoi(fields[i+1])
  583. i++
  584. }
  585. case "master":
  586. if i+1 < len(fields) {
  587. master = fields[i+1]
  588. i++
  589. }
  590. case "self":
  591. isLocal = true
  592. case "permanent":
  593. isLocal = true
  594. isStatic = true
  595. case "static":
  596. isStatic = true
  597. default:
  598. if val, err := strconv.ParseFloat(fields[i], 64); err == nil {
  599. age = val
  600. }
  601. }
  602. }
  603. if master == "" && strings.HasPrefix(dev, "br") {
  604. master = dev
  605. }
  606. var flags []string
  607. if isLocal {
  608. flags = append(flags, "L")
  609. } else if isStatic {
  610. flags = append(flags, "S")
  611. } else {
  612. flags = append(flags, "D")
  613. }
  614. hosts = append(hosts, BridgeHost{
  615. MAC: mac,
  616. Interface: dev,
  617. Bridge: master,
  618. VlanID: vlan,
  619. Age: age,
  620. Local: isLocal,
  621. Static: isStatic,
  622. Flags: strings.Join(flags, ","),
  623. })
  624. }
  625. return hosts, nil
  626. }
  627. func (m *BridgeManager) FlushHosts(bridge string) error {
  628. if bridge == "" {
  629. return fmt.Errorf("bridge name required for flush")
  630. }
  631. _ = exec.Command("bridge", "fdb", "flush", "dev", bridge, "dynamic").Run()
  632. return nil
  633. }
  634. // ==================== Summary & SyncAll ====================
  635. func (m *BridgeManager) GetSummary() BridgeSummary {
  636. m.mu.RLock()
  637. bCount := len(m.bridges)
  638. pCount := len(m.ports)
  639. vCount := len(m.vlans)
  640. m.mu.RUnlock()
  641. hCount := 0
  642. if hosts, err := m.ListHosts(""); err == nil {
  643. hCount = len(hosts)
  644. }
  645. return BridgeSummary{
  646. BridgesCount: bCount,
  647. PortsCount: pCount,
  648. VLANsCount: vCount,
  649. HostsCount: hCount,
  650. }
  651. }
  652. func (m *BridgeManager) SyncAll() {
  653. m.mu.RLock()
  654. defer m.mu.RUnlock()
  655. // 1. Sync all bridges
  656. for _, b := range m.bridges {
  657. _ = m.syncBridge(b)
  658. }
  659. // 2. Sync all ports
  660. for _, p := range m.ports {
  661. _ = m.syncPort(p)
  662. }
  663. // 3. Sync all VLAN entries
  664. for _, v := range m.vlans {
  665. _ = m.syncVLAN(v)
  666. }
  667. }
  668. // Helper: parse "10" or "10,20,30" or "10-20" into int slice
  669. func parseVLANRange(s string) []int {
  670. var result []int
  671. parts := strings.Split(s, ",")
  672. for _, p := range parts {
  673. p = strings.TrimSpace(p)
  674. if strings.Contains(p, "-") {
  675. rangeParts := strings.Split(p, "-")
  676. if len(rangeParts) == 2 {
  677. start, err1 := strconv.Atoi(strings.TrimSpace(rangeParts[0]))
  678. end, err2 := strconv.Atoi(strings.TrimSpace(rangeParts[1]))
  679. if err1 == nil && err2 == nil && start <= end {
  680. for i := start; i <= end; i++ {
  681. result = append(result, i)
  682. }
  683. continue
  684. }
  685. }
  686. }
  687. if val, err := strconv.Atoi(p); err == nil {
  688. result = append(result, val)
  689. }
  690. }
  691. return result
  692. }
  693. func formatBytes(b uint64) string {
  694. const unit = 1024
  695. if b < unit {
  696. return fmt.Sprintf("%d B", b)
  697. }
  698. div, exp := uint64(unit), 0
  699. for n := b / unit; n >= unit; n /= unit {
  700. div *= unit
  701. exp++
  702. }
  703. return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
  704. }