@push.rocks/smartnftables 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +9 -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.manageddockerforwarding.d.ts +2 -1
- package/dist_ts/classes.manageddockerforwarding.js +3 -2
- package/dist_ts/forwardhooks.d.ts +8 -0
- package/dist_ts/forwardhooks.js +42 -0
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/managed.docker.types.d.ts +2 -1
- package/dist_ts/managed.forwardhooks.types.d.ts +35 -0
- package/dist_ts/managed.forwardhooks.types.js +2 -0
- package/package.json +1 -1
- package/readme.md +69 -16
- package/rust/src/docker.frontend.rs +2 -2
- package/rust/src/docker.graph.rs +36 -11
- package/rust/src/docker.policy.rs +90 -31
- package/rust/src/docker.rs +37 -13
- package/rust/src/docker_tests.rs +161 -2
- package/rust/src/egress.host.rs +44 -45
- package/rust/src/egress.rs +56 -0
- package/rust/src/forwardhooks.rs +176 -0
- package/rust/src/forwardhooks_tests.rs +136 -0
- package/rust/src/main.rs +12 -8
- package/rust/src/wire.links.rs +28 -6
- package/rust/src/wire.rs +22 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.manageddockerforwarding.ts +2 -1
- package/ts/forwardhooks.ts +39 -0
- package/ts/index.ts +2 -0
- package/ts/managed.docker.types.ts +2 -1
- package/ts/managed.forwardhooks.types.ts +26 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-10-01 - 4.2.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- `ManagedDockerForwarding` now admits every forwarded flow the non-exclusive `hostTransit` barrier admits, each with its replies: besides the leased ranges, each inbound publication (uplink to handoff after the barrier's DNAT, matched on its inside destination and its published `hostIp` and port(s) as the original destination) and each symmetric publication's flows from its target ports (handoff to uplink, before the barrier's SNAT). The barrier's forward chain and the contribution derive from one enumeration of the scope's forwarded flows (`HostScope::forwarding`), and the barrier's compiled bytes are unchanged. A router's own egress reaches the host as a leased flow and stays covered by the leased rules. Every flow kind is three rules (NEW with TCP SYN only, ESTABLISHED, ESTABLISHED reply); the native graph check verifies the conntrack original-destination tuple bytes. A scope without publications keeps its exact rules, comments and graphs. The 192-rule and 100,000-byte bound is unchanged, so beside one handoff with a TCP and a UDP range a scope with more than 62 publications, or 31 symmetric ones, is refused as `EXHAUSTED`.
|
|
8
|
+
- `ManagedDockerForwarding.reconcile()` amends a running generation in place: a handoff link that `previous` and `target` both bind exactly may stay up, because the single `iptables-nft-restore` transaction swaps the owned block in one atomic commit. Links the transition adds or drops, and every link of a first apply, must still be down.
|
|
9
|
+
- `ManagedDockerForwarding.release()` and `close()` count a handoff link whose index no link holds any more as down, so a process restarted in the same boot can release the previous contribution after its router namespace and veths are gone. An existing link must still be the exact bound link and down, and reconcile keeps requiring existing links.
|
|
10
|
+
- Add `inspectForwardHooks({ binaryPath?, networkNamespaceFd? })`: a read-only, generation-consistent report of every nf_tables forward base chain of the `ip`, `ip6` and `inet` families with its table handle, priority and policy, of Docker's `DOCKER-USER`/`DOCKER-FORWARD` chains, and of registered legacy xtables tables, so a caller can refuse `exclusiveForwarding` while another forward owner exists. The native binary gains a `--forward-hooks` mode for it.
|
|
11
|
+
|
|
3
12
|
## 2026-10-01 - 4.1.0
|
|
4
13
|
|
|
5
14
|
### Features
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "5c2f8a063c4d9b145882ca1782151a016adb44940a9a0c99e13cbd5eeea619bb",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "4.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "4.2.0",
|
|
7
|
+
"gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-10-
|
|
9
|
+
"builtAt": "2026-10-01T20:26:51.965Z",
|
|
10
10
|
"tsrustVersion": "2.0.1",
|
|
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": "acdccfdbc055aaea30aaf547020585cabfe0a07be6e590794372fbc8950c5f6b",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "4.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "4.2.0",
|
|
7
|
+
"gitCommit": "8d4de9479b77ba7fd1297d895dc5038f65e2f99a",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-10-
|
|
9
|
+
"builtAt": "2026-10-01T20:27:00.986Z",
|
|
10
10
|
"tsrustVersion": "2.0.1",
|
|
11
11
|
"binary": "smartnftables",
|
|
12
12
|
"target": "linux_arm64_musl"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartnftables',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.2.0',
|
|
7
7
|
description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1IQUFtSDtDQUNqSSxDQUFBIn0=
|
|
@@ -12,6 +12,7 @@ export declare class ManagedDockerForwarding {
|
|
|
12
12
|
release(expected: IAppliedManagedDockerForwardingPolicy): Promise<{
|
|
13
13
|
released: true;
|
|
14
14
|
}>;
|
|
15
|
-
/** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
|
|
15
|
+
/** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
|
|
16
|
+
* through cleanup; a handoff link that no longer exists counts as down. */
|
|
16
17
|
close(): Promise<void>;
|
|
17
18
|
}
|
|
@@ -65,7 +65,8 @@ export class ManagedDockerForwarding {
|
|
|
65
65
|
release(expected) {
|
|
66
66
|
return this.#invoke('releaseDockerForwarding', expected);
|
|
67
67
|
}
|
|
68
|
-
/** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
|
|
68
|
+
/** Join operations and exact contributed-rule deletion, then native termination. Keep affected handoff links down
|
|
69
|
+
* through cleanup; a handoff link that no longer exists counts as down. */
|
|
69
70
|
close() {
|
|
70
71
|
if (this.#close)
|
|
71
72
|
return this.#close;
|
|
@@ -124,4 +125,4 @@ export class ManagedDockerForwarding {
|
|
|
124
125
|
return owned;
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
128
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkZG9ja2VyZm9yd2FyZGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMubWFuYWdlZGRvY2tlcmZvcndhcmRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFHNUcseUdBQXlHO0FBQ3pHLE1BQU0sT0FBTyx1QkFBdUI7SUFDekIsUUFBUSxDQUE0QztJQUM3RCxPQUFPLEdBQTBFLElBQUksQ0FBQztJQUN0RixNQUFNLEdBQTBFLEtBQUssQ0FBQztJQUN0RixLQUFLLEdBQTRCLElBQUksQ0FBQztJQUN0QyxNQUFNLEdBQXlCLElBQUksQ0FBQztJQUNwQyxhQUFhLEdBQUcsS0FBSyxDQUFDO0lBRXRCLFlBQVksT0FBd0M7UUFDbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEMsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxvQkFBb0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztlQUNySCxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7ZUFDckksSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENB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
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IForwardHookInspection, IForwardHookInspectionOptions } from './managed.forwardhooks.types.js';
|
|
2
|
+
/** Reads every packet-filter owner on the IPv4/IPv6 forward hook of one network
|
|
3
|
+
* namespace: nf_tables forward base chains of the `ip`, `ip6` and `inet`
|
|
4
|
+
* families, Docker's DOCKER-USER/DOCKER-FORWARD chains and registered legacy
|
|
5
|
+
* xtables tables. One native process takes one generation-consistent read and
|
|
6
|
+
* exits; it claims, adopts and changes nothing. The result is a point-in-time
|
|
7
|
+
* view: another owner can appear after it. */
|
|
8
|
+
export declare function inspectForwardHooks(options?: IForwardHookInspectionOptions): Promise<IForwardHookInspection>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { snapshot, managedIpcBytes, ManagedNftablesError, nativeError } from './classes.managednftables.js';
|
|
3
|
+
/** Reads every packet-filter owner on the IPv4/IPv6 forward hook of one network
|
|
4
|
+
* namespace: nf_tables forward base chains of the `ip`, `ip6` and `inet`
|
|
5
|
+
* families, Docker's DOCKER-USER/DOCKER-FORWARD chains and registered legacy
|
|
6
|
+
* xtables tables. One native process takes one generation-consistent read and
|
|
7
|
+
* exits; it claims, adopts and changes nothing. The result is a point-in-time
|
|
8
|
+
* view: another owner can appear after it. */
|
|
9
|
+
export async function inspectForwardHooks(options = {}) {
|
|
10
|
+
const captured = snapshot(options);
|
|
11
|
+
if (Object.keys(captured).some((key) => !['binaryPath', 'networkNamespaceFd'].includes(key))
|
|
12
|
+
|| captured.binaryPath !== undefined && (typeof captured.binaryPath !== 'string'
|
|
13
|
+
|| !plugins.path.isAbsolute(captured.binaryPath) || captured.binaryPath.includes('\0'))
|
|
14
|
+
|| captured.networkNamespaceFd !== undefined && (!Number.isInteger(captured.networkNamespaceFd)
|
|
15
|
+
|| captured.networkNamespaceFd < 0 || captured.networkNamespaceFd > 0x7fff_ffff)) {
|
|
16
|
+
throw new ManagedNftablesError('INVALID');
|
|
17
|
+
}
|
|
18
|
+
if (process.platform !== 'linux' || !['x64', 'arm64'].includes(process.arch))
|
|
19
|
+
throw new ManagedNftablesError('UNSUPPORTED_PLATFORM');
|
|
20
|
+
const root = plugins.url.fileURLToPath(new URL('../', import.meta.url));
|
|
21
|
+
const bridge = new plugins.smartrust.RustBridge({
|
|
22
|
+
binaryName: 'smartnftables', binaryPath: captured.binaryPath
|
|
23
|
+
?? plugins.path.join(root, 'dist_rust', `smartnftables_linux_${process.arch === 'x64' ? 'amd64' : 'arm64'}_musl`),
|
|
24
|
+
localPaths: [], searchSystemPath: false,
|
|
25
|
+
cliArgs: ['--forward-hooks', ...(captured.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
26
|
+
inheritedFileDescriptors: captured.networkNamespaceFd === undefined ? undefined : [captured.networkNamespaceFd],
|
|
27
|
+
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: managedIpcBytes,
|
|
28
|
+
maxPendingRequestsByMethod: { inspectForwardHooks: 1 },
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
if (!await bridge.spawn())
|
|
32
|
+
throw new ManagedNftablesError('UNAVAILABLE');
|
|
33
|
+
return snapshot(await bridge.sendCommand('inspectForwardHooks', {}));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw error instanceof ManagedNftablesError ? error : nativeError(error);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await bridge.terminate({ gracePeriodMs: 1000 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yd2FyZGhvb2tzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZm9yd2FyZGhvb2tzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxRQUFRLEVBQUUsZUFBZSxFQUFFLG9CQUFvQixFQUFFLFdBQVcsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRzVHOzs7Ozs4Q0FLOEM7QUFDOUMsTUFBTSxDQUFDLEtBQUssVUFBVSxtQkFBbUIsQ0FBQyxVQUF5QyxFQUFFO0lBQ25GLE1BQU0sUUFBUSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsWUFBWSxFQUFFLG9CQUFvQixDQUFDLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1dBQ3ZGLFFBQVEsQ0FBQyxVQUFVLEtBQUssU0FBUyxJQUFJLENBQUMsT0FBTyxRQUFRLENBQUMsVUFBVSxLQUFLLFFBQVE7ZUFDM0UsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLElBQUksUUFBUSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7V0FDdEYsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUM7ZUFDMUYsUUFBUSxDQUFDLGtCQUFrQixHQUFHLENBQUMsSUFBSSxRQUFRLENBQUMsa0JBQWtCLEdBQUcsV0FBVyxDQUFDLEVBQUUsQ0FBQztRQUNyRixNQUFNLElBQUksb0JBQW9CLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUNELElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztRQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ3JJLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDeEUsTUFBTSxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBdUI7UUFDcEUsVUFBVSxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVU7ZUFDdkQsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFdBQVcsRUFBRSx1QkFBdUIsT0FBTyxDQUFDLElBQUksS0FBSyxLQUFLLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsT0FBTyxPQUFPLENBQUM7UUFDbkgsVUFBVSxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxLQUFLO1FBQ3ZDLE9BQU8sRUFBRSxDQUFDLGlCQUFpQixFQUFFLEdBQUcsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsd0JBQXdCLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNuSCx3QkFBd0IsRUFBRSxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDO1FBQy9HLGNBQWMsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxlQUFlO1FBQy9FLDBCQUEwQixFQUFFLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQyxFQUFFO0tBQ3ZELENBQUMsQ0FBQztJQUNILElBQUksQ0FBQztRQUNILElBQUksQ0FBQyxNQUFNLE1BQU0sQ0FBQyxLQUFLLEVBQUU7WUFBRSxNQUFNLElBQUksb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDekUsT0FBTyxRQUFRLENBQUMsTUFBTSxNQUFNLENBQUMsV0FBVyxDQUFDLHFCQUFxQixFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLEtBQUssWUFBWSxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0UsQ0FBQztZQUFTLENBQUM7UUFDVCxNQUFNLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNsRCxDQUFDO0FBQ0gsQ0FBQyJ9
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -13,3 +13,5 @@ export { buildFirewallRule, buildIPSetCreate, buildIPSetAddElements, buildIPSetR
|
|
|
13
13
|
export type { TNftProtocol, TNftFamily, TNftChainHook, TNftChainType, TNftPolicy, TFirewallAction, TCtState, INftDnatRule, INftSnatRule, INftMasqueradeRule, INftRateLimitRule, INftConnectionRateRule, INftFirewallRule, INftIPSetBlockOptions, INftIPSetConfig, INftCleanupOptions, INftRuleGroup, ISmartNftablesOptions, INftStatus, } from './nft.types.js';
|
|
14
14
|
export * from './managed.docker.types.js';
|
|
15
15
|
export * from './classes.manageddockerforwarding.js';
|
|
16
|
+
export type * from './managed.forwardhooks.types.js';
|
|
17
|
+
export { inspectForwardHooks } from './forwardhooks.js';
|
package/dist_ts/index.js
CHANGED
|
@@ -14,4 +14,5 @@ export { buildRateLimitRule, buildPerIpRateLimitRule, buildConnectionRateRule, }
|
|
|
14
14
|
export { buildFirewallRule, buildIPSetCreate, buildIPSetAddElements, buildIPSetRemoveElements, buildIPSetDelete, buildIPSetMatchRule, } from './nft.rulebuilder.firewall.js';
|
|
15
15
|
export * from './managed.docker.types.js';
|
|
16
16
|
export * from './classes.manageddockerforwarding.js';
|
|
17
|
-
|
|
17
|
+
export { inspectForwardHooks } from './forwardhooks.js';
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjO0FBQ2QsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUlyRixlQUFlO0FBQ2YsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUU5RCxXQUFXO0FBQ1gsT0FBTyxFQUFFLFdBQVcsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFbEYscUNBQXFDO0FBQ3JDLE9BQU8sRUFDTCxlQUFlLEVBQ2YsaUJBQWlCLEVBQ2pCLGlCQUFpQixHQUNsQixNQUFNLDRCQUE0QixDQUFDO0FBRXBDLE9BQU8sRUFDTCxjQUFjLEVBQ2QsYUFBYSxFQUNiLG1CQUFtQixHQUNwQixNQUFNLDBCQUEwQixDQUFDO0FBRWxDLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsdUJBQXVCLEVBQ3ZCLHVCQUF1QixHQUN4QixNQUFNLGdDQUFnQyxDQUFDO0FBRXhDLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsZ0JBQWdCLEVBQ2hCLHFCQUFxQixFQUNyQix3QkFBd0IsRUFDeEIsZ0JBQWdCLEVBQ2hCLG1CQUFtQixHQUNwQixNQUFNLCtCQUErQixDQUFDO0FBd0J2QyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsc0NBQXNDLENBQUM7QUFFckQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sbUJBQW1CLENBQUMifQ==
|
|
@@ -32,7 +32,8 @@ export interface IAppliedManagedDockerForwardingPolicy {
|
|
|
32
32
|
};
|
|
33
33
|
prepared: IPreparedManagedDockerForwardingPolicy;
|
|
34
34
|
}
|
|
35
|
-
/** Persist before mutation.
|
|
35
|
+
/** Persist before mutation. A handoff link both generations bind exactly may stay up: one atomic transaction swaps
|
|
36
|
+
* the contribution. Keep every link the transition adds or drops down until reconciliation is confirmed. */
|
|
36
37
|
export interface IManagedDockerForwardingTransition {
|
|
37
38
|
previous: IAppliedManagedDockerForwardingPolicy | null;
|
|
38
39
|
target: IPreparedManagedDockerForwardingPolicy;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IManagedNftOptions } from './managed.types.js';
|
|
2
|
+
export interface IForwardHookInspectionOptions extends Pick<IManagedNftOptions, 'binaryPath' | 'networkNamespaceFd'> {
|
|
3
|
+
}
|
|
4
|
+
/** One nf_tables base chain on the forward hook of the `ip`, `ip6` or `inet` family. */
|
|
5
|
+
export interface IForwardHookChain {
|
|
6
|
+
family: 'ip' | 'ip6' | 'inet';
|
|
7
|
+
table: string;
|
|
8
|
+
/** Kernel table handle, as a managed receipt's `tableHandle`. */
|
|
9
|
+
tableHandle: string;
|
|
10
|
+
chain: string;
|
|
11
|
+
type: string;
|
|
12
|
+
priority: number;
|
|
13
|
+
policy: 'accept' | 'drop';
|
|
14
|
+
}
|
|
15
|
+
/** One consistent read of every packet-filter owner on the routed forward path of a network namespace. */
|
|
16
|
+
export interface IForwardHookInspection {
|
|
17
|
+
/** Every forward base chain, sorted by family, table and chain; managed tables included. */
|
|
18
|
+
chains: IForwardHookChain[];
|
|
19
|
+
/** Docker's iptables-nft dispatch chains in table `ip filter`. */
|
|
20
|
+
docker: {
|
|
21
|
+
dockerUser: boolean;
|
|
22
|
+
dockerForward: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Registered legacy xtables tables, whose hooks are not nf_tables chains. */
|
|
25
|
+
legacyTables: {
|
|
26
|
+
ipv4: string[];
|
|
27
|
+
ipv6: string[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export type TForwardHookCommands = {
|
|
31
|
+
inspectForwardHooks: {
|
|
32
|
+
params: Record<string, never>;
|
|
33
|
+
result: IForwardHookInspection;
|
|
34
|
+
};
|
|
35
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartnftables",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/readme.md
CHANGED
|
@@ -452,8 +452,8 @@ under host grants. 64 symmetric ranges take about 19,600 element bytes.
|
|
|
452
452
|
The caller still owns the route to `targetAddress` through that handoff, the
|
|
453
453
|
workload listener, and every other packet owner on the host. As with leased egress,
|
|
454
454
|
an ACCEPT here cannot override Docker's independent FORWARD DROP; the
|
|
455
|
-
`ManagedDockerForwarding` contribution below
|
|
456
|
-
the barrier
|
|
455
|
+
`ManagedDockerForwarding` contribution below admits every inbound and symmetric
|
|
456
|
+
publication of the barrier through it, with the leased flows.
|
|
457
457
|
|
|
458
458
|
Each router generation binds an immutable lease reference, transit source address,
|
|
459
459
|
leased TCP/UDP source-port ranges, a nonzero conntrack zone and a 16-byte label.
|
|
@@ -693,7 +693,8 @@ forwarding that another owner (Docker, a container or VM bridge, a VPN router, a
|
|
|
693
693
|
second routing daemon) accepts, and accepting in this table still cannot override
|
|
694
694
|
another owner's drop. Use it only on a host where this table is the sole forwarding
|
|
695
695
|
owner; on a Docker host keep it absent and use the Docker contribution below, which
|
|
696
|
-
refuses an exclusive barrier.
|
|
696
|
+
refuses an exclusive barrier. `inspectForwardHooks()`, described below, reports
|
|
697
|
+
the other forward owners of the namespace for that decision. The
|
|
697
698
|
member does not fence flowtable offload, packet queues, proxies or anything outside
|
|
698
699
|
the forward hook.
|
|
699
700
|
|
|
@@ -880,7 +881,7 @@ drainage, conntrack cleanup, elapsed-time expiry, or safe address/port/zone reus
|
|
|
880
881
|
|
|
881
882
|
### Docker forwarding contribution
|
|
882
883
|
|
|
883
|
-
`ManagedDockerForwarding` admits the
|
|
884
|
+
`ManagedDockerForwarding` admits exactly the forwarded flows of an exact applied
|
|
884
885
|
schema-v2 `hostTransit` receipt through Docker's existing IPv4 forwarding path.
|
|
885
886
|
It reads and verifies that dedicated barrier's complete table and local links,
|
|
886
887
|
without adopting its ownership. The contribution uses Docker's supported
|
|
@@ -916,32 +917,84 @@ and never changes a host forwarding policy. `prepare()` refuses a barrier with
|
|
|
916
917
|
`exclusiveForwarding` as `INVALID`: its drop would also deny Docker's own container
|
|
917
918
|
forwarding.
|
|
918
919
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
920
|
+
The contribution and the barrier's forward chain derive from one enumeration of
|
|
921
|
+
the scope's forwarded flows, so the contribution admits exactly the flow kinds the
|
|
922
|
+
non-exclusive barrier admits, each with its replies:
|
|
923
|
+
|
|
924
|
+
| Flow kind | Original direction | Live tuple | Conntrack original tuple |
|
|
925
|
+
| --- | --- | --- | --- |
|
|
926
|
+
| Leased range | handoff → uplink | transit source address and leased port range | same source |
|
|
927
|
+
| Inbound publication | uplink → handoff, after the barrier's DNAT | inside target address and port(s) as destination | published `hostIp` and port(s) as destination |
|
|
928
|
+
| Symmetric publication | handoff → uplink, before the barrier's SNAT | target address and port(s) as source | same source |
|
|
929
|
+
|
|
930
|
+
Every flow kind has three rules: the NEW opening (TCP only with SYN and
|
|
931
|
+
FIN/RST/ACK clear), the ESTABLISHED original direction and the ESTABLISHED reply
|
|
932
|
+
with swapped interfaces and tuple sides. A router's own egress, such as a VPN client
|
|
933
|
+
in the router namespace dialling its hub, reaches the host as a leased flow: the
|
|
934
|
+
router translates it to the transit address and a leased port range, so it is
|
|
935
|
+
admitted by the leased rules exactly when the barrier admits it. Host grants and
|
|
936
|
+
host-local platform endpoints use INPUT/OUTPUT and never cross FORWARD. Each rule
|
|
937
|
+
binds the handoff and uplink names. The exact v2 host barrier independently checks
|
|
938
|
+
interface indices, MAC/iflink facts, the default host conntrack zone, protected
|
|
939
|
+
original/current destinations and explicit outer SNAT; it governs every forwarded
|
|
940
|
+
packet on its handoffs, so the host forwards the intersection, which is the
|
|
941
|
+
barrier's admitted set. A scope without publications keeps its exact 4.1.0 rules,
|
|
942
|
+
comments and graphs. A complete contribution is bounded to 192 rules and 100,000
|
|
943
|
+
generated command bytes: three rules per leased range and per publication, and
|
|
944
|
+
three more per symmetric publication. A larger scope is refused as `EXHAUSTED`
|
|
945
|
+
(`restoreRules`/`restoreBytes`) before any subprocess.
|
|
926
946
|
|
|
927
947
|
Create the class with `ownerId`, a caller-retained `instanceId` of at least 16
|
|
928
948
|
identifier characters, and optional `binaryPath`/`networkNamespaceFd`. After
|
|
929
949
|
`start()`, call `prepare({ schemaVersion: 1, revision, barrier })`, where `barrier`
|
|
930
950
|
is the exact applied host policy. Persist the complete `{ previous, target }`
|
|
931
|
-
transition before `reconcile()`.
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
951
|
+
transition before `reconcile()`. The native owner fences the handoff links before
|
|
952
|
+
and after its single no-flush restore transaction, which deletes the previous block
|
|
953
|
+
and inserts the target block in one atomic nf_tables commit. A handoff link that
|
|
954
|
+
`previous` and `target` both bind exactly may stay up, so a publication or lease
|
|
955
|
+
amendment of a running generation applies in place: every flow both contributions
|
|
956
|
+
admit passes throughout, a flow only the target admits passes from the commit, and
|
|
957
|
+
the referenced barrier must already be the target. Every handoff link the
|
|
958
|
+
transition adds or drops, and every link of a first apply, must be down; retain
|
|
959
|
+
those exact links until cleanup finishes. Exact target replay can recover a lost
|
|
960
|
+
response without rewriting live rules.
|
|
935
961
|
|
|
936
962
|
`inspect().present` confirms the contribution and its referenced barrier, not
|
|
937
963
|
the complete host packet path or durable controller authority. Errors retain
|
|
938
964
|
failed-owned state and pending intent. `release(applied)` and `close()` delete
|
|
939
|
-
only exact contributed rules
|
|
965
|
+
only exact contributed rules. Their fence requires every handoff link down or gone:
|
|
966
|
+
a link whose index no link holds any more, such as a veth that left with its router
|
|
967
|
+
namespace when the node process ended, counts as down, so a restarted process in
|
|
968
|
+
the same boot can release the previous contribution before applying the next one.
|
|
969
|
+
A different link at a bound index still rejects. A cold boot needs a new
|
|
940
970
|
barrier receipt and caller-authorized replay; old boot receipts cannot authorize
|
|
941
971
|
native operations. The caller owns restart ordering, disjoint retained leases,
|
|
942
972
|
exclusive privileged mutation authority and activation. The frontend transaction
|
|
943
973
|
does not provide compare-and-swap against other privileged processes.
|
|
944
974
|
|
|
975
|
+
### Forward hook owners
|
|
976
|
+
|
|
977
|
+
`inspectForwardHooks({ binaryPath?, networkNamespaceFd? })` reads every
|
|
978
|
+
packet-filter owner on the routed forward path of one network namespace and
|
|
979
|
+
changes nothing. One native process takes the read and exits:
|
|
980
|
+
|
|
981
|
+
- `chains`: every nf_tables base chain on the forward hook of the `ip`, `ip6` and
|
|
982
|
+
`inet` families, with `family`, `table`, `tableHandle`, `chain`, `type`,
|
|
983
|
+
`priority` and `policy`, sorted by family, table and chain. Managed tables are
|
|
984
|
+
included; compare `table` and `tableHandle` with a receipt's
|
|
985
|
+
`identity.tableName` and `tableHandle` to find your own.
|
|
986
|
+
- `docker`: whether Docker's iptables-nft `DOCKER-USER` and `DOCKER-FORWARD`
|
|
987
|
+
chains exist in table `ip filter`.
|
|
988
|
+
- `legacyTables`: the registered legacy xtables `ipv4` and `ipv6` tables. Their
|
|
989
|
+
hooks, including FORWARD, are not nf_tables objects and never appear in `chains`.
|
|
990
|
+
|
|
991
|
+
The table and chain dumps share one ruleset generation, or the read is
|
|
992
|
+
`CONFLICT`. Without `CAP_NET_ADMIN` in the namespace it is `PERMISSION`. Bridge
|
|
993
|
+
and netdev hooks do not see routed packets and are not reported. Guard
|
|
994
|
+
`exclusiveForwarding` with it: refuse while any chain other than your own barrier's,
|
|
995
|
+
either Docker chain or any legacy table is present. The read is a point in time;
|
|
996
|
+
the caller still owns exclusive authority over who may add a forward owner later.
|
|
997
|
+
|
|
945
998
|
### Native qualification
|
|
946
999
|
|
|
947
1000
|
The separate [Docker fixture](test/native/readme.docker.md) exercises the native
|
|
@@ -113,10 +113,10 @@ fn normalized(args: &[String]) -> Result<Vec<(String, Vec<String>)>> {
|
|
|
113
113
|
let mut values=BTreeMap::new();let mut modules=BTreeSet::new();let mut index=0;
|
|
114
114
|
while index<args.len() {
|
|
115
115
|
let key=&args[index];let count=if key=="--tcp-flags" {2} else {1};
|
|
116
|
-
if !["-i","-o","-s","-d","-p","-m","--sport","--dport","--tcp-flags","--ctstate","--ctproto","--ctorigsrc","--ctorigsrcport","--ctdir","--comment","-j"].contains(&key.as_str()) {return Err(Error::Conflict);}
|
|
116
|
+
if !["-i","-o","-s","-d","-p","-m","--sport","--dport","--tcp-flags","--ctstate","--ctproto","--ctorigsrc","--ctorigsrcport","--ctorigdst","--ctorigdstport","--ctdir","--comment","-j"].contains(&key.as_str()) {return Err(Error::Conflict);}
|
|
117
117
|
let mut value=args.get(index+1..index+1+count).ok_or(Error::Conflict)?.to_vec();index+=1+count;
|
|
118
118
|
if key=="-m" {if !modules.insert(value[0].clone()) {return Err(Error::Conflict);}continue;}
|
|
119
|
-
if key=="--ctorigsrc" && !value[0].contains('/') {value[0].push_str("/32");}
|
|
119
|
+
if (key=="--ctorigsrc" || key=="--ctorigdst") && !value[0].contains('/') {value[0].push_str("/32");}
|
|
120
120
|
if key=="--ctproto" {value[0]=match value[0].as_str() {"tcp"=>"6".into(),"udp"=>"17".into(),_=>value[0].clone()};}
|
|
121
121
|
if values.insert(key.clone(),value).is_some() {return Err(Error::Conflict);}
|
|
122
122
|
}
|
package/rust/src/docker.graph.rs
CHANGED
|
@@ -9,17 +9,36 @@ fn payload(base: u32, offset: u32, length: u32) -> Attr {
|
|
|
9
9
|
expr("payload",vec![Attr::u32(1,1),Attr::u32(2,base),Attr::u32(3,offset),Attr::u32(4,length)])
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
fn span(value: &str) -> Result<(u16, u16)> {
|
|
13
|
+
let (first,last)=value.split_once(':').unwrap_or((value,value));
|
|
14
|
+
Ok((first.parse::<u16>().map_err(|_|Error::Invalid)?,last.parse::<u16>().map_err(|_|Error::Invalid)?))
|
|
15
|
+
}
|
|
16
|
+
/// The one key of a rule among alternatives, with its value.
|
|
17
|
+
fn either<'a>(rule: &'a Rule, keys: [&'static str; 2]) -> Result<(usize, &'a str)> {
|
|
18
|
+
let mut found=keys.iter().enumerate().filter_map(|(index,key)|value(rule,key).ok().map(|value|(index,value)));
|
|
19
|
+
let result=found.next().ok_or(Error::Invalid)?;
|
|
20
|
+
if found.next().is_some() {return Err(Error::Invalid);}
|
|
21
|
+
Ok(result)
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
/// Fixed Linux UAPI / xtables 1.8.10 and 1.8.11 constructors, not a raw rule API.
|
|
13
25
|
pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
|
|
14
26
|
let reply=value(rule,"--ctdir")?=="REPLY";
|
|
15
27
|
let new=value(rule,"--ctstate")?=="NEW";
|
|
16
28
|
let protocol=match value(rule,"-p")? {"tcp"=>6_u16,"udp"=>17,_=>return Err(Error::Invalid)};
|
|
17
|
-
|
|
18
|
-
let
|
|
19
|
-
let (
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
let
|
|
29
|
+
// The packet's own source (0) or destination (1) address and port.
|
|
30
|
+
let (side,live)=either(rule,["-s","-d"])?;
|
|
31
|
+
let live=live.strip_suffix("/32").ok_or(Error::Invalid)?.parse::<std::net::Ipv4Addr>().map_err(|_|Error::Invalid)?.octets();
|
|
32
|
+
let (port_side,ports)=either(rule,["--sport","--dport"])?;
|
|
33
|
+
if port_side!=side {return Err(Error::Invalid);}
|
|
34
|
+
let (first,last)=span(ports)?;
|
|
35
|
+
// The originally tracked source (0) or destination (1) address and port.
|
|
36
|
+
let (tracked_side,tracked)=either(rule,["--ctorigsrc","--ctorigdst"])?;
|
|
37
|
+
let tracked=tracked.parse::<std::net::Ipv4Addr>().map_err(|_|Error::Invalid)?.octets();
|
|
38
|
+
let (tracked_port_side,tracked_ports)=either(rule,["--ctorigsrcport","--ctorigdstport"])?;
|
|
39
|
+
if tracked_port_side!=tracked_side {return Err(Error::Invalid);}
|
|
40
|
+
let (tracked_first,tracked_last)=span(tracked_ports)?;
|
|
41
|
+
let mut result=vec![payload(1,if side==0 {12} else {16},4),compare(live.to_vec())];
|
|
23
42
|
for (key,argument) in [(6,"-i"),(7,"-o")] {
|
|
24
43
|
result.extend(meta(key,[value(rule,argument)?.as_bytes(),&[0]].concat()));
|
|
25
44
|
}
|
|
@@ -30,7 +49,7 @@ pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
|
|
|
30
49
|
Attr::nested(4,vec![Attr::bytes(1,vec![0x17])]),Attr::nested(5,vec![Attr::bytes(1,vec![0])]),
|
|
31
50
|
]),compare(vec![2])]);
|
|
32
51
|
}
|
|
33
|
-
result.push(payload(2,if
|
|
52
|
+
result.push(payload(2,if side==0 {0} else {2},2));
|
|
34
53
|
result.push(if first==last {compare(first.to_be_bytes().to_vec())} else {
|
|
35
54
|
expr("range",vec![Attr::u32(1,1),Attr::u32(2,0),
|
|
36
55
|
Attr::nested(3,vec![Attr::bytes(1,first.to_be_bytes().to_vec())]),
|
|
@@ -38,11 +57,17 @@ pub(super) fn expected(rule: &Rule) -> Result<Vec<Attr>> {
|
|
|
38
57
|
});
|
|
39
58
|
// xt_conntrack_mtinfo3 is 164 bytes, XT_ALIGN'ed to 168 on both shipped
|
|
40
59
|
// 64-bit Linux targets. All fields and padding are initialized explicitly.
|
|
60
|
+
// The original source address/mask are at 0/16, its ports at 138/154; the
|
|
61
|
+
// original destination's at 32/48 and 140/156.
|
|
41
62
|
let mut conntrack=vec![0;168];
|
|
42
|
-
|
|
43
|
-
conntrack[
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
let address=32*tracked_side;
|
|
64
|
+
conntrack[address..address+4].copy_from_slice(&tracked);
|
|
65
|
+
conntrack[address+16..address+32].fill(0xff); // canonical bare-host nf_inet_addr mask
|
|
66
|
+
// STATE|PROTO|DIRECTION with ORIGSRC|ORIGSRC_PORT or ORIGDST|ORIGDST_PORT.
|
|
67
|
+
let flags=0x1003|if tracked_side==0 {0x0104} else {0x0208};
|
|
68
|
+
let port=2*tracked_side;
|
|
69
|
+
for (offset,number) in [(136,protocol),(138+port,tracked_first),(146,flags),
|
|
70
|
+
(148,if reply {0x1000} else {0}),(150,if new {8} else {2}),(154+port,tracked_last)] {
|
|
46
71
|
conntrack[offset..offset+2].copy_from_slice(&number.to_ne_bytes());
|
|
47
72
|
}
|
|
48
73
|
result.push(expr("match",vec![Attr::string(1,"conntrack"),Attr::u32(2,3),Attr::bytes(3,conntrack)]));
|