@push.rocks/smartnftables 4.1.0 → 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 (35) hide show
  1. package/changelog.md +9 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
  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.forwardhooks.types.d.ts +35 -0
  15. package/dist_ts/managed.forwardhooks.types.js +2 -0
  16. package/package.json +1 -1
  17. package/readme.md +69 -16
  18. package/rust/src/docker.frontend.rs +2 -2
  19. package/rust/src/docker.graph.rs +36 -11
  20. package/rust/src/docker.policy.rs +90 -31
  21. package/rust/src/docker.rs +37 -13
  22. package/rust/src/docker_tests.rs +161 -2
  23. package/rust/src/egress.host.rs +44 -45
  24. package/rust/src/egress.rs +56 -0
  25. package/rust/src/forwardhooks.rs +176 -0
  26. package/rust/src/forwardhooks_tests.rs +136 -0
  27. package/rust/src/main.rs +12 -8
  28. package/rust/src/wire.links.rs +28 -6
  29. package/rust/src/wire.rs +22 -2
  30. package/ts/00_commitinfo_data.ts +1 -1
  31. package/ts/classes.manageddockerforwarding.ts +2 -1
  32. package/ts/forwardhooks.ts +39 -0
  33. package/ts/index.ts +2 -0
  34. package/ts/managed.docker.types.ts +2 -1
  35. package/ts/managed.forwardhooks.types.ts +26 -0
package/changelog.md CHANGED
@@ -1,5 +1,14 @@
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
+
3
12
  ## 2026-10-01 - 4.1.0
4
13
 
5
14
  ### Features
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "31265adcd734d687d19c98508332ec591f36afd9581c27a3c7b7fac647620b07",
3
+ "binarySha256": "5c2f8a063c4d9b145882ca1782151a016adb44940a9a0c99e13cbd5eeea619bb",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "4.1.0",
7
- "gitCommit": "71cf6d5ee0d229b03b01f85901f5d50984b570a3",
6
+ "projectVersion": "4.2.0",
7
+ "gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-01T18:44:25.782Z",
9
+ "builtAt": "2026-10-01T20:26:51.965Z",
10
10
  "tsrustVersion": "2.0.1",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_amd64_musl"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "a6d37a21727fed45c2a4079261930682fbb136b67ac1b24971015725b2b4ada5",
