| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709 |
- package firewall
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "hash/fnv"
- "net"
- "os"
- "os/exec"
- "sort"
- "strconv"
- "strings"
- )
- // MarkNameToID converts a string mark name into a deterministic 32-bit fwmark integer
- func MarkNameToID(name string) int {
- if name == "" {
- return 0
- }
- if id, err := strconv.Atoi(name); err == nil && id >= 0 {
- return id
- }
- h := fnv.New32a()
- h.Write([]byte(name))
- val := int(h.Sum32() % 65000)
- if val < 100 {
- val += 100
- }
- return val
- }
- // ReadRuleCounters reads `iptables-save -c` and populates the Packets and Bytes fields for all rules
- func ReadRuleCounters(filterRules []FilterRule, natRules []NATRule, mangleRules []MangleRule) (map[string][2]uint64, error) {
- out, err := exec.Command("iptables-save", "-c").Output()
- if err != nil {
- return nil, err
- }
- if v6, err := exec.Command("ip6tables-save", "-c").Output(); err == nil {
- out = append(out, '\n')
- out = append(out, v6...)
- }
- counters := make(map[string][2]uint64)
- scanner := bufio.NewScanner(bytes.NewReader(out))
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if !strings.HasPrefix(line, "[") {
- continue
- }
- closeBracket := strings.Index(line, "]")
- if closeBracket == -1 {
- continue
- }
- countsStr := line[1:closeBracket]
- parts := strings.Split(countsStr, ":")
- if len(parts) != 2 {
- continue
- }
- pkts, _ := strconv.ParseUint(parts[0], 10, 64)
- bts, _ := strconv.ParseUint(parts[1], 10, 64)
- commentIdx := strings.Index(line, "--comment \"id:")
- if commentIdx != -1 {
- sub := line[commentIdx+len("--comment \"id:"):]
- endQuote := strings.Index(sub, "\"")
- if endQuote != -1 {
- ruleID := sub[:endQuote]
- existing := counters[ruleID]
- counters[ruleID] = [2]uint64{existing[0] + pkts, existing[1] + bts}
- }
- }
- }
- return counters, nil
- }
- // resolveInterfaceList resolves an interface list or role name (e.g. "WAN", "LAN") to interface names.
- func resolveInterfaceList(listName string) []string {
- listName = strings.TrimSpace(listName)
- if listName == "" {
- return nil
- }
- data, err := os.ReadFile("/etc/debtik/metadata.json")
- if err != nil {
- return nil
- }
- return resolveRoles(data, listName)
- }
- func resolveRoles(data []byte, listName string) []string {
- var meta struct {
- Interfaces map[string]struct {
- Role string `json:"role"`
- } `json:"interfaces"`
- }
- if err := json.Unmarshal(data, &meta); err != nil {
- return nil
- }
- var matched []string
- for ifaceName, info := range meta.Interfaces {
- if strings.EqualFold(info.Role, listName) {
- matched = append(matched, ifaceName)
- }
- }
- sort.Strings(matched)
- return matched
- }
- func getInterfaceCombinations(inIface, inList, outIface, outList string) [][2]string {
- var inListResolved []string
- if inIface != "" {
- inListResolved = []string{inIface}
- } else if inList != "" {
- inListResolved = resolveInterfaceList(inList)
- } else {
- inListResolved = []string{""}
- }
- var outListResolved []string
- if outIface != "" {
- outListResolved = []string{outIface}
- } else if outList != "" {
- outListResolved = resolveInterfaceList(outList)
- } else {
- outListResolved = []string{""}
- }
- if inList != "" && inIface == "" && len(inListResolved) == 0 {
- return nil
- }
- if outList != "" && outIface == "" && len(outListResolved) == 0 {
- return nil
- }
- var combos [][2]string
- for _, in := range inListResolved {
- for _, out := range outListResolved {
- combos = append(combos, [2]string{in, out})
- }
- }
- return combos
- }
- // BuildIPTablesRestore generates the atomic configuration for `iptables-restore`
- func BuildIPTablesRestore(filterRules []FilterRule, natRules []NATRule, mangleRules []MangleRule) string {
- var sb strings.Builder
- // ================= MANGLE TABLE =================
- sb.WriteString("*mangle\n")
- sb.WriteString(":PREROUTING ACCEPT [0:0]\n")
- sb.WriteString(":INPUT ACCEPT [0:0]\n")
- sb.WriteString(":FORWARD ACCEPT [0:0]\n")
- sb.WriteString(":OUTPUT ACCEPT [0:0]\n")
- sb.WriteString(":POSTROUTING ACCEPT [0:0]\n")
- // Custom user chains in mangle
- userMangleChains := make(map[string]bool)
- for _, r := range mangleRules {
- if r.Disabled {
- continue
- }
- ch := strings.ToUpper(r.Chain)
- if ch != "PREROUTING" && ch != "POSTROUTING" && ch != "INPUT" && ch != "OUTPUT" && ch != "FORWARD" && ch != "" {
- if !userMangleChains[ch] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", ch))
- userMangleChains[ch] = true
- }
- }
- if r.Action == "jump" && r.JumpTarget != "" {
- jt := strings.ToUpper(r.JumpTarget)
- if jt != "ACCEPT" && jt != "DROP" && jt != "RETURN" && !userMangleChains[jt] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", jt))
- userMangleChains[jt] = true
- }
- }
- }
- for _, r := range mangleRules {
- if r.Disabled {
- continue
- }
- for _, line := range buildMangleRuleLines(r) {
- if line != "" {
- sb.WriteString(line + "\n")
- }
- }
- }
- sb.WriteString("COMMIT\n")
- // ================= NAT TABLE =================
- sb.WriteString("*nat\n")
- sb.WriteString(":PREROUTING ACCEPT [0:0]\n")
- sb.WriteString(":INPUT ACCEPT [0:0]\n")
- sb.WriteString(":OUTPUT ACCEPT [0:0]\n")
- sb.WriteString(":POSTROUTING ACCEPT [0:0]\n")
- userNATChains := make(map[string]bool)
- for _, r := range natRules {
- if r.Disabled {
- continue
- }
- ch := strings.ToUpper(r.Chain)
- if ch != "PREROUTING" && ch != "POSTROUTING" && ch != "OUTPUT" && ch != "INPUT" && ch != "SRCNAT" && ch != "DSTNAT" && ch != "" {
- if !userNATChains[ch] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", ch))
- userNATChains[ch] = true
- }
- }
- if r.Action == "jump" && r.JumpTarget != "" {
- jt := strings.ToUpper(r.JumpTarget)
- if jt != "ACCEPT" && jt != "RETURN" && !userNATChains[jt] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", jt))
- userNATChains[jt] = true
- }
- }
- }
- for _, r := range natRules {
- if r.Disabled {
- continue
- }
- for _, line := range buildNATRuleLines(r) {
- if line != "" {
- sb.WriteString(line + "\n")
- }
- }
- }
- sb.WriteString("COMMIT\n")
- // ================= FILTER TABLE =================
- sb.WriteString("*filter\n")
- sb.WriteString(":INPUT ACCEPT [0:0]\n")
- sb.WriteString(":FORWARD ACCEPT [0:0]\n")
- sb.WriteString(":OUTPUT ACCEPT [0:0]\n")
- userFilterChains := make(map[string]bool)
- for _, r := range filterRules {
- if r.Disabled {
- continue
- }
- ch := strings.ToUpper(r.Chain)
- if ch != "INPUT" && ch != "FORWARD" && ch != "OUTPUT" && ch != "" {
- if !userFilterChains[ch] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", ch))
- userFilterChains[ch] = true
- }
- }
- if r.Action == "jump" && r.JumpTarget != "" {
- jt := strings.ToUpper(r.JumpTarget)
- if jt != "ACCEPT" && jt != "DROP" && jt != "REJECT" && jt != "RETURN" && !userFilterChains[jt] {
- sb.WriteString(fmt.Sprintf(":%s - [0:0]\n", jt))
- userFilterChains[jt] = true
- }
- }
- }
- for _, r := range filterRules {
- if r.Disabled {
- continue
- }
- for _, line := range buildFilterRuleLines(r) {
- if line != "" {
- sb.WriteString(line + "\n")
- }
- }
- }
- sb.WriteString("COMMIT\n")
- return sb.String()
- }
- func buildFilterRuleLines(r FilterRule) []string {
- r.Protocol = inferProtocol(values(r))
- combos := getInterfaceCombinations(r.InInterface, r.InInterfaceList, r.OutInterface, r.OutInterfaceList)
- if len(combos) == 0 {
- return nil
- }
- chain := strings.ToUpper(r.Chain)
- if chain == "" {
- chain = "FORWARD"
- }
- var lines []string
- for _, combo := range combos {
- inIface, outIface := combo[0], combo[1]
- var args []string
- args = append(args, fmt.Sprintf("-A %s", chain))
- // Match criteria
- args = appendMatchCriteria(args, r.SrcAddress, r.DstAddress, r.Protocol, r.SrcPort, r.DstPort, inIface, outIface, r.ConnectionState, r.ConnectionMark, r.RoutingMark, r.SrcAddressList, r.DstAddressList, r.SrcGeoIP, r.DstGeoIP)
- args = appendAdvanced(args, r)
- // Comment ID for counters mapping & user comment
- args = append(args, fmt.Sprintf("-m comment --comment \"id:%s\"", r.ID))
- // Action
- act := strings.ToLower(r.Action)
- switch act {
- case "accept":
- args = append(args, "-j ACCEPT")
- case "drop":
- args = append(args, "-j DROP")
- case "reject":
- args = append(args, "-j REJECT")
- if r.RejectWith != "" {
- args = append(args, fmt.Sprintf("--reject-with %s", r.RejectWith))
- }
- case "return":
- args = append(args, "-j RETURN")
- case "jump":
- if r.JumpTarget != "" {
- args = append(args, fmt.Sprintf("-j %s", strings.ToUpper(r.JumpTarget)))
- }
- case "log":
- args = append(args, "-j LOG")
- if r.LogPrefix != "" {
- args = append(args, "--log-prefix "+quote(r.LogPrefix))
- }
- case "add-src-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s src", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "add-dst-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s dst", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "passthrough":
- // A rule without a target counts matching packets and continues.
- default:
- continue
- }
- lines = append(lines, strings.Join(args, " "))
- }
- return decorateRuleLines("filter", r, lines)
- }
- func buildNATRuleLines(r NATRule) []string {
- r.Protocol = inferProtocol(values(r))
- combos := getInterfaceCombinations(r.InInterface, r.InInterfaceList, r.OutInterface, r.OutInterfaceList)
- if len(combos) == 0 {
- return nil
- }
- chain := strings.ToUpper(r.Chain)
- if chain == "SRCNAT" {
- chain = "POSTROUTING"
- } else if chain == "DSTNAT" {
- chain = "PREROUTING"
- }
- var lines []string
- for _, combo := range combos {
- inIface, outIface := combo[0], combo[1]
- var args []string
- args = append(args, fmt.Sprintf("-A %s", chain))
- args = appendMatchCriteria(args, r.SrcAddress, r.DstAddress, r.Protocol, r.SrcPort, r.DstPort, inIface, outIface, nil, r.ConnectionMark, r.RoutingMark, r.SrcAddressList, r.DstAddressList, r.SrcGeoIP, r.DstGeoIP)
- args = appendAdvanced(args, r)
- args = append(args, fmt.Sprintf("-m comment --comment \"id:%s\"", r.ID))
- act := strings.ToLower(r.Action)
- switch act {
- case "masquerade":
- args = append(args, "-j MASQUERADE")
- if r.ToPorts != "" {
- args = append(args, "--to-ports "+r.ToPorts)
- }
- case "src-nat":
- if r.ToAddresses != "" {
- target := r.ToAddresses
- if r.ToPorts != "" {
- target = net.JoinHostPort(r.ToAddresses, r.ToPorts)
- }
- args = append(args, fmt.Sprintf("-j SNAT --to-source %s", target))
- } else {
- args = append(args, "-j MASQUERADE")
- }
- case "dst-nat":
- if r.ToAddresses != "" {
- target := r.ToAddresses
- if r.ToPorts != "" {
- target = net.JoinHostPort(r.ToAddresses, r.ToPorts)
- }
- args = append(args, fmt.Sprintf("-j DNAT --to-destination %s", target))
- }
- case "redirect":
- if r.ToPorts != "" {
- args = append(args, fmt.Sprintf("-j REDIRECT --to-ports %s", r.ToPorts))
- } else {
- args = append(args, "-j REDIRECT")
- }
- case "jump":
- if r.JumpTarget != "" {
- args = append(args, fmt.Sprintf("-j %s", strings.ToUpper(r.JumpTarget)))
- }
- case "log":
- args = append(args, "-j LOG")
- if r.LogPrefix != "" {
- args = append(args, "--log-prefix "+quote(r.LogPrefix))
- }
- case "add-src-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s src", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "add-dst-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s dst", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "accept":
- args = append(args, "-j ACCEPT")
- case "return":
- args = append(args, "-j RETURN")
- case "netmap":
- args = append(args, "-j NETMAP --to "+r.ToAddresses)
- case "passthrough":
- default:
- continue
- }
- lines = append(lines, strings.Join(args, " "))
- }
- return decorateRuleLines("nat", r, lines)
- }
- func buildMangleRuleLines(r MangleRule) []string {
- r.Protocol = inferProtocol(values(r))
- combos := getInterfaceCombinations(r.InInterface, r.InInterfaceList, r.OutInterface, r.OutInterfaceList)
- if len(combos) == 0 {
- return nil
- }
- chain := strings.ToUpper(r.Chain)
- if chain == "" {
- chain = "PREROUTING"
- }
- var lines []string
- for _, combo := range combos {
- inIface, outIface := combo[0], combo[1]
- var args []string
- args = append(args, fmt.Sprintf("-A %s", chain))
- args = appendMatchCriteria(args, r.SrcAddress, r.DstAddress, r.Protocol, r.SrcPort, r.DstPort, inIface, outIface, r.ConnectionState, r.ConnectionMark, r.RoutingMark, r.SrcAddressList, r.DstAddressList, r.SrcGeoIP, r.DstGeoIP)
- args = appendAdvanced(args, r)
- args = append(args, fmt.Sprintf("-m comment --comment \"id:%s\"", r.ID))
- act := strings.ToLower(r.Action)
- switch act {
- case "mark-connection":
- if r.NewConnectionMark != "" {
- markID := MarkNameToID(r.NewConnectionMark)
- args = append(args, fmt.Sprintf("-j CONNMARK --set-mark %d", markID))
- }
- case "mark-routing":
- if r.NewRoutingMark != "" {
- markID := MarkNameToID(r.NewRoutingMark)
- args = append(args, fmt.Sprintf("-j MARK --set-mark %d", markID))
- }
- case "mark-packet":
- if r.NewPacketMark != "" {
- markID := MarkNameToID(r.NewPacketMark)
- args = append(args, fmt.Sprintf("-j MARK --set-mark %d", markID))
- }
- case "change-mss":
- if r.TCPFlags == "" {
- args = append(args, "--tcp-flags SYN,RST SYN")
- }
- args = append(args, "-j TCPMSS")
- if r.NewMSS == "clamp-to-pmtu" || r.NewMSS == "" {
- args = append(args, "--clamp-mss-to-pmtu")
- } else {
- args = append(args, fmt.Sprintf("--set-mss %s", r.NewMSS))
- }
- case "set-dscp", "change-dscp":
- if r.NewDSCP >= 0 {
- args = append(args, fmt.Sprintf("-j DSCP --set-dscp %d", r.NewDSCP))
- }
- case "change-ttl":
- if r.NewTTL != "" {
- module, flag := "TTL", "ttl"
- if r.IPVersion == 6 {
- module, flag = "HL", "hl"
- }
- if strings.HasPrefix(r.NewTTL, "+") {
- args = append(args, fmt.Sprintf("-j %s --%s-inc %s", module, flag, strings.TrimPrefix(r.NewTTL, "+")))
- } else if strings.HasPrefix(r.NewTTL, "-") {
- args = append(args, fmt.Sprintf("-j %s --%s-dec %s", module, flag, strings.TrimPrefix(r.NewTTL, "-")))
- } else {
- args = append(args, fmt.Sprintf("-j %s --%s-set %s", module, flag, r.NewTTL))
- }
- }
- case "set-priority":
- if r.NewPriority != "" && chain == "POSTROUTING" {
- args = append(args, fmt.Sprintf("-j CLASSIFY --set-class 0:%s", r.NewPriority))
- }
- case "drop":
- args = append(args, "-j DROP")
- case "jump":
- if r.JumpTarget != "" {
- args = append(args, fmt.Sprintf("-j %s", strings.ToUpper(r.JumpTarget)))
- }
- case "log":
- args = append(args, "-j LOG")
- if r.LogPrefix != "" {
- args = append(args, "--log-prefix "+quote(r.LogPrefix))
- }
- case "add-src-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s src", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "add-dst-to-address-list":
- if r.AddressList != "" {
- args = append(args, fmt.Sprintf("-j SET --add-set %s dst", GetDynamicIPSetName(r.AddressList)))
- if seconds, _ := timeoutSeconds(r.AddressListTimeout); seconds > 0 {
- args = append(args, fmt.Sprintf("--timeout %d", seconds))
- }
- }
- case "accept":
- args = append(args, "-j ACCEPT")
- case "return":
- args = append(args, "-j RETURN")
- case "passthrough":
- default:
- continue
- }
- lines = append(lines, strings.Join(args, " "))
- }
- return decorateRuleLines("mangle", r, lines)
- }
- func appendMatchCriteria(args []string, srcAddr, dstAddr, proto, srcPort, dstPort, inIface, outIface string, ctStates []string, connMark, routingMark, srcList, dstList, srcGeoIP, dstGeoIP string) []string {
- if srcAddr != "" {
- if strings.HasPrefix(srcAddr, "!") {
- args = append(args, fmt.Sprintf("! -s %s", strings.TrimPrefix(srcAddr, "!")))
- } else {
- args = append(args, fmt.Sprintf("-s %s", srcAddr))
- }
- }
- if dstAddr != "" {
- if strings.HasPrefix(dstAddr, "!") {
- args = append(args, fmt.Sprintf("! -d %s", strings.TrimPrefix(dstAddr, "!")))
- } else {
- args = append(args, fmt.Sprintf("-d %s", dstAddr))
- }
- }
- protoInvert, protoName := plain(strings.ToLower(proto))
- effectiveProto := protoName
- if effectiveProto != "" && effectiveProto != "all" {
- args = append(args, fmt.Sprintf("%s-p %s", protoInvert, effectiveProto))
- } else if srcPort != "" || dstPort != "" {
- effectiveProto = "tcp"
- args = append(args, "-p tcp")
- }
- if srcPort != "" && (effectiveProto == "tcp" || effectiveProto == "udp") {
- inv, normalized := plain(srcPort)
- normalized = portValue(normalized)
- if strings.Contains(normalized, ",") {
- args = append(args, fmt.Sprintf("-m multiport %s--sports %s", inv, normalized))
- } else {
- args = append(args, fmt.Sprintf("%s--sport %s", inv, normalized))
- }
- }
- if dstPort != "" && (effectiveProto == "tcp" || effectiveProto == "udp") {
- inv, normalized := plain(dstPort)
- normalized = portValue(normalized)
- if strings.Contains(normalized, ",") {
- args = append(args, fmt.Sprintf("-m multiport %s--dports %s", inv, normalized))
- } else {
- args = append(args, fmt.Sprintf("%s--dport %s", inv, normalized))
- }
- }
- if inIface != "" {
- if strings.HasPrefix(inIface, "!") {
- args = append(args, fmt.Sprintf("! -i %s", strings.TrimPrefix(inIface, "!")))
- } else {
- args = append(args, fmt.Sprintf("-i %s", inIface))
- }
- }
- if outIface != "" {
- if strings.HasPrefix(outIface, "!") {
- args = append(args, fmt.Sprintf("! -o %s", strings.TrimPrefix(outIface, "!")))
- } else {
- args = append(args, fmt.Sprintf("-o %s", outIface))
- }
- }
- if len(ctStates) > 0 {
- var upperStates []string
- for _, s := range ctStates {
- upperStates = append(upperStates, strings.ToUpper(s))
- }
- args = append(args, fmt.Sprintf("-m conntrack --ctstate %s", strings.Join(upperStates, ",")))
- }
- if connMark != "" {
- inv, name := plain(connMark)
- markID := MarkNameToID(name)
- args = append(args, fmt.Sprintf("-m connmark %s--mark %d", inv, markID))
- }
- if routingMark != "" {
- inv, name := plain(routingMark)
- markID := MarkNameToID(name)
- args = append(args, fmt.Sprintf("-m mark %s--mark %d", inv, markID))
- }
- if srcList != "" {
- invert := ""
- cleanList := srcList
- if strings.HasPrefix(cleanList, "!") {
- invert = "! "
- cleanList = strings.TrimPrefix(cleanList, "!")
- }
- setName := GetIPSetName(cleanList)
- args = append(args, fmt.Sprintf("-m set %s--match-set %s src", invert, setName))
- }
- if dstList != "" {
- invert := ""
- cleanList := dstList
- if strings.HasPrefix(cleanList, "!") {
- invert = "! "
- cleanList = strings.TrimPrefix(cleanList, "!")
- }
- setName := GetIPSetName(cleanList)
- args = append(args, fmt.Sprintf("-m set %s--match-set %s dst", invert, setName))
- }
- if srcGeoIP != "" {
- invert := ""
- cleanGeo := srcGeoIP
- if strings.HasPrefix(cleanGeo, "!") {
- invert = "! "
- cleanGeo = strings.TrimPrefix(cleanGeo, "!")
- }
- setName := GetGeoIPSetName(cleanGeo)
- args = append(args, fmt.Sprintf("-m set %s--match-set %s src", invert, setName))
- }
- if dstGeoIP != "" {
- invert := ""
- cleanGeo := dstGeoIP
- if strings.HasPrefix(cleanGeo, "!") {
- invert = "! "
- cleanGeo = strings.TrimPrefix(cleanGeo, "!")
- }
- setName := GetGeoIPSetName(cleanGeo)
- args = append(args, fmt.Sprintf("-m set %s--match-set %s dst", invert, setName))
- }
- return args
- }
- // ApplyIPTables executes `iptables-restore` with the given rule set payload
- func ApplyIPTables(payload string) error {
- live, err := exec.Command("iptables-save", "-c").Output()
- if err != nil {
- return fmt.Errorf("cannot snapshot existing firewall: %w", err)
- }
- payload = ownedPayload(payload, string(live))
- for _, test := range []bool{true, false} {
- args := []string{"--noflush", "-c"}
- if test {
- args = append(args, "--test")
- }
- cmd := exec.Command("iptables-restore", args...)
- cmd.Stdin = strings.NewReader(payload)
- if out, err := cmd.CombinedOutput(); err != nil {
- return fmt.Errorf("iptables apply (test=%v) failed: %s: %w", test, out, err)
- }
- }
- return nil
- }
|