@push.rocks/smartnftables 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/changelog.md +19 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +5 -5
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +5 -5
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/classes.manageddockerforwarding.d.ts +2 -1
  8. package/dist_ts/classes.manageddockerforwarding.js +3 -2
  9. package/dist_ts/forwardhooks.d.ts +8 -0
  10. package/dist_ts/forwardhooks.js +42 -0
  11. package/dist_ts/index.d.ts +2 -0
  12. package/dist_ts/index.js +2 -1
  13. package/dist_ts/managed.docker.types.d.ts +2 -1
  14. package/dist_ts/managed.egress.types.d.ts +5 -0
  15. package/dist_ts/managed.forwardhooks.types.d.ts +35 -0
  16. package/dist_ts/managed.forwardhooks.types.js +2 -0
  17. package/package.json +5 -4
  18. package/readme.md +139 -19
  19. package/rust/src/docker.frontend.rs +2 -2
  20. package/rust/src/docker.graph.rs +36 -11
  21. package/rust/src/docker.policy.rs +95 -32
  22. package/rust/src/docker.rs +37 -13
  23. package/rust/src/docker_tests.rs +164 -2
  24. package/rust/src/egress.host.rs +53 -45
  25. package/rust/src/egress.rs +64 -0
  26. package/rust/src/egress_exclusiveforwarding_tests.rs +256 -0
  27. package/rust/src/forwardhooks.rs +176 -0
  28. package/rust/src/forwardhooks_tests.rs +136 -0
  29. package/rust/src/main.rs +12 -8
  30. package/rust/src/owner_exclusiveforwarding_tests.rs +200 -0
  31. package/rust/src/owner_host_traffic_tests.rs +1 -1
  32. package/rust/src/owner_tests.rs +3 -0
  33. package/rust/src/wire.links.rs +28 -6
  34. package/rust/src/wire.rs +22 -2
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts/classes.manageddockerforwarding.ts +2 -1
  37. package/ts/forwardhooks.ts +39 -0
  38. package/ts/index.ts +2 -0
  39. package/ts/managed.docker.types.ts +2 -1
  40. package/ts/managed.egress.types.ts +5 -0
  41. package/ts/managed.forwardhooks.types.ts +26 -0
