@push.rocks/smartnftables 2.6.0 → 4.0.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 (44) hide show
  1. package/changelog.md +33 -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.js +3 -4
  8. package/dist_ts/classes.managednftables.d.ts +14 -2
  9. package/dist_ts/classes.managednftables.js +48 -8
  10. package/dist_ts/managed.egress.types.d.ts +25 -3
  11. package/dist_ts/managed.types.d.ts +14 -0
  12. package/package.json +7 -7
  13. package/readme.md +262 -64
  14. package/rust/src/docker.policy.rs +2 -1
  15. package/rust/src/egress.compile.rs +153 -76
  16. package/rust/src/egress.host.rs +24 -51
  17. package/rust/src/egress.hostgrant.rs +1 -1
  18. package/rust/src/egress.keys.rs +305 -0
  19. package/rust/src/egress.leased.rs +399 -0
  20. package/rust/src/egress.private.rs +197 -0
  21. package/rust/src/egress.published.rs +370 -0
  22. package/rust/src/egress.router.rs +61 -294
  23. package/rust/src/egress.rs +140 -19
  24. package/rust/src/egress_hostgrant_tests.rs +158 -20
  25. package/rust/src/egress_localport_tests.rs +11 -2
  26. package/rust/src/egress_publishedrange_tests.rs +812 -0
  27. package/rust/src/egress_routerequivalence_tests.rs +448 -0
  28. package/rust/src/egress_tests.rs +289 -230
  29. package/rust/src/egress_workloadgrant_tests.rs +21 -13
  30. package/rust/src/main.rs +57 -3
  31. package/rust/src/owner.rs +69 -14
  32. package/rust/src/owner_host_traffic_tests.rs +4 -0
  33. package/rust/src/owner_identity_tests.rs +166 -80
  34. package/rust/src/owner_publishedrange_traffic_tests.rs +306 -0
  35. package/rust/src/owner_router_traffic_tests.rs +2 -0
  36. package/rust/src/owner_scale_traffic_tests.rs +383 -0
  37. package/rust/src/owner_tests.rs +6 -0
  38. package/rust/src/policy.rs +30 -29
  39. package/rust/src/tests.rs +100 -0
  40. package/ts/00_commitinfo_data.ts +1 -1
  41. package/ts/classes.manageddockerforwarding.ts +2 -3
  42. package/ts/classes.managednftables.ts +46 -8
  43. package/ts/managed.egress.types.ts +25 -3
  44. package/ts/managed.types.ts +17 -0
@@ -1,9 +1,46 @@
1
1
  import * as plugins from './plugins.js';
2
- import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy,
3
- IPreparedManagedNftPolicy, IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftCommands, TManagedNftPolicy } from './managed.types.js';
2
+ import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy,
3
+ IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftCommands, TManagedNftPolicy, IManagedNftCapacity,
4
+ TManagedNftCapacityBound } from './managed.types.js';
4
5
 
