@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
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
use super::*;
|
|
2
|
+
use crate::egress;
|
|
3
|
+
use serde_json::{json, Value};
|
|
4
|
+
use std::io::{Read, Write};
|
|
5
|
+
use std::net::{SocketAddr, TcpListener, TcpStream, UdpSocket};
|
|
6
|
+
use std::os::fd::AsRawFd;
|
|
7
|
+
use std::time::Duration;
|
|
8
|
+
|
|
9
|
+
const WORK: &str = "v2work";
|
|
10
|
+
const ROUTER: &str = "v2router";
|
|
11
|
+
const HOST: &str = "v2host";
|
|
12
|
+
const PEER: &str = "v2peer";
|
|
13
|
+
|
|
14
|
+
pub(super) fn ns_ip(namespace: &str, args: &[&str]) {
|
|
15
|
+
let mut command = vec!["-n", namespace];
|
|
16
|
+
command.extend(args);
|
|
17
|
+
ip(&command);
|
|
18
|
+
}
|
|
19
|
+
pub(super) fn connect(a: &str, aname: &str, aip: &str, b: &str, bname: &str, bip: &str) {
|
|
20
|
+
ip(&["link", "add", aname, "type", "veth", "peer", "name", bname]);
|
|
21
|
+
ip(&["link", "set", aname, "netns", a]);
|
|
22
|
+
ip(&["link", "set", bname, "netns", b]);
|
|
23
|
+
for (ns, name, address) in [(a, aname, aip), (b, bname, bip)] {
|
|
24
|
+
ns_ip(ns, &["address", "add", address, "dev", name]);
|
|
25
|
+
ns_ip(ns, &["link", "set", name, "up"]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
fn binding(name: &str, address: &str, kind: &str) -> Value {
|
|
29
|
+
let output = std::process::Command::new("/bin/ip")
|
|
30
|
+
.args(["-j", "-d", "link", "show", "dev", name])
|
|
31
|
+
.output()
|
|
32
|
+
.unwrap();
|
|
33
|
+
assert!(output.status.success());
|
|
34
|
+
let observed = serde_json::from_slice::<Value>(&output.stdout).unwrap()[0].clone();
|
|
35
|
+
json!({"interfaceIndex":observed["ifindex"],"interfaceName":name,"interfaceKind":kind,
|
|
36
|
+
"macAddress":if kind=="tun" {Value::Null} else {observed["address"].clone()},
|
|
37
|
+
"interfaceLinkIndex":if kind=="tun" {observed["ifindex"].clone()} else {observed["link_index"].clone()},
|
|
38
|
+
"requiredIpv4Addresses":[address]})
|
|
39
|
+
}
|
|
40
|
+
fn protection() -> Value {
|
|
41
|
+
json!({"authorityDigest":format!("sha256:{}","b".repeat(64)),"prefixes":["10.0.0.0/8","192.0.2.0/24"],
|
|
42
|
+
"platformEndpoints":[{"id":"dns","address":"192.0.2.3","protocol":"udp","port":5300},
|
|
43
|
+
{"id":"mail","address":"192.0.2.3","protocol":"tcp","port":3000}]})
|
|
44
|
+
}
|
|
45
|
+
fn allocation() -> Value {
|
|
46
|
+
json!({"lease":{"id":"lease-a","generation":1,"digest":format!("sha256:{}","a".repeat(64))},
|
|
47
|
+
"transitSourceAddress":"10.240.0.2","sourcePortRanges":[{"protocol":"udp","first":10000,"last":10015},
|
|
48
|
+
{"protocol":"tcp","first":10016,"last":10031}]})
|
|
49
|
+
}
|
|
50
|
+
fn router_policy() -> egress::Policy {
|
|
51
|
+
let mut value = egress::tests::router();
|
|
52
|
+
value["scope"]["links"] = json!([
|
|
53
|
+
binding("work", "10.241.0.1", "veth"),
|
|
54
|
+
binding("vpn", "10.242.0.1", "tun")
|
|
55
|
+
]);
|
|
56
|
+
value["scope"]["handoff"] = binding("handoff", "10.240.0.2", "veth");
|
|
57
|
+
for index in 0..2 {
|
|
58
|
+
value["scope"]["endpoints"][index]["interfaceIndex"] =
|
|
59
|
+
value["scope"]["links"][index]["interfaceIndex"].clone();
|
|
60
|
+
}
|
|
61
|
+
value["scope"]["protection"] = protection();
|
|
62
|
+
let mut generation = allocation();
|
|
63
|
+
generation["conntrackZone"] = json!(17);
|
|
64
|
+
generation["conntrackLabel"] = json!("a".repeat(32));
|
|
65
|
+
generation["grants"] = json!([
|
|
66
|
+
{"sourceEndpoint":"work","sourcePrefix":"10.241.0.2/32","destination":{"kind":"public","prefix":"203.0.113.2/32"},
|
|
67
|
+
"protocol":"udp","sourcePort":null,"destinationPort":42000,"sourcePortRange":{"first":10000,"last":10015}},
|
|
68
|
+
{"sourceEndpoint":null,"sourcePrefix":"10.240.0.2/32","destination":{"kind":"platform","endpointId":"dns"},
|
|
69
|
+
"protocol":"udp","sourcePort":null,"destinationPort":5300,"sourcePortRange":{"first":10000,"last":10015}},
|
|
70
|
+
{"sourceEndpoint":"work","sourcePrefix":"10.241.0.2/32","destination":{"kind":"platform","endpointId":"mail"},
|
|
71
|
+
"protocol":"tcp","sourcePort":null,"destinationPort":3000,"sourcePortRange":{"first":10016,"last":10031}},
|
|
72
|
+
{"sourceEndpoint":null,"sourcePrefix":"10.240.0.2/32","destination":{"kind":"platform","endpointId":"mail"},
|
|
73
|
+
"protocol":"tcp","sourcePort":null,"destinationPort":3000,"sourcePortRange":{"first":10016,"last":10031}}
|
|
74
|
+
]);
|
|
75
|
+
value["scope"]["generations"] = json!([generation]);
|
|
76
|
+
value["scope"]["rules"].as_array_mut().unwrap().push(json!({
|
|
77
|
+
"sourceEndpoint":"vpn","destinationEndpoint":"work","sourcePrefix":"10.242.0.2/32","destinationPrefix":"10.241.0.2/32",
|
|
78
|
+
"protocol":"udp","sourcePort":null,"destinationPort":53}));
|
|
79
|
+
serde_json::from_value(value).unwrap()
|
|
80
|
+
}
|
|
81
|
+
pub(super) fn host_policy() -> egress::Policy {
|
|
82
|
+
let mut value = egress::tests::host();
|
|
83
|
+
value["scope"]["protection"] = protection();
|
|
84
|
+
value["scope"]["handoffs"][0]["link"] = binding("router", "10.240.0.1", "veth");
|
|
85
|
+
value["scope"]["handoffs"][0]["allocations"] = json!([allocation()]);
|
|
86
|
+
value["scope"]["uplink"] = binding("uplink", "192.0.2.2", "veth");
|
|
87
|
+
serde_json::from_value(value).unwrap()
|
|
88
|
+
}
|
|
89
|
+
pub(super) fn socket(ns: &str, address: &'static str) -> UdpSocket {
|
|
90
|
+
let socket = in_namespace(ns, move || UdpSocket::bind(address).unwrap());
|
|
91
|
+
socket
|
|
92
|
+
.set_read_timeout(Some(Duration::from_millis(250)))
|
|
93
|
+
.unwrap();
|
|
94
|
+
socket
|
|
95
|
+
}
|
|
96
|
+
pub(super) fn roundtrip(source: &UdpSocket, target: &UdpSocket) -> SocketAddr {
|
|
97
|
+
source
|
|
98
|
+
.send_to(b"query", target.local_addr().unwrap())
|
|
99
|
+
.unwrap();
|
|
100
|
+
let mut bytes = [0; 64];
|
|
101
|
+
let (size, observed) = target
|
|
102
|
+
.recv_from(&mut bytes)
|
|
103
|
+
.expect("authorized request delivery");
|
|
104
|
+
assert_eq!(&bytes[..size], b"query");
|
|
105
|
+
target.send_to(b"reply", observed).unwrap();
|
|
106
|
+
assert_eq!(source.recv(&mut bytes).expect("labelled reply delivery"), 5);
|
|
107
|
+
assert_eq!(&bytes[..5], b"reply");
|
|
108
|
+
observed
|
|
109
|
+
}
|
|
110
|
+
pub(super) fn denied(source: &UdpSocket, target: &UdpSocket, local: bool) {
|
|
111
|
+
let sent = source.send_to(b"denied", target.local_addr().unwrap());
|
|
112
|
+
if local {
|
|
113
|
+
assert_eq!(sent.unwrap_err().raw_os_error(), Some(libc::EPERM));
|
|
114
|
+
} else {
|
|
115
|
+
assert_eq!(sent.unwrap(), 6);
|
|
116
|
+
}
|
|
117
|
+
let error = target.recv(&mut [0; 64]).unwrap_err();
|
|
118
|
+
assert!(matches!(
|
|
119
|
+
error.kind(),
|
|
120
|
+
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
|
|
121
|
+
));
|
|
122
|
+
}
|
|
123
|
+
fn tun() -> std::fs::File {
|
|
124
|
+
let file = std::fs::OpenOptions::new()
|
|
125
|
+
.read(true)
|
|
126
|
+
.write(true)
|
|
127
|
+
.open("/dev/net/tun")
|
|
128
|
+
.unwrap();
|
|
129
|
+
let mut request: libc::ifreq = unsafe { std::mem::zeroed() };
|
|
130
|
+
for (slot, byte) in request.ifr_name.iter_mut().zip(b"vpn") {
|
|
131
|
+
*slot = *byte as libc::c_char;
|
|
132
|
+
}
|
|
133
|
+
request.ifr_ifru.ifru_flags = (libc::IFF_TUN | libc::IFF_NO_PI) as libc::c_short;
|
|
134
|
+
assert_eq!(
|
|
135
|
+
unsafe { libc::ioctl(file.as_raw_fd(), libc::TUNSETIFF, &request) },
|
|
136
|
+
0
|
|
137
|
+
);
|
|
138
|
+
file
|
|
139
|
+
}
|
|
140
|
+
fn inject(device: &mut std::fs::File, destination: [u8; 4], port: u16) {
|
|
141
|
+
let mut packet = vec![0; 32];
|
|
142
|
+
packet[0] = 0x45;
|
|
143
|
+
packet[2..4].copy_from_slice(&32_u16.to_be_bytes());
|
|
144
|
+
packet[8] = 64;
|
|
145
|
+
packet[9] = 17;
|
|
146
|
+
packet[12..16].copy_from_slice(&[10, 242, 0, 2]);
|
|
147
|
+
packet[16..20].copy_from_slice(&destination);
|
|
148
|
+
let sum: u32 = packet[..20]
|
|
149
|
+
.chunks_exact(2)
|
|
150
|
+
.map(|part| u16::from_be_bytes(part.try_into().unwrap()) as u32)
|
|
151
|
+
.sum();
|
|
152
|
+
let sum = (sum & 0xffff) + (sum >> 16);
|
|
153
|
+
packet[10..12].copy_from_slice(&(!(sum as u16)).to_be_bytes());
|
|
154
|
+
packet[20..22].copy_from_slice(&23456_u16.to_be_bytes());
|
|
155
|
+
packet[22..24].copy_from_slice(&port.to_be_bytes());
|
|
156
|
+
packet[24..26].copy_from_slice(&12_u16.to_be_bytes());
|
|
157
|
+
packet[28..].copy_from_slice(b"test");
|
|
158
|
+
device.write_all(&packet).unwrap();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
#[ignore = "requires disposable isolated native qualification guest"]
|
|
163
|
+
fn v2_four_namespace_private_tun_local_dns_egress_and_revocation() {
|
|
164
|
+
isolated();
|
|
165
|
+
for name in [WORK, ROUTER, HOST, PEER] {
|
|
166
|
+
ip(&["netns", "add", name]);
|
|
167
|
+
ns_ip(name, &["link", "set", "lo", "up"]);
|
|
168
|
+
in_namespace(name, || {
|
|
169
|
+
std::fs::write("/proc/sys/net/ipv4/ip_forward", "1").unwrap()
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
connect(
|
|
173
|
+
WORK,
|
|
174
|
+
"client",
|
|
175
|
+
"10.241.0.2/24",
|
|
176
|
+
ROUTER,
|
|
177
|
+
"work",
|
|
178
|
+
"10.241.0.1/24",
|
|
179
|
+
);
|
|
180
|
+
connect(
|
|
181
|
+
ROUTER,
|
|
182
|
+
"handoff",
|
|
183
|
+
"10.240.0.2/30",
|
|
184
|
+
HOST,
|
|
185
|
+
"router",
|
|
186
|
+
"10.240.0.1/30",
|
|
187
|
+
);
|
|
188
|
+
connect(
|
|
189
|
+
HOST,
|
|
190
|
+
"uplink",
|
|
191
|
+
"192.0.2.2/24",
|
|
192
|
+
PEER,
|
|
193
|
+
"underlay",
|
|
194
|
+
"192.0.2.3/24",
|
|
195
|
+
);
|
|
196
|
+
ns_ip(WORK, &["route", "add", "default", "via", "10.241.0.1"]);
|
|
197
|
+
ns_ip(ROUTER, &["route", "add", "default", "via", "10.240.0.1"]);
|
|
198
|
+
ns_ip(
|
|
199
|
+
HOST,
|
|
200
|
+
&["route", "add", "203.0.113.2/32", "via", "192.0.2.3"],
|
|
201
|
+
);
|
|
202
|
+
ns_ip(PEER, &["address", "add", "203.0.113.2/32", "dev", "lo"]);
|
|
203
|
+
ns_ip(PEER, &["route", "add", "default", "via", "192.0.2.2"]);
|
|
204
|
+
ns_ip(ROUTER, &["tuntap", "add", "dev", "vpn", "mode", "tun"]);
|
|
205
|
+
ns_ip(ROUTER, &["address", "add", "10.242.0.1/24", "dev", "vpn"]);
|
|
206
|
+
ns_ip(ROUTER, &["link", "set", "vpn", "up"]);
|
|
207
|
+
let mut device = in_namespace(ROUTER, tun);
|
|
208
|
+
let (host_owner, host_applied) = in_namespace(HOST, || {
|
|
209
|
+
let mut owner = Owner::new(options("host_packets")).unwrap();
|
|
210
|
+
let applied = owner
|
|
211
|
+
.reconcile(Transition {
|
|
212
|
+
previous: None,
|
|
213
|
+
target: host_policy().prepare().unwrap().into(),
|
|
214
|
+
})
|
|
215
|
+
.unwrap();
|
|
216
|
+
assert!(owner.inspect().enforced);
|
|
217
|
+
(owner, applied)
|
|
218
|
+
});
|
|
219
|
+
let (router_owner, router_applied, router_policy) = in_namespace(ROUTER, || {
|
|
220
|
+
let policy = router_policy();
|
|
221
|
+
let mut owner = Owner::new(options("router_packets")).unwrap();
|
|
222
|
+
let applied = owner
|
|
223
|
+
.reconcile(Transition {
|
|
224
|
+
previous: None,
|
|
225
|
+
target: policy.clone().prepare().unwrap().into(),
|
|
226
|
+
})
|
|
227
|
+
.unwrap();
|
|
228
|
+
assert!(owner.inspect().enforced);
|
|
229
|
+
(owner, applied, policy)
|
|
230
|
+
});
|
|
231
|
+
let work = socket(WORK, "10.241.0.2:40001");
|
|
232
|
+
let local = socket(ROUTER, "10.240.0.2:40002");
|
|
233
|
+
let private_dns = socket(ROUTER, "10.241.0.1:53");
|
|
234
|
+
let public = socket(PEER, "203.0.113.2:42000");
|
|
235
|
+
let upstream = socket(PEER, "192.0.2.3:5300");
|
|
236
|
+
let tunnel_target = socket(WORK, "10.241.0.2:53");
|
|
237
|
+
let observer = in_namespace(HOST, || packet_fixture::PacketSocket::open("router"));
|
|
238
|
+
assert_eq!(
|
|
239
|
+
roundtrip(&work, &private_dns).ip().to_string(),
|
|
240
|
+
"10.241.0.2"
|
|
241
|
+
);
|
|
242
|
+
let outer = roundtrip(&work, &public);
|
|
243
|
+
assert_eq!(outer.ip().to_string(), "192.0.2.2");
|
|
244
|
+
assert!((10000..=10015).contains(&outer.port()));
|
|
245
|
+
let inner = observer
|
|
246
|
+
.matching(|packet| {
|
|
247
|
+
packet.len() >= 28
|
|
248
|
+
&& packet[9] == 17
|
|
249
|
+
&& packet[12..16] == [10, 240, 0, 2]
|
|
250
|
+
&& packet[22..24] == 42000_u16.to_be_bytes()
|
|
251
|
+
})
|
|
252
|
+
.unwrap();
|
|
253
|
+
assert!((10000..=10015).contains(&u16::from_be_bytes(inner[20..22].try_into().unwrap())));
|
|
254
|
+
let dns_outer = roundtrip(&local, &upstream);
|
|
255
|
+
assert_eq!(dns_outer.ip().to_string(), "192.0.2.2");
|
|
256
|
+
assert!((10000..=10015).contains(&dns_outer.port()));
|
|
257
|
+
// A real TCP socket exercises the same ranged handoff and labelled reply path.
|
|
258
|
+
in_namespace(WORK, || {
|
|
259
|
+
std::fs::write("/proc/sys/net/ipv4/tcp_ecn", "1").unwrap()
|
|
260
|
+
});
|
|
261
|
+
let listener = in_namespace(PEER, || TcpListener::bind("192.0.2.3:3000").unwrap());
|
|
262
|
+
let mut client = in_namespace(WORK, || {
|
|
263
|
+
TcpStream::connect_timeout(
|
|
264
|
+
&"192.0.2.3:3000".parse().unwrap(),
|
|
265
|
+
Duration::from_secs(1),
|
|
266
|
+
)
|
|
267
|
+
.unwrap()
|
|
268
|
+
});
|
|
269
|
+
let (mut server, peer) = listener.accept().unwrap();
|
|
270
|
+
assert_eq!(peer.ip().to_string(), "192.0.2.2");
|
|
271
|
+
assert!((10016..=10031).contains(&peer.port()));
|
|
272
|
+
client
|
|
273
|
+
.set_read_timeout(Some(Duration::from_secs(1)))
|
|
274
|
+
.unwrap();
|
|
275
|
+
server
|
|
276
|
+
.set_read_timeout(Some(Duration::from_secs(1)))
|
|
277
|
+
.unwrap();
|
|
278
|
+
client.write_all(b"mail").unwrap();
|
|
279
|
+
server.read_exact(&mut [0; 4]).unwrap();
|
|
280
|
+
server.write_all(b"okay").unwrap();
|
|
281
|
+
client.read_exact(&mut [0; 4]).unwrap();
|
|
282
|
+
let syn = observer
|
|
283
|
+
.matching(|packet| {
|
|
284
|
+
packet.len() >= 40
|
|
285
|
+
&& packet[9] == 6
|
|
286
|
+
&& packet[12..16] == [10, 240, 0, 2]
|
|
287
|
+
&& packet[33] & 0x17 == 2
|
|
288
|
+
})
|
|
289
|
+
.unwrap();
|
|
290
|
+
assert_eq!(syn[33] & 0xc0, 0xc0);
|
|
291
|
+
assert!((10016..=10031).contains(&u16::from_be_bytes(syn[20..22].try_into().unwrap())));
|
|
292
|
+
let mut local_tcp = in_namespace(ROUTER, || {
|
|
293
|
+
TcpStream::connect_timeout(
|
|
294
|
+
&"192.0.2.3:3000".parse().unwrap(),
|
|
295
|
+
Duration::from_secs(1),
|
|
296
|
+
)
|
|
297
|
+
.unwrap()
|
|
298
|
+
});
|
|
299
|
+
let (mut local_peer, local_outer) = listener.accept().unwrap();
|
|
300
|
+
assert!((10016..=10031).contains(&local_outer.port()));
|
|
301
|
+
local_tcp
|
|
302
|
+
.set_read_timeout(Some(Duration::from_secs(1)))
|
|
303
|
+
.unwrap();
|
|
304
|
+
local_peer
|
|
305
|
+
.set_read_timeout(Some(Duration::from_secs(1)))
|
|
306
|
+
.unwrap();
|
|
307
|
+
local_tcp.write_all(b"relay").unwrap();
|
|
308
|
+
local_peer.read_exact(&mut [0; 5]).unwrap();
|
|
309
|
+
local_peer.write_all(b"ready").unwrap();
|
|
310
|
+
local_tcp.read_exact(&mut [0; 5]).unwrap();
|
|
311
|
+
// An unlabelled opening ACK/RST must not acquire the current generation.
|
|
312
|
+
let injector = in_namespace(WORK, || packet_fixture::PacketSocket::open("client"));
|
|
313
|
+
let router_mac = in_namespace(ROUTER, || packet_fixture::mac("work"));
|
|
314
|
+
for (port, flags) in [(40050, 0x10), (40051, 0x04), (40052, 0x12)] {
|
|
315
|
+
injector.send(
|
|
316
|
+
router_mac,
|
|
317
|
+
&packet_fixture::tcp([10, 241, 0, 2], [192, 0, 2, 3], port, 3000, flags),
|
|
318
|
+
);
|
|
319
|
+
assert!(observer
|
|
320
|
+
.matching(|packet| packet.len() >= 40
|
|
321
|
+
&& packet[9] == 6
|
|
322
|
+
&& packet[12..16] == [10, 240, 0, 2]
|
|
323
|
+
&& packet[33] == flags
|
|
324
|
+
&& packet[24..28] == 100_u32.to_be_bytes())
|
|
325
|
+
.is_none());
|
|
326
|
+
}
|
|
327
|
+
injector.send(
|
|
328
|
+
router_mac,
|
|
329
|
+
&packet_fixture::tcp([10, 241, 0, 2], [192, 0, 2, 3], 40053, 3000, 0xc2),
|
|
330
|
+
);
|
|
331
|
+
assert!(observer
|
|
332
|
+
.matching(|packet| packet.len() >= 40
|
|
333
|
+
&& packet[9] == 6
|
|
334
|
+
&& packet[12..16] == [10, 240, 0, 2]
|
|
335
|
+
&& packet[33] == 0xc2
|
|
336
|
+
&& packet[24..28] == 100_u32.to_be_bytes())
|
|
337
|
+
.is_some());
|
|
338
|
+
// Reassembly must apply the same directed grant; a noninitial fragment alone
|
|
339
|
+
// cannot obtain an independent path around transport/zone classification.
|
|
340
|
+
let fragments = packet_fixture::fragments(
|
|
341
|
+
&packet_fixture::udp([10, 241, 0, 2], [203, 0, 113, 2], 40001, 42000),
|
|
342
|
+
100,
|
|
343
|
+
);
|
|
344
|
+
injector.send(router_mac, &fragments[1]);
|
|
345
|
+
assert!(public.recv(&mut [0; 64]).is_err());
|
|
346
|
+
injector.send(router_mac, &fragments[0]);
|
|
347
|
+
let mut received = [0; 64];
|
|
348
|
+
assert_eq!(public.recv(&mut received).unwrap(), 4);
|
|
349
|
+
assert_eq!(&received[..4], b"frag");
|
|
350
|
+
for fragment in packet_fixture::fragments(
|
|
351
|
+
&packet_fixture::udp([10, 241, 0, 2], [192, 0, 2, 3], 40001, 5300),
|
|
352
|
+
101,
|
|
353
|
+
) {
|
|
354
|
+
injector.send(router_mac, &fragment);
|
|
355
|
+
}
|
|
356
|
+
assert!(upstream.recv(&mut [0; 64]).is_err());
|
|
357
|
+
// Exhaust the whole 16-port UDP range to the same remote tuple. No mapping
|
|
358
|
+
// may escape the lease; the next flow must fail closed in source NAT.
|
|
359
|
+
let mut held = Vec::new();
|
|
360
|
+
let mut observed_ports = std::collections::BTreeSet::from([outer.port()]);
|
|
361
|
+
for source_port in 40100..40115 {
|
|
362
|
+
let source = in_namespace(WORK, move || {
|
|
363
|
+
UdpSocket::bind(format!("10.241.0.2:{source_port}")).unwrap()
|
|
364
|
+
});
|
|
365
|
+
source
|
|
366
|
+
.set_read_timeout(Some(Duration::from_millis(250)))
|
|
367
|
+
.unwrap();
|
|
368
|
+
assert!(observed_ports.insert(roundtrip(&source, &public).port()));
|
|
369
|
+
held.push(source);
|
|
370
|
+
}
|
|
371
|
+
assert_eq!(observed_ports, (10000..=10015).collect());
|
|
372
|
+
let exhausted = socket(WORK, "10.241.0.2:40115");
|
|
373
|
+
denied(&exhausted, &public, false);
|
|
374
|
+
denied(&work, &upstream, false);
|
|
375
|
+
denied(&local, &public, true);
|
|
376
|
+
inject(&mut device, [10, 241, 0, 2], 53);
|
|
377
|
+
assert_eq!(tunnel_target.recv(&mut [0; 64]).unwrap(), 4);
|
|
378
|
+
inject(&mut device, [203, 0, 113, 2], 42000);
|
|
379
|
+
assert!(public.recv(&mut [0; 64]).is_err());
|
|
380
|
+
let (owner, applied, template) = in_namespace(ROUTER, move || {
|
|
381
|
+
let mut owner = router_owner;
|
|
382
|
+
let mut revoked = router_policy.clone();
|
|
383
|
+
revoked.revision = 2;
|
|
384
|
+
let egress::Scope::RouterEgress(scope) = &mut revoked.scope else {
|
|
385
|
+
panic!()
|
|
386
|
+
};
|
|
387
|
+
scope.generations.clear();
|
|
388
|
+
let applied = owner
|
|
389
|
+
.reconcile(Transition {
|
|
390
|
+
previous: Some(router_applied),
|
|
391
|
+
target: revoked.prepare().unwrap().into(),
|
|
392
|
+
})
|
|
393
|
+
.unwrap();
|
|
394
|
+
assert!(owner.inspect().enforced);
|
|
395
|
+
(owner, applied, router_policy)
|
|
396
|
+
});
|
|
397
|
+
public.send_to(b"retired", outer).unwrap();
|
|
398
|
+
assert!(work.recv(&mut [0; 64]).is_err());
|
|
399
|
+
denied(&work, &public, false);
|
|
400
|
+
denied(&local, &upstream, true);
|
|
401
|
+
roundtrip(&work, &private_dns);
|
|
402
|
+
inject(&mut device, [10, 241, 0, 2], 53);
|
|
403
|
+
assert_eq!(tunnel_target.recv(&mut [0; 64]).unwrap(), 4);
|
|
404
|
+
// Retain the old host allocation, install a disjoint new range, then enable
|
|
405
|
+
// the new router generation. No conntrack drainage or reuse is asserted.
|
|
406
|
+
let (host_owner, host_applied) = in_namespace(HOST, move || {
|
|
407
|
+
let mut owner = host_owner;
|
|
408
|
+
let mut policy = host_policy();
|
|
409
|
+
policy.revision = 2;
|
|
410
|
+
let egress::Scope::HostTransit(scope) = &mut policy.scope else {
|
|
411
|
+
panic!()
|
|
412
|
+
};
|
|
413
|
+
let mut next = scope.handoffs[0].allocations[0].clone();
|
|
414
|
+
next.lease.generation = 2;
|
|
415
|
+
next.lease.digest = format!("sha256:{}", "d".repeat(64));
|
|
416
|
+
for range in &mut next.source_port_ranges {
|
|
417
|
+
range.first += 1000;
|
|
418
|
+
range.last += 1000;
|
|
419
|
+
}
|
|
420
|
+
scope.handoffs[0].allocations.push(next);
|
|
421
|
+
let applied = owner
|
|
422
|
+
.reconcile(Transition {
|
|
423
|
+
previous: Some(host_applied),
|
|
424
|
+
target: policy.prepare().unwrap().into(),
|
|
425
|
+
})
|
|
426
|
+
.unwrap();
|
|
427
|
+
assert!(owner.inspect().enforced);
|
|
428
|
+
(owner, applied)
|
|
429
|
+
});
|
|
430
|
+
let (mut owner, applied) = in_namespace(ROUTER, move || {
|
|
431
|
+
let mut owner = owner;
|
|
432
|
+
let mut policy = template;
|
|
433
|
+
policy.revision = 3;
|
|
434
|
+
let egress::Scope::RouterEgress(scope) = &mut policy.scope else {
|
|
435
|
+
panic!()
|
|
436
|
+
};
|
|
437
|
+
let generation = &mut scope.generations[0];
|
|
438
|
+
generation.lease.generation = 2;
|
|
439
|
+
generation.lease.digest = format!("sha256:{}", "d".repeat(64));
|
|
440
|
+
generation.conntrack_zone = 18;
|
|
441
|
+
generation.conntrack_label = "f".repeat(32);
|
|
442
|
+
for range in &mut generation.source_port_ranges {
|
|
443
|
+
range.first += 1000;
|
|
444
|
+
range.last += 1000;
|
|
445
|
+
}
|
|
446
|
+
for grant in &mut generation.grants {
|
|
447
|
+
grant.source_port_range.first += 1000;
|
|
448
|
+
grant.source_port_range.last += 1000;
|
|
449
|
+
}
|
|
450
|
+
let applied = owner
|
|
451
|
+
.reconcile(Transition {
|
|
452
|
+
previous: Some(applied),
|
|
453
|
+
target: policy.prepare().unwrap().into(),
|
|
454
|
+
})
|
|
455
|
+
.unwrap();
|
|
456
|
+
assert!(owner.inspect().enforced);
|
|
457
|
+
(owner, applied)
|
|
458
|
+
});
|
|
459
|
+
let current_outer = roundtrip(&work, &public);
|
|
460
|
+
assert!((11000..=11015).contains(¤t_outer.port()));
|
|
461
|
+
roundtrip(&local, &upstream);
|
|
462
|
+
public.send_to(b"retired", outer).unwrap();
|
|
463
|
+
assert!(work.recv(&mut [0; 64]).is_err());
|
|
464
|
+
in_namespace(ROUTER, move || owner.release(applied).unwrap());
|
|
465
|
+
in_namespace(HOST, move || {
|
|
466
|
+
let mut owner = host_owner;
|
|
467
|
+
owner.release(host_applied).unwrap();
|
|
468
|
+
});
|
|
469
|
+
println!("V2_PACKET_PROOF private_dns=true tun_private=true tun_public_denied=true workload_public=true local_dns_and_tcp=true handoff_udp_tcp_ranges=true ecn_syn=true opening_ack_rst_denied=true fragments_checked=true range_exhaustion_denied=true protected_grant_denied=true ungranted_local_denied=true retired_replies_denied=true revoked_egress_denied=true disjoint_generation_restored=true flows_drained=false reusable=false");
|
|
470
|
+
}
|