Skip to content

[Duplicate Code] IPv4/IPv6 parallel iptables chain management duplicated in host-iptables-cleanup.ts and host-iptables-rules.ts #3355

@github-actions

Description

@github-actions

Duplicate Code Opportunity

Summary

  • Pattern: The logic to list, parse, and delete rules from a DOCKER-USER chain — then flush and delete the chain itself — is implemented twice in host-iptables-cleanup.ts: once for IPv4 (iptables) and again for IPv6 (ip6tables). A near-identical chain-teardown block also appears in host-iptables-rules.ts during setup.
  • Locations: src/host-iptables-cleanup.ts lines 21–85, src/host-iptables-rules.ts lines 110–145
  • Impact: ~35 duplicate lines in cleanup alone; security-critical path where a bug fixed in the IPv4 branch may silently remain in the IPv6 branch

Evidence

src/host-iptables-cleanup.ts — IPv4 block (lines 21–52):

if (bridgeName) {
  const { stdout } = await execa('iptables', [
    '-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers',
  ], { reject: false });
  const lines = stdout.split('\n');
  const lineNumbers: number[] = [];
  for (const line of lines) {
    if ((line.includes(`-i ${bridgeName}`) || line.includes(`-o ${bridgeName}`)) && line.includes(CHAIN_NAME)) {
      const match = line.match(/^(\d+)/);
      if (match) lineNumbers.push(parseInt(match[1], 10));
    }
  }
  for (const lineNum of lineNumbers.reverse()) {
    await execa('iptables', ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
  }
}
await execa('iptables', ['-t', 'filter', '-F', CHAIN_NAME], { reject: false });
await execa('iptables', ['-t', 'filter', '-X', CHAIN_NAME], { reject: false });

src/host-iptables-cleanup.ts — IPv6 block (lines 58–85) — near-identical:

if (bridgeName) {
  const { stdout: stdout6 } = await execa('ip6tables', [
    '-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers',
  ], { reject: false });
  const lines6 = stdout6.split('\n');
  const lineNumbers6: number[] = [];
  for (const line of lines6) {
    if (line.includes(CHAIN_NAME_V6)) { ... }
  }
  for (const lineNum of lineNumbers6.reverse()) {
    await execa('ip6tables', ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
  }
}
await execa('ip6tables', ['-t', 'filter', '-F', CHAIN_NAME_V6], { reject: false });
await execa('ip6tables', ['-t', 'filter', '-X', CHAIN_NAME_V6], { reject: false });

The only differences are: iptables vs ip6tables, CHAIN_NAME vs CHAIN_NAME_V6, variable name suffixes, and the bridge-match predicate.

A third copy of the chain-teardown pattern (list → parse → delete in reverse → flush → delete) appears in src/host-iptables-rules.ts lines 113–145 during pre-setup cleanup.

Suggested Refactoring

Extract a shared helper into src/host-iptables-shared.ts:

async function cleanupChain(
  cmd: 'iptables' | 'ip6tables',
  chainName: string,
  bridgeName: string | null,
  matchPredicate?: (line: string) => boolean,
): Promise<void> {
  if (bridgeName) {
    const { stdout } = await execa(cmd, ['-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers'], { reject: false });
    const lineNumbers: number[] = [];
    for (const line of stdout.split('\n')) {
      if ((matchPredicate ? matchPredicate(line) : line.includes(chainName))) {
        const match = line.match(/^(\d+)/);
        if (match) lineNumbers.push(parseInt(match[1], 10));
      }
    }
    for (const lineNum of lineNumbers.reverse()) {
      await execa(cmd, ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
    }
  }
  await execa(cmd, ['-t', 'filter', '-F', chainName], { reject: false });
  await execa(cmd, ['-t', 'filter', '-X', chainName], { reject: false });
}

Both cleanup and setup callers can then delegate to this helper, keeping IPv4/IPv6 parity guaranteed by construction.

Affected Files

  • src/host-iptables-cleanup.ts — lines 21–85
  • src/host-iptables-rules.ts — lines 110–145

Effort Estimate

Low — pure refactor, no behavior change; existing tests cover both paths.


Detected by Duplicate Code Detector workflow. Run date: 2026-05-18

Generated by Duplicate Code Detector · ● 8.6M ·

  • expires on Jun 17, 2026, 10:01 PM UTC

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions