This commit is contained in:
Quentin McGaw
2026-07-16 21:09:07 +00:00
parent cd9ba54b37
commit 113253b523
5 changed files with 1097 additions and 1 deletions
+137 -1
View File
@@ -1,6 +1,23 @@
package nftables
import "github.com/google/nftables"
import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
"github.com/google/nftables"
)
const (
iptablesCommand = "iptables-nft"
iptablesFallbackCmd = "iptables"
ip6tablesCommand = "ip6tables-nft"
ip6tablesFallbackCmd = "ip6tables"
)
func IsSupported() bool {
conn, err := nftables.New()
@@ -10,3 +27,122 @@ func IsSupported() bool {
_, err = conn.ListTable("filter")
return err == nil
}
// Version obtains the version of the installed nftables.
func (f *Firewall) Version(ctx context.Context) (string, error) {
const emptyVersionError = "nft version string is empty"
cmd := exec.CommandContext(ctx, "nft", "-v")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("running nft -v: %w", err)
}
outputStr := strings.TrimSpace(string(output))
words := strings.Fields(outputStr)
if len(words) == 0 {
return "", errors.New(emptyVersionError) //nolint:err113
}
return words[0], nil
}
// findIptablesCommand finds the available iptables-nft or iptables command.
func findIptablesCommand() (string, error) {
if path, err := exec.LookPath(iptablesCommand); err == nil {
return path, nil
}
if path, err := exec.LookPath(iptablesFallbackCmd); err == nil {
return path, nil
}
return "", fmt.Errorf("iptables command not found: %s or %s", iptablesCommand, iptablesFallbackCmd) //nolint:err113
}
// findIP6tablesCommand finds the available ip6tables-nft or ip6tables command.
func findIP6tablesCommand() (string, error) {
if path, err := exec.LookPath(ip6tablesCommand); err == nil {
return path, nil
}
if path, err := exec.LookPath(ip6tablesFallbackCmd); err == nil {
return path, nil
}
return "", fmt.Errorf("ip6tables command not found: %s or %s", ip6tablesCommand, ip6tablesFallbackCmd) //nolint:err113
}
// RunUserPostRules reads and executes custom iptables-style rules from a file.
// Since iptables-nft is nftables under the hood, we delegate to it for rule
// parsing compatibility with user-written iptables rules.
func (f *Firewall) RunUserPostRules(ctx context.Context, filepath string) error {
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
if os.IsNotExist(err) {
return nil
} else if err != nil {
return fmt.Errorf("opening user rules file: %w", err)
}
content, err := io.ReadAll(file)
if err != nil {
_ = file.Close()
return fmt.Errorf("reading user rules file: %w", err)
}
if err := file.Close(); err != nil {
return fmt.Errorf("closing user rules file: %w", err)
}
lines := strings.Split(string(content), "\n")
iptablesCmd, err := findIptablesCommand()
if err != nil {
f.logger.Warnf("iptables-nft not available, skipping user post-rules for IPv4")
}
ip6tablesCmd, err := findIP6tablesCommand()
if err != nil {
f.logger.Warnf("ip6tables-nft not available, IPv6 user post-rules will fail")
}
for lineNum, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
var cmdName string
var ruleArgs string
switch {
case strings.HasPrefix(line, "iptables "):
cmdName = iptablesCmd
ruleArgs = strings.TrimPrefix(line, "iptables ")
case strings.HasPrefix(line, "iptables-nft "):
cmdName = iptablesCmd
ruleArgs = strings.TrimPrefix(line, "iptables-nft ")
case strings.HasPrefix(line, "iptables-legacy "):
cmdName = iptablesCmd
ruleArgs = strings.TrimPrefix(line, "iptables-legacy ")
case strings.HasPrefix(line, "ip6tables "):
cmdName = ip6tablesCmd
ruleArgs = strings.TrimPrefix(line, "ip6tables ")
case strings.HasPrefix(line, "ip6tables-nft "):
cmdName = ip6tablesCmd
ruleArgs = strings.TrimPrefix(line, "ip6tables-nft ")
case strings.HasPrefix(line, "ip6tables-legacy "):
cmdName = ip6tablesCmd
ruleArgs = strings.TrimPrefix(line, "ip6tables-legacy ")
default:
continue
}
if cmdName == "" {
continue
}
args := strings.Fields(ruleArgs)
if len(args) == 0 {
continue
}
cmd := exec.CommandContext(ctx, cmdName, args...)
output, err := cmd.CombinedOutput()
if err != nil {
outputStr := strings.TrimSpace(string(output))
return fmt.Errorf("running user rule on line %d (%s %s): %w: %s",
lineNum+1, cmdName, ruleArgs, err, outputStr)
}
}
return nil
}