| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- package firewall
- import (
- "fmt"
- "os/exec"
- "regexp"
- "strings"
- )
- func ApplyDualStack(filters []FilterRule, nats []NATRule, mangles []MangleRule) error {
- for _, r := range filters {
- if err := validateRule("filter", r); err != nil {
- return err
- }
- }
- for _, r := range nats {
- if err := validateRule("nat", r); err != nil {
- return err
- }
- }
- for _, r := range mangles {
- if err := validateRule("mangle", r); err != nil {
- return err
- }
- }
- var f4, f6 []FilterRule
- var n4, n6 []NATRule
- var m4, m6 []MangleRule
- validate := func(family int, srcGeo, dstGeo string) error {
- if family != 0 && family != 4 && family != 6 {
- return fmt.Errorf("IP family must be 4 or 6")
- }
- if family == 6 && (srcGeo != "" || dstGeo != "") {
- return fmt.Errorf("GeoIP datasets currently support IPv4 only; remove GeoIP matching from the IPv6 rule")
- }
- return nil
- }
- for _, r := range filters {
- if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
- return err
- }
- if r.IPVersion == 6 {
- if r.Protocol == "icmp" {
- r.Protocol = "ipv6-icmp"
- }
- f6 = append(f6, r)
- } else {
- f4 = append(f4, r)
- }
- }
- for _, r := range nats {
- if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
- return err
- }
- if r.IPVersion == 6 {
- n6 = append(n6, r)
- } else {
- n4 = append(n4, r)
- }
- }
- for _, r := range mangles {
- if err := validate(r.IPVersion, r.SrcGeoIP, r.DstGeoIP); err != nil {
- return err
- }
- if r.IPVersion == 6 {
- m6 = append(m6, r)
- } else {
- m4 = append(m4, r)
- }
- }
- type family struct {
- binary string
- payload string
- }
- families := []family{{"iptables", BuildIPTablesRestore(f4, n4, m4)}}
- six := BuildIPTablesRestore(f6, n6, m6)
- sets := regexp.MustCompile(`(--(?:match|add)-set) ([^ ]+)`)
- six = sets.ReplaceAllString(six, "${1} ${2}_v6")
- live6, err := exec.Command("ip6tables-save").Output()
- need6 := len(f6)+len(n6)+len(m6) > 0 || strings.Contains(string(live6), "DT_")
- if need6 {
- if err != nil {
- return fmt.Errorf("IPv6 firewall unavailable: %w", err)
- }
- families = append(families, family{"ip6tables", six})
- }
- // Validate all families before applying either one.
- for i := range families {
- live, err := exec.Command(families[i].binary+"-save", "-c").Output()
- if err != nil {
- return err
- }
- families[i].payload = ownedPayload(families[i].payload, string(live))
- cmd := exec.Command(families[i].binary+"-restore", "--noflush", "-c", "--test")
- cmd.Stdin = strings.NewReader(families[i].payload)
- if out, err := cmd.CombinedOutput(); err != nil {
- return fmt.Errorf("%s validation: %s: %w", families[i].binary, out, err)
- }
- }
- for _, family := range families {
- cmd := exec.Command(family.binary+"-restore", "--noflush", "-c")
- cmd.Stdin = strings.NewReader(family.payload)
- if out, err := cmd.CombinedOutput(); err != nil {
- return fmt.Errorf("%s apply: %s: %w", family.binary, out, err)
- }
- }
- return nil
- }
|