@push.rocks/smartnftables 1.2.1 → 1.4.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 +37 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +14 -0
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +14 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.managednftables.d.ts +21 -0
- package/dist_ts/classes.managednftables.js +182 -0
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/managed.types.d.ts +103 -0
- package/dist_ts/managed.types.js +2 -0
- package/dist_ts/plugins.d.ts +5 -2
- package/dist_ts/plugins.js +6 -3
- package/native-notices/crates/block-buffer-0.10.4/license-apache +201 -0
- package/native-notices/crates/block-buffer-0.10.4/license-mit +25 -0
- package/native-notices/crates/cfg-if-1.0.4/license-apache +201 -0
- package/native-notices/crates/cfg-if-1.0.4/license-mit +25 -0
- package/native-notices/crates/cpufeatures-0.2.17/license-apache +201 -0
- package/native-notices/crates/cpufeatures-0.2.17/license-mit +25 -0
- package/native-notices/crates/crypto-common-0.1.7/license-apache +201 -0
- package/native-notices/crates/crypto-common-0.1.7/license-mit +25 -0
- package/native-notices/crates/digest-0.10.7/license-apache +201 -0
- package/native-notices/crates/digest-0.10.7/license-mit +25 -0
- package/native-notices/crates/generic-array-0.14.7/license +21 -0
- package/native-notices/crates/itoa-1.0.18/license-apache +176 -0
- package/native-notices/crates/itoa-1.0.18/license-mit +23 -0
- package/native-notices/crates/libc-0.2.189/license-apache +176 -0
- package/native-notices/crates/libc-0.2.189/license-mit +25 -0
- package/native-notices/crates/memchr-2.8.3/copying +3 -0
- package/native-notices/crates/memchr-2.8.3/license-mit +21 -0
- package/native-notices/crates/proc-macro2-1.0.107/license-apache +176 -0
- package/native-notices/crates/proc-macro2-1.0.107/license-mit +23 -0
- package/native-notices/crates/quote-1.0.47/license-apache +176 -0
- package/native-notices/crates/quote-1.0.47/license-mit +23 -0
- package/native-notices/crates/serde-1.0.229/license-apache +176 -0
- package/native-notices/crates/serde-1.0.229/license-mit +23 -0
- package/native-notices/crates/serde_core-1.0.229/license-apache +176 -0
- package/native-notices/crates/serde_core-1.0.229/license-mit +23 -0
- package/native-notices/crates/serde_derive-1.0.229/license-apache +176 -0
- package/native-notices/crates/serde_derive-1.0.229/license-mit +23 -0
- package/native-notices/crates/serde_json-1.0.151/license-apache +176 -0
- package/native-notices/crates/serde_json-1.0.151/license-mit +23 -0
- package/native-notices/crates/sha2-0.10.9/license-apache +201 -0
- package/native-notices/crates/sha2-0.10.9/license-mit +27 -0
- package/native-notices/crates/syn-3.0.5/license-apache +176 -0
- package/native-notices/crates/syn-3.0.5/license-mit +23 -0
- package/native-notices/crates/typenum-1.20.1/license +1 -0
- package/native-notices/crates/typenum-1.20.1/license-apache +201 -0
- package/native-notices/crates/typenum-1.20.1/license-mit +21 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-apache +176 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-mit +23 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-unicode +39 -0
- package/native-notices/crates/version_check-0.9.5/license-apache +201 -0
- package/native-notices/crates/version_check-0.9.5/license-mit +19 -0
- package/native-notices/crates/zmij-1.0.23/license-mit +23 -0
- package/native-notices/manifest.json +520 -0
- package/native-notices/readme.md +36 -0
- package/native-notices/rust-standard-library/addr2line-0.25.1-license-apache +201 -0
- package/native-notices/rust-standard-library/addr2line-0.25.1-license-mit +25 -0
- package/native-notices/rust-standard-library/cargo.lock +522 -0
- package/native-notices/rust-standard-library/compiler-builtins-license.txt +275 -0
- package/native-notices/rust-standard-library/dependency-notices.html +9072 -0
- package/native-notices/rust-standard-library/license-apache +176 -0
- package/native-notices/rust-standard-library/license-mit +25 -0
- package/native-notices/rust-standard-library/llvm-libunwind-license.txt +311 -0
- package/native-notices/rust-standard-library/musl-1.2.5-copyright.txt +193 -0
- package/native-notices/rust-standard-library/unicode-license.txt +39 -0
- package/package.json +23 -8
- package/readme.md +160 -2
- package/rust/.cargo/config.toml +2 -0
- package/rust/Cargo.lock +192 -0
- package/rust/Cargo.toml +13 -0
- package/rust/src/main.rs +159 -0
- package/rust/src/namespace.rs +51 -0
- package/rust/src/owner.rs +604 -0
- package/rust/src/owner_namespace_tests.rs +242 -0
- package/rust/src/owner_tests.rs +798 -0
- package/rust/src/policy.rs +427 -0
- package/rust/src/tests.rs +97 -0
- package/rust/src/wire.rs +437 -0
- package/scripts/check-native-notices.mjs +27 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +153 -0
- package/ts/index.ts +2 -0
- package/ts/managed.types.ts +91 -0
- package/ts/plugins.ts +6 -0
- /package/{license → license.md} +0 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
use crate::policy::{identifier, Prepared};
|
|
2
|
+
use crate::wire::{self, Attr, Socket};
|
|
3
|
+
use crate::{Error, Result};
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use std::os::unix::fs::MetadataExt;
|
|
6
|
+
|
|
7
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
8
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
9
|
+
pub struct Options {
|
|
10
|
+
pub owner_id: String,
|
|
11
|
+
pub instance_id: String,
|
|
12
|
+
pub table_name: String,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
16
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
17
|
+
pub struct Identity {
|
|
18
|
+
pub owner_id: String,
|
|
19
|
+
pub instance_id: String,
|
|
20
|
+
pub table_name: String,
|
|
21
|
+
pub boot_id: String,
|
|
22
|
+
pub namespace_device: String,
|
|
23
|
+
pub namespace_inode: String,
|
|
24
|
+
}
|
|
25
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
26
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
27
|
+
pub struct Receipt {
|
|
28
|
+
pub identity: Identity,
|
|
29
|
+
pub table_handle: String,
|
|
30
|
+
pub revision: u64,
|
|
31
|
+
pub digest: String,
|
|
32
|
+
}
|
|
33
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
34
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
35
|
+
pub struct Applied {
|
|
36
|
+
pub receipt: Receipt,
|
|
37
|
+
pub prepared: Prepared,
|
|
38
|
+
}
|
|
39
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
40
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
41
|
+
pub struct Transition {
|
|
42
|
+
#[serde(deserialize_with = "crate::policy::required_nullable")]
|
|
43
|
+
pub previous: Option<Applied>,
|
|
44
|
+
pub target: Prepared,
|
|
45
|
+
}
|
|
46
|
+
#[derive(Clone, Debug, Serialize)]
|
|
47
|
+
#[serde(rename_all = "camelCase")]
|
|
48
|
+
pub struct Status {
|
|
49
|
+
pub identity: Identity,
|
|
50
|
+
pub state: String,
|
|
51
|
+
pub enforced: bool,
|
|
52
|
+
pub applied: Option<Applied>,
|
|
53
|
+
pub pending: Option<Transition>,
|
|
54
|
+
pub error: Option<String>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
struct Graph {
|
|
58
|
+
generation: u32,
|
|
59
|
+
table: Vec<Attr>,
|
|
60
|
+
chains: Vec<Vec<Attr>>,
|
|
61
|
+
rules: Vec<Vec<Attr>>,
|
|
62
|
+
}
|
|
63
|
+
pub struct Owner {
|
|
64
|
+
identity: Identity,
|
|
65
|
+
socket: Option<Socket>,
|
|
66
|
+
applied: Option<Applied>,
|
|
67
|
+
pending: Option<Transition>,
|
|
68
|
+
error: Option<Error>,
|
|
69
|
+
released: bool,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn context() -> Result<(String, String, String)> {
|
|
73
|
+
let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
|
74
|
+
.map_err(|_| Error::Unavailable)?
|
|
75
|
+
.trim()
|
|
76
|
+
.to_string();
|
|
77
|
+
if boot.len() != 36 || !boot.bytes().all(|c| c.is_ascii_hexdigit() || c == b'-') {
|
|
78
|
+
return Err(Error::Unavailable);
|
|
79
|
+
}
|
|
80
|
+
let namespace =
|
|
81
|
+
std::fs::metadata("/proc/thread-self/ns/net").map_err(|_| Error::Unavailable)?;
|
|
82
|
+
Ok((
|
|
83
|
+
boot,
|
|
84
|
+
namespace.dev().to_string(),
|
|
85
|
+
namespace.ino().to_string(),
|
|
86
|
+
))
|
|
87
|
+
}
|
|
88
|
+
fn kernel_floor() -> Result<()> {
|
|
89
|
+
let mut name: libc::utsname = unsafe { std::mem::zeroed() };
|
|
90
|
+
if unsafe { libc::uname(&mut name) } != 0 {
|
|
91
|
+
return Err(Error::Unavailable);
|
|
92
|
+
}
|
|
93
|
+
let release = unsafe { std::ffi::CStr::from_ptr(name.release.as_ptr()) }
|
|
94
|
+
.to_str()
|
|
95
|
+
.map_err(|_| Error::Unsupported)?;
|
|
96
|
+
let mut parts = release.split('.');
|
|
97
|
+
let major: u32 = parts
|
|
98
|
+
.next()
|
|
99
|
+
.ok_or(Error::Unsupported)?
|
|
100
|
+
.parse()
|
|
101
|
+
.map_err(|_| Error::Unsupported)?;
|
|
102
|
+
let minor: u32 = parts
|
|
103
|
+
.next()
|
|
104
|
+
.ok_or(Error::Unsupported)?
|
|
105
|
+
.parse()
|
|
106
|
+
.map_err(|_| Error::Unsupported)?;
|
|
107
|
+
if major < 6 || major == 6 && minor < 9 {
|
|
108
|
+
return Err(Error::Unsupported);
|
|
109
|
+
}
|
|
110
|
+
Ok(())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl Owner {
|
|
114
|
+
pub fn new(options: Options) -> Result<Self> {
|
|
115
|
+
if !identifier(&options.owner_id)
|
|
116
|
+
|| !identifier(&options.instance_id)
|
|
117
|
+
|| options.instance_id.len() < 16
|
|
118
|
+
|| !identifier(&options.table_name)
|
|
119
|
+
|| !options.table_name.starts_with("snft_")
|
|
120
|
+
{
|
|
121
|
+
return Err(Error::Invalid);
|
|
122
|
+
}
|
|
123
|
+
let (boot_id, namespace_device, namespace_inode) = context()?;
|
|
124
|
+
Ok(Self {
|
|
125
|
+
identity: Identity {
|
|
126
|
+
owner_id: options.owner_id,
|
|
127
|
+
instance_id: options.instance_id,
|
|
128
|
+
table_name: options.table_name,
|
|
129
|
+
boot_id,
|
|
130
|
+
namespace_device,
|
|
131
|
+
namespace_inode,
|
|
132
|
+
},
|
|
133
|
+
socket: None,
|
|
134
|
+
applied: None,
|
|
135
|
+
pending: None,
|
|
136
|
+
error: None,
|
|
137
|
+
released: false,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
fn socket(&mut self) -> Result<&mut Socket> {
|
|
141
|
+
kernel_floor()?;
|
|
142
|
+
if context()?
|
|
143
|
+
!= (
|
|
144
|
+
self.identity.boot_id.clone(),
|
|
145
|
+
self.identity.namespace_device.clone(),
|
|
146
|
+
self.identity.namespace_inode.clone(),
|
|
147
|
+
)
|
|
148
|
+
{
|
|
149
|
+
return Err(Error::Conflict);
|
|
150
|
+
}
|
|
151
|
+
if self.socket.is_none() {
|
|
152
|
+
self.socket = Some(Socket::open()?);
|
|
153
|
+
}
|
|
154
|
+
Ok(self.socket.as_mut().unwrap())
|
|
155
|
+
}
|
|
156
|
+
fn userdata(&self) -> Vec<u8> {
|
|
157
|
+
format!(
|
|
158
|
+
"smartnftables1:{}:{}",
|
|
159
|
+
self.identity.owner_id, self.identity.instance_id
|
|
160
|
+
)
|
|
161
|
+
.into_bytes()
|
|
162
|
+
}
|
|
163
|
+
fn graph(&mut self) -> Result<Option<Graph>> {
|
|
164
|
+
let table_name = self.identity.table_name.clone();
|
|
165
|
+
let socket = self.socket()?;
|
|
166
|
+
for _ in 0..3 {
|
|
167
|
+
let generation = socket.generation()?;
|
|
168
|
+
let table = match socket.query(1, vec![Attr::string(1, &table_name)], false) {
|
|
169
|
+
Ok(mut rows) if rows.len() == 1 => rows.remove(0),
|
|
170
|
+
Err(Error::Absent) => return Ok(None),
|
|
171
|
+
Err(error) => return Err(error),
|
|
172
|
+
_ => return Err(Error::Protocol),
|
|
173
|
+
};
|
|
174
|
+
let chains = socket.query(4, vec![Attr::string(1, &table_name)], true)?;
|
|
175
|
+
let rules = socket.query(7, vec![Attr::string(1, &table_name)], true)?;
|
|
176
|
+
// The managed compiler never creates any of these. Foreign objects reject.
|
|
177
|
+
for operation in [10, 19, 23] {
|
|
178
|
+
if !socket
|
|
179
|
+
.query(operation, vec![Attr::string(1, &table_name)], true)?
|
|
180
|
+
.is_empty()
|
|
181
|
+
{
|
|
182
|
+
return Err(Error::Conflict);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if generation == socket.generation()? {
|
|
186
|
+
return Ok(Some(Graph {
|
|
187
|
+
generation,
|
|
188
|
+
table,
|
|
189
|
+
chains,
|
|
190
|
+
rules,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
Err(Error::Conflict)
|
|
195
|
+
}
|
|
196
|
+
fn binding(&self, graph: &Graph, expected: Option<&Applied>) -> Result<()> {
|
|
197
|
+
if wire::text(&graph.table, 1)? != self.identity.table_name
|
|
198
|
+
|| wire::one(&graph.table, 6)?.value != self.userdata()
|
|
199
|
+
|| ![4, 6].contains(&wire::number(&graph.table, 2)?)
|
|
200
|
+
|| graph
|
|
201
|
+
.table
|
|
202
|
+
.iter()
|
|
203
|
+
.any(|attr| ![1, 2, 3, 4, 5, 6, 7].contains(&attr.id()))
|
|
204
|
+
{
|
|
205
|
+
return Err(Error::Conflict);
|
|
206
|
+
}
|
|
207
|
+
if let Some(expected) = expected {
|
|
208
|
+
self.validate_receipt(expected)?;
|
|
209
|
+
if expected.receipt.table_handle != wire::handle(&graph.table, 4)?.to_string() {
|
|
210
|
+
return Err(Error::Conflict);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
Ok(())
|
|
214
|
+
}
|
|
215
|
+
fn validate_receipt(&self, expected: &Applied) -> Result<()> {
|
|
216
|
+
expected.prepared.validate()?;
|
|
217
|
+
let handle: u64 = expected
|
|
218
|
+
.receipt
|
|
219
|
+
.table_handle
|
|
220
|
+
.parse()
|
|
221
|
+
.map_err(|_| Error::Invalid)?;
|
|
222
|
+
if handle == 0
|
|
223
|
+
|| handle.to_string() != expected.receipt.table_handle
|
|
224
|
+
|| expected.receipt.identity != self.identity
|
|
225
|
+
|| expected.receipt.revision != expected.prepared.policy.revision
|
|
226
|
+
|| expected.receipt.digest != expected.prepared.digest
|
|
227
|
+
{
|
|
228
|
+
return Err(Error::Conflict);
|
|
229
|
+
}
|
|
230
|
+
Ok(())
|
|
231
|
+
}
|
|
232
|
+
fn matches(&self, graph: &Graph, prepared: &Prepared) -> Result<bool> {
|
|
233
|
+
let program = prepared.program(&self.identity.table_name)?;
|
|
234
|
+
let expected_chains: Vec<_> = program
|
|
235
|
+
.iter()
|
|
236
|
+
.filter(|(kind, _)| *kind == 3)
|
|
237
|
+
.map(|(_, attrs)| chain_identity(attrs))
|
|
238
|
+
.collect::<Result<_>>()?;
|
|
239
|
+
let actual_chains: Vec<_> = graph
|
|
240
|
+
.chains
|
|
241
|
+
.iter()
|
|
242
|
+
.map(|attrs| chain_identity(attrs))
|
|
243
|
+
.collect::<Result<_>>()?;
|
|
244
|
+
let mut expected_chains = expected_chains;
|
|
245
|
+
let mut actual_chains = actual_chains;
|
|
246
|
+
expected_chains.sort();
|
|
247
|
+
actual_chains.sort();
|
|
248
|
+
let expected_rules: Vec<_> = program
|
|
249
|
+
.iter()
|
|
250
|
+
.filter(|(kind, _)| *kind == 6)
|
|
251
|
+
.map(|(_, attrs)| rule_identity(attrs))
|
|
252
|
+
.collect::<Result<_>>()?;
|
|
253
|
+
let actual_rules: Vec<_> = graph
|
|
254
|
+
.rules
|
|
255
|
+
.iter()
|
|
256
|
+
.map(|attrs| rule_identity(attrs))
|
|
257
|
+
.collect::<Result<_>>()?;
|
|
258
|
+
// Dumps order chains differently; preserve order within each chain.
|
|
259
|
+
let regroup = |rules: Vec<(String, Vec<u8>)>| {
|
|
260
|
+
let mut result = std::collections::BTreeMap::<String, Vec<Vec<u8>>>::new();
|
|
261
|
+
for (chain, rule) in rules {
|
|
262
|
+
result.entry(chain).or_default().push(rule);
|
|
263
|
+
}
|
|
264
|
+
result
|
|
265
|
+
};
|
|
266
|
+
Ok(expected_chains == actual_chains && regroup(expected_rules) == regroup(actual_rules))
|
|
267
|
+
}
|
|
268
|
+
fn adopt(&mut self, graph: Graph, transition: &Transition) -> Result<Graph> {
|
|
269
|
+
self.binding(&graph, transition.previous.as_ref())?;
|
|
270
|
+
if !self.matches(&graph, &transition.target)?
|
|
271
|
+
&& !transition
|
|
272
|
+
.previous
|
|
273
|
+
.as_ref()
|
|
274
|
+
.map(|prior| self.matches(&graph, &prior.prepared))
|
|
275
|
+
.transpose()?
|
|
276
|
+
.unwrap_or(false)
|
|
277
|
+
{
|
|
278
|
+
return Err(Error::Conflict);
|
|
279
|
+
}
|
|
280
|
+
let port = self.socket()?.port;
|
|
281
|
+
if wire::number(&graph.table, 2)? == 6 {
|
|
282
|
+
if wire::number(&graph.table, 7)? != port {
|
|
283
|
+
return Err(Error::ForeignOwner);
|
|
284
|
+
}
|
|
285
|
+
return Ok(graph);
|
|
286
|
+
}
|
|
287
|
+
let handle = wire::handle(&graph.table, 4)?;
|
|
288
|
+
let table_name = self.identity.table_name.clone();
|
|
289
|
+
self.socket()?.batch(
|
|
290
|
+
graph.generation,
|
|
291
|
+
vec![(0, 0, vec![Attr::string(1, &table_name), Attr::u32(2, 6)])],
|
|
292
|
+
)?;
|
|
293
|
+
// Postclaim proof is essential: generation IDs wrap. Now other sockets
|
|
294
|
+
// cannot mutate this graph between verification and the following batch.
|
|
295
|
+
let after = self.graph()?.ok_or(Error::Conflict)?;
|
|
296
|
+
self.binding(&after, transition.previous.as_ref())?;
|
|
297
|
+
if wire::handle(&after.table, 4)? != handle
|
|
298
|
+
|| wire::number(&after.table, 2)? != 6
|
|
299
|
+
|| wire::number(&after.table, 7)? != port
|
|
300
|
+
|| !self.matches(&after, &transition.target)?
|
|
301
|
+
&& !transition
|
|
302
|
+
.previous
|
|
303
|
+
.as_ref()
|
|
304
|
+
.map(|prior| self.matches(&after, &prior.prepared))
|
|
305
|
+
.transpose()?
|
|
306
|
+
.unwrap_or(false)
|
|
307
|
+
{
|
|
308
|
+
// Only drop this socket's acquired ownership, never the persistent graph.
|
|
309
|
+
self.socket = None;
|
|
310
|
+
return Err(Error::Conflict);
|
|
311
|
+
}
|
|
312
|
+
Ok(after)
|
|
313
|
+
}
|
|
314
|
+
fn applied(&self, graph: &Graph, prepared: Prepared) -> Result<Applied> {
|
|
315
|
+
Ok(Applied {
|
|
316
|
+
receipt: Receipt {
|
|
317
|
+
identity: self.identity.clone(),
|
|
318
|
+
table_handle: wire::handle(&graph.table, 4)?.to_string(),
|
|
319
|
+
revision: prepared.policy.revision,
|
|
320
|
+
digest: prepared.digest.clone(),
|
|
321
|
+
},
|
|
322
|
+
prepared,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
pub fn reconcile(&mut self, transition: Transition) -> Result<Applied> {
|
|
326
|
+
if self.released {
|
|
327
|
+
return Err(Error::Conflict);
|
|
328
|
+
}
|
|
329
|
+
transition.target.validate()?;
|
|
330
|
+
if let Some(pending) = &self.pending {
|
|
331
|
+
if pending != &transition {
|
|
332
|
+
return Err(Error::Conflict);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if let Some(previous) = &transition.previous {
|
|
336
|
+
self.validate_receipt(previous)?;
|
|
337
|
+
if transition.target.policy.revision <= previous.prepared.policy.revision {
|
|
338
|
+
return Err(Error::Conflict);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Reject known unsupported privilege/kernel states before admitting a target.
|
|
342
|
+
self.socket()?.generation()?;
|
|
343
|
+
transition.target.validate_interfaces()?;
|
|
344
|
+
self.pending = Some(transition.clone());
|
|
345
|
+
let result = self.reconcile_inner(&transition);
|
|
346
|
+
match &result {
|
|
347
|
+
Ok(applied) => {
|
|
348
|
+
self.applied = Some(applied.clone());
|
|
349
|
+
self.pending = None;
|
|
350
|
+
self.error = None;
|
|
351
|
+
}
|
|
352
|
+
Err(error) => self.error = Some(*error),
|
|
353
|
+
}
|
|
354
|
+
result
|
|
355
|
+
}
|
|
356
|
+
fn reconcile_inner(&mut self, transition: &Transition) -> Result<Applied> {
|
|
357
|
+
let table_name = self.identity.table_name.clone();
|
|
358
|
+
let graph = self.graph()?;
|
|
359
|
+
let mut operations = Vec::new();
|
|
360
|
+
let generation = if let Some(graph) = graph {
|
|
361
|
+
let graph = self.adopt(graph, transition)?;
|
|
362
|
+
if self.matches(&graph, &transition.target)? {
|
|
363
|
+
transition.target.validate_interfaces()?;
|
|
364
|
+
return self.applied(&graph, transition.target.clone());
|
|
365
|
+
}
|
|
366
|
+
let previous = transition.previous.as_ref().ok_or(Error::Conflict)?;
|
|
367
|
+
if !self.matches(&graph, &previous.prepared)? {
|
|
368
|
+
return Err(Error::Conflict);
|
|
369
|
+
}
|
|
370
|
+
for rule in &graph.rules {
|
|
371
|
+
operations.push((
|
|
372
|
+
8,
|
|
373
|
+
0,
|
|
374
|
+
vec![
|
|
375
|
+
Attr::string(1, &table_name),
|
|
376
|
+
Attr::string(2, &wire::text(rule, 2)?),
|
|
377
|
+
Attr::u64(3, wire::handle(rule, 3)?),
|
|
378
|
+
],
|
|
379
|
+
));
|
|
380
|
+
}
|
|
381
|
+
for chain in &graph.chains {
|
|
382
|
+
operations.push((
|
|
383
|
+
5,
|
|
384
|
+
0,
|
|
385
|
+
vec![
|
|
386
|
+
Attr::string(1, &table_name),
|
|
387
|
+
Attr::u64(2, wire::handle(chain, 2)?),
|
|
388
|
+
],
|
|
389
|
+
));
|
|
390
|
+
}
|
|
391
|
+
graph.generation
|
|
392
|
+
} else {
|
|
393
|
+
if transition.previous.is_some() || self.applied.is_some() {
|
|
394
|
+
return Err(Error::Conflict);
|
|
395
|
+
}
|
|
396
|
+
operations.push((
|
|
397
|
+
0,
|
|
398
|
+
0x600,
|
|
399
|
+
vec![
|
|
400
|
+
Attr::string(1, &table_name),
|
|
401
|
+
Attr::u32(2, 6),
|
|
402
|
+
Attr::bytes(6, self.userdata()),
|
|
403
|
+
],
|
|
404
|
+
));
|
|
405
|
+
self.socket()?.generation()?
|
|
406
|
+
};
|
|
407
|
+
for (operation, attributes) in transition.target.program(&table_name)? {
|
|
408
|
+
operations.push((
|
|
409
|
+
operation,
|
|
410
|
+
if operation == 3 { 0x600 } else { 0xc00 },
|
|
411
|
+
attributes,
|
|
412
|
+
));
|
|
413
|
+
}
|
|
414
|
+
self.socket()?.batch(generation, operations)?;
|
|
415
|
+
let graph = self.graph()?.ok_or(Error::Conflict)?;
|
|
416
|
+
self.binding(&graph, transition.previous.as_ref())?;
|
|
417
|
+
if wire::number(&graph.table, 2)? != 6
|
|
418
|
+
|| wire::number(&graph.table, 7)? != self.socket()?.port
|
|
419
|
+
|| !self.matches(&graph, &transition.target)?
|
|
420
|
+
{
|
|
421
|
+
return Err(Error::Conflict);
|
|
422
|
+
}
|
|
423
|
+
transition.target.validate_interfaces()?;
|
|
424
|
+
self.applied(&graph, transition.target.clone())
|
|
425
|
+
}
|
|
426
|
+
pub fn inspect(&mut self) -> Status {
|
|
427
|
+
let mut enforced = false;
|
|
428
|
+
if let Some(applied) = self.applied.clone().filter(|_| !self.released) {
|
|
429
|
+
match self.graph().and_then(|graph| {
|
|
430
|
+
let graph = graph.ok_or(Error::Conflict)?;
|
|
431
|
+
self.binding(&graph, Some(&applied))?;
|
|
432
|
+
if wire::number(&graph.table, 2)? != 6
|
|
433
|
+
|| wire::number(&graph.table, 7)? != self.socket()?.port
|
|
434
|
+
|| !self.matches(&graph, &applied.prepared)?
|
|
435
|
+
{
|
|
436
|
+
return Err(Error::Conflict);
|
|
437
|
+
}
|
|
438
|
+
applied.prepared.validate_interfaces()?;
|
|
439
|
+
Ok(())
|
|
440
|
+
}) {
|
|
441
|
+
Ok(()) => enforced = self.pending.is_none() && self.error.is_none(),
|
|
442
|
+
Err(error) => self.error = Some(error),
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
Status {
|
|
446
|
+
identity: self.identity.clone(),
|
|
447
|
+
state: if self.released {
|
|
448
|
+
"released"
|
|
449
|
+
} else if self.error.is_some() || self.pending.is_some() {
|
|
450
|
+
"failed-owned"
|
|
451
|
+
} else if enforced {
|
|
452
|
+
"applied"
|
|
453
|
+
} else {
|
|
454
|
+
"idle"
|
|
455
|
+
}
|
|
456
|
+
.into(),
|
|
457
|
+
enforced,
|
|
458
|
+
applied: self.applied.clone(),
|
|
459
|
+
pending: self.pending.clone(),
|
|
460
|
+
error: self.error.map(|error| error.code().into()),
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
pub fn release(&mut self, expected: Applied) -> Result<()> {
|
|
464
|
+
self.validate_receipt(&expected)?;
|
|
465
|
+
if self.pending.is_some() {
|
|
466
|
+
return Err(Error::Conflict);
|
|
467
|
+
}
|
|
468
|
+
if self.released {
|
|
469
|
+
return if self.applied.as_ref() == Some(&expected) {
|
|
470
|
+
Ok(())
|
|
471
|
+
} else {
|
|
472
|
+
Err(Error::Conflict)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
let result = (|| {
|
|
476
|
+
let Some(graph) = self.graph()? else {
|
|
477
|
+
// The exact caller-retained boot/namespace receipt is validated.
|
|
478
|
+
// Absence also confirms cleanup after a lost ACK and full restart.
|
|
479
|
+
return Ok(());
|
|
480
|
+
};
|
|
481
|
+
// Recovery on a fresh owner is explicit and validates this exact snapshot.
|
|
482
|
+
let transition = Transition {
|
|
483
|
+
previous: Some(expected.clone()),
|
|
484
|
+
target: expected.prepared.clone(),
|
|
485
|
+
};
|
|
486
|
+
let graph = self.adopt(graph, &transition)?;
|
|
487
|
+
if !self.matches(&graph, &expected.prepared)? {
|
|
488
|
+
return Err(Error::Conflict);
|
|
489
|
+
}
|
|
490
|
+
// Retain qualified ownership before deletion; a lost delete ACK can
|
|
491
|
+
// subsequently prove this exact table absent without inventing success.
|
|
492
|
+
self.applied = Some(expected.clone());
|
|
493
|
+
self.socket()?.batch(
|
|
494
|
+
graph.generation,
|
|
495
|
+
vec![(2, 0, vec![Attr::u64(4, wire::handle(&graph.table, 4)?)])],
|
|
496
|
+
)?;
|
|
497
|
+
if let Some(after) = self.graph()? {
|
|
498
|
+
if wire::handle(&after.table, 4)? == wire::handle(&graph.table, 4)? {
|
|
499
|
+
return Err(Error::Conflict);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
Ok(())
|
|
503
|
+
})();
|
|
504
|
+
match result {
|
|
505
|
+
Ok(()) => {
|
|
506
|
+
self.released = true;
|
|
507
|
+
self.applied = Some(expected);
|
|
508
|
+
self.error = None;
|
|
509
|
+
self.socket = None;
|
|
510
|
+
Ok(())
|
|
511
|
+
}
|
|
512
|
+
Err(error) => {
|
|
513
|
+
self.error = Some(error);
|
|
514
|
+
Err(error)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
pub fn close(&mut self) -> Result<()> {
|
|
519
|
+
if self.released {
|
|
520
|
+
return Ok(());
|
|
521
|
+
}
|
|
522
|
+
if let Some(applied) = self.applied.clone() {
|
|
523
|
+
return self.release(applied);
|
|
524
|
+
}
|
|
525
|
+
if self.socket.is_some()
|
|
526
|
+
&& (self.pending.is_some() || self.error.is_some())
|
|
527
|
+
&& self.graph()?.is_some()
|
|
528
|
+
{
|
|
529
|
+
return Err(Error::Conflict);
|
|
530
|
+
}
|
|
531
|
+
self.pending = None;
|
|
532
|
+
self.error = None;
|
|
533
|
+
self.socket = None;
|
|
534
|
+
self.released = true;
|
|
535
|
+
Ok(())
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
fn chain_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
540
|
+
let mut identity = Vec::new();
|
|
541
|
+
for attribute in attributes {
|
|
542
|
+
match attribute.id() {
|
|
543
|
+
2 | 6 | 9 => {} // Kernel handle, use count and alignment padding.
|
|
544
|
+
4 => identity.push(Attr::nested(4, wire::attrs(&attribute.value)?)),
|
|
545
|
+
1 | 3 | 5 | 7 | 10 => identity.push(attribute.clone()),
|
|
546
|
+
_ => return Err(Error::Conflict),
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
serde_json::to_vec(&wire::canonical(&identity, false)?).map_err(|_| Error::Protocol)
|
|
550
|
+
}
|
|
551
|
+
fn rule_identity(attributes: &[Attr]) -> Result<(String, Vec<u8>)> {
|
|
552
|
+
let chain = wire::text(attributes, 2)?;
|
|
553
|
+
if attributes
|
|
554
|
+
.iter()
|
|
555
|
+
.any(|attribute| ![1, 2, 3, 4, 6, 7, 8].contains(&attribute.id()))
|
|
556
|
+
{
|
|
557
|
+
return Err(Error::Conflict);
|
|
558
|
+
}
|
|
559
|
+
let expressions = wire::attrs(&wire::one(attributes, 4)?.value)?;
|
|
560
|
+
let mut result = Vec::new();
|
|
561
|
+
for expression in expressions {
|
|
562
|
+
if expression.id() != 1 {
|
|
563
|
+
return Err(Error::Conflict);
|
|
564
|
+
}
|
|
565
|
+
let fields = wire::attrs(&expression.value)?;
|
|
566
|
+
if fields.len() != 2 {
|
|
567
|
+
return Err(Error::Conflict);
|
|
568
|
+
}
|
|
569
|
+
let name = wire::text(&fields, 1)?;
|
|
570
|
+
let mut data = wire::attrs(&wire::one(&fields, 2)?.value)?;
|
|
571
|
+
let nested: &[u16] = match name.as_str() {
|
|
572
|
+
"immediate" => &[2],
|
|
573
|
+
"cmp" => &[3],
|
|
574
|
+
"bitwise" => &[4, 5],
|
|
575
|
+
"meta" | "payload" => &[],
|
|
576
|
+
_ => return Err(Error::Conflict),
|
|
577
|
+
};
|
|
578
|
+
for attribute in &mut data {
|
|
579
|
+
if nested.contains(&attribute.id()) {
|
|
580
|
+
let mut inner = wire::attrs(&attribute.value)?;
|
|
581
|
+
if name == "immediate" {
|
|
582
|
+
for entry in &mut inner {
|
|
583
|
+
if entry.id() == 2 {
|
|
584
|
+
entry.kind |= 0x8000;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
*attribute = Attr::nested(attribute.id(), inner);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
result.push((name, wire::canonical(&data, false)?));
|
|
592
|
+
}
|
|
593
|
+
let encoded = serde_json::to_vec(&(
|
|
594
|
+
wire::text(attributes, 1)?,
|
|
595
|
+
result,
|
|
596
|
+
&wire::one(attributes, 7)?.value,
|
|
597
|
+
))
|
|
598
|
+
.map_err(|_| Error::Protocol)?;
|
|
599
|
+
Ok((chain, encoded))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
#[cfg(test)]
|
|
603
|
+
#[path = "owner_tests.rs"]
|
|
604
|
+
mod native_tests;
|