3
+ "binarySha256": "acdccfdbc055aaea30aaf547020585cabfe0a07be6e590794372fbc8950c5f6b",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "4.1.0",
7
- "gitCommit": "71cf6d5ee0d229b03b01f85901f5d50984b570a3",
6
+ "projectVersion": "4.2.0",
7
+ "gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-01T18:44:34.745Z",
9
+ "builtAt": "2026-10-01T20:27:00.986Z",
10
10
  "tsrustVersion": "2.0.1",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_arm64_musl"
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '4.1.0',
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkZG9ja2VyZm9yd2FyZGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMubWFuYWdlZGRvY2tlcmZvcndhcmRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFHNUcseUdBQXlHO0FBQ3pHLE1BQU0sT0FBTyx1QkFBdUI7SUFDekIsUUFBUSxDQUE0QztJQUM3RCxPQUFPLEdBQTBFLElBQUksQ0FBQztJQUN0RixNQUFNLEdBQTBFLEtBQUssQ0FBQztJQUN0RixLQUFLLEdBQTRCLElBQUksQ0FBQztJQUN0QyxNQUFNLEdBQXlCLElBQUksQ0FBQztJQUNwQyxhQUFhLEdBQUcsS0FBSyxDQUFDO0lBRXRCLFlBQVksT0FBd0M7UUFDbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxvQkFBb0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztlQUNySCxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7ZUFDckksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsTUFBTSxHQUFHLEVBQUU7ZUFDcEMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEtBQUssU0FBUyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDL0ksTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzVDLENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPO2VBQzlFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxDQUFDO2VBQzNGLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEdBQUcsV0FBVyxDQUFDO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ2xHLENBQUM7SUFFRCxJQUFXLEtBQUssS0FBYSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBRWxELHVGQUF1RjtJQUNoRixLQUFLO1FBQ1YsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLEtBQUs7WUFBRSxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO1FBQzFGLElBQUksQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDdkIsSUFBSSxPQUFPLENBQUMsUUFBUSxLQUFLLE9BQU8sSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO2dCQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1lBQ3JJLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDeEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFtQztnQkFDaEYsVUFBVSxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVO3VCQUM1RCxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFLHVCQUF1QixPQUFPLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLE9BQU8sQ0FBQztnQkFDbkgsVUFBVSxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxLQUFLO2dCQUN2QyxPQUFPLEVBQUUsQ0FBQyxxQkFBcUIsRUFBRSxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyx3QkFBd0IsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUM1SCx3QkFBd0IsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUM7Z0JBQ3pILGNBQWMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxlQUFlO2dCQUMvRSwwQkFBMEIsRUFBRSxFQUFFLHVCQUF1QixFQUFFLENBQUMsRUFBRSxvQkFBb0IsRUFBRSxDQUFDLEVBQUUseUJBQXlCLEVBQUUsQ0FBQyxFQUFFLHVCQUF1QixFQUFFLENBQUMsRUFBRSx1QkFBdUIsRUFBRSxDQUFDLEVBQUUscUJBQXFCLEVBQUUsQ0FBQyxFQUFFO2FBQ3BNLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7Z0JBQzNCLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztvQkFBRSxJQUFJLENBQUMsTUFBTSxHQUFHLGNBQWMsQ0FBQztZQUNsRixDQUFDLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFO2dCQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUMvRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUM7WUFDOUMsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsc0JBQXNCLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQ3pHLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxVQUFVO2dCQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUM5RSxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQztZQUFDLE9BQU8sTUFBTSxDQUFDO1FBQ3ZDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDTCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDM0IsQ0FBQztJQUVNLE9BQU8sQ0FBQyxNQUFzQztRQUNuRCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMseUJBQXlCLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUNNLFNBQVMsQ0FBQyxVQUE4QztRQUM3RCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsMkJBQTJCLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUNNLE9BQU8sS0FBOEMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLHlCQUF5QixFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxRyxPQUFPLENBQUMsUUFBK0M7UUFDNUQsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLHlCQUF5QixFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxzSUFBc0k7SUFDL0gsS0FBSztRQUNWLElBQUksSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDcEMsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFNBQVM7WUFBRSxPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUN4RCxJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztRQUN4QixJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDeEIsTUFBTSxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUN6QyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDakIsSUFBSSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7b0JBQ3ZCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7b0JBQzNFLElBQUksTUFBTSxDQUFDLFFBQVEsS0FBSyxJQUFJO3dCQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxVQUFVLENBQUMsQ0FBQztnQkFDM0UsQ0FBQztnQkFDRCxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsYUFBYSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7WUFDeEQsQ0FBQztZQUNELElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1lBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUM7UUFDL0MsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO1lBQ2QsSUFBSSxDQUFDLE1BQU0sR0FBRyxjQUFjLENBQUM7WUFBQyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztZQUNqRCxNQUFNLElBQUksb0JBQW9CLENBQUMscUJBQXFCLENBQUMsQ0FBQztRQUN4RCxDQUFDLENBQUMsQ0FBQztRQUNILE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUNyQixDQUFDO0lBRUQsT0FBTyxDQUFtRCxNQUFTLEVBQUUsTUFBcUQ7UUFDeEgsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLGNBQWMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQztRQUNwSixJQUFJLFFBQXVELENBQUM7UUFDNUQsSUFBSSxDQUFDO1lBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUFDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFBQyxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBQUMsQ0FBQztRQUMxRyxJQUFJLE1BQU0sS0FBSywyQkFBMkIsSUFBSSxNQUFNLEtBQUsseUJBQXlCO1lBQUUsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFJLENBQUM7UUFDOUcsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUM1RSxNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDeEMsSUFBSSxNQUFNLEtBQUsseUJBQXlCO2dCQUFFLElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDO1lBQ3JFLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTLElBQUksQ0FBQyxNQUFNLEtBQUssMkJBQTJCLElBQUksTUFBTSxLQUFLLHlCQUF5QjttQkFDM0csTUFBTSxLQUFLLHlCQUF5QixJQUFLLGNBQWlELENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQztnQkFBRSxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQztZQUN2SSxPQUFPLGNBQWMsQ0FBQztRQUN4QixDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ04sQ0FBQztJQUNELE1BQU0sQ0FBSSxJQUFnQjtRQUN4QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDakMsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFNBQVM7Z0JBQUUsSUFBSSxDQUFDLE1BQU0sR0FBRyxjQUFjLENBQUM7WUFDNUQsTUFBTSxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDM0IsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUNuQixLQUFLLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1RSxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7Q0FDRiJ9
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;
@@ -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.1.0",
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",
package/readme.md CHANGED
@@ -452,8 +452,8 @@ under host grants. 64 symmetric ranges take about 19,600 element bytes.
452
452
  The caller still owns the route to `targetAddress` through that handoff, the
453
453
  workload listener, and every other packet owner on the host. As with leased egress,
454
454
  an ACCEPT here cannot override Docker's independent FORWARD DROP; the
455
- `ManagedDockerForwarding` contribution below derives only leased handoff egress from
456
- 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.
457
457
 
458
458
  Each router generation binds an immutable lease reference, transit source address,
