import { spawnSync } from "node:child_process"; import { settings } from "./config.js"; import { deviceCidrs, normalizeCidr } from "./devices.js"; const DEFAULT_NAT_BYPASS_CIDRS = "0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4"; function splitCidrs(value) { return String(value || "") .split(/[\s,]+/) .map((item) => normalizeCidr(item)) .filter(Boolean); } function unique(list) { return [...new Set(list)]; } export function sourceBypassCidrs( profiles, envCidrs = process.env.TPROXY_BYPASS_SOURCE_CIDRS || "", ) { return unique([ ...splitCidrs(envCidrs), ...deviceCidrs(profiles?.devices || [], "bypass"), ]); } export function buildSourceBypassIptablesCommands( cidrs, { chain = settings.tproxySourceBypassChain, forwardChain = settings.tproxySourceForwardChain, natChain = settings.tproxySourceNatChain, natBypassCidrs = splitCidrs( process.env.BYPASS_CIDRS || DEFAULT_NAT_BYPASS_CIDRS, ), } = {}, ) { return [ ["-w", "-t", "mangle", "-F", chain], ["-w", "-F", forwardChain], ["-w", "-t", "nat", "-F", natChain], ...cidrs.map((cidr) => [ "-w", "-t", "mangle", "-A", chain, "-s", cidr, "-j", "ACCEPT", ]), ...cidrs.flatMap((cidr) => [ ["-w", "-A", forwardChain, "-s", cidr, "-j", "ACCEPT"], [ "-w", "-A", forwardChain, "-d", cidr, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", ], ]), ...natBypassCidrs.map((cidr) => [ "-w", "-t", "nat", "-A", natChain, "-d", cidr, "-j", "RETURN", ]), ...cidrs.map((cidr) => [ "-w", "-t", "nat", "-A", natChain, "-s", cidr, "-j", "MASQUERADE", ]), ]; } export function syncTproxySourceBypass(profiles, options = {}) { if (settings.appMode !== "gateway") { return { success: true, skipped: true, cidrs: [] }; } const cidrs = sourceBypassCidrs( profiles, options.envCidrs ?? process.env.TPROXY_BYPASS_SOURCE_CIDRS, ); const commands = buildSourceBypassIptablesCommands(cidrs, options); for (const args of commands) { const result = spawnSync("iptables", args, { encoding: "utf8", timeout: 1000, }); if (result.error || result.status !== 0) { return { success: false, cidrs, error: result.error?.message || (result.stderr || result.stdout || "iptables command failed").trim(), }; } } return { success: true, cidrs }; }