@push.rocks/smartnftables 1.7.0 → 1.9.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 +12 -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 +14 -1
- package/dist_ts/classes.managednftables.js +59 -6
- package/dist_ts/managed.types.d.ts +7 -1
- package/package.json +1 -1
- package/readme.md +47 -2
- package/rust/src/main.rs +4 -0
- package/rust/src/owner.detach.rs +73 -0
- package/rust/src/owner.rs +20 -1
- package/rust/src/owner_detach_tests.rs +252 -0
- package/rust/src/owner_namespace_tests.rs +4 -4
- package/rust/src/owner_poolguard_tests.rs +4 -1
- package/rust/src/owner_tests.rs +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +51 -5
- package/ts/managed.types.ts +2 -1
package/changelog.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-14 - 1.9.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add terminal closeRetaining cleanup for ambiguous reconciliation without requiring an applied receipt; preserve cleanup mode across failures.
|
|
8
|
+
|
|
9
|
+
## 2026-09-14 - 1.8.0
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- Add terminal policy detachment with exact PERSIST verification, retained-intent recovery and failure-safe native process cleanup.
|
|
14
|
+
|
|
3
15
|
## 2026-09-14 - 1.7.0
|
|
4
16
|
|
|
5
17
|
### Features
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "e7f3f755b5f9579969e94d70442710a8b2ebde0aa8dda4c0b312094b8771caf6",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "1.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "1.9.0",
|
|
7
|
+
"gitCommit": "fb3d6ad37496d2010fa77e8e6619565898519748",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
9
|
+
"builtAt": "2026-09-14T04:36:00.044Z",
|
|
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": "f80787be41352d19daf0b3916cb285fe29aa3e88f637180109a07ab8ec609b14",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "1.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "1.9.0",
|
|
7
|
+
"gitCommit": "fb3d6ad37496d2010fa77e8e6619565898519748",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
9
|
+
"builtAt": "2026-09-14T04:36:00.187Z",
|
|
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: '1.
|
|
6
|
+
version: '1.9.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=
|
|
@@ -18,6 +18,19 @@ export declare class ManagedNftables<TPolicy extends TManagedNftPolicy = IManage
|
|
|
18
18
|
release(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{
|
|
19
19
|
released: true;
|
|
20
20
|
}>;
|
|
21
|
-
/**
|
|
21
|
+
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
22
|
+
* joins the native process. Retain the full applied value before this call.
|
|
23
|
+
* After any failure close() only terminates; it never deletes the table. */
|
|
24
|
+
detach(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{
|
|
25
|
+
detached: true;
|
|
26
|
+
}>;
|
|
27
|
+
/** Terminal no-delete cleanup when the exact applied result is unavailable.
|
|
28
|
+
* Joins admitted operations and the child, without inspecting or acknowledging
|
|
29
|
+
* policy retention, enforcement or release. Persist the original transition
|
|
30
|
+
* before reconcile; a fresh owner must recover it after ambiguous application.
|
|
31
|
+
* The first admitted close choice persists across cleanup failures. */
|
|
32
|
+
closeRetaining(): Promise<void>;
|
|
33
|
+
/** Join policy operations, then delete and terminate unless retention was selected.
|
|
34
|
+
* Packet/allocation fencing belongs to the caller; already-admitted close cannot be undone. */
|
|
22
35
|
close(): Promise<void>;
|
|
23
36
|
}
|
|
@@ -61,7 +61,12 @@ export class ManagedNftables {
|
|
|
61
61
|
#state = 'new';
|
|
62
62
|
#work = null;
|
|
63
63
|
#close = null;
|
|
64
|
+
#closeMode = null;
|
|
65
|
+
#closeRetainingSelected = false;
|
|
64
66
|
#nativeMayOwn = false;
|
|
67
|
+
#retaining = false;
|
|
68
|
+
#detachExpected = null;
|
|
69
|
+
#detach = null;
|
|
65
70
|
constructor(options) {
|
|
66
71
|
this.#options = snapshot(options);
|
|
67
72
|
if (Object.keys(this.#options).some((key) => !['ownerId', 'instanceId', 'tableName', 'binaryPath', 'networkNamespaceFd'].includes(key))
|
|
@@ -78,7 +83,7 @@ export class ManagedNftables {
|
|
|
78
83
|
get state() { return this.#state; }
|
|
79
84
|
/** Starts only the compiler/IPC owner. The returned idle status is not enforcement. */
|
|
80
85
|
start() {
|
|
81
|
-
if (this.#state !== 'new')
|
|
86
|
+
if (this.#state !== 'new' || this.#retaining)
|
|
82
87
|
return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
83
88
|
this.#state = 'starting';
|
|
84
89
|
const work = (async () => {
|
|
@@ -92,7 +97,7 @@ export class ManagedNftables {
|
|
|
92
97
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
93
98
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
94
99
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
95
|
-
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, closeOwner: 1 },
|
|
100
|
+
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
|
|
96
101
|
});
|
|
97
102
|
this.#bridge.on('exit', () => {
|
|
98
103
|
if (!['closing', 'stopped'].includes(this.#state))
|
|
@@ -119,17 +124,65 @@ export class ManagedNftables {
|
|
|
119
124
|
release(expected) {
|
|
120
125
|
return this.#invoke('releasePolicy', expected);
|
|
121
126
|
}
|
|
122
|
-
/**
|
|
127
|
+
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
128
|
+
* joins the native process. Retain the full applied value before this call.
|
|
129
|
+
* After any failure close() only terminates; it never deletes the table. */
|
|
130
|
+
detach(expected) {
|
|
131
|
+
if (this.#closeRetainingSelected || this.#closeMode === 'delete')
|
|
132
|
+
return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
133
|
+
this.#retaining = true;
|
|
134
|
+
let captured;
|
|
135
|
+
try {
|
|
136
|
+
captured = snapshot(expected);
|
|
137
|
+
if (this.#detachExpected && !plugins.util.isDeepStrictEqual(this.#detachExpected, captured))
|
|
138
|
+
throw new ManagedNftablesError('CONFLICT');
|
|
139
|
+
if (this.#detach)
|
|
140
|
+
return this.#detach;
|
|
141
|
+
if (!['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work || this.#close)
|
|
142
|
+
throw new ManagedNftablesError('UNAVAILABLE');
|
|
143
|
+
this.#detachExpected = captured;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return Promise.reject(error instanceof ManagedNftablesError ? error : new ManagedNftablesError('INVALID'));
|
|
147
|
+
}
|
|
148
|
+
const work = this.#track(this.#bridge.sendCommand('detachPolicy', captured).then((value) => {
|
|
149
|
+
const result = snapshot(value);
|
|
150
|
+
if (result?.detached !== true || Object.keys(result).length !== 1)
|
|
151
|
+
throw new ManagedNftablesError('PROTOCOL');
|
|
152
|
+
this.#nativeMayOwn = false;
|
|
153
|
+
return result;
|
|
154
|
+
}));
|
|
155
|
+
const detached = (async () => { const result = await work; await this.close(); return result; })();
|
|
156
|
+
this.#detach = detached;
|
|
157
|
+
void detached.catch(() => { this.#detach = null; });
|
|
158
|
+
return detached;
|
|
159
|
+
}
|
|
160
|
+
/** Terminal no-delete cleanup when the exact applied result is unavailable.
|
|
161
|
+
* Joins admitted operations and the child, without inspecting or acknowledging
|
|
162
|
+
* policy retention, enforcement or release. Persist the original transition
|
|
163
|
+
* before reconcile; a fresh owner must recover it after ambiguous application.
|
|
164
|
+
* The first admitted close choice persists across cleanup failures. */
|
|
165
|
+
closeRetaining() {
|
|
166
|
+
if (this.#closeMode === 'delete')
|
|
167
|
+
return Promise.reject(new ManagedNftablesError('CONFLICT'));
|
|
168
|
+
this.#closeRetainingSelected = true;
|
|
169
|
+
this.#retaining = true;
|
|
170
|
+
return this.close();
|
|
171
|
+
}
|
|
172
|
+
/** Join policy operations, then delete and terminate unless retention was selected.
|
|
173
|
+
* Packet/allocation fencing belongs to the caller; already-admitted close cannot be undone. */
|
|
123
174
|
close() {
|
|
124
175
|
if (this.#close)
|
|
125
176
|
return this.#close;
|
|
126
177
|
if (this.#state === 'stopped')
|
|
127
178
|
return Promise.resolve();
|
|
179
|
+
this.#closeMode ??= this.#retaining ? 'retain' : 'delete';
|
|
180
|
+
const retaining = this.#closeMode === 'retain';
|
|
128
181
|
this.#state = 'closing';
|
|
129
182
|
this.#close = (async () => {
|
|
130
183
|
await this.#work?.catch(() => undefined);
|
|
131
184
|
if (this.#bridge) {
|
|
132
|
-
if (this.#nativeMayOwn) {
|
|
185
|
+
if (this.#nativeMayOwn && !retaining) {
|
|
133
186
|
const result = await this.#bridge.sendCommand('closeOwner', {});
|
|
134
187
|
if (result.released !== true)
|
|
135
188
|
throw new ManagedNftablesError('PROTOCOL');
|
|
@@ -146,7 +199,7 @@ export class ManagedNftables {
|
|
|
146
199
|
return this.#close;
|
|
147
200
|
}
|
|
148
201
|
#invoke(method, params) {
|
|
149
|
-
if (!['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work)
|
|
202
|
+
if (this.#retaining || !['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work)
|
|
150
203
|
return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
151
204
|
let captured;
|
|
152
205
|
try {
|
|
@@ -179,4 +232,4 @@ export class ManagedNftables {
|
|
|
179
232
|
return owned;
|
|
180
233
|
}
|
|
181
234
|
}
|
|
182
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
|
|
235
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
|
|
@@ -53,7 +53,7 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
|
|
|
53
53
|
}
|
|
54
54
|
export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
55
55
|
identity: IManagedNftIdentity;
|
|
56
|
-
state: 'idle' | 'applied' | 'failed-owned' | 'released';
|
|
56
|
+
state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
|
|
57
57
|
enforced: boolean;
|
|
58
58
|
applied: IAppliedManagedNftPolicy<TPolicy> | null;
|
|
59
59
|
pending: IManagedNftTransition<TPolicy> | null;
|
|
@@ -96,6 +96,12 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
|
|
|
96
96
|
released: true;
|
|
97
97
|
};
|
|
98
98
|
};
|
|
99
|
+
detachPolicy: {
|
|
100
|
+
params: IAppliedManagedNftPolicy<TPolicy>;
|
|
101
|
+
result: {
|
|
102
|
+
detached: true;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
99
105
|
closeOwner: {
|
|
100
106
|
params: Record<string, never>;
|
|
101
107
|
result: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartnftables",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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
|
@@ -113,7 +113,9 @@ grants remain outstanding.
|
|
|
113
113
|
| `reconcile({ previous, target })` | Creates or replaces the entire policy in one kernel transaction. `previous` is null initially, otherwise the full prior applied result. |
|
|
114
114
|
| `inspect()` | Reads the current owned graph and reports `enforced`, pending intent and a bounded error code. |
|
|
115
115
|
| `release(applied)` | Verifies the exact retained graph and confirms deletion; repeats are idempotent. |
|
|
116
|
-
| `
|
|
116
|
+
| `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. |
|
|
117
|
+
| `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. |
|
|
118
|
+
| `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. |
|
|
117
119
|
|
|
118
120
|
Schema-v1 policies are directed. Return traffic requires its own grant; there is no broad
|
|
119
121
|
connection-tracking bypass. A null endpoint denotes the actual local host, with an
|
|
@@ -154,6 +156,48 @@ invalidates the old boot receipt; the caller must fence its old link/workload
|
|
|
154
156
|
lifecycle before starting a new owner instance. Cleanup failures and uncertain
|
|
155
157
|
operations must not be reported as successful release or used to transfer authority.
|
|
156
158
|
|
|
159
|
+
For an orderly restart that must preserve policy, persist the complete applied
|
|
160
|
+
result and call `await owner.detach(applied)` before calling `close()`. The native
|
|
161
|
+
operation validates the exact receipt, graph, interfaces, boot and namespace. It
|
|
162
|
+
accepts this socket's OWNER/PERSIST table or an exact orphan; it never adopts a
|
|
163
|
+
foreign owner. After closing the owner socket it reads the graph again and requires
|
|
164
|
+
the same handle and compiled body with PERSIST-only flags. Native inspection then
|
|
165
|
+
reports `state: 'retained'`. The facade returns success only after child termination.
|
|
166
|
+
|
|
167
|
+
Calling `detach()` irreversibly selects retention cleanup, including invalid input,
|
|
168
|
+
transport failure, timeout, native rejection or malformed acknowledgement. Subsequent
|
|
169
|
+
`close()` only joins and terminates; it never sends a table-deletion command. It
|
|
170
|
+
cannot undo a `close()` already admitted earlier. Other policy operations reject.
|
|
171
|
+
Resolve any pending policy transition by exact replay before requesting detachment.
|
|
172
|
+
If a detach ACK is lost while the process remains available, only the same captured
|
|
173
|
+
body may be retried. After process loss, start a fresh owner with identical options
|
|
174
|
+
and retry `detach(applied)` against the exact orphan. Absent graphs, foreign active
|
|
175
|
+
owners, changed interfaces, handles, bodies or boot/namespace identities reject.
|
|
176
|
+
Native release/reconcile/close commands reject after a valid detach intent, even
|
|
177
|
+
when post-drop verification fails; explicit recovery requires the retained body.
|
|
178
|
+
|
|
179
|
+
If reconciliation remains ambiguous and no complete applied result is available,
|
|
180
|
+
call `await owner.closeRetaining()`. This permanently stops command admission,
|
|
181
|
+
joins admitted work, and terminates and joins the native child without sending
|
|
182
|
+
`detachPolicy` or `closeOwner`. It does not inspect the graph or acknowledge
|
|
183
|
+
retention, enforcement, release, or namespace survival. An undelivered reconcile
|
|
184
|
+
may have left no graph; a lost reply may have left the original or target graph.
|
|
185
|
+
Keep the complete original transition durably and recover it through a fresh owner
|
|
186
|
+
with identical options. Never fabricate an applied receipt to request detachment.
|
|
187
|
+
|
|
188
|
+
After `closeRetaining()`, all policy commands, including `detach()`, reject.
|
|
189
|
+
Concurrent cleanup calls share the same join. A failed join keeps the native
|
|
190
|
+
owner and retention choice for retry through `closeRetaining()` or `close()`.
|
|
191
|
+
An ordinary `close()` admitted first keeps its deletion choice across failures;
|
|
192
|
+
a later `closeRetaining()` rejects instead of reversing that choice.
|
|
193
|
+
|
|
194
|
+
Detachment confirms retention at observation time. A completed facade retry returns
|
|
195
|
+
its existing acknowledgement; it is not a fresh kernel inspection. Retention does
|
|
196
|
+
not guarantee future enforcement, namespace survival or reboot ordering, and does
|
|
197
|
+
not withdraw external authority, drain packets, clear conntrack or release leases.
|
|
198
|
+
A separately created owner can later recover the full policy and perform an
|
|
199
|
+
explicit release after the caller has fenced that authority and packet lifecycle.
|
|
200
|
+
|
|
157
201
|
Apply and release receipts describe this owner's table and policy operations.
|
|
158
202
|
They do not certify that packets admitted under an earlier graph have drained:
|
|
159
203
|
foreign NFQUEUE or other deferred packet owners may still hold such packets.
|
|
@@ -201,7 +245,8 @@ kernel reboot. It does not fence foreign packet queues, later packet rewrites,
|
|
|
201
245
|
offloads or proxies, and supplies no quarantine release, conntrack drainage or
|
|
202
246
|
allocation-reuse evidence. Historical leases remain the caller's durable ownership
|
|
203
247
|
responsibility. PERSIST retains this table only while its network namespace exists;
|
|
204
|
-
`close()`
|
|
248
|
+
`release()` and ordinary `close()` delete it, so revoke any external protection
|
|
249
|
+
claim before release, or use the verified retention operation described above.
|
|
205
250
|
|
|
206
251
|
Local bindings include name, index, kind, MAC (null for L3 TUN), interface-link
|
|
207
252
|
index, and required IPv4 addresses. RTM_GETLINK/RTM_GETADDR verify those local
|
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
|
+
"detachPolicy" => {
|
|
95
|
+
owner.as_mut().ok_or(Error::Conflict)?.detach(decode(request.params)?)?;
|
|
96
|
+
Ok(serde_json::json!({"detached":true}))
|
|
97
|
+
}
|
|
94
98
|
"closeOwner" => {
|
|
95
99
|
if decode::<serde_json::Value>(request.params)? != serde_json::json!({}) {
|
|
96
100
|
return Err(Error::Invalid);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
|
|
3
|
+
impl Owner {
|
|
4
|
+
/// Terminal retention, never adoption/deletion. Exact retry may observe an
|
|
5
|
+
/// orphan after an ACK was lost, including through a fresh native process.
|
|
6
|
+
pub fn detach(&mut self, expected: Applied) -> Result<()> {
|
|
7
|
+
self.validate_receipt(&expected)?;
|
|
8
|
+
if self.released || self.pending.is_some() {
|
|
9
|
+
return Err(Error::Conflict);
|
|
10
|
+
}
|
|
11
|
+
if let Some(retention) = &self.retention {
|
|
12
|
+
if retention.expected != expected {
|
|
13
|
+
return Err(Error::Conflict);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
self.retention = Some(Retention {
|
|
17
|
+
expected: expected.clone(),
|
|
18
|
+
confirmed: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
let result = (|| {
|
|
22
|
+
self.verify_retention(&expected, false)?;
|
|
23
|
+
// OWNER disappears with this socket; PERSIST must retain exactly
|
|
24
|
+
// the same table. The fresh verification socket never adopts it.
|
|
25
|
+
self.socket = None;
|
|
26
|
+
self.verify_retention(&expected, true)?;
|
|
27
|
+
self.socket = None;
|
|
28
|
+
Ok(())
|
|
29
|
+
})();
|
|
30
|
+
self.retention.as_mut().unwrap().confirmed = result.is_ok();
|
|
31
|
+
match result {
|
|
32
|
+
Ok(()) => {
|
|
33
|
+
self.applied = Some(expected);
|
|
34
|
+
self.error = None;
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
Err(error) => {
|
|
38
|
+
self.error = Some(error);
|
|
39
|
+
Err(error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn verify_retention(&mut self, expected: &Applied, orphan_only: bool) -> Result<()> {
|
|
45
|
+
let graph = self.graph()?.ok_or(Error::Conflict)?;
|
|
46
|
+
self.binding(&graph, Some(expected))?;
|
|
47
|
+
let flags = wire::number(&graph.table, 2)?;
|
|
48
|
+
if (orphan_only && flags != 4)
|
|
49
|
+
|| (flags == 6 && wire::number(&graph.table, 7)? != self.socket()?.port)
|
|
50
|
+
|| !self.matches(&graph, &expected.prepared)?
|
|
51
|
+
{
|
|
52
|
+
return Err(Error::Conflict);
|
|
53
|
+
}
|
|
54
|
+
expected.prepared.validate_interfaces()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(super) fn inspect_retained(&mut self) -> Status {
|
|
58
|
+
let expected = self.retention.as_ref().unwrap().expected.clone();
|
|
59
|
+
if let Err(error) = self.verify_retention(&expected, true) {
|
|
60
|
+
self.retention.as_mut().unwrap().confirmed = false;
|
|
61
|
+
self.error = Some(error);
|
|
62
|
+
}
|
|
63
|
+
let enforced = self.retention.as_ref().unwrap().confirmed && self.error.is_none();
|
|
64
|
+
Status {
|
|
65
|
+
identity: self.identity.clone(),
|
|
66
|
+
state: if enforced { "retained" } else { "failed-owned" }.into(),
|
|
67
|
+
enforced,
|
|
68
|
+
applied: self.applied.clone(),
|
|
69
|
+
pending: self.pending.clone(),
|
|
70
|
+
error: self.error.map(|error| error.code().into()),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/rust/src/owner.rs
CHANGED
|
@@ -68,8 +68,17 @@ pub struct Owner {
|
|
|
68
68
|
pending: Option<Transition>,
|
|
69
69
|
error: Option<Error>,
|
|
70
70
|
released: bool,
|
|
71
|
+
retention: Option<Retention>,
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
struct Retention {
|
|
75
|
+
expected: Applied,
|
|
76
|
+
confirmed: bool,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[path = "owner.detach.rs"]
|
|
80
|
+
mod detach;
|
|
81
|
+
|
|
73
82
|
pub(crate) fn context() -> Result<(String, String, String)> {
|
|
74
83
|
let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
|
75
84
|
.map_err(|_| Error::Unavailable)?
|
|
@@ -154,6 +163,7 @@ impl Owner {
|
|
|
154
163
|
pending: None,
|
|
155
164
|
error: None,
|
|
156
165
|
released: false,
|
|
166
|
+
retention: None,
|
|
157
167
|
})
|
|
158
168
|
}
|
|
159
169
|
fn socket(&mut self) -> Result<&mut Socket> {
|
|
@@ -350,7 +360,7 @@ impl Owner {
|
|
|
350
360
|
})
|
|
351
361
|
}
|
|
352
362
|
pub fn reconcile(&mut self, transition: Transition) -> Result<Applied> {
|
|
353
|
-
if self.released {
|
|
363
|
+
if self.released || self.retention.is_some() {
|
|
354
364
|
return Err(Error::Conflict);
|
|
355
365
|
}
|
|
356
366
|
transition.target.validate()?;
|
|
@@ -453,6 +463,9 @@ impl Owner {
|
|
|
453
463
|
self.applied(&graph, transition.target.clone())
|
|
454
464
|
}
|
|
455
465
|
pub fn inspect(&mut self) -> Status {
|
|
466
|
+
if self.retention.is_some() {
|
|
467
|
+
return self.inspect_retained();
|
|
468
|
+
}
|
|
456
469
|
let mut enforced = false;
|
|
457
470
|
if let Some(applied) = self.applied.clone().filter(|_| !self.released) {
|
|
458
471
|
match self.graph().and_then(|graph| {
|
|
@@ -490,6 +503,9 @@ impl Owner {
|
|
|
490
503
|
}
|
|
491
504
|
}
|
|
492
505
|
pub fn release(&mut self, expected: Applied) -> Result<()> {
|
|
506
|
+
if self.retention.is_some() {
|
|
507
|
+
return Err(Error::Conflict);
|
|
508
|
+
}
|
|
493
509
|
self.validate_receipt(&expected)?;
|
|
494
510
|
if self.pending.is_some() {
|
|
495
511
|
return Err(Error::Conflict);
|
|
@@ -545,6 +561,9 @@ impl Owner {
|
|
|
545
561
|
}
|
|
546
562
|
}
|
|
547
563
|
pub fn close(&mut self) -> Result<()> {
|
|
564
|
+
if self.retention.is_some() {
|
|
565
|
+
return Err(Error::Conflict);
|
|
566
|
+
}
|
|
548
567
|
if self.released {
|
|
549
568
|
return Ok(());
|
|
550
569
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
use super::namespace_tests::NativeOwner;
|
|
2
|
+
use super::*;
|
|
3
|
+
use crate::egress;
|
|
4
|
+
use serde_json::json;
|
|
5
|
+
use std::io::Write;
|
|
6
|
+
use std::time::{Duration, Instant};
|
|
7
|
+
|
|
8
|
+
fn pool_target(revision: u64) -> Prepared {
|
|
9
|
+
let mut policy = egress::tests::pool_guard();
|
|
10
|
+
policy["revision"] = json!(revision);
|
|
11
|
+
serde_json::from_value::<egress::Policy>(policy)
|
|
12
|
+
.unwrap()
|
|
13
|
+
.prepare()
|
|
14
|
+
.unwrap()
|
|
15
|
+
.into()
|
|
16
|
+
}
|
|
17
|
+
fn apply(name: &str) -> (Owner, Applied) {
|
|
18
|
+
let mut owner = Owner::new(options(name)).unwrap();
|
|
19
|
+
let applied = owner
|
|
20
|
+
.reconcile(Transition {
|
|
21
|
+
previous: None,
|
|
22
|
+
target: pool_target(1),
|
|
23
|
+
})
|
|
24
|
+
.unwrap();
|
|
25
|
+
(owner, applied)
|
|
26
|
+
}
|
|
27
|
+
fn retained(owner: &mut Owner, applied: &Applied) {
|
|
28
|
+
let status = owner.inspect();
|
|
29
|
+
assert_eq!(status.state, "retained");
|
|
30
|
+
assert!(status.enforced);
|
|
31
|
+
assert_eq!(status.applied, Some(applied.clone()));
|
|
32
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
33
|
+
assert_eq!(wire::number(&graph.table, 2).unwrap(), 4);
|
|
34
|
+
assert_eq!(
|
|
35
|
+
wire::handle(&graph.table, 4).unwrap().to_string(),
|
|
36
|
+
applied.receipt.table_handle
|
|
37
|
+
);
|
|
38
|
+
assert!(owner.matches(&graph, &applied.prepared).unwrap());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
43
|
+
fn detach_retains_exact_graph_is_terminal_and_replays_without_adoption() {
|
|
44
|
+
isolated();
|
|
45
|
+
let (mut owner, applied) = apply("detach_replay");
|
|
46
|
+
owner.detach(applied.clone()).unwrap();
|
|
47
|
+
retained(&mut owner, &applied);
|
|
48
|
+
owner.detach(applied.clone()).unwrap();
|
|
49
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
50
|
+
assert_eq!(owner.release(applied.clone()), Err(Error::Conflict));
|
|
51
|
+
assert_eq!(
|
|
52
|
+
owner.reconcile(Transition {
|
|
53
|
+
previous: Some(applied.clone()),
|
|
54
|
+
target: pool_target(2)
|
|
55
|
+
}),
|
|
56
|
+
Err(Error::Conflict)
|
|
57
|
+
);
|
|
58
|
+
let mut different = applied.clone();
|
|
59
|
+
different.prepared = pool_target(2);
|
|
60
|
+
different.receipt.revision = 2;
|
|
61
|
+
different.receipt.digest = different.prepared.digest.clone();
|
|
62
|
+
assert_eq!(owner.detach(different), Err(Error::Conflict));
|
|
63
|
+
retained(&mut owner, &applied);
|
|
64
|
+
drop(owner);
|
|
65
|
+
let mut fresh = Owner::new(options("detach_replay")).unwrap();
|
|
66
|
+
fresh.detach(applied.clone()).unwrap();
|
|
67
|
+
retained(&mut fresh, &applied);
|
|
68
|
+
// A separate active owner invalidates retention inspection. It is never
|
|
69
|
+
// detached by this reader, even with the same full logical identity.
|
|
70
|
+
let mut active = Owner::new(options("detach_replay")).unwrap();
|
|
71
|
+
active
|
|
72
|
+
.reconcile(Transition {
|
|
73
|
+
previous: None,
|
|
74
|
+
target: applied.prepared.clone(),
|
|
75
|
+
})
|
|
76
|
+
.unwrap();
|
|
77
|
+
assert_eq!(fresh.detach(applied.clone()), Err(Error::Conflict));
|
|
78
|
+
assert!(!fresh.inspect().enforced);
|
|
79
|
+
assert!(active.inspect().enforced);
|
|
80
|
+
assert_eq!(fresh.close(), Err(Error::Conflict));
|
|
81
|
+
drop(active);
|
|
82
|
+
fresh.detach(applied.clone()).unwrap();
|
|
83
|
+
retained(&mut fresh, &applied);
|
|
84
|
+
drop(fresh);
|
|
85
|
+
Owner::new(options("detach_replay"))
|
|
86
|
+
.unwrap()
|
|
87
|
+
.release(applied)
|
|
88
|
+
.unwrap();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
93
|
+
fn detach_rejects_pending_wrong_receipts_foreign_mutation_and_absence() {
|
|
94
|
+
isolated();
|
|
95
|
+
let (mut owner, applied) = apply("detach_faults");
|
|
96
|
+
let pending = Transition {
|
|
97
|
+
previous: Some(applied.clone()),
|
|
98
|
+
target: pool_target(2),
|
|
99
|
+
};
|
|
100
|
+
owner.pending = Some(pending.clone());
|
|
101
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
102
|
+
assert!(owner.retention.is_none());
|
|
103
|
+
let applied = owner.reconcile(pending).unwrap();
|
|
104
|
+
for field in ["boot", "namespace", "handle", "digest"] {
|
|
105
|
+
let mut invalid = applied.clone();
|
|
106
|
+
match field {
|
|
107
|
+
"boot" => {
|
|
108
|
+
invalid.receipt.identity.boot_id = "00000000-0000-0000-0000-000000000000".into()
|
|
109
|
+
}
|
|
110
|
+
"namespace" => invalid.receipt.identity.namespace_inode = "0".into(),
|
|
111
|
+
"handle" => invalid.receipt.table_handle = "18446744073709551615".into(),
|
|
112
|
+
_ => invalid.receipt.digest = format!("sha256:{}", "0".repeat(64)),
|
|
113
|
+
}
|
|
114
|
+
let mut reader = Owner::new(options("detach_faults")).unwrap();
|
|
115
|
+
assert_eq!(reader.detach(invalid), Err(Error::Conflict));
|
|
116
|
+
assert!(owner.inspect().enforced);
|
|
117
|
+
}
|
|
118
|
+
owner.detach(applied.clone()).unwrap();
|
|
119
|
+
// Mutation after detachment is observed on exact retry and permanently
|
|
120
|
+
// retains the failed owner. No failure cleanup adopts or deletes the graph.
|
|
121
|
+
let mut foreign = Socket::open().unwrap();
|
|
122
|
+
let generation = foreign.generation().unwrap();
|
|
123
|
+
foreign
|
|
124
|
+
.batch(
|
|
125
|
+
generation,
|
|
126
|
+
vec![(
|
|
127
|
+
3,
|
|
128
|
+
0x600,
|
|
129
|
+
vec![
|
|
130
|
+
Attr::string(1, "snft_detach_faults"),
|
|
131
|
+
Attr::string(3, "foreign"),
|
|
132
|
+
],
|
|
133
|
+
)],
|
|
134
|
+
)
|
|
135
|
+
.unwrap();
|
|
136
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
137
|
+
assert!(!owner.inspect().enforced);
|
|
138
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
139
|
+
assert_eq!(owner.release(applied.clone()), Err(Error::Conflict));
|
|
140
|
+
assert!(owner
|
|
141
|
+
.graph()
|
|
142
|
+
.unwrap()
|
|
143
|
+
.unwrap()
|
|
144
|
+
.chains
|
|
145
|
+
.iter()
|
|
146
|
+
.any(|chain| wire::text(chain, 3).unwrap() == "foreign"));
|
|
147
|
+
drop(owner);
|
|
148
|
+
// This disposable fixture's foreign owner deletes only its exact orphan.
|
|
149
|
+
let generation = foreign.generation().unwrap();
|
|
150
|
+
foreign
|
|
151
|
+
.batch(
|
|
152
|
+
generation,
|
|
153
|
+
vec![(2, 0, vec![Attr::string(1, "snft_detach_faults")])],
|
|
154
|
+
)
|
|
155
|
+
.unwrap();
|
|
156
|
+
let mut absent = Owner::new(options("detach_faults")).unwrap();
|
|
157
|
+
assert_eq!(absent.detach(applied), Err(Error::Conflict));
|
|
158
|
+
assert!(!absent.inspect().enforced);
|
|
159
|
+
assert_eq!(absent.close(), Err(Error::Conflict));
|
|
160
|
+
assert!(absent.graph().unwrap().is_none());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
165
|
+
fn detach_rechecks_interface_bindings_without_releasing_on_drift() {
|
|
166
|
+
isolated();
|
|
167
|
+
let endpoint = endpoint("a", 71);
|
|
168
|
+
let policy = crate::policy::Policy {
|
|
169
|
+
schema_version: 1,
|
|
170
|
+
revision: 1,
|
|
171
|
+
endpoints: vec![endpoint],
|
|
172
|
+
rules: vec![],
|
|
173
|
+
};
|
|
174
|
+
let mut owner = Owner::new(options("detach_links")).unwrap();
|
|
175
|
+
let applied = owner
|
|
176
|
+
.reconcile(Transition {
|
|
177
|
+
previous: None,
|
|
178
|
+
target: policy.prepare().unwrap().into(),
|
|
179
|
+
})
|
|
180
|
+
.unwrap();
|
|
181
|
+
ip(&["link", "set", "worka", "name", "renameda"]);
|
|
182
|
+
assert_eq!(owner.detach(applied.clone()), Err(Error::Conflict));
|
|
183
|
+
assert_eq!(owner.close(), Err(Error::Conflict));
|
|
184
|
+
let graph = owner.graph().unwrap().unwrap();
|
|
185
|
+
assert_eq!(
|
|
186
|
+
wire::handle(&graph.table, 4).unwrap().to_string(),
|
|
187
|
+
applied.receipt.table_handle
|
|
188
|
+
);
|
|
189
|
+
ip(&["link", "set", "renameda", "name", "worka"]);
|
|
190
|
+
owner.detach(applied.clone()).unwrap();
|
|
191
|
+
retained(&mut owner, &applied);
|
|
192
|
+
drop(owner);
|
|
193
|
+
Owner::new(options("detach_links"))
|
|
194
|
+
.unwrap()
|
|
195
|
+
.release(applied)
|
|
196
|
+
.unwrap();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
201
|
+
fn detach_recovers_lost_response_and_undelivered_request_across_real_native_processes() {
|
|
202
|
+
isolated();
|
|
203
|
+
let namespace = std::fs::File::open("/proc/thread-self/ns/net").unwrap();
|
|
204
|
+
for delivered in [true, false] {
|
|
205
|
+
let name = if delivered {
|
|
206
|
+
"detach_ack"
|
|
207
|
+
} else {
|
|
208
|
+
"detach_unsent"
|
|
209
|
+
};
|
|
210
|
+
let mut native = NativeOwner::start(&namespace);
|
|
211
|
+
native.call("openOwner", serde_json::to_value(options(name)).unwrap());
|
|
212
|
+
let applied: Applied = serde_json::from_value(native.call(
|
|
213
|
+
"reconcilePolicy",
|
|
214
|
+
json!({"previous":null,"target":pool_target(1)}),
|
|
215
|
+
))
|
|
216
|
+
.unwrap();
|
|
217
|
+
let mut reader = Owner::new(options(name)).unwrap();
|
|
218
|
+
reader.validate_receipt(&applied).unwrap();
|
|
219
|
+
if delivered {
|
|
220
|
+
writeln!(
|
|
221
|
+
native.child.stdin.as_mut().unwrap(),
|
|
222
|
+
"{}",
|
|
223
|
+
json!({"id":"lost-detach","method":"detachPolicy","params":applied})
|
|
224
|
+
)
|
|
225
|
+
.unwrap();
|
|
226
|
+
let until = Instant::now() + Duration::from_secs(3);
|
|
227
|
+
loop {
|
|
228
|
+
let graph = reader.graph().unwrap().unwrap();
|
|
229
|
+
if wire::number(&graph.table, 2).unwrap() == 4 {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
assert!(Instant::now() < until);
|
|
233
|
+
std::thread::sleep(Duration::from_millis(5));
|
|
234
|
+
}
|
|
235
|
+
// Do not read the detach response: terminate the actual process
|
|
236
|
+
// after observing its kernel effect but before recording an ACK.
|
|
237
|
+
}
|
|
238
|
+
drop(native);
|
|
239
|
+
let mut fresh = NativeOwner::start(&namespace);
|
|
240
|
+
fresh.call("openOwner", serde_json::to_value(options(name)).unwrap());
|
|
241
|
+
assert_eq!(
|
|
242
|
+
fresh.call("detachPolicy", serde_json::to_value(&applied).unwrap()),
|
|
243
|
+
json!({"detached":true})
|
|
244
|
+
);
|
|
245
|
+
assert_eq!(fresh.call("inspectPolicy", json!({}))["state"], "retained");
|
|
246
|
+
drop(fresh);
|
|
247
|
+
let graph = reader.graph().unwrap().unwrap();
|
|
248
|
+
assert_eq!(wire::number(&graph.table, 2).unwrap(), 4);
|
|
249
|
+
assert!(reader.matches(&graph, &applied.prepared).unwrap());
|
|
250
|
+
reader.release(applied).unwrap();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -30,13 +30,13 @@ fn command(namespace: &File) -> Command {
|
|
|
30
30
|
command
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
struct NativeOwner {
|
|
34
|
-
child: Child,
|
|
33
|
+
pub(super) struct NativeOwner {
|
|
34
|
+
pub(super) child: Child,
|
|
35
35
|
messages: Receiver<Value>,
|
|
36
36
|
reader: Option<JoinHandle<()>>,
|
|
37
37
|
}
|
|
38
38
|
impl NativeOwner {
|
|
39
|
-
fn start(namespace: &File) -> Self {
|
|
39
|
+
pub(super) fn start(namespace: &File) -> Self {
|
|
40
40
|
let mut child = command(namespace).spawn().unwrap();
|
|
41
41
|
let output = child.stdout.take().unwrap();
|
|
42
42
|
let (send, messages) = mpsc::channel();
|
|
@@ -76,7 +76,7 @@ impl NativeOwner {
|
|
|
76
76
|
assert_eq!(response["id"], method);
|
|
77
77
|
response
|
|
78
78
|
}
|
|
79
|
-
fn call(&mut self, method: &str, params: Value) -> Value {
|
|
79
|
+
pub(super) fn call(&mut self, method: &str, params: Value) -> Value {
|
|
80
80
|
let response = self.response(method, params);
|
|
81
81
|
assert_eq!(response["success"], true, "{response}");
|
|
82
82
|
response["result"].clone()
|
|
@@ -234,6 +234,8 @@ fn allocation_pool_guard_covers_all_host_hooks_dnat_untracked_and_reply_directio
|
|
|
234
234
|
})
|
|
235
235
|
.unwrap();
|
|
236
236
|
assert!(owner.inspect().enforced);
|
|
237
|
+
owner.detach(applied.clone()).unwrap();
|
|
238
|
+
assert_eq!(owner.inspect().state, "retained");
|
|
237
239
|
(owner, applied)
|
|
238
240
|
});
|
|
239
241
|
for ((destination, _, _), target) in cases.iter().zip(&targets) {
|
|
@@ -254,7 +256,8 @@ fn allocation_pool_guard_covers_all_host_hooks_dnat_untracked_and_reply_directio
|
|
|
254
256
|
in_namespace(HOST, move || {
|
|
255
257
|
let mut owner = owner;
|
|
256
258
|
assert!(owner.inspect().enforced);
|
|
257
|
-
owner
|
|
259
|
+
drop(owner);
|
|
260
|
+
Owner::new(options("pool_traffic")).unwrap().release(applied).unwrap();
|
|
258
261
|
});
|
|
259
262
|
// Positive control after release proves the denials were this guard.
|
|
260
263
|
for ((destination, _, _), target) in cases.iter().zip(&targets) {
|
package/rust/src/owner_tests.rs
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -49,7 +49,12 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
49
49
|
#state: 'new' | 'starting' | 'ready' | 'closing' | 'failed-owned' | 'stopped' = 'new';
|
|
50
50
|
#work: Promise<unknown> | null = null;
|
|
51
51
|
#close: Promise<void> | null = null;
|
|
52
|
+
#closeMode: 'delete' | 'retain' | null = null;
|
|
53
|
+
#closeRetainingSelected = false;
|
|
52
54
|
#nativeMayOwn = false;
|
|
55
|
+
#retaining = false;
|
|
56
|
+
#detachExpected: IAppliedManagedNftPolicy<TPolicy> | null = null;
|
|
57
|
+
#detach: Promise<{ detached: true }> | null = null;
|
|
53
58
|
|
|
54
59
|
constructor(options: IManagedNftOptions) {
|
|
55
60
|
this.#options = snapshot(options);
|
|
@@ -68,7 +73,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
68
73
|
|
|
69
74
|
/** Starts only the compiler/IPC owner. The returned idle status is not enforcement. */
|
|
70
75
|
public start(): Promise<IManagedNftStatus<TPolicy>> {
|
|
71
|
-
if (this.#state !== 'new') return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
76
|
+
if (this.#state !== 'new' || this.#retaining) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
72
77
|
this.#state = 'starting';
|
|
73
78
|
const work = (async () => {
|
|
74
79
|
if (process.platform !== 'linux' || !['x64', 'arm64'].includes(process.arch)) throw new ManagedNftablesError('UNSUPPORTED_PLATFORM');
|
|
@@ -80,7 +85,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
80
85
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
81
86
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
82
87
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
83
|
-
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, closeOwner: 1 },
|
|
88
|
+
maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
|
|
84
89
|
});
|
|
85
90
|
this.#bridge.on('exit', () => {
|
|
86
91
|
if (!['closing', 'stopped'].includes(this.#state)) this.#state = 'failed-owned';
|
|
@@ -105,15 +110,56 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
105
110
|
return this.#invoke('releasePolicy', expected);
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/**
|
|
113
|
+
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
114
|
+
* joins the native process. Retain the full applied value before this call.
|
|
115
|
+
* After any failure close() only terminates; it never deletes the table. */
|
|
116
|
+
public detach(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{ detached: true }> {
|
|
117
|
+
if (this.#closeRetainingSelected || this.#closeMode === 'delete') return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
118
|
+
this.#retaining = true;
|
|
119
|
+
let captured: IAppliedManagedNftPolicy<TPolicy>;
|
|
120
|
+
try {
|
|
121
|
+
captured = snapshot(expected);
|
|
122
|
+
if (this.#detachExpected && !plugins.util.isDeepStrictEqual(this.#detachExpected, captured)) throw new ManagedNftablesError('CONFLICT');
|
|
123
|
+
if (this.#detach) return this.#detach;
|
|
124
|
+
if (!['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work || this.#close) throw new ManagedNftablesError('UNAVAILABLE');
|
|
125
|
+
this.#detachExpected = captured;
|
|
126
|
+
} catch (error) { return Promise.reject(error instanceof ManagedNftablesError ? error : new ManagedNftablesError('INVALID')); }
|
|
127
|
+
const work = this.#track(this.#bridge.sendCommand('detachPolicy', captured).then((value) => {
|
|
128
|
+
const result = snapshot(value);
|
|
129
|
+
if (result?.detached !== true || Object.keys(result).length !== 1) throw new ManagedNftablesError('PROTOCOL');
|
|
130
|
+
this.#nativeMayOwn = false;
|
|
131
|
+
return result;
|
|
132
|
+
}));
|
|
133
|
+
const detached = (async () => { const result = await work; await this.close(); return result; })();
|
|
134
|
+
this.#detach = detached;
|
|
135
|
+
void detached.catch(() => { this.#detach = null; });
|
|
136
|
+
return detached;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Terminal no-delete cleanup when the exact applied result is unavailable.
|
|
140
|
+
* Joins admitted operations and the child, without inspecting or acknowledging
|
|
141
|
+
* policy retention, enforcement or release. Persist the original transition
|
|
142
|
+
* before reconcile; a fresh owner must recover it after ambiguous application.
|
|
143
|
+
* The first admitted close choice persists across cleanup failures. */
|
|
144
|
+
public closeRetaining(): Promise<void> {
|
|
145
|
+
if (this.#closeMode === 'delete') return Promise.reject(new ManagedNftablesError('CONFLICT'));
|
|
146
|
+
this.#closeRetainingSelected = true;
|
|
147
|
+
this.#retaining = true;
|
|
148
|
+
return this.close();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Join policy operations, then delete and terminate unless retention was selected.
|
|
152
|
+
* Packet/allocation fencing belongs to the caller; already-admitted close cannot be undone. */
|
|
109
153
|
public close(): Promise<void> {
|
|
110
154
|
if (this.#close) return this.#close;
|
|
111
155
|
if (this.#state === 'stopped') return Promise.resolve();
|
|
156
|
+
this.#closeMode ??= this.#retaining ? 'retain' : 'delete';
|
|
157
|
+
const retaining = this.#closeMode === 'retain';
|
|
112
158
|
this.#state = 'closing';
|
|
113
159
|
this.#close = (async () => {
|
|
114
160
|
await this.#work?.catch(() => undefined);
|
|
115
161
|
if (this.#bridge) {
|
|
116
|
-
if (this.#nativeMayOwn) {
|
|
162
|
+
if (this.#nativeMayOwn && !retaining) {
|
|
117
163
|
const result = await this.#bridge.sendCommand('closeOwner', {});
|
|
118
164
|
if (result.released !== true) throw new ManagedNftablesError('PROTOCOL');
|
|
119
165
|
}
|
|
@@ -128,7 +174,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
128
174
|
}
|
|
129
175
|
|
|
130
176
|
#invoke<K extends keyof TManagedNftCommands<TPolicy>>(method: K, params: TManagedNftCommands<TPolicy>[K]['params']): Promise<TManagedNftCommands<TPolicy>[K]['result']> {
|
|
131
|
-
if (!['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
177
|
+
if (this.#retaining || !['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
132
178
|
let captured: TManagedNftCommands<TPolicy>[K]['params'];
|
|
133
179
|
try { captured = snapshot(params); } catch { return Promise.reject(new ManagedNftablesError('INVALID')); }
|
|
134
180
|
if (method === 'reconcilePolicy' || method === 'releasePolicy') this.#nativeMayOwn = true;
|
package/ts/managed.types.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
|
|
|
62
62
|
|
|
63
63
|
export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
|
|
64
64
|
identity: IManagedNftIdentity;
|
|
65
|
-
state: 'idle' | 'applied' | 'failed-owned' | 'released';
|
|
65
|
+
state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
|
|
66
66
|
enforced: boolean;
|
|
67
67
|
applied: IAppliedManagedNftPolicy<TPolicy> | null;
|
|
68
68
|
pending: IManagedNftTransition<TPolicy> | null;
|
|
@@ -91,5 +91,6 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
|
|
|
91
91
|
reconcilePolicy: { params: IManagedNftTransition<TPolicy>; result: IAppliedManagedNftPolicy<TPolicy> };
|
|
92
92
|
inspectPolicy: { params: Record<string, never>; result: IManagedNftStatus<TPolicy> };
|
|
93
93
|
releasePolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { released: true } };
|
|
94
|
+
detachPolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { detached: true } };
|
|
94
95
|
closeOwner: { params: Record<string, never>; result: { released: true } };
|
|
95
96
|
};
|