459
459
  leased TCP/UDP source-port ranges, a nonzero conntrack zone and a 16-byte label.
@@ -693,7 +693,8 @@ forwarding that another owner (Docker, a container or VM bridge, a VPN router, a
693
693
  second routing daemon) accepts, and accepting in this table still cannot override
694
694
  another owner's drop. Use it only on a host where this table is the sole forwarding
695
695
  owner; on a Docker host keep it absent and use the Docker contribution below, which
696
- refuses an exclusive barrier. The
696
+ refuses an exclusive barrier. `inspectForwardHooks()`, described below, reports
697
+ the other forward owners of the namespace for that decision. The
697
698
  member does not fence flowtable offload, packet queues, proxies or anything outside
698
699
  the forward hook.
699
700
 
@@ -880,7 +881,7 @@ drainage, conntrack cleanup, elapsed-time expiry, or safe address/port/zone reus
880
881
 
881
882
  ### Docker forwarding contribution
882
883
 
883
- `ManagedDockerForwarding` admits the leased handoff traffic from an exact applied
884
+ `ManagedDockerForwarding` admits exactly the forwarded flows of an exact applied
884
885
  schema-v2 `hostTransit` receipt through Docker's existing IPv4 forwarding path.
885
886
  It reads and verifies that dedicated barrier's complete table and local links,
886
887
  without adopting its ownership. The contribution uses Docker's supported
@@ -916,32 +917,84 @@ and never changes a host forwarding policy. `prepare()` refuses a barrier with
916
917
  `exclusiveForwarding` as `INVALID`: its drop would also deny Docker's own container
917
918
  forwarding.
918
919
 
919
- Each contributed rule binds the handoff and uplink names, current and original
920
- transit source, exact leased TCP/UDP port range, packet direction and connection
921
- state. New TCP flows require SYN with FIN/RST/ACK clear; reverse traffic requires
922
- ESTABLISHED. The exact v2 host barrier independently checks interface indices,
923
- MAC/iflink facts, the default host conntrack zone, protected original/current
924
- destinations and explicit outer SNAT. A complete contribution is bounded to 192
925
- rules and 100,000 generated command bytes.
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.
926
946
 
927
947
  Create the class with `ownerId`, a caller-retained `instanceId` of at least 16
928
948
  identifier characters, and optional `binaryPath`/`networkNamespaceFd`. After
929
949
  `start()`, call `prepare({ schemaVersion: 1, revision, barrier })`, where `barrier`
930
950
  is the exact applied host policy. Persist the complete `{ previous, target }`
931
- transition before `reconcile()`. Keep every affected handoff link down through
932
- mutations; retain those exact links until cleanup finishes. The native owner
933
- verifies this down state before and after its single no-flush restore transaction.
934
- 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.
935
961
 
936
962
  `inspect().present` confirms the contribution and its referenced barrier, not
937
963
  the complete host packet path or durable controller authority. Errors retain
938
964
  failed-owned state and pending intent. `release(applied)` and `close()` delete
939
- 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
940
970
  barrier receipt and caller-authorized replay; old boot receipts cannot authorize
941
971
  native operations. The caller owns restart ordering, disjoint retained leases,
942
972
  exclusive privileged mutation authority and activation. The frontend transaction
943
973
  does not provide compare-and-swap against other privileged processes.
944
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
+
945
998
  ### Native qualification
946
999
 
947
1000
  The separate [Docker fixture](test/native/readme.docker.md) exercises the native
@@ -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
  }
@@ -9,17 +9,36 @@ fn payload(base: u32, offset: u32, length: u32) -> Attr {
9
9
  expr("payload",vec![Attr::u32(1,1),Attr::u32(2,base),Attr::u32(3,offset),Attr::u32(4,length)])
10
10
  }
11
11
 
12
+ fn span(value: &str) -> Result<(u16, u16)> {
13
+ let (first,last)=value.split_once(':').unwrap_or((value,value));
14
+ Ok((first.parse::<u16>().map_err(|_|Error::Invalid)?,last.parse::<u16>().map_err(|_|Error::Invalid)?))
15
+ }
16
+ /// The one key of a rule among alternatives, with its value.
17
+ fn either<'a>(rule: &'a Rule, keys: [&'static str; 2]) -> Result<(usize, &'a str)> {
18
+ let mut found=keys.iter().enumerate().filter_map(|(index,key)|value(rule,key).ok().map(|value|(index,value)));
19
+ let result=found.next().ok_or(Error::Invalid)?;
20
+ if found.next().is_some() {return Err(Error::Invalid);}
21
+ Ok(result)
22
+ }
23
+
12
24
  /// Fixed Linux UAPI / xtables 1.8.10 and 1.8.11 constructors, not a raw rule API.
13
25
  pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
