@push.rocks/smartnftables 2.1.0 → 2.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.
- package/changelog.md +6 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.managednftables.d.ts +9 -1
- package/dist_ts/classes.managednftables.js +33 -9
- package/dist_ts/managed.types.d.ts +13 -0
- package/package.json +1 -1
- package/readme.md +30 -0
- package/rust/src/main.rs +4 -0
- package/rust/src/owner.detach.rs +1 -1
- package/rust/src/owner.rs +18 -2
- package/rust/src/owner.transitionrelease.rs +107 -0
- package/rust/src/owner_tests.rs +3 -0
- package/rust/src/owner_transitionrelease_tests.rs +327 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +33 -9
- package/ts/managed.types.ts +9 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-15 - 2.2.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add exact transition release with full boot and namespace binding, terminal retry and joined cleanup, including recovery after policy interfaces disappear.
|
|
8
|
+
|
|
3
9
|
## 2026-09-15 - 2.1.0
|
|
4
10
|
|
|
5
11
|
### Features
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "d997150e9cde38e1ba2c93c5b894c367f9b562a31291ee568ccd1e4a54034b61",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "2.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "2.2.0",
|
|
7
|
+
"gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
9
|
+
"builtAt": "2026-09-15T06:33:44.972Z",
|
|
10
10
|
"tsrustVersion": "1.13.0",
|
|
11
11
|
"binary": "smartnftables",
|
|
12
12
|
"target": "linux_amd64_musl"
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "14a625bdb893667872dad6b339aa77f64c6d7e538158bff3e4b92e5684d70e61",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "2.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "2.2.0",
|
|
7
|
+
"gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
9
|
+
"builtAt": "2026-09-15T06:33:49.548Z",
|
|
10
10
|
"tsrustVersion": "1.13.0",
|
|
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: '2.
|
|
6
|
+
version: '2.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=
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy, IManagedNftStatus, TManagedNftPolicy } from './managed.types.js';
|
|
1
|
+
import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy, IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftPolicy } from './managed.types.js';
|
|
2
2
|
export declare class ManagedNftablesError extends Error {
|
|
3
3
|
readonly code: string;
|
|
4
4
|
constructor(code: string);
|
|
@@ -22,6 +22,14 @@ export declare class ManagedNftables<TPolicy extends TManagedNftPolicy = IManage
|
|
|
22
22
|
release(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{
|
|
23
23
|
released: true;
|
|
24
24
|
}>;
|
|
25
|
+
/** Terminal release when an apply acknowledgement was lost. Retain the full
|
|
26
|
+
* original identity and transition first. The native owner accepts only an
|
|
27
|
+
* absent table or the exact previous/target graph; it never recreates policy
|
|
28
|
+
* or requires surviving interfaces. Exact retries and inspection remain
|
|
29
|
+
* available; close() retries admitted cleanup, closeRetaining() only joins. */
|
|
30
|
+
releaseTransition(request: IManagedNftTransitionRelease<TPolicy>): Promise<{
|
|
31
|
+
released: true;
|
|
32
|
+
}>;
|
|
25
33
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
26
34
|
* joins the native process. Retain the full applied value before this call.
|
|
27
35
|
* After any failure close() only terminates; it never deletes the table. */
|
|
@@ -67,6 +67,7 @@ export class ManagedNftables {
|
|
|
67
67
|
#retaining = false;
|
|
68
68
|
#detachExpected = null;
|
|
69
69
|
#detach = null;
|
|
70
|
+
#transitionRelease = null;
|
|
70
71
|
#failure = new AbortController();
|
|
71
72
|
#processLost = false;
|
|
72
73
|
constructor(options) {
|
|
@@ -109,7 +110,7 @@ export class ManagedNftables {
|
|
|
109
110
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
110
111
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
111
112
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
112
|
-
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
|
|
113
|
+
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, releasePolicyTransition: 1, detachPolicy: 1, closeOwner: 1 },
|
|
113
114
|
});
|
|
114
115
|
this.#bridge.on('exit', () => {
|
|
115
116
|
this.#processLost = true;
|
|
@@ -137,11 +138,19 @@ export class ManagedNftables {
|
|
|
137
138
|
release(expected) {
|
|
138
139
|
return this.#invoke('releasePolicy', expected);
|
|
139
140
|
}
|
|
141
|
+
/** Terminal release when an apply acknowledgement was lost. Retain the full
|
|
142
|
+
* original identity and transition first. The native owner accepts only an
|
|
143
|
+
* absent table or the exact previous/target graph; it never recreates policy
|
|
144
|
+
* or requires surviving interfaces. Exact retries and inspection remain
|
|
145
|
+
* available; close() retries admitted cleanup, closeRetaining() only joins. */
|
|
146
|
+
releaseTransition(request) {
|
|
147
|
+
return this.#invoke('releasePolicyTransition', request);
|
|
148
|
+
}
|
|
140
149
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
141
150
|
* joins the native process. Retain the full applied value before this call.
|
|
142
151
|
* After any failure close() only terminates; it never deletes the table. */
|
|
143
152
|
detach(expected) {
|
|
144
|
-
if (this.#closeRetainingSelected || this.#closeMode === 'delete')
|
|
153
|
+
if (this.#closeRetainingSelected || this.#closeMode === 'delete' || this.#transitionRelease)
|
|
145
154
|
return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
146
155
|
this.#retaining = true;
|
|
147
156
|
let captured;
|
|
@@ -196,8 +205,13 @@ export class ManagedNftables {
|
|
|
196
205
|
await this.#work?.catch(() => undefined);
|
|
197
206
|
if (this.#bridge) {
|
|
198
207
|
if (this.#nativeMayOwn && !retaining) {
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
// Even native validation may have rejected before selecting its own
|
|
209
|
+
// terminal intent. Retry this exact request; generic close must not
|
|
210
|
+
// delete an earlier applied graph outside the requested transition.
|
|
211
|
+
const result = this.#transitionRelease
|
|
212
|
+
? await this.#bridge.sendCommand('releasePolicyTransition', this.#transitionRelease.request)
|
|
213
|
+
: await this.#bridge.sendCommand('closeOwner', {});
|
|
214
|
+
if (result.released !== true || this.#transitionRelease && Object.keys(result).length !== 1)
|
|
201
215
|
throw new ManagedNftablesError('PROTOCOL');
|
|
202
216
|
}
|
|
203
217
|
await this.#bridge.terminate({ gracePeriodMs: 1000 });
|
|
@@ -222,18 +236,28 @@ export class ManagedNftables {
|
|
|
222
236
|
catch {
|
|
223
237
|
return Promise.reject(new ManagedNftablesError('INVALID'));
|
|
224
238
|
}
|
|
225
|
-
if (method
|
|
239
|
+
if (this.#transitionRelease && (method !== 'inspectPolicy' && method !== 'releasePolicyTransition'
|
|
240
|
+
|| method === 'releasePolicyTransition' && !plugins.util.isDeepStrictEqual(this.#transitionRelease.request, captured))) {
|
|
241
|
+
return Promise.reject(new ManagedNftablesError('CONFLICT'));
|
|
242
|
+
}
|
|
243
|
+
if (method === 'releasePolicyTransition')
|
|
244
|
+
this.#transitionRelease = { request: captured };
|
|
245
|
+
if (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition')
|
|
226
246
|
this.#nativeMayOwn = true;
|
|
227
247
|
return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
|
|
228
248
|
const capturedResult = snapshot(result);
|
|
229
|
-
if (method === 'releasePolicy')
|
|
249
|
+
if (method === 'releasePolicy' || method === 'releasePolicyTransition') {
|
|
250
|
+
if (method === 'releasePolicyTransition' && (capturedResult?.released !== true
|
|
251
|
+
|| Object.keys(capturedResult).length !== 1))
|
|
252
|
+
throw new ManagedNftablesError('PROTOCOL');
|
|
230
253
|
this.#nativeMayOwn = false;
|
|
254
|
+
}
|
|
231
255
|
if (method === 'inspectPolicy') {
|
|
232
256
|
const status = capturedResult;
|
|
233
257
|
if (status.state === 'failed-owned' || status.error !== null)
|
|
234
258
|
this.#fail();
|
|
235
259
|
}
|
|
236
|
-
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy'
|
|
260
|
+
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
|
|
237
261
|
|| method === 'inspectPolicy' && capturedResult.error === null
|
|
238
262
|
&& capturedResult.state !== 'failed-owned'))
|
|
239
263
|
this.#state = 'ready';
|
|
@@ -252,7 +276,7 @@ export class ManagedNftables {
|
|
|
252
276
|
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
253
277
|
&& (method === 'preparePolicy' || code === 'INVALID');
|
|
254
278
|
if (!validation && (this.#state !== 'closing'
|
|
255
|
-
|| ['reconcilePolicy', 'releasePolicy', 'detachPolicy'].includes(method)))
|
|
279
|
+
|| ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method)))
|
|
256
280
|
this.#fail();
|
|
257
281
|
throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
|
|
258
282
|
});
|
|
@@ -261,4 +285,4 @@ export class ManagedNftables {
|
|
|
261
285
|
return owned;
|
|
262
286
|
}
|
|
263
287
|
}
|
|
264
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
|
|
288
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
|
|
@@ -51,6 +51,13 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
|
|
|
51
51
|
previous: IAppliedManagedNftPolicy<TPolicy> | null;
|
|
52
52
|
target: IPreparedManagedNftPolicy<TPolicy>;
|
|
53
53
|
}
|
|
54
|
+
/** Persist the original boot/namespace identity and full transition before cleanup.
|
|
55
|
+
* This releases only its exact owned table; it does not prove packet drain or
|
|
56
|
+
* authorize address reuse. Surviving policy interfaces are not required. */
|
|
57
|
+
export interface IManagedNftTransitionRelease<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
58
|
+
identity: IManagedNftIdentity;
|
|
59
|
+
transition: IManagedNftTransition<TPolicy>;
|
|
60
|
+
}
|
|
54
61
|
export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
55
62
|
identity: IManagedNftIdentity;
|
|
56
63
|
state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
|
|
@@ -96,6 +103,12 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
|
|
|
96
103
|
released: true;
|
|
97
104
|
};
|
|
98
105
|
};
|
|
106
|
+
releasePolicyTransition: {
|
|
107
|
+
params: IManagedNftTransitionRelease<TPolicy>;
|
|
108
|
+
result: {
|
|
109
|
+
released: true;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
99
112
|
detachPolicy: {
|
|
100
113
|
params: IAppliedManagedNftPolicy<TPolicy>;
|
|
101
114
|
result: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartnftables",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.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
|
@@ -114,6 +114,7 @@ grants remain outstanding.
|
|
|
114
114
|
| `reconcile({ previous, target })` | Creates or replaces the entire policy in one kernel transaction. `previous` is null initially, otherwise the full prior applied result. |
|
|
115
115
|
| `inspect()` | Reads the current owned graph and reports `enforced`, pending intent and a bounded error code. |
|
|
116
116
|
| `release(applied)` | Verifies the exact retained graph and confirms deletion; repeats are idempotent. |
|
|
117
|
+
| `releaseTransition({ identity, transition })` | Selects terminal cleanup using the original full identity and transition when the applied result is unavailable. Accepts absence or deletes the exact previous/target graph without requiring surviving interfaces. |
|
|
117
118
|
| `detach(applied)` | Selects terminal retention, verifies the same graph with PERSIST-only flags after dropping socket ownership, then joins the native process. Returns `{ detached: true }`; retain the full applied result. |
|
|
118
119
|
| `closeRetaining()` | Stops command admission, joins admitted operations and terminates the native process without requesting deletion or confirming retention. Use when the exact applied result is unavailable. |
|
|
119
120
|
| `close()` | Stops command admission, joins admitted policy operations, confirms owned-table deletion unless retention was selected, then joins the native process. Failure retains the owner for explicit recovery. |
|
|
@@ -202,6 +203,35 @@ may have left no graph; a lost reply may have left the original or target graph.
|
|
|
202
203
|
Keep the complete original transition durably and recover it through a fresh owner
|
|
203
204
|
with identical options. Never fabricate an applied receipt to request detachment.
|
|
204
205
|
|
|
206
|
+
When cleanup is required instead of recovering enforcement, persist a
|
|
207
|
+
`IManagedNftTransitionRelease` containing the identity returned by the original
|
|
208
|
+
`start()` and the full original transition. After fencing workload packet
|
|
209
|
+
admission and joining the old owner, start a fresh owner with the same options:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Both values come from the caller's durable journal, before the lost apply ACK.
|
|
213
|
+
const cleanup = { identity: originalIdentity, transition: originalTransition };
|
|
214
|
+
await recoveryOwner.releaseTransition(cleanup);
|
|
215
|
+
await recoveryOwner.close();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`releaseTransition()` validates the original boot and namespace, canonical target,
|
|
219
|
+
complete previous result when present, table identity and entire rule graph. It
|
|
220
|
+
accepts an absent table, the previous graph, or the target graph; it never creates
|
|
221
|
+
or replaces policy. Deleted or changed interfaces do not prevent this cleanup.
|
|
222
|
+
Orphan adoption checks the graph before and after claiming OWNER, binds the prior
|
|
223
|
+
or already-observed handle, and rejects another live owner. Deletion uses the exact
|
|
224
|
+
handle and a generation-fenced batch, followed by an absence check.
|
|
225
|
+
|
|
226
|
+
Once admitted, only that captured cleanup body may be retried. Other policy writes,
|
|
227
|
+
preparation and detachment reject; inspection never claims enforcement. An ordinary
|
|
228
|
+
`close()` retries the selected cleanup before joining the child. `closeRetaining()`
|
|
229
|
+
only joins, allowing a fresh process to retry the durably retained cleanup after
|
|
230
|
+
an ambiguous acknowledgement. Local inert-input or busy rejection does not select
|
|
231
|
+
cleanup. A successful release acknowledges that table operation at observation
|
|
232
|
+
time, including an exact retry after a lost delete ACK. It does not prove packet
|
|
233
|
+
drain, clear conntrack, release allocations, or cross a changed boot/namespace.
|
|
234
|
+
|
|
205
235
|
After `closeRetaining()`, all policy commands, including `detach()`, reject.
|
|
206
236
|
Concurrent cleanup calls share the same join. A failed join keeps the native
|
|
207
237
|
owner and retention choice for retry through `closeRetaining()` or `close()`.
|
package/rust/src/main.rs
CHANGED
|
@@ -91,6 +91,10 @@ fn dispatch(owner: &mut Option<owner::Owner>, request: Request) -> Result<serde_
|
|
|
91
91
|
.release(decode(request.params)?)?;
|
|
92
92
|
Ok(serde_json::json!({"released":true}))
|
|
93
93
|
}
|
|
94
|
+
"releasePolicyTransition" => {
|
|
95
|
+
owner.as_mut().ok_or(Error::Conflict)?.release_transition(decode(request.params)?)?;
|
|
96
|
+
Ok(serde_json::json!({"released":true}))
|
|
97
|
+
}
|
|
94
98
|
"detachPolicy" => {
|
|
95
99
|
owner.as_mut().ok_or(Error::Conflict)?.detach(decode(request.params)?)?;
|
|
96
100
|
Ok(serde_json::json!({"detached":true}))
|
package/rust/src/owner.detach.rs
CHANGED
|
@@ -5,7 +5,7 @@ impl Owner {
|
|
|
5
5
|
/// orphan after an ACK was lost, including through a fresh native process.
|
|
6
6
|
pub fn detach(&mut self, expected: Applied) -> Result<()> {
|
|
7
7
|
self.validate_receipt(&expected)?;
|
|
8
|
-
if self.released || self.pending.is_some() {
|
|
8
|
+
if self.released || self.pending.is_some() || self.transition_release.is_some() {
|
|
9
9
|
return Err(Error::Conflict);
|
|
10
10
|
}
|
|
11
11
|
if let Some(retention) = &self.retention {
|
package/rust/src/owner.rs
CHANGED
|
@@ -44,6 +44,12 @@ pub struct Transition {
|
|
|
44
44
|
pub previous: Option<Applied>,
|
|
45
45
|
pub target: Prepared,
|
|
46
46
|
}
|
|
47
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
48
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
49
|
+
pub struct TransitionRelease {
|
|
50
|
+
pub identity: Identity,
|
|
51
|
+
pub transition: Transition,
|
|
52
|
+
}
|
|
47
53
|
#[derive(Clone, Debug, Serialize)]
|
|
48
54
|
#[serde(rename_all = "camelCase")]
|
|
49
55
|
pub struct Status {
|
|
@@ -69,6 +75,7 @@ pub struct Owner {
|
|
|
69
75
|
error: Option<Error>,
|
|
70
76
|
released: bool,
|
|
71
77
|
retention: Option<Retention>,
|
|
78
|
+
transition_release: Option<TransitionRelease>,
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
struct Retention {
|
|
@@ -78,6 +85,8 @@ struct Retention {
|
|
|
78
85
|
|
|
79
86
|
#[path = "owner.detach.rs"]
|
|
80
87
|
mod detach;
|
|
88
|
+
#[path = "owner.transitionrelease.rs"]
|
|
89
|
+
mod transitionrelease;
|
|
81
90
|
|
|
82
91
|
pub(crate) fn context() -> Result<(String, String, String)> {
|
|
83
92
|
let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
|
@@ -164,6 +173,7 @@ impl Owner {
|
|
|
164
173
|
error: None,
|
|
165
174
|
released: false,
|
|
166
175
|
retention: None,
|
|
176
|
+
transition_release: None,
|
|
167
177
|
})
|
|
168
178
|
}
|
|
169
179
|
fn socket(&mut self) -> Result<&mut Socket> {
|
|
@@ -360,7 +370,7 @@ impl Owner {
|
|
|
360
370
|
})
|
|
361
371
|
}
|
|
362
372
|
pub fn reconcile(&mut self, transition: Transition) -> Result<Applied> {
|
|
363
|
-
if self.released || self.retention.is_some() {
|
|
373
|
+
if self.released || self.retention.is_some() || self.transition_release.is_some() {
|
|
364
374
|
return Err(Error::Conflict);
|
|
365
375
|
}
|
|
366
376
|
transition.target.validate()?;
|
|
@@ -463,6 +473,9 @@ impl Owner {
|
|
|
463
473
|
self.applied(&graph, transition.target.clone())
|
|
464
474
|
}
|
|
465
475
|
pub fn inspect(&mut self) -> Status {
|
|
476
|
+
if self.transition_release.is_some() {
|
|
477
|
+
return self.inspect_transition_release();
|
|
478
|
+
}
|
|
466
479
|
if self.retention.is_some() {
|
|
467
480
|
return self.inspect_retained();
|
|
468
481
|
}
|
|
@@ -503,7 +516,7 @@ impl Owner {
|
|
|
503
516
|
}
|
|
504
517
|
}
|
|
505
518
|
pub fn release(&mut self, expected: Applied) -> Result<()> {
|
|
506
|
-
if self.retention.is_some() {
|
|
519
|
+
if self.retention.is_some() || self.transition_release.is_some() {
|
|
507
520
|
return Err(Error::Conflict);
|
|
508
521
|
}
|
|
509
522
|
self.validate_receipt(&expected)?;
|
|
@@ -561,6 +574,9 @@ impl Owner {
|
|
|
561
574
|
}
|
|
562
575
|
}
|
|
563
576
|
pub fn close(&mut self) -> Result<()> {
|
|
577
|
+
if let Some(request) = self.transition_release.clone() {
|
|
578
|
+
return self.release_transition(request);
|
|
579
|
+
}
|
|
564
580
|
if self.retention.is_some() {
|
|
565
581
|
return Err(Error::Conflict);
|
|
566
582
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Owner {
|
|
4
|
+
/// Terminal cleanup of an exact caller-retained transition. Unlike reconcile,
|
|
5
|
+
/// this never creates/replaces policy and does not require surviving interfaces.
|
|
6
|
+
pub fn release_transition(&mut self, request: TransitionRelease) -> Result<()> {
|
|
7
|
+
if self.retention.is_some() || request.identity != self.identity {
|
|
8
|
+
return Err(Error::Conflict);
|
|
9
|
+
}
|
|
10
|
+
request.transition.target.validate()?;
|
|
11
|
+
if let Some(previous) = &request.transition.previous {
|
|
12
|
+
self.validate_receipt(previous)?;
|
|
13
|
+
if previous.prepared.policy.kind() != request.transition.target.policy.kind()
|
|
14
|
+
|| previous.prepared.policy.revision()
|
|
15
|
+
>= request.transition.target.policy.revision()
|
|
16
|
+
{
|
|
17
|
+
return Err(Error::Conflict);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if let Some(selected) = &self.transition_release {
|
|
21
|
+
if selected != &request {
|
|
22
|
+
return Err(Error::Conflict);
|
|
23
|
+
}
|
|
24
|
+
if self.released {
|
|
25
|
+
return Ok(());
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
if self.released
|
|
29
|
+
|| self
|
|
30
|
+
.pending
|
|
31
|
+
.as_ref()
|
|
32
|
+
.is_some_and(|pending| pending != &request.transition)
|
|
33
|
+
|| self.applied.as_ref().is_some_and(|applied| {
|
|
34
|
+
Some(applied) != request.transition.previous.as_ref()
|
|
35
|
+
&& applied.prepared != request.transition.target
|
|
36
|
+
})
|
|
37
|
+
{
|
|
38
|
+
return Err(Error::Conflict);
|
|
39
|
+
}
|
|
40
|
+
// Capture terminal intent before any query, ownership claim or delete.
|
|
41
|
+
self.transition_release = Some(request.clone());
|
|
42
|
+
}
|
|
43
|
+
let result = self.release_transition_inner(&request.transition);
|
|
44
|
+
match result {
|
|
45
|
+
Ok(()) => {
|
|
46
|
+
self.released = true;
|
|
47
|
+
self.pending = None;
|
|
48
|
+
self.error = None;
|
|
49
|
+
self.socket = None;
|
|
50
|
+
Ok(())
|
|
51
|
+
}
|
|
52
|
+
Err(error) => {
|
|
53
|
+
self.error = Some(error);
|
|
54
|
+
Err(error)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn release_transition_inner(&mut self, transition: &Transition) -> Result<()> {
|
|
60
|
+
let Some(graph) = self.graph()? else {
|
|
61
|
+
return Ok(());
|
|
62
|
+
};
|
|
63
|
+
// Also bind any handle this process already observed before an ambiguous
|
|
64
|
+
// delete. A replacement under the same name must never inherit authority.
|
|
65
|
+
if let Some(applied) = &self.applied {
|
|
66
|
+
self.binding(&graph, Some(applied))?;
|
|
67
|
+
}
|
|
68
|
+
let graph = self.adopt(graph, transition)?;
|
|
69
|
+
let prepared = if self.matches(&graph, &transition.target)? {
|
|
70
|
+
transition.target.clone()
|
|
71
|
+
} else {
|
|
72
|
+
transition
|
|
73
|
+
.previous
|
|
74
|
+
.as_ref()
|
|
75
|
+
.ok_or(Error::Conflict)?
|
|
76
|
+
.prepared
|
|
77
|
+
.clone()
|
|
78
|
+
};
|
|
79
|
+
self.applied = Some(self.applied(&graph, prepared)?);
|
|
80
|
+
self.socket()?.batch(
|
|
81
|
+
graph.generation,
|
|
82
|
+
vec![(2, 0, vec![Attr::u64(4, wire::handle(&graph.table, 4)?)])],
|
|
83
|
+
)?;
|
|
84
|
+
if self.graph()?.is_some() {
|
|
85
|
+
return Err(Error::Conflict);
|
|
86
|
+
}
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
pub(super) fn inspect_transition_release(&self) -> Status {
|
|
91
|
+
// Cleanup intent cannot grant enforcement, including after a failed
|
|
92
|
+
// attempt that acquired OWNER on an otherwise valid persistent graph.
|
|
93
|
+
Status {
|
|
94
|
+
identity: self.identity.clone(),
|
|
95
|
+
state: if self.released {
|
|
96
|
+
"released"
|
|
97
|
+
} else {
|
|
98
|
+
"failed-owned"
|
|
99
|
+
}
|
|
100
|
+
.into(),
|
|
101
|
+
enforced: false,
|
|
102
|
+
applied: self.applied.clone(),
|
|
103
|
+
pending: self.pending.clone(),
|
|
104
|
+
error: self.error.map(|error| error.code().into()),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
package/rust/src/owner_tests.rs
CHANGED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
use super::namespace_tests::NativeOwner;
|
|
2
|
+
use super::*;
|
|
3
|
+
use serde_json::json;
|
|
4
|
+
use std::io::Write;
|
|
5
|
+
use std::time::{Duration, Instant};
|
|
6
|
+
|
|
7
|
+
fn target(revision: u64) -> Prepared {
|
|
8
|
+
let mut policy = crate::egress::tests::pool_guard();
|
|
9
|
+
policy["revision"] = json!(revision);
|
|
10
|
+
serde_json::from_value::<crate::egress::Policy>(policy)
|
|
11
|
+
.unwrap()
|
|
12
|
+
.prepare()
|
|
13
|
+
.unwrap()
|
|
14
|
+
.into()
|
|
15
|
+
}
|
|
16
|
+
fn request(owner: &Owner, transition: Transition) -> TransitionRelease {
|
|
17
|
+
TransitionRelease {
|
|
18
|
+
identity: owner.identity.clone(),
|
|
19
|
+
transition,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn transition_release_validates_closed_identity_and_body_before_admission() {
|
|
25
|
+
let mut owner = Owner::new(options("release_input")).unwrap();
|
|
26
|
+
let command = request(
|
|
27
|
+
&owner,
|
|
28
|
+
Transition {
|
|
29
|
+
previous: None,
|
|
30
|
+
target: target(1),
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
for field in ["bootId", "namespaceDevice", "namespaceInode", "instanceId"] {
|
|
34
|
+
let mut invalid = serde_json::to_value(&command).unwrap();
|
|
35
|
+
invalid["identity"][field] = json!("invalid");
|
|
36
|
+
assert_eq!(
|
|
37
|
+
owner.release_transition(serde_json::from_value(invalid).unwrap()),
|
|
38
|
+
Err(Error::Conflict)
|
|
39
|
+
);
|
|
40
|
+
assert!(owner.transition_release.is_none());
|
|
41
|
+
}
|
|
42
|
+
let mut invalid = command.clone();
|
|
43
|
+
invalid.transition.target.digest = format!("sha256:{}", "0".repeat(64));
|
|
44
|
+
assert!(owner.release_transition(invalid).is_err());
|
|
45
|
+
assert!(owner.transition_release.is_none());
|
|
46
|
+
for pointer in ["", "/identity", "/transition", "/transition/target"] {
|
|
47
|
+
let mut invalid = serde_json::to_value(&command).unwrap();
|
|
48
|
+
invalid.pointer_mut(pointer).unwrap()["extra"] = json!(true);
|
|
49
|
+
assert!(serde_json::from_value::<TransitionRelease>(invalid).is_err());
|
|
50
|
+
}
|
|
51
|
+
let mut invalid = serde_json::to_value(&command).unwrap();
|
|
52
|
+
invalid["transition"]
|
|
53
|
+
.as_object_mut()
|
|
54
|
+
.unwrap()
|
|
55
|
+
.remove("previous");
|
|
56
|
+
assert!(serde_json::from_value::<TransitionRelease>(invalid).is_err());
|
|
57
|
+
assert!(owner.socket.is_none());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
62
|
+
fn release_original_transition_after_interfaces_disappear_never_recreates_policy() {
|
|
63
|
+
isolated();
|
|
64
|
+
for replacement in [false, true] {
|
|
65
|
+
for delivered in [false, true] {
|
|
66
|
+
let endpoint = endpoint("a", 72);
|
|
67
|
+
let policy = crate::policy::Policy {
|
|
68
|
+
schema_version: 1,
|
|
69
|
+
revision: 1,
|
|
70
|
+
endpoints: vec![endpoint],
|
|
71
|
+
rules: vec![],
|
|
72
|
+
};
|
|
73
|
+
let mut owner = Owner::new(options("release_links")).unwrap();
|
|
74
|
+
let previous = replacement.then(|| {
|
|
75
|
+
owner
|
|
76
|
+
.reconcile(Transition {
|
|
77
|
+
previous: None,
|
|
78
|
+
target: policy.clone().prepare().unwrap().into(),
|
|
79
|
+
})
|
|
80
|
+
.unwrap()
|
|
81
|
+
});
|
|
82
|
+
let mut next = policy;
|
|
83
|
+
if replacement {
|
|
84
|
+
next.revision = 2;
|
|
85
|
+
}
|
|
86
|
+
let command = request(
|
|
87
|
+
&owner,
|
|
88
|
+
Transition {
|
|
89
|
+
previous,
|
|
90
|
+
target: next.prepare().unwrap().into(),
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
if delivered {
|
|
94
|
+
owner.reconcile(command.transition.clone()).unwrap();
|
|
95
|
+
}
|
|
96
|
+
// Only the original command crosses this process boundary.
|
|
97
|
+
drop(owner);
|
|
98
|
+
ip(&["link", "del", "worka"]);
|
|
99
|
+
ip(&["netns", "del", "a"]);
|
|
100
|
+
let mut fresh = Owner::new(options("release_links")).unwrap();
|
|
101
|
+
assert!(fresh.reconcile(command.transition.clone()).is_err());
|
|
102
|
+
if replacement && delivered {
|
|
103
|
+
// Model a retained native pending command after an ambiguous batch.
|
|
104
|
+
fresh.pending = Some(command.transition.clone());
|
|
105
|
+
}
|
|
106
|
+
fresh.release_transition(command.clone()).unwrap();
|
|
107
|
+
let status = fresh.inspect();
|
|
108
|
+
assert_eq!(status.state, "released");
|
|
109
|
+
assert!(!status.enforced);
|
|
110
|
+
assert!(fresh.graph().unwrap().is_none());
|
|
111
|
+
fresh.release_transition(command.clone()).unwrap();
|
|
112
|
+
assert_eq!(
|
|
113
|
+
fresh.reconcile(command.transition.clone()),
|
|
114
|
+
Err(Error::Conflict)
|
|
115
|
+
);
|
|
116
|
+
let mut different = command;
|
|
117
|
+
different.transition.target = target(3);
|
|
118
|
+
assert_eq!(fresh.release_transition(different), Err(Error::Conflict));
|
|
119
|
+
fresh.close().unwrap();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[test]
|
|
125
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
126
|
+
fn release_transition_rejects_foreign_owner_and_keeps_exact_terminal_choice() {
|
|
127
|
+
isolated();
|
|
128
|
+
let mut active = Owner::new(options("release_foreign")).unwrap();
|
|
129
|
+
let command = request(
|
|
130
|
+
&active,
|
|
131
|
+
Transition {
|
|
132
|
+
previous: None,
|
|
133
|
+
target: target(1),
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
let applied = active.reconcile(command.transition.clone()).unwrap();
|
|
137
|
+
let mut fresh = Owner::new(options("release_foreign")).unwrap();
|
|
138
|
+
assert_eq!(
|
|
139
|
+
fresh.release_transition(command.clone()),
|
|
140
|
+
Err(Error::ForeignOwner)
|
|
141
|
+
);
|
|
142
|
+
assert_eq!(fresh.inspect().state, "failed-owned");
|
|
143
|
+
assert!(!fresh.inspect().enforced);
|
|
144
|
+
assert_eq!(
|
|
145
|
+
fresh.reconcile(command.transition.clone()),
|
|
146
|
+
Err(Error::Conflict)
|
|
147
|
+
);
|
|
148
|
+
assert_eq!(fresh.detach(applied.clone()), Err(Error::Conflict));
|
|
149
|
+
assert_eq!(fresh.release(applied), Err(Error::Conflict));
|
|
150
|
+
assert!(active.inspect().enforced);
|
|
151
|
+
drop(active);
|
|
152
|
+
// close may retry only the already-selected exact release operation.
|
|
153
|
+
fresh.close().unwrap();
|
|
154
|
+
assert!(fresh.graph().unwrap().is_none());
|
|
155
|
+
fresh.release_transition(command).unwrap();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
160
|
+
fn release_transition_binds_previous_handle_and_complete_graph() {
|
|
161
|
+
isolated();
|
|
162
|
+
for fault in ["handle", "body", "pending"] {
|
|
163
|
+
let mut active = Owner::new(options("release_binding")).unwrap();
|
|
164
|
+
let previous = active
|
|
165
|
+
.reconcile(Transition {
|
|
166
|
+
previous: None,
|
|
167
|
+
target: target(1),
|
|
168
|
+
})
|
|
169
|
+
.unwrap();
|
|
170
|
+
let command = request(
|
|
171
|
+
&active,
|
|
172
|
+
Transition {
|
|
173
|
+
previous: Some(previous.clone()),
|
|
174
|
+
target: target(2),
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
drop(active);
|
|
178
|
+
let mut fresh = Owner::new(options("release_binding")).unwrap();
|
|
179
|
+
let mut invalid = command.clone();
|
|
180
|
+
if fault == "handle" {
|
|
181
|
+
invalid
|
|
182
|
+
.transition
|
|
183
|
+
.previous
|
|
184
|
+
.as_mut()
|
|
185
|
+
.unwrap()
|
|
186
|
+
.receipt
|
|
187
|
+
.table_handle = "18446744073709551615".into();
|
|
188
|
+
} else if fault == "pending" {
|
|
189
|
+
fresh.pending = Some(Transition {
|
|
190
|
+
previous: Some(previous.clone()),
|
|
191
|
+
target: target(3),
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
let mut foreign = Socket::open().unwrap();
|
|
195
|
+
let generation = foreign.generation().unwrap();
|
|
196
|
+
foreign
|
|
197
|
+
.batch(
|
|
198
|
+
generation,
|
|
199
|
+
vec![(
|
|
200
|
+
3,
|
|
201
|
+
0x600,
|
|
202
|
+
vec![
|
|
203
|
+
Attr::string(1, "snft_release_binding"),
|
|
204
|
+
Attr::string(3, "foreign"),
|
|
205
|
+
],
|
|
206
|
+
)],
|
|
207
|
+
)
|
|
208
|
+
.unwrap();
|
|
209
|
+
}
|
|
210
|
+
assert_eq!(fresh.release_transition(invalid), Err(Error::Conflict));
|
|
211
|
+
assert!(fresh.graph().unwrap().is_some());
|
|
212
|
+
assert!(!fresh.inspect().enforced);
|
|
213
|
+
drop(fresh);
|
|
214
|
+
// Disposable fixture cleanup never enters the production API.
|
|
215
|
+
let mut cleanup = Socket::open().unwrap();
|
|
216
|
+
let generation = cleanup.generation().unwrap();
|
|
217
|
+
cleanup
|
|
218
|
+
.batch(
|
|
219
|
+
generation,
|
|
220
|
+
vec![(2, 0, vec![Attr::string(1, "snft_release_binding")])],
|
|
221
|
+
)
|
|
222
|
+
.unwrap();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
228
|
+
fn release_transition_retry_never_deletes_a_replacement_for_its_observed_handle() {
|
|
229
|
+
isolated();
|
|
230
|
+
let mut owner = Owner::new(options("release_replaced")).unwrap();
|
|
231
|
+
let command = request(
|
|
232
|
+
&owner,
|
|
233
|
+
Transition {
|
|
234
|
+
previous: None,
|
|
235
|
+
target: target(1),
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
let first = owner.reconcile(command.transition.clone()).unwrap();
|
|
239
|
+
// Stop at the exact production boundary after deletion, before its final
|
|
240
|
+
// observation. Retained state must still bind the handle already acquired.
|
|
241
|
+
owner.transition_release = Some(command.clone());
|
|
242
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
243
|
+
owner
|
|
244
|
+
.socket()
|
|
245
|
+
.unwrap()
|
|
246
|
+
.batch(
|
|
247
|
+
graph.generation,
|
|
248
|
+
vec![(
|
|
249
|
+
2,
|
|
250
|
+
0,
|
|
251
|
+
vec![Attr::u64(4, wire::handle(&graph.table, 4).unwrap())],
|
|
252
|
+
)],
|
|
253
|
+
)
|
|
254
|
+
.unwrap();
|
|
255
|
+
let mut replacement = Owner::new(options("release_replaced")).unwrap();
|
|
256
|
+
let second = replacement.reconcile(command.transition.clone()).unwrap();
|
|
257
|
+
assert_ne!(first.receipt.table_handle, second.receipt.table_handle);
|
|
258
|
+
drop(replacement);
|
|
259
|
+
assert_eq!(owner.release_transition(command), Err(Error::Conflict));
|
|
260
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
261
|
+
assert_eq!(
|
|
262
|
+
wire::handle(&graph.table, 4).unwrap().to_string(),
|
|
263
|
+
second.receipt.table_handle
|
|
264
|
+
);
|
|
265
|
+
assert_eq!(owner.applied, Some(first));
|
|
266
|
+
assert!(!owner.inspect().enforced);
|
|
267
|
+
drop(owner);
|
|
268
|
+
Owner::new(options("release_replaced"))
|
|
269
|
+
.unwrap()
|
|
270
|
+
.release(second)
|
|
271
|
+
.unwrap();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
276
|
+
fn release_transition_recovers_lost_delete_ack_across_actual_native_processes() {
|
|
277
|
+
isolated();
|
|
278
|
+
let namespace = std::fs::File::open("/proc/thread-self/ns/net").unwrap();
|
|
279
|
+
let mut native = NativeOwner::start(&namespace);
|
|
280
|
+
let status = native.call(
|
|
281
|
+
"openOwner",
|
|
282
|
+
serde_json::to_value(options("release_ack")).unwrap(),
|
|
283
|
+
);
|
|
284
|
+
let command = TransitionRelease {
|
|
285
|
+
identity: serde_json::from_value(status["identity"].clone()).unwrap(),
|
|
286
|
+
transition: Transition {
|
|
287
|
+
previous: None,
|
|
288
|
+
target: target(1),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
native.call(
|
|
292
|
+
"reconcilePolicy",
|
|
293
|
+
serde_json::to_value(&command.transition).unwrap(),
|
|
294
|
+
);
|
|
295
|
+
writeln!(
|
|
296
|
+
native.child.stdin.as_mut().unwrap(),
|
|
297
|
+
"{}",
|
|
298
|
+
json!({
|
|
299
|
+
"id":"lost-release", "method":"releasePolicyTransition", "params":command,
|
|
300
|
+
})
|
|
301
|
+
)
|
|
302
|
+
.unwrap();
|
|
303
|
+
let mut observer = Owner::new(options("release_ack")).unwrap();
|
|
304
|
+
let until = Instant::now() + Duration::from_secs(3);
|
|
305
|
+
while observer.graph().unwrap().is_some() {
|
|
306
|
+
assert!(Instant::now() < until);
|
|
307
|
+
std::thread::sleep(Duration::from_millis(5));
|
|
308
|
+
}
|
|
309
|
+
// Never read or retain the release response.
|
|
310
|
+
drop(native);
|
|
311
|
+
let mut fresh = NativeOwner::start(&namespace);
|
|
312
|
+
fresh.call(
|
|
313
|
+
"openOwner",
|
|
314
|
+
serde_json::to_value(options("release_ack")).unwrap(),
|
|
315
|
+
);
|
|
316
|
+
assert_eq!(
|
|
317
|
+
fresh.call(
|
|
318
|
+
"releasePolicyTransition",
|
|
319
|
+
serde_json::to_value(&command).unwrap()
|
|
320
|
+
),
|
|
321
|
+
json!({"released":true})
|
|
322
|
+
);
|
|
323
|
+
let status = fresh.call("inspectPolicy", json!({}));
|
|
324
|
+
assert_eq!(status["state"], "released");
|
|
325
|
+
assert_eq!(status["enforced"], false);
|
|
326
|
+
assert!(observer.graph().unwrap().is_none());
|
|
327
|
+
}
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy,
|
|
3
|
-
IPreparedManagedNftPolicy, IManagedNftStatus, TManagedNftCommands, TManagedNftPolicy } from './managed.types.js';
|
|
3
|
+
IPreparedManagedNftPolicy, IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftCommands, TManagedNftPolicy } from './managed.types.js';
|
|
4
4
|
|
|
5
5
|
export class ManagedNftablesError extends Error {
|
|
6
6
|
constructor(public readonly code: string) { super(`Managed nftables ${code}.`); }
|
|
@@ -55,6 +55,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
55
55
|
#retaining = false;
|
|
56
56
|
#detachExpected: IAppliedManagedNftPolicy<TPolicy> | null = null;
|
|
57
57
|
#detach: Promise<{ detached: true }> | null = null;
|
|
58
|
+
#transitionRelease: { request: IManagedNftTransitionRelease<TPolicy> } | null = null;
|
|
58
59
|
readonly #failure = new AbortController();
|
|
59
60
|
#processLost = false;
|
|
60
61
|
|
|
@@ -97,7 +98,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
97
98
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
98
99
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
99
100
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
100
|
-
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
|
|
101
|
+
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, releasePolicyTransition: 1, detachPolicy: 1, closeOwner: 1 },
|
|
101
102
|
});
|
|
102
103
|
this.#bridge.on('exit', () => {
|
|
103
104
|
this.#processLost = true;
|
|
@@ -123,11 +124,20 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
123
124
|
return this.#invoke('releasePolicy', expected);
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
/** Terminal release when an apply acknowledgement was lost. Retain the full
|
|
128
|
+
* original identity and transition first. The native owner accepts only an
|
|
129
|
+
* absent table or the exact previous/target graph; it never recreates policy
|
|
130
|
+
* or requires surviving interfaces. Exact retries and inspection remain
|
|
131
|
+
* available; close() retries admitted cleanup, closeRetaining() only joins. */
|
|
132
|
+
public releaseTransition(request: IManagedNftTransitionRelease<TPolicy>): Promise<{ released: true }> {
|
|
133
|
+
return this.#invoke('releasePolicyTransition', request);
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
127
137
|
* joins the native process. Retain the full applied value before this call.
|
|
128
138
|
* After any failure close() only terminates; it never deletes the table. */
|
|
129
139
|
public detach(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{ detached: true }> {
|
|
130
|
-
if (this.#closeRetainingSelected || this.#closeMode === 'delete') return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
140
|
+
if (this.#closeRetainingSelected || this.#closeMode === 'delete' || this.#transitionRelease) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
131
141
|
this.#retaining = true;
|
|
132
142
|
let captured: IAppliedManagedNftPolicy<TPolicy>;
|
|
133
143
|
try {
|
|
@@ -173,8 +183,13 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
173
183
|
await this.#work?.catch(() => undefined);
|
|
174
184
|
if (this.#bridge) {
|
|
175
185
|
if (this.#nativeMayOwn && !retaining) {
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
// Even native validation may have rejected before selecting its own
|
|
187
|
+
// terminal intent. Retry this exact request; generic close must not
|
|
188
|
+
// delete an earlier applied graph outside the requested transition.
|
|
189
|
+
const result = this.#transitionRelease
|
|
190
|
+
? await this.#bridge.sendCommand('releasePolicyTransition', this.#transitionRelease.request)
|
|
191
|
+
: await this.#bridge.sendCommand('closeOwner', {});
|
|
192
|
+
if (result.released !== true || this.#transitionRelease && Object.keys(result).length !== 1) throw new ManagedNftablesError('PROTOCOL');
|
|
178
193
|
}
|
|
179
194
|
await this.#bridge.terminate({ gracePeriodMs: 1000 });
|
|
180
195
|
}
|
|
@@ -191,15 +206,24 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
191
206
|
if (this.#retaining || !['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
192
207
|
let captured: TManagedNftCommands<TPolicy>[K]['params'];
|
|
193
208
|
try { captured = snapshot(params); } catch { return Promise.reject(new ManagedNftablesError('INVALID')); }
|
|
194
|
-
if (method
|
|
209
|
+
if (this.#transitionRelease && (method !== 'inspectPolicy' && method !== 'releasePolicyTransition'
|
|
210
|
+
|| method === 'releasePolicyTransition' && !plugins.util.isDeepStrictEqual(this.#transitionRelease.request, captured))) {
|
|
211
|
+
return Promise.reject(new ManagedNftablesError('CONFLICT'));
|
|
212
|
+
}
|
|
213
|
+
if (method === 'releasePolicyTransition') this.#transitionRelease = { request: captured as IManagedNftTransitionRelease<TPolicy> };
|
|
214
|
+
if (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition') this.#nativeMayOwn = true;
|
|
195
215
|
return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
|
|
196
216
|
const capturedResult = snapshot(result);
|
|
197
|
-
if (method === 'releasePolicy'
|
|
217
|
+
if (method === 'releasePolicy' || method === 'releasePolicyTransition') {
|
|
218
|
+
if (method === 'releasePolicyTransition' && ((capturedResult as { released: true })?.released !== true
|
|
219
|
+
|| Object.keys(capturedResult).length !== 1)) throw new ManagedNftablesError('PROTOCOL');
|
|
220
|
+
this.#nativeMayOwn = false;
|
|
221
|
+
}
|
|
198
222
|
if (method === 'inspectPolicy') {
|
|
199
223
|
const status = capturedResult as IManagedNftStatus<TPolicy>;
|
|
200
224
|
if (status.state === 'failed-owned' || status.error !== null) this.#fail();
|
|
201
225
|
}
|
|
202
|
-
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy'
|
|
226
|
+
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
|
|
203
227
|
|| method === 'inspectPolicy' && (capturedResult as IManagedNftStatus<TPolicy>).error === null
|
|
204
228
|
&& (capturedResult as IManagedNftStatus<TPolicy>).state !== 'failed-owned')) this.#state = 'ready';
|
|
205
229
|
return capturedResult;
|
|
@@ -216,7 +240,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
216
240
|
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
217
241
|
&& (method === 'preparePolicy' || code === 'INVALID');
|
|
218
242
|
if (!validation && (this.#state !== 'closing'
|
|
219
|
-
|| ['reconcilePolicy', 'releasePolicy', 'detachPolicy'].includes(method))) this.#fail();
|
|
243
|
+
|| ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method))) this.#fail();
|
|
220
244
|
throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
|
|
221
245
|
});
|
|
222
246
|
this.#work = owned;
|
package/ts/managed.types.ts
CHANGED
|
@@ -60,6 +60,14 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
|
|
|
60
60
|
target: IPreparedManagedNftPolicy<TPolicy>;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Persist the original boot/namespace identity and full transition before cleanup.
|
|
64
|
+
* This releases only its exact owned table; it does not prove packet drain or
|
|
65
|
+
* authorize address reuse. Surviving policy interfaces are not required. */
|
|
66
|
+
export interface IManagedNftTransitionRelease<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
67
|
+
identity: IManagedNftIdentity;
|
|
68
|
+
transition: IManagedNftTransition<TPolicy>;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
64
72
|
identity: IManagedNftIdentity;
|
|
65
73
|
state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
|
|
@@ -91,6 +99,7 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
|
|
|
91
99
|
reconcilePolicy: { params: IManagedNftTransition<TPolicy>; result: IAppliedManagedNftPolicy<TPolicy> };
|
|
92
100
|
inspectPolicy: { params: Record<string, never>; result: IManagedNftStatus<TPolicy> };
|
|
93
101
|
releasePolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { released: true } };
|
|
102
|
+
releasePolicyTransition: { params: IManagedNftTransitionRelease<TPolicy>; result: { released: true } };
|
|
94
103
|
detachPolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { detached: true } };
|
|
95
104
|
closeOwner: { params: Record<string, never>; result: { released: true } };
|
|
96
105
|
};
|