@push.rocks/smartnftables 1.3.1 → 1.5.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 +24 -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 +7 -7
- package/dist_ts/classes.managednftables.js +9 -3
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +1 -1
- package/dist_ts/managed.egress.types.d.ts +91 -0
- package/dist_ts/managed.egress.types.js +2 -0
- package/dist_ts/managed.types.d.ts +25 -19
- package/package.json +5 -5
- package/readme.md +97 -3
- package/rust/src/egress.compile.rs +288 -0
- package/rust/src/egress.host.rs +103 -0
- package/rust/src/egress.router.rs +231 -0
- package/rust/src/egress.rs +682 -0
- package/rust/src/egress_tests.rs +332 -0
- package/rust/src/main.rs +23 -3
- package/rust/src/managed.rs +105 -0
- package/rust/src/namespace.rs +51 -0
- package/rust/src/owner.rs +96 -6
- package/rust/src/owner_coexistence_tests.rs +77 -0
- package/rust/src/owner_egress_identity_tests.rs +155 -0
- package/rust/src/owner_egress_tests.rs +153 -0
- package/rust/src/owner_egress_traffic_tests.rs +470 -0
- package/rust/src/owner_host_traffic_tests.rs +305 -0
- package/rust/src/owner_identity_tests.rs +169 -0
- package/rust/src/owner_link_tests.rs +119 -0
- package/rust/src/owner_namespace_tests.rs +242 -0
- package/rust/src/owner_packet_fixture.rs +200 -0
- package/rust/src/owner_tests.rs +36 -7
- package/rust/src/policy.rs +120 -81
- package/rust/src/tests.rs +22 -0
- package/rust/src/wire.links.rs +268 -0
- package/rust/src/wire.rs +3 -64
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +19 -14
- package/ts/index.ts +1 -0
- package/ts/managed.egress.types.ts +85 -0
- package/ts/managed.types.ts +24 -16
package/rust/src/policy.rs
CHANGED
|
@@ -210,10 +210,10 @@ impl Policy {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
fn expr(name: &str, data: Vec<Attr>) -> Attr {
|
|
213
|
+
pub(crate) fn expr(name: &str, data: Vec<Attr>) -> Attr {
|
|
214
214
|
Attr::nested(1, vec![Attr::string(1, name), Attr::nested(2, data)])
|
|
215
215
|
}
|
|
216
|
-
fn compare(value: Vec<u8>) -> Attr {
|
|
216
|
+
pub(crate) fn compare(value: Vec<u8>) -> Attr {
|
|
217
217
|
expr(
|
|
218
218
|
"cmp",
|
|
219
219
|
vec![
|
|
@@ -223,7 +223,7 @@ fn compare(value: Vec<u8>) -> Attr {
|
|
|
223
223
|
],
|
|
224
224
|
)
|
|
225
225
|
}
|
|
226
|
-
fn meta(key: u32, value: Vec<u8>) -> Vec<Attr> {
|
|
226
|
+
pub(crate) fn meta(key: u32, value: Vec<u8>) -> Vec<Attr> {
|
|
227
227
|
vec![
|
|
228
228
|
expr("meta", vec![Attr::u32(1, 1), Attr::u32(2, key)]),
|
|
229
229
|
compare(value),
|
|
@@ -239,7 +239,7 @@ fn interface(endpoint: &Endpoint, incoming: bool) -> Vec<Attr> {
|
|
|
239
239
|
result.extend(meta(if incoming { 6 } else { 7 }, name));
|
|
240
240
|
result
|
|
241
241
|
}
|
|
242
|
-
fn ipv4() -> Vec<Attr> {
|
|
242
|
+
pub(crate) fn ipv4() -> Vec<Attr> {
|
|
243
243
|
meta(15, vec![libc::NFPROTO_IPV4 as u8])
|
|
244
244
|
}
|
|
245
245
|
fn address(cidr: &str, source: bool) -> Result<Vec<Attr>> {
|
|
@@ -268,7 +268,7 @@ fn address(cidr: &str, source: bool) -> Result<Vec<Attr>> {
|
|
|
268
268
|
compare(address.to_be_bytes().to_vec()),
|
|
269
269
|
])
|
|
270
270
|
}
|
|
271
|
-
fn verdict(code: i32, chain: Option<&str>) -> Attr {
|
|
271
|
+
pub(crate) fn verdict(code: i32, chain: Option<&str>) -> Attr {
|
|
272
272
|
let mut data = vec![Attr::u32(1, code as u32)];
|
|
273
273
|
if let Some(chain) = chain {
|
|
274
274
|
data.push(Attr::string(2, chain));
|
|
@@ -329,99 +329,138 @@ impl Prepared {
|
|
|
329
329
|
],
|
|
330
330
|
));
|
|
331
331
|
}
|
|
332
|
+
for (name, expressions) in self.guard_chains()? {
|
|
333
|
+
program.push((3, vec![Attr::string(1, table), Attr::string(3, &name)]));
|
|
334
|
+
for expressions in expressions {
|
|
335
|
+
rule(&name, expressions, &mut program);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for chain in ["input", "forward", "output"] {
|
|
339
|
+
let parts = self.filter_rules(chain)?;
|
|
340
|
+
for expressions in parts
|
|
341
|
+
.guards
|
|
342
|
+
.into_iter()
|
|
343
|
+
.chain(parts.grants)
|
|
344
|
+
.chain(parts.denials)
|
|
345
|
+
{
|
|
346
|
+
rule(chain, expressions, &mut program);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
Ok(program)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/// Reused inside a combined router graph; order remains part of schema-v1's bytes.
|
|
354
|
+
pub(crate) struct PrivateRules {
|
|
355
|
+
pub guards: Vec<Vec<Attr>>,
|
|
356
|
+
pub grants: Vec<Vec<Attr>>,
|
|
357
|
+
pub denials: Vec<Vec<Attr>>,
|
|
358
|
+
}
|
|
359
|
+
impl Prepared {
|
|
360
|
+
pub(crate) fn guard_chains(&self) -> Result<Vec<(String, Vec<Vec<Attr>>)>> {
|
|
361
|
+
let mut result = Vec::new();
|
|
332
362
|
for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
|
|
333
363
|
for incoming in [true, false] {
|
|
334
364
|
let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
|
|
335
|
-
|
|
365
|
+
let mut rules = Vec::new();
|
|
336
366
|
for cidr in &endpoint.source_prefixes {
|
|
337
367
|
let mut expressions = ipv4();
|
|
338
368
|
expressions.extend(address(cidr, incoming)?);
|
|
339
369
|
expressions.push(verdict(-5, None));
|
|
340
|
-
|
|
370
|
+
rules.push(expressions);
|
|
341
371
|
}
|
|
342
|
-
|
|
372
|
+
rules.push(vec![verdict(0, None)]);
|
|
373
|
+
result.push((name, rules));
|
|
343
374
|
}
|
|
344
375
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
for
|
|
358
|
-
if chain == "input" &&
|
|
359
|
-
|| chain == "output" && grant.source_endpoint.is_some()
|
|
360
|
-
|| chain == "forward"
|
|
361
|
-
&& (grant.source_endpoint.is_none() || grant.destination_endpoint.is_none())
|
|
362
|
-
{
|
|
376
|
+
Ok(result)
|
|
377
|
+
}
|
|
378
|
+
pub(crate) fn filter_rules(&self, chain: &str) -> Result<PrivateRules> {
|
|
379
|
+
if !["input", "forward", "output"].contains(&chain) {
|
|
380
|
+
return Err(Error::Invalid);
|
|
381
|
+
}
|
|
382
|
+
let mut result = PrivateRules {
|
|
383
|
+
guards: Vec::new(),
|
|
384
|
+
grants: Vec::new(),
|
|
385
|
+
denials: Vec::new(),
|
|
386
|
+
};
|
|
387
|
+
for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
|
|
388
|
+
for incoming in [true, false] {
|
|
389
|
+
if (chain == "input" && !incoming) || (chain == "output" && incoming) {
|
|
363
390
|
continue;
|
|
364
391
|
}
|
|
365
|
-
let
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
392
|
+
let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
|
|
393
|
+
let mut expressions = interface(endpoint, incoming);
|
|
394
|
+
expressions.push(verdict(-3, Some(&name)));
|
|
395
|
+
result.guards.push(expressions);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
for grant in &self.policy.rules {
|
|
399
|
+
if chain == "input" && grant.destination_endpoint.is_some()
|
|
400
|
+
|| chain == "output" && grant.source_endpoint.is_some()
|
|
401
|
+
|| chain == "forward"
|
|
402
|
+
&& (grant.source_endpoint.is_none() || grant.destination_endpoint.is_none())
|
|
403
|
+
{
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
let mut expressions = ipv4();
|
|
407
|
+
for (id, incoming) in [
|
|
408
|
+
(&grant.source_endpoint, true),
|
|
409
|
+
(&grant.destination_endpoint, false),
|
|
410
|
+
] {
|
|
411
|
+
if let Some(id) = id {
|
|
412
|
+
let endpoint = self
|
|
413
|
+
.policy
|
|
414
|
+
.endpoints
|
|
415
|
+
.iter()
|
|
416
|
+
.find(|ep| &ep.id == id)
|
|
417
|
+
.ok_or(Error::Invalid)?;
|
|
418
|
+
expressions.extend(interface(endpoint, incoming));
|
|
385
419
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
420
|
+
}
|
|
421
|
+
expressions.extend(address(&grant.source_prefix, true)?);
|
|
422
|
+
expressions.extend(address(&grant.destination_prefix, false)?);
|
|
423
|
+
if grant.protocol != "any" {
|
|
424
|
+
expressions.extend(meta(16, vec![if grant.protocol == "tcp" { 6 } else { 17 }]));
|
|
425
|
+
}
|
|
426
|
+
for (port, offset) in [(grant.source_port, 0), (grant.destination_port, 2)] {
|
|
427
|
+
if let Some(port) = port {
|
|
428
|
+
expressions.push(expr(
|
|
429
|
+
"payload",
|
|
430
|
+
vec![
|
|
431
|
+
Attr::u32(1, 1),
|
|
432
|
+
Attr::u32(2, 2),
|
|
433
|
+
Attr::u32(3, offset),
|
|
434
|
+
Attr::u32(4, 2),
|
|
435
|
+
],
|
|
436
|
+
));
|
|
437
|
+
expressions.push(compare(port.to_be_bytes().to_vec()));
|
|
399
438
|
}
|
|
400
|
-
expressions.push(verdict(1, None));
|
|
401
|
-
rule(chain, expressions, &mut program);
|
|
402
439
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
440
|
+
expressions.push(verdict(1, None));
|
|
441
|
+
result.grants.push(expressions);
|
|
442
|
+
}
|
|
443
|
+
for endpoint in &self.policy.endpoints {
|
|
444
|
+
for incoming in [true, false] {
|
|
445
|
+
if (chain == "input" && !incoming) || (chain == "output" && incoming) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
// Either selector matching is enough to retain denial after rename/reuse.
|
|
449
|
+
for mut expressions in [
|
|
450
|
+
meta(
|
|
451
|
+
if incoming { 4 } else { 5 },
|
|
452
|
+
endpoint.interface_index.to_ne_bytes().to_vec(),
|
|
453
|
+
),
|
|
454
|
+
meta(
|
|
455
|
+
if incoming { 6 } else { 7 },
|
|
456
|
+
[endpoint.interface_name.as_bytes(), &[0]].concat(),
|
|
457
|
+
),
|
|
458
|
+
] {
|
|
459
|
+
expressions.push(verdict(0, None));
|
|
460
|
+
result.denials.push(expressions);
|
|
422
461
|
}
|
|
423
462
|
}
|
|
424
463
|
}
|
|
425
|
-
Ok(
|
|
464
|
+
Ok(result)
|
|
426
465
|
}
|
|
427
466
|
}
|
package/rust/src/tests.rs
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
use crate::{decode, policy::Policy, wire, Error};
|
|
2
|
+
use sha2::{Digest, Sha256};
|
|
2
3
|
|
|
3
4
|
fn policy() -> Policy {
|
|
4
5
|
serde_json::from_str(r#"{"schemaVersion":1,"revision":1,"endpoints":[{"id":"a","interfaceIndex":2,"interfaceName":"worka","interfaceKind":"veth","sourcePrefixes":["10.81.0.2/32"]},{"id":"b","interfaceIndex":3,"interfaceName":"workb","interfaceKind":"veth","sourcePrefixes":["10.81.0.3/32"]}],"rules":[{"sourceEndpoint":"a","destinationEndpoint":"b","sourcePrefix":"10.81.0.2/32","destinationPrefix":"10.81.0.3/32","protocol":"udp","sourcePort":null,"destinationPort":53}]}"#).unwrap()
|
|
5
6
|
}
|
|
6
7
|
|
|
8
|
+
#[test]
|
|
9
|
+
fn v1_policy_and_program_bytes_remain_frozen() {
|
|
10
|
+
let prepared = policy().prepare().unwrap();
|
|
11
|
+
let mut bytes = Vec::new();
|
|
12
|
+
for (kind, attributes) in prepared.program("snft_v1_golden").unwrap() {
|
|
13
|
+
let encoded = wire::encode_attrs(&attributes);
|
|
14
|
+
bytes.extend(kind.to_be_bytes());
|
|
15
|
+
bytes.extend((encoded.len() as u32).to_be_bytes());
|
|
16
|
+
bytes.extend(encoded);
|
|
17
|
+
}
|
|
18
|
+
assert_eq!(
|
|
19
|
+
prepared.digest,
|
|
20
|
+
"sha256:13897f4e711db2111618c61eb3f0be00714ed79f2075bf7f75e16f93f26ce2db"
|
|
21
|
+
);
|
|
22
|
+
assert_eq!(bytes.len(), 11020);
|
|
23
|
+
assert_eq!(
|
|
24
|
+
format!("{:x}", Sha256::digest(bytes)),
|
|
25
|
+
"e57215884bca4bc8e0d7bef4c975cc3a1cc9e9233e44c248502a9efd99fa5c34"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
7
29
|
#[test]
|
|
8
30
|
fn canonical_policy_rejects_ambiguous_or_unbound_grants() {
|
|
9
31
|
let prepared = policy().prepare().unwrap();
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
use super::{attrs, one, text, Attr, Socket};
|
|
2
|
+
use crate::{Error, Result};
|
|
3
|
+
use std::collections::BTreeSet;
|
|
4
|
+
use std::net::Ipv4Addr;
|
|
5
|
+
use std::time::{Duration, Instant};
|
|
6
|
+
|
|
7
|
+
struct Link {
|
|
8
|
+
hardware: u16,
|
|
9
|
+
flags: u32,
|
|
10
|
+
attributes: Vec<Attr>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Current local facts only. Peer namespaces and link-generation lifetimes remain caller-owned.
|
|
14
|
+
#[derive(Clone, Copy)]
|
|
15
|
+
pub struct BoundInterface<'a> {
|
|
16
|
+
pub index: u32,
|
|
17
|
+
pub name: &'a str,
|
|
18
|
+
pub kind: &'a str,
|
|
19
|
+
pub mac: Option<&'a str>,
|
|
20
|
+
pub link_index: u32,
|
|
21
|
+
pub addresses: &'a [String],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn read_link(socket: &mut Socket, index: u32) -> Result<Link> {
|
|
25
|
+
let sequence = socket.sequence()?;
|
|
26
|
+
let mut request = Vec::new();
|
|
27
|
+
request.extend(32_u32.to_ne_bytes());
|
|
28
|
+
request.extend(18_u16.to_ne_bytes());
|
|
29
|
+
request.extend(1_u16.to_ne_bytes());
|
|
30
|
+
request.extend(sequence.to_ne_bytes());
|
|
31
|
+
request.extend(socket.port.to_ne_bytes());
|
|
32
|
+
request.extend([0; 4]);
|
|
33
|
+
request.extend(index.to_ne_bytes());
|
|
34
|
+
request.extend([0; 8]);
|
|
35
|
+
socket.send(&request)?;
|
|
36
|
+
let until = Instant::now() + Duration::from_secs(3);
|
|
37
|
+
loop {
|
|
38
|
+
for message in socket.receive(until)? {
|
|
39
|
+
if message.sequence != sequence {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if message.kind == 2 {
|
|
43
|
+
let error = i32::from_ne_bytes(
|
|
44
|
+
message
|
|
45
|
+
.payload
|
|
46
|
+
.get(..4)
|
|
47
|
+
.ok_or(Error::Protocol)?
|
|
48
|
+
.try_into()
|
|
49
|
+
.unwrap(),
|
|
50
|
+
);
|
|
51
|
+
if error != 0 {
|
|
52
|
+
return Err(Error::kernel(error));
|
|
53
|
+
}
|
|
54
|
+
} else if message.kind == 16 && message.payload.len() >= 16 {
|
|
55
|
+
if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
|
|
56
|
+
return Err(Error::Conflict);
|
|
57
|
+
}
|
|
58
|
+
return Ok(Link {
|
|
59
|
+
hardware: u16::from_ne_bytes(message.payload[2..4].try_into().unwrap()),
|
|
60
|
+
flags: u32::from_ne_bytes(message.payload[8..12].try_into().unwrap()),
|
|
61
|
+
attributes: attrs(&message.payload[16..])?,
|
|
62
|
+
});
|
|
63
|
+
} else {
|
|
64
|
+
return Err(Error::Protocol);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
fn unbridged(attributes: &[Attr]) -> Result<()> {
|
|
70
|
+
if attributes.iter().any(|attr| attr.id() == 10)
|
|
71
|
+
&& one(attributes, 10)?.value != 0_u32.to_ne_bytes()
|
|
72
|
+
{
|
|
73
|
+
return Err(Error::Conflict);
|
|
74
|
+
}
|
|
75
|
+
Ok(())
|
|
76
|
+
}
|
|
77
|
+
fn virtual_kind(attributes: &[Attr], kind: &str) -> Result<()> {
|
|
78
|
+
let info = attrs(&one(attributes, 18)?.value)?;
|
|
79
|
+
if text(&info, 1)? != kind {
|
|
80
|
+
return Err(Error::Conflict);
|
|
81
|
+
}
|
|
82
|
+
if kind == "tun" && one(&attrs(&one(&info, 2)?.value)?, 3)?.value != [1] {
|
|
83
|
+
return Err(Error::Conflict);
|
|
84
|
+
}
|
|
85
|
+
Ok(())
|
|
86
|
+
}
|
|
87
|
+
/// Preserve schema-v1's local name/index/kind/master checks exactly.
|
|
88
|
+
pub fn verify_interfaces<'a>(
|
|
89
|
+
interfaces: impl Iterator<Item = (u32, &'a str, &'a str)>,
|
|
90
|
+
) -> Result<()> {
|
|
91
|
+
let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
|
|
92
|
+
for (index, name, kind) in interfaces {
|
|
93
|
+
let link = read_link(&mut socket, index)?;
|
|
94
|
+
if text(&link.attributes, 3)? != name {
|
|
95
|
+
return Err(Error::Conflict);
|
|
96
|
+
}
|
|
97
|
+
unbridged(&link.attributes)?;
|
|
98
|
+
virtual_kind(&link.attributes, kind)?;
|
|
99
|
+
}
|
|
100
|
+
Ok(())
|
|
101
|
+
}
|
|
102
|
+
fn matches(link: &Link, binding: &BoundInterface<'_>) -> Result<()> {
|
|
103
|
+
if text(&link.attributes, 3)? != binding.name || link.flags & libc::IFF_LOOPBACK as u32 != 0 {
|
|
104
|
+
return Err(Error::Conflict);
|
|
105
|
+
}
|
|
106
|
+
unbridged(&link.attributes)?;
|
|
107
|
+
match binding.kind {
|
|
108
|
+
"veth" | "tun" => virtual_kind(&link.attributes, binding.kind)?,
|
|
109
|
+
"ethernet" => {
|
|
110
|
+
// Physical/VirtIO Ethernet has no IFLA_LINKINFO; bridge, VLAN, bond,
|
|
111
|
+
// dummy and other virtual kinds are not an implicit uplink role.
|
|
112
|
+
if link.attributes.iter().any(|item| item.id() == 18) {
|
|
113
|
+
return Err(Error::Conflict);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
_ => return Err(Error::Invalid),
|
|
117
|
+
}
|
|
118
|
+
if binding.kind == "tun" {
|
|
119
|
+
if link.hardware != 65534 || binding.mac.is_some() {
|
|
120
|
+
return Err(Error::Conflict);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
let mac = &one(&link.attributes, 1)?.value;
|
|
124
|
+
if link.hardware != 1
|
|
125
|
+
|| mac.len() != 6
|
|
126
|
+
|| mac[0] & 1 != 0
|
|
127
|
+
|| mac.iter().all(|item| *item == 0)
|
|
128
|
+
{
|
|
129
|
+
return Err(Error::Conflict);
|
|
130
|
+
}
|
|
131
|
+
let actual = mac
|
|
132
|
+
.iter()
|
|
133
|
+
.map(|item| format!("{item:02x}"))
|
|
134
|
+
.collect::<Vec<_>>()
|
|
135
|
+
.join(":");
|
|
136
|
+
if binding.mac != Some(actual.as_str()) {
|
|
137
|
+
return Err(Error::Conflict);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let peer = if link.attributes.iter().any(|item| item.id() == 5) {
|
|
141
|
+
u32::from_ne_bytes(
|
|
142
|
+
one(&link.attributes, 5)?
|
|
143
|
+
.value
|
|
144
|
+
.as_slice()
|
|
145
|
+
.try_into()
|
|
146
|
+
.map_err(|_| Error::Protocol)?,
|
|
147
|
+
)
|
|
148
|
+
} else {
|
|
149
|
+
binding.index
|
|
150
|
+
};
|
|
151
|
+
if peer != binding.link_index {
|
|
152
|
+
return Err(Error::Conflict);
|
|
153
|
+
}
|
|
154
|
+
Ok(())
|
|
155
|
+
}
|
|
156
|
+
fn read_addresses(socket: &mut Socket, index: u32) -> Result<BTreeSet<String>> {
|
|
157
|
+
let sequence = socket.sequence()?;
|
|
158
|
+
let mut request = Vec::new();
|
|
159
|
+
request.extend(24_u32.to_ne_bytes());
|
|
160
|
+
request.extend(22_u16.to_ne_bytes());
|
|
161
|
+
request.extend(0x301_u16.to_ne_bytes());
|
|
162
|
+
request.extend(sequence.to_ne_bytes());
|
|
163
|
+
request.extend(socket.port.to_ne_bytes());
|
|
164
|
+
request.extend([libc::AF_INET as u8, 0, 0, 0]);
|
|
165
|
+
request.extend(index.to_ne_bytes());
|
|
166
|
+
socket.send(&request)?;
|
|
167
|
+
let until = Instant::now() + Duration::from_secs(3);
|
|
168
|
+
let mut result = BTreeSet::new();
|
|
169
|
+
let mut total = 0;
|
|
170
|
+
let mut count = 0;
|
|
171
|
+
loop {
|
|
172
|
+
for message in socket.receive(until)? {
|
|
173
|
+
if message.sequence != sequence {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if message.flags & 0x10 != 0 {
|
|
177
|
+
return Err(Error::Conflict);
|
|
178
|
+
}
|
|
179
|
+
if message.kind == 2 || message.kind == 3 {
|
|
180
|
+
if message.kind == 2 || !message.payload.is_empty() {
|
|
181
|
+
let error = i32::from_ne_bytes(
|
|
182
|
+
message
|
|
183
|
+
.payload
|
|
184
|
+
.get(..4)
|
|
185
|
+
.ok_or(Error::Protocol)?
|
|
186
|
+
.try_into()
|
|
187
|
+
.unwrap(),
|
|
188
|
+
);
|
|
189
|
+
if error != 0 {
|
|
190
|
+
return Err(Error::kernel(error));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if message.kind == 3 {
|
|
194
|
+
return Ok(result);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
count += 1;
|
|
199
|
+
total += message.payload.len();
|
|
200
|
+
if message.kind != 20 || message.payload.len() < 8 || count > 4096 || total > 2_097_152
|
|
201
|
+
{
|
|
202
|
+
return Err(Error::Protocol);
|
|
203
|
+
}
|
|
204
|
+
if message.payload[0] != libc::AF_INET as u8 {
|
|
205
|
+
return Err(Error::Protocol);
|
|
206
|
+
}
|
|
207
|
+
if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
let data = attrs(&message.payload[8..])?;
|
|
211
|
+
let local = if data.iter().any(|item| item.id() == 2) {
|
|
212
|
+
one(&data, 2)?
|
|
213
|
+
} else {
|
|
214
|
+
one(&data, 1)?
|
|
215
|
+
};
|
|
216
|
+
let bytes: [u8; 4] = local
|
|
217
|
+
.value
|
|
218
|
+
.as_slice()
|
|
219
|
+
.try_into()
|
|
220
|
+
.map_err(|_| Error::Protocol)?;
|
|
221
|
+
let flags = if data.iter().any(|item| item.id() == 8) {
|
|
222
|
+
u32::from_ne_bytes(
|
|
223
|
+
one(&data, 8)?
|
|
224
|
+
.value
|
|
225
|
+
.as_slice()
|
|
226
|
+
.try_into()
|
|
227
|
+
.map_err(|_| Error::Protocol)?,
|
|
228
|
+
)
|
|
229
|
+
} else {
|
|
230
|
+
u32::from(message.payload[2])
|
|
231
|
+
};
|
|
232
|
+
// Tentative, failed or deprecated source addresses do not qualify.
|
|
233
|
+
if flags & (0x40 | 0x08 | 0x20) != 0 {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if data.iter().any(|item| item.id() == 6) {
|
|
237
|
+
let cache = &one(&data, 6)?.value;
|
|
238
|
+
if cache.len() != 16 {
|
|
239
|
+
return Err(Error::Protocol);
|
|
240
|
+
}
|
|
241
|
+
if u32::from_ne_bytes(cache[..4].try_into().unwrap()) == 0
|
|
242
|
+
|| u32::from_ne_bytes(cache[4..8].try_into().unwrap()) == 0
|
|
243
|
+
{
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
result.insert(Ipv4Addr::from(bytes).to_string());
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
pub fn verify_bound_interfaces<'a>(
|
|
252
|
+
interfaces: impl Iterator<Item = BoundInterface<'a>>,
|
|
253
|
+
) -> Result<()> {
|
|
254
|
+
let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
|
|
255
|
+
for binding in interfaces {
|
|
256
|
+
matches(&read_link(&mut socket, binding.index)?, &binding)?;
|
|
257
|
+
let addresses = read_addresses(&mut socket, binding.index)?;
|
|
258
|
+
if binding
|
|
259
|
+
.addresses
|
|
260
|
+
.iter()
|
|
261
|
+
.any(|address| !addresses.contains(address))
|
|
262
|
+
{
|
|
263
|
+
return Err(Error::Conflict);
|
|
264
|
+
}
|
|
265
|
+
matches(&read_link(&mut socket, binding.index)?, &binding)?;
|
|
266
|
+
}
|
|
267
|
+
Ok(())
|
|
268
|
+
}
|
package/rust/src/wire.rs
CHANGED
|
@@ -123,70 +123,9 @@ pub struct Socket {
|
|
|
123
123
|
sequence: u32,
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
pub
|
|
129
|
-
interfaces: impl Iterator<Item = (u32, &'a str, &'a str)>,
|
|
130
|
-
) -> Result<()> {
|
|
131
|
-
let mut socket = Socket::open_protocol(libc::NETLINK_ROUTE)?;
|
|
132
|
-
for (index, name, kind) in interfaces {
|
|
133
|
-
let sequence = socket.sequence()?;
|
|
134
|
-
let mut request = Vec::new();
|
|
135
|
-
request.extend(32_u32.to_ne_bytes());
|
|
136
|
-
request.extend(18_u16.to_ne_bytes());
|
|
137
|
-
request.extend(1_u16.to_ne_bytes());
|
|
138
|
-
request.extend(sequence.to_ne_bytes());
|
|
139
|
-
request.extend(socket.port.to_ne_bytes());
|
|
140
|
-
request.extend([0; 4]);
|
|
141
|
-
request.extend(index.to_ne_bytes());
|
|
142
|
-
request.extend([0; 8]);
|
|
143
|
-
socket.send(&request)?;
|
|
144
|
-
let until = Instant::now() + Duration::from_secs(3);
|
|
145
|
-
let response = 'read: loop {
|
|
146
|
-
for message in socket.receive(until)? {
|
|
147
|
-
if message.sequence != sequence {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if message.kind == 2 {
|
|
151
|
-
let error = i32::from_ne_bytes(
|
|
152
|
-
message
|
|
153
|
-
.payload
|
|
154
|
-
.get(..4)
|
|
155
|
-
.ok_or(Error::Protocol)?
|
|
156
|
-
.try_into()
|
|
157
|
-
.unwrap(),
|
|
158
|
-
);
|
|
159
|
-
if error != 0 {
|
|
160
|
-
return Err(Error::kernel(error));
|
|
161
|
-
}
|
|
162
|
-
} else if message.kind == 16 && message.payload.len() >= 16 {
|
|
163
|
-
if u32::from_ne_bytes(message.payload[4..8].try_into().unwrap()) != index {
|
|
164
|
-
return Err(Error::Conflict);
|
|
165
|
-
}
|
|
166
|
-
break 'read attrs(&message.payload[16..])?;
|
|
167
|
-
} else {
|
|
168
|
-
return Err(Error::Protocol);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
if text(&response, 3)? != name {
|
|
173
|
-
return Err(Error::Conflict);
|
|
174
|
-
}
|
|
175
|
-
if response.iter().any(|attr| attr.id() == 10)
|
|
176
|
-
&& one(&response, 10)?.value != 0_u32.to_ne_bytes()
|
|
177
|
-
{
|
|
178
|
-
return Err(Error::Conflict);
|
|
179
|
-
}
|
|
180
|
-
let info = attrs(&one(&response, 18)?.value)?;
|
|
181
|
-
if text(&info, 1)? != kind {
|
|
182
|
-
return Err(Error::Conflict);
|
|
183
|
-
}
|
|
184
|
-
if kind == "tun" && one(&attrs(&one(&info, 2)?.value)?, 3)?.value != [1] {
|
|
185
|
-
return Err(Error::Conflict);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
Ok(())
|
|
189
|
-
}
|
|
126
|
+
#[path = "wire.links.rs"]
|
|
127
|
+
mod links;
|
|
128
|
+
pub use links::{verify_bound_interfaces, verify_interfaces, BoundInterface};
|
|
190
129
|
impl Socket {
|
|
191
130
|
pub fn open() -> Result<Self> {
|
|
192
131
|
Self::open_protocol(libc::NETLINK_NETFILTER)
|
package/ts/00_commitinfo_data.ts
CHANGED