6
+ /** `INVALID`: the input is outside the contract. `EXHAUSTED`: the policy exceeds a
7
+ * capacity bound of the atomic replacement budget, and a smaller policy may fit.
8
+ * Both carry the native refusal text in `reason`; `EXHAUSTED` also names its
9
+ * bound in `details`. Every other code, and the facade's own rejections, carry
10
+ * neither. */
5
11
  export class ManagedNftablesError extends Error {
6
- constructor(public readonly code: string) { super(`Managed nftables ${code}.`); }
12
+ constructor(public readonly code: string, public readonly reason: string | null = null,
13
+ public readonly details: Readonly<IManagedNftCapacity> | null = null) {
14
+ super(reason === null ? `Managed nftables ${code}.` : `Managed nftables ${code}: ${reason}`);
15
+ }
16
+ }
17
+
18
+ const capacityBounds: ReadonlySet<string> = new Set<TManagedNftCapacityBound>(['ruleBytes', 'targetBytes', 'operations',
19
+ 'endpoints', 'links', 'rules', 'grants', 'hostGrants', 'workloadGrants', 'publishedPorts', 'localTcpPortOwners',
20
+ 'restoreRules', 'restoreBytes']);
21
+
22
+ function capacity(value: unknown): Readonly<IManagedNftCapacity> | null {
23
+ if (typeof value !== 'object' || value === null || Object.getPrototypeOf(value) !== Object.prototype
24
+ || Object.keys(value).sort().join(',') !== 'actual,bound,limit') return null;
25
+ const { bound, limit, actual } = value as Record<string, unknown>;
26
+ if (typeof bound !== 'string' || !capacityBounds.has(bound) || typeof limit !== 'number' || typeof actual !== 'number'
27
+ || !Number.isSafeInteger(limit) || !Number.isSafeInteger(actual) || limit < 0 || actual <= limit) return null;
28
+ return Object.freeze({ bound: bound as TManagedNftCapacityBound, limit, actual });
29
+ }
30
+
31
+ /** Maps a failed native request. An `INVALID` or `EXHAUSTED` response keeps the
32
+ * bounded native refusal text and, for `EXHAUSTED`, its bound; a refusal outside
33
+ * that shape is `PROTOCOL`. Other responses keep only their code, and a failure
34
+ * without a native code is `UNAVAILABLE`. */
35
+ export function nativeError(error: unknown): ManagedNftablesError {
36
+ if (!(error instanceof plugins.smartrust.RustBridgeRequestError)) return new ManagedNftablesError('UNAVAILABLE');
37
+ const { responseErrorCode: code, message: reason, responseErrorData: data } = error;
38
+ if (typeof code !== 'string' || !/^[A-Z_]{1,64}$/.test(code)) return new ManagedNftablesError('UNAVAILABLE');
39
+ if (code !== 'INVALID' && code !== 'EXHAUSTED') return new ManagedNftablesError(code);
40
+ if (!/^[\x20-\x7e]{1,256}$/.test(reason)) return new ManagedNftablesError('PROTOCOL');
41
+ if (code === 'INVALID') return data === undefined ? new ManagedNftablesError(code, reason) : new ManagedNftablesError('PROTOCOL');
42
+ const details = capacity(data);
43
+ return details ? new ManagedNftablesError(code, reason, details) : new ManagedNftablesError('PROTOCOL');
7
44
  }
8
45
 
9
46
  /** Bounds of the private native IPC. A status carries up to three complete policies (applied,
@@ -240,16 +277,17 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
240
277
  #track<T>(work: Promise<T>, method: keyof TManagedNftCommands<TPolicy>): Promise<T> {
241
278
  const owned = work.catch((error) => {
242
279
  if (this.#state !== 'closing') this.#state = 'failed-owned';
243
- const code = error instanceof plugins.smartrust.RustBridgeRequestError ? error.responseErrorCode : undefined;
244
- // Only a native compiler rejection or explicit INVALID response proves
245
- // effect-free validation. Other native errors can follow lost ownership
280
+ const failure = nativeError(error);
281
+ // Only a native compiler rejection or explicit INVALID or EXHAUSTED
282
+ // response proves effect-free validation: the native owner refuses both
283
+ // before any kernel work. Other native errors can follow lost ownership
246
284
  // or admitted kernel work. Recovery stays available; confidence is lost.
247
285
  const validation = error instanceof plugins.smartrust.RustBridgeRequestError
248
286
  && error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
249
- && (method === 'preparePolicy' || code === 'INVALID');
287
+ && (method === 'preparePolicy' || failure.code === 'INVALID' || failure.code === 'EXHAUSTED');
250
288
  if (!validation && (this.#state !== 'closing'
251
289
  || ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method))) this.#fail();
252
- throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
290
+ throw failure;
253
291
  });
254
292
  this.#work = owned;
255
293
  void owned.then(() => { this.#work = null; }, () => { this.#work = null; });
@@ -60,9 +60,14 @@ export interface IManagedNftEgressGenerationV2 extends IManagedNftHandoffAllocat
60
60
  * `transitSourceAddress:transitPort`, this translates on to the workload. */
