manager.go 17 KB

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