family.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package firewall
  2. import (
  3. "fmt"
  4. "os/exec"
  5. "regexp"
  6. "strings"
  7. )
  8. func ApplyDualStack(filters []FilterRule, nats []NATRule, mangles []MangleRule) error {
  9. for _, r := range filters {
  10. if err := validateRule("filter", r); err != nil {
  11. return err
  12. }
  13. }
  14. for _, r := range nats {
  15. if err := validateRule("nat", r); err != nil {
  16. return err
  17. }
  18. }
  19. for _, r := range mangles {
  20. if err := validateRule("mangle", r); err != nil {
  21. return err
  22. }
  23. }
  24. var f4, f6 []FilterRule
  25. var n4, n6 []NATRule
  26. var m4, m6 []MangleRule
  27. validate := func(family int, srcGeo, dstGeo string) error {
  28. if family != 0 && family != 4 && family != 6 {
  29. return fmt.Errorf("IP family must be 4 or 6")
  30. }
  31. if family == 6 && (srcGeo != "" || dstGeo != "") {
  32. return fmt.Errorf("GeoIP datasets currently support IPv4 only; remove GeoIP matching from the IPv6 rule")
  33. }
  34. return nil
  35. }
  36. for _, r := range filters {
  37. if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
  38. return err
  39. }
  40. if r.IPVersion == 6 {
  41. if r.Protocol == "icmp" {
  42. r.Protocol = "ipv6-icmp"
  43. }
  44. f6 = append(f6, r)
  45. } else {
  46. f4 = append(f4, r)
  47. }
  48. }
  49. for _, r := range nats {
  50. if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
  51. return err
  52. }
  53. if r.IPVersion == 6 {
  54. n6 = append(n6, r)
  55. } else {
  56. n4 = append(n4, r)
  57. }
  58. }
  59. for _, r := range mangles {
  60. if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
  61. return err
  62. }
  63. if r.IPVersion == 6 {
  64. m6 = append(m6, r)
  65. } else {
  66. m4 = append(m4, r)
  67. }
  68. }
  69. type family struct {
  70. binary string
  71. payload string
  72. }
  73. families := []family{{"iptables", BuildIPTablesRestore(f4, n4, m4)}}
  74. six := BuildIPTablesRestore(f6, n6, m6)
  75. sets := regexp.MustCompile(`(--(?:match|add)-set) ([^ ]+)`)
  76. six = sets.ReplaceAllString(six, "${1} ${2}_v6")
  77. live6, err := exec.Command("ip6tables-save").Output()
  78. need6 := len(f6)+len(n6)+len(m6) > 0 || strings.Contains(string(live6), "DT_")
  79. if need6 {
  80. if err != nil {
  81. return fmt.Errorf("IPv6 firewall unavailable: %w", err)
  82. }
  83. families = append(families, family{"ip6tables", six})
  84. }
  85. // Validate all families before applying either one.
  86. for i := range families {
  87. live, err := exec.Command(families[i].binary+"-save", "-c").Output()
  88. if err != nil {
  89. return err
  90. }
  91. families[i].payload = ownedPayload(families[i].payload, string(live))
  92. cmd := exec.Command(families[i].binary+"-restore", "--noflush", "-c", "--test")
  93. cmd.Stdin = strings.NewReader(families[i].payload)
  94. if out, err := cmd.CombinedOutput(); err != nil {
  95. return fmt.Errorf("%s validation: %s: %w", families[i].binary, out, err)
  96. }
  97. }
  98. for _, family := range families {
  99. cmd := exec.Command(family.binary+"-restore", "--noflush", "-c")
  100. cmd.Stdin = strings.NewReader(family.payload)
  101. if out, err := cmd.CombinedOutput(); err != nil {
  102. return fmt.Errorf("%s apply: %s: %w", family.binary, out, err)
  103. }
  104. }
  105. return nil
  106. }