family.go 2.6 KB

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