manager.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. package firewall
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "os"
  8. "os/exec"
  9. "strings"
  10. "sync"
  11. "time"
  12. )
  13. const (
  14. FilterStorePath = "/etc/proxmox-router/firewall/filter.json"
  15. NATStorePath = "/etc/proxmox-router/firewall/nat.json"
  16. MangleStorePath = "/etc/proxmox-router/firewall/mangle.json"
  17. AddressListsStorePath = "/etc/proxmox-router/firewall/address_lists.json"
  18. )
  19. type FirewallManager struct {
  20. mu sync.Mutex
  21. committed []byte
  22. lastError string
  23. applied bool
  24. filterRules []FilterRule
  25. natRules []NATRule
  26. mangleRules []MangleRule
  27. addressLists []AddressListEntry
  28. geoIPMgr *GeoIPManager
  29. }
  30. func NewFirewallManager() *FirewallManager {
  31. m := &FirewallManager{
  32. filterRules: make([]FilterRule, 0),
  33. natRules: make([]NATRule, 0),
  34. mangleRules: make([]MangleRule, 0),
  35. addressLists: make([]AddressListEntry, 0),
  36. geoIPMgr: NewGeoIPManager(),
  37. }
  38. m.geoIPMgr.referenced = m.geoReferenced
  39. loadErr := m.load()
  40. m.remember()
  41. if loadErr != nil {
  42. m.lastError = loadErr.Error()
  43. log.Printf("Firewall configuration not loaded: %v", loadErr)
  44. return m
  45. }
  46. // Defaults are installed only once, never because a user intentionally saved
  47. // an empty list or deleted all rules.
  48. fresh := true
  49. for _, path := range []string{FilterStorePath, NATStorePath, MangleStorePath, AddressListsStorePath} {
  50. if _, err := os.Stat(path); !os.IsNotExist(err) {
  51. fresh = false
  52. }
  53. }
  54. if fresh {
  55. if err := m.initDefaultRules(); err != nil {
  56. m.lastError = err.Error()
  57. return m
  58. }
  59. }
  60. if err := m.Apply(); err != nil {
  61. log.Printf("Firewall not applied: %v", err)
  62. }
  63. m.remember()
  64. return m
  65. }
  66. func (m *FirewallManager) initDefaultRules() error {
  67. if len(m.natRules) == 0 {
  68. // Default Masquerade NAT only on WAN
  69. m.natRules = append(m.natRules, NATRule{
  70. ID: fmt.Sprintf("nat-%d", time.Now().UnixNano()),
  71. Order: 1,
  72. Chain: "srcnat",
  73. Action: "masquerade",
  74. OutInterfaceList: "WAN",
  75. Comment: "defconf: masquerade outgoing traffic to WAN",
  76. Disabled: false,
  77. })
  78. }
  79. if len(m.filterRules) == 0 {
  80. // Default Fasttrack / Established,Related Filter
  81. m.filterRules = append(m.filterRules, FilterRule{
  82. ID: fmt.Sprintf("filter-%d-1", time.Now().UnixNano()),
  83. Order: 1,
  84. Chain: "input",
  85. Action: "accept",
  86. ConnectionState: []string{"established", "related"},
  87. Comment: "defconf: accept established,related",
  88. Disabled: false,
  89. })
  90. m.filterRules = append(m.filterRules, FilterRule{
  91. ID: fmt.Sprintf("filter-%d-2", time.Now().UnixNano()),
  92. Order: 2,
  93. Chain: "forward",
  94. Action: "accept",
  95. ConnectionState: []string{"established", "related"},
  96. Comment: "defconf: accept established,related forward",
  97. Disabled: false,
  98. })
  99. }
  100. return m.save()
  101. }
  102. func (m *FirewallManager) load() error {
  103. var failures []error
  104. for path, target := range map[string]any{FilterStorePath: &m.filterRules, NATStorePath: &m.natRules, MangleStorePath: &m.mangleRules, AddressListsStorePath: &m.addressLists} {
  105. data, err := os.ReadFile(path)
  106. if os.IsNotExist(err) {
  107. continue
  108. }
  109. if err == nil {
  110. err = json.Unmarshal(data, target)
  111. }
  112. if err != nil {
  113. failures = append(failures, fmt.Errorf("%s: %w", path, err))
  114. }
  115. }
  116. if m.filterRules == nil {
  117. m.filterRules = []FilterRule{}
  118. }
  119. if m.natRules == nil {
  120. m.natRules = []NATRule{}
  121. }
  122. if m.mangleRules == nil {
  123. m.mangleRules = []MangleRule{}
  124. }
  125. if m.addressLists == nil {
  126. m.addressLists = []AddressListEntry{}
  127. }
  128. return errors.Join(failures...)
  129. }
  130. func (m *FirewallManager) snapshot() []byte {
  131. data, _ := json.Marshal([]any{m.filterRules, m.natRules, m.mangleRules, m.addressLists})
  132. return data
  133. }
  134. func (m *FirewallManager) remember() { m.committed = m.snapshot() }
  135. func (m *FirewallManager) restore(data []byte) {
  136. var fields []json.RawMessage
  137. if json.Unmarshal(data, &fields) == nil && len(fields) == 4 {
  138. _ = json.Unmarshal(fields[0], &m.filterRules)
  139. _ = json.Unmarshal(fields[1], &m.natRules)
  140. _ = json.Unmarshal(fields[2], &m.mangleRules)
  141. _ = json.Unmarshal(fields[3], &m.addressLists)
  142. }
  143. }
  144. func (m *FirewallManager) save() error {
  145. if err := os.MkdirAll("/etc/proxmox-router/firewall", 0755); err != nil {
  146. return err
  147. }
  148. files := map[string][]byte{}
  149. for path, value := range map[string]any{FilterStorePath: m.filterRules, NATStorePath: m.natRules, MangleStorePath: m.mangleRules, AddressListsStorePath: m.addressLists} {
  150. data, err := json.MarshalIndent(value, "", " ")
  151. if err != nil {
  152. return err
  153. }
  154. files[path] = data
  155. }
  156. return saveFileGroup(files)
  157. }
  158. func (m *FirewallManager) commitChange() error {
  159. before := append([]byte(nil), m.committed...)
  160. err := m.Apply()
  161. if err == nil {
  162. err = m.save()
  163. }
  164. if err != nil {
  165. m.restore(before)
  166. rollbackErr := m.Apply()
  167. saveErr := m.save()
  168. if rollbackErr != nil || saveErr != nil {
  169. m.lastError = fmt.Sprintf("firewall change failed: %v; rollback apply: %v; rollback save: %v", err, rollbackErr, saveErr)
  170. m.applied = false
  171. return errors.New(m.lastError)
  172. }
  173. return err
  174. }
  175. m.remember()
  176. return nil
  177. }
  178. func (m *FirewallManager) Reapply() error { m.mu.Lock(); defer m.mu.Unlock(); return m.Apply() }
  179. // Apply re-synchronizes AddressLists & GeoIP sets with ipset and applies iptables
  180. func (m *FirewallManager) Apply() (result error) {
  181. defer func() {
  182. m.applied = result == nil
  183. m.lastError = ""
  184. if result != nil {
  185. m.lastError = result.Error()
  186. }
  187. }()
  188. for _, r := range m.filterRules {
  189. if err := validateRule("filter", r); err != nil {
  190. return err
  191. }
  192. }
  193. for _, r := range m.natRules {
  194. if err := validateRule("nat", r); err != nil {
  195. return err
  196. }
  197. }
  198. for _, r := range m.mangleRules {
  199. if err := validateRule("mangle", r); err != nil {
  200. return err
  201. }
  202. }
  203. // Sync GeoIP country sets
  204. if m.geoIPMgr != nil {
  205. // Also ensure any GeoIP lists explicitly referenced in active rules are loaded into ipset
  206. for _, r := range m.filterRules {
  207. if !r.Disabled {
  208. if r.SrcGeoIP != "" {
  209. if err := m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP); err != nil {
  210. return err
  211. }
  212. }
  213. if r.DstGeoIP != "" {
  214. if err := m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP); err != nil {
  215. return err
  216. }
  217. }
  218. }
  219. }
  220. for _, r := range m.natRules {
  221. if !r.Disabled {
  222. if r.SrcGeoIP != "" {
  223. if err := m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP); err != nil {
  224. return err
  225. }
  226. }
  227. if r.DstGeoIP != "" {
  228. if err := m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP); err != nil {
  229. return err
  230. }
  231. }
  232. }
  233. }
  234. for _, r := range m.mangleRules {
  235. if !r.Disabled {
  236. if r.SrcGeoIP != "" {
  237. if err := m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP); err != nil {
  238. return err
  239. }
  240. }
  241. if r.DstGeoIP != "" {
  242. if err := m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP); err != nil {
  243. return err
  244. }
  245. }
  246. }
  247. }
  248. }
  249. // Collect any custom address lists referenced in active rules
  250. var refLists []string
  251. for _, r := range m.filterRules {
  252. if !r.Disabled {
  253. if r.SrcAddressList != "" {
  254. refLists = append(refLists, r.SrcAddressList)
  255. }
  256. if r.DstAddressList != "" {
  257. refLists = append(refLists, r.DstAddressList)
  258. }
  259. if r.AddressList != "" {
  260. refLists = append(refLists, r.AddressList)
  261. }
  262. }
  263. }
  264. for _, r := range m.natRules {
  265. if !r.Disabled {
  266. if r.SrcAddressList != "" {
  267. refLists = append(refLists, r.SrcAddressList)
  268. }
  269. if r.DstAddressList != "" {
  270. refLists = append(refLists, r.DstAddressList)
  271. }
  272. if r.AddressList != "" {
  273. refLists = append(refLists, r.AddressList)
  274. }
  275. }
  276. }
  277. for _, r := range m.mangleRules {
  278. if !r.Disabled {
  279. if r.SrcAddressList != "" {
  280. refLists = append(refLists, r.SrcAddressList)
  281. }
  282. if r.DstAddressList != "" {
  283. refLists = append(refLists, r.DstAddressList)
  284. }
  285. if r.AddressList != "" {
  286. refLists = append(refLists, r.AddressList)
  287. }
  288. }
  289. }
  290. // Sync custom ipset address lists
  291. if err := SyncAddressLists(m.addressLists, refLists...); err != nil {
  292. return err
  293. }
  294. if err := ApplyDualStack(m.filterRules, m.natRules, m.mangleRules); err != nil {
  295. return err
  296. }
  297. collectUnusedAddressLists(m.addressLists, refLists)
  298. return nil
  299. }
  300. func (m *FirewallManager) GeoIP() *GeoIPManager {
  301. return m.geoIPMgr
  302. }
  303. // ==================== Filter Rules ====================
  304. func (m *FirewallManager) ListFilterRules() []FilterRule {
  305. m.mu.Lock()
  306. defer m.mu.Unlock()
  307. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  308. res := make([]FilterRule, len(m.filterRules))
  309. for i, r := range m.filterRules {
  310. res[i] = r
  311. if c, ok := counters[r.ID]; ok {
  312. res[i].Packets = c[0]
  313. res[i].Bytes = c[1]
  314. }
  315. }
  316. return res
  317. }
  318. func (m *FirewallManager) SaveFilterRule(r FilterRule) error {
  319. m.mu.Lock()
  320. defer m.mu.Unlock()
  321. if r.ID == "" {
  322. r.ID = fmt.Sprintf("filter-%d", time.Now().UnixNano())
  323. }
  324. found := false
  325. for i, existing := range m.filterRules {
  326. if existing.ID == r.ID {
  327. r.Order = existing.Order
  328. m.filterRules[i] = r
  329. found = true
  330. break
  331. }
  332. }
  333. if !found {
  334. r.Order = len(m.filterRules) + 1
  335. m.filterRules = append(m.filterRules, r)
  336. }
  337. return m.commitChange()
  338. }
  339. func (m *FirewallManager) DeleteFilterRule(id string) error {
  340. m.mu.Lock()
  341. defer m.mu.Unlock()
  342. var updated []FilterRule
  343. for _, r := range m.filterRules {
  344. if r.ID != id {
  345. updated = append(updated, r)
  346. }
  347. }
  348. for i := range updated {
  349. updated[i].Order = i + 1
  350. }
  351. m.filterRules = updated
  352. return m.commitChange()
  353. }
  354. func (m *FirewallManager) MoveFilterRule(id string, dir string) error {
  355. m.mu.Lock()
  356. defer m.mu.Unlock()
  357. idx := -1
  358. for i, r := range m.filterRules {
  359. if r.ID == id {
  360. idx = i
  361. break
  362. }
  363. }
  364. if idx == -1 {
  365. return fmt.Errorf("rule not found")
  366. }
  367. if dir == "up" && idx > 0 {
  368. m.filterRules[idx], m.filterRules[idx-1] = m.filterRules[idx-1], m.filterRules[idx]
  369. } else if dir == "down" && idx < len(m.filterRules)-1 {
  370. m.filterRules[idx], m.filterRules[idx+1] = m.filterRules[idx+1], m.filterRules[idx]
  371. }
  372. for i := range m.filterRules {
  373. m.filterRules[i].Order = i + 1
  374. }
  375. return m.commitChange()
  376. }
  377. func (m *FirewallManager) SetFilterRuleState(id string, disabled bool) error {
  378. m.mu.Lock()
  379. defer m.mu.Unlock()
  380. for i := range m.filterRules {
  381. if m.filterRules[i].ID == id {
  382. m.filterRules[i].Disabled = disabled
  383. return m.commitChange()
  384. }
  385. }
  386. return fmt.Errorf("rule not found")
  387. }
  388. // ==================== NAT Rules ====================
  389. func (m *FirewallManager) ListNATRules() []NATRule {
  390. m.mu.Lock()
  391. defer m.mu.Unlock()
  392. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  393. res := make([]NATRule, len(m.natRules))
  394. for i, r := range m.natRules {
  395. res[i] = r
  396. if c, ok := counters[r.ID]; ok {
  397. res[i].Packets = c[0]
  398. res[i].Bytes = c[1]
  399. }
  400. }
  401. return res
  402. }
  403. func (m *FirewallManager) SaveNATRule(r NATRule) error {
  404. m.mu.Lock()
  405. defer m.mu.Unlock()
  406. if r.ID == "" {
  407. r.ID = fmt.Sprintf("nat-%d", time.Now().UnixNano())
  408. }
  409. found := false
  410. for i, existing := range m.natRules {
  411. if existing.ID == r.ID {
  412. r.Order = existing.Order
  413. m.natRules[i] = r
  414. found = true
  415. break
  416. }
  417. }
  418. if !found {
  419. r.Order = len(m.natRules) + 1
  420. m.natRules = append(m.natRules, r)
  421. }
  422. return m.commitChange()
  423. }
  424. func (m *FirewallManager) DeleteNATRule(id string) error {
  425. m.mu.Lock()
  426. defer m.mu.Unlock()
  427. var updated []NATRule
  428. for _, r := range m.natRules {
  429. if r.ID != id {
  430. updated = append(updated, r)
  431. }
  432. }
  433. for i := range updated {
  434. updated[i].Order = i + 1
  435. }
  436. m.natRules = updated
  437. return m.commitChange()
  438. }
  439. func (m *FirewallManager) MoveNATRule(id string, dir string) error {
  440. m.mu.Lock()
  441. defer m.mu.Unlock()
  442. idx := -1
  443. for i, r := range m.natRules {
  444. if r.ID == id {
  445. idx = i
  446. break
  447. }
  448. }
  449. if idx == -1 {
  450. return fmt.Errorf("rule not found")
  451. }
  452. if dir == "up" && idx > 0 {
  453. m.natRules[idx], m.natRules[idx-1] = m.natRules[idx-1], m.natRules[idx]
  454. } else if dir == "down" && idx < len(m.natRules)-1 {
  455. m.natRules[idx], m.natRules[idx+1] = m.natRules[idx+1], m.natRules[idx]
  456. }
  457. for i := range m.natRules {
  458. m.natRules[i].Order = i + 1
  459. }
  460. return m.commitChange()
  461. }
  462. func (m *FirewallManager) SetNATRuleState(id string, disabled bool) error {
  463. m.mu.Lock()
  464. defer m.mu.Unlock()
  465. for i := range m.natRules {
  466. if m.natRules[i].ID == id {
  467. m.natRules[i].Disabled = disabled
  468. return m.commitChange()
  469. }
  470. }
  471. return fmt.Errorf("rule not found")
  472. }
  473. // ==================== Mangle Rules ====================
  474. func (m *FirewallManager) ListMangleRules() []MangleRule {
  475. m.mu.Lock()
  476. defer m.mu.Unlock()
  477. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  478. res := make([]MangleRule, len(m.mangleRules))
  479. for i, r := range m.mangleRules {
  480. res[i] = r
  481. if c, ok := counters[r.ID]; ok {
  482. res[i].Packets = c[0]
  483. res[i].Bytes = c[1]
  484. }
  485. }
  486. return res
  487. }
  488. func (m *FirewallManager) SaveMangleRule(r MangleRule) error {
  489. m.mu.Lock()
  490. defer m.mu.Unlock()
  491. if r.ID == "" {
  492. r.ID = fmt.Sprintf("mangle-%d", time.Now().UnixNano())
  493. }
  494. found := false
  495. for i, existing := range m.mangleRules {
  496. if existing.ID == r.ID {
  497. r.Order = existing.Order
  498. m.mangleRules[i] = r
  499. found = true
  500. break
  501. }
  502. }
  503. if !found {
  504. r.Order = len(m.mangleRules) + 1
  505. m.mangleRules = append(m.mangleRules, r)
  506. }
  507. return m.commitChange()
  508. }
  509. func (m *FirewallManager) DeleteMangleRule(id string) error {
  510. m.mu.Lock()
  511. defer m.mu.Unlock()
  512. var updated []MangleRule
  513. for _, r := range m.mangleRules {
  514. if r.ID != id {
  515. updated = append(updated, r)
  516. }
  517. }
  518. for i := range updated {
  519. updated[i].Order = i + 1
  520. }
  521. m.mangleRules = updated
  522. return m.commitChange()
  523. }
  524. func (m *FirewallManager) MoveMangleRule(id string, dir string) error {
  525. m.mu.Lock()
  526. defer m.mu.Unlock()
  527. idx := -1
  528. for i, r := range m.mangleRules {
  529. if r.ID == id {
  530. idx = i
  531. break
  532. }
  533. }
  534. if idx == -1 {
  535. return fmt.Errorf("rule not found")
  536. }
  537. if dir == "up" && idx > 0 {
  538. m.mangleRules[idx], m.mangleRules[idx-1] = m.mangleRules[idx-1], m.mangleRules[idx]
  539. } else if dir == "down" && idx < len(m.mangleRules)-1 {
  540. m.mangleRules[idx], m.mangleRules[idx+1] = m.mangleRules[idx+1], m.mangleRules[idx]
  541. }
  542. for i := range m.mangleRules {
  543. m.mangleRules[i].Order = i + 1
  544. }
  545. return m.commitChange()
  546. }
  547. func (m *FirewallManager) SetMangleRuleState(id string, disabled bool) error {
  548. m.mu.Lock()
  549. defer m.mu.Unlock()
  550. for i := range m.mangleRules {
  551. if m.mangleRules[i].ID == id {
  552. m.mangleRules[i].Disabled = disabled
  553. return m.commitChange()
  554. }
  555. }
  556. return fmt.Errorf("rule not found")
  557. }
  558. // ==================== Address Lists ====================
  559. func (m *FirewallManager) ListAddressLists() []AddressListEntry {
  560. m.mu.Lock()
  561. defer m.mu.Unlock()
  562. res := make([]AddressListEntry, len(m.addressLists))
  563. copy(res, m.addressLists)
  564. return append(res, m.dynamicEntries()...)
  565. }
  566. func (m *FirewallManager) SaveAddressListEntry(e AddressListEntry) error {
  567. if e.Dynamic || e.Timeout != 0 {
  568. return fmt.Errorf("dynamic entries are managed by packet rules")
  569. }
  570. m.mu.Lock()
  571. defer m.mu.Unlock()
  572. if e.ID == "" {
  573. e.ID = fmt.Sprintf("addrlist-%d", time.Now().UnixNano())
  574. }
  575. found := false
  576. for i, existing := range m.addressLists {
  577. if existing.ID == e.ID {
  578. m.addressLists[i] = e
  579. found = true
  580. break
  581. }
  582. }
  583. if !found {
  584. m.addressLists = append(m.addressLists, e)
  585. }
  586. return m.commitChange()
  587. }
  588. func (m *FirewallManager) DeleteAddressListEntry(id string) error {
  589. if strings.HasPrefix(id, "dynamic-") {
  590. m.mu.Lock()
  591. defer m.mu.Unlock()
  592. return deleteDynamicEntry(id)
  593. }
  594. m.mu.Lock()
  595. defer m.mu.Unlock()
  596. var updated []AddressListEntry
  597. for _, e := range m.addressLists {
  598. if e.ID != id {
  599. updated = append(updated, e)
  600. }
  601. }
  602. m.addressLists = updated
  603. return m.commitChange()
  604. }
  605. func (m *FirewallManager) SetAddressListEntryState(id string, disabled bool) error {
  606. m.mu.Lock()
  607. defer m.mu.Unlock()
  608. for i := range m.addressLists {
  609. if m.addressLists[i].ID == id {
  610. m.addressLists[i].Disabled = disabled
  611. return m.commitChange()
  612. }
  613. }
  614. return fmt.Errorf("address list entry not found")
  615. }
  616. func (m *FirewallManager) ResetCounters() error {
  617. m.mu.Lock()
  618. defer m.mu.Unlock()
  619. for _, binary := range []string{"iptables", "ip6tables"} {
  620. out, err := exec.Command(binary + "-save").Output()
  621. if err != nil {
  622. if binary == "iptables" {
  623. return err
  624. }
  625. continue
  626. }
  627. table := ""
  628. for _, line := range strings.Split(string(out), "\n") {
  629. if strings.HasPrefix(line, "*") {
  630. table = line[1:]
  631. }
  632. if strings.HasPrefix(line, ":DT_") {
  633. chain := strings.Fields(line)[0][1:]
  634. if err := exec.Command(binary, "-t", table, "-Z", chain).Run(); err != nil {
  635. return err
  636. }
  637. }
  638. }
  639. }
  640. return nil
  641. }
  642. func (m *FirewallManager) GetSummary() FirewallSummary {
  643. m.mu.Lock()
  644. defer m.mu.Unlock()
  645. conns, connErr := ListActiveConnections()
  646. connectionError := ""
  647. if connErr != nil {
  648. connectionError = connErr.Error()
  649. }
  650. geoCount := 0
  651. geoSubnets := 0
  652. if m.geoIPMgr != nil {
  653. status := m.geoIPMgr.GetStatus()
  654. if c, ok := status["enabled_count"].(int); ok {
  655. geoCount = c
  656. }
  657. if s, ok := status["total_subnets"].(int); ok {
  658. geoSubnets = s
  659. }
  660. }
  661. return FirewallSummary{
  662. FilterRulesCount: len(m.filterRules),
  663. NATRulesCount: len(m.natRules),
  664. MangleRulesCount: len(m.mangleRules),
  665. AddressListsCount: len(m.addressLists),
  666. GeoCountriesCount: geoCount,
  667. GeoSubnetsCount: geoSubnets,
  668. ActiveConnections: len(conns),
  669. ConnectionsError: connectionError,
  670. Applied: m.applied, LastError: m.lastError,
  671. }
  672. }
  673. // RenameInterface updates firewall rule interface names when an interface is renamed.
  674. func (m *FirewallManager) RenameInterface(oldName, newName string) {
  675. m.mu.Lock()
  676. defer m.mu.Unlock()
  677. changed := false
  678. for i := range m.filterRules {
  679. if m.filterRules[i].InInterface == oldName {
  680. m.filterRules[i].InInterface = newName
  681. changed = true
  682. }
  683. if m.filterRules[i].OutInterface == oldName {
  684. m.filterRules[i].OutInterface = newName
  685. changed = true
  686. }
  687. }
  688. for i := range m.natRules {
  689. if m.natRules[i].InInterface == oldName {
  690. m.natRules[i].InInterface = newName
  691. changed = true
  692. }
  693. if m.natRules[i].OutInterface == oldName {
  694. m.natRules[i].OutInterface = newName
  695. changed = true
  696. }
  697. }
  698. for i := range m.mangleRules {
  699. if m.mangleRules[i].InInterface == oldName {
  700. m.mangleRules[i].InInterface = newName
  701. changed = true
  702. }
  703. if m.mangleRules[i].OutInterface == oldName {
  704. m.mangleRules[i].OutInterface = newName
  705. changed = true
  706. }
  707. }
  708. if changed {
  709. if err := m.commitChange(); err != nil {
  710. m.lastError = err.Error()
  711. log.Printf("Firewall rename failed: %v", err)
  712. }
  713. }
  714. }