manager.go 22 KB

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