import type { TNftFamily, TNftProtocol, INftDnatRule, INftSnatRule, INftMasqueradeRule } from './nft.types.js'; /** * Expand a protocol spec into concrete protocol strings. */ function expandProtocols(protocol?: TNftProtocol): string[] { switch (protocol ?? 'tcp') { case 'tcp': return ['tcp']; case 'udp': return ['udp']; case 'both': return ['tcp', 'udp']; } } /** * Build DNAT rules for port forwarding. * Generates DNAT + optional masquerade for each protocol. * Direct port of Rust build_dnat_rule. */ export function buildDnatRules( tableName: string, family: TNftFamily, rule: INftDnatRule, ): string[] { const protocols = expandProtocols(rule.protocol); const commands: string[] = []; for (const proto of protocols) { // DNAT rule commands.push( `nft add rule ${family} ${tableName} prerouting ${proto} dport ${rule.sourcePort} dnat to ${rule.targetHost}:${rule.targetPort}` ); // Masquerade (SNAT) unless preserveSourceIP is set if (!rule.preserveSourceIP) { commands.push( `nft add rule ${family} ${tableName} postrouting ${proto} dport ${rule.targetPort} masquerade` ); } } return commands; } /** * Build an SNAT rule to rewrite source address. */ export function buildSnatRule( tableName: string, family: TNftFamily, rule: INftSnatRule, ): string[] { const protocols = expandProtocols(rule.protocol); const commands: string[] = []; for (const proto of protocols) { commands.push( `nft add rule ${family} ${tableName} postrouting ${proto} dport ${rule.targetPort} snat to ${rule.sourceAddress}` ); } return commands; } /** * Build a masquerade rule for outgoing NAT. */ export function buildMasqueradeRule( tableName: string, family: TNftFamily, rule: INftMasqueradeRule, ): string[] { const protocols = expandProtocols(rule.protocol); const commands: string[] = []; for (const proto of protocols) { commands.push( `nft add rule ${family} ${tableName} postrouting ${proto} dport ${rule.targetPort} masquerade` ); } return commands; }