@push.rocks/smartnftables 4.4.0 → 4.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 +7 -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/package.json +1 -1
- package/readme.md +52 -1
- package/rust/src/egress.compile.rs +105 -38
- package/rust/src/egress.hostgrant.rs +74 -42
- package/rust/src/egress.keys.rs +380 -55
- package/rust/src/egress.leased.rs +71 -42
- package/rust/src/egress.poolguard.rs +8 -13
- package/rust/src/egress.private.rs +32 -37
- package/rust/src/egress.published.rs +74 -47
- package/rust/src/egress.router.rs +1 -1
- package/rust/src/egress.rs +8 -0
- package/rust/src/egress.workloadgrant.rs +30 -28
- package/rust/src/egress_keys_tests.rs +150 -0
- package/rust/src/egress_tests.rs +47 -4
- package/rust/src/owner.rs +96 -28
- package/rust/src/owner_listing_tests.rs +634 -0
- package/rust/src/owner_tests.rs +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
|
@@ -57,7 +57,7 @@ fn host_grant_return(program: &mut Program<'_>, scope: &RouterScope) -> Result<(
|
|
|
57
57
|
if !scope.host_grants.is_empty() {
|
|
58
58
|
let mut expressions = ipv4();
|
|
59
59
|
expressions.extend(link(&scope.handoff, true));
|
|
60
|
-
expressions.extend(hostgrant::arrival_lookup());
|
|
60
|
+
expressions.extend(hostgrant::arrival_lookup()?);
|
|
61
61
|
program.end("raw_return", expressions, 1)?;
|
|
62
62
|
}
|
|
63
63
|
Ok(())
|
package/rust/src/egress.rs
CHANGED
|
@@ -1344,6 +1344,14 @@ impl Prepared {
|
|
|
1344
1344
|
pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<crate::wire::Attr>)>> {
|
|
1345
1345
|
compile::compile(self, table)
|
|
1346
1346
|
}
|
|
1347
|
+
/// Every set the compiled graph declares, with its key and data fields.
|
|
1348
|
+
#[cfg(test)]
|
|
1349
|
+
pub(crate) fn declared(
|
|
1350
|
+
&self,
|
|
1351
|
+
table: &str,
|
|
1352
|
+
) -> Result<std::collections::BTreeMap<String, compile::SetDeclaration>> {
|
|
1353
|
+
compile::declared(self, table)
|
|
1354
|
+
}
|
|
1347
1355
|
pub fn validate_interfaces(&self) -> Result<()> {
|
|
1348
1356
|
let links: Vec<&LocalLink> = match &self.policy.scope {
|
|
1349
1357
|
Scope::RouterEgress(scope) => scope
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
//! exact link that holds it (index and name), and both the incoming and the
|
|
7
7
|
//! outgoing link are looked up on every packet. Only the grant's source opens;
|
|
8
8
|
//! the destination only answers.
|
|
9
|
-
use super::hostgrant::{
|
|
9
|
+
use super::hostgrant::{tuple_key, tuple_lookup, TUPLE};
|
|
10
|
+
use super::keys::{Field, Loads, IIF, IIFNAME, IP_SADDR};
|
|
10
11
|
use super::*;
|
|
11
12
|
use std::collections::BTreeSet;
|
|
12
13
|
|
|
@@ -14,16 +15,16 @@ use std::collections::BTreeSet;
|
|
|
14
15
|
pub(super) const TUPLE_SET: &str = "workload_grant";
|
|
15
16
|
/// Every grant address with the exact link that holds it.
|
|
16
17
|
pub(super) const LINK_SET: &str = "workload_link";
|
|
18
|
+
/// A grant address and its link, as the incoming direction loads them.
|
|
19
|
+
const LINK: [Field; 3] = [IIF, IIFNAME, IP_SADDR];
|
|
17
20
|
|
|
18
21
|
/// The link a packet crosses and the address on its side of that link: the
|
|
19
22
|
/// incoming link and the source, or the outgoing link and the destination.
|
|
20
|
-
fn link_lookup(incoming: bool) -> Vec<Attr
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
lookup(LINK_SET),
|
|
26
|
-
]
|
|
23
|
+
fn link_lookup(incoming: bool) -> Result<Vec<Attr>> {
|
|
24
|
+
Loads::new()
|
|
25
|
+
.link(incoming)
|
|
26
|
+
.address(incoming)
|
|
27
|
+
.lookup(LINK_SET, &LINK)
|
|
27
28
|
}
|
|
28
29
|
fn link_key(link: &LocalLink, address: &str) -> Result<Vec<u8>> {
|
|
29
30
|
let mut key = link.interface_index.to_ne_bytes().to_vec();
|
|
@@ -49,9 +50,9 @@ pub(super) fn sets(program: &mut Program<'_>, scope: &RouterScope) -> Result<()>
|
|
|
49
50
|
)?);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
|
-
program.
|
|
53
|
+
program.exact_set(TUPLE_SET, 3, &TUPLE, None)?;
|
|
53
54
|
program.elements(TUPLE_SET, keys)?;
|
|
54
|
-
program.
|
|
55
|
+
program.exact_set(LINK_SET, 4, &LINK, None)?;
|
|
55
56
|
program.elements(LINK_SET, links.into_iter().collect())
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -64,25 +65,26 @@ pub(super) fn admissions(program: &mut Program<'_>, scope: &RouterScope) -> Resu
|
|
|
64
65
|
if scope.workload_grants.is_empty() {
|
|
65
66
|
return Ok(());
|
|
66
67
|
}
|
|
67
|
-
let admission =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
let admission =
|
|
69
|
+
|reply: bool, connection_state: u32, transport: Option<&str>| -> Result<Vec<Attr>> {
|
|
70
|
+
let mut result = ipv4();
|
|
71
|
+
result.extend(ct(17, None, 0_u16.to_ne_bytes().to_vec()));
|
|
72
|
+
result.extend(ct(1, None, vec![u8::from(reply)]));
|
|
73
|
+
result.extend(state(connection_state));
|
|
74
|
+
if let Some(transport) = transport {
|
|
75
|
+
result.extend(meta(16, vec![protocol(transport)]));
|
|
76
|
+
if transport == "tcp" {
|
|
77
|
+
result.extend(opening_tcp());
|
|
78
|
+
}
|
|
76
79
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
program.end("forward", admission(false, 2, None), 1)?;
|
|
80
|
+
result.extend(link_lookup(true)?);
|
|
81
|
+
result.extend(link_lookup(false)?);
|
|
82
|
+
result.extend(tuple_lookup(TUPLE_SET, false, reply)?);
|
|
83
|
+
Ok(result)
|
|
84
|
+
};
|
|
85
|
+
program.end("forward", admission(false, 2, None)?, 1)?;
|
|
84
86
|
for transport in ["udp", "tcp"] {
|
|
85
|
-
program.end("forward", admission(false, 8, Some(transport))
|
|
87
|
+
program.end("forward", admission(false, 8, Some(transport))?, 1)?;
|
|
86
88
|
}
|
|
87
|
-
program.end("forward", admission(true, 2, None)
|
|
89
|
+
program.end("forward", admission(true, 2, None)?, 1)
|
|
88
90
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//! Set declarations against what nftables 1.0.9 itself writes. Each expected
|
|
2
|
+
//! value was dumped from `nft --debug=mnl add set|map ...` with the host nft
|
|
3
|
+
//! 1.0.9 (libnftnl 1.2.6) on x86_64; userdata numbers are host byte order.
|
|
4
|
+
use super::*;
|
|
5
|
+
|
|
6
|
+
fn hex(text: &str) -> Vec<u8> {
|
|
7
|
+
let digits: String = text.split_whitespace().collect();
|
|
8
|
+
(0..digits.len())
|
|
9
|
+
.step_by(2)
|
|
10
|
+
.map(|at| u8::from_str_radix(&digits[at..at + 2], 16).unwrap())
|
|
11
|
+
.collect()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// `typeof ip saddr . th dport . ct original ip saddr . ct zone`
|
|
15
|
+
#[test]
|
|
16
|
+
#[cfg(target_endian = "little")]
|
|
17
|
+
fn concatenated_key_types_and_userdata_are_nfts() {
|
|
18
|
+
let key = [IP_SADDR, TH_DPORT, CT_ORIGINAL_SADDR, CT_ZONE];
|
|
19
|
+
assert_eq!(datatype(&key), 0x001c_d1c4);
|
|
20
|
+
assert_eq!(key_length(&key), 16);
|
|
21
|
+
assert_eq!(
|
|
22
|
+
userdata(&key, None).unwrap(),
|
|
23
|
+
hex("00 04 00000000 03 60 00 04 0d000000 01 58
|
|
24
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
25
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
|
|
26
|
+
02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 00000000
|
|
27
|
+
03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff")
|
|
28
|
+
);
|
|
29
|
+
// `typeof meta oif . meta oifname . ct reply ip saddr . ct reply proto-src`
|
|
30
|
+
let key = [OIF, OIFNAME, CT_REPLY_SADDR, CT_REPLY_SPORT];
|
|
31
|
+
assert_eq!(datatype(&key), 0x0052_91cd);
|
|
32
|
+
assert_eq!(key_length(&key), 28);
|
|
33
|
+
assert_eq!(
|
|
34
|
+
userdata(&key, None).unwrap(),
|
|
35
|
+
hex("00 04 00000000 03 54 00 04 0d000000 01 4c
|
|
36
|
+
00 0e 00 04 09000000 01 06 00 04 05000000
|
|
37
|
+
01 0e 00 04 09000000 01 06 00 04 07000000
|
|
38
|
+
02 14 00 04 0c000000 01 0c 00 04 13000000 01 04 01000000
|
|
39
|
+
03 14 00 04 0c000000 01 0c 00 04 0b000000 01 04 01000000")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// `typeof meta l4proto . ct protocol . ip daddr : ip saddr . th sport . th dport`
|
|
44
|
+
/// and `typeof ct zone : ct label`.
|
|
45
|
+
#[test]
|
|
46
|
+
#[cfg(target_endian = "little")]
|
|
47
|
+
fn map_data_types_and_userdata_are_nfts() {
|
|
48
|
+
let key = [L4PROTO, CT_PROTOCOL, IP_DADDR];
|
|
49
|
+
let data = [IP_SADDR, TH_SPORT, TH_DPORT];
|
|
50
|
+
assert_eq!(datatype(&key), 0xc307);
|
|
51
|
+
assert_eq!(datatype(&data), 0x734d);
|
|
52
|
+
assert_eq!((key_length(&key), data_length(&data)), (12, 12));
|
|
53
|
+
assert_eq!(
|
|
54
|
+
userdata(&key, Some(&data)).unwrap(),
|
|
55
|
+
hex("00 04 00000000 01 04 00000000
|
|
56
|
+
03 44 00 04 0d000000 01 3c
|
|
57
|
+
00 0e 00 04 09000000 01 06 00 04 10000000
|
|
58
|
+
01 14 00 04 0c000000 01 0c 00 04 0a000000 01 04 ffffffff
|
|
59
|
+
02 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0c000000
|
|
60
|
+
04 4a 00 04 0d000000 01 42
|
|
61
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
62
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
63
|
+
02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 02000000
|
|
64
|
+
06 04 00000000")
|
|
65
|
+
);
|
|
66
|
+
let (key, data) = ([CT_ZONE], [CT_LABEL]);
|
|
67
|
+
assert_eq!((datatype(&key), datatype(&data)), (4, 30));
|
|
68
|
+
assert_eq!(data_length(&data), 16);
|
|
69
|
+
assert_eq!(
|
|
70
|
+
userdata(&key, Some(&data)).unwrap(),
|
|
71
|
+
hex("00 04 01000000 01 04 01000000
|
|
72
|
+
03 14 00 04 0c000000 01 0c 00 04 11000000 01 04 ffffffff
|
|
73
|
+
04 14 00 04 0c000000 01 0c 00 04 0d000000 01 04 ffffffff
|
|
74
|
+
06 04 00000000")
|
|
75
|
+
);
|
|
76
|
+
// nft declares the zone alone as its 2 bytes; this map keeps the zone
|
|
77
|
+
// padded to its register, as every release before typed sets did.
|
|
78
|
+
assert_eq!(key_length(&key), 4);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Beyond four fields nft 1.0.9 cannot describe a key by `typeof`: its
|
|
82
|
+
/// concatenation userdata stops at `NFT_REG32_SIZE` (4) entries, and the type
|
|
83
|
+
/// keeps 32 bits, six per field, dropping what a longer key shifts out.
|
|
84
|
+
#[test]
|
|
85
|
+
fn longer_keys_carry_their_type_and_byte_order_alone() {
|
|
86
|
+
let key = [IIF, L4PROTO, IP_SADDR, IP_DADDR, TH_DPORT];
|
|
87
|
+
assert_eq!(
|
|
88
|
+
datatype(&key),
|
|
89
|
+
(20 << 24) | (12 << 18) | (7 << 12) | (7 << 6) | 13
|
|
90
|
+
);
|
|
91
|
+
assert_eq!(userdata(&key, None).unwrap(), hex("00 04 00000000"));
|
|
92
|
+
let flow = [
|
|
93
|
+
IIF,
|
|
94
|
+
L4PROTO,
|
|
95
|
+
CT_PROTOCOL,
|
|
96
|
+
IP_SADDR,
|
|
97
|
+
IP_DADDR,
|
|
98
|
+
TH_DPORT,
|
|
99
|
+
CT_ORIGINAL_SADDR,
|
|
100
|
+
CT_ORIGINAL_DADDR,
|
|
101
|
+
CT_ORIGINAL_DPORT,
|
|
102
|
+
CT_ZONE,
|
|
103
|
+
];
|
|
104
|
+
let truncated = flow.iter().fold(0_u64, |result, field| {
|
|
105
|
+
(result << 6) | u64::from(field.datatype)
|
|
106
|
+
});
|
|
107
|
+
assert_eq!(u64::from(datatype(&flow)), truncated & u64::from(u32::MAX));
|
|
108
|
+
let data = [IP_SADDR, TH_SPORT, TH_SPORT];
|
|
109
|
+
let mut expected = hex("00 04 00000000 01 04 00000000");
|
|
110
|
+
expected.extend(hex("04 4a 00 04 0d000000 01 42
|
|
111
|
+
00 14 00 04 07000000 01 0c 00 04 0c000000 01 04 0b000000
|
|
112
|
+
01 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
113
|
+
02 14 00 04 07000000 01 0c 00 04 0b000000 01 04 01000000
|
|
114
|
+
06 04 00000000"));
|
|
115
|
+
#[cfg(target_endian = "little")]
|
|
116
|
+
assert_eq!(userdata(&flow, Some(&data)).unwrap(), expected);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// A lookup loads every field of the set's key, by length and type; the other
|
|
120
|
+
/// direction's field of the same kind fits, anything else refuses.
|
|
121
|
+
#[test]
|
|
122
|
+
fn lookups_must_fit_the_declared_key() {
|
|
123
|
+
let declared = [IIF, IIFNAME, IP_SADDR];
|
|
124
|
+
assert!(Loads::new()
|
|
125
|
+
.link(true)
|
|
126
|
+
.address(true)
|
|
127
|
+
.lookup("s", &declared)
|
|
128
|
+
.is_ok());
|
|
129
|
+
assert!(Loads::new()
|
|
130
|
+
.link(false)
|
|
131
|
+
.address(false)
|
|
132
|
+
.lookup("s", &declared)
|
|
133
|
+
.is_ok());
|
|
134
|
+
assert_eq!(
|
|
135
|
+
Loads::new().link(true).port(true).lookup("s", &declared),
|
|
136
|
+
Err(Error::Invalid)
|
|
137
|
+
);
|
|
138
|
+
assert_eq!(
|
|
139
|
+
Loads::new().link(true).lookup("s", &declared),
|
|
140
|
+
Err(Error::Invalid)
|
|
141
|
+
);
|
|
142
|
+
assert_eq!(
|
|
143
|
+
Loads::new()
|
|
144
|
+
.index(true)
|
|
145
|
+
.address(true)
|
|
146
|
+
.address(true)
|
|
147
|
+
.lookup("s", &declared),
|
|
148
|
+
Err(Error::Invalid)
|
|
149
|
+
);
|
|
150
|
+
}
|
package/rust/src/egress_tests.rs
CHANGED
|
@@ -507,8 +507,27 @@ fn prepared(value: Value) -> Prepared {
|
|
|
507
507
|
normalized(value).unwrap().prepare().unwrap()
|
|
508
508
|
}
|
|
509
509
|
fn program_bytes(prepared: &Prepared, table: &str) -> Vec<u8> {
|
|
510
|
+
encoded(prepared.program(table).unwrap())
|
|
511
|
+
}
|
|
512
|
+
/// LEGACY(<=4.4.x): the program as releases before typed sets compiled it,
|
|
513
|
+
/// whose goldens it must still reproduce byte for byte, so their kernel graphs
|
|
514
|
+
/// stay adoptable.
|
|
515
|
+
fn untyped_program_bytes(prepared: &Prepared, table: &str) -> Vec<u8> {
|
|
516
|
+
encoded(
|
|
517
|
+
prepared
|
|
518
|
+
.program(table)
|
|
519
|
+
.unwrap()
|
|
520
|
+
.into_iter()
|
|
521
|
+
.map(|(kind, attributes)| match kind {
|
|
522
|
+
9 => (kind, crate::owner::untyped_set(&attributes)),
|
|
523
|
+
_ => (kind, attributes),
|
|
524
|
+
})
|
|
525
|
+
.collect(),
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
fn encoded(program: Vec<(u16, Vec<crate::wire::Attr>)>) -> Vec<u8> {
|
|
510
529
|
let mut bytes = Vec::new();
|
|
511
|
-
for (kind, attributes) in
|
|
530
|
+
for (kind, attributes) in program {
|
|
512
531
|
let encoded = crate::wire::encode_attrs(&attributes);
|
|
513
532
|
bytes.extend(kind.to_be_bytes());
|
|
514
533
|
bytes.extend((encoded.len() as u32).to_be_bytes());
|
|
@@ -606,12 +625,20 @@ fn host_v2_published_ports_compile_uplink_dnat_and_exact_forward_admission() {
|
|
|
606
625
|
for (_, expressions) in &forward[..4] {
|
|
607
626
|
assert_eq!(expressions[expressions.len() - 2], "lookup");
|
|
608
627
|
}
|
|
609
|
-
|
|
628
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
629
|
+
let bytes = untyped_program_bytes(&published, "snft_v2_host_golden");
|
|
610
630
|
assert_eq!(bytes.len(), 30988);
|
|
611
631
|
assert_eq!(
|
|
612
632
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
613
633
|
"b36f359c6439e9fcd77c291c7dac7854014225d3de1f8a4e49df6fec6ae570c4"
|
|
614
634
|
);
|
|
635
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
636
|
+
let bytes = program_bytes(&published, "snft_v2_host_golden");
|
|
637
|
+
assert_eq!(bytes.len(), 31168);
|
|
638
|
+
assert_eq!(
|
|
639
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
640
|
+
"609f2388e3c5cf45b921182c17252c072c4db2f07e3cc605e282faddd937d458"
|
|
641
|
+
);
|
|
615
642
|
}
|
|
616
643
|
|
|
617
644
|
#[test]
|
|
@@ -724,12 +751,20 @@ fn router_v2_without_published_ports_keeps_its_exact_digest_and_compiled_bytes()
|
|
|
724
751
|
);
|
|
725
752
|
// Re-frozen with the set-backed router graph of 3.0.0: the digest is the
|
|
726
753
|
// policy's and did not change, the compiled graph did.
|
|
727
|
-
|
|
754
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
755
|
+
let bytes = untyped_program_bytes(&baseline, "snft_v2_router_golden");
|
|
728
756
|
assert_eq!(bytes.len(), 47674);
|
|
729
757
|
assert_eq!(
|
|
730
758
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
731
759
|
"ea609865f32031f6835ef4fc7f67f5754a15f466580308bdccc9db87d4ccd2c5"
|
|
732
760
|
);
|
|
761
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
762
|
+
let bytes = program_bytes(&baseline, "snft_v2_router_golden");
|
|
763
|
+
assert_eq!(bytes.len(), 48362);
|
|
764
|
+
assert_eq!(
|
|
765
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
766
|
+
"47280ecb596125d6421b1ab42e2d768e53844f55d7d0f130fe31a8cd5b9a5668"
|
|
767
|
+
);
|
|
733
768
|
let serialized = serde_json::to_string(&baseline.policy).unwrap();
|
|
734
769
|
assert!(!serialized.contains("publishedPorts"));
|
|
735
770
|
// An explicit empty publication is the same canonical policy, digest and graph.
|
|
@@ -796,12 +831,20 @@ fn router_v2_published_ports_compile_handoff_dnat_classification_and_admission()
|
|
|
796
831
|
assert!(incoming[4..]
|
|
797
832
|
.iter()
|
|
798
833
|
.any(|expressions| expressions.contains(&"ct".to_string())));
|
|
799
|
-
|
|
834
|
+
// The graph of 4.4.x, byte for byte, once its sets are untyped again.
|
|
835
|
+
let bytes = untyped_program_bytes(&published, "snft_v2_router_golden");
|
|
800
836
|
assert_eq!(bytes.len(), 55494);
|
|
801
837
|
assert_eq!(
|
|
802
838
|
format!("{:x}", Sha256::digest(&bytes)),
|
|
803
839
|
"60a7ba1c15d49e5ff54cfbf9fbe2d72f03973acd1a897eef2d975547ef4abf5b"
|
|
804
840
|
);
|
|
841
|
+
// Re-frozen with typed sets in 4.5.0.
|
|
842
|
+
let bytes = program_bytes(&published, "snft_v2_router_golden");
|
|
843
|
+
assert_eq!(bytes.len(), 56454);
|
|
844
|
+
assert_eq!(
|
|
845
|
+
format!("{:x}", Sha256::digest(&bytes)),
|
|
846
|
+
"1d2a72707bc10ed689e3f9b4c1cb3e798e89c01fc97115078dcd32e891b22306"
|
|
847
|
+
);
|
|
805
848
|
}
|
|
806
849
|
|
|
807
850
|
#[test]
|
package/rust/src/owner.rs
CHANGED
|
@@ -302,7 +302,13 @@ impl Owner {
|
|
|
302
302
|
}
|
|
303
303
|
Ok(())
|
|
304
304
|
}
|
|
305
|
+
/// Whether `graph` is `prepared`'s compiled graph. LEGACY(<=4.4.x): this
|
|
306
|
+
/// includes the untyped sets of a release before typed sets; with the next
|
|
307
|
+
/// major it becomes exact agreement alone.
|
|
305
308
|
fn matches(&self, graph: &Graph, prepared: &Prepared) -> Result<bool> {
|
|
309
|
+
Ok(self.agreement(graph, prepared)? != Agreement::Different)
|
|
310
|
+
}
|
|
311
|
+
fn agreement(&self, graph: &Graph, prepared: &Prepared) -> Result<Agreement> {
|
|
306
312
|
let program = prepared.program(&self.identity.table_name)?;
|
|
307
313
|
let expected_chains: Vec<_> = program
|
|
308
314
|
.iter()
|
|
@@ -336,21 +342,8 @@ impl Owner {
|
|
|
336
342
|
}
|
|
337
343
|
result
|
|
338
344
|
};
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
for (kind, attrs) in &program {
|
|
342
|
-
match kind {
|
|
343
|
-
9 => {
|
|
344
|
-
expected_sets.insert(wire::text(attrs, 2)?, (set_identity(attrs)?, Vec::new()));
|
|
345
|
-
}
|
|
346
|
-
12 => {
|
|
347
|
-
let entry = expected_sets
|
|
348
|
-
.get_mut(&wire::text(attrs, 2)?)
|
|
349
|
-
.ok_or(Error::Invalid)?;
|
|
350
|
-
entry.1.extend(element_keys(&wire::attrs(&wire::one(attrs, 3)?.value)?)?);
|
|
351
|
-
}
|
|
352
|
-
_ => {}
|
|
353
|
-
}
|
|
345
|
+
if expected_chains != actual_chains || regroup(expected_rules) != regroup(actual_rules) {
|
|
346
|
+
return Ok(Agreement::Different);
|
|
354
347
|
}
|
|
355
348
|
let mut actual_sets = std::collections::BTreeMap::new();
|
|
356
349
|
for (attrs, keys) in &graph.sets {
|
|
@@ -361,12 +354,22 @@ impl Owner {
|
|
|
361
354
|
return Err(Error::Conflict);
|
|
362
355
|
}
|
|
363
356
|
}
|
|
364
|
-
for entry in
|
|
357
|
+
for entry in actual_sets.values_mut() {
|
|
365
358
|
entry.1.sort();
|
|
366
359
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
360
|
+
if expected_sets(&program, false)? == actual_sets {
|
|
361
|
+
return Ok(Agreement::Exact);
|
|
362
|
+
}
|
|
363
|
+
// LEGACY(<=4.4.x): kernel state applied before typed sets carries every
|
|
364
|
+
// set with key and data type 0 and no userdata, which Linux stores and
|
|
365
|
+
// dumps but never interprets, so it enforces exactly the same graph.
|
|
366
|
+
// Such a graph is adopted, inspected and released as this graph, and
|
|
367
|
+
// reconciliation always replaces it. Remove with the next major.
|
|
368
|
+
Ok(if expected_sets(&program, true)? == actual_sets {
|
|
369
|
+
Agreement::Untyped
|
|
370
|
+
} else {
|
|
371
|
+
Agreement::Different
|
|
372
|
+
})
|
|
370
373
|
}
|
|
371
374
|
fn adopt(&mut self, graph: Graph, transition: &Transition) -> Result<Graph> {
|
|
372
375
|
self.binding(&graph, transition.previous.as_ref())?;
|
|
@@ -464,13 +467,18 @@ impl Owner {
|
|
|
464
467
|
let mut operations = Vec::new();
|
|
465
468
|
let generation = if let Some(graph) = graph {
|
|
466
469
|
let graph = self.adopt(graph, transition)?;
|
|
467
|
-
|
|
470
|
+
let target = self.agreement(&graph, &transition.target)?;
|
|
471
|
+
if target == Agreement::Exact {
|
|
468
472
|
transition.target.validate_interfaces()?;
|
|
469
473
|
return self.applied(&graph, transition.target.clone());
|
|
470
474
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
475
|
+
// LEGACY(<=4.4.x): the target's own untyped graph is never already
|
|
476
|
+
// applied; it is replaced like a previous graph, in the same batch.
|
|
477
|
+
if target == Agreement::Different {
|
|
478
|
+
let previous = transition.previous.as_ref().ok_or(Error::Conflict)?;
|
|
479
|
+
if !self.matches(&graph, &previous.prepared)? {
|
|
480
|
+
return Err(Error::Conflict);
|
|
481
|
+
}
|
|
474
482
|
}
|
|
475
483
|
for rule in &graph.rules {
|
|
476
484
|
operations.push((
|
|
@@ -533,7 +541,7 @@ impl Owner {
|
|
|
533
541
|
self.binding(&graph, transition.previous.as_ref())?;
|
|
534
542
|
if wire::number(&graph.table, 2)? != 6
|
|
535
543
|
|| wire::number(&graph.table, 7)? != self.socket()?.port
|
|
536
|
-
||
|
|
544
|
+
|| self.agreement(&graph, &transition.target)? != Agreement::Exact
|
|
537
545
|
{
|
|
538
546
|
return Err(Error::Conflict);
|
|
539
547
|
}
|
|
@@ -668,6 +676,61 @@ impl Owner {
|
|
|
668
676
|
}
|
|
669
677
|
}
|
|
670
678
|
|
|
679
|
+
/// How a kernel graph agrees with a compiled one.
|
|
680
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
681
|
+
enum Agreement {
|
|
682
|
+
/// Every chain, rule, set and element, with every set's types.
|
|
683
|
+
Exact,
|
|
684
|
+
/// LEGACY(<=4.4.x): the same graph with untyped sets. Remove with the next
|
|
685
|
+
/// major.
|
|
686
|
+
Untyped,
|
|
687
|
+
Different,
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/// Sets by name, each with its identity and sorted element keys.
|
|
691
|
+
type Sets = std::collections::BTreeMap<String, (Vec<u8>, Vec<Vec<u8>>)>;
|
|
692
|
+
/// The compiled graph's sets. LEGACY(<=4.4.x): `untyped` declares them as a
|
|
693
|
+
/// release before typed sets did; remove the parameter with the next major.
|
|
694
|
+
fn expected_sets(program: &[(u16, Vec<Attr>)], untyped: bool) -> Result<Sets> {
|
|
695
|
+
// Element order is the set backend's; element keys are the identity.
|
|
696
|
+
let mut sets = Sets::new();
|
|
697
|
+
for (kind, attrs) in program {
|
|
698
|
+
match kind {
|
|
699
|
+
9 => {
|
|
700
|
+
let identity = if untyped {
|
|
701
|
+
set_identity(&untyped_set(attrs))?
|
|
702
|
+
} else {
|
|
703
|
+
set_identity(attrs)?
|
|
704
|
+
};
|
|
705
|
+
sets.insert(wire::text(attrs, 2)?, (identity, Vec::new()));
|
|
706
|
+
}
|
|
707
|
+
12 => {
|
|
708
|
+
let entry = sets
|
|
709
|
+
.get_mut(&wire::text(attrs, 2)?)
|
|
710
|
+
.ok_or(Error::Invalid)?;
|
|
711
|
+
entry.1.extend(element_keys(&wire::attrs(&wire::one(attrs, 3)?.value)?)?);
|
|
712
|
+
}
|
|
713
|
+
_ => {}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
for entry in sets.values_mut() {
|
|
717
|
+
entry.1.sort();
|
|
718
|
+
}
|
|
719
|
+
Ok(sets)
|
|
720
|
+
}
|
|
721
|
+
/// LEGACY(<=4.4.x): a set declaration as releases before typed sets wrote it:
|
|
722
|
+
/// key and data type 0 and no userdata. Remove with the next major.
|
|
723
|
+
pub(super) fn untyped_set(attributes: &[Attr]) -> Vec<Attr> {
|
|
724
|
+
attributes
|
|
725
|
+
.iter()
|
|
726
|
+
.filter(|attribute| attribute.id() != 13)
|
|
727
|
+
.map(|attribute| match attribute.id() {
|
|
728
|
+
4 | 6 => Attr::u32(attribute.id(), 0),
|
|
729
|
+
_ => attribute.clone(),
|
|
730
|
+
})
|
|
731
|
+
.collect()
|
|
732
|
+
}
|
|
733
|
+
|
|
671
734
|
fn chain_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
672
735
|
let mut identity = Vec::new();
|
|
673
736
|
for attribute in attributes {
|
|
@@ -681,9 +744,10 @@ fn chain_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
|
681
744
|
serde_json::to_vec(&wire::canonical(&identity, false)?).map_err(|_| Error::Protocol)
|
|
682
745
|
}
|
|
683
746
|
/// A set is its table, name, flags, key type and key length, a map's data type
|
|
684
|
-
/// and length,
|
|
685
|
-
///
|
|
686
|
-
///
|
|
747
|
+
/// and length, the field lengths of a concatenation and its userdata (byte
|
|
748
|
+
/// orders and `typeof` descriptions). The handle, the informational backend
|
|
749
|
+
/// type and count, the transaction ID and an empty description are not
|
|
750
|
+
/// identity; anything else rejects.
|
|
687
751
|
fn set_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
688
752
|
let mut identity = Vec::new();
|
|
689
753
|
for attribute in attributes {
|
|
@@ -694,13 +758,17 @@ fn set_identity(attributes: &[Attr]) -> Result<Vec<u8>> {
|
|
|
694
758
|
identity.push(Attr::bytes(9, fields));
|
|
695
759
|
}
|
|
696
760
|
}
|
|
697
|
-
1..=7 => identity.push(attribute.clone()),
|
|
761
|
+
1..=7 | 13 => identity.push(attribute.clone()),
|
|
698
762
|
_ => return Err(Error::Conflict),
|
|
699
763
|
}
|
|
700
764
|
}
|
|
701
765
|
for id in [1, 2, 3, 4, 5] {
|
|
702
766
|
wire::one(&identity, id)?;
|
|
703
767
|
}
|
|
768
|
+
// At most one userdata: a typed set carries it, a legacy untyped one not.
|
|
769
|
+
if identity.iter().filter(|attribute| attribute.id() == 13).count() > 1 {
|
|
770
|
+
return Err(Error::Conflict);
|
|
771
|
+
}
|
|
704
772
|
// A map carries its data type and length, and nothing else does.
|
|
705
773
|
let map = wire::number(&identity, 3)? & 8 != 0;
|
|
706
774
|
for id in [6, 7] {
|