61
61
  export interface IManagedNftRouterPublishedPortV2 {
62
62
  protocol: 'tcp' | 'udp';
63
- /** Published handoff port, 1–65535 and unique per protocol across the scope.
64
- * It is the `targetPort` of the matching host-transit publication. */
63
+ /** Published handoff port, 1–65535; published ports never overlap per protocol across the
64
+ * scope. It is the `targetPort` of the matching host-transit publication. */
65
65
  transitPort: number;
66
+ /** Optional last port of a published range `transitPort..transitPortEnd`, greater than
67
+ * `transitPort`. A range keeps every port, so `endpointPort` must equal `transitPort`; like one
68
+ * port it is one interval set element per set, and its translation keeps the port. Absent for
69
+ * one port, which is its only canonical form. */
70
+ transitPortEnd?: number;
66
71
  endpointPort: number;
67
72
  /** Exact current workload endpoint address behind one veth endpoint of this
68
73
  * scope. Never a router-local address, a gateway or a platform endpoint. */
@@ -70,6 +75,12 @@ export interface IManagedNftRouterPublishedPortV2 {
70
75
  /** Exact leased transit source address of one current generation, present on
71
76
  * `handoff`. No wildcard, secondary-address or route inference. */
72
77
  transitSourceAddress: string;
78
+ /** Optional: the workload may open flows from `endpointAddress` and its published endpoint
79
+ * port(s) toward unprotected space, source-translated to `transitSourceAddress` and the
80
+ * published transit port (a range keeps the port). The protected union stays denied, and its
81
+ * endpoint ports may not overlap another publication's on the same address. Absent and
82
+ * `false` are the same canonical policy. */
83
+ symmetric?: boolean;
73
84
  }
74
85
 
75
86
  /** One exact host-origin flow: the node's own host network namespace dials one port of one local
@@ -136,13 +147,24 @@ export interface IManagedNftRouterEgressScopeV2 {
136
147
  /** One inbound uplink publication, compiled inside the same host-transit generation. */
137
148
  export interface IManagedNftPublishedPortV2 {
138
149
  protocol: 'tcp' | 'udp';
139
- /** Published uplink port, 1–65535 and unique per protocol across the scope. */
150
+ /** Published uplink port, 1–65535; published ports never overlap per protocol across the scope. */
140
151
  hostPort: number;
152
+ /** Optional last port of a published range `hostPort..hostPortEnd`, greater than `hostPort`.
153
+ * A range keeps every port, so `targetPort` must equal `hostPort`; like one port it is one
154
+ * interval set element per set, and its translation keeps the port. Absent for one port, which
155
+ * is its only canonical form. */
156
+ hostPortEnd?: number;
141
157
  targetPort: number;
142
158
  /** Exact leased transit source address of one current allocation; it selects the handoff. */
143
159
  targetAddress: string;
144
160
  /** Exact current uplink address that publishes this port. No wildcard or route inference. */
145
161
  hostIp: string;
162
+ /** Optional: flows from `targetAddress` and the published target port(s) may leave through the
163
+ * uplink toward unprotected space, source-translated to `hostIp` and the published uplink port
164
+ * (a range keeps the port). The protected union stays denied, its target ports may not fall in
165
+ * a leased range of `targetAddress` or overlap another publication's. Absent and `false` are
166
+ * the same canonical policy. */
167
+ symmetric?: boolean;
146
168
  }
147
169
 
148
170
  /** Shared-host capture barrier and explicit outer SNAT; Docker forwarding remains a separate owner. */
@@ -93,6 +93,23 @@ export interface IManagedNftOptions {
93
93
 
94
94
  export type TManagedNftPolicy = IManagedNftPolicy | IManagedNftPolicyV2;
95
95
 
96
+ /** Capacity bounds of the atomic replacement budget. The compiled target's
97
+ * `ruleBytes` (100,000), `targetBytes` (212,000) and `operations` (768), the input
98
+ * counts that exist to hold it (`endpoints`, `links`, `rules`, `grants`,
99
+ * `hostGrants`, `workloadGrants`, `publishedPorts`, `localTcpPortOwners`) and the
100
+ * Docker forwarding contribution's `restoreRules` (192) and `restoreBytes` (100,000). */
101
+ export type TManagedNftCapacityBound = 'ruleBytes' | 'targetBytes' | 'operations' | 'endpoints' | 'links' | 'rules'
102
+ | 'grants' | 'hostGrants' | 'workloadGrants' | 'publishedPorts' | 'localTcpPortOwners' | 'restoreRules' | 'restoreBytes';
103
+
104
+ /** The bound an `EXHAUSTED` refusal names. `actual` is always above `limit`. It is
105
+ * exact for input counts and the schema-v1 budget; the other compiled budgets stop
106
+ * at the first message or rule past the limit, so the policy needs at least `actual`. */
107
+ export interface IManagedNftCapacity {
108
+ bound: TManagedNftCapacityBound;
109
+ limit: number;
110
+ actual: number;
111
+ }
112
+
96
113
  export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> = {
97
114
  preparePolicy: { params: TPolicy; result: IPreparedManagedNftPolicy<TPolicy> };
98
115
  openOwner: { params: Pick<IManagedNftOptions, 'ownerId' | 'instanceId' | 'tableName'>; result: IManagedNftStatus<TPolicy> };