mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 00:00:52 +00:00
729 lines
21 KiB
Go
729 lines
21 KiB
Go
package forwarding
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
firewallutil "github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/iptables_helper"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
|
"github.com/mattn/go-shellwords"
|
|
)
|
|
|
|
type iptablesBackend interface {
|
|
IPv6Available() bool
|
|
Run(table string, args ...string) error
|
|
RunWithStd(table string, args ...string) (string, error)
|
|
RunIPv6(table string, args ...string) error
|
|
RunIPv6WithStd(table string, args ...string) (string, error)
|
|
Restore(ctx context.Context, family, input string) error
|
|
LoadRulesFromFile(table, chain, fileName string) error
|
|
LoadIPv6RulesFromFile(table, chain, fileName string) error
|
|
}
|
|
|
|
type systemIptablesBackend struct{}
|
|
|
|
func (systemIptablesBackend) IPv6Available() bool {
|
|
commands, err := lifecycle.ResolveIptablesCommands()
|
|
return err == nil && commands.IPv6Available()
|
|
}
|
|
|
|
func (systemIptablesBackend) Run(table string, args ...string) error {
|
|
return iptables_helper.Run(table, args...)
|
|
}
|
|
|
|
func (systemIptablesBackend) RunWithStd(table string, args ...string) (string, error) {
|
|
if len(args) == 1 && args[0] == "-S" {
|
|
return iptables_helper.ReadTable(context.Background(), table, false)
|
|
}
|
|
return iptables_helper.RunWithStd(table, args...)
|
|
}
|
|
|
|
func (systemIptablesBackend) RunIPv6(table string, args ...string) error {
|
|
return iptables_helper.RunIPv6(table, args...)
|
|
}
|
|
|
|
func (systemIptablesBackend) RunIPv6WithStd(table string, args ...string) (string, error) {
|
|
if len(args) == 1 && args[0] == "-S" {
|
|
return iptables_helper.ReadTable(context.Background(), table, true)
|
|
}
|
|
return iptables_helper.RunIPv6WithStd(table, args...)
|
|
}
|
|
|
|
func (systemIptablesBackend) Restore(ctx context.Context, family, input string) error {
|
|
commands, err := lifecycle.ResolveIptablesCommands()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
executable := commands.Restore4
|
|
if family == FamilyIPv6 {
|
|
executable = commands.Restore6
|
|
if executable == "" {
|
|
return fmt.Errorf("ip6tables-restore command family is unavailable")
|
|
}
|
|
}
|
|
var stderr strings.Builder
|
|
manager := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second), cmd.WithStdin(strings.NewReader(input)), cmd.WithStderr(&stderr))
|
|
err = manager.RunWithOptionalSudo(executable, "--noflush", "--wait")
|
|
if err == nil && strings.TrimSpace(stderr.String()) != "" {
|
|
err = fmt.Errorf("firewall command warning: %s", strings.TrimSpace(stderr.String()))
|
|
}
|
|
return firewallutil.WrapBatchCommandError(executable+" --noflush --wait", input, err)
|
|
}
|
|
|
|
func (systemIptablesBackend) LoadRulesFromFile(table, chain, fileName string) error {
|
|
return iptables_helper.LoadRulesFromFile(table, chain, fileName)
|
|
}
|
|
|
|
func (systemIptablesBackend) LoadIPv6RulesFromFile(table, chain, fileName string) error {
|
|
return iptables_helper.LoadIPv6RulesFromFile(table, chain, fileName)
|
|
}
|
|
|
|
type forwardingSystem interface {
|
|
ReadFile(name string) ([]byte, error)
|
|
WriteFile(name string, data []byte, perm os.FileMode) error
|
|
}
|
|
|
|
type defaultForwardingSystem struct{}
|
|
|
|
func (defaultForwardingSystem) ReadFile(name string) ([]byte, error) {
|
|
return os.ReadFile(name)
|
|
}
|
|
|
|
func (defaultForwardingSystem) WriteFile(name string, data []byte, perm os.FileMode) error {
|
|
return cmd.WriteFileWithOptionalSudo(name, data, perm)
|
|
}
|
|
|
|
type Iptables struct {
|
|
provider string
|
|
backend iptablesBackend
|
|
system forwardingSystem
|
|
}
|
|
|
|
func NewIptables(provider string) *Iptables {
|
|
return &Iptables{
|
|
provider: provider,
|
|
backend: systemIptablesBackend{},
|
|
system: defaultForwardingSystem{},
|
|
}
|
|
}
|
|
|
|
func (l *Iptables) Name() string {
|
|
return l.provider
|
|
}
|
|
|
|
func (l *Iptables) List() ([]Rule, error) {
|
|
stdout, err := l.backend.RunWithStd(iptables_helper.NatTab, "-S")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list NAT rules: %w", err)
|
|
}
|
|
rules := parseIptablesRules(stdout, FamilyIPv4)
|
|
if !l.backend.IPv6Available() {
|
|
return rules, nil
|
|
}
|
|
stdout, err = l.backend.RunIPv6WithStd(iptables_helper.NatTab, "-S")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list IPv6 NAT rules: %w", err)
|
|
}
|
|
return append(rules, parseIptablesRules(stdout, FamilyIPv6)...), nil
|
|
}
|
|
|
|
func (l *Iptables) ReplaceRules(rules []Rule) error {
|
|
byFamily := map[string][]Rule{
|
|
FamilyIPv4: nil,
|
|
FamilyIPv6: nil,
|
|
}
|
|
for _, rule := range rules {
|
|
normalized, err := NormalizeRule(rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
byFamily[normalized.Family] = append(byFamily[normalized.Family], normalized)
|
|
}
|
|
var failures []error
|
|
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
|
|
if family == FamilyIPv6 && !l.backend.IPv6Available() {
|
|
if len(byFamily[family]) > 0 {
|
|
failures = append(failures, fmt.Errorf("ip6tables command family is unavailable"))
|
|
}
|
|
continue
|
|
}
|
|
if family == FamilyIPv6 {
|
|
if len(byFamily[family]) > 0 {
|
|
if err := ensureForwardingSysctls(l.system, true); err != nil {
|
|
failures = append(failures, err)
|
|
continue
|
|
}
|
|
} else {
|
|
initialized, _, err := l.familyInitStatus(family)
|
|
if err != nil {
|
|
failures = append(failures, err)
|
|
continue
|
|
}
|
|
if !initialized {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
if err := l.batchEnsureChains(family); err != nil {
|
|
return err
|
|
}
|
|
script, err := buildIptablesForwardScript(byFamily[family], OperationAdd, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := l.backend.Restore(context.Background(), family, script); err != nil {
|
|
return fmt.Errorf("restore %s forwarding rules: %w", family, err)
|
|
}
|
|
}
|
|
return errors.Join(failures...)
|
|
}
|
|
|
|
func (l *Iptables) CreateRules(ctx context.Context, rules []Rule) error {
|
|
if len(rules) == 0 {
|
|
return nil
|
|
}
|
|
script, err := buildIptablesForwardScript(rules, OperationAdd, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
family := rules[0].Family
|
|
if family == "" {
|
|
family = FamilyIPv4
|
|
}
|
|
if family == FamilyIPv6 {
|
|
if !l.backend.IPv6Available() {
|
|
return fmt.Errorf("ip6tables command family is unavailable")
|
|
}
|
|
if err := ensureForwardingSysctls(l.system, true); err != nil {
|
|
return err
|
|
}
|
|
if err := l.batchEnsureChains(family); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return l.backend.Restore(ctx, family, script)
|
|
}
|
|
|
|
func (l *Iptables) DeleteRules(ctx context.Context, rules []Rule) error {
|
|
if len(rules) == 0 {
|
|
return nil
|
|
}
|
|
script, err := buildIptablesForwardScript(rules, OperationRemove, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
family := rules[0].Family
|
|
if family == "" {
|
|
family = FamilyIPv4
|
|
}
|
|
return l.backend.Restore(ctx, family, script)
|
|
}
|
|
|
|
func buildIptablesForwardScript(rules []Rule, operation OperationType, replace bool) (string, error) {
|
|
var natRules, filterRules [][]string
|
|
if replace {
|
|
natRules = [][]string{{"-F", ChainPreRouting}, {"-F", ChainPostRouting}}
|
|
filterRules = [][]string{{"-F", ChainForward}}
|
|
}
|
|
verb := "-A"
|
|
if operation == OperationRemove {
|
|
verb = "-D"
|
|
} else if operation != OperationAdd {
|
|
return "", fmt.Errorf("unsupported forwarding operation %q", operation)
|
|
}
|
|
family := ""
|
|
for _, rule := range rules {
|
|
normalized, err := NormalizeRule(rule)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
rule = normalized
|
|
if family != "" && family != rule.Family {
|
|
return "", fmt.Errorf("iptables forwarding batch must use one address family")
|
|
}
|
|
family = rule.Family
|
|
sourcePort := strings.ReplaceAll(rule.Port, "-", ":")
|
|
targetPort := strings.ReplaceAll(rule.TargetPort, "-", ":")
|
|
preRouting := []string{verb, ChainPreRouting}
|
|
if rule.Interface != "" {
|
|
preRouting = append(preRouting, "-i", rule.Interface)
|
|
}
|
|
preRouting = append(preRouting, "-p", rule.Protocol, "--dport", sourcePort)
|
|
if !isRemoteTarget(rule.Family, rule.TargetIP) {
|
|
natRules = append(natRules, append(preRouting, "-j", "REDIRECT", "--to-port", rule.TargetPort))
|
|
continue
|
|
}
|
|
natRules = append(natRules,
|
|
append(preRouting, "-j", "DNAT", "--to-destination", forwardingTarget(rule)),
|
|
[]string{verb, ChainPostRouting, "-d", rule.TargetIP, "-p", rule.Protocol, "--dport", targetPort, "-j", "MASQUERADE"},
|
|
)
|
|
filterRules = append(filterRules,
|
|
[]string{verb, ChainForward, "-d", rule.TargetIP, "-p", rule.Protocol, "--dport", targetPort, "-j", "ACCEPT"},
|
|
[]string{verb, ChainForward, "-s", rule.TargetIP, "-p", rule.Protocol, "--sport", targetPort, "-j", "ACCEPT"},
|
|
)
|
|
}
|
|
var script strings.Builder
|
|
for _, table := range []struct {
|
|
name string
|
|
rules [][]string
|
|
}{{iptables_helper.NatTab, natRules}, {iptables_helper.FilterTab, filterRules}} {
|
|
script.WriteByte('*')
|
|
script.WriteString(table.name)
|
|
script.WriteByte('\n')
|
|
for _, rule := range table.rules {
|
|
for index, token := range rule {
|
|
if token == "" || strings.ContainsAny(token, " \t\r\n\"'") {
|
|
return "", fmt.Errorf("invalid iptables-restore token %q", token)
|
|
}
|
|
if index > 0 {
|
|
script.WriteByte(' ')
|
|
}
|
|
script.WriteString(token)
|
|
}
|
|
script.WriteByte('\n')
|
|
}
|
|
script.WriteString("COMMIT\n")
|
|
}
|
|
return script.String(), nil
|
|
}
|
|
|
|
func forwardingTarget(rule Rule) string {
|
|
if rule.Family == FamilyIPv6 {
|
|
return "[" + rule.TargetIP + "]:" + rule.TargetPort
|
|
}
|
|
return rule.TargetIP + ":" + rule.TargetPort
|
|
}
|
|
|
|
func isRemoteTarget(family, target string) bool {
|
|
if family == FamilyIPv6 {
|
|
return target != "" && target != "::1" && target != "localhost"
|
|
}
|
|
return target != "" && target != "127.0.0.1" && target != "localhost"
|
|
}
|
|
|
|
func (l *Iptables) Enable() error {
|
|
if err := ensureForwardingSysctls(l.system, false); err != nil {
|
|
return err
|
|
}
|
|
return l.batchEnsureChains(FamilyIPv4)
|
|
}
|
|
|
|
func (l *Iptables) batchEnsureChains(family string) error {
|
|
list := l.backend.RunWithStd
|
|
if family == FamilyIPv6 {
|
|
list = l.backend.RunIPv6WithStd
|
|
}
|
|
outputs := make(map[string]string, 2)
|
|
for _, table := range []string{iptables_helper.NatTab, iptables_helper.FilterTab} {
|
|
output, err := list(table, "-S")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outputs[table] = output
|
|
}
|
|
script := buildIptablesForwardLifecycleScript(outputs, true)
|
|
if script == "" {
|
|
return nil
|
|
}
|
|
if err := l.backend.Restore(context.Background(), family, script); err != nil {
|
|
return fmt.Errorf("batch initialize %s forwarding chains: %w", family, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Iptables) Cleanup() error {
|
|
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
|
|
if family == FamilyIPv6 && !l.backend.IPv6Available() {
|
|
continue
|
|
}
|
|
list := l.backend.RunWithStd
|
|
if family == FamilyIPv6 {
|
|
list = l.backend.RunIPv6WithStd
|
|
}
|
|
outputs := make(map[string]string, 2)
|
|
for _, table := range []string{iptables_helper.NatTab, iptables_helper.FilterTab} {
|
|
rules, err := list(table, "-S")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outputs[table] = rules
|
|
}
|
|
script := buildIptablesForwardLifecycleScript(outputs, false)
|
|
if script != "" {
|
|
if err := l.backend.Restore(context.Background(), family, script); err != nil {
|
|
return fmt.Errorf("batch delete %s forwarding chains: %w", family, err)
|
|
}
|
|
}
|
|
}
|
|
for _, file := range []string{ForwardFile, PreRoutingFile, PostRoutingFile,
|
|
iptables_helper.IPv6FileName(ForwardFile), iptables_helper.IPv6FileName(PreRoutingFile), iptables_helper.IPv6FileName(PostRoutingFile)} {
|
|
if err := os.Remove(filepath.Join(global.Dir.FirewallDir, file)); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildIptablesForwardLifecycleScript(outputs map[string]string, create bool) string {
|
|
items := []struct{ table, parent, chain string }{
|
|
{iptables_helper.NatTab, "PREROUTING", ChainPreRouting},
|
|
{iptables_helper.NatTab, "POSTROUTING", ChainPostRouting},
|
|
}
|
|
byTable := make(map[string][]string, 2)
|
|
for _, item := range items {
|
|
output := outputs[item.table]
|
|
chainExists := containsExactLine(output, "-N "+item.chain)
|
|
binding := "-A " + item.parent + " -j " + item.chain
|
|
bindingCount := countExactLines(output, binding)
|
|
if create {
|
|
if !chainExists {
|
|
byTable[item.table] = append(byTable[item.table], "-N "+item.chain)
|
|
}
|
|
if bindingCount == 0 {
|
|
byTable[item.table] = append(byTable[item.table], "-A "+item.parent+" -j "+item.chain)
|
|
}
|
|
continue
|
|
}
|
|
for range bindingCount {
|
|
byTable[item.table] = append(byTable[item.table], "-D "+item.parent+" -j "+item.chain)
|
|
}
|
|
if chainExists {
|
|
byTable[item.table] = append(byTable[item.table], "-F "+item.chain, "-X "+item.chain)
|
|
}
|
|
}
|
|
|
|
filterOutput := outputs[iptables_helper.FilterTab]
|
|
filterChainExists := containsExactLine(filterOutput, "-N "+ChainForward)
|
|
filterBinding := "-A FORWARD -j " + ChainForward
|
|
filterBindingCount := countExactLines(filterOutput, filterBinding)
|
|
if create {
|
|
if !filterChainExists {
|
|
byTable[iptables_helper.FilterTab] = append(byTable[iptables_helper.FilterTab], "-N "+ChainForward)
|
|
}
|
|
if !forwardBindingEffective(filterOutput) {
|
|
for range filterBindingCount {
|
|
byTable[iptables_helper.FilterTab] = append(byTable[iptables_helper.FilterTab], "-D FORWARD -j "+ChainForward)
|
|
}
|
|
byTable[iptables_helper.FilterTab] = append(byTable[iptables_helper.FilterTab], canonicalForwardBindingRule(filterOutput))
|
|
}
|
|
} else {
|
|
for range filterBindingCount {
|
|
byTable[iptables_helper.FilterTab] = append(byTable[iptables_helper.FilterTab], "-D FORWARD -j "+ChainForward)
|
|
}
|
|
if filterChainExists {
|
|
byTable[iptables_helper.FilterTab] = append(byTable[iptables_helper.FilterTab], "-F "+ChainForward, "-X "+ChainForward)
|
|
}
|
|
}
|
|
var script strings.Builder
|
|
for _, table := range []string{iptables_helper.NatTab, iptables_helper.FilterTab} {
|
|
lines := byTable[table]
|
|
if len(lines) == 0 {
|
|
continue
|
|
}
|
|
script.WriteByte('*')
|
|
script.WriteString(table)
|
|
script.WriteByte('\n')
|
|
script.WriteString(strings.Join(lines, "\n"))
|
|
script.WriteString("\nCOMMIT\n")
|
|
}
|
|
return script.String()
|
|
}
|
|
|
|
func countExactLines(output, want string) int {
|
|
count := 0
|
|
for _, line := range strings.Split(output, "\n") {
|
|
if strings.TrimSpace(line) == want {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func forwardBindingEffective(output string) bool {
|
|
binding := "-A FORWARD -j " + ChainForward
|
|
bindingPosition := 0
|
|
terminalPosition := 0
|
|
position := 0
|
|
bindings := 0
|
|
for _, line := range strings.Split(output, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "-A FORWARD ") {
|
|
continue
|
|
}
|
|
position++
|
|
if line == binding {
|
|
bindings++
|
|
bindingPosition = position
|
|
}
|
|
if terminalPosition == 0 && isUnconditionalForwardTerminal(line) {
|
|
terminalPosition = position
|
|
}
|
|
}
|
|
return bindings == 1 && (terminalPosition == 0 || bindingPosition < terminalPosition)
|
|
}
|
|
|
|
func canonicalForwardBindingRule(output string) string {
|
|
binding := "-A FORWARD -j " + ChainForward
|
|
position := 1
|
|
for _, line := range strings.Split(output, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "-A FORWARD ") || line == binding {
|
|
continue
|
|
}
|
|
if isUnconditionalForwardTerminal(line) {
|
|
return fmt.Sprintf("-I FORWARD %d -j %s", position, ChainForward)
|
|
}
|
|
position++
|
|
}
|
|
return "-A FORWARD -j " + ChainForward
|
|
}
|
|
|
|
func isUnconditionalForwardTerminal(line string) bool {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 4 || fields[0] != "-A" || fields[1] != "FORWARD" || fields[2] != "-j" {
|
|
return false
|
|
}
|
|
switch fields[3] {
|
|
case "ACCEPT", "DROP", "REJECT", "RETURN":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func containsExactLine(output, want string) bool {
|
|
for _, line := range strings.Split(output, "\n") {
|
|
if strings.TrimSpace(line) == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (l *Iptables) InitStatus() (bool, bool, error) {
|
|
ipv4Init, ipv4Bind, err := l.familyInitStatus(FamilyIPv4)
|
|
if err != nil {
|
|
return false, false, err
|
|
}
|
|
if !l.backend.IPv6Available() {
|
|
return ipv4Init, ipv4Bind, nil
|
|
}
|
|
ipv6Init, ipv6Bind, err := l.familyInitStatus(FamilyIPv6)
|
|
if err != nil {
|
|
return false, false, err
|
|
}
|
|
return ipv4Init || ipv6Init, ipv4Bind || ipv6Bind, nil
|
|
}
|
|
|
|
func (l *Iptables) familyInitStatus(family string) (bool, bool, error) {
|
|
sysctlPath := "/proc/sys/net/ipv4/ip_forward"
|
|
label := "IPv4"
|
|
list := l.backend.RunWithStd
|
|
if family == FamilyIPv6 {
|
|
sysctlPath = "/proc/sys/net/ipv6/conf/all/forwarding"
|
|
label = "IPv6"
|
|
list = l.backend.RunIPv6WithStd
|
|
}
|
|
data, err := l.system.ReadFile(sysctlPath)
|
|
if family == FamilyIPv6 && errors.Is(err, os.ErrNotExist) {
|
|
return false, false, nil
|
|
}
|
|
if err != nil {
|
|
return false, false, fmt.Errorf("read %s forwarding status: %w", label, err)
|
|
}
|
|
forwardingEnabled := strings.TrimSpace(string(data)) != "0"
|
|
natRules, err := list(iptables_helper.NatTab, "-S")
|
|
if err != nil {
|
|
return false, false, fmt.Errorf("list %s NAT initialization rules: %w", label, err)
|
|
}
|
|
natInit, natBind := checkInitAndBind(
|
|
[]string{"-N " + ChainPreRouting, "-N " + ChainPostRouting},
|
|
[]string{"-A PREROUTING -j " + ChainPreRouting, "-A POSTROUTING -j " + ChainPostRouting},
|
|
strings.Split(natRules, "\n"),
|
|
)
|
|
if !natInit {
|
|
return false, false, nil
|
|
}
|
|
filterRules, err := list(iptables_helper.FilterTab, "-S")
|
|
if err != nil {
|
|
return false, false, fmt.Errorf("list %s filter initialization rules: %w", label, err)
|
|
}
|
|
filterInit, _ := checkInitAndBind(
|
|
[]string{"-N " + ChainForward},
|
|
nil,
|
|
strings.Split(filterRules, "\n"),
|
|
)
|
|
filterBind := forwardBindingEffective(filterRules)
|
|
return natInit && filterInit, forwardingEnabled && natBind && filterInit && filterBind, nil
|
|
}
|
|
|
|
func (l *Iptables) FamilyStatus(family string) (bool, bool, error) {
|
|
if family == FamilyIPv6 && !l.backend.IPv6Available() {
|
|
return false, false, nil
|
|
}
|
|
return l.familyInitStatus(family)
|
|
}
|
|
|
|
func checkInitAndBind(initRules, bindRules, lines []string) (bool, bool) {
|
|
for _, rule := range initRules {
|
|
if !containsExactRule(lines, rule) {
|
|
return false, false
|
|
}
|
|
}
|
|
for _, rule := range bindRules {
|
|
if !containsExactRule(lines, rule) {
|
|
return true, false
|
|
}
|
|
}
|
|
return true, true
|
|
}
|
|
|
|
func containsExactRule(lines []string, rule string) bool {
|
|
for _, line := range lines {
|
|
if strings.TrimSpace(line) == strings.TrimSpace(rule) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (l *Iptables) Replay() error {
|
|
for _, family := range []string{FamilyIPv4, FamilyIPv6} {
|
|
if family == FamilyIPv6 && !l.backend.IPv6Available() {
|
|
continue
|
|
}
|
|
if err := l.batchEnsureChains(family); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, item := range []struct {
|
|
table string
|
|
chain string
|
|
file string
|
|
}{
|
|
{iptables_helper.FilterTab, ChainForward, ForwardFile},
|
|
{iptables_helper.NatTab, ChainPreRouting, PreRoutingFile},
|
|
{iptables_helper.NatTab, ChainPostRouting, PostRoutingFile},
|
|
} {
|
|
if err := l.backend.LoadRulesFromFile(item.table, item.chain, item.file); err != nil {
|
|
return err
|
|
}
|
|
if l.backend.IPv6Available() {
|
|
if err := l.backend.LoadIPv6RulesFromFile(item.table, item.chain, iptables_helper.IPv6FileName(item.file)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseIptablesRules(stdout, family string) []Rule {
|
|
var rules []Rule
|
|
num := 0
|
|
lines:
|
|
for _, line := range strings.Split(stdout, "\n") {
|
|
fields, err := shellwords.Parse(line)
|
|
if err != nil || len(fields) < 2 || fields[0] != "-A" || fields[1] != ChainPreRouting {
|
|
continue
|
|
}
|
|
num++
|
|
rule := Rule{Num: strconv.Itoa(num), Family: family}
|
|
target := ""
|
|
for index := 2; index < len(fields); index++ {
|
|
var value *string
|
|
switch fields[index] {
|
|
case "!":
|
|
continue lines
|
|
case "-p", "--protocol":
|
|
value = &rule.Protocol
|
|
case "-i", "--in-interface":
|
|
value = &rule.Interface
|
|
case "--dport", "--destination-port":
|
|
value = &rule.Port
|
|
case "-j", "--jump":
|
|
value = &target
|
|
case "--to-destination":
|
|
if index+1 >= len(fields) {
|
|
continue lines
|
|
}
|
|
index++
|
|
rule.TargetIP, rule.TargetPort = parseIptablesTarget(fields[index])
|
|
case "--to-ports", "--to-port":
|
|
value = &rule.TargetPort
|
|
case "-m", "--match", "--comment":
|
|
if index+1 >= len(fields) {
|
|
continue lines
|
|
}
|
|
index++
|
|
}
|
|
if value != nil {
|
|
if index+1 >= len(fields) {
|
|
continue lines
|
|
}
|
|
index++
|
|
*value = fields[index]
|
|
}
|
|
}
|
|
if rule.Protocol == "" || rule.Port == "" || rule.TargetPort == "" {
|
|
continue
|
|
}
|
|
switch target {
|
|
case "REDIRECT":
|
|
rule.TargetIP = "127.0.0.1"
|
|
if family == FamilyIPv6 {
|
|
rule.TargetIP = "::1"
|
|
}
|
|
case "DNAT":
|
|
if rule.TargetIP == "" {
|
|
continue
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
rule.Protocol = loadProtocol(rule.Protocol)
|
|
rule.Port = strings.ReplaceAll(rule.Port, ":", "-")
|
|
rule.TargetPort = strings.ReplaceAll(rule.TargetPort, ":", "-")
|
|
rules = append(rules, rule)
|
|
}
|
|
return rules
|
|
}
|
|
|
|
func parseIptablesTarget(value string) (string, string) {
|
|
if strings.HasPrefix(value, "[") {
|
|
separator := strings.LastIndex(value, "]:")
|
|
if separator > 0 {
|
|
return value[1:separator], value[separator+2:]
|
|
}
|
|
return "", ""
|
|
}
|
|
separator := strings.LastIndex(value, ":")
|
|
if separator <= 0 {
|
|
return "", ""
|
|
}
|
|
return value[:separator], value[separator+1:]
|
|
}
|
|
|
|
func loadProtocol(protocol string) string {
|
|
switch protocol {
|
|
case "0":
|
|
return "all"
|
|
case "1":
|
|
return "icmp"
|
|
case "6":
|
|
return "tcp"
|
|
case "17":
|
|
return "udp"
|
|
default:
|
|
return protocol
|
|
}
|
|
}
|