manager.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. package firewall
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "sync"
  8. "time"
  9. )
  10. const (
  11. FilterStorePath = "/etc/proxmox-router/firewall/filter.json"
  12. NATStorePath = "/etc/proxmox-router/firewall/nat.json"
  13. MangleStorePath = "/etc/proxmox-router/firewall/mangle.json"
  14. AddressListsStorePath = "/etc/proxmox-router/firewall/address_lists.json"
  15. )
  16. type FirewallManager struct {
  17. mu sync.Mutex
  18. filterRules []FilterRule
  19. natRules []NATRule
  20. mangleRules []MangleRule
  21. addressLists []AddressListEntry
  22. geoIPMgr *GeoIPManager
  23. }
  24. func NewFirewallManager() *FirewallManager {
  25. m := &FirewallManager{
  26. filterRules: make([]FilterRule, 0),
  27. natRules: make([]NATRule, 0),
  28. mangleRules: make([]MangleRule, 0),
  29. addressLists: make([]AddressListEntry, 0),
  30. geoIPMgr: NewGeoIPManager(),
  31. }
  32. m.load()
  33. // If clean installation and no rules exist, initialize default sensible router rules (like RouterOS default firewall)
  34. if len(m.filterRules) == 0 || len(m.natRules) == 0 {
  35. m.initDefaultRules()
  36. }
  37. _ = m.Apply()
  38. return m
  39. }
  40. func (m *FirewallManager) initDefaultRules() {
  41. if len(m.natRules) == 0 {
  42. // Default Masquerade NAT
  43. m.natRules = append(m.natRules, NATRule{
  44. ID: fmt.Sprintf("nat-%d", time.Now().UnixNano()),
  45. Order: 1,
  46. Chain: "srcnat",
  47. Action: "masquerade",
  48. Comment: "defconf: masquerade outgoing traffic",
  49. Disabled: false,
  50. })
  51. }
  52. if len(m.filterRules) == 0 {
  53. // Default Fasttrack / Established,Related Filter
  54. m.filterRules = append(m.filterRules, FilterRule{
  55. ID: fmt.Sprintf("filter-%d-1", time.Now().UnixNano()),
  56. Order: 1,
  57. Chain: "input",
  58. Action: "accept",
  59. ConnectionState: []string{"established", "related"},
  60. Comment: "defconf: accept established,related",
  61. Disabled: false,
  62. })
  63. m.filterRules = append(m.filterRules, FilterRule{
  64. ID: fmt.Sprintf("filter-%d-2", time.Now().UnixNano()),
  65. Order: 2,
  66. Chain: "forward",
  67. Action: "accept",
  68. ConnectionState: []string{"established", "related"},
  69. Comment: "defconf: accept established,related forward",
  70. Disabled: false,
  71. })
  72. }
  73. _ = m.save()
  74. }
  75. func (m *FirewallManager) load() {
  76. if data, err := os.ReadFile(FilterStorePath); err == nil {
  77. _ = json.Unmarshal(data, &m.filterRules)
  78. }
  79. if data, err := os.ReadFile(NATStorePath); err == nil {
  80. _ = json.Unmarshal(data, &m.natRules)
  81. }
  82. if data, err := os.ReadFile(MangleStorePath); err == nil {
  83. _ = json.Unmarshal(data, &m.mangleRules)
  84. }
  85. if data, err := os.ReadFile(AddressListsStorePath); err == nil {
  86. _ = json.Unmarshal(data, &m.addressLists)
  87. }
  88. if m.filterRules == nil {
  89. m.filterRules = make([]FilterRule, 0)
  90. }
  91. if m.natRules == nil {
  92. m.natRules = make([]NATRule, 0)
  93. }
  94. if m.mangleRules == nil {
  95. m.mangleRules = make([]MangleRule, 0)
  96. }
  97. if m.addressLists == nil {
  98. m.addressLists = make([]AddressListEntry, 0)
  99. }
  100. }
  101. func (m *FirewallManager) save() error {
  102. _ = os.MkdirAll("/etc/proxmox-router/firewall", 0755)
  103. if d, err := json.MarshalIndent(m.filterRules, "", " "); err == nil {
  104. _ = os.WriteFile(FilterStorePath, d, 0600)
  105. }
  106. if d, err := json.MarshalIndent(m.natRules, "", " "); err == nil {
  107. _ = os.WriteFile(NATStorePath, d, 0600)
  108. }
  109. if d, err := json.MarshalIndent(m.mangleRules, "", " "); err == nil {
  110. _ = os.WriteFile(MangleStorePath, d, 0600)
  111. }
  112. if d, err := json.MarshalIndent(m.addressLists, "", " "); err == nil {
  113. _ = os.WriteFile(AddressListsStorePath, d, 0600)
  114. }
  115. return nil
  116. }
  117. // Apply re-synchronizes AddressLists & GeoIP sets with ipset and applies iptables
  118. func (m *FirewallManager) Apply() error {
  119. // Sync GeoIP country sets
  120. if m.geoIPMgr != nil {
  121. _ = m.geoIPMgr.ApplyAllEnabledSets()
  122. // Also ensure any GeoIP lists explicitly referenced in active rules are loaded into ipset
  123. for _, r := range m.filterRules {
  124. if !r.Disabled {
  125. if r.SrcGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP) }
  126. if r.DstGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP) }
  127. }
  128. }
  129. for _, r := range m.natRules {
  130. if !r.Disabled {
  131. if r.SrcGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP) }
  132. if r.DstGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP) }
  133. }
  134. }
  135. for _, r := range m.mangleRules {
  136. if !r.Disabled {
  137. if r.SrcGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.SrcGeoIP) }
  138. if r.DstGeoIP != "" { _ = m.geoIPMgr.EnsureCountryOrPreset(r.DstGeoIP) }
  139. }
  140. }
  141. }
  142. // Collect any custom address lists referenced in active rules
  143. var refLists []string
  144. for _, r := range m.filterRules {
  145. if !r.Disabled {
  146. if r.SrcAddressList != "" { refLists = append(refLists, r.SrcAddressList) }
  147. if r.DstAddressList != "" { refLists = append(refLists, r.DstAddressList) }
  148. if r.AddressList != "" { refLists = append(refLists, r.AddressList) }
  149. }
  150. }
  151. for _, r := range m.natRules {
  152. if !r.Disabled {
  153. if r.SrcAddressList != "" { refLists = append(refLists, r.SrcAddressList) }
  154. if r.DstAddressList != "" { refLists = append(refLists, r.DstAddressList) }
  155. if r.AddressList != "" { refLists = append(refLists, r.AddressList) }
  156. }
  157. }
  158. for _, r := range m.mangleRules {
  159. if !r.Disabled {
  160. if r.SrcAddressList != "" { refLists = append(refLists, r.SrcAddressList) }
  161. if r.DstAddressList != "" { refLists = append(refLists, r.DstAddressList) }
  162. if r.AddressList != "" { refLists = append(refLists, r.AddressList) }
  163. }
  164. }
  165. // Sync custom ipset address lists
  166. _ = SyncAddressLists(m.addressLists, refLists...)
  167. payload := BuildIPTablesRestore(m.filterRules, m.natRules, m.mangleRules)
  168. return ApplyIPTables(payload)
  169. }
  170. func (m *FirewallManager) GeoIP() *GeoIPManager {
  171. return m.geoIPMgr
  172. }
  173. // ==================== Filter Rules ====================
  174. func (m *FirewallManager) ListFilterRules() []FilterRule {
  175. m.mu.Lock()
  176. defer m.mu.Unlock()
  177. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  178. res := make([]FilterRule, len(m.filterRules))
  179. for i, r := range m.filterRules {
  180. res[i] = r
  181. if c, ok := counters[r.ID]; ok {
  182. res[i].Packets = c[0]
  183. res[i].Bytes = c[1]
  184. }
  185. }
  186. return res
  187. }
  188. func (m *FirewallManager) SaveFilterRule(r FilterRule) error {
  189. m.mu.Lock()
  190. defer m.mu.Unlock()
  191. if r.ID == "" {
  192. r.ID = fmt.Sprintf("filter-%d", time.Now().UnixNano())
  193. }
  194. found := false
  195. for i, existing := range m.filterRules {
  196. if existing.ID == r.ID {
  197. r.Order = existing.Order
  198. m.filterRules[i] = r
  199. found = true
  200. break
  201. }
  202. }
  203. if !found {
  204. r.Order = len(m.filterRules) + 1
  205. m.filterRules = append(m.filterRules, r)
  206. }
  207. _ = m.save()
  208. return m.Apply()
  209. }
  210. func (m *FirewallManager) DeleteFilterRule(id string) error {
  211. m.mu.Lock()
  212. defer m.mu.Unlock()
  213. var updated []FilterRule
  214. for _, r := range m.filterRules {
  215. if r.ID != id {
  216. updated = append(updated, r)
  217. }
  218. }
  219. for i := range updated {
  220. updated[i].Order = i + 1
  221. }
  222. m.filterRules = updated
  223. _ = m.save()
  224. return m.Apply()
  225. }
  226. func (m *FirewallManager) MoveFilterRule(id string, dir string) error {
  227. m.mu.Lock()
  228. defer m.mu.Unlock()
  229. idx := -1
  230. for i, r := range m.filterRules {
  231. if r.ID == id {
  232. idx = i
  233. break
  234. }
  235. }
  236. if idx == -1 {
  237. return fmt.Errorf("rule not found")
  238. }
  239. if dir == "up" && idx > 0 {
  240. m.filterRules[idx], m.filterRules[idx-1] = m.filterRules[idx-1], m.filterRules[idx]
  241. } else if dir == "down" && idx < len(m.filterRules)-1 {
  242. m.filterRules[idx], m.filterRules[idx+1] = m.filterRules[idx+1], m.filterRules[idx]
  243. }
  244. for i := range m.filterRules {
  245. m.filterRules[i].Order = i + 1
  246. }
  247. _ = m.save()
  248. return m.Apply()
  249. }
  250. func (m *FirewallManager) SetFilterRuleState(id string, disabled bool) error {
  251. m.mu.Lock()
  252. defer m.mu.Unlock()
  253. for i := range m.filterRules {
  254. if m.filterRules[i].ID == id {
  255. m.filterRules[i].Disabled = disabled
  256. _ = m.save()
  257. return m.Apply()
  258. }
  259. }
  260. return fmt.Errorf("rule not found")
  261. }
  262. // ==================== NAT Rules ====================
  263. func (m *FirewallManager) ListNATRules() []NATRule {
  264. m.mu.Lock()
  265. defer m.mu.Unlock()
  266. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  267. res := make([]NATRule, len(m.natRules))
  268. for i, r := range m.natRules {
  269. res[i] = r
  270. if c, ok := counters[r.ID]; ok {
  271. res[i].Packets = c[0]
  272. res[i].Bytes = c[1]
  273. }
  274. }
  275. return res
  276. }
  277. func (m *FirewallManager) SaveNATRule(r NATRule) error {
  278. m.mu.Lock()
  279. defer m.mu.Unlock()
  280. if r.ID == "" {
  281. r.ID = fmt.Sprintf("nat-%d", time.Now().UnixNano())
  282. }
  283. found := false
  284. for i, existing := range m.natRules {
  285. if existing.ID == r.ID {
  286. r.Order = existing.Order
  287. m.natRules[i] = r
  288. found = true
  289. break
  290. }
  291. }
  292. if !found {
  293. r.Order = len(m.natRules) + 1
  294. m.natRules = append(m.natRules, r)
  295. }
  296. _ = m.save()
  297. return m.Apply()
  298. }
  299. func (m *FirewallManager) DeleteNATRule(id string) error {
  300. m.mu.Lock()
  301. defer m.mu.Unlock()
  302. var updated []NATRule
  303. for _, r := range m.natRules {
  304. if r.ID != id {
  305. updated = append(updated, r)
  306. }
  307. }
  308. for i := range updated {
  309. updated[i].Order = i + 1
  310. }
  311. m.natRules = updated
  312. _ = m.save()
  313. return m.Apply()
  314. }
  315. func (m *FirewallManager) MoveNATRule(id string, dir string) error {
  316. m.mu.Lock()
  317. defer m.mu.Unlock()
  318. idx := -1
  319. for i, r := range m.natRules {
  320. if r.ID == id {
  321. idx = i
  322. break
  323. }
  324. }
  325. if idx == -1 {
  326. return fmt.Errorf("rule not found")
  327. }
  328. if dir == "up" && idx > 0 {
  329. m.natRules[idx], m.natRules[idx-1] = m.natRules[idx-1], m.natRules[idx]
  330. } else if dir == "down" && idx < len(m.natRules)-1 {
  331. m.natRules[idx], m.natRules[idx+1] = m.natRules[idx+1], m.natRules[idx]
  332. }
  333. for i := range m.natRules {
  334. m.natRules[i].Order = i + 1
  335. }
  336. _ = m.save()
  337. return m.Apply()
  338. }
  339. func (m *FirewallManager) SetNATRuleState(id string, disabled bool) error {
  340. m.mu.Lock()
  341. defer m.mu.Unlock()
  342. for i := range m.natRules {
  343. if m.natRules[i].ID == id {
  344. m.natRules[i].Disabled = disabled
  345. _ = m.save()
  346. return m.Apply()
  347. }
  348. }
  349. return fmt.Errorf("rule not found")
  350. }
  351. // ==================== Mangle Rules ====================
  352. func (m *FirewallManager) ListMangleRules() []MangleRule {
  353. m.mu.Lock()
  354. defer m.mu.Unlock()
  355. counters, _ := ReadRuleCounters(m.filterRules, m.natRules, m.mangleRules)
  356. res := make([]MangleRule, len(m.mangleRules))
  357. for i, r := range m.mangleRules {
  358. res[i] = r
  359. if c, ok := counters[r.ID]; ok {
  360. res[i].Packets = c[0]
  361. res[i].Bytes = c[1]
  362. }
  363. }
  364. return res
  365. }
  366. func (m *FirewallManager) SaveMangleRule(r MangleRule) error {
  367. m.mu.Lock()
  368. defer m.mu.Unlock()
  369. if r.ID == "" {
  370. r.ID = fmt.Sprintf("mangle-%d", time.Now().UnixNano())
  371. }
  372. found := false
  373. for i, existing := range m.mangleRules {
  374. if existing.ID == r.ID {
  375. r.Order = existing.Order
  376. m.mangleRules[i] = r
  377. found = true
  378. break
  379. }
  380. }
  381. if !found {
  382. r.Order = len(m.mangleRules) + 1
  383. m.mangleRules = append(m.mangleRules, r)
  384. }
  385. _ = m.save()
  386. return m.Apply()
  387. }
  388. func (m *FirewallManager) DeleteMangleRule(id string) error {
  389. m.mu.Lock()
  390. defer m.mu.Unlock()
  391. var updated []MangleRule
  392. for _, r := range m.mangleRules {
  393. if r.ID != id {
  394. updated = append(updated, r)
  395. }
  396. }
  397. for i := range updated {
  398. updated[i].Order = i + 1
  399. }
  400. m.mangleRules = updated
  401. _ = m.save()
  402. return m.Apply()
  403. }
  404. func (m *FirewallManager) MoveMangleRule(id string, dir string) error {
  405. m.mu.Lock()
  406. defer m.mu.Unlock()
  407. idx := -1
  408. for i, r := range m.mangleRules {
  409. if r.ID == id {
  410. idx = i
  411. break
  412. }
  413. }
  414. if idx == -1 {
  415. return fmt.Errorf("rule not found")
  416. }
  417. if dir == "up" && idx > 0 {
  418. m.mangleRules[idx], m.mangleRules[idx-1] = m.mangleRules[idx-1], m.mangleRules[idx]
  419. } else if dir == "down" && idx < len(m.mangleRules)-1 {
  420. m.mangleRules[idx], m.mangleRules[idx+1] = m.mangleRules[idx+1], m.mangleRules[idx]
  421. }
  422. for i := range m.mangleRules {
  423. m.mangleRules[i].Order = i + 1
  424. }
  425. _ = m.save()
  426. return m.Apply()
  427. }
  428. func (m *FirewallManager) SetMangleRuleState(id string, disabled bool) error {
  429. m.mu.Lock()
  430. defer m.mu.Unlock()
  431. for i := range m.mangleRules {
  432. if m.mangleRules[i].ID == id {
  433. m.mangleRules[i].Disabled = disabled
  434. _ = m.save()
  435. return m.Apply()
  436. }
  437. }
  438. return fmt.Errorf("rule not found")
  439. }
  440. // ==================== Address Lists ====================
  441. func (m *FirewallManager) ListAddressLists() []AddressListEntry {
  442. m.mu.Lock()
  443. defer m.mu.Unlock()
  444. res := make([]AddressListEntry, len(m.addressLists))
  445. copy(res, m.addressLists)
  446. return res
  447. }
  448. func (m *FirewallManager) SaveAddressListEntry(e AddressListEntry) error {
  449. m.mu.Lock()
  450. defer m.mu.Unlock()
  451. if e.ID == "" {
  452. e.ID = fmt.Sprintf("addrlist-%d", time.Now().UnixNano())
  453. }
  454. found := false
  455. for i, existing := range m.addressLists {
  456. if existing.ID == e.ID {
  457. m.addressLists[i] = e
  458. found = true
  459. break
  460. }
  461. }
  462. if !found {
  463. m.addressLists = append(m.addressLists, e)
  464. }
  465. _ = m.save()
  466. return m.Apply()
  467. }
  468. func (m *FirewallManager) DeleteAddressListEntry(id string) error {
  469. m.mu.Lock()
  470. defer m.mu.Unlock()
  471. var updated []AddressListEntry
  472. for _, e := range m.addressLists {
  473. if e.ID != id {
  474. updated = append(updated, e)
  475. }
  476. }
  477. m.addressLists = updated
  478. _ = m.save()
  479. return m.Apply()
  480. }
  481. func (m *FirewallManager) SetAddressListEntryState(id string, disabled bool) error {
  482. m.mu.Lock()
  483. defer m.mu.Unlock()
  484. for i := range m.addressLists {
  485. if m.addressLists[i].ID == id {
  486. m.addressLists[i].Disabled = disabled
  487. _ = m.save()
  488. return m.Apply()
  489. }
  490. }
  491. return fmt.Errorf("address list entry not found")
  492. }
  493. func (m *FirewallManager) ResetCounters() error {
  494. _ = exec.Command("iptables", "-Z").Run()
  495. return nil
  496. }
  497. func (m *FirewallManager) GetSummary() FirewallSummary {
  498. m.mu.Lock()
  499. defer m.mu.Unlock()
  500. conns, _ := ListActiveConnections()
  501. geoCount := 0
  502. geoSubnets := 0
  503. if m.geoIPMgr != nil {
  504. status := m.geoIPMgr.GetStatus()
  505. if c, ok := status["enabled_count"].(int); ok {
  506. geoCount = c
  507. }
  508. if s, ok := status["total_subnets"].(int); ok {
  509. geoSubnets = s
  510. }
  511. }
  512. return FirewallSummary{
  513. FilterRulesCount: len(m.filterRules),
  514. NATRulesCount: len(m.natRules),
  515. MangleRulesCount: len(m.mangleRules),
  516. AddressListsCount: len(m.addressLists),
  517. GeoCountriesCount: geoCount,
  518. GeoSubnetsCount: geoSubnets,
  519. ActiveConnections: len(conns),
  520. }
  521. }
  522. // RenameInterface updates firewall rule interface names when an interface is renamed.
  523. func (m *FirewallManager) RenameInterface(oldName, newName string) {
  524. m.mu.Lock()
  525. defer m.mu.Unlock()
  526. changed := false
  527. for i := range m.filterRules {
  528. if m.filterRules[i].InInterface == oldName {
  529. m.filterRules[i].InInterface = newName
  530. changed = true
  531. }
  532. if m.filterRules[i].OutInterface == oldName {
  533. m.filterRules[i].OutInterface = newName
  534. changed = true
  535. }
  536. }
  537. for i := range m.natRules {
  538. if m.natRules[i].InInterface == oldName {
  539. m.natRules[i].InInterface = newName
  540. changed = true
  541. }
  542. if m.natRules[i].OutInterface == oldName {
  543. m.natRules[i].OutInterface = newName
  544. changed = true
  545. }
  546. }
  547. for i := range m.mangleRules {
  548. if m.mangleRules[i].InInterface == oldName {
  549. m.mangleRules[i].InInterface = newName
  550. changed = true
  551. }
  552. if m.mangleRules[i].OutInterface == oldName {
  553. m.mangleRules[i].OutInterface = newName
  554. changed = true
  555. }
  556. }
  557. if changed {
  558. _ = m.save()
  559. _ = m.Apply()
  560. }
  561. }