manager.go 18 KB

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