import { type EgressAttempt } from "./sandbox.js"; /** A host plus the IPs it resolved to, split by family (nft needs them apart). */ export interface ResolvedHost { readonly host: string; readonly v4: readonly string[]; readonly v6: readonly string[]; } /** Per-host and aggregate nftables counters read back after a confined run. */ export interface EgressCounters { /** Allowlisted hosts that saw traffic, with the nft counter for each. */ readonly allowed: readonly { readonly host: string; readonly packets: number; readonly bytes: number; }[]; /** The catch-all drop rule: traffic to anything OFF the allowlist. */ readonly dropped: { readonly packets: number; readonly bytes: number; }; } /** * Whether this host can run the allowlisted-egress sandbox: it needs the same * bubblewrap confinement the other tiers use PLUS `slirp4netns` (the rootless * gateway) and `nft` (the packet-layer allowlist). Cached — the answer can't * change within a run. `available` is injected so the bwrap probe isn't repeated. */ export declare function probeEgressAvailable(available: boolean): boolean; /** Cached {@link probeEgressAvailable}. `available` = the bwrap-sandbox probe. */ export declare function egressAvailable(available: boolean): boolean; /** * Parse `getent ahosts ` output into the unique IPs, split by family. The * first whitespace token of each line is an address; `:` marks IPv6. Pure, so the * resolution-parsing is unit-tested without touching DNS. */ export declare function parseGetent(stdout: string): { v4: string[]; v6: string[]; }; /** * Resolve every allowlisted host to IPs. The lookup is injectable so the * empty-result / family-split behaviour is unit-tested with a fake resolver. */ export declare function resolveAllow(hosts: readonly string[], lookup?: (host: string) => { v4: string[]; v6: string[]; }): ResolvedHost[]; /** * Nameservers usable from INSIDE the egress netns. Parses a resolv.conf body but * drops loopback stubs (see {@link isLoopbackResolver}) — they can't be reached * across the namespace — and falls back to a public resolver (8.8.8.8, which * slirp4netns NATs out) when nothing routable remains. */ export declare function parseResolvers(resolvConf: string): string[]; /** * Build the nftables ruleset that enforces the allowlist INSIDE the netns: a * `policy drop` output chain that accepts loopback, established replies, DNS to * the resolvers, and each allowlisted host's resolved IPs (a per-host `counter` + * `comment "allow:"` so the read-back maps traffic to a name), then a * catch-all `log`+`counter`+`drop`. v4 and v6 are separate rules (an `inet` set * can't mix families) sharing the host's comment. Pure → the ruleset is asserted * in a unit test. */ export declare function buildEgressNft(opts: { allow: readonly ResolvedHost[]; resolvers: readonly string[]; }): string; /** The bound paths the in-netns wrapper and the orchestrator hand back through. */ export interface EgressFiles { readonly ioDir: string; readonly netready: string; readonly nft: string; readonly event: string; readonly counters: string; } /** * Assemble the full bwrap argv for an allowlisted-egress run: the shared * confinement args, the hook's added-back env, `CAP_NET_ADMIN` (so the in-netns * wrapper can load nft), `--info-fd 3` (so the orchestrator learns the child PID * to hand slirp4netns), the `VIG_*` paths the wrapper reads, and the trailing * `sh -c `. Pure (the bwrap/setenv args are computed by the caller), so * the assembled shape — caps, info-fd, the VIG_* env, the wrapper payload — is * asserted in a unit test. */ export declare function buildEgressBwrapArgv(opts: { base: readonly string[]; setenv: readonly string[]; files: EgressFiles; command: string; wrapper: string; /** Host path to the generated resolv.conf bound over /etc/resolv.conf in-netns, * so the hook resolves via the routable resolvers, not the host's loopback stub. */ resolvConf: string; }): string[]; /** * Parse `nft list chain inet vig output` back into per-host allowed counters and * the aggregate drop counter. Sums multiple rules that share a host comment (the * v4 + v6 split). Pure, so the read-back is unit-tested without a sandbox. */ export declare function parseNftCounters(nftText: string): EgressCounters; /** * Map parsed counters to the run result: one {@link EgressAttempt} per * allowlisted host that saw traffic (`allowed: true`, with its packet/byte * counts), plus the aggregate dropped counter. `ts` is stamped once per run (the * counters are end-of-run totals, not per-connection events). Pure. */ export declare function countersToResult(counters: EgressCounters, now: number): { egress: EgressAttempt[]; egressDropped: { packets: number; bytes: number; }; }; //# sourceMappingURL=egress.d.ts.map