Files
ssongliu e119239d3d refactor: manage firewall rules through runtime state and file backups (#13998)
Replace database rule synchronization with native rule operations and file-based backup, import, and recovery. Update firewall management views, descriptions, whitelist handling, and translations.

Include terminal reconnect fixes and xpack build tags for Darwin build targets. Remove temporary regression test files before committing.
2026-10-08 18:19:30 +08:00

124 lines
3.0 KiB
Go

package nftables_helper
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
)
const RulesFile = "1panel_filter.nft"
func PersistRuleset(ctx context.Context) error {
var ruleset strings.Builder
run := cmd.NewCommandMgr(cmd.WithContext(ctx), cmd.WithTimeout(60*time.Second))
for _, family := range []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6} {
stdout, exists, err := readNftObject(func(args ...string) (string, error) {
return run.RunWithOptionalSudoAndStdout("nft", args...)
}, "list", "table", TableFamily(family), TableName)
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
if !exists {
continue
}
ruleset.WriteString(stdout)
if !strings.HasSuffix(stdout, "\n") {
ruleset.WriteByte('\n')
}
}
return atomicWrite(filepath.Join(global.Dir.FirewallDir, RulesFile), []byte(ruleset.String()))
}
func Restore(families ...filter.Family) error {
if len(families) == 0 {
families = []filter.Family{filter.FamilyIPv4, filter.FamilyIPv6}
}
data, err := os.ReadFile(filepath.Join(global.Dir.FirewallDir, RulesFile))
if errors.Is(err, os.ErrNotExist) {
return nil
}
if err != nil {
return err
}
var script strings.Builder
for _, family := range families {
tableFamily := TableFamily(family)
_, exists, err := readNftObject(run, "list", "table", tableFamily, TableName)
if family == filter.FamilyIPv6 && errors.Is(err, filter.ErrFamilyUnavailable) {
continue
}
if err != nil {
return err
}
if exists {
continue
}
table, err := savedTableRules(string(data), tableFamily, TableName)
if err != nil {
return err
}
script.WriteString(table)
}
if script.Len() == 0 {
return nil
}
return RunScript(script.String())
}
func savedTableRules(ruleset, family, table string) (string, error) {
header := "table " + family + " " + table + " {"
lines := strings.Split(ruleset, "\n")
for index, line := range lines {
if line != header {
continue
}
for end := index + 1; end < len(lines); end++ {
if lines[end] == "}" {
return strings.Join(lines[index:end+1], "\n") + "\n", nil
}
}
return "", fmt.Errorf("incomplete saved nftables table %s %s", family, table)
}
return "", nil
}
func atomicWrite(target string, data []byte) error {
file, err := os.CreateTemp(filepath.Dir(target), ".nft-rules-*")
if err != nil {
return err
}
temporary := file.Name()
committed := false
defer func() {
_ = file.Close()
if !committed {
_ = os.Remove(temporary)
}
}()
if _, err := file.Write(data); err != nil {
return err
}
if err := file.Sync(); err != nil {
return err
}
if err := file.Close(); err != nil {
return err
}
if err := os.Rename(temporary, target); err != nil {
return fmt.Errorf("replace nftables rules file: %w", err)
}
committed = true
return nil
}