feat(iptables): make rule insertions idempotent and robust
continuous-integration/drone/push Build is passing

Add lineExistsInContainer helper to check for existing rules before insertion,
making InsertPreroutingRuleInContainer and InsertPostroutingMasqueradeInContainer
idempotent. Change cleanup errors from fatal to warnings for better fault tolerance.
This commit is contained in:
gyurix
2026-06-16 00:28:13 +02:00
parent 246346f8b1
commit 04322b699e
+46 -3
View File
@@ -397,13 +397,48 @@ func (m *Manager) DeleteForwardAccept(chain, comment string) error {
return nil return nil
} }
// lineExistsInContainer checks if a line matching the patterns exists inside a container namespace
func (m *Manager) lineExistsInContainer(pid int, table, chain string, patterns ...string) bool {
iptPath := m.binary
nsenterArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath, "-w", "-n", "-t", table, "-L", chain}
cmd := exec.Command("nsenter", nsenterArgs...)
output, err := cmd.Output()
if err != nil {
logger.Debug("IPTABLES: lineExistsInContainer list failed for PID %d chain %s: %v", pid, chain, err)
return false
}
for _, line := range strings.Split(string(output), "\n") {
matchesAll := true
for _, pattern := range patterns {
if !strings.Contains(line, pattern) {
matchesAll = false
break
}
}
if matchesAll {
return true
}
}
return false
}
// InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace // InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace
func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error { func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
logger.Info("IPTABLES: inserting PREROUTING DNAT rule in container PID %d: src=%s proto=%s dport=%s -> %s:%s comment=%q", logger.Info("IPTABLES: inserting PREROUTING DNAT rule in container PID %d: src=%s proto=%s dport=%s -> %s:%s comment=%q",
pid, sourceIP, proto, sourcePort, targetIP, targetPort, comment) pid, sourceIP, proto, sourcePort, targetIP, targetPort, comment)
// Idempotent check: if the rule already exists, skip
if m.lineExistsInContainer(pid, "nat", "PREROUTING", "DNAT", sourcePort, targetIP, comment) {
logger.Debug("IPTABLES: PREROUTING DNAT rule already exists in container PID %d (pattern=%s -> %s), skipping", pid, sourcePort, targetIP)
return nil
}
// Clean up any stale/duplicate rules first
patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment} patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
if err := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); err != nil { if err := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); err != nil {
return fmt.Errorf("failed to delete old container PREROUTING rules: %w", err) logger.Warn("IPTABLES: failed to delete old container PREROUTING rules: %v (continuing)", err)
} }
args := []string{ args := []string{
@@ -421,9 +456,17 @@ func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sour
func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error { func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error {
logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: src=%s proto=%s sport=%s comment=%q", logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: src=%s proto=%s sport=%s comment=%q",
pid, sourceCIDR, proto, sourcePort, comment) pid, sourceCIDR, proto, sourcePort, comment)
// Idempotent check: if the rule already exists, skip
if m.lineExistsInContainer(pid, "nat", "POSTROUTING", "MASQUERADE", comment, sourceCIDR) {
logger.Debug("IPTABLES: POSTROUTING MASQUERADE rule already exists in container PID %d (src=%s comment=%s), skipping", pid, sourceCIDR, comment)
return nil
}
// Clean up any stale/duplicate rules first
patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort} patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort}
if err := m.deleteMatchingLinesInContainer(pid, "nat", "POSTROUTING", patterns...); err != nil { if err := m.deleteMatchingLinesInContainer(pid, "nat", "POSTROUTING", patterns...); err != nil {
return fmt.Errorf("failed to delete old container POSTROUTING rules: %w", err) logger.Warn("IPTABLES: failed to delete old container POSTROUTING rules: %v (continuing)", err)
} }
args := []string{ args := []string{
@@ -435,4 +478,4 @@ func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, pr
"-j", "MASQUERADE", "-j", "MASQUERADE",
} }
return m.runInContainer(pid, "nat", args...) return m.runInContainer(pid, "nat", args...)
} }