Files
1Panel/agent/utils/firewall/client/iptables/common.go
T
HynoR f35b0deb29 refactor(firewall): extract port forwarding subsystem (#13347)
Port forwarding no longer shares the filter client. FilterClient keeps only
filter capabilities, and forwarding gets its own adapter, service and boot
replay:

- utils/firewall/forwarding holds the provider adapters. firewalld uses native
  forward-port, ufw and iptables share the NAT implementation moved out of
  client/iptables/forward.go.
- service/forwarding.go owns base info, search, operate, enable and replay.
  The API keeps its routes and dispatches on name/type/operate.
- init/firewall replays forwarding through that service instead of loading NAT
  rule files inline.

Also adds 1PANEL_FORWARD to the IptablesOp name enum: the frontend already
sends {"name":"1PANEL_FORWARD","operate":"init-forward"} and the validator
rejected it with 400 before reaching the service. Besides that, the only
observable difference is that a forward-tab search no longer triggers the
port/address record cleanup goroutine on the side.
2026-07-30 14:07:41 +08:00

216 lines
6.2 KiB
Go

package iptables
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
const (
ChainInput = "INPUT"
ChainOutput = "OUTPUT"
Chain1PanelInput = "1PANEL_INPUT"
Chain1PanelOutput = "1PANEL_OUTPUT"
Chain1PanelBasicBefore = "1PANEL_BASIC_BEFORE"
Chain1PanelBasic = "1PANEL_BASIC"
Chain1PanelBasicAfter = "1PANEL_BASIC_AFTER"
)
const (
EstablishedRule = "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment \"ESTABLISHED Whitelist\""
IoRuleIn = "-i lo -j ACCEPT -m comment --comment \"Loopback Whitelist\""
DropAllTcp = "-p tcp -j DROP"
DropAllUdp = "-p udp -j DROP"
AllowSSH = "-p tcp --dport ssh -j ACCEPT"
)
const (
ACCEPT = "ACCEPT"
DROP = "DROP"
REJECT = "REJECT"
ANYWHERE = "anywhere"
)
const (
FilterTab = "filter"
NatTab = "nat"
)
func runIptables(tab string, ignoreExist1, withWait bool, ruleArgs ...string) (string, error) {
options := []cmd.Option{cmd.WithTimeout(60 * time.Second)}
if ignoreExist1 {
options = append(options, cmd.WithIgnoreExist1())
}
cmdMgr := cmd.NewCommandMgr(options...)
args := []string{"-t", tab}
if withWait {
args = append(args, "-w")
}
args = append(args, ruleArgs...)
return cmdMgr.RunWithOptionalSudoAndStdout("iptables", args...)
}
func RunWithStd(tab string, args ...string) (string, error) {
stdout, err := runIptables(tab, true, true, args...)
if err != nil {
global.LOG.Errorf("iptables command failed [table=%s, args=%s]: %v", tab, strings.Join(args, " "), err)
return stdout, err
}
return stdout, nil
}
func RunWithoutIgnore(tab string, args ...string) (string, error) {
stdout, err := runIptables(tab, false, false, args...)
if err != nil {
return stdout, err
}
return stdout, nil
}
func Run(tab string, args ...string) error {
if _, err := RunWithStd(tab, args...); err != nil {
return err
}
return nil
}
func NewChain(tab, chain string) error {
return Run(tab, "-N", chain)
}
func ClearChain(tab, chain string) error {
return Run(tab, "-F", chain)
}
func AddRule(tab, chain string, ruleArgs ...string) error {
if CheckRuleExist(tab, chain, ruleArgs...) {
return nil
}
args := append([]string{"-A", chain}, ruleArgs...)
return Run(tab, args...)
}
func DeleteRule(tab, chain string, ruleArgs ...string) error {
args := append([]string{"-D", chain}, ruleArgs...)
return Run(tab, args...)
}
func CheckChainExist(tab, chain string) (bool, error) {
stdout, err := RunWithStd(tab, "-S")
if err != nil {
global.LOG.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err)
return false, fmt.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err)
}
for _, line := range strings.Split(stdout, "\n") {
if strings.TrimSpace(line) == "-N "+chain {
return true, nil
}
}
return false, nil
}
func CheckChainBind(tab, parentChain, chain string) (bool, error) {
stdout, err := RunWithStd(tab, "-S", parentChain)
if err != nil {
global.LOG.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err)
return false, fmt.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err)
}
for _, line := range strings.Split(stdout, "\n") {
if strings.Contains(line, "-j "+chain) {
return true, nil
}
}
return false, nil
}
func CheckRuleExist(tab, chain string, ruleArgs ...string) bool {
args := append([]string{"-C", chain}, ruleArgs...)
_, err := RunWithoutIgnore(tab, args...)
return err == nil
}
func AddChain(tab, chain string) error {
exists, err := CheckChainExist(tab, chain)
if err != nil {
return fmt.Errorf("check chain %s exist from tab %s failed, err: %w", chain, tab, err)
}
if !exists {
if err := NewChain(tab, chain); err != nil {
return fmt.Errorf("add chain %s for tab %s failed, err: %w", tab, chain, err)
}
}
return nil
}
func BindChain(tab, targetChain, chain string, position int) error {
line, err := FindChainNum(tab, targetChain, chain)
if err != nil {
return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err)
}
if line == 0 {
if err := Run(tab, "-I", targetChain, strconv.Itoa(position), "-j", chain); err != nil {
return fmt.Errorf("bind chain %s to %s failed, err: %w", chain, targetChain, err)
}
}
return nil
}
func UnbindChain(tab, targetChain, chain string) error {
line, err := FindChainNum(tab, targetChain, chain)
if err != nil {
return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err)
}
if line != 0 {
return Run(tab, "-D", targetChain, strconv.Itoa(line))
}
return nil
}
func FindChainNum(tab, targetChain, chain string) (int, error) {
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(60*time.Second))
commandName, commandArgs := cmd.WrapWithOptionalSudo("iptables", "-w", "-t", tab, "-L", targetChain, "--line-numbers", "-n")
stdout, err := cmdMgr.RunPipe(
cmd.PipeCommand{Name: commandName, Args: commandArgs},
cmd.PipeCommand{Name: "grep", Args: []string{"-w", chain}},
)
if err != nil {
return 0, fmt.Errorf("failed to list rules in chain %s: %w", targetChain, err)
}
lineItem := strings.TrimSpace(stdout)
lines := strings.Split(lineItem, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
if fields[1] == chain {
itemNum, err := strconv.Atoi(fields[0])
return itemNum, err
}
}
return 0, nil
}
func AddChainWithAppend(tab, parentChain, chain string) error {
exists, err := CheckChainExist(tab, chain)
if err != nil {
return fmt.Errorf("failed to check chain %s: %w", chain, err)
}
if !exists {
if err := NewChain(tab, chain); err != nil {
return fmt.Errorf("failed to create chain %s: %w", chain, err)
}
}
isBind, err := CheckChainBind(tab, parentChain, chain)
if err != nil {
return fmt.Errorf("check chain %s bind to %s failed, err: %w", parentChain, chain, err)
}
if !isBind {
if err := AppendChain(tab, parentChain, chain); err != nil {
return fmt.Errorf("failed to append %s to %s: %w", chain, parentChain, err)
}
}
return nil
}
func AppendChain(tab string, parentChain, chain string) error {
return Run(tab, "-A", parentChain, "-j", chain)
}