This commit is contained in:
@@ -6,12 +6,31 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IPTablesAPI defines the interface for iptables operations, enabling mock implementations for testing
|
||||
type IPTablesAPI interface {
|
||||
Binary() string
|
||||
EnsureIPForward() error
|
||||
EnsureEstablishedRelated(chain string) error
|
||||
DeleteLine(chain string, lineNum string) error
|
||||
InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error
|
||||
InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targetIP, targetPort, comment string) error
|
||||
InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, comment string) error
|
||||
InsertPostroutingMasqueradeForTarget(targetCIDR, proto, targetPort, comment string) error
|
||||
InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error
|
||||
DeleteForwardAccept(chain, comment string) error
|
||||
InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error
|
||||
InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error
|
||||
}
|
||||
|
||||
// Manager manages iptables rules via the iptables/iptables-legacy CLI
|
||||
type Manager struct {
|
||||
binary string // /usr/sbin/iptables or /usr/sbin/iptables-legacy
|
||||
binary string
|
||||
debug bool
|
||||
}
|
||||
|
||||
// Ensure Manager implements IPTablesAPI
|
||||
var _ IPTablesAPI = (*Manager)(nil)
|
||||
|
||||
// NewManager creates a new iptables manager, auto-detecting the binary
|
||||
func NewManager(debug bool) *Manager {
|
||||
m := &Manager{debug: debug}
|
||||
@@ -83,17 +102,14 @@ func (m *Manager) EnsureIPForward() error {
|
||||
}
|
||||
|
||||
// EnsureEstablishedRelated inserts an ESTABLISHED,RELATED accept rule at the top of a chain
|
||||
// if it doesn't already exist
|
||||
func (m *Manager) EnsureEstablishedRelated(chain string) error {
|
||||
checkArgs := []string{"-w", "-n", "-L", chain}
|
||||
cmd := exec.Command(m.binary, checkArgs...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Chain may not exist, create it
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only insert if ESTABLISHED,RELATED rule is not present
|
||||
if !strings.Contains(string(output), "ESTABLISHED") || !strings.Contains(string(output), "RELATED") {
|
||||
args := []string{"-w", "-I", chain, "-m", "state", "--state", "established,related", "-j", "ACCEPT"}
|
||||
return m.run(args...)
|
||||
@@ -114,7 +130,6 @@ func (m *Manager) DeleteLineInContainer(pid int, table, chain, lineNum string) e
|
||||
}
|
||||
|
||||
// getLineNumbers returns line numbers matching certain criteria in a chain/table
|
||||
// This implements the grep logic from the shell script: iptables -w --line-number -n -L $CHAIN | grep ...
|
||||
func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) []string {
|
||||
args := []string{"-w", "--line-number", "-n", "-L", chain}
|
||||
if table != "" {
|
||||
@@ -150,7 +165,6 @@ func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) []
|
||||
// deleteMatchingLines deletes all lines in a chain matching the given patterns
|
||||
func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...string) error {
|
||||
lines := m.getLineNumbers(chain, table, grepPatterns...)
|
||||
// Reverse order (highest line first) so deletions don't shift line numbers
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
if err := m.DeleteLine(chain, lines[i]); err != nil {
|
||||
return err
|
||||
@@ -161,7 +175,6 @@ func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...strin
|
||||
|
||||
// deleteMatchingLinesInContainer deletes matching lines inside a container namespace
|
||||
func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, grepPatterns ...string) error {
|
||||
// For container namespaces, we use a different approach: list via nsenter + grep
|
||||
iptPath := "/sbin/iptables-legacy"
|
||||
if !strings.Contains(m.binary, "legacy") {
|
||||
iptPath = "/sbin/iptables"
|
||||
@@ -192,7 +205,6 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g
|
||||
}
|
||||
}
|
||||
|
||||
// Delete in reverse order
|
||||
for i := len(matchingLines) - 1; i >= 0; i-- {
|
||||
if err := m.DeleteLineInContainer(pid, table, chain, matchingLines[i]); err != nil {
|
||||
return err
|
||||
@@ -203,13 +215,11 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g
|
||||
|
||||
// InsertPreroutingRule inserts a DNAT PREROUTING rule on the host
|
||||
func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
|
||||
// First, delete existing matching rules
|
||||
patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
|
||||
if err := m.deleteMatchingLines("PREROUTING", "nat", patterns...); err != nil {
|
||||
return fmt.Errorf("failed to delete old PREROUTING rules: %w", err)
|
||||
}
|
||||
|
||||
// Insert the new rule
|
||||
args := []string{
|
||||
"-w", "-t", "nat", "-I", "PREROUTING",
|
||||
"-d", sourceIP,
|
||||
@@ -236,7 +246,6 @@ func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targ
|
||||
|
||||
// InsertPostroutingMasquerade inserts a MASQUERADE POSTROUTING rule on the host
|
||||
func (m *Manager) InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, comment string) error {
|
||||
// Delete existing matching rules first
|
||||
patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort}
|
||||
if err := m.deleteMatchingLines("POSTROUTING", "nat", patterns...); err != nil {
|
||||
return fmt.Errorf("failed to delete old POSTROUTING rules: %w", err)
|
||||
@@ -273,7 +282,6 @@ func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, target
|
||||
|
||||
// InsertForwardAccept inserts a FORWARD ACCEPT rule on the host
|
||||
func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error {
|
||||
// Build grep patterns to match existing rules
|
||||
var grepPatterns []string
|
||||
grepPatterns = append(grepPatterns, proto)
|
||||
if sourceIP != "" {
|
||||
@@ -289,12 +297,10 @@ func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePo
|
||||
grepPatterns = append(grepPatterns, targetPort)
|
||||
}
|
||||
|
||||
// Delete old matching rules
|
||||
if err := m.deleteMatchingLines(chain, "", grepPatterns...); err != nil {
|
||||
return fmt.Errorf("failed to delete old FORWARD rules: %w", err)
|
||||
}
|
||||
|
||||
// Build iptables args
|
||||
args := []string{"-w", "-I", chain, "-p", proto}
|
||||
if sourceIP != "" {
|
||||
args = append(args, "-s", sourceIP)
|
||||
@@ -326,7 +332,6 @@ func (m *Manager) DeleteForwardAccept(chain, comment string) error {
|
||||
|
||||
// InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace
|
||||
func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
|
||||
// Delete existing first
|
||||
patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
|
||||
if err := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); err != nil {
|
||||
return fmt.Errorf("failed to delete old container PREROUTING rules: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user