mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
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.
270 lines
6.2 KiB
Go
270 lines
6.2 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
|
)
|
|
|
|
type Iptables struct{}
|
|
|
|
func NewIptables() (*Iptables, error) {
|
|
return &Iptables{}, nil
|
|
}
|
|
|
|
func (i *Iptables) Name() string {
|
|
return "iptables"
|
|
}
|
|
|
|
func (i *Iptables) Status() (bool, error) {
|
|
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("iptables", "-L", "-n")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
firstLine := strings.Split(strings.TrimSpace(stdout), "\n")[0]
|
|
return strings.Contains(firstLine, "Chain"), nil
|
|
}
|
|
|
|
func (i *Iptables) Start() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *Iptables) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *Iptables) Restart() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *Iptables) Reload() error {
|
|
return nil
|
|
}
|
|
|
|
func (i *Iptables) Version() (string, error) {
|
|
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("iptables", "--version")
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get iptables version: %w", err)
|
|
}
|
|
parts := strings.Fields(stdout)
|
|
if len(parts) >= 2 {
|
|
return strings.TrimPrefix(parts[1], "v"), nil
|
|
}
|
|
return strings.TrimSpace(stdout), nil
|
|
}
|
|
|
|
func (i *Iptables) ListPort() ([]FireInfo, error) {
|
|
var datas []FireInfo
|
|
basicRules, err := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasic)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
beforeRules, _ := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasicBefore)
|
|
basicRules = append(basicRules, beforeRules...)
|
|
for _, item := range basicRules {
|
|
if len(item.DstPort) == 0 {
|
|
continue
|
|
}
|
|
if item.Strategy == "drop" || item.Strategy == "reject" {
|
|
item.Strategy = "drop"
|
|
}
|
|
|
|
datas = append(datas, FireInfo{
|
|
Chain: item.Chain,
|
|
Address: item.SrcIP,
|
|
Protocol: item.Protocol,
|
|
Port: item.DstPort,
|
|
Strategy: item.Strategy,
|
|
Family: "ipv4",
|
|
})
|
|
}
|
|
|
|
return datas, nil
|
|
}
|
|
|
|
func (i *Iptables) ListAddress() ([]FireInfo, error) {
|
|
var datas []FireInfo
|
|
basicRules, err := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasic)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, item := range basicRules {
|
|
if len(item.DstPort) != 0 || len(item.SrcPort) != 0 {
|
|
continue
|
|
}
|
|
if item.Strategy == "drop" || item.Strategy == "reject" {
|
|
item.Strategy = "drop"
|
|
}
|
|
datas = append(datas, FireInfo{
|
|
Address: item.SrcIP,
|
|
Strategy: item.Strategy,
|
|
Family: "ipv4",
|
|
})
|
|
}
|
|
return datas, nil
|
|
}
|
|
|
|
func (i *Iptables) Port(port FireInfo, operation string) error {
|
|
if operation != "add" && operation != "remove" {
|
|
return buserr.New("ErrCmdIllegal")
|
|
}
|
|
if len(port.Chain) == 0 {
|
|
port.Chain = iptables.Chain1PanelBasic
|
|
}
|
|
|
|
portSpec, err := normalizePortSpec(port.Port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
protocol := port.Protocol
|
|
if protocol == "" {
|
|
protocol = "tcp"
|
|
}
|
|
|
|
action := "ACCEPT"
|
|
if port.Strategy == "drop" {
|
|
action = "DROP"
|
|
}
|
|
|
|
ruleArgs := []string{"-p", protocol, "--dport", portSpec, "-j", action}
|
|
if operation == "add" {
|
|
if err := iptables.AddRule(iptables.FilterTab, port.Chain, ruleArgs...); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := iptables.DeleteRule(iptables.FilterTab, port.Chain, ruleArgs...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
name := iptables.BasicFileName
|
|
if port.Chain == iptables.Chain1PanelBasicBefore {
|
|
name = iptables.BasicBeforeFileName
|
|
}
|
|
if err := iptables.SaveRulesToFile(iptables.FilterTab, port.Chain, name); err != nil {
|
|
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *Iptables) RichRules(rule FireInfo, operation string) error {
|
|
if operation != "add" && operation != "remove" {
|
|
return buserr.New("ErrCmdIllegal")
|
|
}
|
|
if len(rule.Chain) == 0 {
|
|
rule.Chain = iptables.Chain1PanelBasic
|
|
}
|
|
|
|
address := strings.TrimSpace(rule.Address)
|
|
if strings.EqualFold(address, "Anywhere") {
|
|
address = ""
|
|
}
|
|
|
|
action := "ACCEPT"
|
|
if rule.Strategy == "drop" {
|
|
action = "DROP"
|
|
}
|
|
|
|
var ruleArgs []string
|
|
if address != "" {
|
|
ruleArgs = append(ruleArgs, "-s", address)
|
|
}
|
|
|
|
protocol := strings.TrimSpace(rule.Protocol)
|
|
if rule.Port != "" && protocol == "" {
|
|
protocol = "tcp"
|
|
}
|
|
|
|
if protocol != "" {
|
|
ruleArgs = append(ruleArgs, "-p", protocol)
|
|
}
|
|
|
|
if rule.Port != "" {
|
|
portSegment, err := normalizePortSpec(rule.Port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if protocol == "" {
|
|
return fmt.Errorf("protocol is required when specifying a port")
|
|
}
|
|
ruleArgs = append(ruleArgs, "--dport", portSegment)
|
|
}
|
|
|
|
ruleArgs = append(ruleArgs, "-j", action)
|
|
if operation == "add" {
|
|
if err := iptables.AddRule(iptables.FilterTab, rule.Chain, ruleArgs...); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := iptables.DeleteRule(iptables.FilterTab, rule.Chain, ruleArgs...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
name := iptables.BasicFileName
|
|
if rule.Chain == iptables.Chain1PanelBasicBefore {
|
|
name = iptables.BasicBeforeFileName
|
|
}
|
|
if err := iptables.SaveRulesToFile(iptables.FilterTab, rule.Chain, name); err != nil {
|
|
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parsePort(portStr string) (int, error) {
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid port number: %s", portStr)
|
|
}
|
|
if port < 1 || port > 65535 {
|
|
return 0, fmt.Errorf("port out of range: %d", port)
|
|
}
|
|
return port, nil
|
|
}
|
|
|
|
func normalizePortSpec(port string) (string, error) {
|
|
value := strings.TrimSpace(port)
|
|
if value == "" {
|
|
return "", fmt.Errorf("port is required")
|
|
}
|
|
|
|
separator := ""
|
|
if strings.Contains(value, "-") {
|
|
separator = "-"
|
|
} else if strings.Contains(value, ":") {
|
|
separator = ":"
|
|
}
|
|
|
|
if separator != "" {
|
|
parts := strings.Split(value, separator)
|
|
if len(parts) != 2 {
|
|
return "", fmt.Errorf("invalid port range: %s", port)
|
|
}
|
|
start, err := parsePort(strings.TrimSpace(parts[0]))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
end, err := parsePort(strings.TrimSpace(parts[1]))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if start > end {
|
|
return "", fmt.Errorf("invalid port range: %d-%d", start, end)
|
|
}
|
|
return fmt.Sprintf("%d:%d", start, end), nil
|
|
}
|
|
|
|
single, err := parsePort(value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("%d", single), nil
|
|
}
|