package/changelog.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-01 - 4.2.0
4
+
5
+ ### Features
6
+
7
+ - `ManagedDockerForwarding` now admits every forwarded flow the non-exclusive `hostTransit` barrier admits, each with its replies: besides the leased ranges, each inbound publication (uplink to handoff after the barrier's DNAT, matched on its inside destination and its published `hostIp` and port(s) as the original destination) and each symmetric publication's flows from its target ports (handoff to uplink, before the barrier's SNAT). The barrier's forward chain and the contribution derive from one enumeration of the scope's forwarded flows (`HostScope::forwarding`), and the barrier's compiled bytes are unchanged. A router's own egress reaches the host as a leased flow and stays covered by the leased rules. Every flow kind is three rules (NEW with TCP SYN only, ESTABLISHED, ESTABLISHED reply); the native graph check verifies the conntrack original-destination tuple bytes. A scope without publications keeps its exact rules, comments and graphs. The 192-rule and 100,000-byte bound is unchanged, so beside one handoff with a TCP and a UDP range a scope with more than 62 publications, or 31 symmetric ones, is refused as `EXHAUSTED`.
8
+ - `ManagedDockerForwarding.reconcile()` amends a running generation in place: a handoff link that `previous` and `target` both bind exactly may stay up, because the single `iptables-nft-restore` transaction swaps the owned block in one atomic commit. Links the transition adds or drops, and every link of a first apply, must still be down.
9
+ - `ManagedDockerForwarding.release()` and `close()` count a handoff link whose index no link holds any more as down, so a process restarted in the same boot can release the previous contribution after its router namespace and veths are gone. An existing link must still be the exact bound link and down, and reconcile keeps requiring existing links.
10
+ - Add `inspectForwardHooks({ binaryPath?, networkNamespaceFd? })`: a read-only, generation-consistent report of every nf_tables forward base chain of the `ip`, `ip6` and `inet` families with its table handle, priority and policy, of Docker's `DOCKER-USER`/`DOCKER-FORWARD` chains, and of registered legacy xtables tables, so a caller can refuse `exclusiveForwarding` while another forward owner exists. The native binary gains a `--forward-hooks` mode for it.
11
+
12
+ ## 2026-10-01 - 4.1.0
13
+
14
+ ### Features
15
+
16
+ - Add optional exclusive forwarding to the schema-v2 `hostTransit` scope: `exclusiveForwarding: true` ends the scope's forward chain in one unconditional drop, so the host forwards exactly the scope's leased, published and symmetric handoff flows and their replies, and every other forwarded packet drops, IPv4 and IPv6, on every pair of links, including flows established before the member was applied. It is the host-wide forwarding default-deny that Docker's FORWARD policy DROP used to provide, for hosts where this table is the only forwarding owner: a drop in any table's forward chain is final, so it also denies forwarding other owners accept. With no `handoffs` the scope forwards nothing. The drop is created, verified, adopted, recovered, replaced and released with the table in one batch; this package sets no sysctl. Absent and `false` are the same canonical policy, digest and compiled bytes. `ManagedDockerForwarding.prepare()` refuses an exclusive barrier as `INVALID`, because its drop would deny Docker's own container forwarding.
17
+
18
+ ### Maintenance
19
+
20
+ - Release tooling currency: `packageManager` `pnpm@12.6.0`, `@git.zone/cli` 8.1.0, `@git.zone/tsrust` 2.0.1 and `@git.zone/tsrun` `^3.0.2`; `@git.zone/tsbuild` 5.0.0 and `@git.zone/tstest` 6.3.2 are already current, and `@types/node` stays at 26.6.2 because 26.6.3 is younger than seven days. tsrust 2 builds only the host architecture in a plain `tsrust`, so the new `build:release` script builds both configured musl targets with `tsrust --configured-targets` and checks the set with `tsrust verify`, and `release.preflight.buildCommand` runs it, so a release still ships the amd64 and arm64 binaries. The 36 lock entries recorded with SHA-1 integrity are restored to the SHA-512 integrity registry.npmjs.org publishes, after each SHA-1 matched the npmjs shasum.
21
+
3
22
  ## 2026-09-28 - 4.0.1
4
23
 
5
24
  ### Fixes
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "e57ca68c62eeca26010cf3acf5159dd360205566ca1e96189faebffc2d49b0d7",
3
+ "binarySha256": "5c2f8a063c4d9b145882ca1782151a016adb44940a9a0c99e13cbd5eeea619bb",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "4.0.1",
7
- "gitCommit": "16dff8eb2060592212ea695074d7b53f39be8192",
6
+ "projectVersion": "4.2.0",
7
+ "gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-09-28T17:43:55.875Z",
10
- "tsrustVersion": "1.15.1",
9
+ "builtAt": "2026-10-01T20:26:51.965Z",
10
+ "tsrustVersion": "2.0.1",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_amd64_musl"
13
13
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "35d2fab639a6bc81cbe8395dc8d2dded56ce16712995007b127018fe0ed5ad20",
3
+ "binarySha256": "acdccfdbc055aaea30aaf547020585cabfe0a07be6e590794372fbc8950c5f6b",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "4.0.1",
7
- "gitCommit": "16dff8eb2060592212ea695074d7b53f39be8192",
6
+ "projectVersion": "4.2.0",
7
+ "gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-09-28T17:44:06.408Z",
10
- "tsrustVersion": "1.15.1",
9
+ "builtAt": "2026-10-01T20:27:00.986Z",
10
+ "tsrustVersion": "2.0.1",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_arm64_musl"
13
13
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '4.0.1',
6
+ version: '4.2.0',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1IQUFtSDtDQUNqSSxDQUFBIn0=
@@ -12,6 +12,7 @@ export declare class ManagedDockerForwarding {
12
12
  release(expected: IAppliedManagedDockerForwardingPolicy): Promise<{
13
13
  released: true;
14
14
  }>;
15
- /** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down through cleanup. */
15
+ /** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
16
+ * through cleanup; a handoff link that no longer exists counts as down. */
16
17
  close(): Promise<void>;
17
18
  }
@@ -65,7 +65,8 @@ export class ManagedDockerForwarding {
65
65
  release(expected) {
66
66
  return this.#invoke('releaseDockerForwarding', expected);
67
67
  }
68
- /** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down through cleanup. */
68
+ /** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
69
+ * through cleanup; a handoff link that no longer exists counts as down. */
69
70
  close() {
70
71
  if (this.#close)
71
72
  return this.#close;
@@ -124,4 +125,4 @@ export class ManagedDockerForwarding {
124
125
  return owned;
125
126
  }
126
127
  }
127
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkZG9ja2VyZm9yd2FyZGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMubWFuYWdlZGRvY2tlcmZvcndhcmRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFHNUcseUdBQXlHO0FBQ3pHLE1BQU0sT0FBTyx1QkFBdUI7SUFDekIsUUFBUSxDQUE0QztJQUM3RCxPQUFPLEdBQTBFLElBQUksQ0FBQztJQUN0RixNQUFNLEdBQTBFLEtBQUssQ0FBQztJQUN0RixLQUFLLEdBQTRCLElBQUksQ0FBQztJQUN0QyxNQUFNLEdBQXlCLElBQUksQ0FBQztJQUNwQyxhQUFhLEdBQUcsS0FBSyxDQUFDO0lBRXRCLFlBQVksT0FBd0M7UUFDbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxvQkFBb0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztlQUNySCxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7ZUFDckksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENB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
128
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkZG9ja2VyZm9yd2FyZGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMubWFuYWdlZGRvY2tlcmZvcndhcmRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFHNUcseUdBQXlHO0FBQ3pHLE1BQU0sT0FBTyx1QkFBdUI7SUFDekIsUUFBUSxDQUE0QztJQUM3RCxPQUFPLEdBQTBFLElBQUksQ0FBQztJQUN0RixNQUFNLEdBQTBFLEtBQUssQ0FBQztJQUN0RixLQUFLLEdBQTRCLElBQUksQ0FBQztJQUN0QyxNQUFNLEdBQXlCLElBQUksQ0FBQztJQUNwQyxhQUFhLEdBQUcsS0FBSyxDQUFDO0lBRXRCLFlBQVksT0FBd0M7UUFDbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxvQkFBb0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztlQUNySCxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7ZUFDckksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENB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
@@ -0,0 +1,8 @@
1
+ import type { IForwardHookInspection, IForwardHookInspectionOptions } from './managed.forwardhooks.types.js';
2
+ /** Reads every packet-filter owner on the IPv4/IPv6 forward hook of one network
3
+ * namespace: nf_tables forward base chains of the `ip`, `ip6` and `inet`
4
+ * families, Docker's DOCKER-USER/DOCKER-FORWARD chains and registered legacy
5
+ * xtables tables. One native process takes one generation-consistent read and
6
+ * exits; it claims, adopts and changes nothing. The result is a point-in-time
7
+ * view: another owner can appear after it. */
8
+ export declare function inspectForwardHooks(options?: IForwardHookInspectionOptions): Promise<IForwardHookInspection>;
@@ -0,0 +1,42 @@
1
+ import * as plugins from './plugins.js';
2
+ import { snapshot, managedIpcBytes, ManagedNftablesError, nativeError } from './classes.managednftables.js';
3
+ /** Reads every packet-filter owner on the IPv4/IPv6 forward hook of one network
4
+ * namespace: nf_tables forward base chains of the `ip`, `ip6` and `inet`
5
+ * families, Docker's DOCKER-USER/DOCKER-FORWARD chains and registered legacy
6
+ * xtables tables. One native process takes one generation-consistent read and
7
+ * exits; it claims, adopts and changes nothing. The result is a point-in-time
8
+ * view: another owner can appear after it. */
9
+ export async function inspectForwardHooks(options = {}) {
10
+ const captured = snapshot(options);
11
+ if (Object.keys(captured).some((key) => !['binaryPath', 'networkNamespaceFd'].includes(key))
12
+ || captured.binaryPath !== undefined && (typeof captured.binaryPath !== 'string'
13
+ || !plugins.path.isAbsolute(captured.binaryPath) || captured.binaryPath.includes('\0'))
14
+ || captured.networkNamespaceFd !== undefined && (!Number.isInteger(captured.networkNamespaceFd)
15
+ || captured.networkNamespaceFd < 0 || captured.networkNamespaceFd > 0x7fff_ffff)) {
16
+ throw new ManagedNftablesError('INVALID');
17
+ }
18
+ if (process.platform !== 'linux' || !['x64', 'arm64'].includes(process.arch))
19
+ throw new ManagedNftablesError('UNSUPPORTED_PLATFORM');
20
+ const root = plugins.url.fileURLToPath(new URL('../', import.meta.url));
21
+ const bridge = new plugins.smartrust.RustBridge({
22
+ binaryName: 'smartnftables', binaryPath: captured.binaryPath
23
+ ?? plugins.path.join(root, 'dist_rust', `smartnftables_linux_${process.arch === 'x64' ? 'amd64' : 'arm64'}_musl`),
24
+ localPaths: [], searchSystemPath: false,
25
+ cliArgs: ['--forward-hooks', ...(captured.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
26
+ inheritedFileDescriptors: captured.networkNamespaceFd === undefined ? undefined : [captured.networkNamespaceFd],
27
+ readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: managedIpcBytes,
28
+ maxPendingRequestsByMethod: { inspectForwardHooks: 1 },
29
+ });
30
+ try {
31
+ if (!await bridge.spawn())
32
+ throw new ManagedNftablesError('UNAVAILABLE');
33
+ return snapshot(await bridge.sendCommand('inspectForwardHooks', {}));
34
+ }
35
+ catch (error) {
36
+ throw error instanceof ManagedNftablesError ? error : nativeError(error);
37
+ }
38
+ finally {
39
+ await bridge.terminate({ gracePeriodMs: 1000 });
40
+ }
41
+ }
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yd2FyZGhvb2tzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZm9yd2FyZGhvb2tzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxRQUFRLEVBQUUsZUFBZSxFQUFFLG9CQUFvQixFQUFFLFdBQVcsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRzVHOzs7Ozs4Q0FLOEM7QUFDOUMsTUFBTSxDQUFDLEtBQUssVUFBVSxtQkFBbUIsQ0FBQyxVQUF5QyxFQUFFO0lBQ25GLE1BQU0sUUFBUSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsWUFBWSxFQUFFLG9CQUFvQixDQUFDLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1dBQ3ZGLFFBQVEsQ0FBQyxVQUFVLEtBQUssU0FBUyxJQUFJLENBQUMsT0FBTyxRQUFRLENBQUMsVUFBVSxLQUFLLFFBQVE7ZUFDM0UsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksUUFBUSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7V0FDdEYsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUM7ZUFDMUYsUUFBUSxDQUFDLGtCQUFrQixHQUFHLENBQUMsSUFBSSxRQUFRLENBQUMsa0JBQWtCLEdBQUcsV0FBVyxDQUFDLEVBQUUsQ0FBQztRQUNyRixNQUFNLElBQUksb0JBQW9CLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUNELElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztRQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ3JJLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDeEUsTUFBTSxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBdUI7UUFDcEUsVUFBVSxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVU7ZUFDdkQsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRSx1QkFBdUIsT0FBTyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsT0FBTyxPQUFPLENBQUM7UUFDbkgsVUFBVSxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxLQUFLO1FBQ3ZDLE9BQU8sRUFBRSxDQUFDLGlCQUFpQixFQUFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsd0JBQXdCLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNuSCx3QkFBd0IsRUFBRSxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDO1FBQy9HLGNBQWMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxlQUFlO1FBQy9FLDBCQUEwQixFQUFFLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQyxFQUFFO0tBQ3ZELENBQUMsQ0FBQztJQUNILElBQUksQ0FBQztRQUNILElBQUksQ0FBQyxNQUFNLE1BQU0sQ0FBQyxLQUFLLEVBQUU7WUFBRSxNQUFNLElBQUksb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDekUsT0FBTyxRQUFRLENBQUMsTUFBTSxNQUFNLENBQUMsV0FBVyxDQUFDLHFCQUFxQixFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLEtBQUssWUFBWSxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0UsQ0FBQztZQUFTLENBQUM7UUFDVCxNQUFNLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNsRCxDQUFDO0FBQ0gsQ0FBQyJ9
@@ -13,3 +13,5 @@ export { buildFirewallRule, buildIPSetCreate, buildIPSetAddElements, buildIPSetR
13
13
  export type { TNftProtocol, TNftFamily, TNftChainHook, TNftChainType, TNftPolicy, TFirewallAction, TCtState, INftDnatRule, INftSnatRule, INftMasqueradeRule, INftRateLimitRule, INftConnectionRateRule, INftFirewallRule, INftIPSetBlockOptions, INftIPSetConfig, INftCleanupOptions, INftRuleGroup, ISmartNftablesOptions, INftStatus, } from './nft.types.js';
14
14
  export * from './managed.docker.types.js';
15
15
  export * from './classes.manageddockerforwarding.js';
16
+ export type * from './managed.forwardhooks.types.js';
17
+ export { inspectForwardHooks } from './forwardhooks.js';
package/dist_ts/index.js CHANGED
@@ -14,4 +14,5 @@ export { buildRateLimitRule, buildPerIpRateLimitRule, buildConnectionRateRule, }
14
14
  export { buildFirewallRule, buildIPSetCreate, buildIPSetAddElements, buildIPSetRemoveElements, buildIPSetDelete, buildIPSetMatchRule, } from './nft.rulebuilder.firewall.js';
15
15
  export * from './managed.docker.types.js';
16
16
  export * from './classes.manageddockerforwarding.js';
17
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBQ2QsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUlyRixlQUFlO0FBQ2YsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUU5RCxXQUFXO0FBQ1gsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFbEYscUNBQXFDO0FBQ3JDLE9BQU8sRUFDTCxlQUFlLEVBQ2YsaUJBQWlCLEVBQ2pCLGlCQUFpQixHQUNsQixNQUFNLDRCQUE0QixDQUFDO0FBRXBDLE9BQU8sRUFDTCxjQUFjLEVBQ2QsYUFBYSxFQUNiLG1CQUFtQixHQUNwQixNQUFNLDBCQUEwQixDQUFDO0FBRWxDLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsdUJBQXVCLEVBQ3ZCLHVCQUF1QixHQUN4QixNQUFNLGdDQUFnQyxDQUFDO0FBRXhDLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsZ0JBQWdCLEVBQ2hCLHFCQUFxQixFQUNyQix3QkFBd0IsRUFDeEIsZ0JBQWdCLEVBQ2hCLG1CQUFtQixHQUNwQixNQUFNLCtCQUErQixDQUFDO0FBd0J2QyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsc0NBQXNDLENBQUMifQ==
17
+ export { inspectForwardHooks } from './forwardhooks.js';
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBQ2QsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUlyRixlQUFlO0FBQ2YsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUU5RCxXQUFXO0FBQ1gsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFbEYscUNBQXFDO0FBQ3JDLE9BQU8sRUFDTCxlQUFlLEVBQ2YsaUJBQWlCLEVBQ2pCLGlCQUFpQixHQUNsQixNQUFNLDRCQUE0QixDQUFDO0FBRXBDLE9BQU8sRUFDTCxjQUFjLEVBQ2QsYUFBYSxFQUNiLG1CQUFtQixHQUNwQixNQUFNLDBCQUEwQixDQUFDO0FBRWxDLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsdUJBQXVCLEVBQ3ZCLHVCQUF1QixHQUN4QixNQUFNLGdDQUFnQyxDQUFDO0FBRXhDLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsZ0JBQWdCLEVBQ2hCLHFCQUFxQixFQUNyQix3QkFBd0IsRUFDeEIsZ0JBQWdCLEVBQ2hCLG1CQUFtQixHQUNwQixNQUFNLCtCQUErQixDQUFDO0FBd0J2QyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsc0NBQXNDLENBQUM7QUFFckQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sbUJBQW1CLENBQUMifQ==
@@ -32,7 +32,8 @@ export interface IAppliedManagedDockerForwardingPolicy {
32
32
  };
33
33
  prepared: IPreparedManagedDockerForwardingPolicy;
34
34
  }
35
- /** Persist before mutation. Keep both generations' exact handoff links down until reconciliation is confirmed. */
35
+ /** Persist before mutation. A handoff link both generations bind exactly may stay up: one atomic transaction swaps
36
+ * the contribution. Keep every link the transition adds or drops down until reconciliation is confirmed. */
36
37
  export interface IManagedDockerForwardingTransition {
37
38
  previous: IAppliedManagedDockerForwardingPolicy | null;
38
39
  target: IPreparedManagedDockerForwardingPolicy;
@@ -196,6 +196,11 @@ export interface IManagedNftHostTransitScopeV2 {
196
196
  * leased source address and a source port of the allocation's range for the endpoint's protocol; its
197
197
  * ESTABLISHED replies return. Absent and empty are the same canonical policy, digest and compiled graph. */
198
198
  localPlatformEndpoints?: string[];
199
+ /** Optional: this table is the host's only forwarding owner. Every forwarded packet the scope does not
200
+ * admit (its leased, published and symmetric handoff flows) drops, IPv4 and IPv6, on every pair of
201
+ * interfaces, including forwarding that another table would accept. With no `handoffs` the host forwards
202
+ * nothing. It sets no sysctl. Absent and `false` are the same canonical policy, digest and compiled graph. */
203
+ exclusiveForwarding?: boolean;
199
204
  }
200
205
  /** One loopback TCP service that only one local user may dial. Every packet the host sends to the
201
206
  * exact address and port from a socket of any other user is rejected with a TCP reset, after ordinary
@@ -0,0 +1,35 @@
1
+ import type { IManagedNftOptions } from './managed.types.js';
2
+ export interface IForwardHookInspectionOptions extends Pick<IManagedNftOptions, 'binaryPath' | 'networkNamespaceFd'> {
3
+ }
4
+ /** One nf_tables base chain on the forward hook of the `ip`, `ip6` or `inet` family. */
5
+ export interface IForwardHookChain {
6
+ family: 'ip' | 'ip6' | 'inet';
7
+ table: string;
8
+ /** Kernel table handle, as a managed receipt's `tableHandle`. */
9
+ tableHandle: string;
10
+ chain: string;
11
+ type: string;
12
+ priority: number;
13
+ policy: 'accept' | 'drop';
14
+ }
15
+ /** One consistent read of every packet-filter owner on the routed forward path of a network namespace. */
16
+ export interface IForwardHookInspection {
17
+ /** Every forward base chain, sorted by family, table and chain; managed tables included. */
18
+ chains: IForwardHookChain[];
19
+ /** Docker's iptables-nft dispatch chains in table `ip filter`. */
20
+ docker: {
21
+ dockerUser: boolean;
22
+ dockerForward: boolean;
23
+ };
24
+ /** Registered legacy xtables tables, whose hooks are not nf_tables chains. */
25
+ legacyTables: {
26
+ ipv4: string[];
27
+ ipv6: string[];
28
+ };
29
+ }
30
+ export type TForwardHookCommands = {
31
+ inspectForwardHooks: {
32
+ params: Record<string, never>;
33
+ result: IForwardHookInspection;
34
+ };
35
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuYWdlZC5mb3J3YXJkaG9va3MudHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9tYW5hZ2VkLmZvcndhcmRob29rcy50eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartnftables",
3
- "version": "4.0.1",
3
+ "version": "4.2.0",
4
4
  "private": false,
5
5
  "description": "A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.",
6
6
  "main": "dist_ts/index.js",
@@ -9,10 +9,10 @@
9
9
  "author": "Task Venture Capital GmbH",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "@git.zone/cli": "7.3.0",
12
+ "@git.zone/cli": "8.1.0",
13
13
  "@git.zone/tsbuild": "^5.0.0",
14
- "@git.zone/tsrun": "^3.0.1",
15
- "@git.zone/tsrust": "1.15.1",
14
+ "@git.zone/tsrun": "^3.0.2",
15
+ "@git.zone/tsrust": "2.0.1",
16
16
  "@git.zone/tstest": "6.3.2",
17
17
  "@types/node": "26.6.2",
18
18
  "typescript": "^7.0.2"
@@ -66,6 +66,7 @@
66
66
  "scripts": {
67
67
  "test": "(tstest test/**/test*.ts --verbose --timeout 60)",
68
68
  "build": "(tsbuild tsfolders) && (tsrust)",
69
+ "build:release": "(tsbuild tsfolders) && (tsrust --configured-targets) && (tsrust verify)",
69
70
  "format": "(gitzone format)",
70
71
  "buildDocs": "tsdoc"
71
72
  }
package/readme.md CHANGED
@@ -321,7 +321,7 @@ allocation-pool guard policy kinds.
321
321
  | V2 scope | Required authority and behavior |
322
322
  | --- | --- |
323
323
  | `routerEgress` | Private `endpoints` and `rules`, one exact `links` binding per endpoint, a separate veth `handoff`, `protection`, and active `generations`. Private veth/TUN/local DNS and egress share one table so terminal private denial cannot override a separate egress table. Optional `publishedPorts` add the inbound second hop from the handoff to a workload endpoint, one port or a port range; both directions are classified into the default conntrack zone ahead of every leased classifier, so a published endpoint port is dedicated to its publication and never becomes leased egress. A `symmetric` publication also lets the workload open flows from its published ports. Optional `hostGrants` forward exact host-origin flows from the handoff to a workload endpoint. Optional `workloadGrants` let one workload open one exact port of another, one way. |
324
- | `hostTransit` | Exact handoff `link`/`allocations` pairs, complete `protection`, an explicit veth or Ethernet `uplink`, and its current `snatAddress`. It checks each handoff's leased source address and protocol/port range, default conntrack zone, direction, uplink, and protected destinations before outer SNAT. Optional `publishedPorts` add inbound uplink destination NAT inside the same generation, one port or a port range, and a `symmetric` publication also carries the workload's own flows from its published ports out through the uplink. Optional `hostGrants` let the host's own address on a handoff dial exact workload ports. Optional `localPlatformEndpoints` serve platform endpoints on the host's own addresses to leased flows. |
324
+ | `hostTransit` | Exact handoff `link`/`allocations` pairs, complete `protection`, an explicit veth or Ethernet `uplink`, and its current `snatAddress`. It checks each handoff's leased source address and protocol/port range, default conntrack zone, direction, uplink, and protected destinations before outer SNAT. Optional `publishedPorts` add inbound uplink destination NAT inside the same generation, one port or a port range, and a `symmetric` publication also carries the workload's own flows from its published ports out through the uplink. Optional `hostGrants` let the host's own address on a handoff dial exact workload ports. Optional `localPlatformEndpoints` serve platform endpoints on the host's own addresses to leased flows. Optional `exclusiveForwarding` makes the table the host's only forwarding owner: every other forwarded packet drops. |
325
325
  | `allocationPoolGuard` | An authenticated `authorityDigest` and complete current allocation-pool `prefixes`. Installs host-wide IPv4 destination denial before any handoff exists, without link, uplink or SNAT dependencies. Optional `hostGrants` are its only exceptions. Optional `localTcpPortOwners` restrict loopback TCP ports to one local user each. |
326
326
 
327
327
  `allocationPoolGuard` accepts 1–64 canonical, disjoint RFC1918 prefixes. Supply
@@ -333,7 +333,9 @@ source, then return; all remaining packets check their current destination. This
333
333
  denies DNAT into or away from a pool and untracked current-destination traffic,
334
334
  while permitting reverse-SNAT replies to authorized transit sources. It grants no
335
335
  egress permissions; exact `hostTransit` and router policies remain separate owners.
336
- Unrelated host traffic and IPv6 retain their existing behavior.
336
+ Unrelated host traffic and IPv6 retain their existing behavior; the guard never denies
337
+ forwarding between other links. On a host that forwards only for the handoffs, see
338
+ [Exclusive forwarding](#exclusive-forwarding).
337
339
 
338
340
  For example, a guard policy is `{ schemaVersion: 2, revision: 1, scope: {
339
341
  kind: 'allocationPoolGuard', authorityDigest, prefixes: ['10.240.0.0/16',
@@ -450,8 +452,8 @@ under host grants. 64 symmetric ranges take about 19,600 element bytes.
450
452
  The caller still owns the route to `targetAddress` through that handoff, the
451
453
  workload listener, and every other packet owner on the host. As with leased egress,
452
454
  an ACCEPT here cannot override Docker's independent FORWARD DROP; the
453
- `ManagedDockerForwarding` contribution below derives only leased handoff egress from
454
- the barrier and currently carries no inbound publication.
455
+ `ManagedDockerForwarding` contribution below admits every inbound and symmetric
456
+ publication of the barrier through it, with the leased flows.
455
457
 
456
458
  Each router generation binds an immutable lease reference, transit source address,
457
459
  leased TCP/UDP source-port ranges, a nonzero conntrack zone and a 16-byte label.
@@ -652,6 +654,62 @@ rules. Absent and empty are the same canonical policy, digest and compiled bytes
652
654
  caller keeps a declared endpoint's address outside every guarded allocation pool and
653
655
  owns the listener.
654
656
 
657
+ #### Exclusive forwarding
658
+
659
+ A host that forwards IPv4 (`net.ipv4.conf.all.forwarding=1`) routes between all of its
660
+ links. On a Docker host, Docker set that switch and also the iptables FORWARD policy
661
+ DROP, and `ManagedDockerForwarding` admits the handoff flows through it. On a host
662
+ without Docker nothing denies forwarding that does not touch a handoff: host transit
663
+ governs only handoff traffic, and the allocation-pool guard only pool destinations,
664
+ so a LAN peer could use the host as its gateway, the uplink could hairpin, and two
665
+ other links could exchange traffic. `hostTransit.exclusiveForwarding: true` closes
666
+ that path inside the barrier's own table:
667
+
668
+ ```typescript
669
+ const transit: IManagedNftPolicyV2 = { schemaVersion: 2, revision, scope: {
670
+ kind: 'hostTransit', protection, handoffs, uplink, snatAddress, exclusiveForwarding: true } };
671
+ ```
672
+
673
+ The forward chain ends in one unconditional drop, after every rule of the scope.
674
+ The host then forwards exactly what the scope admits: the leased flows of each
675
+ handoff to the uplink with their ESTABLISHED replies, the inbound publications and
676
+ their replies, and the symmetric publications' flows. Everything else that reaches
677
+ the forward hook drops, whatever its links: IPv4 between the uplink, a LAN or any
678
+ other link, a hairpin through the uplink, flows that were established before the
679
+ member was applied, and forwarded IPv6. There is no ESTABLISHED/RELATED bypass, as
680
+ everywhere in this scope, so ICMP errors for leased flows stay denied as before.
681
+ Allocation pools need no exception: pool addresses are routed inside the router
682
+ namespace and leave it translated to the transit source, so on the host only
683
+ transit addresses cross the forward hook, and the pool guard denies pool
684
+ destinations there anyway. IPv6 is dropped because the scope's packet model is
685
+ IPv4 only; a host whose IPv6 forwarding is off never presents IPv6 to the hook.
686
+ Input, output and NAT are unchanged, and so are the host's own flows: host grants
687
+ and host-local platform endpoints are INPUT and OUTPUT traffic. With no `handoffs`
688
+ the scope forwards nothing at all.
689
+
690
+ nftables runs the forward base chain of every table, and a drop in any one of them
691
+ is final, while an accept only ends its own chain. The member therefore drops
692
+ forwarding that another owner (Docker, a container or VM bridge, a VPN router, a
693
+ second routing daemon) accepts, and accepting in this table still cannot override
694
+ another owner's drop. Use it only on a host where this table is the sole forwarding
695
+ owner; on a Docker host keep it absent and use the Docker contribution below, which
696
+ refuses an exclusive barrier. `inspectForwardHooks()`, described below, reports
697
+ the other forward owners of the namespace for that decision. The
698
+ member does not fence flowtable offload, packet queues, proxies or anything outside
699
+ the forward hook.
700
+
701
+ The drop is part of the scope's graph: it is created, verified, adopted, recovered,
702
+ replaced and released with the rest of the table in one batch, and a replacement
703
+ without the member or `release()` removes it, so forwarding resumes for every link
704
+ the moment the table goes. This package sets no sysctl. The denial holds only while
705
+ an exclusive table is applied, so the caller enables host forwarding only after an
706
+ exclusive scope is enforced and disables it before releasing that scope, or keeps
707
+ an exclusive scope applied between generations (with no handoffs it is a pure
708
+ host-wide forwarding denial) and replaces it in place. Persistent forwarding in
709
+ `sysctl.d` would make the host forward unfiltered after every boot until the scope
710
+ is applied again, so leave the boot default off. Absent and `false` are the same
711
+ canonical policy, digest and compiled bytes; the member adds one rule.
712
+
655
713
  #### Workload grants
656
714
 
657
715
  `routerEgress.workloadGrants` is optional: a stateful one-way flow between two
@@ -823,7 +881,7 @@ drainage, conntrack cleanup, elapsed-time expiry, or safe address/port/zone reus
823
881
 
824
882
  ### Docker forwarding contribution
825
883
 
826
- `ManagedDockerForwarding` admits the leased handoff traffic from an exact applied
884
+ `ManagedDockerForwarding` admits exactly the forwarded flows of an exact applied
827
885
  schema-v2 `hostTransit` receipt through Docker's existing IPv4 forwarding path.
828
886
  It reads and verifies that dedicated barrier's complete table and local links,
829
887
  without adopting its ownership. The contribution uses Docker's supported
@@ -855,34 +913,88 @@ an explicit `/32` has different hidden match bytes in these frontends. A
855
913
  semantically equal rewrite with a different exact representation rejects, as does
856
914
  an early ACCEPT that the frontend reconstructs as the same command.
857
915
  The contribution never creates, flushes, adopts or deletes a Docker table/chain,
858
- and never changes a host forwarding policy.
859
-
860
- Each contributed rule binds the handoff and uplink names, current and original
861
- transit source, exact leased TCP/UDP port range, packet direction and connection
862
- state. New TCP flows require SYN with FIN/RST/ACK clear; reverse traffic requires
863
- ESTABLISHED. The exact v2 host barrier independently checks interface indices,
864
- MAC/iflink facts, the default host conntrack zone, protected original/current
865
- destinations and explicit outer SNAT. A complete contribution is bounded to 192
866
- rules and 100,000 generated command bytes.
916
+ and never changes a host forwarding policy. `prepare()` refuses a barrier with
917
+ `exclusiveForwarding` as `INVALID`: its drop would also deny Docker's own container
918
+ forwarding.
919
+
920
+ The contribution and the barrier's forward chain derive from one enumeration of
921
+ the scope's forwarded flows, so the contribution admits exactly the flow kinds the
922
+ non-exclusive barrier admits, each with its replies:
923
+
924
+ | Flow kind | Original direction | Live tuple | Conntrack original tuple |
925
+ | --- | --- | --- | --- |
926
+ | Leased range | handoff → uplink | transit source address and leased port range | same source |
927
+ | Inbound publication | uplink → handoff, after the barrier's DNAT | inside target address and port(s) as destination | published `hostIp` and port(s) as destination |
928
+ | Symmetric publication | handoff → uplink, before the barrier's SNAT | target address and port(s) as source | same source |
929
+
930
+ Every flow kind has three rules: the NEW opening (TCP only with SYN and
931
+ FIN/RST/ACK clear), the ESTABLISHED original direction and the ESTABLISHED reply
932
+ with swapped interfaces and tuple sides. A router's own egress, such as a VPN client
933
+ in the router namespace dialling its hub, reaches the host as a leased flow: the
934
+ router translates it to the transit address and a leased port range, so it is
935
+ admitted by the leased rules exactly when the barrier admits it. Host grants and
936
+ host-local platform endpoints use INPUT/OUTPUT and never cross FORWARD. Each rule
937
+ binds the handoff and uplink names. The exact v2 host barrier independently checks
938
+ interface indices, MAC/iflink facts, the default host conntrack zone, protected
939
+ original/current destinations and explicit outer SNAT; it governs every forwarded
940
+ packet on its handoffs, so the host forwards the intersection, which is the
941
+ barrier's admitted set. A scope without publications keeps its exact 4.1.0 rules,
942
+ comments and graphs. A complete contribution is bounded to 192 rules and 100,000
943
+ generated command bytes: three rules per leased range and per publication, and
944
+ three more per symmetric publication. A larger scope is refused as `EXHAUSTED`
945
+ (`restoreRules`/`restoreBytes`) before any subprocess.
867
946
 
868
947
  Create the class with `ownerId`, a caller-retained `instanceId` of at least 16
869
948
  identifier characters, and optional `binaryPath`/`networkNamespaceFd`. After
870
949
  `start()`, call `prepare({ schemaVersion: 1, revision, barrier })`, where `barrier`
871
950
  is the exact applied host policy. Persist the complete `{ previous, target }`
872
- transition before `reconcile()`. Keep every affected handoff link down through
873
- mutations; retain those exact links until cleanup finishes. The native owner
874
- verifies this down state before and after its single no-flush restore transaction.
875
- Exact target replay can recover a lost response without rewriting live rules.
951
+ transition before `reconcile()`. The native owner fences the handoff links before
952
+ and after its single no-flush restore transaction, which deletes the previous block
953
+ and inserts the target block in one atomic nf_tables commit. A handoff link that
954
+ `previous` and `target` both bind exactly may stay up, so a publication or lease
955
+ amendment of a running generation applies in place: every flow both contributions
956
+ admit passes throughout, a flow only the target admits passes from the commit, and
957
+ the referenced barrier must already be the target. Every handoff link the
958
+ transition adds or drops, and every link of a first apply, must be down; retain
959
+ those exact links until cleanup finishes. Exact target replay can recover a lost
960
+ response without rewriting live rules.
876
961
 
877
962
  `inspect().present` confirms the contribution and its referenced barrier, not
878
963
  the complete host packet path or durable controller authority. Errors retain
879
964
  failed-owned state and pending intent. `release(applied)` and `close()` delete
880
- only exact contributed rules under the same link fence. A cold boot needs a new
965
+ only exact contributed rules. Their fence requires every handoff link down or gone:
966
+ a link whose index no link holds any more, such as a veth that left with its router
967
+ namespace when the node process ended, counts as down, so a restarted process in
968
+ the same boot can release the previous contribution before applying the next one.
969
+ A different link at a bound index still rejects. A cold boot needs a new
881
970
  barrier receipt and caller-authorized replay; old boot receipts cannot authorize
882
971
  native operations. The caller owns restart ordering, disjoint retained leases,
883
972
  exclusive privileged mutation authority and activation. The frontend transaction
884
973
  does not provide compare-and-swap against other privileged processes.
885
974
 
975
+ ### Forward hook owners
976
+
977
+ `inspectForwardHooks({ binaryPath?, networkNamespaceFd? })` reads every
978
+ packet-filter owner on the routed forward path of one network namespace and
979
+ changes nothing. One native process takes the read and exits:
980
+
981
+ - `chains`: every nf_tables base chain on the forward hook of the `ip`, `ip6` and
982
+ `inet` families, with `family`, `table`, `tableHandle`, `chain`, `type`,
983
+ `priority` and `policy`, sorted by family, table and chain. Managed tables are
984
+ included; compare `table` and `tableHandle` with a receipt's
985
+ `identity.tableName` and `tableHandle` to find your own.
986
+ - `docker`: whether Docker's iptables-nft `DOCKER-USER` and `DOCKER-FORWARD`
987
+ chains exist in table `ip filter`.
988
+ - `legacyTables`: the registered legacy xtables `ipv4` and `ipv6` tables. Their
989
+ hooks, including FORWARD, are not nf_tables objects and never appear in `chains`.
990
+
991
+ The table and chain dumps share one ruleset generation, or the read is
992
+ `CONFLICT`. Without `CAP_NET_ADMIN` in the namespace it is `PERMISSION`. Bridge
993
+ and netdev hooks do not see routed packets and are not reported. Guard
994
+ `exclusiveForwarding` with it: refuse while any chain other than your own barrier's,
995
+ either Docker chain or any legacy table is present. The read is a point in time;
996
+ the caller still owns exclusive authority over who may add a forward owner later.
997
+
886
998
  ### Native qualification
887
999
 
888
1000
  The separate [Docker fixture](test/native/readme.docker.md) exercises the native
@@ -970,6 +1082,14 @@ another destination workload, the destination's TCP and UDP openings toward the
970
1082
  source stay dark; withdrawal stops new flows and the established one; release reopens
971
1083
  the path. A set of 1024 grants persists across owner loss, is re-verified element by
972
1084
  element and survives lost-ACK replay.
1085
+ Exclusive forwarding is qualified on a host namespace with a handoff, an uplink and
1086
+ a LAN link: against positive controls with no policy and with the scope without the
1087
+ member, the member denies the LAN's IPv4 to the uplink peer, the peer's way back into
1088
+ the LAN, the LAN's IPv6 and a LAN flow established before it, while leased egress
1089
+ still leaves translated and an unleased port stays denied; without handoffs leased
1090
+ egress drops too; replacing it with a scope without the member and releasing an
1091
+ exclusive scope both reopen the LAN path. The same probe fails against a compiler
1092
+ without the drop.
973
1093
  Loopback TCP port owners are qualified against pre-policy positive controls on the
974
1094
  same listeners: the owning uid (root, and uid 1000 for a second port) connects, every
975
1095
  other uid is reset with `ECONNREFUSED`, an unowned port and another loopback address
@@ -113,10 +113,10 @@ fn normalized(args: &[String]) -> Result<Vec<(String, Vec<String>)>> {
113
113
  let mut values=BTreeMap::new();let mut modules=BTreeSet::new();let mut index=0;
114
114
  while index<args.len() {
115
115
  let key=&args[index];let count=if key=="--tcp-flags" {2} else {1};
116
- if !["-i","-o","-s","-d","-p","-m","--sport","--dport","--tcp-flags","--ctstate","--ctproto","--ctorigsrc","--ctorigsrcport","--ctdir","--comment","-j"].contains(&key.as_str()) {return Err(Error::Conflict);}
116
+ if !["-i","-o","-s","-d","-p","-m","--sport","--dport","--tcp-flags","--ctstate","--ctproto","--ctorigsrc","--ctorigsrcport","--ctorigdst","--ctorigdstport","--ctdir","--comment","-j"].contains(&key.as_str()) {return Err(Error::Conflict);}
117
117
  let mut value=args.get(index+1..index+1+count).ok_or(Error::Conflict)?.to_vec();index+=1+count;
118
118
  if key=="-m" {if !modules.insert(value[0].clone()) {return Err(Error::Conflict);}continue;}
119
- if key=="--ctorigsrc" && !value[0].contains('/') {value[0].push_str("/32");}
119
+ if (key=="--ctorigsrc" || key=="--ctorigdst") && !value[0].contains('/') {value[0].push_str("/32");}
120
120
  if key=="--ctproto" {value[0]=match value[0].as_str() {"tcp"=>"6".into(),"udp"=>"17".into(),_=>value[0].clone()};}
121
121
  if values.insert(key.clone(),value).is_some() {return Err(Error::Conflict);}
122
122
  }