14
26
  let reply=value(rule,"--ctdir")?=="REPLY";
15
27
  let new=value(rule,"--ctstate")?=="NEW";
16
28
  let protocol=match value(rule,"-p")? {"tcp"=>6_u16,"udp"=>17,_=>return Err(Error::Invalid)};
17
- let address=value(rule,"--ctorigsrc")?.parse::<std::net::Ipv4Addr>().map_err(|_|Error::Invalid)?.octets();
18
- let ports=value(rule,"--ctorigsrcport")?;
19
- let (first,last)=ports.split_once(':').unwrap_or((ports,ports));
20
- let first=first.parse::<u16>().map_err(|_|Error::Invalid)?;
21
- let last=last.parse::<u16>().map_err(|_|Error::Invalid)?;
22
- let mut result=vec![payload(1,if reply {16} else {12},4),compare(address.to_vec())];
29
+ // The packet's own source (0) or destination (1) address and port.
30
+ let (side,live)=either(rule,["-s","-d"])?;
31
+ let live=live.strip_suffix("/32").ok_or(Error::Invalid)?.parse::<std::net::Ipv4Addr>().map_err(|_|Error::Invalid)?.octets();
32
+ let (port_side,ports)=either(rule,["--sport","--dport"])?;
33
+ if port_side!=side {return Err(Error::Invalid);}
34
+ let (first,last)=span(ports)?;
35
+ // The originally tracked source (0) or destination (1) address and port.
36
+ let (tracked_side,tracked)=either(rule,["--ctorigsrc","--ctorigdst"])?;
37
+ let tracked=tracked.parse::<std::net::Ipv4Addr>().map_err(|_|Error::Invalid)?.octets();
38
+ let (tracked_port_side,tracked_ports)=either(rule,["--ctorigsrcport","--ctorigdstport"])?;
39
+ if tracked_port_side!=tracked_side {return Err(Error::Invalid);}
40
+ let (tracked_first,tracked_last)=span(tracked_ports)?;
41
+ let mut result=vec![payload(1,if side==0 {12} else {16},4),compare(live.to_vec())];
23
42
  for (key,argument) in [(6,"-i"),(7,"-o")] {
24
43
  result.extend(meta(key,[value(rule,argument)?.as_bytes(),&[0]].concat()));
25
44
  }
@@ -30,7 +49,7 @@ pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
30
49
  Attr::nested(4,vec![Attr::bytes(1,vec![0x17])]),Attr::nested(5,vec![Attr::bytes(1,vec![0])]),
31
50
  ]),compare(vec![2])]);
32
51
  }
33
- result.push(payload(2,if reply {2} else {0},2));
52
+ result.push(payload(2,if side==0 {0} else {2},2));
34
53
  result.push(if first==last {compare(first.to_be_bytes().to_vec())} else {
35
54
  expr("range",vec![Attr::u32(1,1),Attr::u32(2,0),
36
55
  Attr::nested(3,vec![Attr::bytes(1,first.to_be_bytes().to_vec())]),
@@ -38,11 +57,17 @@ pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
38
57
  });
39
58
  // xt_conntrack_mtinfo3 is 164 bytes, XT_ALIGN'ed to 168 on both shipped
40
59
  // 64-bit Linux targets. All fields and padding are initialized explicitly.
60
+ // The original source address/mask are at 0/16, its ports at 138/154; the
61
+ // original destination's at 32/48 and 140/156.
41
62
  let mut conntrack=vec![0;168];
42
- conntrack[..4].copy_from_slice(&address);
43
- conntrack[16..32].fill(0xff); // canonical bare-host nf_inet_addr mask
44
- for (offset,number) in [(136,protocol),(138,first),(146,0x1107),
45
- (148,if reply {0x1000} else {0}),(150,if new {8} else {2}),(154,last)] {
63
+ let address=32*tracked_side;
64
+ conntrack[address..address+4].copy_from_slice(&tracked);
65
+ conntrack[address+16..address+32].fill(0xff); // canonical bare-host nf_inet_addr mask
66
+ // STATE|PROTO|DIRECTION with ORIGSRC|ORIGSRC_PORT or ORIGDST|ORIGDST_PORT.
67
+ let flags=0x1003|if tracked_side==0 {0x0104} else {0x0208};
68
+ let port=2*tracked_side;
69
+ for (offset,number) in [(136,protocol),(138+port,tracked_first),(146,flags),
70
+ (148,if reply {0x1000} else {0}),(150,if new {8} else {2}),(154+port,tracked_last)] {
46
71
  conntrack[offset..offset+2].copy_from_slice(&number.to_ne_bytes());
47
72
  }
48
73
  result.push(expr("match",vec![Attr::string(1,"conntrack"),Attr::u32(2,3),Attr::bytes(3,conntrack)]));