diff --git a/network-go/iptables/iptables.go b/network-go/iptables/iptables.go index 86f9896..c096dbb 100644 --- a/network-go/iptables/iptables.go +++ b/network-go/iptables/iptables.go @@ -397,13 +397,48 @@ func (m *Manager) DeleteForwardAccept(chain, comment string) error { 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 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", 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} 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{ @@ -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 { logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: src=%s proto=%s sport=%s comment=%q", 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} 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{ @@ -435,4 +478,4 @@ func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, pr "-j", "MASQUERADE", } return m.runInContainer(pid, "nat", args...) -} \ No newline at end of file +}